~/blog/engineering/saras-prop-trading-engine
// dispatch — Engineering
ENGINEERING04/11/2614 MIN

Building a Prop Trading Engine on Advisor Intelligence

How we built an end-to-end automated prop trading system at Saras — from ingesting 400+ daily trade ideas to live execution on Dhan, with a set of novel metrics that actually generate profits.

Introduction

Saras tracks over 400 daily trade ideas from SEBI-registered financial advisors — scraping Telegram channels, Twitter, YouTube, and research reports at scale. The platform gives retail traders a data-driven lens to evaluate advisor quality before following them.

But there's a natural question that follows: if we're already computing who the best advisors are, why not trade on it?

That's the question that kicked off this project. I led the end-to-end design and build of Saras's internal prop trading system — a fully automated pipeline that ingests advisor signals, scores them against a set of custom analytics, and executes live orders through Dhan's broker API.

This post is a deep dive into the architecture, the unique metrics we invented along the way, and the engineering decisions that made it production-ready.


The Problem We Were Solving

Saras aggregates trade signals from hundreds of SEBI-registered advisors across India. These signals arrive via MongoDB — parsed from Telegram, Twitter, and other social channels by our data collection layer. Each signal has:

  • Entry price, stoploss, target(s)
  • Instrument (NSE equity, Index Futures, Stock Options, etc.)
  • Advisor identity and historical track record

The challenge isn't finding signals. It's knowing which signals to trust and how much capital to deploy.

At the start, we'd evaluate advisors informally — win rate, average return. But these basic metrics lie. An advisor with 80% win rate and -30% drawdown on a 1 Crore portfolio is not a good advisor. We needed something better.


System Architecture

The system has two major loops: an analytics pipeline (offline, runs on every ingestion) and a live trading engine (real-time, runs during market hours).

◆ DIAGRAMMERMAID

Every component has a clear responsibility. The analytics pipeline runs asynchronously via RQ (Redis Queue) and never blocks the API. The trading engine runs as a long-lived async Python process, separate from the API entirely.


The Data Pipeline

Before any metrics or trading rules can exist, raw advisor signals need to be cleaned and enriched. Our pipeline runs 9 sequential steps on every CSV/JSON upload:

◆ DIAGRAMMERMAID

Feature Engineering is where the interesting work happens. For every trade, we derive:

  • Position sizing (lots, margin, leverage) against a ₹5L corpus
  • Holding period in hours and days
  • Time session (Morning / Midday / Afternoon / After Hours in IST)
  • Market context: is_intraday, is_expiry_day (Thursday)
  • Outcome flags: is_winning_trade, target_hit, stoploss_hit

Regime Mapping is handled by a separate Go microservice we call the regime service. It classifies each trading day into one of five regimes:

  • Trending Bullish
  • Trending Bearish
  • Sideways
  • Sideways Bullish
  • Sideways Bearish

This dimension matters a lot. An advisor who crushes it in Trending Bullish regimes might be terrible in sideways markets. Segmenting by regime is what lets us build trustworthy rules.


The 6-Dimensional Rule Engine

Most systems look at advisor performance at one level: "how does advisor X do overall?" We go deeper.

Every trading rule is the intersection of 6 dimensions:

Advisor × Category × Period × Regime × Day of Week × Time Session
DimensionValues
AdvisorAny SEBI-RA tracked on Saras
CategoryNSE_EQ, Index Futures, Stock Futures, Index Options, Stock Options
PeriodIntraday, Swing, Positional
RegimeTrending Bullish, Trending Bearish, Sideways, Sideways Bullish, Sideways Bearish
Day of WeekMonday–Friday
Time SessionMorning (09:15–11:30), Midday (11:30–13:30), Afternoon (13:30–15:30)

A rule only activates for live trading if it clears all quality gates: minimum trades, win rate, Sharpe ratio, profit factor, and max drawdown thresholds — all category-specific.

We also generate mini rules (Advisor × Category × Period, optionally with Regime) as a broader signal when full 6D data is sparse.

◆ DIAGRAMMERMAID

The Metrics That Actually Matter

