Skip to content

Syncing trades

Push each client's executed trades so TradLyt can build the behavioural history that powers stats, exit analytics and pre-trade.

POST /users/{user_ref}/trades

Two ways to send trades

Both land on the same canonical normalised fill schema (documented below) — the only difference is who does the mapping.

  1. Normalised — start here. Map your fills to the normalised schema yourself and send broker_type: "normalized". This works immediately, no setup on our side, and is the right path for your first integration.
  2. Native format — optional, later. Send us a sample tradebook export during onboarding and we'll build an adapter for your exact format. Once it's registered, you push your raw export as-is (with your broker_type) and TradLyt does the mapping — no transformation on your side.

Most brokers begin with path 1 and move to path 2 only if they'd rather not maintain the mapping themselves.

Request body

Field Type Description
trades array Executed legs. Path 1: the normalised schema below. Path 2: your raw native format.
broker_type string "normalized" for path 1; the adapter key we register for your tradebook in path 2.

Normalised fill schema

Each fill is one executed leg (not a netted position). Send both legs of a round-trip — the pipeline FIFO-pairs them per symbol, computes charges, and closes the trade.

Field Type Required Notes
symbol string ✅ Broker trading symbol, e.g. SENSEX2671677500PE.
trade_type enum ✅ BUY or SELL.
quantity integer ✅ Units, not lots.
price float ✅ Fill price.
execution_time datetime ✅ ISO-8601 with timezone, or epoch.
exchange string NSE / BSE / …
segment enum EQ or FO.
product enum MIS / CNC / NRML.
order_id string Used for dedup when present.
trade_id string Broker fill id — the dedup key. Send it so re-syncs are safe.
instrument_category enum EQ / OPT / FUT. Inferred from symbol when omitted.
option_type enum CE / PE.
strike_price float Options only.
expiry_date string YYYY-MM-DD.
underlying string E.g. NIFTY, SENSEX.
isin string Equity ISIN, if available.
{
  "broker_type": "normalized",
  "trades": [
    {
      "symbol": "SENSEX2671677500PE",
      "trade_type": "SELL",
      "quantity": 40,
      "price": 429.95,
      "execution_time": "2026-07-16T09:45:12+05:30",
      "exchange": "BSE",
      "segment": "FO",
      "product": "MIS",
      "order_id": "250716000123456",
      "trade_id": "78412345"
    }
  ]
}

Response

{
  "user_ref": "CLIENT00423",
  "fills_received": 2,
  "fills_normalized": 2,
  "result": {
    "status": "completed",
    "trades_count": 1,
    "positions_closed_count": 1,
    "open_positions_count": 0,
    "fills_count": 2,
    "date_range": { "from": "2026-07-16", "to": "2026-07-16" },
    "total_pnl": -430.0,
    "total_charges": 41.2,
    "errors": [],
    "warnings": []
  },
  "analysis": "queued"
}

analysis tells you what happened to the behavioural analysis:

  • "queued" — first sync for this client: the full analysis is running asynchronously. Poll GET /stats until status: "ready".
  • "synced" — incremental / live push: trades stored, the new batch retagged, and the aggregate score/pattern cache refreshed; /stats reflects them on the next call (no polling needed).
  • "skipped" — nothing new (all deduped).

First sync & analysis

On the first sync, push the client's full history from 2024-04-01 — that's the floor for our market-data-backed Rafiki scoring, so it's the earliest we can analyse fully. (Older trades are accepted and still count toward patterns/score, but can't be Rafiki-scored.)

Storing the fills is synchronous — the response returns as soon as they're stored, deduped and paired. The heavy behavioural analysis then runs asynchronously (the same pipeline as TradLyt's own onboarding: retag → 10 pattern detectors → per-trade Rafiki → personalised thresholds → score). When it kicks off, the response carries "analysis": "queued".

Poll GET /stats until it returns status: "ready" — until then it returns status: "analysing" with a stage, so you can show a "building your insights…" spinner exactly like TradLyt's dashboard does after onboarding.

Chunk a very large first sync

Storing is synchronous, so a multi-year history in a single call makes for a long request. Split the initial backfill into month- or quarter-sized batches; dedup makes overlaps safe, and the analysis re-runs after each batch.

Idempotency & cadence

  • Sending the same trade_id twice is a no-op (deduped) — safe to re-sync.
  • Partial-day pushes are fine. Sync as often as you like; nightly is the minimum for fresh behavioural baselines.
  • Live / intraday: push fills as they execute (or in small batches). After the first sync these return analysis: "synced" — fast, no polling; /stats and /pre-trade reflect the new trades on the next call. This is the broker-push equivalent of TradLyt's own per-minute broker sync for direct users.

Order & position monitoring is push-based

Trade sync covers executed fills. For alerts on resting orders / open positions (a stop about to trigger, a naked position, nearing square-off), push those events to POST /order-event from your OMS — TradLyt doesn't poll your order book. (For direct users we poll the broker; for you, you push.)

Payload errors

A fill missing a required key, or with a trade_type that isn't BUY/SELL, returns 422 bad_trade_payload. The detail.message names the offending fill index so you can fix the mapping.