Order Flow

Order Execution Flow

Go-Live Gate

execute_* strategy method ├── GoLiveManager.get_state(strategy) DRY_RUN / SHADOW / LIVE from SQLite records ├── DRY_RUN → scanner prices become simulated fills; no broker orders ├── SHADOW → can_trade() + available_balance ≥ max_margin × 1.10 → real orders + SHADOW_DR_* metrics snapshot └── LIVE → can_trade() + available_balance ≥ max_margin × 1.10 → real orders only

Box Spread

execute_box_spread(opp) ├── can_trade() killed? low_balance? daily_limit? max_positions? │ └── abort if any check fails └── _place_legs_ioc(leg_specs, lot, tag) ├── get_ltp(NSE_FNO, [id1, id2, id3, id4]) ← FRESH prices, replaces stale scan prices ├── For each leg: │ 1. anchor = scanner bid (SELL) / ask (BUY); if LTP moved past it, use LTP as anchor slipped = anchor × (1 + 0.2%) BUY / anchor × (1 − 0.2%) SELL [ORDER_SLIPPAGE_PCT] │ 2. limit_px = tick_round(slipped) ← nearest valid ₹0.05 │ 3. place_order(..., validity="IOC") │ 4. daily_order_count += 1 │ 5. _poll_fill(oid, timeout=15 s) │ ├── TRADED → ✓ continue │ ├── PART_TRADED_AND_CANCELLED → ✓ treat as filled │ ├── CANCELLED / REJECTED → retry with 0.8% slip │ └── timeout (15 s) → retry with 0.8% slip │ 6. Retry: re-fetch LTP for this leg · place IOC LIMIT at 0.8% → filled ✓ continue → still failed → _auto_reverse │ 7. _auto_reverse(filled_legs_so_far): │ ├── Pass 1: IOC LIMIT at LTP ± 0.1% per filled leg │ └── Pass 2: MARKET fallback for legs that didn't fill on LIMIT └── All 4 filled → open_trades.append(trade) · _save_trades() · Telegram alert

MDC Controlled Scale-In Guards

MasterDCScanner.scan(open_trades) ├── no open MDC anchor → normal first-entry classification ├── newest remaining MDC anchor → require valid open_time, spot_at_entry, and near_straddle_entry ├── cooldown → require ≥ 90 minutes ├── movement → require |spot − anchor spot| ≥ max(200 pts, 50% of anchor near straddle) ├── anchor health → require state OPEN, live P&L available, and P&L > −10% of risk basis ├── classify current regime normally → DC / DCS / DCS_SKEW / DDC / IC └── ExecutionEngine exact-leg guard → reject incomplete, malformed, or identical complete option security-ID sets before slot checks, simulation, or orders

Double Calendar Straddle MARKET ORDERS

Runtime mode comes from SQLite records. In DRY_RUN, no orders are placed — scanner LTPs are used as simulated fill prices. SHADOW and LIVE use MARKET orders with no LTP pre-fetch; shadow also writes a comparison record to metrics.

execute_double_calendar(signal) ├── state == DRY_RUN? → use scanner LTPs as fills, compute entry_cost via TransactionCosts.dc_entry_cost(), no orders ├── [LIVE] can_trade() kill switch + balance + limits ├── Place 4 MARKET legs in order (far first reduces net gamma risk during entry): │ 1. BUY far_CE (MARKET, price=0) │ 2. BUY far_PE (MARKET, price=0) │ 3. SELL near_CE (MARKET, price=0) │ 4. SELL near_PE (MARKET, price=0) │ each leg: daily_order_count += 1 → _poll_fill(oid) │ ├── filled → continue to next leg │ └── not filled → _auto_reverse(filled_legs_so_far) → abort └── All 4 filled / dry-run → ├── execution.double_calendar.build_dc_trade() strategy="DC", entry_cost=₹X, net_debit=₹Y, near_straddle_entry=Z ├── open_trades.append(trade) · TradeStore.save() ├── metrics.record_execution(..., outcome="simulated"/"executed") ├── if SHADOW: metrics.record_close(tag="SHADOW_DR_DC_*", close_reason="shadow_entry_snapshot", shadow=True) └── Telegram alert with near/far premiums, net debit, 3 stop conditions

