Local tool + research · private repo · audit dated Jul 2026

I built the validation harness that killed my own strategy.

Swing Screener started as a CLI that scans a watchlist for the setups I trade. Then I built a backtest engine on top of it. Then I spent a week trying to destroy my own headline result — and largely succeeded. This page is about that second part, because it is the more useful engineering story.

129symbols, 11 sectors, 10 years
4,933trades in the broad re-test
150parameter cells swept
0strategy lines changed to get there

The Problem

A backtest that confirms you is worth nothing.

My walk-forward said the anchored-VWAP-reclaim setup earned +0.23R over 101 trades. That is exactly the kind of number people put in a bio and then trade on. It came from eight correlated large-cap tech names I picked with hindsight, over one arbitrary window alignment. Before putting money behind it, the honest move is to build the test designed to kill it, not the one designed to confirm it.


What I Built

Three layers: screener, backtester, audit.

The screener

A terminal CLI over free yfinance data. Scans a watchlist for three setups — anchored-VWAP reclaim, Minervini-style volatility contraction with volume dry-up, and gap/opening-range levels — and prints a table of what triggered with the key levels. Saves a markdown report for the day.

The backtester

Replays the same detectors bar-by-bar with no lookahead: at each bar it only sees data up to that bar. When a setup fires it opens a trade using that setup's own entry and stop levels, targets a configurable R multiple, and walks forward to stop, target, or timeout. Reports hit rate, average R and expectancy per setup.

The audit harness

Universe expansion, survivorship bucketing, a 150-cell parameter sweep, regime splits, correlation-adjusted significance, block bootstraps, and — the one that mattered — signal-free control benchmarks. Six scripts, all reproducible from the repo.

The bit I'm proudest of. The repo's own backtest loop is O(n²) with pandas overhead — far too slow for a 150-cell sweep across 129 symbols. So I wrote a prefix-sum re-implementation and then asserted it bit-exact against the real repo code path across 160 windows and 16 symbols, zero mismatches. Speed came from a proof of equivalence, not from quietly changing the strategy under the test. No strategy source was modified during the audit.

What The Audit Found

The headline number did not survive.

VERDICT: MARGINAL — do not size up.

Reproduced verbatim from my own report. The recommendation to myself: cap risk at 0.25% of equity, run a 60-trade forward test with a −10R kill switch, and do not scale until forward expectancy clears +0.10R. Trade it small or not at all.

1. It wasn't reproducible

The 126-bar window grid start is arbitrary. Sliding it across 21 alignments on the same eight names gives a median of +0.004R, ranging −0.045R to +0.287R. The +0.23R headline sat near the top of that distribution — a coin flip that landed well.

2. It's mostly long drift

Bucketing symbols by their own 10-year buy-and-hold return, expectancy is a clean monotonic function of it: +0.159R on winners, +0.038R (t = 1.22, not significant) on names that actually went down. That is the signature of harvesting market drift, not of a pattern edge.

3. The signal carries no information

The killer control: entries taken when price is above the anchored VWAP earn +0.100R; entries taken when price is below it earn +0.098R. Identical. The setup's entire premise — that reclaiming the anchored cost basis means something — tests as pure noise.

4. What's left, costs eat

Against a benchmark matched decile-by-decile on risk-as-%-of-price, the signal's incremental contribution is +0.030R, 95% CI [−0.014, +0.077] — crossing zero. Round-trip slippage of 5–10 bps costs 0.018–0.036R, i.e. 60–120% of it.

What did survive, in fairness. The strategy is not fake and it is not curve-fit. Expanded to 129 symbols over 10 years it still returns +0.117R over 4,933 trades with a block-bootstrap 95% CI of [+0.057, +0.174] and a correlation-adjusted t of +3.4. All 150 parameter cells came back positive with a smooth, monotone surface — and the shipped parameterisation sits mid-pack rather than on the peak, which is the strongest evidence against curve-fitting. The raw expectancy is real. It just isn't the signal's.

The Stack

Free data, no black boxes.

Python uv pandas · numpy yfinance Rich (terminal UI) Walk-forward backtest Moving-block bootstrap ICC / design-effect adjustment Risk-matched control benchmarks pytest (no-network fixtures)

Indicator math — anchored VWAP, VCP contraction and volume dry-up, gap arithmetic — is unit-tested against small synthetic OHLCV fixtures with no network calls, so the tests are deterministic and fast.


Honest Status

A local tool and a research artifact.

What it is

A CLI I run on my own watchlist, plus a validation harness and a full written audit. Both work. The audit is reproducible from scripts in the repo.

What it is not

Not a profitable trading system, and I'm not claiming one. There is no live money behind it. The execution engine that would trade this setup exists and is tested, but it stays in paper mode precisely because of what this audit found.

Why this is on my portfolio at all. Anyone can produce a backtest with a good number in it. The transferable skill is building the adversarial test that finds out whether the number means anything — controls, correlation adjustment, survivorship checks, bit-exactness proofs — and then writing down the answer when it goes against you. Educational tooling, not financial advice.