Markets research lab
A small Python lab where I backtested a few well-known trading ideas on public data, paper only.
Backtests and paper trading on public data only. Research, not financial advice.
−26.4¢ per dollar staked by the Nothing Ever Happens rule on mostly crypto markets, before fees
Buying No: return per $1 staked before fees, by No price 24 hours before close
0 to 10¢, 86 markets: 1 of 86 won; 85 of them were crypto price markets.
| Label | Value |
|---|---|
| 0 to 10¢, 86 markets | −86.3¢ |
| 10 to 20¢, 37 markets | +28.7¢ |
| 20 to 30¢, 29 markets | −3.6¢ |
| 30 to 40¢, 21 markets | +9.3¢ |
| 40 to 50¢, 18 markets | −0.3¢ |
| 50 to 60¢, 21 markets | +22.4¢ |
| 60 to 70¢, 20 markets | +14.6¢ |
| 70 to 80¢, 18 markets | +18.8¢ |
| 80 to 90¢, 30 markets | +9.2¢ |
| 90¢ to $1, 173 markets | +0.9¢ |
- Where
- Personal project
- Role
- Solo builder
- Built with
- Python, pandas, NumPy, SciPy, pytest, uv, TypeScript, SQLite, Vitest, FRED and Polymarket public APIs
What I made
I built it in Python. Each study's question, time window, filters and test went into git before any result existed, and each backtest used one fixed set of parameters on a saved snapshot of public data.
- Public dataFRED, Polymarket, SEC filings and fund prices, saved as a hashed snapshot
- Method firstquestion, window, filters and test committed before any result
- BacktestPython reads only the snapshot, one fixed parameter set, paper only
- Confidence intervalseeded bootstrap or permutation test behind each number
- Pagefigures copied from the results file the code wrote
5studies, each with its method committed to git before its result
9,964Polymarket markets priced 24 hours before close
235tests, including a recompute of every results file
What came out of it
Favourite-longshot bias
On 9,964 Polymarket markets, favourites and longshots won about as often as their prices implied a day before close. Bought at those prices, though, longshots under 30¢ would have lost 25.0 cents per dollar staked before fees, most of it under 10¢.
Polymarket contracts: realised minus implied win rate, by price 24 hours before close
40 to 50¢, 1,358 markets: The only range whose 95% interval excludes zero: a pattern to check, not a finding.
| Label | Value |
|---|---|
| 0 to 10¢, 2,299 markets | −0.3 pts |
| 10 to 20¢, 1,173 markets | −1 pts |
| 20 to 30¢, 1,445 markets | −0.9 pts |
| 30 to 40¢, 1,028 markets | −2.8 pts |
| 40 to 50¢, 1,358 markets | −8.9 pts |
| 50 to 60¢, 1,130 markets | −0.3 pts |
| 60 to 70¢, 434 markets | +3.1 pts |
| 70 to 80¢, 396 markets | +0.2 pts |
| 80 to 90¢, 262 markets | +0.3 pts |
| 90¢ to $1, 439 markets | +0.2 pts |
Nothing Ever Happens rule
The rule, from an open-source Polymarket bot, buys No at 65¢ or less. On 222 such markets No won about as often as its price implied, yet would have lost 26.4 cents per dollar staked before fees (95% CI 3.4 to 47.1).
Kelly sizing
I simulated 10K paths of 500 bets with an invented edge: a 50¢ contract believed to win 55% of the time that actually won 52%. Full Kelly's median worst drawdown was 93.4%; capping each stake held it to 27.8%.
Kelly sizing with an overestimated edge: median worst drawdown over 500 bets
Any of the three, capped at 5% of current and 2% of starting capital per bet: With the cap, all three rules had the same median drawdown.
| Label | Value |
|---|---|
| Full Kelly | 93.4% |
| Half Kelly | 66.9% |
| Quarter Kelly | 39.6% |
| Any of the three, capped at 5% of current and 2% of starting capital per bet | 27.8% |
Fed, CPI and jobs-report days
On FRED data from October 2016 to September 2026, the S&P 500's slightly bigger moves on release days didn't hold up after a correction for nine tests. The 2-year Treasury yield moved more on all three.
2-year Treasury yield: average daily move by type of day
Other trading days: The baseline every release type sits above.
| Label | Value |
|---|---|
| Fed decision days | 5.4 bp |
| CPI release days | 5.3 bp |
| Jobs report days | 5.8 bp |
| Other trading days | 3.3 bp |
An ETF that bet against a TV host's picks (SJIM)
I rebuilt the fund's total return from its archived prices and matched it to its own published figures. Over 235 trading days it returned −15.7% against SPY's +28.1%, but one fund over eleven months can't separate the strategy from the market.
SJIM against SPY: total return over the fund's life
An ETF that bet against a TV host's stock picks (SJIM): Mean daily difference −0.18 points; its 95% interval, −0.38 to +0.04, includes zero.
| Label | Value |
|---|---|
| An ETF that bet against a TV host's stock picks (SJIM) | −15.7% |
| SPDR S&P 500 ETF (SPY) | +28.1% |
An earlier paper-trading harness
Before the lab I built a TypeScript and SQLite harness that scored Polymarket wallet trades against 21 versions of a copy-trading strategy, 1.16M simulated decisions. 14 of its 20 version changes made exactly the same calls as the version before.
Paper-trading harness: where one version's buy signals went
Copied: The share of buy signals this version copied.
| Label | Value |
|---|---|
| Wallet over 20 buys an hour (noise filter) | 41.9% |
| Wallet's own stake below the minimum | 33.1% |
| Price below the floor | 10% |
| Price above the ceiling | 7.4% |
| Daily exposure cap reached | 4% |
| Simulated cash used up | 2.4% |
| Market already held | 1% |
| Copied | 0.3% |
Where it's going
The full version is a strategy library. Each trading idea gets a method registered before any result, a backtest on public data, then paper trading on new data.
- NowFive studiesEach method committed before its result, backtested on a saved public snapshot.
- Retests on later dataPatterns found after the fact are retested on later political and global-events markets.
- Strategy libraryEvery idea is a module: its method, its data snapshot, one fixed parameter set.
- Forward paper tradingEach registered idea is paper-traded on new data, never with real money.