Skip to content
Course contents
Live risk, operations, and the honest close

The gate before every order

Every order the strategy wants to send should pass through an automated gate that can say no: no order larger than a set size, no position beyond a limit, no trading once the day's loss cap is hit, no order at an obviously wrong price. This chapter builds that risk gate against the simulated broker and adds a kill switch that halts and can flatten. It is the Risk and Psychology rulebook turned into code.

10 min readChapter 24 of 28
What you will learn
  • Build a pre-trade risk gate enforcing max order size, max position, a daily loss limit, and price sanity checks
  • Implement an automated kill switch that halts trading
  • Place the gate between the strategy and the order manager so nothing bypasses it

This is the part the whole risk track has been building toward. You have a strategy that runs itself, an order manager that tracks every order, and reconciliation that keeps you honest with the broker. What you do not yet have is the thing that says no. The risk gate is a piece of code that sits between the strategy and the market and refuses any order that breaks a rule you set in advance, calmly, every time, without the strategy being able to argue. It is the Risk and Psychology rulebook, made mechanical.

What the gate enforces

Every order passes through an automated risk gate that can say no on size, position, the day's loss, or an obviously wrong price, with a kill switch that halts and can flatten.
Every order passes through an automated risk gate that can say no on size, position, the day's loss, or an obviously wrong price, with a kill switch that halts and can flatten.

A good risk gate checks a handful of things before letting an order through, and each maps to a rule you already know from the risk courses.

  • Maximum order size: no single order larger than a set number of shares or lots, so a fat-finger bug or a runaway loop cannot send a huge order.
  • Maximum position: no order that would take your total position past a limit, so you cannot accidentally build a position larger than you meant to hold.
  • Daily loss limit: once the day's loss reaches a set amount, stop trading entirely. This is the loss limit from Risk and Psychology, the circuit breaker for yourself, now enforced by code that cannot be talked out of it.
  • Price sanity: reject an order at a price wildly far from the last known one, which catches a bad tick or a mistaken price before it becomes a trade.

Here is a gate that enforces all four, plus the kill switch.

ExampleThe risk gate: every order checked against hard limitsch24/risk_gate.py
# The risk gate sits between the strategy and the broker. Every order passes
# through check() first, and any order that breaks a rule is refused. When the
# day's loss crosses the limit, the kill switch trips and nothing more trades.
# This is the Risk and Psychology rulebook turned into code that cannot be argued
# with by a strategy having a bad day.
class RiskGate:
    def __init__(self, broker, max_order_qty, max_position, daily_loss_limit):
        self.broker = broker
        self.max_order_qty = max_order_qty
        self.max_position = max_position
        self.daily_loss_limit = daily_loss_limit      # a positive rupee number
        self.start_equity = self._equity()
        self.halted = False

    def _position_qty(self, symbol):
        for p in self.broker.get_positions():
            if p["symbol"] == symbol:
                return p["quantity"]
        return 0

    def _equity(self):
        cash = self.broker.get_funds()["available_cash"]
        value = 0.0
        for p in self.broker.get_positions():
            quote = self.broker.get_quote(p["symbol"])
            if quote:
                value += p["quantity"] * quote["last_price"]
        return cash + value

    def check(self, symbol, side, quantity, price):
        if self.halted:
            return False, "halted (kill switch)"
        loss = self.start_equity - self._equity()
        if loss >= self.daily_loss_limit:
            self.halted = True
            return False, f"daily loss limit hit (down {loss:.0f}); KILL SWITCH tripped"
        if quantity > self.max_order_qty:
            return False, f"order size {quantity} over max {self.max_order_qty}"
        signed = quantity if side == "BUY" else -quantity
        if abs(self._position_qty(symbol) + signed) > self.max_position:
            return False, f"would breach max position {self.max_position}"
        quote = self.broker.get_quote(symbol)
        if quote and abs(price - quote["last_price"]) / quote["last_price"] > 0.10:
            return False, "price more than 10% from last; rejected as a bad tick"
        return True, "ok"

    def send(self, symbol, side, quantity, price=None):
        quote = self.broker.get_quote(symbol) or {}
        ref_price = price if price is not None else quote.get("last_price")
        ok, reason = self.check(symbol, side, quantity, ref_price)
        if not ok:
            print(f"  BLOCKED {side} {quantity} {symbol}: {reason}")
            return None
        order_type = "MARKET" if price is None else "LIMIT"
        order = self.broker.place_order(symbol, side, quantity, order_type, price)
        print(f"  ALLOWED {side} {quantity} {symbol}: {order.status}")
        return order

