Backend & APIs Topic
Real-Time Stock Trading Platform System Design
1. Problem Statement
Design a real-time stock trading platform supporting:
-
Real-time market data streaming (ticks, LTP, depth)
-
Order placement (limit, market, stop-loss)
-
Portfolio tracking with P&L
-
Charting with technical indicators
-
News and sentiment analysis
-
Alerts and notifications
-
Risk management and compliance
-
High-frequency trading support
-
Support for 10M+ active users, 1M+ concurrent connections
2. System Requirements
Functional Requirements:
-
Market Data:
-
Real-time tick-by-tick data (NSE, BSE, MCX)
-
Level 2 market depth (order book)
-
Index values and sector performance
-
Historical data (1 min to 1 year)
-
-
Trading Features:
-
Buy/sell orders (intraday, delivery, margin)
-
Order types: Market, Limit, Stop-loss, Bracket, Cover
-
Order modification and cancellation
-
Basket orders
-
SIP (Systematic Investment Plan)
-
-
Portfolio Management:
-
Real-time P&L calculation
-
Position tracking
-
Margin utilization
-
Tax P&L statements
-
Dividend tracking
-
-
Analytics & Research:
-
Advanced charting (candlestick, line, Heikin-Ashi)
-
Technical indicators (200+ indicators)
-
Backtesting engine
-
Screeners and scanners
-
News and corporate announcements
-
-
Risk Management:
-
Pre-trade risk checks
-
Position limits
-
Margin requirements
-
Circuit breaker enforcement
-
Compliance monitoring
-
Non-Functional Requirements:
-
Latency: < 10ms for order execution
-
Throughput: 100,000+ orders/second
-
Availability: 99.99% uptime (zero downtime during market hours)
-
Data Consistency: Strong consistency for orders, eventual for market data
-
Scalability: Handle 1M+ concurrent WebSocket connections
-
Security: Bank-grade security, 2FA, encryption at rest and transit
-
Regulatory Compliance: SEBI, GDPR, KYC norms
-
Fault Tolerance: Zero data loss, automatic failover
3. High-Level Architecture
text
┌─────────────────────────────────────────────────────────────────────┐
│ Client Applications │
├────────────────────┬────────────────────┬───────────────────────────┤
│ Kite Web │ Kite Mobile │ Console/API │
│ (React) │ (Flutter/React) │ (REST/WebSocket) │
└────────────────────┴────────────────────┴───────────────────────────┘
│
┌─────────────────────────────────▼───────────────────────────────────┐
│ Global Load Balancer (Anycast) │
│ (WAF, DDoS Protection, SSL Termination) │
└────────────────────┬─────────────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────────────┐
│ API Gateway Cluster │
│ (Rate Limiting, Authentication, Routing) │
└────────────────────┬─────────────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────────────┐
│ Microservices Ecosystem │
├──────────┬──────────┬──────────┬──────────┬──────────┬──────────────┤
│Order Mgmt│Market Data│ Portfolio │ Risk │ User │ Analytics │
│ Service │ Service │ Service │ Service │ Service │ Service │
├──────────┴──────────┴──────────┴──────────┴──────────┴──────────────┤
│ Service Mesh (Istio/Linkerd) │
└─────────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────────▼────────────────────────────────────────┐
│ Message Bus (Kafka/Pulsar) │
│ (Event Sourcing, CQRS, Real-time Event Streaming) │
└─────────────┬──────────────────┬─────────────────────────────────────┘
│ │
┌─────────▼──────┐ ┌────────▼────────┐ ┌──────────────────────┐
│ Exchange │ │ Market Data │ │ External │
│ Gateway │ │ Feed Handlers │ │ APIs │
│ (OMD/ITCH) │ │ (TCP/UDP) │ │ (News, Fundamentals) │
└────────┬───────┘ └────────┬────────┘ └──────────────────────┘
│ │
┌────────▼───────────────────▼────────┐
│ Data Storage Layer │
├──────────┬──────────┬───────────────┤
│ Times- │ Orders │ Cache │
│ series │ DB │ (Redis) │
│ (DolphinDB│ (PostgreSQL│ (Hazelcast)│
│ /QuestDB) │ /Cockroach)│ │
└──────────┴──────────┴───────────────┘
4. Component Design Deep Dive
4.1 Market Data Feed Architecture