Here's where things get interesting. Standard metrics — win rate, average return — are useful but incomplete. We built several novel metrics that have proven to be much better predictors of live performance.

1. Corpus Replay System

This is the centerpiece of our analytics engine.

The problem with legacy metrics: If you evaluate each trade independently against a fixed ₹5L capital, the drawdown numbers are fictional. Concurrent trades don't compete for the same capital pool, so you never see the real risk.

Our solution: Replay all of an advisor's trades over a 90-day rolling window against a shared ₹1 Crore virtual corpus. Trades compete for capital, margins are deducted on entry, and the corpus value updates on every exit.

◆ DIAGRAMMERMAID

From this single shared equity curve, we derive:

  • True Maximum Drawdown (MDD): Peak-to-trough decline on the actual corpus, not compounded trade percentages
  • Corpus-based Sharpe Ratio: Calculated from real portfolio returns, not isolated per-trade excess returns
  • Corpus Volatility: Standard deviation of per-trade corpus changes — reflects concurrent trade effects

This also handles capital exhaustion: if 20 concurrent Index Futures trades each need ₹5L margin, only 20 can execute. Trades 21+ are skipped and recorded as trades_skipped_capital. The legacy system would pretend they all ran — wildly overstating the advisor's capacity.

MetricLegacyCorpus-Based
Capital ModelIsolated ₹5L per tradeShared ₹1Cr pool
MDDCompounded return % (fictional)True corpus drawdown
Concurrent tradesIndependent, no interactionCompete for capital
Sharpe RatioFrom isolated returnsFrom portfolio returns

2. MAE/MFE — Measuring Trade Quality, Not Just Outcomes

Maximum Adverse Excursion (MAE) and Maximum Favorable Excursion (MFE) are intra-trade risk metrics powered by tick-by-tick price data from our AccelPix service.

MAE tells you: how much pain did you endure before the trade closed?

MFE tells you: what was the peak opportunity available during the trade?

Both are expressed as R-multiples (multiples of initial risk: |entry - stoploss|):

MAE % = (worst price move against position / R) × 100
MFE % = (best price move in favor of position / R) × 100

The ratio of these two gives us the Pain/Reward Ratio — a better signal than win rate alone:

Pain/Reward = Average MAE % ÷ Average MFE %

A Pain/Reward below 0.3 is excellent. Above 0.8 is a strategy that requires enduring large drawdowns to capture modest gains. Not something you want to trade in size.

We also compute percentile distributions (75th, 90th) for both metrics — useful for understanding tail risk in a rule.

3. Post-SL and Post-Target Extensions

These metrics answer a question most systems never even ask:

  • Post-SL Extension: After a stop loss was hit, how much further did price move against the position in the next 30 minutes?
  • Post-Target Extension: After a target was hit, how much further did price move in favor in the next 30 minutes?
Post-SL Extension = (stoploss - worst_price_in_30min_window) / entry_price × 100

Consistently negative Post-SL extensions indicate stop hunting — price is triggering stops and then reversing. This tells you to either widen stops or move them off obvious levels.

Consistently large Post-Target extensions mean you're leaving money on the table — trailing stops would serve better than fixed targets.

These insights directly feed our execution strategy: advisors with high stop-hunt signals get their SL levels adjusted in live trading.

4. WSE/WTE — Trades Without Stops or Targets

Not all advisors use explicit stop losses. For trades where stoploss is NULL, we compute Without SL Extension (WSE) — the worst adverse price move during the trade's lifetime, expressed as a percentage of entry price.

Similarly, Without Target Extension (WTE) measures the peak favorable move for trades without a defined target.

WSE % = (entry_price - lowest_tick) / entry_price × 100   [for BUY positions]
WTE % = (highest_tick - entry_price) / entry_price × 100  [for BUY positions]

These metrics exist because ignoring trades-without-SL from your risk model is dangerous. If the average WSE for an advisor is 15%, you need to factor that into your position sizing even if their explicit SL-hit rate looks fine.

5. Recency Weighting (Exponential Decay)

Performance that happened 6 months ago should count less than last week. We use exponential decay with a 10-day half-life for all weighted metrics:

weight = exp(-ln(2) / half_life × days_ago)

A trade from today  → weight = 1.0
A trade from 10 days ago → weight = 0.5
A trade from 20 days ago → weight = 0.25

This produces two parallel sets of metrics:

  • Unweighted: Raw historical average
  • Weighted: Recent-trend-adjusted

The gap between them — WWR-UWR Divergence (Weighted Win Rate minus Unweighted Win Rate) — is one of our most predictive signals.

6. Trust Flag Classification

Divergence = Weighted Win Rate - Unweighted Win Rate
DivergenceTrust FlagMeaning
> +5%UNTRUSTEDSuspiciously hot streak
0% to +5%TRUSTEDImproving trend
-5% to 0%WATCHLISTSlight decline
< -5%UNTRUSTEDDeteriorating performance

Both extremes are flagged as untrusted. A large positive divergence often indicates a lucky recent streak that won't persist, not a genuine improvement.

7. Crowd Confidence Scoring

This is the most unique metric we built — and it's specific to the prop trading flow.

Saras tracks signals from many advisors simultaneously. If 12 out of 14 advisors with open NIFTY positions are bullish right now, that consensus carries real information. We built a per-underlying crowd ledger that computes live sentiment:

PYTHON
bullish_confidence = open_bullish_count / (open_bullish_count + open_bearish_count)
crowd_confidence_score = max(bullish_confidence, bearish_confidence)

The score is computed from open positions only — not historical or closed ones. This reflects current live market conviction.

When a qualifying trade aligns with the crowd direction AND the crowd score exceeds the threshold (default 75%), it receives a capital multiplier boost.

Direction classification handles the option direction inversion correctly:

CategoryPositionOption TypeSentiment
Index/Stock FuturesBUYBullish
Index/Stock OptionsBUYCEBullish
Index/Stock OptionsBUYPEBearish
Index/Stock OptionsSELLCEBearish
Index/Stock OptionsSELLPEBullish

Getting this table right matters — a PE option BUY is a bearish bet, not bullish.


The Live Trading Engine

The trading engine runs as a long-lived async Python process — entirely separate from the API. It manages 6 concurrent tasks:

◆ DIAGRAMMERMAID

Pre-Market Setup (08:45 IST)

Every trading day, before the trading worker starts, a pre-market worker runs a 5-step sequential setup:

  1. Renew Dhan token (API call + TOTP fallback via pyotp)
  2. Download and load the Dhan scrip master into Redis
  3. Validate ticker → security_id mappings
  4. Run eligibility evaluation (normal + reverse)
  5. Initialize daily corpus from yesterday's remaining capital + P&L

When all 5 steps succeed, it sets a Redis readiness flag. The trading worker won't start processing signals until this flag is set.

Signal Processing Flow

When a new signal arrives via MongoDB change stream:

◆ DIAGRAMMERMAID

Crowd sentiment recording happens before any rule matching or gating. This is intentional — we want the crowd ledger to reflect all advisor activity, not just signals that pass our filters.

Dhan Super Orders

For each trade, we use Dhan's Super Order API — a single atomic request that places three legs simultaneously:

  1. Entry order (limit or market)
  2. Stop-loss leg (automatically triggers on fill)
  3. Target leg (automatically triggers on fill)

This means once the entry fills, both the SL and target are already on the exchange. There's no window where the position is unprotected.

Multi-Target Execution and Trailing SL Chain

When an advisor posts multiple targets for the same signal (e.g., "NIFTY 24500 T1: 24600, T2: 24700, T3: 24800"), we split capital equally across legs. Each leg is a separate Super Order — same trade_group_id, different leg_number.

When the first target hits, we implement a trailing SL chain:

◆ DIAGRAMMERMAID

For a BUY with 3 targets at 142, 171, 200 (entry ~127):

  • T1 (142) hits → siblings' SL moves to 127 (entry, now risk-free)
  • T2 (171) hits → remaining sibling's SL moves to 142 (previous target)
  • Final trade exits at 200 or the tightened 142 SL, never below breakeven

The Reverse Strategy

