How many switches stand between a strategy and a real broker order? In Kestrel, the answer is ten, and none of them trusts the others.
Intro
How Far Is a Real Order?
A strategy decides to buy. How many separate things must be true before a real order reaches the broker? In Kestrel, the answer is ten. And the interesting part is that no single layer is trusted to be enough. This is episode twenty-five: Ten Layers of Live-Trading Safety.
- Strategy to broker
- Count the switches
Educational Only, Off by Default
Before anything else, a clear statement. This course is educational. Live trading in Kestrel is off by default, and what follows is a description of a design, not a claim that it has been proven in production. Nothing here is financial advice, and no strategy is implied to work.
- Live is off by default
- Educational only
- Not financial advice
Where We Left Off
Last time, in episode twenty-four, we looked at the order service: sessions, shadow books and crash recovery. Today we zoom out and enumerate every switch that stands between an alpha's target position and a real FYERS order. Uncertain outcomes and session details are covered in those earlier episodes, so we will not repeat them.
- Ep 23: uncertain orders
- Ep 24: sessions
Why Defence in Depth
The Swiss Cheese Model
Picture slices of Swiss cheese stacked in a row. Every slice has holes, because every check can be wrong, misconfigured, or skipped. An accident needs a path through every hole at once. Add independent slices, and the odds that the holes line up shrink dramatically.
- Each layer has holes
- Holes must not align
Independent Means Independent
The key word is independent. If two layers read the same setting, one typo defeats both. So Kestrel places layers in different files, different binaries and even different sources of truth: a config file, the process environment, the broker's own answers. One mistake should not open two gates.
- Different files
- Different processes
- Different failure modes
The Ten Gates at a Glance
Here is the whole map. One: config validation. Two: the engine refusing real venues in backtest. Three: live enabled in orders dot json. Four: the environment variable. Five: the account allow-list. Six: login and profile. Seven: the INTRADAY rule. Eight: reconcile before welcome. Nine: runtime limits. Ten: the boot restore rule.
- Config, engine, switches
- Account, login, product
- Reconcile, runtime, boot
Gates 1 and 2: Config and Engine
Gate 1: Config Validation
Gate one happens before anything starts. If a strategy's mode is live, its orders section must be of type service, with an account. Any other orders path for a live strategy is a ConfigError. The run simply never begins, and the validator, kestrel config, can tell you why without launching anything.
- mode live needs service
- Account required
- Else: ConfigError
The Trader Has No Broker Code
There is a deeper guarantee behind gate one. The trader binary contains no FYERS code at all. Even if a bug skipped validation, the trader has nothing to call. It can only send intents to the order service. A forbidden path is not merely blocked; it does not exist.
- kestrel_trader: no FYERS code
- Cannot bypass the service
Gate 2: Backtest Refuses Real Venues
Gate two lives in the engine. A venue can report whether it places real orders, through placesRealOrders. In backtest mode, the engine refuses any venue that answers yes. So even a mistaken wiring cannot make a historical replay talk to a live broker.
- placesRealOrders()
- Backtest refuses it
Gates 3 and 4: The Two Switches
Gate 3: live.enabled
Gate three: in orders dot json, live dot enabled must be true. Only then is a FyersRouter even constructed. If the flag is false, there is no router object in memory, no transport, and nothing to misuse. The strongest protection is an object that was never created.
- orders.json live.enabled
- Must be true
- Else no FyersRouter built
Gate 4: The Environment Variable
Gate four is a second, separate switch: the variable FYERS underscore ALLOW underscore LIVE underscore ORDERS must be exactly the string one, in the order service's own process environment. It is read with getenv. A value in a dot env file does not count, because the code never consults the file for this decision.
- FYERS_ALLOW_LIVE_ORDERS
- Exactly '1'
- Service process environment
The Environment Check, Verbatim
Here is the function, and it is tiny. It reads the variable, and returns true only if the pointer is not null and the string equals one. If the variable is missing, that is false. If it is anything else, that is also false. There is no parsing cleverness to get wrong.
- getenv, compare to "1"
- null means false
Why Exactly One
Why insist on exactly one? Because loose parsing invites accidents. If true, yes, or two all counted, a stray value pasted from somewhere else might switch on real trading. A strict match turns the switch into a deliberate act. If you meant it, you type the one character the code expects.
- true: no
- yes: no
- 2: no
Why Not a Dotfile
And why the process environment rather than a dotfile? Dotfiles get copied between machines, committed by accident, and edited casually. The process environment belongs to one running process, set by whoever launches it. It makes enabling live a visible act at launch time, not a leftover line in a file.
- Dotfiles get copied
- Env is per process
- Deliberate, visible act
Gates 5 to 7: Account and Product
Gate 5: The Account Allow-List
Gate five concerns who is asking. A live hello must carry the account, and that account must appear in the live dot accounts list. A session cannot invent an account and be trusted. The operator decides, ahead of time, which accounts may ever be used.
- hello.account
- Must be in live.accounts
The Refusal Code, Verbatim
This is the start of the refusal function. First it asks whether live is enabled and a router exists. Then whether the environment switch is set. Then it reads the account from the hello, and refuses if it is empty. Notice how every message names the file and path the operator should change.
- liveRefusal(m)
- Checks run in order
- Message names the file
Messages Tell You What to Fix
A refusal returns a pair: a code, and a human message. The code is for machines, and the message is for the person at the keyboard. A refusal that only says no teaches nothing. One that says change orders dot json, live dot enabled, turns a failure into a ten-second fix.
- Code plus message
- Names file and path
- Operator can act
Gate 6: Login and Profile
Gate six asks the broker itself. Today's FYERS login must be valid, and fetching the profile must work. If not, the refusal is login. Then the profile's id must equal the account in the hello. If they differ, the refusal is account. The service checks reality, not just the claim.
- Today's login must work
- Profile id equals account
- Errors: login, account
Gate 7: INTRADAY and Carrying Alphas
Gate seven is about product types. FYERS squares off INTRADAY positions at the close. An alpha that carries positions overnight would have its position silently closed by the broker, leaving Kestrel's books wrong. So INTRADAY with a carrying alpha is refused, with the code config.
- Broker squares off INTRADAY
- Carrying alpha refused
- Refusal: config
Gate 8: Reconcile Before Welcome
The Live Hello Becomes a Ticket
Now the flow around gates six and eight. A live hello becomes a PendingLive. In stage Check, the service asks the router to request a check for the account. In stage Sync, it asks for a sync. The runner is welcomed only after both tickets return. Until then, it waits at the door.
- PendingLive
- Stage Check, stage Sync
- Welcome after both
Asynchronous by Design
All of this is asynchronous. The broker can be slow, and the service loop must never block on it. So the router returns tickets, and the loop keeps serving everyone else: market data, other sessions, status requests. When a ticket completes, the pending hello moves to its next stage.
- Service loop never blocks
- Tickets, not waits
Reconcile: Two Ledgers
Reconcile compares two ledgers. On the left, what Kestrel believes each alpha holds. On the right, what the broker reports. Suppose alpha A shows plus fifty on both sides, and alpha B shows plus seventy-five here but only plus twenty-five at the broker. Alpha A matches. Alpha B does not.
- Our book vs broker book
- Compare per alpha
Block Only the Affected Alpha
What happens to alpha B? It is blocked, greyed out, and cannot trade. Alpha A is unaffected and continues. The blast radius of a mismatch is exactly the alphas that mismatch, not the whole account, so one confused position does not halt everything that is healthy.
- Mismatch blocks that alpha
- Others continue
- Greyed out
Never Trade to Fix It
Crucially, nothing is ever traded to fix a mismatch. The system could send twenty-five units to make the books agree, but it does not know which side is wrong. Maybe the broker is right. Maybe a manual trade happened. And if broker positions cannot be read at all, every alpha holding a position is blocked.
- No auto-correction
- Humans investigate
- Unreadable: block all holders
Gate 9: Runtime Guards
Gate 9: Risk Limits
Gate nine is the runtime gate, and it runs on every order. Risk limits cover order notional, position notional, gross exposure, daily loss, orders per second, and a price collar around a reference price. Global caps in orders dot json under risk dot caps sit above all per-strategy limits.
- Order and position notional
- Gross exposure, daily loss
- Orders per second, price collar
Local Rejections Before the Wire
Some orders can be rejected locally before bothering the broker. Expired contracts. Index instruments, which are not tradable. IOC orders on MCX. Quantities that are not whole multiples of the lot size. Catching these yourself is faster and cleaner than learning about them from a broker error message.
- Expired contracts
- Index instruments
- IOC on MCX
- Lot multiples
Kill Switch and Flatten
Gate nine also includes the operator's emergency controls. The kill switch stops new orders. Flatten closes out positions. These are not strategy logic; they are levers for a human who sees something wrong. Last, a place is never re-sent when its outcome is uncertain, and the ledger is written before the post. Episode twenty-three covers why.
- Kill switch
- Flatten
- Operator override
Gate 10: Boot Restore
Gate 10: Restoring at Boot
Gate ten covers restarts. When the service boots, a live session with journal records may be restored, but only if live is enabled and the environment switch is set. Otherwise the service logs an ERROR saying that session's FYERS orders are not supervised. It tells you plainly, instead of pretending all is well.
- Journal has live records
- Needs live.enabled AND env switch
- Else ERROR: NOT supervised
Worked Example
Three Misconfigurations
Let us test the stack with three mistakes. An operator writes a live strategy with a non-service orders path: stopped at gate one by a ConfigError. Another sets everything correctly but leaves live dot enabled false: stopped at gate three, no router. A third forgets the environment variable: stopped at gate four.
- Stopped at gate 1
- Stopped at gate 3
- Stopped at gate 4
Forgot the Variable
Walk through the third case. The operator sets mode live, starts the order service, and the runner sends a live hello. Live is enabled, so the router exists. But liveOrdersAllowed returns false because getenv found nothing. The hello is refused with live_disabled, and the exact message says the variable must be set to one.
- mode live, env unset
- hello refused
- live_disabled
Setting It to true
Now the operator guesses and sets the variable to true. The runner tries again. getenv returns a pointer this time, but the string true is not equal to one, so the function returns false. Still refused, with the same message. Only after setting exactly one does this gate open, and gates five to eight still wait beyond it.
- FYERS_ALLOW_LIVE_ORDERS=true
- Still refused
- Only "1" passes
Pitfalls
Pitfall: One Master Switch
Pitfall one: a single master switch. It looks simple, but a single flag is one typo, one merge, one copied config away from disaster. Independent layers force several unrelated mistakes to coincide. Simplicity for the operator is nice, but safety comes from requiring agreement among things that fail differently.
- One flag, one typo
- One accident away
- Many switches, many sources
Pitfall: Auto-Fixing Mismatches
Pitfall two: auto-fixing. It feels helpful to send a corrective order when books disagree. But if the broker is the side that is right, or someone traded by hand, the fix moves you further from reality, with real money. Block the alpha, tell a human, and let them decide.
- Fix trade can double the error
- Unknown which side is wrong
Pitfall: Safety From a Dotfile
Pitfall three: reading safety switches from a dotfile. A dotfile outlives the day it was written. It gets copied to a new machine, synced into a backup, loaded by a different tool. A switch that arms real orders should be set where it is visible: the environment of the process, at launch.
- Silent leftovers
- Copied between hosts
- Use process environment
Paper Can Never Reach FYERS
One more structural guarantee. Paper sessions in the same process use PaperVenue, which has no transport at all. So even when live is enabled and the environment switch is set, a paper session cannot reach FYERS. It has no wire to send on. Again, absence beats a rule.
- PaperVenue has no transport
- Even when live is enabled
Recap
Recap: Ten Layers
To recap. Config validation and the engine refusal. Live enabled, so a router exists. The exact-one environment variable. The account allow-list, today's login and profile, and the INTRADAY rule. Reconcile before welcome, without ever trading to fix. Runtime limits and local rejections. And boot restore, which is supervised or loudly flagged.
- Config, engine, live.enabled, env
- Account, login, product
- Reconcile, runtime, boot
Next: A Day in Paper Trading
Next, in the final episode, we run the whole stack through a trading day in paper mode, and collect the lessons learned. Remember: live is off by default, the course is educational, and nothing here is financial advice. Thanks for watching.
- Ep 26: the whole stack
- Lessons learned
- Subscribe