trading-bot
npx skills add https://github.com/spot-canvas/sn --skill trading-bot
Agent 安装分布
Skill 文档
Trading Bot â Signal-Driven Architecture
Build a trading bot that consumes live signals from SignalNGN via sn signals, executes trades via ledger trades add, and enforces risk management locally.
Architecture
sn signals --json â Bot Process â ledger trades add (trade recording)
â â â
NATS stream Risk Management Loop ledger positions (state queries)
(periodic price check)
The bot is a single long-running process with two concurrent concerns:
- Signal loop â reads JSON lines from
sn signals --jsonstdout, decides whether to act, executes trades - Risk loop â periodically checks open positions against SL/TP/trailing stop/max hold time, closes positions that hit limits
Prerequisites: sn and ledger CLIs installed and configured. See the sn and ledger skills for setup. No other dependencies needed â sn provides signals AND live prices, ledger provides trade recording AND position queries.
Core Loop
Spawn sn signals --json as a subprocess. Read stdout line by line. Each line is a complete JSON signal.
while true:
proc = spawn("sn signals --json")
for line in proc.stdout:
signal = parse_json(line)
handle_signal(signal)
maybe_run_risk_check() # every N seconds between signals
# proc exited â wait, then restart
sleep(10)
refresh_config()
Key points:
sn signalsmaintains the NATS connection internally and handles auth- Signals have a 2-minute TTL in NATS headers â stale signals are never delivered
- If the subprocess exits (network blip, server restart), restart it with backoff
- On restart, refresh the trading config â it may have changed server-side
Signal Payload
Each JSON line from sn signals --json:
{
"exchange": "coinbase",
"product": "BTC-USD",
"granularity": "FIVE_MINUTES",
"strategy": "ml_xgboost",
"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": "Strong bullish signal across 38 features",
"position_pct": 0.25,
"market": "futures",
"leverage": 2,
"indicators": { "rsi": 58.3, "macd_hist": 0.0042, "sma50": 96800, "sma200": 91200 },
"timestamp": 1740218400
}
| Field | Use |
|---|---|
action |
BUY (open long), SELL (close long), SHORT (open short), COVER (close short) |
confidence |
0â1. Filter by threshold (e.g. only act on ⥠0.72) |
price |
Candle close at signal time. Use as entry price |
stop_loss / take_profit |
Strategy-suggested levels. Validate before using (see Risk Management) |
position_pct |
Kelly-derived sizing (0 = no recommendation, use fixed sizing) |
risk_reasoning |
Human-readable explanation of SL/TP rationale |
market |
"spot" or "futures" |
Trading Config
Fetch the server-side trading config to know which products and strategies are active.
sn trading list --enabled --json
Returns an array of config objects. Build a lookup map keyed by product_id:
{
"BTC-USD": {
"exchange": "coinbase",
"granularity": "FIVE_MINUTES",
"long_leverage": 2,
"short_leverage": 2,
"strategies_long": ["ml_xgboost"],
"strategies_short": ["ml_xgboost"],
"strategies_spot": []
}
}
Strategy Filtering
The engine appends direction suffixes to strategy names when publishing signals:
| Config name | Signal strategy field |
|---|---|
ml_xgboost |
ml_xgboost (long) or ml_xgboost+trend |
ml_xgboost |
ml_xgboost_short or ml_xgboost_short+bearish |
user:bb_squeeze |
user:bb_squeeze or user:bb_squeeze+trend |
Use prefix matching: a signal’s strategy matches a config entry if it equals the entry OR starts with entry+ or entry_. This is critical â exact matching will miss most signals.
for allowed in config_strategies:
if signal.strategy == allowed
or signal.strategy.startswith(allowed + "+")
or signal.strategy.startswith(allowed + "_"):
â match
Ignore signals from strategies not in the config for that product.
Trade Execution
Opening a Position
- Check for existing position:
ledger positions <account> --jsonâ skip if already have an open position for this symbol + side - Calculate size: Use
position_pct(Kelly) if > 0, otherwise fixed percentage of portfolio - Calculate quantity:
size_usd / price - Validate SL/TP: See Risk Management section
- Record via ledger:
ledger trades add <account> --symbol ... --side ... --quantity ... --price ... - Save position state: Persist SL/TP/trailing stop data locally for the risk loop
# Check existing position
ledger positions paper --json | jq '.[] | select(.symbol == "BTC-USD" and .side == "long" and .status == "open")'
# Record entry trade
ledger trades add paper \
--symbol BTC-USD --side buy --quantity 0.015 --price 98500.50 \
--fee 0.30 --market-type futures --leverage 2 --margin 750.00 \
--strategy ml_xgboost --confidence 0.82 \
--entry-reason "Strong bullish signal across 38 features" \
--stop-loss 97200 --take-profit 100800
side:"buy"for long entry or short exit,"sell"for short entry or long exitfee: Estimate â 0.02% for futures, 0.1% for spotleverageandmargin: Only for futures.margin = size_usd / leverage- Trade ID is auto-generated. Pass
--trade-idto set a specific one (idempotent)
Closing a Position
- Get position details:
ledger portfolio <account> --jsonâ find the position by symbol + side - Use the position’s
avg_entry_priceandquantityfor P&L calculation - Record the closing trade with the opposite side (
sellto close long,buyto close short) - Include
--exit-reason(e.g. “ð STOP LOSS”, “ð¯ TARGET HIT”, “ð TRAILING STOP”) - Clean up local position state
ledger trades add paper \
--symbol BTC-USD --side sell --quantity 0.015 --price 97180.00 \
--fee 0.30 --market-type futures \
--exit-reason "ð STOP LOSS"
Position Sizing
if signal.position_pct > 0:
base_size = portfolio_value à position_pct # Kelly-derived
else:
base_size = portfolio_value à fixed_pct # e.g. 15%
size = clamp(base_size, min=150, max=2000) # USD bounds
quantity = size / price
- Kelly sizing (
position_pct): The signal provides a fraction of capital to risk, derived from the strategy’s historical win rate and payoff ratio - Fixed sizing: Fallback when no Kelly recommendation. 10â20% of portfolio per position is typical
- Bounds: Enforce min/max to avoid micro-positions or overexposure
Risk Management
See references/risk-management.md for full implementation details.
Summary of the risk management stack:
| Layer | Trigger | Default |
|---|---|---|
| Stop Loss | Price hits SL level | -3% post-leverage |
| Take Profit | Price hits TP level | +6% post-leverage |
| Trailing Stop | Activates at +2% P&L, trails 1.5% behind peak | â |
| Max Hold | Position held > N hours | 72 hours |
SL/TP Validation
Always validate the signal’s SL/TP before using them:
sl_valid = stop_loss > 0 AND |stop_loss - price| / price > 0.001 # >0.1% from entry
tp_valid = take_profit > 0 AND |take_profit - price| / price > 0.001
if not sl_valid â use default SL (entry ± 3%/leverage)
if not tp_valid â use default TP (entry ± 6%/leverage)
This catches degenerate cases where SL/TP equal the entry price.
Risk Loop
Run every 5 minutes (or between signal processing). For each open position:
- Fetch current price
- Calculate P&L percentage (accounting for leverage and direction)
- Update trailing stop if P&L exceeds activation threshold
- Check all exit conditions (SL â trailing â TP â max hold)
- Close position if any condition triggers
Cooldowns
Prevent duplicate trades from rapid-fire signals on the same product:
key = f"{product}:{strategy}:{action}"
if last_signal[key] was < 5 minutes ago â skip
5 minutes is a sensible default for 5-minute candle strategies.
Daemon Management
For production, run the bot as a managed process that auto-restarts on failure.
macOS (launchd):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.trading-bot</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/path/to/trading_bot.py</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:~/go/bin</string>
</dict>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/path/to/bot.log</string>
<key>StandardErrorPath</key>
<string>/path/to/bot.log</string>
<key>WorkingDirectory</key>
<string>/path/to/workspace</string>
</dict>
</plist>
Control:
launchctl load ~/Library/LaunchAgents/com.example.trading-bot.plist # start
launchctl unload ~/Library/LaunchAgents/com.example.trading-bot.plist # stop
launchctl stop com.example.trading-bot # restart (KeepAlive relaunches)
Linux (systemd): Standard user service unit with Restart=always.
Notifications
Send trade notifications to Telegram (or any messaging channel) on:
- Position opened â include entry price, size, SL/TP, strategy, indicators
- Position closed â include exit price, P&L (% and USD), reason
- Risk management closes â include which condition triggered (ð SL, ð¯ TP, ð trailing, â° max hold)
Use OpenClaw’s message send for notifications, or direct Telegram Bot API.
Portfolio Value Tracking
Track the live portfolio value as:
portfolio_value = starting_capital + total_realized_pnl + total_unrealized_pnl
starting_capital: Initial deposit (e.g. $10,000)total_realized_pnl: Fromledger portfolio <account>âtotal_realized_pnlfield (sum of all closed position P&L)total_unrealized_pnl: Sum of(current_price - entry) / entry à leveragefor each open position, multiplied by position cost basis
Display this on dashboards instead of a static starting balance. Color green when above starting capital, red when below.
State Files
The bot maintains local state files alongside the script:
| File | Purpose |
|---|---|
.position_state.json |
SL/TP/trailing stop levels, peak P&L, open time per position |
.exit_reasons.json |
Why each position was closed (for dashboard display) |
These are the bot’s local runtime state â the ledger is the source of truth for actual positions and trades. If state files are lost, the bot reconstructs defaults from ledger positions on next risk check.
Checklist for a New Bot
- Install
snandledgerCLIs, runsn auth login - Set up trading config:
sn trading set <exchange> <product> --granularity ... --long ... --short ... --enable - Reload engine:
sn trading reload - Implement signal loop (spawn
sn signals --json, read lines) - Implement strategy filter (prefix match against trading config)
- Implement trade execution (position check â size â
ledger trades add) - Implement SL/TP validation (reject degenerate values, fall back to defaults)
- Implement risk management loop (periodic price check â SL/TP/trailing/max hold)
- Implement cooldowns (prevent duplicate signals)
- Implement subprocess auto-restart with backoff
- Set up as a managed daemon (launchd/systemd)
- Add notifications (Telegram or other channel)
- Test with paper account before going live