record-trade

SKILL.md

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).
Weekly Installs
19
First Seen
Feb 25, 2026
Installed on
opencode19
github-copilot19
codex19
kimi-cli19
gemini-cli19
cursor19