Building a kill switch
Everything in chapters 73-74 (loss limits, exposure limits) decides *whether to open new risk*. A kill switch is the separate, more severe mechanism for *stopping everything immediately* — manual or automatic — including cancelling resting orders and, depending on severity, closing open positions.
Levels of severity — don't collapse these into one action
from enum import Enum
class KillLevel(Enum):
HALT_NEW_ENTRIES = 1 # stop opening new positions, let existing ones run their course
CANCEL_PENDING = 2 # + cancel all resting/pending orders
FLATTEN_ALL = 3 # + market-close every open position immediately
class KillSwitch:
def __init__(self, kite, order_manager):
self.kite = kite
self.om = order_manager
self.active_level = None
def trigger(self, level: KillLevel, reason: str):
logging.critical(f"KILL SWITCH triggered: {level.name} — {reason}")
send_alert(f"KILL SWITCH: {level.name} — {reason}")
self.active_level = level
if level.value >= KillLevel.CANCEL_PENDING.value:
self._cancel_all_orders()
if level.value >= KillLevel.FLATTEN_ALL.value:
self._flatten_all_positions()
def is_entry_allowed(self) -> bool:
return self.active_level is None
def _cancel_all_orders(self):
for o in self.kite.orders():
if o["status"] in ("OPEN", "TRIGGER PENDING"):
self.om.cancel(o["order_id"])
def _flatten_all_positions(self):
for p in self.kite.positions()["net"]:
if p["quantity"] == 0:
continue
side = "SELL" if p["quantity"] > 0 else "BUY"
self.om.place(strategy_code="kill-switch", exchange=p["exchange"],
tradingsymbol=p["tradingsymbol"], transaction_type=side,
quantity=abs(p["quantity"]), product=p["product"], order_type="MARKET")
Automatic triggers — wire these to real conditions, not just a manual button
def check_automatic_kill_conditions(kite, kill_switch: KillSwitch, config: dict):
equity = get_current_equity(kite)
if equity < config["start_equity"] * (1 - config["max_daily_loss_pct"] / 100):
kill_switch.trigger(KillLevel.HALT_NEW_ENTRIES, "Daily loss limit breached")
if price_cache.is_stale(critical_token, max_age=60):
kill_switch.trigger(KillLevel.HALT_NEW_ENTRIES, "Market data feed stale")
if consecutive_order_rejections(order_log, window_minutes=5) > 5:
kill_switch.trigger(KillLevel.CANCEL_PENDING, "Repeated order rejections — possible bug")
Manual kill switch — needs to be reachable even if your main bot process is unresponsive
# A tiny separate script, runnable independently of the main bot process
def emergency_flatten():
kite = get_authenticated_kite() # chapter 10
om = OrderManager(kite)
ks = KillSwitch(kite, om)
ks.trigger(KillLevel.FLATTEN_ALL, "Manual emergency stop")
if __name__ == "__main__":
emergency_flatten()
Keep this as a standalone script you can run from a phone via SSH or a simple webhook (chapter 91's Telegram bot can trigger this) — a kill switch that only works if your main bot's event loop is healthy defeats its own purpose in exactly the scenario where you need it most (the bot hung or misbehaving).