The TRPS bot (Tail Risk Protection Selling)#
The TRPS bot is conceptually simple: the strategy on the TRPS page is already a list of rules with times attached, and the code does nothing but execute it. The hard part isn’t the trading logic — a hundred lines or so — but everything around it: reconciliation, handling broker errors, behavior in degenerate cases. I’ll describe it the way the bot lives it: a state machine moving through the New York day, where every transition has explicit preconditions and every failure has a safe destination (Principle 2 on the Execution page). Times are in the America/New_York timezone, naturally, and the exchange holiday calendar is the first thing the bot consults on waking: on market holidays it doesn’t transition at all, and on half-days (early closes) the evening times shift accordingly.
The day, state by state#
09:25 — Reconciliation. Before doing anything else, the bot queries IBKR: open positions, live orders, balance and excess liquidity. It checks everything against the journal (Principle 4). The normal case: the position holds the 1DTE puts sold yesterday evening, expiring today, with no active orders. Any other configuration — a position the journal doesn’t know about, an orphan order, an unexpected overnight fill — leads to the HALT state: no trading, immediate notification, wait for the human. Reconciliation also checks the health of the environment: fresh market data (an SPX price more than a few seconds old is a symptom, not a data point), connection to the Gateway, system clock aligned with NTP. It’s the pre-flight check: two minutes of paranoia that buy a day of peace of mind.
10:00 — Changing of the guard. Half an hour after the open, the bot hands over from the overnight protection to the daytime one, with a strict ordering rule: cancel first, then transmit. It cancels the night guard still alive on the servers (see the dedicated section below), and only then transmits the stop-limit orders on the puts in position: trigger at a multiple of the premium collected (5-20x, from the configuration file), limit 2-4 ticks above the trigger, day validity (TRPS page: daytime stops live only during the session). Never two protections overlapping on the same position: a daytime stop placed while a guard is still active is the surest way to close twice. There is one exception, and it’s deliberate: if the futures guard fired during the night, the bot transmits no automatic stop — the position is already covered by the futures, and a stop on top would be a double hedge — but it sends a critical notification and waits for the human decision. From this moment on, in any case, Principle 1 applies: the protections live on IBKR’s servers and the bot could even die — the day is already secured. The half-hour wait after the open isn’t laziness: the first thirty minutes of the session are the noisiest, and a stop placed at 9:31 risks getting hit by the technical bounce rather than the real move.
Daytime — Passive monitoring. Between 10:00 and 15:30 the bot decides nothing: it watches. A slow loop (once a minute is more than enough) checks connection, positions and margin, records a heartbeat in the journal and pings the external dead-man switch. If a stop fires, IBKR executes it: the bot learns of it from the fill event, updates the journal, notifies me with the details (execution price versus trigger: the number that feeds the slippage statistics on the Tail risk page) and — importantly — does nothing else. No re-entry, no “making it back”: the strategy’s rules don’t include reopening, so the bot doesn’t even know how.
15:30 — Selection. This is where the only real trading logic lives. The bot reads the current VIX, computes the target IV (the configured multiple, ~1.5-2.5× VIX), and scans the chain of SPXW puts expiring tomorrow, looking for the first prominent strike (multiples of 25) whose bid premium falls in the target band (IV ~1.5-2.5× VIX, TRPS page). For sizing it queries the what-if (Setup page): the number of contracts is the smaller of what the configured leverage allows (notional ≤ 3-4× the account) and what the stressed margin permits with the required cushion. Every number from this phase — VIX, candidate strike, premium, simulated margin — goes into the journal before execution: if something goes wrong afterwards, I’ll know exactly what the bot thought it was doing.
15:30-16:15 — The window. After selection comes the window in which tomorrow’s 1DTEs are sold (TRPS page). Execution is via limit orders that start at the ask and give up a tick every few minutes (TRPS page: on 15 cents, the spread is a third of the gross). If contracts remain unsold when the window closes, so be it: what’s sold is sold, the journal records the miss, and the statistics will tell me whether it’s a fluke or a symptom.
~16:00 — Mounting the guard. This is the fifth appointment, the newest on the list. As soon as the fill of the sale is confirmed, the bot arms the night guard: one or more GTC orders that stand watch on the broker’s and exchange’s servers while it sleeps. The mechanism has a single trigger and two possible reactions. The trigger is a price level on the ES future, set — adjustably — halfway between the index price at the moment of the sale and the put’s strike: as long as ES stays above it, the night is quiet and nothing happens; if it breaks through, the guard fires. The reaction is a configuration choice between two alternative modes (one or the other, never both): buy back the put — closing out the risk entirely — or sell futures to cover its delta — cushioning it without closing. I explain both in the next section, because the guard is the most important architectural change since the project began; here the principle is enough: the overnight protection is born before the bot goes to sleep and doesn’t depend on it staying on.
16:20 — End of day. Final positions-versus-expectations check (guard included: a position without its guard order is an anomaly worth a notification), preparation of tomorrow’s stop orders (ready in the journal, to be transmitted half an hour after the open), summary write-up, closing ping, and the bot enters its night state: dormant by design. The difference from the first version of the architecture is all here: the bot sleeps as before, but the positions no longer spend the night with leverage as their only structural protection (Tail risk and TRPS pages) — there’s an armed guard on the servers, and whether the bot is on or off still changes nothing, which is exactly how it should be.
The state machine seen as a timeline: two critical appointments (the 10:00 changing of the guard, the 16:00 window with the mounting of the night guard), a long stretch of passive monitoring in between, and a night in which the bot sleeps while the guard orders stand watch on the broker’s and exchange’s servers.
The night guard#
To understand why it exists, look at the clock. The sale fills around 16:00 New York time; the daytime stop-limit reaches the servers only half an hour after tomorrow’s open. In between lie roughly eighteen hours — from Friday to Monday, an entire weekend — in which the put is in position and no stop defends it, because stops on SPX options simply don’t operate at night. It’s the uncovered window from the TRPS page, the one where the overnight gap lives. For years there was only one answer: leverage, decided upstream, sized for disaster. The night guard adds a second line: orders that live on the broker’s and exchange’s servers and fire while the bot — and I — sleep. ES futures, unlike options, trade nearly 24 hours on Globex (with a single declared hole: maintenance between 17:00 and 18:00 ET), and they are the ones standing sentry.
The trigger. Everything revolves around a single price level on the ES future, computed once at the moment of the sale and valid for the entire night. I set it at a fraction of the distance between the index price and the strike sold, and an example makes it concrete: if I sell the put with the index at 6,800 and the strike at 6,400 — 400 points of headroom — and set the fraction to 0.4, the trigger lands at 6,800 − 0.4 × 400 = 6,640. As long as ES stays above 6,640 the guard sleeps; the moment it touches it, it fires. The fraction is the parameter that sets its temperament: the higher it is (toward 0.5), the closer the trigger stays to the starting price, so the guard steps in early and far from the strike — plenty of false starts, but little damage when the alarm is real; the lower it is (toward 0.2), the more the trigger slides toward the strike, so the guard ignores passing scares and acts only when the move is serious — fewer false starts, but a later intervention, closer to the point where the put really starts to hurt. The exact same trigger applies to both modes: what changes is what the bot does when it fires, not when it fires.
First mode: the buyback. On the fill of the sale, the bot places a GTC buyback order on the same put, active outside regular hours too, conditioned on the trigger level on the ES future. If ES touches the trigger during the night, the broker buys the put back and the story ends there: position closed, gamma zeroed, nothing to manage in the morning. The order’s limit price is deliberately brutal — it models what the option could be worth at the trigger with volatility stressed to roughly double, not what it would be worth on a well-behaved night — because the goal is to get out, not to optimize the price. The cost is paid in two ways: on truly bad nights you buy back at a premium inflated by the IV explosion (that’s the cost of insurance), and on a false alarm you pay the full round trip on an illiquid option at night. The philosophy: a definitive overnight stop-loss. One piece of technical honesty is due: the price condition is simulated by the broker’s servers, not native to the exchange — one rung of reliability below a real stop.
Second mode: the futures hedge. The idea starts from a simple observation: at night the SPX options book is a desert, but the ES futures book is among the deepest on the planet; instead of buying the option back where there’s no liquidity, you sell the future where there’s plenty. On the fill of the sale the bot places stop orders on the futures at the same trigger level — and these are native CME stops, not simulated conditions. If the trigger fires, the bot has sold futures for a notional equal to the delta the put will have at the trigger (computed with stressed volatility, because on a trigger-worthy night the IV is not last evening’s), multiplied by an adjustable coverage factor. The size is composed by mixing the two formats: the big leg on the full E-mini contract (more liquid at night and with costs per notional roughly a third), the fine-tuning remainder on the Micros, worth a tenth. The put, however, stays open: at the trigger the combined position is close to neutral, but the hedge is static — if the decline continues, the put’s delta races toward one and the futures stay what they are. The futures guard mitigates the tail, it doesn’t remove it. And it has its sharp edges, which I’ll list with no sugarcoating: a gap that jumps past the order’s price band (a few ES points, the cap on accepted slippage) can leave the stop unexecuted; the E-mini and Micro legs are independent orders that can fill unevenly; the executed futures absorb overnight margin; and below the size of one Micro the guard simply can’t be mounted. The philosophy: a shock absorber, not an exit.
Choosing between the two. It’s a configuration parameter, not a dogma, because neither dominates the other across all scenarios — which is why the bot implements both and leaves the decision to me. The rule I use is simple, and it revolves around one question: how liquid is the market where I want to be found when the alarm goes off? I choose the buyback when I want to close the risk and actually sleep: it pays the panic price, but in the morning it leaves nothing to manage. It works as long as positions are small and the options book, even on a bad night, stays thick enough to absorb the buyback order. I switch to the futures hedge when size grows and that assumption breaks: in a real overnight crash the options book evaporates — buying back becomes expensive or impossible — while the ES futures book holds. The hedge doesn’t close the position, it turns it into something nearly neutral to be dismantled in daylight, but it’s the only one of the two with a reliable counterparty precisely when it matters most. In one line: buyback for the bad but orderly nights; futures for the extreme, disorderly ones, where expecting liquidity from options is naive and the only serious place to be found is the futures book.
The morning after. If the night passed quietly — the vast majority — the 10:00 changing of the guard cancels the overnight orders and mounts the daytime stop, as described above. If the buyback fired, there’s nothing left to protect: you log it, you learn, you start again in the evening. If the futures hedge fired, the bot stops one step short of full automation: critical notification and human decision, because the two clean exits — closing put and futures together at the open, or holding the hedge until the 16:00 expiry, accepting gamma and margin — depend on how the morning looks, and that’s a judgment I chose not to delegate.
Calibration, with transparency. The right trigger fraction can’t be guessed at a desk: it has to be measured. That’s why the guard also has a listen-only mode, in which it places no orders but samples the ES future all night long and records the low it touched and what fraction of the strike-to-fill distance it corresponds to. And that’s the mode the system is running in right now: a few weeks of measured nights before arming the orders, with the planned path going from the buyback toward the futures hedge. It seems like the bare minimum of seriousness to me: a system that can sell futures at three in the morning earns its trust one measured night at a time.
How it fails, gracefully#
The value of an architecture is measured on the bad cases, so here they are, from most frequent to rarest. The Gateway goes down during the 15:30 window: the bot loses its connection halfway through the submissions; on restart (systemd + IBC do their job in a couple of minutes) the first action is always reconciliation, which discovers how many contracts actually went out and resumes — or doesn’t, if the window has closed: better an evening at half size than a double submission. That’s why every order carries a unique identifier written to the journal before transmission: idempotence isn’t an engineer’s affectation, it’s what separates “retry” from “double up”. Partial fill on the stop: the stop-limit executes part and the rest hangs beyond the limit; the bot notifies and leaves the decision to me (runbook: normally, manual market close of the tail — the extra loss is the premium the TRPS page pays for stop-limits). Home internet down at 15:25: the LTE failover (Setup page) should cover it; if it doesn’t, the evening is skipped and yesterday’s positions expired at 16:00 anyway — fail-static in its purest form: a halted system is a system with no new positions, not a system in danger. The most feared scenario: the overnight gap from the Tail risk page. For years the honest answer was “no code can handle it”: today there’s the night guard, but the same honesty demands stating what it can and cannot do. It can buy back the put or sell futures while the market falls continuously; it can do nothing against an instantaneous jump that reopens beyond the trigger and beyond the band — the buyback’s conditional order is a simulation on the broker’s servers, the futures stop doesn’t execute beyond its price limit, and the hedge, when it does execute, is static. The defense of last resort remains what it has always been: leverage, decided in the configuration file long before (Ergodicity page), and if it comes to it, the human with ES futures from the phone (Futures and Execution pages) — which today is the fallback of the fallback, not the plan. The architecture doesn’t protect against the tail: it protects the strategy that protects against the tail, and the night guard shortens the list of nights on which that sentence has to do all the work by itself.
Configuration, testing, real life#
A word about notifications, which all travel over the messaging channel from the Setup page, because their design is half of the system’s perceived reliability. Three levels: the info level (sale executed, evening summary, regular pings) arrives as a notification with no sound and stays in the journal; the warning level (retry for BigMarginChange, fill at a worse price than expected) generates a push message I can ignore for hours; the critical level (failed reconciliation, HALT state, stop executed, dead-man fired) generates an insistent notification that demands an acknowledgment. And the channel works in reverse too: from anywhere in the day I can ask for the status to see positions and live orders, or order a halt to stop the state machine — the bot cancels pending non-protective orders, freezes any new sale and leaves the stops on the broker’s servers untouched. The real-world use case for the kill switch isn’t the disaster movie but the mundane: a notification that doesn’t add up, a doubt about a configuration just changed, and I’d rather have an evening without premiums than an evening with a doubt. Calibration matters more than technology: a system that screams about everything trains its operator to ignore it — that’s alert fatigue, and an ignored alarm is worse than no alarm at all. The rule I use: if in the last three months a type of notification has never changed my behavior, it moves down a level.
All the parameters from the TRPS page — IV multiple, premium band, leverage, stop multiple — live in a versioned configuration file: every change is a commit with a date and a rationale, never a number tweaked on the fly. It’s the software version of the rule against creeping optimization (TRPS page): changing the parameters remains possible, but it costs enough friction that it has to be a decision. Testing follows the pipeline from the Execution page: months on a paper account, crossing at least one episode of real volatility, with particular attention to the odd days — half-days, futures roll days, sessions with macro data at 8:30 — because that’s where the bugs hide. And a final note of operational humility: after more than a year in service, nearly all the manual interventions I’ve made concern the infrastructure (Gateway, network, updates), not the trading. The junior employee executes flawlessly; it’s the office that occasionally falls apart.