Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0e16c1c
feat(starters): quant-research-loop — the five-stage trading loop, do…
claude Jun 26, 2026
7db2836
feat(quant-research-loop): enforced trial counting + write-once lockbox
claude Jun 28, 2026
227145c
feat(quant-research-loop): lock trading target + configurable timeframe
claude Jun 28, 2026
08ac6ce
feat(quant-research-loop): wire real BTC daily data (Coin Metrics)
claude Jun 28, 2026
cb8aaba
feat(quant-research-loop): walk-forward K-of-N validation (#3)
claude Jun 28, 2026
488c8a5
feat(quant-research-loop): volatility targeting overlay (beat the ver…
claude Jun 28, 2026
a318aca
feat(quant-research-loop): research budget auto-halt (#4) + forward q…
claude Jun 28, 2026
6347547
feat(quant-research-loop): strategy registry + three new hypotheses
claude Jun 28, 2026
768fafa
feat(quant-research-loop): full 2010-2026 history + true out-of-time …
claude Jun 28, 2026
4221350
docs(stories): quant loop part II — passed research, failed out-of-time
claude Jul 1, 2026
658f9b9
feat(quant-research-loop): on-chain features + MVRV strategies (hones…
claude Jul 1, 2026
5677399
feat(quant-research-loop): cross-sectional momentum (first real signal)
claude Jul 1, 2026
6d52c2f
test(quant-research-loop): fix xsectional vol-target test (tighter ta…
claude Jul 1, 2026
a7aede8
feat(quant-research-loop): market-neutral + risk-off refinements (hon…
claude Jul 1, 2026
87095dd
feat(quant-research-loop): survivorship correction (result deflates)
claude Jul 1, 2026
42fee8a
feat(quant-research-loop): pre-registered forward paper-trade harness
claude Jul 2, 2026
a5b23cc
feat(quant-research-loop): register regime-trend as a second forward …
claude Jul 2, 2026
e25df27
feat(quant-research-loop): per-trade blotter (single-asset)
claude Jul 3, 2026
fee5308
feat(quant-research-loop): per-coin cross-sectional blotter
claude Jul 3, 2026
0c116fb
feat(quant-research-loop): scheduled forward price-check loop
claude Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/quant-forward-track.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Quant Forward Track

# Scheduled forward paper-trade check for starters/quant-research-loop.
# Each run pulls the latest prices, marks the two FROZEN strategies to market, and
# commits the forward P&L record — so from registration onward we can see whether
# the strategies actually make money on data that did not exist when they were
# designed. No re-optimization; forward equity is the verdict.
#
# Activates once merged to the default branch. Trigger manually any time via the
# "Run workflow" button. Self-suppresses (no commit) until new price data exists.

on:
schedule:
- cron: '15 6 * * *' # daily, 06:15 UTC
workflow_dispatch:

permissions:
contents: write

concurrency:
group: quant-forward-track
cancel-in-progress: false

jobs:
track:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Mark frozen strategies to market (live prices)
working-directory: starters/quant-research-loop
run: python -m engine.forward_paper --run --refresh

- name: Commit forward record if it changed
run: |
git config user.name "loop-forward-bot"
git config user.email "actions@users.noreply.github.com"
git add starters/quant-research-loop/quant-forward-state.md \
starters/quant-research-loop/quant-forward-log.md 2>/dev/null || true
if git diff --cached --quiet; then
echo "No change (awaiting new price data or no movement) — nothing to commit."
else
git commit -m "chore(quant): forward paper-trade check [automated]"
git push
fi
10 changes: 10 additions & 0 deletions starters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ npx @cobusgreyling/loop-init . -p pr-babysitter -t claude
| [changelog-drafter](./changelog-drafter/) | Changelog Drafter | Grok, Claude, Codex | L1 draft → L2 |
| [issue-triage](./issue-triage/) | Issue Triage | Grok, Claude, Codex | L1 propose-only |

## Domain example (runnable engine)

| Starter | Domain | Stack | Posture |
|---------|--------|-------|---------|
| [quant-research-loop](./quant-research-loop/) | Crypto strategy research | Pure-stdlib Python | L1/L2 paper-only, real OOS verifier |

A worked example of the five-stage loop applied to quant trading — built the
safe way (paper-only, numerical maker/checker that catches overfitting). See
[stories/quant-loop-the-verifier-problem.md](../stories/quant-loop-the-verifier-problem.md).

After copying:

```bash
Expand Down
11 changes: 11 additions & 0 deletions starters/quant-research-loop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Generated by runs — not committed
paper-account.json
quant-run-log.md
quant-state.md
quant-blotter-*.md
research-ledger.json
# forward-track record (quant-forward-state.md / quant-forward-log.md) IS committed
# so the scheduled loop builds a durable P&L history in git
__pycache__/
*.pyc
data/*.csv
144 changes: 144 additions & 0 deletions starters/quant-research-loop/LOOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Loop Configuration — Quant Research (Crypto, Paper)

The five-stage trading loop from the viral "loop engineering for quant" article,
rebuilt the way this repo insists: **report-first, paper-only, with a real
numerical checker** instead of an LLM that opines on backtests.

**Trading target:** BTC/USDT · spot · daily (1d) bars. Single most-liquid pair
keeps cost assumptions realistic; spot avoids funding/leverage/liquidation; daily
matches the Donchian 20/55 (Turtle) breakout. `--timeframe` sets bar size AND
Sharpe annualization together.

**Data sources:** `coinmetrics` (real daily close, works behind egress policy,
bundled snapshot in `sample-data/`), `live` (Binance OHLCV — US-blocked, use
Binance.US/Coinbase locally), or `--csv` your own OHLCV.

## Active Loops

| Stage | Primitive | Cadence | Status | Module |
|-------|-----------|---------|--------|--------|
| Ingest | Automation | 1h (data dependent) | L1 | `engine/data.py` |
| Signal | Maker sub-agent | on data update | L1 | `engine/strategy.py` |
| Verify | Checker sub-agent | per signal | **gate** | `engine/verifier.py` |
| Execute | Connector | per verified signal | **L1 paper-only** | `engine/paper_broker.py` |
| Risk | Kill switch | every cycle | always on | `engine/risk.py` |

## Human Gates

- **Live trading is NOT wired and will not be added without explicit sign-off.**
`paper_broker.py` has no exchange credentials and places zero real orders.
- Going live requires, at minimum: an order-execution connector behind an
allowlist, position + notional caps, a separate live kill-switch process, and
a human approving the switch. Treat that as a different project.
- A REJECT from the checker is final for that cycle. The maker forms a new
hypothesis; it does not argue with the numbers.

## Anti-overfitting gates (the actual edge of this starter)

- Verdicts are judged **out-of-sample**, never in-sample.
- Sharpe must beat the **deflated benchmark** for the honest `n_trials`.
- **Probabilistic Sharpe >= 0.95** — the result must be distinguishable from noise.
- Every "lesson learned" appended to a skill is a **hypothesis to re-test**, not
an in-sample patch. Self-improving must not become self-overfitting.

### Campaign mode (`--search`) — automated search, kept honest

Because an auto-search loop can no longer rely on you to count how many ideas it
tried, two guards do the accounting structurally:

1. **Enforced trial counting.** `engine/search.py` ticks a counter per candidate;
`engine/ledger.py` persists it in `research-ledger.json` and accumulates it
across cycles. The deflated Sharpe gate consumes the cumulative count — the bar
rises with everything ever searched. (Roadmap #1 — done.)
2. **Write-once lockbox.** `engine/split.py` splits train/validation/lockbox; the
lockbox is opened once on the winner only. The ledger fingerprints it and
BLOCKS any re-open. A BLOCKED verdict means "get fresh data or forward-test."
(Roadmap #2 — done.)

3. **Walk-forward K-of-N (`--walkforward`).** `engine/walkforward.py` re-optimizes
on a rolling in-sample window and scores each next OOS fold; requires K-of-N
folds to pass AND the pooled OOS curve to clear deflated/PSR/drawdown. Catches
strategies that only worked in one regime — which a single lockbox can miss.
(Roadmap #3 — done.)

**Strategy overlay — volatility targeting (`--vol-target`).** Sizes the position
by target_vol/realized_vol so risk is roughly constant; `engine/strategy.py`.
Transforms the breakout on real BTC (drawdown 65%→28%, 5/5 walk-forward folds)
but still misses the aggregate drawdown cap by 3pts at the principled 0.40
default. Lower targets pass — but choosing one post-hoc is uncounted multiple
testing, so the real test is forward data (#5), not a re-run.

4. **Research budget + auto-halt (`--trial-budget`).** `engine/ledger.py` halts
searching once cumulative trials reach the budget — the alpha-spending cap that
stops a forever-loop from turning all data into in-sample data. (#4 — done.)
5. **Forward quarantine (`--forward-test`).** `engine/quarantine.py` holds out the
newest slice, researches on the earlier window, and forward-tests the survivor
on data nothing touched. Approval requires research AND forward to pass; each
forward window is spent after a few tests. The only verdict tuning can't game.
(#5 — done.)

All five hardening steps are now implemented. The harness is "safe to run
unattended" in the research/paper sense.

**Strategy library (`--strategy`):** donchian (breakout), tsmom (time-series
momentum), meanrev (short-term reversion), regime (trend gated by calm vol),
mvrv (on-chain valuation contrarian), trendval (regime + MVRV euphoria brake).
On-chain investigation: neither MVRV formulation cleared the bar or improved the
best price strategy out-of-time — an honest negative (a compelling narrative is
not evidence).

**Multi-asset (`engine/xsectional.py` + `multi_data.py`):** cross-sectional
momentum over a 12-coin panel — the FIRST hypothesis with genuine statistical
signal (beats deflated Sharpe, PSR 1.0). Real, repeatable edge (PSR 1.0, beats deflated bar). Refinements under a
pre-registered 40% drawdown mandate: market-neutral long/short BACKFIRED (crypto
short leg is toxic — squeezes; momentum edge is long-only asymmetric); market-
trend risk-off (cash when BTC below trend) HELPED (drawdown 68%->44-47%, Sharpe
~1.2). Lands close to the 40% mandate (~43-47% DD out-of-sample) but does not
cleanly clear it. Stopped tweaking to avoid uncounted multiple testing.

**Survivorship correction:** expanded the 12 hand-picked survivors to a 32-coin
universe including real collapses (FTT/BTG/BSV/XVG, point-in-time eligibility).
Same strategy deflates: out-of-time Sharpe 1.09->0.78, drawdown 36%->51%, return
+691%->+304%. Survivorship inflated Sharpe ~30-40% and hid ~15pts of drawdown.
PSR stays 1.0 (signal is real) but it is a 51%-DD reject on a realistic universe —
and still an upper bound (truly delisted coins excluded). Real edge, honestly
deflated, not approvable.

**Forward paper trade (`engine/forward_paper.py`, pre-registered):** research is
over; the one real signal is FROZEN (write-once `forward-registration.json`) and
paper-traded forward — no re-optimization, forward equity is the verdict. A proper
loop: `--run --refresh` pulls latest prices and marks to market; the daily
`.github/workflows/quant-forward-track.yml` cron commits the forward P&L record.
Committed + live data both end 2026-05-23 so the record is "awaiting data" until
the source publishes new bars; an illustrative 2024–2025 replay lost money on
cross-sectional (~0.85x) but made +48% on regime — which is exactly why forward,
not backtest, decides. See PREREGISTRATION.md.

**Capstone — true out-of-time test** (research 2010–2020, forward 2020–2026, data
never touched by research/tuning): `regime` was the first to PASS honest research
(5/5 folds, 14% DD) but FAILED forward (37% DD). All strategies made big returns
2020–2026 (tsmom +531%) but with 37–41% drawdowns — beta to a bull market, not
alpha. Verdict: none approvable. The harness correctly refused to dress beta as
alpha. Real edge is the hard part; the loop just stops you fooling yourself.

Remaining work is strategy research (better hypotheses) and, as a separate gated
project, live execution.

## Budget & Observability

- Run log: `quant-run-log.md` (appended each cycle)
- Live state: `quant-state.md`
- Paper account: `paper-account.json`
- Kill switch: drawdown breaker in `risk.py`; set `--kill-drawdown`.

## Phased rollout

1. **L1 (now):** synthetic/CSV data, paper execution, read `quant-state.md`.
2. **L2:** live read-only data feed, paper execution, multi-split walk-forward.
3. **L3 (separate sign-off):** live execution behind allowlist + caps + human gate.

## Links

- Maker skill: `skills/alpha-research/SKILL.md`
- Checker skill: `skills/backtest-verifier/SKILL.md`
- Repo safety: [docs/safety.md](../../docs/safety.md) · [docs/failure-modes.md](../../docs/failure-modes.md)
48 changes: 48 additions & 0 deletions starters/quant-research-loop/PREREGISTRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Pre-Registration — Forward Paper Trade

This is a commitment device. The research phase found exactly one signal with real,
repeatable edge (cross-sectional momentum + market risk-off), honestly deflated by
survivorship to a ~51% drawdown. Rather than keep tuning until a backtest looks
good — the trap this whole project exists to expose — we FREEZE it here and let
forward time judge it.

## The frozen strategies

Two earned a forward test, registered write-once in `forward-registration.json`:

1. **`xsectional-momentum-riskoff`** — long-only top-K cross-sectional momentum over
a 32-coin survivorship-corrected universe (incl. FTT/BTG/BSV/XVG collapses),
with a BTC-trend risk-off to cash and 40% vol targeting. The signal with the
highest raw edge (PSR 1.0) — but a ~51% corrected drawdown.
2. **`regime-trend`** — the humble single-asset BTC trend gated by a calm-vol
regime, 40% vol targeting. Once survivorship is honestly accounted, it is the
better RISK bet (~37% drawdown; on a 2024–25 illustrative replay it returned
+48% at 27% drawdown while cross-sectional lost 15% at 48%).

Both are paper-traded forward in parallel; forward data decides which — if either —
holds up. Exact configs are in `forward-registration.json` (written once).

## The rules

1. **No re-optimization.** The config is frozen. `--run` only marks it to market.
2. **Registration is write-once.** Changing the strategy means registering a NEW
hypothesis with a NEW start date — never editing this one after seeing results.
3. **Forward paper equity is the verdict**, not any backtest. Paper only; no live orders.
4. **Mandate:** max drawdown ≤ 40%. The corrected *historical* drawdown was ~51%
(over mandate), so the honest expectation is that this is a stretch — forward
data is the unbiased tiebreaker, not a foregone pass.

## Honest expectations

This is not a bet that the strategy will pass. It is a bet that we will find out
*honestly*. An illustrative replay of the frozen config on the recent 2024–2025
window lost money (≈0.85× equity, negative Sharpe, ~48% drawdown) — recent crypto
regimes have been hard for momentum. Forward trading tells the truth either way.

## Run it

```bash
python3 -m engine.forward_paper --register --strategy regime-trend # freeze (write-once)
python3 -m engine.forward_paper --run # mark ALL to market (schedule this)
python3 -m engine.forward_paper --since 2024-01-01 # illustrative replay (NOT the live record)
```
Loading
Loading