Listening to Trading Signals with sn signals
sn signals subscribes to the live NATS signal stream and prints one JSON object per signal to stdout. This is the primary input loop for a trading bot agent.
Prerequisites
sn must be installed and configured:
Install
brew tap spot-canvas/sn https://github.com/Spot-Canvas/sn brew install spot-canvas/sn/sn
Configure (only needed if pointing at a non-default server)
sn config set api_url https://signalngn-api-potbdcvufa-ew.a.run.app
No NATS credentials setup is needed — sn signals uses embedded read-only credentials by default.
Basic Usage
All signals, all products, all strategies
sn signals --json
Filter to one product
sn signals --json --exchange coinbase --product BTC-USD
Filter to one granularity
sn signals --json --exchange coinbase --product BTC-USD --granularity ONE_HOUR
Filter to one strategy
sn signals --json --exchange coinbase --product BTC-USD --strategy macd_momentum
Combine filters
sn signals --json --exchange coinbase --product BTC-USD --granularity ONE_HOUR --strategy macd_momentum
Granularity values: ONE_MINUTE , FIVE_MINUTES , FIFTEEN_MINUTES , THIRTY_MINUTES , ONE_HOUR , TWO_HOURS , SIX_HOURS , ONE_DAY
Press Ctrl-C to stop.
Signal Payload (JSON)
Each line of sn signals --json output is a JSON object with this shape:
{ "exchange": "coinbase", "product": "BTC-USD", "granularity": "ONE_HOUR", "strategy": "macd_momentum", "action": "BUY", "confidence": 0.82, "price": 98500.50, "stop_loss": 97200.00, "take_profit": 100800.00, "risk_reasoning": "ATR-based stop 1.3% below entry", "reason": "MACD crossed above signal line with bullish momentum", "position_pct": 0.25, "market": "spot", "leverage": 1, "indicators": { "rsi": 58.3, "macd_hist": 0.0042, "sma50": 96800.0, "sma200": 91200.0 }, "timestamp": 1740218400 }
Field Reference
Field Type Description
exchange
string Always "coinbase" currently
product
string e.g. "BTC-USD" , "ETH-USD"
granularity
string Candle timeframe the strategy evaluated
strategy
string Strategy that produced the signal
action
string "BUY" , "SELL" , "SHORT" , or "COVER"
confidence
float 0.0–1.0. Higher = stronger conviction
price
float Candle close price at signal time
stop_loss
float Suggested stop-loss price (0 if not set)
take_profit
float Suggested take-profit price (0 if not set)
risk_reasoning
string Why those SL/TP levels were chosen
reason
string Human-readable explanation of the signal
position_pct
float Fraction of capital suggested (0 = no recommendation)
market
string "spot" or "futures"
leverage
int 1 for spot; >1 for futures
indicators
object rsi , macd_hist , sma50 , sma200 at signal time
timestamp
int Unix seconds of the candle that triggered this signal
Action Semantics
Action Meaning
BUY
Open a long position (spot or futures-long)
SELL
Close a long position
SHORT
Open a short position (futures only)
COVER
Close a short position (futures only)
Important: never infer direction from the strategy name alone. ml_xgboost_short
is the short-engine instance of ml_xgboost and will only ever emit SHORT (or COVER ). It will never emit BUY . Always use the action field to determine what to do.
Trading Bot Patterns
Filter by confidence threshold
Only act on signals with confidence >= 0.7
sn signals --json --exchange coinbase --product BTC-USD |
jq 'select(.confidence >= 0.7)'
Filter to actionable signals only
Ignore any signal that has no stop_loss set
sn signals --json |
jq 'select(.action != "" and .stop_loss > 0)'
Watch a specific product for BUY signals
sn signals --json --product BTC-USD |
jq 'select(.action == "BUY") | {strategy, confidence, price, stop_loss, take_profit}'
Multi-strategy consensus (count BUYs in last batch)
Print each signal, accumulate in a loop and count
sn signals --json --product BTC-USD --granularity ONE_HOUR |
jq -r 'select(.action == "BUY") | [.strategy, .confidence] | @tsv'
Forward signals to a webhook
sn signals --json |
jq -c 'select(.confidence >= 0.6)' |
while read -r sig; do
curl -s -X POST https://my-bot/signal
-H "Content-Type: application/json"
-d "$sig"
done
NATS Subject Pattern
The underlying subject for every signal is:
signals.<exchange>.<product_id>.<granularity>.<strategy>
Examples:
signals.coinbase.BTC-USD.ONE_HOUR.macd_momentum signals.coinbase.ETH-USD.FIVE_MINUTES.rsi_mean_reversion signals.coinbase.BTC-USD.ONE_HOUR.ml_xgboost ← long instance (BUY only) signals.coinbase.BTC-USD.ONE_HOUR.ml_xgboost_short ← short instance (SHORT only) signals.coinbase.BTC-USD.ONE_HOUR.user.my-strategy
sn signals handles the subscription and wildcard expansion automatically based on your --exchange , --product , --granularity , --strategy flags.
Available Strategies
Built-in (always running)
Strategy Description
rsi_mean_reversion
RSI oversold/overbought
macd_momentum
MACD crossover
sma_trend
SMA 50/200 trend follow
combined_rsi_macd
RSI + MACD together
bollinger_breakout
Bollinger band breakout
bollinger_rsi
Bollinger + RSI
sma_macd
SMA + MACD
volume_momentum
Volume-weighted momentum
zscore_mean_reversion
Z-score reversion
alpha_beast
Multi-indicator composite
breakout
Price channel breakout
ML (paid tier)
Strategy Description
ml_xgboost
XGBoost model, 39-feature vector. Long-engine instance — emits BUY only
ml_xgboost_short
Same model, short-engine instance — emits SHORT only
ml_transformer
Transformer (attention-based) model. Long-engine instance — emits BUY only
ml_transformer_short
Same model, short-engine instance — emits SHORT only
ml_transformer_1h
Transformer model trained on 1-hour data. Long-engine instance — emits BUY only
ml_transformer_1h_short
Same model, short-engine instance — emits SHORT only
User Starlark strategies (paid tier)
Published as user.<strategy-name> . Managed via sn strategy commands.
ml_xgboost Signal Semantics
The XGBoost model outputs three probabilities on every candle: P(hold) , P(long) , P(short) . The engine creates two separate instances from the same model — one constrained to the long side, one to the short side — and publishes them on separate NATS subjects.
Long instance — strategy name ml_xgboost
Subject: signals.<exchange>.<product>.<granularity>.ml_xgboost
-
P(short) is zeroed before threshold comparison — the instance can only produce BUY .
-
If P(long) > confidence → publishes action: "BUY" .
-
stop_loss is set below entry price; take_profit above.
Short instance — strategy name ml_xgboost_short
Subject: signals.<exchange>.<product>.<granularity>.ml_xgboost_short
-
P(long) is zeroed before threshold comparison — the instance can only produce SHORT .
-
If P(short) > confidence → publishes action: "SHORT" .
-
stop_loss is set above entry price; take_profit below.
Why two instances?
Without direction constraints the model scores both sides on every candle. In a futures short engine a strong P(long) could leak a BUY signal which a bot might wrongly interpret as "open long". The constraint ensures:
-
ml_xgboost → only ever BUY
-
ml_xgboost_short → only ever SHORT
Both instances share the same confidence threshold and ATR-based SL/TP parameters configured in trading config (atr_stop_mult , rr_ratio , confidence ).
Subscribing to both directions
Long signals only
sn signals --json --strategy ml_xgboost
Short signals only
sn signals --json --strategy ml_xgboost_short
Both (use two subscriptions or no --strategy filter)
sn signals --json --product ADA-USD | jq 'select(.strategy | startswith("ml_xgboost"))'
Message TTL
Signals have a 2-minute TTL set in the NATS message header (Nats-Msg-Ttl: 2m ). Stale signals older than 2 minutes are automatically discarded and will never arrive at your subscriber. You will only ever receive fresh signals.
Credentials
By default sn signals uses embedded read-only credentials that can only subscribe to signals.> (publish is denied on all subjects). No additional setup is needed.
To use custom credentials (e.g. for a private NATS deployment):
sn config set nats_creds_file ~/.config/sn/my.creds
or per-run:
SN_NATS_CREDS_FILE=~/.config/sn/my.creds sn signals --json