Stretched DC MARKET ORDERS

Controlled at runtime by SQLite records: DCS for symmetric DCS and SDCS for DCS_SKEW. Live path uses MARKET orders for all 8 legs — same pattern as DC. Tag prefix is DR_ in dry-run and LIVE_ in shadow/live.

execute_stretched_double_calendar(signal) ├── state == DRY_RUN? → use scanner LTPs as fills, compute entry_cost via TransactionCosts.dcs_entry_cost() (8 legs) ├── [LIVE] can_trade() kill switch + balance + limits ├── [LIVE] Place 8 MARKET legs (far first): │ 1. BUY far_CE (ATM, MARKET) │ 2. BUY far_PE (ATM, MARKET) │ 3. BUY far_OTM_CE (ATM + wing, MARKET) │ 4. BUY far_OTM_PE (ATM − wing, MARKET) │ 5. SELL near_CE (ATM, MARKET) │ 6. SELL near_PE (ATM, MARKET) │ 7. SELL near_OTM_CE (ATM + wing, MARKET) │ 8. SELL near_OTM_PE (ATM − wing, MARKET) │ each leg: daily_order_count += 1 → _poll_fill(oid) │ ├── filled → continue to next leg │ └── not filled → _auto_reverse(filled_legs_so_far) → abort └── All 8 filled / dry-run → execution.double_calendar.build_dcs_trade() → strategy="DCS", wing_offset=N pts stored in trade record

Stretched DC — Close Flow

_close_stretched_double_calendar(trade) ├── get_ltp(NSE_FNO, [8 ids: near_ce, near_pe, far_ce, far_pe, near_otm_ce, near_otm_pe, far_otm_ce, far_otm_pe]) ├── is_dry? → log all 8 LTPs, return True immediately └── execution.double_calendar.dcs_close_legs() → place 8 close legs (IOC LIMIT at LTP ± 0.2% slip; MARKET if LTP=0): 1. BUY near_CE (ATM) ← buy back short near leg 2. BUY near_PE (ATM) 3. BUY near_OTM_CE (ATM + wing) 4. BUY near_OTM_PE (ATM − wing) 5. SELL far_CE (ATM) ← sell long far leg 6. SELL far_PE (ATM) 7. SELL far_OTM_CE (ATM + wing) 8. SELL far_OTM_PE (ATM − wing) any not filled → Telegram ⚠️ DCS Close PARTIAL FAILURE — check Dhan manually

AI Market DRY-RUN ONLY

Phase 1 AI_MARKET execution is intentionally dry-run only. The OpenAI response is persisted first; if the parsed verdict is BULLISH or BEARISH, the engine resolves a NIFTY weekly option near the recommended strike and records a simulated buy-side trade. No broker order is sent even if AI_MARKET_DRY_RUN is changed accidentally.

AutonomousMarketAnalyst.run_once_for_today() ├── skip if disabled, no OPENAI_API_KEY, not an NSE trading day, or today's analysis exists ├── AIRunner.run() → strict JSON verdict, raw response, token usage, estimated cost ├── SQLiteStore.save_ai_market_analysis() ├── NO_TRADE → store analysis only; Telegram summary └── BULLISH / BEARISH → execute_ai_market(parsed, analysis_id) execute_ai_market(parsed, analysis_id) ├── duplicate guard: no second AI_MARKET trade for the same analysis ├── resolve option type: BULLISH → CE, BEARISH → PE ├── resolve strike from parsed recommendation or nearest ATM fallback ├── fetch near-week option LTP from option chain ├── persist open trade with target, stop, lots, expiry, security_id, and raw analysis id └── mirror row in ai_market_trades for dashboard review

Directional Diagonal Calendar DRY-RUN

DDC is a 2-leg directional diagonal. Runtime state now controls whether it simulates or places real orders; shadow/live use the same far-buy then near-sell pattern as the dry-run trade record.

