After running for approximately 6 weeks, three bugs were identified that corrupted the trade data
and statistics enough to make the v1 dataset unreliable for research purposes.
The decision was made to archive the v1 data, document the bugs transparently, and start clean.
v1 archive available for download: trades_v1.json · stats_v1.json · blog_v1.json
v1 Summary (Aug 24 – Oct 8, 2026)
Clean P&L (8 trades)
-$186.32
The 26 stuck-open trades and the win/loss record are not reliable signals about the methodology.
The bugs below caused incorrect hold decisions, incorrect FOMO calculations, and a crash that
prevented trades from ever closing. The data is not clean enough to draw conclusions from.
Bugs Found and Fixed
Bug 1 — fetch_news.py · Fixed 2026-10-08
make_decision() held on SPY sentiment alone
The hold/sell decision was triggering a hold if either the ticker OR SPY news was bullish/bearish.
This meant a CALL could be held based purely on SPY being bullish, even if the ticker itself had
neutral or bearish news. Neutral ticker sentiment was also being counted as confirmation.
Result: trades were held far more often than the methodology intended, and several holds
were triggered by irrelevant SPY headlines.
Fix: ticker sentiment only drives the decision. Bullish ticker = hold CALL.
Bearish ticker = hold PUT. Neutral always sells. SPY/SPX still logged for research
but no longer drives the hold/sell decision.
Bug 2 — sell_signal.py · Fixed 2026-10-08
Saturday KeyError crashed the sell script daily
stats['by_day_of_week'] only had Monday–Friday keys hardcoded.
When a trade's entry date resolved to a Saturday (edge case from timezone handling
or manual test entries), the stats update crashed with a KeyError.
Because the crash happened inside the trade loop, no trades were closed on that run —
they all remained open. This compounded daily, resulting in 26 stuck-open trades
by the time it was caught.
Fix: dynamically create the day key if it doesn't exist rather than assuming Mon–Fri only.
Bug 3 — sell_signal.py · Fixed 2026-10-08
FOMO left_on_table included excluded trades in avg
Trades 1–4 were flagged as excluded from analytics due to a prior FOMO calculation bug
(sell_contract_value used raw stock price × 100 instead of BS option value, producing
values like -$10,451 and -$22,794). The exclusion logic correctly skipped those trades
in most analytics buckets, but the fomo.avg_left_on_table calculation did not check
the exclusion list. The corrupted left_on_table values from trades 1–4 were being
averaged into the stat, making it meaningless.
Fix: fomo block now checks excluded_ids before updating avg_left_on_table,
consistent with all other analytics buckets.
Bug 4 — paper_trade.py · Fixed 2026-10-08
No OTM cap on 2DTE+ contracts
The contract selection filter had a 20% OTM cap for 1DTE contracts but no cap for
2DTE and above. All four clean trades (5–8) had strikes 7–13% OTM on 2–3DTE contracts,
which placed every strike outside the 1σ expected move range. A contract outside the
expected move needs a larger-than-predicted move to profit — systematically selecting
these is a negative edge, not a neutral one.
Fix: added MAX_OTM_GENERAL = 10% cap for 2DTE+ contracts. Keeps strikes inside
or near the expected move range where there is actual probability of profit.
Bug 5 — fetch_signals.py · Fixed 2026-10-08
Blank sector passed through as empty string
Some tickers in stocks_optionable.csv have blank sector fields. These were being
passed through to signals.json and trades.json as empty strings, showing as
"Unknown" in the stats sector breakdown and making sector analysis unreliable.
Fix: when CSV sector is blank, fetch sector from yfinance Ticker.info at signal
generation time. Falls back to 'Unknown' only if yfinance also has no data.
All v1 data is preserved in
data/archive/
and will remain publicly downloadable. The archive is part of the record.
Deleting inconvenient data is not how this project works.