fundamental_arb dossier — does Black-Scholes fair value beat Polymarket price-threshold markets OOS, net?
Status: INSUFFICIENT_DATA (pre-registered 2026-09-13, before any result was computed; result recorded 2026-09-13).
Pre-registration
Frozen with the G4 checks in tests/g4/. Nothing below may change after a result exists; a change means a new, separately registered hypothesis.
Hypothesis. For Polymarket markets that ask whether an asset's price will be above, below, or touch a strike by a date, a Black-Scholes fair probability (archived fundamental_arb pricer, realized volatility) identifies mispriced YES prices. Taking the model's side when the gap is large earns a positive net return out of sample and beats a no-skill benchmark under the same costs.
This is a limited test. Volatility is realized volatility for every asset. The archived bot preferred ATM implied volatility for equities; that variant is not tested here.
Universe.
- Markets parsed by the archived parser
archive/oddsoddy/strategies/fundamental_arb/fundamental_arb_strategy/scanner.py::parse_market, imported verbatim. endDatebetween 2026-04-10T00:00:00Z and 2026-08-21T09:07:27Z,closedtrue, and a clean resolution:oddsx.data.markets.resolve_outcomegives YES = 0 or 1.- Sides
above,below,touch_above,touch_below.betweenis excluded (the archived config disabled it). - Symbols: BTC/USD, ETH/USD, SOL/USD, AAPL, MSFT, GOOGL, AMZN, NVDA, TSLA, META, SPY.
- Strike sanity: 0.5 ≤ strike / spot at decision ≤ 2.0. This removes index-level strikes mapped to SPY and non-price "index" markets.
Decision time. One decision per market.
t_dec = max(first_trade + 1 hour, end − 24 hours).- Skip the market if
end − t_dec < 12 hours(the archivedmin_hours_to_resolution). - Skip the market if no YES-side trade priced it in the hour before
t_dec.
YES price at decision. Price of trades in [t_dec − 60 min, t_dec), weighted by token_amount.
- YES is the first token in
clobTokenIds. - A trade whose outcome token is the second token is converted to YES price as
1 − price.
Model inputs.
spot: the close of the last Alpaca 1-minute bar that ended at or beforet_dec. Crypto usesBTC/USD,ETH/USD,SOL/USD; stocks use the SIP feed with raw (unadjusted) prices.sigma: annualized standard deviation (ddof=1) of daily log returns between the closes of Alpaca daily bars that started within the 30 days beforet_decand ended at or beforet_dec(no partial bar); × √365 for crypto, × √252 for stocks.T: (end − t_dec) in days / 365.25.r= 0.05.p_modelcomes from the archived pricer:bs_prob_above(above),1 − bs_prob_above(below),bs_prob_touch_above,bs_prob_touch_below.
Signal. edge = p_model − yes_price.
- Buy YES if
edge > 0.08. - Sell YES (buy NO) if
edge < −0.08. - Otherwise no bet.
- 0.08 is the archived bot's 6% edge plus its cost buffer. It is fixed a priori, not tuned on TRAIN.
Returns. Hold to resolution, $1 notional per bet.
- Buy at price p:
(1 − p) / pif YES resolves, otherwise −1. - Sell at price p:
p / (1 − p)if NO resolves, otherwise −1.
Costs. oddsx.research.validation cost model, spread 0.02.
fee_ratefrom the market's fee flags:feesEnabledfalse → 0.0;crypto_fees_v2→ 0.07;finance_prices_feesortech_fees→ 0.04; any other enabled fee type → 0.05.
Split. Calendar split on t_dec: TRAIN before 2026-07-01T00:00:00Z, TEST on or after it. TRAIN is reported, never used to choose anything.
Benchmark. The favorite null on the same decision set and costs. Buy YES when yes_price ≥ 0.5, otherwise sell YES. edge_vs_naive = strategy TEST mean net − benchmark TEST mean net.
Verdict rule (TEST, pooled).
INSUFFICIENT_DATAif TEST has fewer than 200 bets.VALIDATEDonly if all three hold: mean net > 0,edge_vs_naive> 0, and the 95% lower bound of mean net > 0. The lower bound comes from a cluster bootstrap: clusters are (symbol, end), 2000 resamples, seed 7.FALSIFIEDotherwise.
Also reported (no effect on the verdict).
- Per-cohort results by asset type and side.
- Median vs mean net return.
- The top-1% |net PnL| share.
Promotion. A VALIDATED verdict does not move anything into strategies/. Promotion is a separate operator decision.
Results
Computed by research/hypotheses/fundamental_arb/run.py on runs/g4/decisions.parquet (commit 34f1db2). Numbers are copied from registry/results/fundamental-arb.json and runs/g4/universe.json.
Universe accounting. 106,318 markets parsed in the window; 3,005 kept (one decision each). Exclusions:
| reason | markets |
|---|---|
end − t_dec under 12 hours | 66,064 |
| no trades | 26,595 |
| not cleanly resolved | 5,042 |
between side | 3,125 |
no YES trade in the hour before t_dec | 2,475 |
| strike sanity | 12 |
Kept decisions by t_dec month: April 176, May 1,507, June 1,230, July 83, August 9. By cohort: crypto|above 918, crypto|touch_above 1,296, stock|above 786, stock|touch_above 5. No below or touch_below market survived the filters.
TRAIN vs TEST (pooled, $1 per bet, net of costs unless stated).
| metric | TRAIN | TEST |
|---|---|---|
| n_decisions | 2,913 | 92 |
| n_bets | 414 | 15 |
| mean_gross | +0.5720 | −0.3956 |
| mean_net | +0.4712 | −0.6679 |
| median_net | +0.1696 | −1.0253 |
| Sharpe (bet-level) | +0.1413 | −0.6406 |
| favorite-null mean_net | −0.0139 | +0.0410 |
| edge_vs_naive | +0.4851 | −0.7089 |
| cluster-bootstrap 95% lower bound of mean_net | +0.1747 | −1.2193 |
| top-1% |net PnL| share | 0.2195 | 0.2092 |
TEST cohorts (reported, no vote). crypto|above: 0 bets. crypto|touch_above: 11 bets, mean net −0.7536. stock|above: 4 bets, mean net −0.4321.
Tails. In TEST the median bet loses the whole stake (median net −1.03) and the mean is −0.67. In TRAIN the median is well below the mean (+0.17 vs +0.47) and the largest 1% of bets carry about 22% of absolute PnL, so the TRAIN mean leans on a few large winners.
Caveats.
- Limited test: realized volatility for every asset. The archived bot's ATM implied volatility for equities was not tested.
- TRAIN never votes. Its positive numbers (+0.47 mean net, +0.49 edge vs the favorite null) chose nothing and are not evidence the edge holds out of sample.
- TEST has only 15 bets from 92 decisions, far below the 200-bet floor. The TEST numbers are negative, but 15 bets cannot support a verdict in either direction.
- The ≥12-hour rule removed most markets (66,064 of 106,318 parsed): every market with less than 12 hours between decision time and end. The kept set is the minority of longer-dated markets. The split of those exclusions by asset is not recorded in
runs/g4/universe.json. - Decisions thin out sharply after June (92 in July–August against 2,913 before). The cause was not investigated here.
Verdict
INSUFFICIENT_DATA
The frozen rule needs at least 200 TEST bets, and TEST produced 15, so the out-of-sample question stays open: neither validated nor refuted. Nothing moves into strategies/.
Follow-up: fundamental_arb_short (goal G4b) is a separate hypothesis with its own pre-registration. It decides 1 hour before end instead of 24 (at least 30 minutes to resolution), so it can include the short-dated markets this rule excluded. It does not change or re-run this verdict.