Skip to content
All strategies

Strategies

SOL 15-Minute Polymarket Up/Down Fair-Value Lag: Price the Binary From the Binance SOL Perp's Move Since the Window Open and Its Realized Vol, Buy the UP Token Only When Expected Value Net of the Polymarket Taker Fee and Spread Clears a Threshold, Hold to Settlement (POLYMARKET sol-updown-15m + SOLUSDT.BINANCE 1-MINUTE signal leg)

Outcome: Backtesting

PolymarketSol15mFairValueLagUp

SOL 15-Minute Polymarket Up/Down Fair-Value Lag: Price the Binary From the Binance SOL Perp's Move Since the Window Open and Its Realized Vol, Buy the UP Token Only When Expected Value Net of the Polymarket Taker Fee…

Hypothesis

Original Research Lead hypothesis (archetype 1 of the prediction-market playbook: crypto signal to binary pricing). Polymarket lists a new 'Will SOL be up over the next 15 minutes?' binary every 15 minutes (series sol-updown-15m). It resolves UP if the SOL price at the window's end is at or above the price at its start. Once a window has run for a few minutes, its fair probability is close to a closed-form function of two numbers we hold at 1-minute resolution from Binance: the log move of SOL since the window……Show moreShow less

Original Research Lead hypothesis (archetype 1 of the prediction-market playbook: crypto signal to binary pricing). Polymarket lists a new 'Will SOL be up over the next 15 minutes?' binary every 15 minutes (series sol-updown-15m). It resolves UP if the SOL price at the window's end is at or above the price at its start. Once a window has run for a few minutes, its fair probability is close to a closed-form function of two numbers we hold at 1-minute resolution from Binance: the log move of SOL since the window opened and SOL's short-horizon realized volatility. Under a driftless random walk, P(UP) = Phi( ln(S_t / S_0) / (sigma_1m * sqrt(tau)) ), where S_0 is the SOLUSDT.BINANCE perp 1-minute close at the market's start_ns, S_t is the latest closed perp 1-minute bar, tau is the minutes left to resolution and sigma_1m is the EWMA standard deviation of 1-minute log returns over the trailing vol_lookback_min minutes, floored at 0.0005. The hypothesis is that the retail-dominated 15m binary book reprices more slowly than the Binance perp, which leads crypto price discovery. When SOL has moved decisively within the window, the UP token's last price still lags the fair probability by more than the fee and spread. We buy the UP token and hold it to settlement. Settlement is free, so the taker fee is paid only once. Long-only by construction: the venue is CASH, and 'betting DOWN' would be the complement series, which this test deliberately leaves out to keep a single pre-registered series. Instruments: the tradeable series is expanded by PolymarketSeriesStrategy from the spec {series: 'sol-updown-15m', outcome: 'Up', lookback_days: 60}. The placeholder instrument is SOL-UPDOWN-15M-0-UP.POLYMARKET; the bar type is its 1-MINUTE-LAST-EXTERNAL. SOLUSDT.BINANCE 1-MINUTE bars are listed in extra_instruments as the signal source only and are never traded. Data age: collection began 2026-08-13. The catalog holds resolved SOL 15m markets from 2026-08-12 to 2026-10-17 (about 66 days, about 6,000 markets), so lookback_days 60 can be filled. Venue and corpus choice: POLYMARKET has 2 of 4,834 experiments, and both prior Polymarket runs ended data_unavailable before any trade, so this mechanism has never been measured here. SOL is used instead of BTC to avoid the BTC-heavy corpus. The design is cross-venue (POLYMARKET + BINANCE), short-horizon (1-minute bars, holds of 15 minutes or less), event-driven, and uses no supplementary feed. It is materially different from the two data-blocked predecessors. The BTC 1H near-expiry harvest and the 15m late-window near-certainty harvest both bought only the favored side in the final minutes. This rule trades mid-window (minutes 3 to 10 of 15), at any probability in a bounded band, gated on fee-adjusted expected value rather than on near-certainty. Fee arithmetic (rate 0.07 on crypto up/down): buying at price p costs 0.07 x (1 - p) per dollar staked (2.8% at p = 0.60, 1.75% at p = 0.75), plus about 0.5% half-spread on entry; there is no exit fill. The expected return per dollar is (q - p) / p, where q is the model fair probability. Entry requires (q - p) / p - 0.07 x (1 - p) - 0.005 >= min_net_edge (baseline 0.05). For example, at p = 0.60 that means q >= 0.66; at p = 0.75 it means q >= 0.80. If half of the modelled gap is real, the realized net per trade is about +2.5% to +4% of stake. That is well above the roughly 1% round trip a spread-dominated binary venue costs.

