Record Trade
Record executed trades to the Spot Canvas trading ledger via its REST import API.
Endpoint: POST /api/v1/import
The ledger URL depends on the environment:
-
Staging: https://signalngn-trader-staging.europe-west1.run.app
-
Production: Use the LEDGER_URL environment variable if set, otherwise ask the user.
Recording a Trade
Use curl to POST a JSON body with one or more trades.
curl -s -X POST "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/import"
-H "Content-Type: application/json"
-d '{
"trades": [{
"trade_id": "<unique-id>",
"account_id": "<account>",
"symbol": "<pair>",
"side": "<buy|sell>",
"quantity": <number>,
"price": <number>,
"fee": <number>,
"fee_currency": "<currency>",
"market_type": "<spot|futures>",
"timestamp": "<RFC3339>",
"strategy": "<strategy-name>",
"entry_reason": "<signal description>",
"exit_reason": "<exit description>",
"confidence": <0-1>,
"stop_loss": <price>,
"take_profit": <price>,
"leverage": <integer>,
"margin": <number>,
"liquidation_price": <number>,
"funding_fee": <number>
}]
}'
Field Reference
Required Fields
Field Type Description Example
trade_id
string Unique trade identifier. Use exchange trade ID or generate a UUID. Must be unique — duplicates are silently skipped. "binance-12345"
account_id
string Account identifier. Accounts are auto-created on first use. Use "live" or "paper" convention. "live"
symbol
string Trading pair. "BTC-USD"
side
string "buy" or "sell"
"buy"
quantity
number Trade quantity (must be > 0). 0.5
price
number Execution price (must be > 0). 50000
fee
number Fee amount. Use 0 if no fee. 25.00
fee_currency
string Currency the fee is denominated in. "USD"
market_type
string "spot" or "futures"
"spot"
timestamp
string Trade execution time in RFC3339 format. "2025-06-15T10:30:00Z"
Strategy Metadata (optional)
Field Type Description Example
strategy
string Strategy name that generated the signal. "macd-rsi-v2"
entry_reason
string Signal reason text at entry. Use on buy trades. "MACD bullish crossover, RSI 42"
exit_reason
string Reason for closing. Use on sell trades. "stop loss hit" , "take profit reached"
confidence
number Signal confidence score, 0–1. 0.85
stop_loss
number Stop loss price level. 48000
take_profit
number Take profit price level. 55000
Futures Fields (optional, for market_type: "futures" only)
Field Type Description Example
leverage
integer Leverage multiplier. 10
margin
number Margin amount. 5000
liquidation_price
number Liquidation price. 45000
funding_fee
number Funding fee amount. 12.50
Response Format
{ "total": 1, "inserted": 1, "duplicates": 0, "errors": 0, "results": [ { "trade_id": "binance-12345", "status": "inserted" } ] }
Status per trade is one of: "inserted" , "duplicate" , "error" .
Examples
Spot buy with strategy metadata
curl -s -X POST "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/import"
-H "Content-Type: application/json"
-d '{
"trades": [{
"trade_id": "bot-'$(date +%s)'",
"account_id": "live",
"symbol": "BTC-USD",
"side": "buy",
"quantity": 0.5,
"price": 50000,
"fee": 25,
"fee_currency": "USD",
"market_type": "spot",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"strategy": "macd-rsi-v2",
"entry_reason": "MACD bullish crossover, RSI 42",
"confidence": 0.85,
"stop_loss": 48000,
"take_profit": 55000
}]
}'
Spot sell (closing position)
curl -s -X POST "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/import"
-H "Content-Type: application/json"
-d '{
"trades": [{
"trade_id": "bot-'$(date +%s)'",
"account_id": "live",
"symbol": "BTC-USD",
"side": "sell",
"quantity": 0.5,
"price": 55000,
"fee": 27.50,
"fee_currency": "USD",
"market_type": "spot",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"exit_reason": "take profit reached"
}]
}'
Leveraged futures trade
curl -s -X POST "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/import"
-H "Content-Type: application/json"
-d '{
"trades": [{
"trade_id": "bot-futures-'$(date +%s)'",
"account_id": "live",
"symbol": "ETH-USD",
"side": "buy",
"quantity": 10,
"price": 3000,
"fee": 6,
"fee_currency": "USD",
"market_type": "futures",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"strategy": "funding-arb",
"confidence": 0.72,
"leverage": 5,
"margin": 6000,
"liquidation_price": 2500,
"stop_loss": 2800,
"take_profit": 3300
}]
}'
Batch import (multiple trades)
Up to 1000 trades per request. Trades are automatically sorted by timestamp for correct position calculation.
curl -s -X POST "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/import"
-H "Content-Type: application/json"
-d '{
"trades": [
{ "trade_id": "t1", "account_id": "paper", "symbol": "BTC-USD", "side": "buy", "quantity": 1.0, "price": 40000, "fee": 20, "fee_currency": "USD", "market_type": "spot", "timestamp": "2025-06-01T10:00:00Z" },
{ "trade_id": "t2", "account_id": "paper", "symbol": "BTC-USD", "side": "sell", "quantity": 1.0, "price": 42000, "fee": 21, "fee_currency": "USD", "market_type": "spot", "timestamp": "2025-06-15T10:00:00Z", "exit_reason": "target hit" }
]
}'
Account Balance
The ledger tracks a cash balance per account. The trading agent should set an initial balance before trading and query it before sizing positions. Balance is automatically adjusted by trade ingestion — opening a position deducts the cost and closing a position credits the realised P&L.
Set (or reset) account balance
Use PUT /api/v1/accounts/{accountId}/balance to set an initial balance or to manually correct it after broker reconciliation. This overwrites the current value unconditionally.
curl -s -X PUT "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/balance"
-H "Content-Type: application/json"
-H "Authorization: Bearer ${LEDGER_API_KEY}"
-d '{"amount": 50000, "currency": "USD"}'
Response:
{ "account_id": "live", "currency": "USD", "amount": 50000 }
currency defaults to "USD" if omitted.
CLI equivalent:
trader accounts balance set live 50000 # set USD balance trader accounts balance set live 40000 --currency EUR # set EUR balance trader accounts balance set live 50000 --json # raw JSON response
Query account balance
curl -s "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/balance"
-H "Authorization: Bearer ${LEDGER_API_KEY}"
Response when set:
{ "account_id": "live", "currency": "USD", "amount": 47250.00 }
Returns HTTP 404 when no balance has been set for the account.
Optional ?currency=EUR query parameter selects a non-default currency.
CLI equivalent:
trader accounts balance get live # show balance table trader accounts balance get live --currency EUR # EUR balance trader accounts balance get live --json # raw JSON
How automatic balance adjustment works
When a trade is ingested the ledger adjusts the USD balance within the same transaction as the position update. The adjustment is a no-op if no balance row exists for the account — no balance row is ever auto-created.
Event Balance change
Spot buy (open / add to position) − quantity × price + fee
Spot sell (partial or full close)
- realised P&L
Futures open − margin (uses margin field; falls back to cost_basis / leverage ; skipped if neither available)
Futures close (partial or full)
- realised P&L (leverage- and fee-adjusted)
Position rebuild does not touch the balance — it only reconstructs position state.
Balance in portfolio and stats responses
When a balance has been set, it is included in the portfolio summary and account stats responses as an optional "balance" field. It is omitted entirely when no balance row exists.
curl -s "${TRADER_URL:-...}/api/v1/accounts/live/portfolio" | python3 -m json.tool
→ { "positions": [...], "total_realized_pnl": 1234.5, "balance": 47250.00 }
curl -s "${TRADER_URL:-...}/api/v1/accounts/live/stats" | python3 -m json.tool
→ { ..., "total_realized_pnl": 1234.5, "balance": 47250.00 }
Querying the Ledger
After recording trades, you can query positions and trade history.
Get account stats (win rate, realized P&L, trade count)
REST endpoint — returns aggregate stats computed from round-trips (closed positions):
curl -s "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/stats" | python3 -m json.tool
Response:
{ "total_trades": 116, "closed_trades": 109, "win_count": 79, "loss_count": 30, "win_rate": 0.7248, "total_realized_pnl": 2555.28, "open_positions": 7, "balance": 47250.00 }
total_trades = closed + open positions (round-trips, not raw buy/sell records). balance is omitted when no balance has been set.
CLI equivalent:
trader accounts show live # human-readable table (includes balance row when set) trader accounts show live --json # raw JSON
Check open positions
curl -s "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/positions?status=open" | python3 -m json.tool
Check closed positions (with exit_price, exit_reason)
curl -s "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/positions?status=closed" | python3 -m json.tool
Get portfolio summary
curl -s "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/portfolio" | python3 -m json.tool
List recent trades
curl -s "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/trades?limit=10" | python3 -m json.tool
Filter trades by symbol
curl -s "${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/accounts/live/trades?symbol=BTC-USD&limit=20" | python3 -m json.tool
trader CLI Reference
The trader CLI wraps the REST API for human use. Key commands:
trader accounts list # list all accounts for the tenant trader accounts show <account-id> # show aggregate stats (win rate, P&L, trade count, balance) trader accounts show <account-id> --json # raw JSON stats
trader accounts balance set <account-id> <amount> # set/overwrite account balance (USD) trader accounts balance set <account-id> <amount> --currency EUR # set non-USD balance trader accounts balance get <account-id> # query current balance trader accounts balance get <account-id> --currency EUR # query non-USD balance
trader trades list <account-id> # round-trip view: one row per position (default) trader trades list <account-id> --raw # raw trade view: one row per individual trade leg trader trades list <account-id> --limit 20 # show last 20 positions (default: 50, 0 = all) trader trades list <account-id> --long # show all columns: ID, full timestamps, exit reason # default (--short): no ID, compact times (no year)
Round-trip view (default)
Shows one row per position. Columns: RESULT, SYMBOL, DIR, SIZE, ENTRY, EXIT, P&L, P&L%, OPENED, CLOSED.
-
RESULT : ✓ win , ✗ loss , or open
-
SIZE : cost basis in USD
-
ENTRY / EXIT : avg entry price and exit price
-
P&L / P&L% : realized P&L amount and percentage
-
OPENED / CLOSED : compact timestamps (MM-DD HH:MM:SS ); --long shows full YYYY-MM-DD HH:MM:SS
A win/loss summary is printed below the table, calculated from the displayed rows:
5 wins 2 losses 71% win rate (7 closed)
Open positions are excluded from the win/loss count.
Raw trade view (--raw )
Shows one row per individual trade leg. Columns (short): SYMBOL, SIDE, QTY, PRICE, FEE, MARKET, TIME.
With --long : adds the TRADE-ID column and shows full timestamps.
Number formatting:
-
QTY: whole numbers for large quantities (e.g. 52447552 not 5.24e+07 )
-
PRICE: up to 6 decimal places for normal prices; scientific notation (2.860e-05 ) for micro-prices
-
FEE: max 4 decimal places, trailing zeros trimmed
Deleting a Trade
Use this only to remove test trades that were recorded by mistake or during testing. Do not use it to delete real trading history.
A trade can only be deleted if its account/symbol has no open position. If the trade contributed to an open position, close the position first (record the offsetting sell/buy trade), then delete the test trades.
Via CLI
trader trades delete <trade-id> --confirm
The --confirm flag is required to prevent accidental deletion.
Examples:
Delete a specific test trade
trader trades delete bot-1234567890 --confirm
Delete and get JSON response
trader trades delete bot-1234567890 --confirm --json
Exit codes and messages:
Situation Output Exit code
Success deleted trade <id>
0
Missing --confirm
use --confirm to delete a trade
non-zero
Trade not found trade not found
non-zero
Trade has open position server error message non-zero
Via REST API
curl -s -X DELETE
"${TRADER_URL:-https://signalngn-trader-staging.europe-west1.run.app}/api/v1/trades/<trade-id>"
-H "Authorization: Bearer ${LEDGER_API_KEY}"
Responses:
Status Meaning
200 {"deleted": "<id>"}
Trade deleted successfully
404 {"error": "trade not found"}
Trade doesn't exist or belongs to another tenant
409 {"error": "trade contributes to an open position and cannot be deleted"}
Close the position first
401
Invalid or missing API key
Important Notes
-
Idempotent: Submitting the same trade_id twice results in a "duplicate" — no error, no double-counting.
-
Auto-creates accounts: If the account_id doesn't exist, it's created automatically ("live" → live type, "paper" → paper type).
-
Position tracking is automatic: The ledger maintains positions from trade history. Buy trades open/increase positions, sell trades reduce/close them.
-
Metadata on positions: stop_loss , take_profit , and confidence are copied from the opening trade to the position. exit_price and exit_reason are set when the position closes.
-
Batch max: 1000 trades per request.
-
Timestamps: Always use RFC3339 format (e.g., 2025-06-15T10:30:00Z ).