Data Flow
This document describes the data flows for key operations in the TradeX platform.
Order Processing Flow
Section titled “Order Processing Flow”sequenceDiagram
participant Client
participant OrderService
participant MetadataService
participant RiskService
participant WalletService
participant Kafka
participant MatchingEngine
participant PositionService
participant MarketData
Client->>OrderService: POST /orders
OrderService->>MetadataService: GetInstrument (gRPC)
OrderService->>RiskService: PreTradeCheck (gRPC)
RiskService-->>OrderService: APPROVED + margin details
OrderService->>WalletService: LockFunds (gRPC)
WalletService-->>OrderService: funds locked
OrderService->>Kafka: order.command.v1
Kafka->>MatchingEngine: order.command.v1
MatchingEngine->>MatchingEngine: Match Orders
MatchingEngine->>Kafka: engine.event.v1
Kafka->>OrderService: engine.event.v1 (update status)
Kafka->>PositionService: engine.event.v1 (apply trade)
Kafka->>MarketData: engine.event.v1 (update book)
OrderService->>WalletService: SettleTrade (gRPC)
MarketData->>Client: WebSocket update
- Client submits order via Order Service REST API
- Instrument validated — tick size, lot size, min notional checked via Metadata Service gRPC
- Risk pre-trade check — Risk Service validates leverage, margin sufficiency, exposure limits, price bands, and account status
- Funds locked — Wallet Service reserves initial margin via
LockFunds - Order published to Kafka (
order.command.v1) - Matching Engine consumes and matches against the order book
- Trade executed —
engine.event.v1published to Kafka - Order Service updates order status (filled / partially filled) and calls
WalletService.SettleTradefor the order-side wallet posting - Position Service also consumes
engine.event.v1, appends toposition_ledger, and emits:position.updated.v1/position.closed.v1settlement.v1(canonical settlement signal — consumed by Wallet Service)risk.exposure.v2(per-user aggregate, debounced)
- Market Data Service updates order book and broadcasts via WebSocket
Market Data Flow
Section titled “Market Data Flow”sequenceDiagram
participant MatchingEngine
participant Kafka
participant MarketData
participant Redis
participant PostgreSQL
participant Client
MatchingEngine->>Kafka: engine.event.v1 (trades + book deltas)
MatchingEngine->>Kafka: engine.snapshot.v1 (full book state)
MatchingEngine->>Kafka: engine.bookhash.v1 (integrity check)
Kafka->>MarketData: Consume events
MarketData->>MarketData: Apply delta / verify hash
MarketData->>Redis: Cache order book
MarketData->>PostgreSQL: Persist trades (TimescaleDB)
MarketData->>Kafka: md.trades.v1 + md.orderbook.delta.v1
MarketData->>Client: WebSocket update
- Matching Engine publishes trade events, book deltas, periodic full snapshots, and book hashes to Kafka
- Market Data Service consumes all three event types — deltas are applied incrementally, snapshots reset local state, hashes verify integrity
- Order book cached in Redis for low-latency REST queries
- Trades persisted to PostgreSQL (TimescaleDB) for OHLCV aggregation and history
- Normalized events re-published to
md.*topics for downstream consumers - Updates broadcast to WebSocket subscribers
User Onboarding Flow
Section titled “User Onboarding Flow”sequenceDiagram
participant Client
participant AuthService
participant Kafka
participant UserService
participant WalletService
Client->>AuthService: POST /signup
AuthService->>AuthService: Hash password, create account
AuthService->>Kafka: auth.account.created.v1
Kafka->>UserService: auth.account.created.v1
UserService->>UserService: Create profile (KYC tier 0)
UserService->>Kafka: user.created.v1
Kafka->>WalletService: user.created.v1
WalletService->>WalletService: Initialize balance (INR + USDT)
AuthService->>Client: Return JWT tokens
- Client signs up — Auth Service hashes password and creates
auth_accountsrecord - Auth event published —
auth.account.created.v1triggers user profile creation - User Service creates profile at KYC tier 0 (limited trading until verified)
- User event published —
user.created.v1triggers wallet initialization - Wallet Service creates zero-balance ledger entries for INR and USDT
- JWT tokens returned to client — account is immediately usable within KYC tier 0 limits
Authentication Flow
Section titled “Authentication Flow”sequenceDiagram
participant Client
participant AuthService
participant PostgreSQL
participant Kafka
Client->>AuthService: POST /login
AuthService->>PostgreSQL: Validate credentials
AuthService->>AuthService: Generate JWT (access + refresh)
AuthService->>PostgreSQL: Store refresh token
AuthService->>Kafka: auth.login.v1
AuthService->>Client: Return tokens
- Client submits credentials
- Auth Service validates credentials against database (bcrypt comparison)
- JWT tokens generated — short-lived access token + long-lived refresh token
- Refresh token stored in database
- Login event published to Kafka for audit trail
- Tokens returned to client
Wallet Operations Flow
Section titled “Wallet Operations Flow”sequenceDiagram
participant Client
participant WalletService
participant PostgreSQL
participant Kafka
participant External
Client->>WalletService: POST /v1/deposit
WalletService->>PostgreSQL: Record deposit (pending)
WalletService->>External: Generate deposit address / initiate payment
External->>External: Confirm deposit
External->>WalletService: Webhook / callback
WalletService->>PostgreSQL: Credit balance (NUMERIC, optimistic lock)
WalletService->>Kafka: wallet.deposit_confirmed.v1
WalletService->>Client: Return status
- Client initiates deposit request
- Wallet Service creates pending deposit record
- External system generates deposit address (crypto) or initiates payment rail (fiat)
- External system confirms the deposit
- Balance credited atomically with optimistic locking (version check prevents double-credit)
- Deposit confirmed event published to Kafka
- Status returned to client
Funding Settlement Flow
Section titled “Funding Settlement Flow”Runs automatically every 8 hours (00
, 08, 16 UTC). Scheduler-driven —md.funding.v1 is consumed only for rate-cache warming, not as a settlement trigger.
sequenceDiagram
participant MarketData
participant FundingService
participant PositionService
participant Kafka
participant WalletService
MarketData->>FundingService: md.funding.v1 (rate cache warm)
FundingService->>FundingService: Phase 1 — open cycle (SCHEDULED)
Note over FundingService: Wait SNAPSHOT_GRACE_PERIOD (30s)
FundingService->>PositionService: ListPositionsBySymbol(as_of_unix_ms = cycle_ts)
PositionService-->>FundingService: Positions as observed at boundary
FundingService->>FundingService: Phase 2 — insert funding_settlements (PENDING)
loop Worker pool (SKIP LOCKED, exp. backoff)
FundingService->>Kafka: position.funding.v1 (idempotent fanout)
FundingService->>Kafka: funding.payment.settled.v1 (audit)
end
Kafka->>PositionService: position.funding.v1
PositionService->>PositionService: Fold funding delta into ledger
PositionService->>Kafka: settlement.v1 (kind = FUNDING)
Kafka->>WalletService: settlement.v1 → ledger posting
FundingService->>FundingService: Sealer — zero-sum check → SEALED or NEEDS_REVIEW
- Market Data Service publishes
md.funding.v1; Funding Service tails it into the in-memory rate cache (does not trigger settlement). - Scheduler Phase 1 (leader-elected):
INSERT … ON CONFLICT DO NOTHINGintofunding_cyclesat the boundary, freezing rate / mark / index. Status →SCHEDULED. - Snapshot grace (30s) — wait for all in-flight trades for the cycle to land.
- Scheduler Phase 2: call
PositionService.ListPositionsBySymbolwithas_of_unix_ms = cycle_timestamp. Position Service replays its ledger at exactly that instant, so the snapshot is deterministic and retry-safe. - Settlement work queue: one
funding_settlementsrow per non-flat position with statusPENDINGand idempotency keyfunding:{cycle_ts_unix}:{user_id}:{symbol}. - Worker pool (every pod,
SELECT … FOR UPDATE SKIP LOCKED) drains the queue: publishesposition.funding.v1(fanout to Position Service) andfunding.payment.settled.v1(public audit). On terminal failure →DEAD_LETTER. - Position Service consumes
position.funding.v1, folds the funding delta into the ledger, and emitssettlement.v1(kind = FUNDING) — the canonical wallet posting signal. - Sealer runs the zero-sum check once all settlements reach a terminal state. Pass →
SEALED. DLQ rows present or|paid − received| > tolerance→NEEDS_REVIEW; admin overrides viaPOST /internal/funding/cycles/:id/seal.
Configuration Update Flow
Section titled “Configuration Update Flow”sequenceDiagram
participant Admin
participant MetadataService
participant PostgreSQL
participant Outbox
participant Kafka
participant Consumer
Admin->>MetadataService: POST /v1/instruments (Maker)
MetadataService->>PostgreSQL: Save Instrument (pending)
MetadataService->>PostgreSQL: Write to Outbox
Admin->>MetadataService: POST /v1/instruments/:symbol/approve (Checker)
MetadataService->>PostgreSQL: Update status (active)
MetadataService->>PostgreSQL: Write to Outbox
MetadataService->>Outbox: Process Outbox
Outbox->>Kafka: md.instrument.created.v1
Kafka->>Consumer: Instrument event
- Admin creates instrument (maker role, status: pending)
- Instrument saved to database with outbox entry
- Admin approves instrument (checker role, status: active)
- Status updated and second outbox entry written
- Outbox worker polls and publishes events to Kafka
- Downstream consumers receive events and update local caches
Liquidation Flow
Section titled “Liquidation Flow”sequenceDiagram
participant RiskService
participant Kafka
participant SettlementService
participant OrderService
participant MatchingEngine
participant WalletService
RiskService->>Kafka: risk.liquidation.v1
Kafka->>SettlementService: risk.liquidation.v1
SettlementService->>SettlementService: Create settlement_record (initiated)
SettlementService->>SettlementService: Compute bankruptcy price
SettlementService->>OrderService: Submit IOC liquidation order (gRPC)
OrderService->>Kafka: order.command.v1
Kafka->>MatchingEngine: order.command.v1
MatchingEngine->>Kafka: engine.event.v1 (fill)
Kafka->>SettlementService: engine.event.v1
SettlementService->>SettlementService: Compute shortfall
alt No shortfall
SettlementService->>Kafka: settlement.completed.v1
else Shortfall covered by insurance fund
SettlementService->>WalletService: Draw from insurance fund
SettlementService->>Kafka: settlement.insurance.used.v1
SettlementService->>Kafka: settlement.completed.v1
else Shortfall requires ADL
SettlementService->>Kafka: settlement.adl.triggered.v1
SettlementService->>Kafka: settlement.completed.v1
end
- Risk Service emits
risk.liquidation.v1when a user’s margin ratio breaches the maintenance threshold - Settlement Service creates a
settlement_recordsrow and computes the bankruptcy price - Liquidation order submitted — IOC limit order at bankruptcy price routed through the normal Order Service → Matching Engine pipeline
- Matching Engine fills the order (partially or fully)
- Settlement Service monitors
engine.event.v1for the fill - Shortfall handled — if the fill price is worse than the bankruptcy price, the insurance fund covers the gap; if the insurance fund is insufficient, ADL proportionally force-reduces the most profitable counterparty positions
- Settlement events published — consuming services update positions, wallets, and compliance records
Index Price Calculation Flow
Section titled “Index Price Calculation Flow”sequenceDiagram
participant ExternalExchanges
participant MarketData
participant PostgreSQL
participant Redis
participant Kafka
participant Client
loop Every interval
MarketData->>ExternalExchanges: Fetch prices (ccxt — Binance, Coinbase, Kraken, OKX)
ExternalExchanges->>MarketData: Return prices
MarketData->>MarketData: Weighted average
MarketData->>PostgreSQL: Store index price
MarketData->>Redis: Cache index price
MarketData->>Kafka: md.index.v1
MarketData->>Client: WebSocket update
end
- Index Price Worker fetches spot prices from configured external exchanges via ccxt
- Weighted average computed using per-exchange weights from Metadata Service config
- Index price stored in PostgreSQL and cached in Redis
md.index.v1published to Kafka for downstream consumers (mark price, funding rate computation)- Update broadcast to WebSocket subscribers