The weekend
The next version needs better evidence. That starts with a cleaner backtest engine.
Next week I want to ground the strategy work in historical evidence instead of intuition. That means rerunning every backtest in the database on the most up-to-date engine, then using the results to generate candidate strategies aligned to my watchlist and basket.
The engine could not handle the workload.
The first time I pointed it at an options backtest the size of what I want to run (a 4,547-stock dynamic universe, 5 years of daily bars, and option-chain lookups for each candidate), it ran out of memory at 65 gigabytes and got killed by the OS.
Here is what I did.
(a) Lazy option chain loading
Dynamic option strategies were eagerly loading 2.9 million option rows up front, before the simulation even started.
I replaced that with a demand-driven loader. Chains get fetched only when a tick actually needs them, with bulk-fetch per cold underlying and per-day caching.
Cloud canary: max RSS 31 GB → 12.4 GB. Bit-exact parity with the old eager path, proven by a commit-by-commit cloud bisect. Cost: 106 seconds slower per run. Memory win, not a speed win.
(b) Raw underlying prices from daily bars, not intraday
After (a) shipped, the canary still ran out of memory.
New diagnosis: the option-chain load was now zero, but the engine was loading the entire intraday stock-price parquet just to look up raw underlying prices. It needs raw prices because option strikes are quoted in raw dollars, while the daily price feed is split-adjusted. Fix:
raw_price = adjusted_daily_price / SplitCurve::factor_at(ts)
Eight new unit tests cover NVDA's 10:1, AVGO's 10:1, GOOG's 20-for-1, and a no-split passthrough. Mag7 load-only repro before/after:
| Metric | Before | After | Delta |
|---|---|---|---|
| Memory used | 19.7 GiB | 1.85 GiB | 10.7× smaller |
| Time to run | 97.8s | 14.1s | 6.9× faster |
| Backtest result | – | – | bit-exact match |
Then the same change against the full 3,410-stock universe: 2.22 GiB of memory.
One-thirtieth of the 65 GiB that killed the engine on Friday.
(c) Cache and infrastructure cleanup
Capped the in-process parquet cache at 1 GiB on every backtesting and optimizer worker.
Deleted the legacy intraday-stock loading path entirely. Rollback now requires a code revert, not a runtime toggle.
Stood up a cloud A/B canary so the next memory regression of this shape gets caught the same way this one did.
The week, in three numbers
65 GiB out-of-memory crash → 2.22 GiB on the same workload. 97.8s → 14.1s on the load test. Bit-exact result parity through both changes. The engine is now ready for a test it could not survive last Monday.
Reflection
The account made money. The system showed me where it was weak.
That is the actual week 1 result. Not just +$2,546. Not just three open spreads and one realized winner. The useful part is that real money forced the system to reveal what paper trading let me ignore.
AVGO exposed a definition problem.
PositionMaxDrawdown sounded like it measured position P/L
drawdown. It did not. Backtests and live trading shared the same
definition, so the strategy was internally consistent. It just was not
measuring what I thought it was measuring.
GOOG exposed a close-logic problem.
The spread was over +50%, but the take-profit and stop-loss filters
sat on the same CloseOption action and were checked in
sequence. To close, the spread effectively had to be above +50% and
below −30% at the same time. Impossible. The important part here
is that backtests and live trading shared that code path, so this was
not a live-only bug. It was an engine bug.
All of these bugs are fixed now.
It is worth naming the counterfactual. With a broken drawdown indicator and an impossible close rule both live on the book, a market that moved the wrong way this week would have made this a very different post. NVDA, META, and GOOG all rallied. AVGO reversed before the broken stop ever had to be honored. The +10% week is real, and the engineering was real, but the gap between "design worked" and "tape was kind" is smaller than the headline suggests.
The uncomfortable part is that fixing them changes the evidence base. A strategy that looked good under the old drawdown indicator and the old close logic might not look as good under the fixed engine. The historical backtests I used to create version 1 were testing something different than what I thought I had designed.
So I am rerunning the numbers.
Version 1 made money. Version 1 also taught me that the next strategy has to be built on cleaner backtests, corrected close semantics, and explicit handling of the positions already on the book.
Aurora is generating that version now. Whatever it returns, I am showing the backtests, the rules, and the trade decisions.
If it clears the gates, it deploys to the live portfolio. If it doesn't, the next episode says why. The point of this challenge was never to write a clean victory lap after the fact.
Episode 9 goes one level wider.
The 100K-backtest article from last year ran on whatever strategies users had submitted: an unconstrained universe.
The question now is different: given my watchlist of 16 names and the current 4-name basket, what option strategies survive the same gates? Sortino > 1.5. Beats SPY. Max drawdown better than −50%. At least 10 trades.
Same engine. Same statistical rigor. Different scope: candidates aligned to the names I actually want to trade.
The output is a ranked list of options strategies on my universe, with confidence intervals, that I can deploy directly. That is what Episode 9 is about.
You can subscribe to the Public Portfolio Challenge and follow the live portfolio to see the changes as they happen.
Week 2 is where the experiment stops being a launch story and starts becoming a feedback loop.
The
NexusTrade MCP server
gives Claude Code and Cursor the same
fetch_portfolios,
backtest_portfolio, and event-query
tools I used this week.
Then run the same public discipline: the runbook, agent bakeoff, live scoreboard, and failure archive are on GitHub →.
No comments yet.