Max MorrishBack to projects

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−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¢

0 to 10¢, 86 markets: 1 of 86 won; 85 of them were crypto price markets.

Buying No: return per $1 staked before fees, by No price 24 hours before close
LabelValue
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¢
The rule buys at 65¢ or less: the first six bars and part of the seventh. 213 of those 222 markets were crypto, which the bot's own code mostly skips, so this tests the rule, not the bot.
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.

  1. Public dataFRED, Polymarket, SEC filings and fund prices, saved as a hashed snapshot
  2. Method firstquestion, window, filters and test committed before any result
  3. BacktestPython reads only the snapshot, one fixed parameter set, paper only
  4. Confidence intervalseeded bootstrap or permutation test behind each number
  5. Pagefigures copied from the results file the code wrote
How each study runs. A test recomputes every results file and fails if any number changes.

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

0 to 10¢, 2,299 markets−0.3 pts10 to 20¢, 1,173 markets−1 pts20 to 30¢, 1,445 markets−0.9 pts30 to 40¢, 1,028 markets−2.8 pts40 to 50¢, 1,358 markets−8.9 pts50 to 60¢, 1,130 markets−0.3 pts60 to 70¢, 434 markets+3.1 pts70 to 80¢, 396 markets+0.2 pts80 to 90¢, 262 markets+0.3 pts90¢ to $1, 439 markets+0.2 pts

40 to 50¢, 1,358 markets: The only range whose 95% interval excludes zero: a pattern to check, not a finding.

Polymarket contracts: realised minus implied win rate, by price 24 hours before close
LabelValue
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
Left of zero, contracts in that price range won less often than their price implied. Economic data, index and commodity levels, weather, sports and crypto markets, closing February 2024 to September 2026.

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

Full Kelly93.4%Half Kelly66.9%Quarter Kelly39.6%Any of the three, capped at 5% of current and 2% ofstarting capital per bet27.8%

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.

Kelly sizing with an overestimated edge: median worst drawdown over 500 bets
LabelValue
Full Kelly93.4%
Half Kelly66.9%
Quarter Kelly39.6%
Any of the three, capped at 5% of current and 2% of starting capital per bet27.8%
Illustrative edge, not measured. Median ending capital was 0.60, 1.46, 1.41 and 1.40 times the start.

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

Fed decision days5.4 bpCPI release days5.3 bpJobs report days5.8 bpOther trading days3.3 bp

Other trading days: The baseline every release type sits above.

2-year Treasury yield: average daily move by type of day
LabelValue
Fed decision days5.4 bp
CPI release days5.3 bp
Jobs report days5.8 bp
Other trading days3.3 bp
Holm-adjusted p < 0.001 for each release type.

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)−15.7%SPDR S&P 500 ETF (SPY)+28.1%

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.

SJIM against SPY: total return over the fund's life
LabelValue
An ETF that bet against a TV host's stock picks (SJIM)−15.7%
SPDR S&P 500 ETF (SPY)+28.1%
Market-price total return with distributions reinvested, March 2023 to February 2024.

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

Wallet over 20 buys an hour (noise filter)41.9%Wallet's own stake below the minimum33.1%Price below the floor10%Price above the ceiling7.4%Daily exposure cap reached4%Simulated cash used up2.4%Market already held1%Copied0.3%

Copied: The share of buy signals this version copied.

Paper-trading harness: where one version's buy signals went
LabelValue
Wallet over 20 buys an hour (noise filter)41.9%
Wallet's own stake below the minimum33.1%
Price below the floor10%
Price above the ceiling7.4%
Daily exposure cap reached4%
Simulated cash used up2.4%
Market already held1%
Copied0.3%
Simulated, paper only: one version's 32.2K buy signals, each counted under the first rule that stopped it. Shares are rounded and sum to 100.1%. No version had more than 24 resolved simulated buys, too few to rank versions by win rate.

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.

  1. NowFive studiesEach method committed before its result, backtested on a saved public snapshot.
  2. Retests on later dataPatterns found after the fact are retested on later political and global-events markets.
  3. Strategy libraryEvery idea is a module: its method, its data snapshot, one fixed parameter set.
  4. Forward paper tradingEach registered idea is paper-traded on new data, never with real money.