ops.py.momentum.py Β· the single choke-point every number passes throughWhat goes in: the point-in-time factor panel (one row per date Γ symbol: closing price, 12-1 momentum, trailing dollar volume), SPY and QQQ closes for the benchmarks, and the SEC filing-date calendar for the earnings filter. Nothing else β no news, no opinions, no knobs.
What it does, every simulated Friday (the same loop for 451 weeks straight):
What comes out: the weekly net return stream β and everything else is derived from it: the equity curve, the report card (CAGR, Sharpe, drawdown, win %, profit factorβ¦), the per-year and per-month splits, and the holdings log that becomes the trade episodes on the Orders tab. Each run's report card is also recorded in the history database, so the grade itself has a paper trail.
Why this layer is important: it is the only place returns are ever computed. The cockpit, the orders, the reports, the Strategy tab β all of them just read its output files. That gives you two guarantees: every idea ever tried was graded by the same referee (so numbers are comparable), and the live Friday picks come from the same code path that produced the 10-year backtest (so "backtest" and "live" cannot quietly drift apart).
| Without the harness | With it |
|---|---|
| Each experiment computes returns its own way β results aren't comparable, and bugs hide. | One engine, one cost model, one universe rule. A better number means a better idea, not a different calculator. |
| Today's stock list gets tested on yesterday's market (survivorship + look-ahead creep in silently). | Universe and signal are rebuilt as-of each Friday from past-only data β the lies are structurally impossible. |
| The live picks come from a script that "resembles" the backtest. | The live book IS the last line of the backtest β current_holdings from the same run. |
| Ten variants get tried; the flattering one gets shown. | Every attempt is a numbered trial in the registry; killed ideas stay killed. |
A backtest is a machine for lying to yourself. There are four classic ways it lies, and the harness is one layer whose only job is to make each of them structurally impossible rather than "avoided by being careful." That's why it sits between the data and every result β nothing reaches a dashboard or the registry without passing through it.
The signal is close[tβ21] / close[tβ252] β 1: both shifts point backwards, so the
Friday ranking mathematically cannot contain anything after Friday. The week's return is then
whatever actually happened next.
If you backtest on today's index members, you only test companies that survived β a pre-filtered list of winners. The harness's universe is rebuilt as of each Friday from trailing dollar volume, over a name list that keeps the delisted (SIVB, FRC, TWTR, ATVIβ¦) in history until the day they actually died.
Every change of the book is charged 5 bps Γ turnover. A weekly top-3 book turns over a lot; the charge compounds against you exactly like the returns compound for you.
Every variant ever evaluated gets a trial number in the registry (#1β17 so far), hypotheses must come from literature before the run, the baseline is LOCKED, and a killed idea stays killed. Multiple testing can't hide, because the count is public.
Every idea gets the same report card, computed by the harness from its weekly net returns:
| Metric | What it answers |
|---|---|
| Annual return (CAGR) | How fast the money compounds, net of costs. |
| Annual volatility | How violently it swings on the way. |
| Sharpe ratio | Return per unit of pain β the headline grade for comparing ideas. |
| Max drawdown | The worst peak-to-trough loss you'd have lived through. |
| Hit rate & avg bps/week | How often a week wins, and by how much on average. |
| Per-year (and per-month) splits | Whether one kind regime is hiding inside the average. |
The bars an idea must clear β all of them, net of costs, over the FULL 10-year window: beat SPY and QQQ buy-and-hold (otherwise just buy the index), beat the incumbent base it wants to replace, and hold up in the year-by-year table (a strategy that made everything in one lucky era fails even with a pretty average).
The promotion ladder β every idea walks the same path, recorded in
data/versions.json:
The actual ledger so far (full detail in data/versions.json; the code of retired
strategies was removed in the 2026-09-06 cleanup β their graded records are retained):
| What was graded | Result | Verdict |
|---|---|---|
| 12-1 signal on the top-3 book (trial #14) | 47.2%/yr Β· Sharpe 1.13 Β· maxDD β41% β beat the older 5-day-skip variant on every metric | β PROMOTED β the live book |
| Formation windows 3-1 / 6-1 / 9-1 (trials #15β17) | 14.5% / 42.6% / 43.2%/yr β all below the base, all with deeper drawdowns | β killed (12-1 stays) |
| Earnings-avoid filter (trials #9β10) | Helps the 3-name book (rescues blowup weeks); hurts the diversified book | kept β on the top-3 book only |
| 200-day SMA "go to cash" gate | Falsified twice β below-trend weeks were the book's best (66% win rate) | β killed (intuition lost to data) |
| 13 intraday strategies (ORB, VWAP bands, UT Bot, pivots, gap fadesβ¦) | 0 of 13 beat buy-and-hold at realistic costs | β killed Β· code removed |
| News-sentiment veto on the book | Cannot be backtested honestly (scoring old news with today's knowledge peeks) | retired with the multi-book cleanup |
| Module | Role | Writes |
|---|---|---|
factors.py | Fetches/caches 10yr daily bars; builds the point-in-time panel β prices, liquidity, momentum columns (per-symbol shifts only). | data/bars/*.parquet |
momentum.py | The harness: walk-forward loop, point-in-time universe, filters, turnover costs, per-year splits, trade episodes. | data/momentum.json |
store.py | SQLite history database: every fetched value (sentiment, news, global quotes, picks, orders, account marks, report cards) is stored on each build and read back for trends and live-feed fallbacks. | data/spectrum.db |
ops.py | Operator layer: picks + why, $40k paper tickets, world strip, marquee, reports, WhatsApp links. Never places real orders. | data/ops.json, data/reports/ |
api/server.py | FastAPI β serves the four tabs; /api/* reads the latest snapshot from the history database (JSON files are exports, used only as fallback). Responses carry served_from: "db" | "file". | β |
data/versions.json | The registry: v1 LOCKED, candidates, every trial numbered. Promotion needs explicit approval + forward validation. | hand-audited |