A Go trading engine for the Signal Ngn ecosystem. It ingests trade events via NATS JetStream, maintains portfolio state (positions, P&L), exposes data through a REST API and CLI, and runs a signal-driven trading engine that can execute paper or live trades autonomously.
- Trade Ingestion: NATS JetStream durable consumer for reliable, at-least-once trade processing
- Portfolio Tracking: Positions derived from trade history with real-time updates
- Multi-Tenancy: Bearer API key auth; each user's data is fully isolated
- Multi-Account: Support for live and paper trading accounts
- Spot & Futures: Handles both spot trades and leveraged futures positions
- REST API: HTTP endpoints for querying portfolio state and importing historic trades
- CLI:
tradercommand-line tool for humans and trading bots - Tax Data: Captures cost basis, realized P&L, fees, and holding periods for tax reporting
- Idempotent: Duplicate trades are safely discarded (dedup by trade ID)
- Rebuildable: Positions can be rebuilt from trade history for audit/repair
- Trading Engine: Signal-driven engine goroutine — subscribes to Synadia NGS signals, executes paper or live (Binance Futures) trades, enforces risk rules (SL/TP, trailing stop, max hold time, daily loss limit, kill switch)
- Live Trade Stream: SSE endpoint streams trade events in real time to the CLI and dashboards
Install the trader CLI:
# go install
go install github.com/Signal-ngn/trader/cmd/trader@latest
# Homebrew (macOS)
brew install --cask Signal-ngn/trader/ledgertrader auth login # opens browser — logs you in and stores your API key
trader accounts list # works immediatelyIf you previously ran sn auth login, the key from ~/.config/sn/config.yaml is picked up automatically — no re-login needed.
For trading bots or CI, set TRADER_API_KEY directly:
export TRADER_API_KEY=your-api-key
trader accounts listThe tenant ID is resolved automatically on first use (via GET /auth/resolve) and cached in ~/.config/trader/config.yaml.
trader accounts list # list all accounts
trader accounts list --json # JSON outputtrader portfolio live # open positions + total realized P&L
trader portfolio paper --jsontrader positions live # open positions (default)
trader positions live --status closed # closed positions
trader positions live --status all # all positions
trader positions live --json# List trades
trader trades list live # 50 most recent trades
trader trades list live --symbol BTC-USD # filter by symbol
trader trades list live --side buy # filter by side
trader trades list live --market-type futures # filter by market type
trader trades list live --start 2025-01-01T00:00:00Z --end 2025-02-01T00:00:00Z
trader trades list live --limit 200 # up to 200 results
trader trades list live --limit 0 # all trades (follows all pages)
trader trades list live --json
# Record a trade
trader trades add live --symbol BTC-USD --side buy --quantity 0.1 --price 95000
# With fees and strategy metadata
trader trades add live \
--symbol BTC-USD --side buy --quantity 0.1 --price 95000 \
--fee 9.50 --strategy macd_momentum --confidence 0.78 \
--stop-loss 93000 --take-profit 99000
# Futures long with leverage
trader trades add live \
--symbol BTC-USD --side buy --quantity 0.5 --price 95000 \
--market-type futures --leverage 10 --margin 4750
# Stream live trade events (SSE → JSONL to stdout)
trader trades watch live
trader trades watch papertrader orders live # 50 most recent orders
trader orders live --status open # open orders only
trader orders live --symbol BTC-USD
trader orders live --limit 0 --json # all orders as JSONtrader import trades.json # import historic trades from file
trader import trades.json --json # show full response JSONThe file must be a JSON object with a "trades" array matching the trade event format. Prints Total / Inserted / Duplicates / Errors and exits non-zero if any errors occurred.
trader auth login # open browser, complete OAuth, write api_key to config
trader auth logout # remove api_key from ~/.config/trader/config.yaml
trader auth status # show whether you are authenticated and which key is activetrader auth login opens your browser to the platform login page, listens for the OAuth callback on a local port, writes the api_key to ~/.config/trader/config.yaml, and prints Authenticated as <email>.
If the browser cannot open automatically the URL is printed for manual navigation. The command times out after 120 seconds.
trader strategies list # list all built-in and user strategies
trader strategies list --active # only active user strategies + all built-ins
trader strategies list --json
trader strategies get <id> # detail view + source code (if any)
trader strategies get 42 --json
trader strategies validate --name my_strat --file strat.star # validate source file
trader strategies create --name my_strat --file strat.star # create user strategy
trader strategies create --name x --file x.star --description "My strat" --params '{"THRESHOLD":2.0}'
trader strategies update 42 --file updated.star # update source
trader strategies activate 42
trader strategies deactivate 42
trader strategies delete 42
# Backtest a user strategy
trader strategies backtest 42 \
--exchange coinbase --product BTC-USD --granularity ONE_HOUR
trader strategies backtest 42 \
--exchange coinbase --product BTC-USD --granularity ONE_HOUR \
--mode futures-long --leverage 5strategies list output columns: TYPE (builtin/user), NAME, DESCRIPTION, ACTIVE (yes/no for user, - for built-in).
trader trading list # all trading configs
trader trading list live # filtered to account
trader trading list --enabled # only enabled configs
trader trading list --json
trader trading get live coinbase BTC-USD # detail view
trader trading get live coinbase BTC-USD --json
# Create or update a config (unset flags preserve existing values)
trader trading set live coinbase BTC-USD \
--granularity ONE_HOUR --spot ml_xgboost --enable
trader trading set live coinbase BTC-USD \
--params ml_xgboost:confidence=0.80 --params ml_xgboost:exit_confidence=0.40
trader trading set live coinbase BTC-USD --params ml_xgboost:clear # remove all params
trader trading set live coinbase BTC-USD --disable
trader trading delete live coinbase BTC-USD # delete a configtrading set flags: --granularity, --long, --short, --spot, --long-leverage, --short-leverage, --trend-filter, --no-trend-filter, --enable, --disable, --params.
trader price BTC-USD # live price (default: coinbase, ONE_MINUTE)
trader price BTC-USD --exchange kraken --granularity ONE_HOUR
trader price BTC-USD --json
trader price --all # all enabled products
trader price --all --json # JSON array of successful resultsOutput columns: EXCHANGE, PRODUCT, PRICE, OPEN, HIGH, LOW, VOLUME, AGE. The AGE column is prefixed with ! when the price is more than 1 hour stale.
# Submit and wait for result
trader backtest run \
--exchange coinbase --product BTC-USD --strategy ml_xgboost --granularity ONE_HOUR
trader backtest run \
--exchange coinbase --product BTC-USD --strategy ml_xgboost --granularity ONE_HOUR \
--mode futures-long --leverage 5 --trend-filter \
--params confidence=0.80 --params exit_confidence=0.40
# No-wait mode — print job ID and exit
trader backtest run --exchange coinbase --product BTC-USD \
--strategy ml_xgboost --granularity ONE_HOUR --no-wait
# List and inspect results
trader backtest list # 20 most recent (default)
trader backtest list --strategy ml_xgboost --limit 50
trader backtest list --sort winrate
trader backtest list --limit 0 # all results
trader backtest list --json
trader backtest get 123 # full detail table
trader backtest get 123 --json
trader backtest job <job-id> # poll job status / fetch resultbacktest run required flags: --exchange, --product, --strategy, --granularity. Optional: --mode (default spot), --start, --end, --leverage, --trend-filter, --no-wait, --params.
# Stream all signals from your enabled trading configs (Ctrl-C to stop)
trader signals
# Filter to specific exchange/product/granularity/strategy
trader signals --exchange coinbase --product BTC-USD
trader signals --exchange coinbase --product BTC-USD --granularity ONE_HOUR
trader signals --strategy ml_xgboost
# Machine-readable output (one JSON object per line)
trader signals --jsonOn startup trader signals:
- Calls
GET /config/tradingto build an allowlist of your enabled strategy slots - Connects to NATS at
nats_urlusing embedded read-only credentials - Subscribes to
signals.<exchange>.<product>.<granularity>.<strategy>using wildcards for unset filters - Only prints signals that match your allowlist
Use nats_creds_file to override the embedded credentials (e.g., for custom NGS accounts).
trader config show # show all config values and sources
trader config set trader_url https://... # override service URL
trader config set api_url https://... # override platform API URL
trader config get trader_urlConfig file: ~/.config/trader/config.yaml
| Key | Default | Env override |
|---|---|---|
trader_url |
https://signalngn-trader-potbdcvufa-ew.a.run.app |
TRADER_URL |
api_key |
(from ~/.config/sn/config.yaml) |
TRADER_API_KEY |
tenant_id |
(resolved automatically) | TRADER_TENANT_ID |
api_url |
https://signalngn-api-potbdcvufa-ew.a.run.app |
TRADER_API_URL |
web_url |
(none) | TRADER_WEB_URL |
ingestion_url |
https://signalngn-ingestion-potbdcvufa-ew.a.run.app |
TRADER_INGESTION_URL |
nats_url |
tls://connect.ngs.global |
TRADER_NATS_URL |
nats_creds_file |
(embedded credentials) | TRADER_NATS_CREDS_FILE |
trader --trader-url http://localhost:8080 accounts list # ledger URL override
trader --api-url http://localhost:9090 strategies list # platform API URL override
trader --ingestion-url http://localhost:9091 strategies validate --name x --file x.star
trader --web-url http://localhost:3000 auth login # web app URL override
trader --json accounts list # JSON output (any command)The ledger ships an agent skill that gives AI coding agents full knowledge of the trader CLI — commands, flags, trade event format, and bot patterns. Install it so your agent can record trades and query portfolio state without needing to look anything up.
Works with Claude Code, Cursor, pi, Windsurf, Codex, and many more.
npx skills add Signal-ngn/traderFor global installation (available in all projects):
npx skills add Signal-ngn/trader -gOnce installed the skill is available as trader in your agent. Invoke it in any conversation where the agent needs to interact with the ledger:
Use the ledger skill to check my open positions before placing this trade.
The skill covers: accounts, portfolio, positions, trades, orders, import, config, NATS event format, and common trading bot patterns.
- Go 1.24+
- Task
- Docker & Docker Compose
task setup # creates .env, installs deps, starts infra
task dev # run service with go runtask build # build server binary → bin/ledger
task build:cli # build CLI binary → bin/ledger
task build:all # build both
task dev # run server with go run
task test # unit tests
task test:v # verbose
task test:race # with race detector
task test:integration # requires running infra
task test:all # unit + integration + race
task test:cover # coverage report
task fmt # gofmt
task lint # go vet + staticcheck
task infra:up # start PostgreSQL + NATS
task infra:down # stop containers
task infra:reset # stop + delete volumes
task infra:status # show running containers
task docker:build # build Docker image
task deploy:production # deploy to Cloud Runcmd/trader/ # CLI entry point
cmd/traderd/ # Server entry point
internal/
├── config/ # Configuration loading
├── domain/ # Core types: Trade, Position, Account, Order
├── store/ # PostgreSQL repository, migrations
├── ingest/ # NATS JetStream consumer, trade processing
├── engine/ # Trading engine goroutine (signals, positions, risk, exchange)
└── api/ # REST handlers, routing, middleware, SSE stream
migrations/ # SQL migration files
Data flows:
Trading Bot → NATS JetStream → Ingestion → PostgreSQL ← REST API ← CLI / Dashboard
↑
NGS Signals → Engine ──────────────────────────→┘
└→ Exchange (Binance Futures / Noop)
The traderd server includes an optional signal-driven trading engine. When enabled it subscribes to Synadia NGS signals, filters them against your configured trading strategies, and executes paper or live trades — writing every fill to the ledger through the same path as manual ingestion.
Set TRADING_ENABLED=true in your environment or .env file. The engine starts as a goroutine inside traderd — no separate binary is needed.
| Mode | TRADING_MODE |
Exchange |
|---|---|---|
| Paper | paper (default) |
Synthetic fills at signal price, zero fees |
| Live | live |
Binance Futures (raw HTTP, HMAC-SHA256 signed) |
| Env var | Default | Description |
|---|---|---|
TRADING_ENABLED |
false |
Set true to start the engine |
TRADING_MODE |
paper |
paper or live |
TRADER_ACCOUNT |
paper |
Account ID all engine trades are booked under |
STRATEGY_FILTER |
— | Optional prefix — only process signals whose strategy starts with this string |
PORTFOLIO_SIZE |
10000 |
Total portfolio size in USD |
POSITION_SIZE_PCT |
10 |
Default position size as % of portfolio (overridden per-signal by position_pct) |
MAX_POSITION_SIZE |
0 |
Max position size in USD (0 = no cap) |
MIN_POSITION_SIZE |
0 |
Min position size in USD (0 = no floor) |
MAX_POSITIONS |
0 |
Max concurrent open positions (0 = no cap) |
DAILY_LOSS_LIMIT |
0 |
Halt new opens once realised losses today exceed this USD amount (0 = disabled) |
KILL_SWITCH_FILE |
/tmp/trader.kill |
Touch this file to halt all new opens immediately — existing positions are still risk-managed |
SN_API_KEY |
— | SignalNGN API key (required when TRADING_ENABLED=true) |
SN_API_URL |
https://api.signal-ngn.com |
SignalNGN API base URL |
SN_NATS_CREDS_FILE |
— | Path to custom NGS NATS credentials file (embedded subscribe-only key used by default) |
BINANCE_API_KEY |
— | Binance API key (live mode only) |
BINANCE_API_SECRET |
— | Binance API secret (live mode only) |
Every incoming NGS signal passes through these checks before a trade is placed:
- Allowlist — fetched from
GET /config/tradingon the SN API, rebuilt every 5 minutes; only enabled trading configs are allowed - Strategy filter — optional
STRATEGY_FILTERprefix match - Staleness — signals older than 2 minutes are dropped
- Confidence —
BUY/SHORTsignals withconfidence < 0.5are dropped - Cooldown — a 5-minute per-(symbol, action) cooldown prevents re-entering immediately after an open
- Kill switch — if
KILL_SWITCH_FILEexists, new opens are skipped (closes still execute) - Daily loss limit — queried live from the DB; counts all realised losses since midnight UTC
- Direction conflict — won't open a new position in the opposite direction to an existing one
- Max positions — won't exceed
MAX_POSITIONSconcurrent open positions
The risk loop runs every 5 minutes and evaluates every open position:
| Rule | Default | Notes |
|---|---|---|
| Stop-loss | −4% from entry | Uses signal stop_loss if provided and > 0.1% from entry |
| Take-profit | +10% from entry | Uses signal take_profit if provided and > 0.1% from entry |
| Trailing stop | Activates at +3% unrealised gain; trails 2% behind peak | Scaled by 1/leverage for futures; never loosens |
| Max hold time | 48 hours | Position is closed regardless of P&L |
Price used for evaluation: last price seen in a received NGS signal → SN price API fallback → skip tick (warning logged).
# Stream all trades for an account to stdout as JSONL
trader trades watch live
# Pipe to jq for pretty-printing
trader trades watch paper | jq .Reconnects automatically every 5 seconds on disconnect. Exit with Ctrl-C.
Each event is a JSON object:
{
"trade_id": "engine-live-BTC-USD-1740912345678901234",
"account_id": "live",
"symbol": "BTC-USD",
"side": "sell",
"quantity": 0.021,
"price": 96800.0,
"fee": 0,
"market_type": "futures",
"timestamp": "2026-03-02T11:00:00Z",
"strategy": "ml_xgboost",
"confidence": 0.82,
"stop_loss": 93200.0,
"take_profit": 104000.0,
"exit_reason": "take profit"
}The SSE endpoint is also available directly:
GET /api/v1/accounts/{accountId}/trades/stream
Authorization: Bearer <api-key>
Accept: text/event-stream
The service subscribes to trader.trades.>.
Subject format: trader.trades.<account>.<market_type>
{
"tenant_id": "c2899e28-2bbe-47c1-8d29-84ee1a04fd37",
"trade_id": "unique-trade-id",
"account_id": "live",
"symbol": "BTC-USD",
"side": "buy",
"quantity": 0.5,
"price": 50000,
"fee": 25,
"fee_currency": "USD",
"market_type": "spot",
"timestamp": "2025-01-15T10:00:00Z"
}tenant_id is required. Futures trades additionally include leverage, margin, liquidation_price, and optionally funding_fee.
Production URL: https://signalngn-trader-potbdcvufa-ew.a.run.app
All /api/v1/ and /auth/resolve endpoints require Authorization: Bearer <api-key>.
GET /auth/resolve
→ {"tenant_id": "<uuid>"}
GET /health
→ {"status": "ok"}
GET /api/v1/accounts
GET /api/v1/accounts/{accountId}/portfolio
GET /api/v1/accounts/{accountId}/positions?status=open|closed|all
GET /api/v1/accounts/{accountId}/trades?symbol=&side=&market_type=&start=&end=&cursor=&limit=
GET /api/v1/accounts/{accountId}/trades/stream (SSE — see Trading Engine section)
GET /api/v1/accounts/{accountId}/orders?status=&symbol=&cursor=&limit=
POST /api/v1/import
{
"trades": [
{
"tenant_id": "c2899e28-2bbe-47c1-8d29-84ee1a04fd37",
"trade_id": "t-001",
"account_id": "live",
"symbol": "BTC-USD",
"side": "buy",
"quantity": 0.5,
"price": 40000,
"fee": 20,
"fee_currency": "USD",
"market_type": "spot",
"timestamp": "2024-06-01T10:00:00Z"
}
]
}Max 1000 trades per request. Duplicate trade IDs are skipped. Re-importing is safe.
{
"total": 1,
"inserted": 1,
"duplicates": 0,
"errors": 0,
"results": [{"trade_id": "t-001", "status": "inserted"}]
}The ledger captures all data needed for tax reporting: cost basis, realized P&L, fees, holding periods, and exit reasons. You can export your trade history to CSV and submit it directly to your tax authority or import it into accounting software.
Note: Tax regulations vary by jurisdiction. The ledger provides the raw transaction data — consult a tax professional for advice on how to apply it.
Each row is one complete trade (entry + exit pair), which is typically what tax authorities require:
go run ./cmd/trader trades list live --json | jq -r '
["RESULT","SYMBOL","DIR","SIZE","ENTRY","EXIT","PNL","PNL%","OPENED","CLOSED","EXIT_REASON"],
(.[] | [
(if .status == "open" then "open" elif .realized_pnl > 0 then "win" else "loss" end),
.symbol,
.side,
.cost_basis,
.avg_entry_price,
(.exit_price // ""),
.realized_pnl,
(if .cost_basis > 0 then (.realized_pnl / .cost_basis * 100 | . * 100 | round | . / 100 | tostring) + "%" else "" end),
.opened_at,
(.closed_at // ""),
(.exit_reason // "")
])
| @csv
' > positions.csvUse --limit 0 to export the full history (all pages):
go run ./cmd/trader trades list live --limit 0 --json | jq -r '...' > positions_full.csvIf your tax authority requires every individual buy/sell transaction:
go run ./cmd/trader trades list live --raw --limit 0 --json | jq -r '
["TRADE_ID","SYMBOL","SIDE","QTY","PRICE","FEE","MARKET","TIMESTAMP"],
(.[] | [.trade_id, .symbol, .side, .quantity, .price, .fee, .market_type, .timestamp])
| @csv
' > trades.csv