And here it is in action, with a strategy trying orders that break each rule.

ExampleThe gate allowing good orders and blocking bad ones, then the kill switchch24/risk_gate_demo.py
# The risk gate in action. Each order is checked before it can reach the broker,
# and a loss beyond the daily limit trips the kill switch, after which nothing
# trades. The strategy never talks to the broker directly, so it cannot get around
# any of this.
from paper_broker import PaperBroker
from risk_gate import RiskGate

broker = PaperBroker(cash=1_000_000, prices={"RELIANCE": 1400})
gate = RiskGate(broker, max_order_qty=100, max_position=200, daily_loss_limit=20_000)

print("Every order passes the gate:")
gate.send("RELIANCE", "BUY", 50)                 # allowed
gate.send("RELIANCE", "BUY", 500)                # blocked: order too big
gate.send("RELIANCE", "BUY", 100)                # allowed -> position 150
gate.send("RELIANCE", "BUY", 100)                # blocked: would breach max position
gate.send("RELIANCE", "BUY", 1, price=2000)      # blocked: price far from last (bad tick)

print("\nThe day's loss trips the kill switch:")
broker.feed_price("RELIANCE", 1250)              # the 150-share position is now deep in loss
gate.send("RELIANCE", "BUY", 10)                 # blocked: daily loss limit -> kill switch
gate.send("RELIANCE", "SELL", 10)                # blocked: halted, whatever the strategy wants

print(f"\nHalted: {gate.halted}")
print("Position:", broker.get_positions())
Output
Every order passes the gate:
  ALLOWED BUY 50 RELIANCE: FILLED
  BLOCKED BUY 500 RELIANCE: order size 500 over max 100
  ALLOWED BUY 100 RELIANCE: FILLED
  BLOCKED BUY 100 RELIANCE: would breach max position 200
  BLOCKED BUY 1 RELIANCE: price more than 10% from last; rejected as a bad tick

The day's loss trips the kill switch:
  BLOCKED BUY 10 RELIANCE: daily loss limit hit (down 22500); KILL SWITCH tripped
  BLOCKED SELL 10 RELIANCE: halted (kill switch)

Halted: True
Position: [{'symbol': 'RELIANCE', 'quantity': 150, 'avg_price': 1400.0}]

Read what the gate did. It allowed a sensible buy of 50, then blocked an order of 500 for being over the size limit. It allowed another 100, taking the position to 150, then blocked the next 100 because it would breach the position limit of 200. It blocked an order priced at 2,000 when the market was at 1,400, catching it as a bad tick. Then the price fell, the day's loss crossed 20,000 rupees, and the kill switch tripped: from that point every order was refused, whatever the strategy wanted. Not one bad order reached the market.

The kill switch

The most important control is the last one, the kill switch. It is a single decision that, once made, stops everything: no more orders, and in a fuller system, flatten the open positions and shut down. It exists for the moments when something is clearly wrong and you do not have time to diagnose it: the strategy has gone haywire, the day's loss has blown past its limit, the market is doing something you do not understand. A kill switch you can trip by hand, and one that trips itself on a hard loss limit, are both essential. When in doubt, the correct action is always to stop, and the kill switch is how a system stops.

The gate goes where nothing can bypass it

For all this to work, the gate must sit where every order has to pass through it, between the strategy and the broker, exactly the separation the architecture chapter insisted on. The strategy asks the gate to send an order; the gate decides; only the gate talks to the broker. If the strategy could reach the broker directly, any bug in the strategy could bypass every limit, and the gate would be theatre. The whole value of the gate is that it is not optional and cannot be gone around. Build it so the strategy has no way to the market except through the rules.

What to carry forward

The risk gate is the control that says no: it checks every order against hard limits, maximum size, maximum position, a daily loss limit, and a price sanity check, and blocks any that break them, exactly as you saw it block a too-large order, a position breach, and a bad tick. A kill switch stops everything when the day's loss limit is hit or you trip it by hand, as it did the moment the loss crossed 20,000. For the gate to mean anything it must be the only route from the strategy to the market, so no bug can bypass it. This is the Risk and Psychology rulebook turned into code that does not get scared or greedy. Next, seeing what your running system is doing: logging, monitoring, and alerts.