The scanner finds names whose volatility has compressed — coiled springs that tend to release with an outsized move. The options layer then reads what the market charges for that move, and that is what decides which side of the trade you want to be on:
Long-dated options are listed on January cycles, so the expiry nearest 13 months out can sit anywhere from ~9 to ~18 months away. The real days-to-expiry is in every row.
Raw data: scan.json · signals.csv
Loading price history…
Loading the month tables…
Loading the weekly history…
Loading backtest…
Loading calibration…
data/scan.json alongside the numbers it
describes, so an explanation here can never drift from the field it explains.
Each weekday after the US close a GitHub Action fetches the ETF holdings that define the universe, re-screens them on industry plus debt and cash ratios, downloads daily prices, recomputes the indicators without look-ahead, reads the option chains for the top-ranked names — the front month and a ≈13-month expiry — runs both strategy engines, and writes JSON:
data/scan.json — signals, IV read, recommendations, and this copydata/charts.json — downsampled price history, and the same closes grouped by calendar monthdata/weekly.json — the same history as one row per ISO week, which the Repeat test walksdata/backtest.json · data/calibration.jsondata/signals.csv — the flat table, for spreadsheetsPython renders no HTML at all. Deciding what to trade got complicated enough that it needed to be data, not markup.
Hand-written, checked into public/, no build step and no external assets.
It fetches the JSON above and renders it. Nothing regenerates it, so you can restyle the
dashboard without touching the scanner — and read the same data from anything else that
speaks JSON.
Run it locally with python run.py then
python -m http.server --directory public 8765. Opening the file over
file:// will not work: the browser blocks fetch there.