The pipeline
Every station is now built and tested (M1–M5, 278 tests); the amber dashed station is the human lane, by design, not limitation.
TradingView scanner: one query per country, ~26 facts per stock. Yahoo Finance: 14 months of dividend-adjusted daily bars (finalists only), plus the FTSE All-Share and S&P 500 series.
GUARD · live fetch refused unless screen date = fetch dateEvery raw response written to data/snapshots/YYYY-MM/ before any parsing: atomically, never overwritten, self-describing.
GUARD · replay refused if snapshot date ≠ requested screen dateUniverse & tradeability (8) → quality (4) → momentum (3) → value (2). Pure functions; first failure recorded per name; missing data excludes, never passes. ~8,000 in, ~80 out.
Survivors of both countries pooled with still-valid holdings. Composite = half cheapness percentile, half momentum percentile; ties pinned to the ticker; positions 1-based and dense.
Hold buffer (rank ≤ 25 AND above 200-DMA), the three sell reasons, equal-slot buys, every-positive-shortfall top-ups, cash slots when fewer than 12 qualify. Penny-exact: cash in − orders out = remainder, by construction.
data/state/portfolio.jsonfunnel.md/.html with reason-code counts and order-sheet.md/.html with a reconciliation footer and any stale-price flags rendered visibly.
data/t212_availability.csvSaturday: any new buy is opened inside the SIPP to confirm the order ticket exists (R-VENUE); failures are skipped by rule and the next name promoted. Monday: the sheet is executed top to bottom, market orders. This is where C1 lives.
Fill prices recorded; state transitions are pure (apply_fills, built in M3): weighted average cost, high-water-mark ratchet, oversell/overspend impossible.
The monthly report: funnel, orders vs executions, performance vs WLDS, costs, temptations, weather thermometer. The year-one product.
Rest of the month: deliberately blind. No station runs between rebalances except forced corporate events.
Module map
| Module | Job | Status |
|---|---|---|
| config.py | Frozen parameters from YAML; SHA-256 tamper alarm (CRLF-proof) | M1 |
| data.py | Scanner + price clients (as-of guards, throttle, cache), archiver, enrichment (exact 12-1, listing months), snapshot replay | M1–M2 |
| universe.py | Candidate record, Verdict/Reason codes, §4.1 gates, prior-EPS establishment | M2 |
| quality.py · momentum.py · value.py | §4.2 / §4.3 / §4.4 gates | M2 |
| rank.py | Pooled league table, percentile scores, deterministic ties | M3 |
| portfolio.py | State machine: schema-1 state, rebalance decisions, pure fills | M3 |
| screen.py · fills.py | CLI entry points; live/replay pipeline, sheet + fills + availability, md + HTML | M4–M5 |
| paper.py | The Paper from the template; compliance.json; weather rendering | M5 |
The monthly sequence
| When | Lane | What happens |
|---|---|---|
| Sat am | machine | fetch (as-of today) → archive → gates → rank → decisions → funnel + sheet |
| Sat | human | R-VENUE check on new buys; availability CSV updated; re-run if a promotion occurred |
| Mon | human | execute the sheet, top to bottom, market orders |
| Mon | machine | fills recorded → state advanced → the Paper filed under reports/YYYY-MM/ |
| Rest of month | nobody | deliberately blind, except forced corporate events |
Deep reference · data contracts and the test invariants
Data contracts: the types everything speaks
| Contract | Shape & rules |
|---|---|
| Candidate | One name at one screen date. Fundamentals in market currency; price resolves GBX pence and foreign-quoted LSE lines via cap ÷ shares (no FX feed). Series-derived fields are None until enrichment proves them; gates fail closed on None. |
| Verdict | (passed, code, detail): one reason per name per cycle; first failing gate wins, in §4.1 listing order. |
| RankedName / RankingResult | Finite-checked scores, dense 1-based league positions; the unrankable carry evidence, never guesses. |
| RebalanceResult | Typed SELL/BUY/TOPUP/CASH decisions + KEEP evidence; penny ledger reconciles to its own totals; JSON-ready for M4 rendering. |
| Snapshot | {meta: {source, country, market, screen_date, fields}, response}: self-describing, replayable, never overwritten. |
| PortfolioState | Schema-versioned JSON; strict key/finite validation; atomic writes; missing file = empty state. |
Six invariants the tests enforce
| 1 | Tampered rulebook → unmissable alarm and a red suite |
| 2 | Missing or broken data can fail a stock, never pass one; finite-checked at source and at gate |
| 3 | No look-ahead: data cannot be relabelled in either direction |
| 4 | Same inputs → same sheet, always; ties break by documented rules |
| 5 | Every threshold loads from the checksummed rulebook; code carries no diverging copy |
| 6 | Raw data archived before parsing, atomically, never overwritten; money reconciles to the penny |
Publishing
The repo lives at a private GitHub remote (Olaemm/upwave). This site (the explainer, the PRD, this page, and from M4 the per-cycle funnel, sheet, and Paper) deploys as static files to Vercel, access-protected, so a scheduled Saturday run is readable from a phone minutes after it fires. The markdown record stays canonical; the site is its human surface.