Capstone: end-to-end mini strategy
Wire together every piece from chapters 1-98 into one working, minimal strategy: an opening range breakout on NIFTY futures, from auth through live monitoring. This is deliberately simple — the point is demonstrating correct plumbing, not a sophisticated edge.
Project structure (extends chapter 5)
algo-trading-bot/
├── main.py # entry point, wires everything together
├── auth/session.py # ch 7-10
├── data/instruments.py # ch 21-23
├── data/historical.py # ch 27-31
├── watchlist.py # ch 35
├── brokers/kite_adapter.py # ch 68
├── orders/manager.py # ch 67
├── risk/sizing.py # ch 71-72, 78
├── risk/limits.py # ch 73-74
├── risk/kill_switch.py # ch 90
├── strategy/orb_signal.py # ch 80 (opening range breakout)
├── strategy/state_machine.py # ch 88
├── monitoring/health_check.py # ch 92
├── monitoring/alerts.py # ch 91
└── journal/trade_journal.py # ch 89, 97
main.py — the wiring
def main():
kite = get_authenticated_kite() # ch 10
validate_api_contract(kite) # ch 94
assert_segment_enabled(kite, "NFO") # ch 11
price_cache = PriceCache() # ch 42
order_manager = OrderManager(kite) # ch 67
journal = TaxAwareJournal() # ch 89, 97
kill_switch = KillSwitch(kite, order_manager) # ch 90
health_check = HealthCheck(kite, price_cache, kill_switch) # ch 92
fut_token = resolve_current_month_future(nfo_df, "NIFTY")["instrument_token"] # ch 23
kws = KiteTicker(API_KEY, access_token)
kws.on_connect = lambda ws, r: ws.subscribe([fut_token]) # ch 37
kws.on_ticks = lambda ws, ticks: [price_cache.update(t) for t in ticks] # ch 42
kws.on_order_update = lambda ws, u: state_machine.on_order_update(u) # ch 53
kws.connect(threaded=True)
account_start_equity = get_current_equity(kite) # ch 73
state_machine = StrategyStateMachine(order_manager, price_cache) # ch 88
threading.Thread(target=heartbeat_loop, args=(health_check,), daemon=True).start() # ch 92
intraday_candles = [] # accumulate today's 1-min bars for the ORB signal
while market_is_open():
if is_square_off_time(): # ch 70
square_off_all_intraday(kite, order_manager)
break
if not check_daily_loss_limit(account_start_equity, get_current_equity(kite), MAX_DAILY_LOSS_PCT): # ch 73
kill_switch.trigger(KillLevel.HALT_NEW_ENTRIES, "Daily loss limit")
df = pd.DataFrame(intraday_candles)
if len(df) > 15: # after opening range period
signal_df = opening_range_breakout_signal(df) # ch 80
latest_signal = signal_df["signal"].iloc[-1]
if kill_switch.is_entry_allowed():
qty = atr_based_qty(account_start_equity, 1.0, price_cache.get_price(fut_token), compute_atr(df).iloc[-1]) # ch 72
state_machine.on_signal(latest_signal, {
"code": "orb-nifty",
"order_kwargs": {"exchange": "NFO", "tradingsymbol": fut_symbol, "product": "NRML", "order_type": "MARKET", "quantity": qty},
})
time.sleep(5)
reconcile_positions(kite, get_local_positions()) # ch 66, end-of-day sanity check
if __name__ == "__main__":
main()
What this capstone deliberately leaves out, and why
No parameter optimization, no multi-instrument portfolio, no complex exit logic — every piece here is traceable to a specific earlier chapter, which is the point. Before adding sophistication, get this simple version running correctly in paper mode (chapter 87) for at least several weeks, reviewing the journal (chapter 89) honestly, before considering it a candidate for small real capital.
Next: 100 — Where to go next