Quota gaps addressed: cross-venue (9.9% vs a target of at least 15%) and short horizon, on POLYMARKET, the most under-used supported venue (2 experiments, both data-blocked). The catalog now holds about 6,000 resolved SOL 15m markets, so the earlier data_unavailable blocker should no longer apply. It is an event-driven design with a fixed clock (resolution), flat most of the time and without supplementary data, which are the tag families with the best survival rates (L224, L189). It avoids every crowded family: no BTC, no momentum confluence, no NR7, no Hyperliquid panel, no funding carry. Long-only is structural on a CASH binary venue.

Implementation

Buys the SOL 15-minute Polymarket UP token when its fair probability, net of the 0.07*(1-p) taker fee and a 0.5% half-spread, beats the token price by at least min_net_edge. The fair probability is a driftless random-walk price, Phi(ln(S_t/S_0)/(sigma_1m*sqrt(tau))), taken from the Binance SOL perp. Entries are allowed from entry_start_min until entry_cutoff_frac of the 15-minute window has passed, and every position is held to settlement. Orders are market orders filled at the close of the decision bar, which on a 24/7 market stands in for the next bar's open.

Iteration 3 fixes the critical alignment defect QA found. Token bars are stamped at irregular seconds within each minute, so the old rule (token timestamp must equal a Binance timestamp exactly) fired on only about 0.14% of decision points. _fair_up now matches one-sided: it uses the latest Binance 1-minute close stamped at or before the token bar's ts_init and less than 60 seconds older (the bar closing at the start of the token bar's minute). This uses no future data and has a match on every token bar. The second decision path in on_extra_bar, and the schedule index it needed, is removed, so decisions are made only on token bars. S_0, the EWMA volatility, tau and minutes_in are computed as before. Setting _pm_entry_cutoff stays in market_signal because strategies may not override on_start, where the base class sets it from the shared entry_cutoff_frac parameter name; setting it again on each call is harmless. Config is unchanged.

Verification Results

Acceptable as is. Ideally the template would read a separately named listing cutoff parameter, so strategies would not need to patch base state.

market_signal sets self._pm_entry_cutoff = 1.0 as a side effect, to neutralise the base class's listing-life cutoff. It works because _pm_decide calls market_signal before checking the cutoff, and the hypothesis's cutoff is applied to the 15-minute window in should_enter_market. But it depends on the base's call order. I verified from the market metadata that start_ns is the listing time, about 24h before the window: for sol-updown-15m-1792278000, start_ts_ns is 1792192132e9 and end_ts_ns is the epoch + 15 min. So the neutralisation is needed and correct.

None required.

S_0 fallback: if the Binance bar that closes at the window open is missing, the code takes S_0 as the open of the first in-window minute. That is a reasonable approximation and does not look ahead, since the bar is already closed when it is used. The fallback cannot recover if the first in-window bar is missing too, in which case that window is skipped. This is harmless.

The fair value comes from the Binance SOLUSDT perp, but Polymarket resolves crypto up/down markets on a Chainlink SOL/USD reference. Near the money, a few basis points of perp-vs-oracle basis can flip outcomes. The p input is the token's last trade, not the mid or ask; in a thin 15-minute book a stale last print can make the model gap look larger than the price actually available. The engine charges PolymarketFeeModel (0.07 x p x (1-p) per share) but models no spread beyond the fill model, so the 0.5% spread_cost in the gate is only an assumption. A driftless-Gaussian q with an EWMA sigma floor of 0.0005 may also be overconfident on fat-tailed 1-minute SOL moves. First check: calibration of q against realized outcomes, and whether the edge survives using entry prices one tick worse.

Iteration History

