trading-bot

Trading Bot — Signal-Driven Architecture

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "trading-bot" with this command: npx skills add spot-canvas/sn/spot-canvas-sn-trading-bot

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 --json stdout, 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 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": [] } }

Product + Strategy Filtering

Critical: The signal stream contains signals for ALL tenants on the platform. The engine publishes signals from the union of all users' trading configs. Your bot must filter signals to only act on products and strategies in your own trading config. Without this filter, the bot will trade random products from other users' configs.

Two-layer filter (both required):

Product filter: Reject any signal whose product is not a key in your trading config map. This is the first check — if the product isn't in your config, drop the signal immediately.

Strategy filter: For products that pass the product filter, check that the signal's strategy matches one of the allowed strategies for that product.

Layer 1: Product must be in our config

cfg = trading_config.get(signal.product) if cfg is None: → reject (product not in our config)

Layer 2: Strategy must be allowed for this product

allowed = cfg.strategies_long + cfg.strategies_short + cfg.strategies_spot if not prefix_match(signal.strategy, allowed): → reject (strategy not allowed)

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

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 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

  • Get position details: ledger portfolio <account> --json → find the position by symbol + side

  • Use the position's avg_entry_price and quantity for P&L calculation

  • Record the closing trade with the opposite side (sell to close long, buy to 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 : 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.

Backtests

Use sn backtest to run and review historical strategy performance before going live.

Running a Backtest

sn backtest run
--exchange binance --product BTC-USD
--strategy ml_xgboost --granularity FIVE_MINUTES
--mode spot
--start 2025-01-01 --end 2025-12-31

Options: --mode spot|futures-long|futures-short , --leverage N , --trend-filter , --no-wait .

Strategy Params

Use --params key=value (repeatable) to override strategy parameters. The result always records the full effective params used — including defaults for anything not specified — so results are self-describing and comparable.

sn backtest run
--exchange binance --product BTC-USD
--strategy ml_xgboost --granularity FIVE_MINUTES
--params confidence=0.80
--params exit_confidence=0.40
--params rr_ratio=2.5
--params atr_stop_mult=1.5

Available params per strategy:

Strategy Params

ml_xgboost

confidence , exit_confidence , rr_ratio , atr_stop_mult

alpha_beast

rsi_buy_max , rsi_sell_min , vol_multiplier , atr_stop_mult , rr_ratio

zscore_mean_reversion

entry , exit , max_pos , dampening

rsi_mean_reversion

oversold , overbought

bollinger_rsi

rsi_oversold , rsi_overbought

macd_momentum

threshold

volume_momentum

multiplier

combined_rsi_macd

oversold , overbought

Listing Results

sn backtest list # newest 20 results (default) sn backtest list --limit 50 # show 50 results sn backtest list --limit 0 # show all results sn backtest list --sort winrate # sort by win rate descending sn backtest list --sort date # sort by date descending (default) sn backtest list --sort winrate --limit 0 # all results, best win rate first sn backtest list --product BTC-USD --limit 0 # filter by product, show all

--limit (default 20 ): number of results to return. 0 means all.

--sort (default date ): date = newest first; winrate = highest win rate first.

Filters: --exchange , --product , --strategy .

Inspecting a Single Result

sn backtest get 113

Shows full metrics: total return, win rate, max drawdown, Sharpe ratio, profit factor, avg win/loss, max consecutive losses.

Interpreting Results

High win rate ≠ profitable. A strategy can show 77% win rate yet negative returns if winning trades are small and losing trades are large. Always check profit_factor (> 1.0 required) and avg_win / avg_loss ratio alongside win rate.

Key metrics to evaluate:

Metric What it measures Good threshold

total_return

Overall P&L % for the period Positive

win_rate

% of trades that closed in profit Context-dependent

profit_factor

Gross profit / gross loss

1.2

max_drawdown

Largest peak-to-trough loss < 20%

sharpe_ratio

Risk-adjusted return

1.0

avg_win / avg_loss

Win size vs loss size

1.0

Checklist for a New Bot

  • Install sn and ledger CLIs, run sn 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

openspec-archive-change

No summary provided by upstream source.

Repository SourceNeeds Review
General

openspec-verify-change

No summary provided by upstream source.

Repository SourceNeeds Review
General

openspec-ff-change

No summary provided by upstream source.

Repository SourceNeeds Review