Some advisors are consistently wrong — and that's valuable too.

We maintain a separate adverse eligibility config with inverted thresholds: avg return must be below threshold, volatility above, profit factor below. Advisors qualifying here are candidates for the reverse strategy.

When a signal from a reverse-eligible advisor arrives and no normal eligible rule matches:

  • Direction is flipped (BUY → SELL, SELL → BUY)
  • SL and target are swapped
  • Capital is drawn from a separate adverse corpus allocation

Even followup signals are handled with swapped logic:

Advisor updates SL → we modify our TARGET leg (not our SL)
Advisor updates Target → we modify our SL leg

It's elegant in its perversity — the advisors who are wrong are just as useful as the ones who are right.


The Corpus Service (Live Capital Tracking)

During live trading, a separate CorpusService manages the actual capital allocation — distinct from the analytics CorpusEngine which handles offline simulation.

The daily corpus is initialized each morning from yesterday's remaining capital plus realized P&L. Within the day, it's segmented by instrument category (Index Futures, Stock Options, etc.) with configurable percentage allocations.

corpus_check_budget(category, required_margin)
  ├── check: category allocation not exhausted
  ├── check: total corpus > required_margin
  └── return: approved capital

on_trade_entry: deploy_capital(category, margin)
on_trade_exit:  release_capital(category, margin) + book_pnl(pnl)

This prevents overexposure in a single category even if there are many qualifying signals.


Daily Data Ingestion (GitHub Actions)

We also have a GitHub Actions workflow that runs daily at market close to ingest the previous day's trade data automatically. This keeps the analytics pipeline fresh without manual intervention.

YAML
# .github/workflows/daily_ingest.yml
schedule:
  - cron: '30 10 * * 1-5'  # 4:00 PM IST (10:30 UTC), Mon-Fri

The ingestion worker fetches yesterday's trades, processes them through the full 9-step pipeline, and refreshes all metrics and rules.


Infrastructure

The full system runs on AWS:

ComponentService
ApplicationEC2 t3.large (2 vCPU, 8GB)
DatabaseAWS RDS PostgreSQL 17
Cache + QueueRedis 7 (Docker, same EC2)
Regime ServiceGo binary on same EC2 (port 8081)
Signal SourceMongoDB Atlas
BrokerDhan API (REST + WebSocket)
NotificationsWuzAPI (WhatsApp) + Telegram Bot

All services run on a shared saras_network Docker bridge. The production docker-compose brings up:

  • saras_api — FastAPI on port 8000, behind Nginx
  • saras_worker — RQ bulk processing worker
  • saras_trading_worker — live trading async loop
  • saras_premarket_worker — daily 08:45 IST setup
  • saras_notification_worker — PostgreSQL LISTEN → WhatsApp/Telegram

The Notification System

Every trade lifecycle event fires a PostgreSQL NOTIFY via a PL/pgSQL trigger on the executed_trades table. A standalone worker listens on that channel and dispatches to WuzAPI (WhatsApp) or Telegram as fallback.

This architecture means notifications are decoupled from the trading engine entirely. A notification failure never affects trade execution.

Trade INSERT/UPDATE
    → PL/pgSQL trigger → pg_notify('trade_event', JSON payload)
    → notification/worker.py (asyncpg LISTEN)
    → build_message() → send_wuzapi() or send_telegram()

Events covered: ENTRY_PLACED, ENTRY_FILLED, ENTRY_REJECTED, STOPLOSS_HIT, TARGET_HIT, EXIT_COMPLETED, SL_UPDATED.


The Dashboard

The analytics dashboard is built in Next.js with server-side rendering and React Query for data fetching. It surfaces everything — from the 6D trading rules table to live crowd sentiment, corpus equity curves, and individual trade P&L breakdowns.

Key pages:

  • Leaderboard — Advisors ranked by corpus-adjusted Sharpe, MDD, weighted win rate
  • Rule Explorer — Filter rules by any combination of the 6 dimensions, sortable by MAE/MFE and Pain/Reward
  • Live Dashboard — Real-time corpus value, active trades, crowd sentiment per ticker
  • Simulation — Three-step eligibility filter → category discovery → position-sized backtest
  • Trade History — Every executed trade with full metadata: crowd score, is_reverse, leg number, entry/exit, P&L