Verification failed (Layer 3 — sandbox backtest): NotImplementedError Bar type used: SOL-UPDOWN-15M-1792226700-UP.POLYMARKET-1-MINUTE-LAST-EXTERNAL, Bars processed: 0 Ensure your strategy produces trades with the given data and parameters.

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The fill is the token bar's last trade (a trade-driven 1-minute bar with ~14 prints per 15-minute window), while the gate's 0.5% half-spread is a fixed assumption. The model also prices against the Binance perp, but the market settles on its own oracle (start vs end price), so basis and oracle timing add noise to q. The engine charges PolymarketFeeModel's price-dependent taker fee, so the in-gate fee arithmetic is not double-counted.……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The fill is the token bar's last trade (a trade-driven 1-minute bar with ~14 prints per 15-minute window), while the gate's 0.5% half-spread is a fixed assumption. The model also prices against the Binance perp, but the market settles on its own oracle (start vs end price), so basis and oracle timing add noise to q. The engine charges PolymarketFeeModel's price-dependent taker fee, so the in-gate fee arithmetic is not double-counted. These are performance questions for the full backtest only. - [critical] The point-in-time alignment requires the token bar's ts_init to EXACTLY equal a Binance 1-minute close stamp (_fair_up: `self._u_ts != now_ns` -> None; on_extra_bar: `int(token_bar.ts_init) == ts`). Polymarket token bars in the catalog are NOT on the minute grid: they are stamped at irregular times (e.g. for SOL-UPDOWN-15M-1786564800-UP the in-window stamps are +15s, +72s, +132s, +192s, +252s, +313s, +369s ... after the epoch). I measured this on the last 300 sol-updown-15m UP markets: only 6 of 4,274 in-window token bars (0.14%) fall exactly on a minute boundary. So q is computed on about 0.14% of the decision points the hypothesis defines (every minute from entry_start_min to the cutoff), roughly 2 per 100 markets before the edge filter. That explains the 2 sandbox trades. The full backtest would test an accidental subsample of timestamp coincidences, not the hypothesis. (line 117) — fix: Match one-sided instead of by equality. On each token bar at now_ns, use the latest Binance close with _u_ts <= now_ns and now_ns - _u_ts < 60s (i.e. the Binance bar closing at floor(now_ns to the minute)). That is point-in-time correct and always available. Keep the S_0 = close at the window-open boundary (ts % win == 0) as it is. The on_extra_bar re-decision path can then be dropped, or changed to use the latest token bar with ts_init <= the Binance ts within the same minute (never a later token bar). Compute minutes_in and tau from the token bar's now_ns (as now). - [warning] S_0 fallback: when the boundary Binance bar is missing, the first in-window bar's open is used. That is acceptable (open ~= the previous close on a continuous perp). Note that if both are missing, _k_ts never matches and that window is skipped silently, which is fine. (line 98) — fix: None required. - [warning] market_signal overwrites the base _pm_entry_cutoff with 1.0 on every call so the base's listing-life cutoff (start_ns is the ~24h-earlier listing) does not block entries; the 15-minute window gate is enforced in should_enter_market instead. That is correct, but it relies on a side effect inside a signal hook. (line 134) — fix: Set self._pm_entry_cutoff = 1.0 once in on_start (after super().on_start()).

Match one-sided instead of by equality. On each token bar at now_ns, use the latest Binance close with _u_ts <= now_ns and now_ns - _u_ts < 60s (i.e. the Binance bar closing at floor(now_ns to the minute)). That is point-in-time correct and always available. Keep the S_0 = close at the window-open boundary (ts % win == 0) as it is. The on_extra_bar re-decision path can then be dropped, or changed to use the latest token bar with ts_init <= the Binance ts within the same minute (never a later token bar). Compute minutes_in and tau from the token bar's now_ns (as now).

The point-in-time alignment requires the token bar's ts_init to EXACTLY equal a Binance 1-minute close stamp (_fair_up: `self._u_ts != now_ns` -> None; on_extra_bar: `int(token_bar.ts_init) == ts`). Polymarket token bars in the catalog are NOT on the minute grid: they are stamped at irregular times (e.g. for SOL-UPDOWN-15M-1786564800-UP the in-window stamps are +15s, +72s, +132s, +192s, +252s, +313s, +369s ... after the epoch). I measured this on the last 300 sol-updown-15m UP markets: only 6 of 4,274 in-window token bars (0.14%) fall exactly on a minute boundary. So q is computed on about 0.14% of the decision points the hypothesis defines (every minute from entry_start_min to the cutoff), roughly 2 per 100 markets before the edge filter. That explains the 2 sandbox trades. The full backtest would test an accidental subsample of timestamp coincidences, not the hypothesis.

S_0 fallback: when the boundary Binance bar is missing, the first in-window bar's open is used. That is acceptable (open ~= the previous close on a continuous perp). Note that if both are missing, _k_ts never matches and that window is skipped silently, which is fine.

Set self._pm_entry_cutoff = 1.0 once in on_start (after super().on_start()).

market_signal overwrites the base _pm_entry_cutoff with 1.0 on every call so the base's listing-life cutoff (start_ns is the ~24h-earlier listing) does not block entries; the 15-minute window gate is enforced in should_enter_market instead. That is correct, but it relies on a side effect inside a signal hook.

Backtest Review

Sharpe
-5.46
Total return
-45.17%
Max drawdown
57.39%
Trades
2955
Win rate
35.8%
Profit factor
0.92

Backtest and paper results are hypothetical. Trading involves risk of loss.