Automates steps 1–7. Refuses to trade. Every strategy must survive the gauntlet.
Educational research only — not financial advice. This software never trades and never will.
flowchart LR
A["1 · IDEA<br/><i>falsifiable thesis</i>"] --> B["2 · DATA<br/><i>yfinance · cached</i>"]
B --> C["3 · ENCODE<br/><i>fn(df) → trades</i>"]
C --> D["4 · BACKTEST<br/><i>4 bps costs always</i>"]
D --> E["5 · ANALYZE<br/><i>metrics in R</i>"]
E --> F["6 · OPTIMIZE<br/><i>≤ 2 params · wide steps</i>"]
F --> G["7 · OVERFIT CHECK<br/><i>IS/OOS gauntlet</i>"]
G --> H{{"VERDICT"}}
H -->|PASS| I["8 · LIVE SETUP<br/><i>human-only</i>"]
H -->|FAIL| J["record reasons<br/><i>propose ONE new hypothesis</i>"]
A hypothesis earns PASS only if all six gates hold — no exceptions, no partial credit:
| # | Gate | Threshold |
|---|---|---|
| Ⅰ | Sample size | ≥ 100 trades |
| Ⅱ | Expectancy | Average trade > 0 after 4 bps round-trip costs — a backtest without costs is void |
| Ⅲ | Split integrity | In-sample / out-of-sample 80/20, ordered by time, never overlapping |
| Ⅳ | Bootstrap EV | 10,000 resamples in R-multiples: P(EV < 0) < 5% OOS and < 20% IS |
| Ⅴ | Overfitting signal | IS and OOS 95% CIs must overlap |
| Ⅵ | Order-luck Monte Carlo | 1,000 runs: ≤ 10% losing simulations |
Plus one discipline the software enforces structurally: every filtered variant is reported next to its unfiltered BASE CASE. If the filter can't beat the dumb version, the filter is decoration.
Survivors get the risk yardstick table — simulated percentiles of P&L and drawdown after 10/20/40 trades. This is the future live kill-switch map: 75th percentile DD → cut size · 95th percentile DD → full stop. A human always decides.
Doctrine: "If you haven't completed every single step, don't trade. Save your money." Process over prediction. A FAIL is progress — record reasons, move on.
| verdict | hypothesis | family | trades | avg trade | OOS P(EV<0) | MC losing |
|---|---|---|---|---|---|---|
| ✗ | donchian20-atr-regime |
breakout (filtered) | 33 | +217.5 bps | 13.5% | 0.4% |
| ✗ | donchian20-base |
breakout (base) | 52 | +197.9 bps | 15.3% | 0.0% |
| ✗ | gapdown2-revert |
mean-reversion (base) | 29 | −0.4 bps | 59.0% | 42.9% |
| ✗ | overnight-base |
overnight (base) | 2,935 | +1.8 bps | 15.9% | 15.9% |
| ✗ | overnight-sma20 |
overnight (filtered) | 1,992 | +0.4 bps | 54.4% | 53.8% |
| ✗ | turn-of-month-3d |
seasonality (base) | 140 | +25.2 bps | 44.6% | 45.2% |
0 PASS / 6 FAIL — and that is the system working. Donchian shows a real edge
(+198 bps/trade, PF 4.15) but dies on sample size; the overnight anomaly exists
but can't clear 4 bps of costs. The gauntlet exists to say no before your
account does. Full reasoning per hypothesis in artifacts/00-index.md.
| Layer | Implementation |
|---|---|
| Data | yfinance daily bars, 2015 → today, parquet-cached, MultiIndex flattened |
| Hypotheses | {id, family, falsifiable thesis, symbol, fn(df) → trades DataFrame} |
| Backtester | Next-bar fills, no look-ahead (unit-tested), costs on every trade |
| Validation | Bootstrap EV in R · shuffle MC · time-ordered 80/20 split · CI overlap |
| Risk | Percentile yardstick map with kill-switch thresholds (75th/95th DD) |
| Determinism | Hypothesis id → SHA-256 seed → identical verdicts every run |
| Automation | Weekly cron: engine → verdict diff → summary with iteration proposals |
Every trade is generated by next-bar execution and verified by regression tests — including one born from a real bug: a Donchian exit window that included the current bar could never be breached by its own close (0 trades forever). It's now a named test so it can never come back.
git clone https://github.com/Ferry737/quant-finder && cd quant-finder
uv sync # .venv · Python 3.12 · numpy · pandas · yfinance · pyarrow
uv run python -m pytest tests/ -q # 49 tests
uv run python finder.py # full engine run, ~2–5 min (bootstrap dominates)engine run complete: 6 hypotheses, 0 PASS, 0 ERROR
global index: artifacts/00-index.md
[ FAIL] donchian20-base trades=52 avg=197.9bps
...
Deliverables per run: artifacts/00-index.md (global, PASS-first) and one
artifacts/<id>/ directory per hypothesis — 00-index.md (thesis, verdict,
reasons, metrics, yardsticks), verdict.json (machine-readable), trades.csv.
The engine snapshots verdicts to .last_run/verdicts.json and diffs every run
against the previous one — verdict changes are headline news.
Edit build_bank() in finder.py:
("my-hypothesis-id", "family",
"Falsifiable question: does X beat costs under Y?",
"SPY", base=True, variant_of=None,
lambda df, r: run_my_logic(df, r)),fn(df, risk_R) → trades DataFrame with columns
[entry_date, exit_date, gross_ret, risk_R]. The id seeds the RNG, so the same
id always produces the same verdict. Then: add a unit test → run the suite →
run the engine → commit.
Iteration discipline: never mutate a failed hypothesis — propose one concrete change as a new hypothesis. Never stack filters to dodge a drawdown; that is overfitting — re-size or redesign instead. Max 2 parameters optimized, wide steps; consistency across top rows beats max net profit.
A desktop app on top of the same gauntlet. It reads the papers Wall Street actually cites, converts their documented claims into testable hypotheses with a local LLM (no cloud, no API keys), and lets the gauntlet judge the output.
uv run python run_lab.py # dashboard → http://127.0.0.1:8517
uv run python run_lab.py --headless --paper mop-2012-tsmom --n 2
uv run python run_lab.py --list-papers| Paper library | 10 curated papers — Lou/Polk/Skouras overnight tug-of-war, Moskowitz/Ooi/Pedersen time-series momentum, George/Hwang 52-week high, Jegadeesh/Titman momentum, Asness value & momentum everywhere, and more. Two honest tiers: open-access (arXiv full text — the LLM reads the actual paper) and abstract-tier (paywalled classics — the documented claim is embedded, never invented) |
| Local model | Qwen2.5-14B-Instruct Q4_K_M (~9GB VRAM — fits a 12GB GPU) via llama.cpp. Download once: huggingface.co/bartowski/Qwen2.5-14B-Instruct-GGUF → models/ (gitignored) |
| Safety rail | The LLM can only choose among 6 whitelisted rule types with clamped parameters — it proposes, it never executes. The gauntlet remains the only judge |
| Artifacts | Every run persisted to artifacts/lab/<run_id>/run.json + per-hypothesis 00-index.md |
First live runs on the developer's RTX 5070: the LLM read the TSMOM claim and proposed a 12-month momentum rule (FAIL: 20 trades < 100 — the gauntlet said no); it read the 52-week-high claim and proposed a 50-day Donchian breakout with a 20-day-low exit (FAIL: sample). The pipeline works precisely because it refuses to flatter the papers.
- No live trading, ever — the human is the only executor of steps 8–9.
- A FAIL is progress — record reasons, move on.
- Filters must earn their place against the base case, or they are decoration.
- Re-size or redesign — never optimize your way around a drawdown.
Built by an autonomous agent under human command. The human holds the only keys to steps 8–9.
⚖️ Educational research — not financial advice. No warranties, expressed or implied.