What Actually Made Us Money

The metrics that consistently separate profitable rules from noise:

  1. Corpus MDD < -15% — Hard gate. We don't trade rules where the simulated portfolio blew out even once in 90 days.

  2. Pain/Reward < 0.4 — Trades that require minimal pain to achieve the reward. Rules with P/R > 0.5 tend to have fat-tail losses that aren't visible in average return figures.

  3. Trust Flag = TRUSTED — WWR > UWR, divergence between 0 and +5%. Rules in WATCHLIST occasionally slip through on fundamentals but get smaller size. UNTRUSTED rules never trade.

  4. Post-SL Extension < -0.5% on average — Systematically negative means the stoploss is being hunted. We widen the SL by the expected extension before placing. This alone improved fill quality significantly.

  5. Crowd Confidence > 75% with alignment — When 12 of 14 advisors are bullish on NIFTY and we have a qualifying BUY rule, we boost capital. The consensus signal is alpha.

The reverse strategy has also been surprisingly effective. There's a segment of advisors who are technically bad — consistently below 40% win rate, high volatility — but they're consistent enough that fading them is profitable. The corpus math still applies: we cap SL/target at 30% to prevent any single bad signal from taking an outsized chunk.


What I'd Do Differently

Event-driven architecture over polling. The MongoDB change stream is excellent, but some parts of the pipeline still use polling intervals. A proper event bus (Kafka or Pulsar) would let us scale the analytics workers independently of ingestion volume.

Separating regime by instrument. Right now, regime is a market-wide classification. But NIFTY regime and RELIANCE equity regime can diverge. Per-ticker regime classification would improve rule precision significantly.

Options-specific metrics. IV rank, days-to-expiry decay, and theta effects aren't captured in our current metrics. For options-heavy advisors, these matter enormously and we're missing signal.

Streaming corpus updates. The corpus replay currently reruns from scratch on every ingestion. For advisors with 200+ trades in the 90-day window, this is expensive. Incremental corpus updates with delta processing would halve the compute.


Tech Stack Summary

LayerTechnology
API FrameworkFastAPI + Uvicorn
Background JobsRQ (Redis Queue)
DatabasePostgreSQL 17
ORMSQLAlchemy 2.0
MigrationsAlembic
Cache + QueueRedis 7
ValidationPydantic v2
Data ProcessingPandas + NumPy + SciPy
HTTP Clienthttpx (async)
Regime ServiceGo
DashboardNext.js + React Query + Recharts
BrokerDhan (REST + WebSocket)
Signal SourceMongoDB Atlas (Change Streams)
NotificationsWuzAPI + Telegram Bot
InfrastructureAWS EC2 + RDS, Docker Compose
LoggingLoguru

Conclusion

Building this system end-to-end forced me to think carefully about what "good" metrics actually mean in trading. Win rate is a starting point. Corpus-adjusted Sharpe and MDD are more honest. Pain/Reward ratio, trust flags, and crowd confidence are where the edge actually lives.

The Saras platform (saras.market) tracks advisor quality so retail investors can make better decisions. The prop trading engine takes that same intelligence and automates the execution — with every safety gate we could think of: kill switches, corpus caps, price validation, eligibility double-gating, and real-time alerts.

The architecture is opinionated but deliberate. Python for the analytics-heavy pipeline, Go for the high-frequency regime service, Next.js for the dashboard, Dhan for execution. Each tool was chosen because it was the best fit — not because it was fashionable.

If you're building something similar — analytics-driven automated trading in Indian markets — I hope this walkthrough saved you some of the hard-won lessons.


References

// next up
ENGINEERING
Building a World-Monitor News Engine for Traders: From ~115 Public Sources to a Daily Brief
© 2026 · HAND-BUILT W/ ♥ & CAFFEINEBUILT WITH NEXT.JS + TAILWIND
HOMEABOUTBLOGSPROJECTSRESUMEPLAY