DirectionalDiagonalScanner.scan(open_trades, ai_direction_hint=None) ├── skip if another strategy="DDC" trade is open ├── capture day open on first scan at/after 09:15 IST ├── compute (spot - day_open) / day_open │ ├── below DDC_TREND_THRESHOLD_PCT → no signal │ ├── weak trend plus same-day AI hint → optional BULLISH/BEARISH direction │ ├── positive trend → BULLISH CE diagonal │ └── negative trend → BEARISH PE diagonal ├── fetch near-week + monthly chains; require near DTE 5-9 and ATM IV 20%-25% ├── far leg = monthly ATM CE/PE └── near leg = near-week OTM CE/PE at ATM ± DDC_OTM_DISTANCE_PTS

DDC Entry + Close Flow

execute_diagonal_calendar(signal) ├── state == DRY_RUN → no live orders ├── persist simulated fills: │ ├── BUY far-month ATM option at far_ltp │ └── SELL near-week OTM option at near_ltp ├── entry debit = far_ltp - near_ltp └── open_trades.append() · TradeStore.save() · Telegram dry-run alert monitor_open_positions() ├── current P&L = (far_current_ltp - near_current_ltp - entry_debit) × lot ├── close if P&L ≥ 30% of entry debit ├── close if P&L ≤ -20% of entry debit ├── close if spot breaches the short near strike └── close if near-week expiry day is at/after 15:00 IST, or near expiry already passed

Iron Condor Fallback DRY-RUN

IC is a 4-leg fallback strategy. It runs only when no primary options trade is open, including MDC_* routed DC-family positions. Runtime go-live state controls whether it simulates or places real orders.

run_scan_cycle() ├── monitor existing positions, then run MDC as the primary options router ├── if any primary options trade is open → skip IC fallback ├── realized_range = dc_scanner.realized_range_today() └── IronCondorScanner.scan(open_trades, realized_range) ├── require near-week DTE 3-7 and ATM IV 18%-26% ├── require realized range ≤ 60% of near ATM straddle ├── build strikes: short CE/PE ATM±300, long CE/PE ATM±500 ├── net credit = (short_ce + short_pe - long_ce - long_pe) × lot └── require net credit ≥ 20% of wing width × lot

IC Entry + Close Flow

execute_iron_condor(signal) ├── state == DRY_RUN → no live orders ├── persist simulated fills: │ ├── SELL short CE and short PE at scanner LTP │ └── BUY long CE and long PE at scanner LTP ├── entry_credit = (short premiums - long premiums) × lot ├── max_loss = wing_width - entry_credit └── open_trades.append() · TradeStore.save() · Telegram dry-run alert monitor_open_positions() ├── current_credit_value = (short_ce + short_pe - long_ce - long_pe) × lot ├── current P&L = entry_credit - current_credit_value ├── close if credit captured ≥ 50% of entry credit ├── close if current loss ≥ 1.5× entry credit └── close if near-week expiry day is at/after 15:00 IST, or near expiry already passed

Double Calendar — Close Flow

_close_double_calendar(trade) ├── is_dry? → log simulated LTPs, compute close_cost, record net P&L, return True immediately ├── get_ltp(NSE_FNO, [near_ce, near_pe, far_ce, far_pe]) └── execution.double_calendar.dc_close_legs() → place 4 close legs (IOC LIMIT, 0.2% slip; MARKET if LTP=0): 1. BUY near_CE ← buy back short near leg 2. BUY near_PE 3. SELL far_CE ← sell long far leg 4. SELL far_PE any not filled → Telegram ⚠️ DC Close PARTIAL FAILURE — check Dhan manually

Calendar Spread DISABLED

Execution is suppressed in run_scan_cycle() — the flow below is for reference only. Uses MARKET orders with fill verification — each leg is polled for TRADED status before the next is placed.

execute_calendar_spread(signal) ├── can_trade() ├── net_profit < CAL_MIN_NET_PROFIT_PER_LOT (₹50) → skip, log WARNING ├── place_order(far_id, MARKET) daily_order_count += 1 │ └── _poll_fill(oid1) │ └── not filled → ABORT (no leg2 placed) ├── place_order(near_id, MARKET) daily_order_count += 1 │ └── _poll_fill(oid2) │ └── not filled → _auto_reverse(leg1) · ABORT └── both confirmed → open_trades.append() · _save_trades() · Telegram "OPENED"