Feed Handler Implementation:
cpp
// High-performance C++ feed handler for low latency
class OMDFeedHandler {
private:
tcp::socket socket_;
char buffer_[65536];
std::atomic<uint64_t> sequence_number_;
MarketDataProcessor& processor_;
public:
void start() {
asio::async_read(socket_, asio::buffer(buffer_, sizeof(OMDHeader)),
[this](const asio::error_code& ec, size_t bytes) {
if (!ec) process_packet();
});
}
void process_packet() {
auto* header = reinterpret_cast<OMDHeader*>(buffer_);
// Validate sequence number
if (header->sequence != sequence_number_.load() + 1) {
handle_sequence_gap();
return;
}
sequence_number_.store(header->sequence);
// Process based on packet type
switch (header->packet_type) {
case PacketType::LOGIN_RESPONSE:
handle_login_response();
break;
case PacketType::MARKET_DATA:
process_market_data();
break;
case PacketType::HEARTBEAT:
send_heartbeat_ack();
break;
}
// Continue reading
start();
}
void process_market_data() {
auto* market_data = reinterpret_cast<MarketDataPacket*>(buffer_);
// Convert to internal format with nanosecond timestamp
Tick tick{
.instrument_id = market_data->instrument_token,
.timestamp = get_nanoseconds(),
.last_price = market_data->last_traded_price,
.volume = market_data->volume_traded,
.bid_price = market_data->best_bid_price,
.bid_quantity = market_data->best_bid_quantity,
.ask_price = market_data->best_ask_price,
.ask_quantity = market_data->best_ask_quantity,
.open_interest = market_data->open_interest
};
// Send to processor (lock-free queue)
processor_.push_tick(tick);
}
};
4.2 Real-time Data Distribution System
java
// WebSocket distribution with Aeron (low-latency messaging)
public class MarketDataDistributor {
private final Aeron aeron;
private final Publication publication;
private final Map<String, Subscription> subscriptions;
private final RingBuffer<Tick> tickBuffer;
public MarketDataDistributor() {
// Initialize Aeron for ultra-low latency IPC
aeron = Aeron.connect(new Context()
.aeronDirectoryName("/dev/shm/aeron")
.threadingMode(ThreadingMode.DEDICATED));
// Create publication for broadcasting ticks
publication = aeron.addPublication(
"aeron:ipc",
MARKET_DATA_STREAM_ID);
// Lock-free ring buffer for tick processing
tickBuffer = new RingBuffer<>(TICK_BUFFER_SIZE);
}
public void distributeTick(Tick tick) {
// Serialize tick to binary format
ByteBuffer buffer = ByteBuffer.allocate(TICK_SIZE);
buffer.putLong(tick.timestamp);
buffer.putInt(tick.instrumentId);
buffer.putLong(tick.lastPrice);
buffer.putInt(tick.volume);
buffer.putLong(tick.bidPrice);
buffer.putInt(tick.bidQuantity);
buffer.putLong(tick.askPrice);
buffer.putInt(tick.askQuantity);
// Publish via Aeron (nanosecond latency)
long result = publication.offer(buffer);
if (result < 0) {
// Buffer backpressure - log and handle
handleBackpressure(tick);
}
// Also update WebSocket connections
updateWebSocketClients(tick);
}
private void updateWebSocketClients(Tick tick) {
// Get all subscribers for this instrument
Set<WebSocketSession> subscribers =
subscriptionManager.getSubscribers(tick.instrumentId);
// Prepare message (binary or JSON based on client capability)
byte[] message = prepareMessage(tick);
// Send to each subscriber (non-blocking)
for (WebSocketSession session : subscribers) {
if (session.isOpen()) {
session.getAsyncRemote().sendBinary(
ByteBuffer.wrap(message));
}
}
}
// Binary protocol for efficiency
private byte[] prepareMessage(Tick tick) {
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.put((byte) 0x01); // Message type: TICK
buffer.putInt(tick.instrumentId);
buffer.putLong(tick.lastPrice);
buffer.putInt(tick.volume);
buffer.putLong(tick.timestamp);
return buffer.array();
}
}
4.3 Order Management System (OMS)
python
# Order Service with event sourcing
class OrderService:
def __init__(self):
self.order_store = OrderStore()
self.risk_engine = RiskEngine()
self.exchange_gateway = ExchangeGateway()
self.event_bus = EventBus()
async def place_order(self, order_request: OrderRequest) -> OrderResponse:
# 1. Generate order ID
order_id = self.generate_order_id()
# 2. Create order event
order_created = OrderCreatedEvent(
order_id=order_id,
user_id=order_request.user_id,
instrument_token=order_request.instrument_token,
transaction_type=order_request.transaction_type,
quantity=order_request.quantity,
price=order_request.price,
order_type=order_request.order_type,
product_type=order_request.product_type,
timestamp=datetime.utcnow()
)
# 3. Validate and apply risk checks
risk_result = await self.risk_engine.validate_order(order_created)
if not risk_result.approved:
return OrderResponse(
order_id=order_id,
status=OrderStatus.REJECTED,
rejection_reason=risk_result.reason
)
# 4. Persist event
await self.order_store.append_event(order_created)
# 5. Route to exchange
if order_request.order_type == OrderType.MARKET:
exchange_response = await self.exchange_gateway.send_market_order(
order_created
)
else:
exchange_response = await self.exchange_gateway.send_limit_order(
order_created
)
# 6. Update order status
if exchange_response.accepted:
order_placed = OrderPlacedEvent(
order_id=order_id,
exchange_order_id=exchange_response.exchange_order_id,
timestamp=datetime.utcnow()
)
await self.order_store.append_event(order_placed)
# Publish event
await self.event_bus.publish(order_placed)
return OrderResponse(
order_id=order_id,
status=OrderStatus.PENDING,
exchange_order_id=exchange_response.exchange_order_id
)
else:
order_rejected = OrderRejectedEvent(
order_id=order_id,
reason=exchange_response.rejection_reason,
timestamp=datetime.utcnow()
)
await self.order_store.append_event(order_rejected)
return OrderResponse(
order_id=order_id,
status=OrderStatus.REJECTED,
rejection_reason=exchange_response.rejection_reason
)
async def get_order_state(self, order_id: str) -> OrderState:
# Reconstruct order state from event stream
events = await self.order_store.get_events(order_id)
order_state = OrderState()
for event in events:
if isinstance(event, OrderCreatedEvent):
order_state.apply_created(event)
elif isinstance(event, OrderPlacedEvent):
order_state.apply_placed(event)
elif isinstance(event, OrderPartiallyFilledEvent):
order_state.apply_partial_fill(event)
elif isinstance(event, OrderFilledEvent):
order_state.apply_fill(event)
elif isinstance(event, OrderCancelledEvent):
order_state.apply_cancelled(event)
elif isinstance(event, OrderRejectedEvent):
order_state.apply_rejected(event)
return order_state
async def cancel_order(self, order_id: str) -> bool:
# Get current order state
order_state = await self.get_order_state(order_id)
if order_state.status not in [OrderStatus.PENDING, OrderStatus.PARTIALLY_FILLED]:
return False
# Send cancellation to exchange
cancellation_response = await self.exchange_gateway.cancel_order(
order_state.exchange_order_id
)
if cancellation_response.success:
# Record cancellation event
cancelled_event = OrderCancelledEvent(
order_id=order_id,
cancelled_by='USER',
timestamp=datetime.utcnow()
)
await self.order_store.append_event(cancelled_event)
await self.event_bus.publish(cancelled_event)
return True
return False
4.4 Risk Management Engine
java
// Real-time risk checking with complex rules
@Component
public class RiskEngine {
@Autowired
private PositionRepository positionRepository;
@Autowired
private MarginCalculator marginCalculator;
@Autowired
private CircuitBreakerService circuitBreakerService;
@Autowired
private RateLimiter rateLimiter;
public RiskCheckResult validateOrder(Order order) {
List<RiskRule> rules = Arrays.asList(
new PositionLimitRule(),
new MarginRequirementRule(),
new CircuitBreakerRule(),
new OrderRateLimitRule(),
new BlackoutPeriodRule(),
new InstrumentSpecificRule(),
new ClientCategoryRule()
);
RiskContext context = new RiskContext(order);
// Load current positions
List<Position> positions = positionRepository
.findByUserId(order.getUserId());
context.setPositions(positions);
// Calculate margins
MarginRequirements margins = marginCalculator
.calculate(order, positions);
context.setMargins(margins);
// Execute rules in parallel
List<CompletableFuture<RiskRuleResult>> futures = rules.stream()
.map(rule -> CompletableFuture.supplyAsync(() ->
rule.evaluate(context)))
.collect(Collectors.toList());
// Wait for all rules to complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
// Combine results
List<RiskRuleResult> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
// Check for any violations
Optional<RiskRuleResult> violation = results.stream()
.filter(result -> !result.isPassed())
.findFirst();
if (violation.isPresent()) {
return RiskCheckResult.rejected(
violation.get().getReason(),
violation.get().getCode()
);
}
return RiskCheckResult.approved();
}
// Real-time margin calculation
public MarginRequirements calculate(Order order, List<Position> positions) {
MarginRequirements requirements = new MarginRequirements();
// SPAN margin calculation
SpanMargin spanMargin = calculateSpanMargin(order, positions);
requirements.setSpanMargin(spanMargin);
// Exposure margin
ExposureMargin exposureMargin = calculateExposureMargin(order, positions);
requirements.setExposureMargin(exposureMargin);
// VAR margin
VarMargin varMargin = calculateVarMargin(order, positions);
requirements.setVarMargin(varMargin);
// Extreme loss margin
ExtremeLossMargin elm = calculateExtremeLossMargin(order, positions);
requirements.setExtremeLossMargin(elm);
// Total margin requirement
BigDecimal totalMargin = spanMargin.getValue()
.add(exposureMargin.getValue())
.add(varMargin.getValue())
.add(elm.getValue());
requirements.setTotalMargin(totalMargin);
// Check against available margin
BigDecimal availableMargin = getAvailableMargin(order.getUserId());
requirements.setAvailableMargin(availableMargin);
requirements.setSufficient(availableMargin.compareTo(totalMargin) >= 0);
return requirements;
}
// Circuit breaker check
private boolean checkCircuitBreaker(String instrumentId, BigDecimal price) {
CircuitBreakerStatus status = circuitBreakerService
.getStatus(instrumentId);
if (status.isTriggered()) {
PriceBand band = status.getPriceBand();
return price.compareTo(band.getLowerLimit()) >= 0
&& price.compareTo(band.getUpperLimit()) <= 0;
}
return true;
}
}
5. Data Storage Architecture
5.1 Time-series Data Storage
sql
-- DolphinDB/QuestDB schema for high-frequency data
-- Create time-series partitioned tables
-- Ticks table (nanosecond precision)
CREATE TABLE ticks (
symbol SYMBOL CAPACITY 1000,
exchange SYMBOL CAPACITY 10,
timestamp TIMESTAMP,
price DOUBLE,
volume LONG,
bid_price DOUBLE,
bid_quantity LONG,
ask_price DOUBLE,
ask_quantity LONG,
trade_type SYMBOL CAPACITY 10,
trade_id LONG
) PARTITION BY DAY
WITH maxUncommittedRows=250000,
o3MaxLag=100ms;
-- OHLCV aggregated data
CREATE TABLE ohlcv_1min (
symbol SYMBOL,
timestamp TIMESTAMP,
open DOUBLE,
high DOUBLE,
low DOUBLE,
close DOUBLE,
volume LONG,
vwap DOUBLE
) PARTITION BY MONTH
INDEX (symbol);
-- Order book snapshots
CREATE TABLE order_book_snapshots (
symbol SYMBOL,
timestamp TIMESTAMP,
bids ARRAY(DOUBLE, LONG), -- Array of [price, quantity]
asks ARRAY(DOUBLE, LONG),
total_bid_quantity LONG,
total_ask_quantity LONG
) PARTITION BY DAY;
-- Create materialized views for common queries
CREATE MATERIALIZED VIEW daily_summary AS
SELECT
symbol,
date_trunc('day', timestamp) as date,
first(price) as open,
max(price) as high,
min(price) as low,
last(price) as close,
sum(volume) as volume,
sum(price * volume) / sum(volume) as vwap
FROM ticks
SAMPLE BY 1d
ALIGN TO CALENDAR;
-- Time-bucket queries
SELECT
bucket,
symbol,
first(price) as open,
max(price) as high,
min(price) as low,
last(price) as close,
sum(volume) as volume
FROM ticks
WHERE symbol = 'RELIANCE'
AND timestamp >= '2024-01-15'
SAMPLE BY 5m
ALIGN TO CALENDAR;
5.2 Orders & Portfolio Database
sql
-- CockroachDB schema for distributed ACID transactions
-- Orders table with partitioning
CREATE TABLE orders (
order_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL,
exchange_order_id STRING,
instrument_token INT NOT NULL,
transaction_type STRING NOT NULL, -- BUY/SELL
quantity INT NOT NULL,
disclosed_quantity INT,
price DECIMAL(18, 2),
trigger_price DECIMAL(18, 2),
order_type STRING NOT NULL, -- MARKET/LIMIT/SL/SL-M
product_type STRING NOT NULL, -- CNC/MIS/NRML
validity STRING DEFAULT 'DAY', -- DAY/IOC
status STRING NOT NULL, -- PENDING/COMPLETE/CANCELLED/REJECTED
status_message STRING,
filled_quantity INT DEFAULT 0,
pending_quantity INT,
average_price DECIMAL(18, 2),
exchange_timestamp TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
INDEX idx_user_created (user_id, created_at DESC),
INDEX idx_status (status, created_at),
FAMILY order_details (instrument_token, transaction_type, quantity, price),
FAMILY status_details (status, filled_quantity, average_price)
) PARTITION BY LIST (status) (
PARTITION pending VALUES IN ('PENDING', 'OPEN'),
PARTITION completed VALUES IN ('COMPLETE', 'REJECTED', 'CANCELLED')
);
-- Order executions/trades
CREATE TABLE trades (
trade_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
order_id UUID REFERENCES orders(order_id),
exchange_trade_id STRING NOT NULL,
instrument_token INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(18, 2) NOT NULL,
trade_type STRING NOT NULL, -- BUY/SELL
trade_timestamp TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
INDEX idx_order_trades (order_id, trade_timestamp),
INDEX idx_instrument_time (instrument_token, trade_timestamp)
);
-- Portfolio positions
CREATE TABLE positions (
position_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL,
instrument_token INT NOT NULL,
product_type STRING NOT NULL, -- CNC/MIS
quantity INT NOT NULL,
average_price DECIMAL(18, 2) NOT NULL,
last_price DECIMAL(18, 2),
pnl DECIMAL(18, 2),
unrealized_pnl DECIMAL(18, 2),
realized_pnl DECIMAL(18, 2),
day_pnl DECIMAL(18, 2),
margin_used DECIMAL(18, 2),
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, instrument_token, product_type),
INDEX idx_user_positions (user_id, updated_at DESC)
);
-- Ledger for fund management
CREATE TABLE ledger (
ledger_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL,
entry_type STRING NOT NULL, -- CREDIT/DEBIT
amount DECIMAL(18, 2) NOT NULL,
balance DECIMAL(18, 2) NOT NULL,
description STRING NOT NULL,
reference_id STRING, -- Order ID, Trade ID, etc.
reference_type STRING, -- ORDER, TRADE, PAYMENT, DIVIDEND
transaction_date DATE NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
INDEX idx_user_ledger (user_id, transaction_date DESC),
INDEX idx_user_balance (user_id, created_at DESC)
);
5.3 In-Memory Cache Architecture
python
# Multi-level caching strategy
class TradingCache:
def __init__(self):
# L1: Local cache (Guava/Caffeine)
self.local_cache = CacheBuilder.newBuilder()
.maximum_size(100000)
.expireAfterWrite(1, TimeUnit.SECONDS)
.recordStats()
.build()
# L2: Redis cluster
self.redis_client = RedisCluster(
startup_nodes=[
{'host': 'redis-1', 'port': 6379},
{'host': 'redis-2', 'port': 6379},
{'host': 'redis-3', 'port': 6379}
],
decode_responses=False
)
# L3: Hazelcast for distributed objects
self.hazelcast = hazelcast.HazelcastClient()
self.order_book_cache = self.hazelcast.get_map("order_books")
self.positions_cache = self.hazelcast.get_map("positions")
async def get_market_data(self, instrument_token: int) -> MarketData:
# Try L1 cache
cache_key = f"market_data:{instrument_token}"
data = self.local_cache.getIfPresent(cache_key)
if data:
return data
# Try L2 cache
data = await self.redis_client.get(cache_key)
if data:
# Deserialize and store in L1
market_data = self.deserialize_market_data(data)
self.local_cache.put(cache_key, market_data)
return market_data
# Fetch from database
market_data = await self.fetch_from_database(instrument_token)
# Update caches asynchronously
asyncio.create_task(self.update_caches(cache_key, market_data))
return market_data
async def get_order_book(self, instrument_token: int) -> OrderBook:
# Check distributed cache first
order_book = await self.order_book_cache.get(instrument_token)
if order_book and not self.is_stale(order_book.timestamp):
return order_book
# Reconstruct from market data feed
order_book = await self.reconstruct_order_book(instrument_token)
# Update cache asynchronously
asyncio.create_task(
self.order_book_cache.set_async(instrument_token, order_book)
)
return order_book
async def get_portfolio(self, user_id: str) -> Portfolio:
cache_key = f"portfolio:{user_id}"
# Check if portfolio needs recalculation
last_update = await self.redis_client.get(f"{cache_key}:updated_at")
if last_update and time.time() - float(last_update) < 5:
# Return cached portfolio
portfolio_data = await self.redis_client.get(cache_key)
return self.deserialize_portfolio(portfolio_data)
# Recalculate portfolio
portfolio = await self.calculate_portfolio(user_id)
# Cache with 5-second TTL
pipeline = self.redis_client.pipeline()
pipeline.set(cache_key, self.serialize_portfolio(portfolio))
pipeline.set(f"{cache_key}:updated_at", time.time())
pipeline.expire(cache_key, 10)
pipeline.expire(f"{cache_key}:updated_at", 10)
await pipeline.execute()
return portfolio
6. Real-time P&L Calculation
java
// Incremental P&L calculation with Spark Streaming
public class PnLCalculator {
public Dataset<Row> calculateRealTimePnL(
Dataset<Tick> marketData,
Dataset<Position> positions) {
// Join positions with latest market data
Dataset<Row> joined = positions.join(
marketData,
positions.col("instrument_token")
.equalTo(marketData.col("instrument_token")),
"left_outer"
);
// Calculate P&L for each position
Dataset<Row> pnl = joined.select(
positions.col("user_id"),
positions.col("instrument_token"),
positions.col("quantity"),
positions.col("average_price"),
marketData.col("last_price").as("current_price"),
expressions.lit(System.currentTimeMillis()).as("timestamp"),
// Unrealized P&L
when(positions.col("transaction_type").equalTo("BUY"),
(marketData.col("last_price")
.minus(positions.col("average_price")))
.multiply(positions.col("quantity"))
).otherwise(
(positions.col("average_price")
.minus(marketData.col("last_price")))
.multiply(positions.col("quantity"))
).as("unrealized_pnl"),
// Day P&L (from start of day)
calculateDayPnl(positions, marketData).as("day_pnl")
);
// Aggregate by user
Dataset<Row> aggregated = pnl.groupBy("user_id")
.agg(
sum("unrealized_pnl").as("total_unrealized_pnl"),
sum("day_pnl").as("total_day_pnl"),
countDistinct("instrument_token").as("positions_count")
);
return aggregated;
}
// Windowed calculation for charts
public Dataset<Row> calculatePnLHistory(
String userId,
String timeframe) {
// Get trade history
Dataset<Trade> trades = spark.read()
.table("trades")
.filter(col("user_id").equalTo(userId))
.as(Encoders.bean(Trade.class));
// Calculate realized P&L per trade
Dataset<Row> tradePnl = trades.select(
col("instrument_token"),
col("trade_timestamp"),
col("quantity"),
col("price"),
col("trade_type"),
// FIFO cost calculation
calculateFifoCost(
col("instrument_token"),
col("trade_timestamp"),
col("quantity"),
col("trade_type")
).as("cost_price"),
// Realized P&L
when(col("trade_type").equalTo("BUY"),
lit(0.0)
).otherwise(
(col("price").minus(col("cost_price")))
.multiply(col("quantity"))
).as("realized_pnl")
);
// Group by time window
Dataset<Row> windowedPnl = tradePnl
.withWatermark("trade_timestamp", "1 hour")
.groupBy(
window(col("trade_timestamp"), timeframe),
col("instrument_token")
)
.agg(
sum("realized_pnl").as("period_pnl"),
sum("quantity").as("volume")
);
return windowedPnl;
}
}
7. Charting & Technical Analysis Engine
typescript
// WebAssembly-based charting engine for high-performance
class ChartEngine {
private wasmModule: WebAssembly.Module;
private chartBuffer: SharedArrayBuffer;
async init() {
// Load WebAssembly module for calculations
this.wasmModule = await WebAssembly.compile(
await fetch('/chart-engine.wasm')
);
// Shared buffer for efficient data transfer
this.chartBuffer = new SharedArrayBuffer(1024 * 1024); // 1MB
// Initialize worker pool
this.workers = this.createWorkerPool(4);
}
async renderChart(
instrumentToken: number,
timeframe: string,
indicators: Indicator[]
): Promise<ChartData> {
// Get OHLC data
const ohlcData = await this.fetchOHLC(instrumentToken, timeframe);
// Calculate indicators in parallel
const indicatorPromises = indicators.map(indicator =>
this.calculateIndicator(ohlcData, indicator)
);
const indicatorResults = await Promise.all(indicatorPromises);
// Prepare WebAssembly memory
const wasmMemory = new WebAssembly.Memory({
initial: 256,
maximum: 512
});
// Copy data to WebAssembly memory
const dataView = new DataView(wasmMemory.buffer);
this.copyToWasmMemory(dataView, ohlcData);
// Execute WebAssembly for heavy calculations
const instance = await WebAssembly.instantiate(
this.wasmModule,
{ env: { memory: wasmMemory } }
);
// Calculate support/resistance levels
const srLevels = instance.exports.calculateSupportResistance(
ohlcData.length,
20 // Lookback period
);
// Calculate Fibonacci retracement
const fibLevels = instance.exports.calculateFibonacci(
ohlcData.high,
ohlcData.low
);
// Generate candle patterns
const patterns = instance.exports.detectCandlePatterns(
ohlcData.open,
ohlcData.high,
ohlcData.low,
ohlcData.close
);
return {
ohlc: ohlcData,
indicators: indicatorResults,
supportResistance: srLevels,
fibonacci: fibLevels,
patterns: patterns,
volumeProfile: await this.calculateVolumeProfile(ohlcData)
};
}
private async calculateIndicator(
data: OHLCData,
indicator: Indicator
): Promise<IndicatorData> {
switch (indicator.type) {
case 'SMA':
return this.calculateSMA(data.close, indicator.period);
case 'EMA':
return this.calculateEMA(data.close, indicator.period);
case 'RSI':
return this.calculateRSI(data.close, indicator.period);
case 'MACD':
return this.calculateMACD(data.close);
case 'BollingerBands':
return this.calculateBollingerBands(data.close, indicator.period);
case 'VWAP':
return this.calculateVWAP(data);
case 'ATR':
return this.calculateATR(data);
default:
throw new Error(`Unknown indicator: ${indicator.type}`);
}
}
private calculateSMA(data: number[], period: number): number[] {
const sma: number[] = [];
for (let i = period - 1; i < data.length; i++) {
const sum = data.slice(i - period + 1, i + 1)
.reduce((a, b) => a + b, 0);
sma.push(sum / period);
}
return sma;
}
// Volume Weighted Average Price
private calculateVWAP(data: OHLCData): number[] {
const vwap: number[] = [];
let cumulativeTPV = 0;
let cumulativeVolume = 0;
for (let i = 0; i < data.close.length; i++) {
const typicalPrice = (data.high[i] + data.low[i] + data.close[i]) / 3;
cumulativeTPV += typicalPrice * data.volume[i];
cumulativeVolume += data.volume[i];
vwap.push(cumulativeTPV / cumulativeVolume);
}
return vwap;
}
}
8. News & Sentiment Analysis
python
class SentimentAnalyzer:
def __init__(self):
self.nlp_pipeline = transformers.pipeline(
"sentiment-analysis",
model="ProsusAI/finbert",
device=0 if torch.cuda.is_available() else -1
)
self.news_feed = NewsFeed()
self.social_monitor = SocialMonitor()
self.redis_client = redis.Redis()
async def analyze_news_sentiment(self, symbol: str) -> SentimentScore:
# Fetch recent news
news_articles = await self.news_feed.get_articles(symbol, limit=50)
# Analyze each article
sentiment_scores = []
for article in news_articles:
# Extract text
text = f"{article.title}. {article.summary}"
# Get sentiment
result = self.nlp_pipeline(text)[0]
sentiment_scores.append({
'article_id': article.id,
'sentiment': result['label'],
'score': result['score'],
'timestamp': article.published_at
})
# Calculate weighted average
weighted_score = self.calculate_weighted_sentiment(sentiment_scores)
# Store in cache
cache_key = f"sentiment:{symbol}:{datetime.utcnow().strftime('%Y%m%d')}"
await self.redis_client.setex(
cache_key,
3600, # 1 hour TTL
json.dumps(weighted_score)
)
return weighted_score
async def monitor_social_sentiment(self, symbol: str):
# Monitor Twitter, Reddit, Telegram
social_posts = await self.social_monitor.get_posts(symbol)
# Real-time sentiment analysis
for post in social_posts:
sentiment = self.analyze_social_post(post)
# Publish to Kafka for real-time dashboard
await self.kafka_producer.send(
'social-sentiment',
key=symbol,
value={
'symbol': symbol,
'platform': post.platform,
'sentiment': sentiment,
'influence_score': post.influence_score,
'timestamp': post.timestamp
}
)
# Update rolling sentiment
await self.update_rolling_sentiment(symbol, sentiment)
def calculate_weighted_sentiment(self, scores: List[dict]) -> dict:
# Weight by recency and source credibility
total_weight = 0
weighted_sum = 0
for score in scores:
# Calculate time weight (exponential decay)
hours_old = (datetime.utcnow() - score['timestamp']).total_seconds() / 3600
time_weight = math.exp(-hours_old / 24) # Half-life of 24 hours
# Source credibility weight
source_weight = self.get_source_weight(score.get('source', 'unknown'))
# Sentiment to numeric (-1 to 1)
sentiment_numeric = self.sentiment_to_numeric(score['sentiment'])
# Combine weights
weight = time_weight * source_weight * score['score']
weighted_sum += sentiment_numeric * weight
total_weight += weight
overall_sentiment = weighted_sum / total_weight if total_weight > 0 else 0
return {
'score': overall_sentiment,
'confidence': total_weight / len(scores) if scores else 0,
'sample_size': len(scores),
'timestamp': datetime.utcnow()
}
async def get_market_sentiment(self) -> MarketSentiment:
# Aggregate sentiment across all symbols
symbols = await self.get_top_symbols()
sentiment_tasks = [
self.analyze_news_sentiment(symbol)
for symbol in symbols
]
all_sentiments = await asyncio.gather(*sentiment_tasks)
# Calculate market-wide sentiment
market_sentiment = {
'bullish': sum(1 for s in all_sentiments if s['score'] > 0.2),
'bearish': sum(1 for s in all_sentiments if s['score'] < -0.2),
'neutral': sum(1 for s in all_sentiments if -0.2 <= s['score'] <= 0.2),
'average_score': sum(s['score'] for s in all_sentiments) / len(all_sentiments),
'timestamp': datetime.utcnow()
}
return market_sentiment
9. Alert System
typescript
class AlertEngine {
private ruleEngine: RuleEngine;
private notificationService: NotificationService;
private alertStore: AlertStore;
async createAlert(userId: string, alertRule: AlertRule): Promise<string> {
const alertId = this.generateAlertId();
// Validate rule
const validation = this.validateAlertRule(alertRule);
if (!validation.valid) {
throw new Error(`Invalid alert rule: ${validation.error}`);
}
// Store alert
await this.alertStore.createAlert({
id: alertId,
userId,
rule: alertRule,
status: 'ACTIVE',
created: new Date(),
lastTriggered: null
});
// Subscribe to relevant market data
await this.subscribeToMarketData(alertId, alertRule);
return alertId;
}
async evaluateAlerts(tick: Tick): Promise<void> {
// Get active alerts for this instrument
const alerts = await this.alertStore.getAlertsForInstrument(
tick.instrumentToken
);
// Evaluate each alert in parallel
const evaluations = alerts.map(alert =>
this.evaluateAlert(alert, tick)
);
const results = await Promise.all(evaluations);
// Trigger notifications for matched alerts
for (const result of results) {
if (result.shouldTrigger) {
await this.triggerAlert(result.alert, tick);
}
}
}
private async evaluateAlert(alert: Alert, tick: Tick): Promise<EvaluationResult> {
const rule = alert.rule;
let conditionMet = false;
switch (rule.condition.type) {
case 'PRICE_ABOVE':
conditionMet = tick.lastPrice > rule.condition.value;
break;
case 'PRICE_BELOW':
conditionMet = tick.lastPrice < rule.condition.value;
break;
case 'PERCENT_CHANGE':
const change = await this.calculatePercentChange(
tick.instrumentToken,
rule.condition.timeframe
);
conditionMet = change >= rule.condition.value;
break;
case 'VOLUME_SPIKE':
const avgVolume = await this.getAverageVolume(
tick.instrumentToken,
rule.condition.lookback
);
conditionMet = tick.volume > avgVolume * rule.condition.multiplier;
break;
case 'INDICATOR_CROSSOVER':
const indicatorValues = await this.getIndicatorValues(
tick.instrumentToken,
rule.condition.indicator
);
conditionMet = this.checkCrossover(indicatorValues);
break;
case 'NEWS_SENTIMENT':
const sentiment = await this.getSentiment(
tick.instrumentToken
);
conditionMet = sentiment.score > rule.condition.threshold;
break;
}
// Check cooldown period
const shouldTrigger = conditionMet &&
this.shouldTriggerAfterCooldown(alert);
return {
alert,
shouldTrigger,
conditionMet,
tick
};
}
private async triggerAlert(alert: Alert, tick: Tick): Promise<void> {
// Update alert state
await this.alertStore.updateAlert(alert.id, {
lastTriggered: new Date(),
triggerCount: (alert.triggerCount || 0) + 1
});
// Prepare notification
const notification = {
type: 'ALERT',
userId: alert.userId,
title: 'Alert Triggered',
body: this.formatAlertMessage(alert, tick),
data: {
alertId: alert.id,
instrumentToken: tick.instrumentToken,
price: tick.lastPrice,
condition: alert.rule.condition.type,
timestamp: tick.timestamp
},
priority: 'HIGH'
};
// Send notification via all channels
await this.notificationService.sendNotification(notification);
// Log alert for analytics
await this.logAlertTrigger(alert, tick);
// Check if alert should be deactivated
if (alert.rule.maxTriggers &&
alert.triggerCount >= alert.rule.maxTriggers) {
await this.deactivateAlert(alert.id);
}
}
private formatAlertMessage(alert: Alert, tick: Tick): string {
const symbol = this.getSymbol(tick.instrumentToken);
const price = tick.lastPrice.toFixed(2);
switch (alert.rule.condition.type) {
case 'PRICE_ABOVE':
return `${symbol} crossed above ${alert.rule.condition.value}. Current: ${price}`;
case 'PRICE_BELOW':
return `${symbol} crossed below ${alert.rule.condition.value}. Current: ${price}`;
case 'VOLUME_SPIKE':
return `Volume spike detected in ${symbol}. Volume: ${tick.volume}`;
default:
return `Alert triggered for ${symbol} at ${price}`;
}
}
}
10. Risk & Compliance Monitoring
java
@Component
public class ComplianceMonitor {
private static final Logger logger = LoggerFactory.getLogger(ComplianceMonitor.class);
@Autowired
private TradeRepository tradeRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private AlertService alertService;
@Scheduled(fixedDelay = 60000) // Run every minute
public void monitorCompliance() {
// 1. Check for insider trading patterns
detectInsiderTrading();
// 2. Monitor for market manipulation
detectMarketManipulation();
// 3. Check concentration limits
checkConcentrationLimits();
// 4. Verify KYC compliance
verifyKYCCompliance();
// 5. Monitor suspicious activities
monitorSuspiciousActivities();
}
private void detectInsiderTrading() {
// Get corporate announcement times
List<CorporateAnnouncement> announcements =
corporateAnnouncementService.getRecentAnnouncements();
for (CorporateAnnouncement announcement : announcements) {
// Look for unusual trading before announcement
List<Trade> suspiciousTrades = tradeRepository
.findTradesAroundAnnouncement(
announcement.getSymbol(),
announcement.getAnnouncementTime().minusHours(24),
announcement.getAnnouncementTime(),
3 // Standard deviations threshold
);
if (!suspiciousTrades.isEmpty()) {
// Generate compliance alert
ComplianceAlert alert = ComplianceAlert.builder()
.type("INSIDER_TRADING_SUSPECTED")
.severity("HIGH")
.description("Unusual trading detected before corporate announcement")
.symbol(announcement.getSymbol())
.announcementId(announcement.getId())
.suspiciousTrades(suspiciousTrades)
.detectedAt(Instant.now())
.build();
alertService.raiseComplianceAlert(alert);
}
}
}
private void detectMarketManipulation() {
// Check for wash trading
detectWashTrading();
// Check for spoofing
detectSpoofing();
// Check for layering
detectLayering();
// Check for pump and dump
detectPumpAndDump();
}
private void detectWashTrading() {
// Find trades where buyer and seller are same beneficial owner
List<WashTradeCandidate> candidates = tradeRepository
.findPotentialWashTrades();
for (WashTradeCandidate candidate : candidates) {
// Analyze trading patterns
if (isWashTradePattern(candidate)) {
ComplianceAlert alert = ComplianceAlert.builder()
.type("WASH_TRADING_DETECTED")
.severity("CRITICAL")
.description("Potential wash trading detected")
.symbol(candidate.getSymbol())
.involvedAccounts(Arrays.asList(
candidate.getBuyerId(),
candidate.getSellerId()
))
.evidence(candidate.getTrades())
.detectedAt(Instant.now())
.build();
alertService.raiseComplianceAlert(alert);
// Freeze accounts temporarily
accountService.freezeAccounts(
Arrays.asList(candidate.getBuyerId(), candidate.getSellerId()),
"Wash trading investigation"
);
}
}
}
private void checkConcentrationLimits() {
// Check single stock concentration
checkSingleStockConcentration();
// Check sector concentration
checkSectorConcentration();
// Check portfolio VAR limits
checkPortfolioVarLimits();
}
private void checkSingleStockConcentration() {
Map<String, List<Position>> positionsByUser = positionRepository
.getPositionsGroupedByUser();
for (Map.Entry<String, List<Position>> entry : positionsByUser.entrySet()) {
String userId = entry.getKey();
List<Position> positions = entry.getValue();
// Calculate concentration for each position
for (Position position : positions) {
double concentration = calculateConcentration(position, positions);
if (concentration > 0.25) { // 25% concentration limit
ComplianceAlert alert = ComplianceAlert.builder()
.type("CONCENTRATION_LIMIT_EXCEEDED")
.severity("MEDIUM")
.description("Single stock concentration exceeded limit")
.userId(userId)
.symbol(position.getSymbol())
.concentration(concentration)
.limit(0.25)
.detectedAt(Instant.now())
.build();
alertService.raiseComplianceAlert(alert);
// Prevent further buying
riskEngine.addTradingRestriction(
userId,
position.getSymbol(),
"BUY",
"Concentration limit exceeded"
);
}
}
}
}
// Real-time order surveillance
public void monitorOrder(Order order) {
// Check for spoofing patterns
if (isSpoofingPattern(order)) {
// Cancel order
exchangeGateway.cancelOrder(order.getExchangeOrderId());
// Penalize user
userService.penalizeUser(order.getUserId(),
"Spoofing detected",
PenaltyType.TRADING_SUSPENSION);
}
// Check for layering
if (isLayeringPattern(order)) {
// Cancel all orders from user
orderService.cancelAllOrders(order.getUserId());
// Report to exchange
exchangeGateway.reportSuspiciousActivity(order);
}
}
}
11. Scalability & Performance Optimizations
11.1 WebSocket Connection Management
go
// Connection pooling with goroutines
type ConnectionPool struct {
connections map[string]*Connection
mu sync.RWMutex
broadcast chan Message
capacity int
}
func NewConnectionPool(capacity int) *ConnectionPool {
return &ConnectionPool{
connections: make(map[string]*Connection),
broadcast: make(chan Message, 100000),
capacity: capacity,
}
}
func (p *ConnectionPool) Add(conn *Connection) error {
p.mu.Lock()
defer p.mu.Unlock()
if len(p.connections) >= p.capacity {
return errors.New("connection pool full")
}
p.connections[conn.ID] = conn
// Start reader/writer goroutines
go p.handleConnection(conn)
return nil
}
func (p *ConnectionPool) handleConnection(conn *Connection) {
defer p.Remove(conn.ID)
for {
select {
case msg, ok := <-conn.ReadChan():
if !ok {
return
}
p.processMessage(conn, msg)
case msg := <-p.broadcast:
if conn.ShouldReceive(msg) {
conn.WriteChan() <- msg
}
}
}
}
func (p *ConnectionPool) Broadcast(msg Message) {
// Non-blocking send with timeout
select {
case p.broadcast <- msg:
case <-time.After(100 * time.Millisecond):
log.Warn("Broadcast channel full, dropping message")
}
}
11.2 Database Query Optimization
sql
-- Materialized views for common queries
CREATE MATERIALIZED VIEW portfolio_summary_daily AS
SELECT
p.user_id,
p.instrument_token,
DATE(p.updated_at) as date,
SUM(p.quantity) as total_quantity,
AVG(p.average_price) as avg_price,
SUM(p.quantity * m.last_price) as current_value,
SUM(p.quantity * (m.last_price - p.average_price)) as unrealized_pnl,
SUM(CASE
WHEN p.product_type = 'MIS' THEN p.margin_used
ELSE 0
END) as margin_used
FROM positions p
JOIN market_data m ON p.instrument_token = m.instrument_token
WHERE p.updated_at >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY p.user_id, p.instrument_token, DATE(p.updated_at)
WITH DATA;
-- Refresh on schedule
CREATE OR REPLACE FUNCTION refresh_portfolio_summary()
RETURNS TRIGGER AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY portfolio_summary_daily;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- Index optimization
CREATE INDEX idx_trades_user_time
ON trades(user_id, trade_timestamp DESC)
INCLUDE (instrument_token, quantity, price, trade_type);
-- Partitioned tables
CREATE TABLE orders_2024_01 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
-- Columnar storage for analytics
CREATE TABLE market_data_analytics (
instrument_token INT,
date DATE,
open DECIMAL,
high DECIMAL,
low DECIMAL,
close DECIMAL,
volume BIGINT
) WITH (
orientation = COLUMN,
compression = HIGH
);
12. Disaster Recovery & High Availability
yaml
# Multi-region deployment with active-active
regions:
- name: ap-south-1 # Primary (Mumbai)
weight: 60
databases:
- type: cockroachdb
nodes: 9
replication: 3
- name: ap-southeast-1 # Secondary (Singapore)
weight: 40
databases:
- type: cockroachdb
nodes: 6
replication: 2
# Zero-downtime failover
failover_strategy:
automatic: true
threshold: 2 # Failed health checks
detection_interval: 10s
failover_delay: 30s
# Data replication
data_replication:
market_data:
replication_factor: 3
consistency_level: LOCAL_QUORUM
durability: DISK
orders:
replication_factor: 5
consistency_level: STRONG
sync_replication: true
portfolio:
replication_factor: 3
consistency_level: EVENTUAL
async_replication: true
# Backup strategy
backup:
frequency: continuous
retention: 7 years
encryption: AES-256
locations:
- s3://backups-primary
- s3://backups-secondary
verification: automatic
Key Challenges & Solutions:
-
Ultra-low Latency:
-
Use kernel bypass networking (DPDK)
-
FPGA/ASIC for order matching
-
Colocation with exchange servers
-
Custom TCP stack optimization
-
-
Order Matching & Execution:
-
Hardware-accelerated order matching engine
-
Smart order routing across multiple exchanges
-
IOC (Immediate-or-Cancel) order handling
-
Price-time priority matching
-
-
Market Data Volume:
-
Compression algorithms (zstd, snappy)
-
Incremental data transmission
-
Client-side data aggregation
-
Bandwidth throttling per client
-
-
Regulatory Compliance:
-
Real-time surveillance system
-
Audit trail with tamper-proof logging
-
SEBI-approved risk management
-
Automated regulatory reporting
-
-
Fraud Detection:
-
ML-based anomaly detection
-
Real-time pattern recognition
-
Behavioral analysis
-
Multi-factor authentication
-
Advanced Features Roadmap:
-
Algorithmic Trading:
-
Strategy backtesting engine
-
Paper trading simulator
-
API for algorithmic trading
-
Co-located execution servers
-
-
Derivatives Trading:
-
Options chain analysis
-
Greeks calculator
-
Margin calculator for derivatives
-
Strategy builder
-
-
Global Markets:
-
International exchange connectivity
-
Multi-currency support
-
Cross-border compliance
-
Timezone-aware trading
-
-
Social Trading:
-
Copy trading
-
Leaderboards
-
Strategy marketplace
-
Social sentiment integration
-
-
AI/ML Features:
-
Predictive analytics
-
Automated portfolio rebalancing
-
Sentiment-based trading signals
-
Risk-adjusted return optimization
-
-
Blockchain Integration:
-
Tokenized securities
-
Smart contract-based settlement
-
Decentralized order book
-
Cross-chain trading
-
This system design provides a comprehensive, high-performance trading platform capable of handling millions of users with sub-millisecond latency, ensuring regulatory compliance while providing advanced trading features and analytics.
Comments (0)
No comments yet. Be the first to comment!