Domain / Business
Infrastructure
External Services
Events / Async
Data / Storage
Security / Auth
Domain Model
«aggregate root»MarketOrder
- orderId: OrderId (UUID)
- clientId: ClientId
- instrument: InstrumentCode (NSE/BSE)
- segment: EQUITY | FNO | COMMODITY
- side: BUY | SELL
- orderType: MARKET | LIMIT | SL | SL_MARKET
- quantity: Int
- price: Money?
- triggerPrice: Money?
- validity: DAY | IOC | GTC
- status: OrderStatus
- brokerRefId: String?
- executedQty: Int
- avgPrice: Money?
+ place(): DomainEvent[TradeOrderPlaced]
+ fill(qty, price): DomainEvent[OrderFilled]
+ partialFill(qty, price): DomainEvent[OrderPartiallyFilled]
+ reject(reason): DomainEvent[OrderRejected]
+ cancel(): DomainEvent[OrderCancelled]
+ modify(newPrice, newQty): void
«aggregate»Watchlist
- watchlistId: WatchlistId
- clientId: ClientId
- name: String (max 5 per client)
- instruments: Set<InstrumentCode> (max 50)
- sortOrder: List<InstrumentCode>
+ addInstrument(code): void
+ removeInstrument(code): void
+ reorder(sorted): void
Value Objects
InstrumentCode OHLCV TickData MarketDepth OrderStatus
Market Data Pipeline LIVE 09:15-15:30 IST
Inbound: NSE/BSE WebSocket → Feed Handler → TimescaleDB → Redis Aggregation → Client SSE
NSE/BSE Feed
WebSocket binary
~5000 ticks/sec
FIX-like protocol
Feed Handler
Deserialize binary
Normalize to OHLCV
Filter subscribed
TimescaleDB
Hypertable: ticks
Compression: 7d
Retention: 2y
Redis Agg
15s OHLCV buckets
Sorted sets for top
TTL: 1 trading day
Client SSE
SseEmitter per client
Watchlist-scoped
p99 < 100ms
«adapter»NseFeedHandler
implements: MarketFeedPort
- wsClient: WebSocketClient
- decoder: BinaryTickDecoder
+ connect(): Flux<TickData>
+ subscribe(instruments): void
+ disconnect(): void
«service»TickAggregationService
- redis: ReactiveRedisOps
- timescale: TickRepository
+ aggregate(tick): OHLCV
+ get15sCandle(inst): OHLCV
+ getMarketDepth(inst): Depth
«adapter»SseMarketStreamer
- emitters: ConcurrentMap<ClientId, SseEmitter>
+ streamToClient(clientId, instruments): SseEmitter
+ broadcast(ohlcv): void
+ evictStale(): void (30s heartbeat)
Performance Budget
Tick ingest: p99 < 5ms
TimescaleDB write: p99 < 10ms
Redis aggregation: p99 < 2ms
SSE delivery: p99 < 100ms
End-to-end: p99 < 150ms
Order Execution Flow
1
User Intent via MIA
MIA parses: "Buy 50 shares of RELIANCE at market"
Extracts: instrument, qty, orderType, side
MIA tool_use → createOrder
2
Validate + Create MarketOrder
PlaceTradeUseCase validates: margin, circuit limits,
market hours, instrument tradeable
DomainEvent: byld.markets.events.order-placed
3
Route to Partner Broker
BrokerGatewayAdapter translates to FIX 4.4
NewOrderSingle (tag 35=D) sent via TCP
FIX 4.4 | tag35=D | TCP persistent
4
Exchange Execution
Broker routes to NSE/BSE matching engine
ExecutionReport (tag 35=8) returned
FIX 4.4 | tag35=8 | p99 < 100ms
5
Fill Confirmation
order.fill(qty, avgPrice) → status = FILLED
Publishes: byld.markets.events.trade-executed → byld-portfolio
Kafka: byld.markets.events.trade-executed
6
Notify + Update UI
SSE push to Kotlin CMP client: order status change
MIA confirms: "50 RELIANCE bought at 2,845.60"
SSE: order.status.changed
«ACL adapter»BrokerGatewayAdapter
implements: BrokerGateway (domain port)
- fixSession: QuickFIX/J Session
- circuitBreaker: Resilience4j ("broker")
+ placeOrder(cmd): BrokerOrderResult
+ cancelOrder(id): void
+ modifyOrder(id, cmd): void
Market Hours Auto-Scaling (HPA + KEDA)
00:00
05:00
09:15
15:30
18:00
23:59
2 pods
8 pods
2 pods
Off-hours: min 2 pods (health checks, pre-market)
Market hours: scale to 8 pods (CPU > 60%)
Transition: 5min ramp
KEDA Cron Scaler
timezone: Asia/Kolkata
start: 09:10 (pre-market warm-up)
end: 15:45 (post-market cooldown)
desiredReplicas: 8
cooldownPeriod: 300s
HPA Metrics
cpu: targetUtilization 60%
memory: targetUtilization 70%
custom: active_sse_connections
min: 2 | max: 12 | scaleDown: 10min
Package Structure + Notes
com.byld.markets
/domain
/model MarketOrder, Watchlist, OHLCV, TickData
/port MarketFeedPort, BrokerGateway, OrderRepo
/application
/usecase PlaceTradeUseCase, ManageWatchlistUseCase
/infrastructure
/feed NseFeedHandler, BseFeedHandler
/broker BrokerGatewayAdapter (FIX)
/sse SseMarketStreamer
/timescale TickRepository, OhlcvRepository
"Premature optimization is the root of all evil, but late optimization of a hot path is negligence."
-- adapted from Knuth
Key Decisions:
1. TimescaleDB for tick storage (not Kafka streams)
2. Redis 15s aggregation (not real-time per-tick push)
3. SSE over WebSocket (simpler, HTTP/2 multiplexed)
4. FIX 4.4 via QuickFIX/J (industry standard)
5. KEDA cron scaler for predictable scaling
6. Market hours = domain rule in MarketHoursPolicy