trading-bot

📁 spot-canvas/sn 📅 3 days ago
4
总安装量
4
周安装量
#49069
全站排名
安装命令
npx skills add https://github.com/spot-canvas/sn --skill trading-bot

Agent 安装分布

openclaw 4
gemini-cli 4
claude-code 4
github-copilot 4
codex 4
kimi-cli 4

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:

  1. Signal loop — reads JSON lines from sn signals --json stdout, decides whether to act, executes trades
  2. 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 signals maintains 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

  1. Check for existing position: ledger positions <account> --json — skip if already have an open position for this symbol + side
  2. Calculate size: Use position_pct (Kelly) if > 0, otherwise fixed percentage of portfolio
  3. Calculate quantity: size_usd / price
  4. Validate SL/TP: See Risk Management section
  5. Record via ledger: ledger trades add <account> --symbol ... --side ... --quantity ... --price ...
  6. 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 exit
  • fee: Estimate — 0.02% for futures, 0.1% for spot
  • leverage and margin: Only for futures. margin = size_usd / leverage
  • Trade ID is auto-generated. Pass --trade-id to set a specific one (idempotent)

Closing a Position

  1. Get position details: ledger portfolio <account> --json → find the position by symbol + side
  2. Use the position’s avg_entry_price and quantity for P&L calculation
  3. Record the closing trade with the opposite side (sell to close long, buy to close short)
  4. Include --exit-reason (e.g. “🛑 STOP LOSS”, “🎯 TARGET HIT”, “🔒 TRAILING STOP”)
  5. 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:

  1. Fetch current price
  2. Calculate P&L percentage (accounting for leverage and direction)
  3. Update trailing stop if P&L exceeds activation threshold
  4. Check all exit conditions (SL → trailing → TP → max hold)
  5. 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: From ledger portfolio <account> → total_realized_pnl field (sum of all closed position P&L)
  • total_unrealized_pnl: Sum of (current_price - entry) / entry × leverage for 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

  1. Install sn and ledger CLIs, run sn auth login
  2. Set up trading config: sn trading set <exchange> <product> --granularity ... --long ... --short ... --enable
  3. Reload engine: sn trading reload
  4. Implement signal loop (spawn sn signals --json, read lines)
  5. Implement strategy filter (prefix match against trading config)
  6. Implement trade execution (position check → size → ledger trades add)
  7. Implement SL/TP validation (reject degenerate values, fall back to defaults)
  8. Implement risk management loop (periodic price check → SL/TP/trailing/max hold)
  9. Implement cooldowns (prevent duplicate signals)
  10. Implement subprocess auto-restart with backoff
  11. Set up as a managed daemon (launchd/systemd)
  12. Add notifications (Telegram or other channel)
  13. Test with paper account before going live