# PolySignals.net: full reference for agents Site: https://polysignals.net Index: https://polysignals.net/llms.txt Live board: https://polysignals.net/signals/ Live plain text: https://polysignals.net/api/v1/live/index.md MCP server: https://polysignals.net/api/mcp (setup: https://polysignals.net/mcp/) OpenAPI: https://polysignals.net/api/v1/openapi.json Operated by Cryptohopper (https://www.cryptohopper.com/). Not financial advice. Not affiliated with Polymarket, Kalshi, Limitless, Gemini, Binance, Deribit, Bybit or OKX. Free, unauthenticated, no uptime guarantee. Terms: https://polysignals.net/terms/ --- # PolySignals.net API (version 2) Free, unauthenticated, read-only JSON. No uptime guarantee. CORS `*` for GET. Not financial advice; not affiliated with Polymarket, Kalshi, Limitless, Gemini, Binance, Deribit, Bybit, OKX or any other venue. Base: `https://polysignals.net/api/v1/` | Endpoint | Content | Written | Cache | | --- | --- | --- | --- | | `GET /signals.json` | Everything: combined + per-venue readings, every coin × timeframe, `current` and `next` window | every tick (`tick_seconds`) | 10 s | | `GET /combined.json` | Combined readings only | once a minute | 30 s | | `GET /venues/{venue}.json` | One venue's readings (`polymarket`, `kalshi`, `limitless`, `gemini`, `binance_options`, `deribit_options`, `bybit_options`, `okx_options`) | once a minute | 30 s | | `GET /history/{coin}.json` | Last 48 h: combined at 1-minute, per venue at 5-minute resolution | every 5 min | 5 min | | `GET /results.json` | Live backtest: per coin and strategy, what following the calls on Coinbase USD has returned vs buy & hold | once a minute | 1 min | | `GET /results/{coin}.json` | One coin's backtest with its recent trades and equity curve | every 5 min | 5 min | | `GET /meta.json` | Coins, timeframes, venues (id, name, url, enabled, coverage), thresholds, methodology version | on start / every 5 min | 5 min | | `GET /openapi.json` | OpenAPI 3.1 description | on start / every 5 min | 5 min | | `GET /markets.json` | Every Cryptohopper market that receives the daily trend signals, per exchange and coin (see below) | after each market-list refresh, about daily | 10 min | | `GET /deliveries.json` | The signaler's recent daily trend signals, newest first: coin, `buy` or `sell`, `first_at` and `last_at` of its sends, and how many markets `sent` (accepted), `rejected` or `expired`, with the `exchanges` it reached, and while a decision is still sending, `queued`: the markets it has yet to reach; `totals` counts every signal since the signaler went live (2026-10-12) by `sent`, `rejected` and `expired` | after every send | 1 min | | `GET /trend-history.json` | Each coin's daily trend call over the latest 90 closes: `as_of` (the close of the latest decision), `days` (UTC dates, oldest first, ending at `as_of`) and per coin a string with one letter per day, `L` long, `F` flat, `-` no close stored; the Trend changes page draws it | after every 00:05 UTC decision | 10 min | | `GET /live/index.md` | Every coin's overall call and daily trend call as markdown, for tools that do not run JavaScript | on request | 30 s | | `GET /live/{coin}.md` | One coin as markdown: each window, each venue, its daily trend call, the Cryptohopper markets that get its signals and its latest delivery, its backtest and the newest headlines that name it; a lower-case ticker redirects | on request | 30 s | | `GET /live/trend.md` | The daily trend changes as markdown, newest decision first, with the latest decision named even when it changed no call; then the coins closest to a change (live price against the band that would change the call) and how the latest signals were delivered | on request | 5 min | | `GET /live/trend.xml` | The same changes as an Atom feed, one entry per decision | on request | 5 min | | `GET /live/trend/{coin}.xml` | One coin's trend changes as an Atom feed | on request | 5 min | | `GET /live/markets.md` | `markets.json` as markdown: the exchanges with their counts, then each exchange's markets per coin | on request | 1 h | | `GET /live/markets/{exchange}.md` | One exchange's markets per coin as markdown, by its Cryptohopper key (`binance`, `kraken2`, `crypto` and so on, as in `markets.json`); 404 lists the keys | on request | 1 h | | `GET /live/news.json` | The latest CoinArticle headlines with the tracked coins each names | at most every 5 min | 5 min | The MCP server (Streamable HTTP, read-only, no sign-in) is at `POST https:///api/mcp`, with its server card at `/api/mcp/server-card`; setup for each client is at `https:///mcp/`. Coins: whatever the venues list, discovered at runtime (for example `BTC ETH SOL XRP DOGE HYPE BNB`; `meta.json` has the live list; its `coins` also lists every coin in signals.json with only a regime call, which has no `history/{coin}.json`). Timeframes: `5m 15m 1h 4h 1d`. ## Limits Up to 50 requests per 10 seconds per IP across `/api/` (JSON, live markdown and the MCP server together); above that the API answers `429` for 10 seconds. The JSON files are cached at the edge for as long as their Cache-Control says, and query strings are ignored, so polling faster than the Cache column returns the same file. For a coin's history, read `history/{coin}.json` every 5 minutes, not every tick. ## signals.json ```json { "version": 2, "generated_at": "2026-09-30T20:26:05Z", "tick_seconds": 30, "venues": ["polymarket", "kalshi", "limitless", "gemini", "binance_options", "deribit_options", "bybit_options", "okx_options"], "coins": { "BTC": { "spot_usd": 83616.4, "overall": { "score": 9, "direction": "neutral", "confidence": 71 }, "timeframes": { "1h": { "combined": { "current": { "prob_up": 0.511, "score": 2, "direction": "neutral", "late_window": false, "window_start": "2026-09-30T20:00:00Z", "window_end": "2026-09-30T21:00:00Z", "sources": ["polymarket", "kalshi", "limitless"], "merged_options": [], "divergence": 0, "confidence": { "score": 97, "label": "high", "depth": 0.97, "agreement": 1, "timing": 1, "sources": 3, "independent_sources": 3, "qualifying_sources": 3, "divergence": 0 } }, "next": null }, "venues": { "kalshi": { "current": { "venue": "kalshi", "coin": "BTC", "timeframe": "1h", "window_start": "2026-09-30T20:00:00Z", "window_end": "2026-09-30T21:00:00Z", "prob_up": 0.51, "score": 2, "direction": "neutral", "spread": 0.01, "volume_usd": 333616, "liquidity_usd": null, "derived": true, "stale": false, "late_window": false, "market_url": "https://kalshi.com/markets/kxbtcd/KXBTCD-26SEP3017", "confidence": { "score": 72, "label": "high", "quality": 0.72, "timing": 1 } }, "next": null } } } }, "regime": { "as_of": "2026-10-12", "call": "long", "close": 81742.5, "sma50": 81019.9, "sigma20": 0.01279, "upper": 82055.94, "lower": 79983.86, "distance_sigmas": 0.7, "days_in_state": 57, "next_decision_at": "2026-10-14T00:05:00Z", "intraday": { "call": "long", "as_of": "2026-10-12T08:00:00Z" } } } } } ``` ### Combined reading | Field | Meaning | | --- | --- | | `prob_up` | Quality-weighted mean of the venue Up probabilities (0–1). `null` when no source has quality > 0. | | `score` | `round((prob_up − 0.5) × 200)`, −100…+100. Mostly a readout of the move since the window opened, not a forecast (methodology, "What the score is"). | | `direction` | `strong_up` (≥ 60), `up` (20–59), `neutral` (−19–19), `down` (−59…−20), `strong_down` (≤ −60). | | `late_window` | Less than 20 % of the window remains. | | `window_start`, `window_end` | ISO-8601 UTC. | | `reference_price`, `reference_source`, `move_since_open`, `time_left` | The window's price at the open and where it came from (`venue`: the venue's own price to beat; `collector_spot`: the Coinbase price PolySignals.net took in the window's first 3 minutes; `candle`: the open of the hourly spot candle that started a daily window), price now ÷ that − 1 (4 decimals), and the fraction of the window remaining (3 decimals). The reference is `null` for the next window and for one that opened before a reference was stored. | | `sources` | Venue ids blended in (quality > 0, or every priced venue in the equal-weighted fallback); a merged options reading lists each options venue in it. | | `merged_options` | Options venues merged into one reading before blending: their median `prob_up`, counted as one source. An options venue more than 0.30 from the median of the others is left out and flagged `outlier`. Empty when none were merged. | | `divergence` | max − min `prob_up` among qualifying sources (quality ≥ 0.2); `null` when fewer than two qualify. | | `raw_divergence` | max − min `prob_up` across every priced source regardless of quality; `null` when fewer than two are priced. | | `confidence.score` | `round(100 × depth × agreement × timing)`, 0–100. | | `confidence.label` | `high` ≥ 70, `medium` 40–69, `low` < 40. | | `confidence.depth` | `1 − Π(1 − quality_i)` over the independent blended sources (a window's options venues count as one, at the best member's quality). | | `confidence.agreement` | `clamp(1 − divergence / 0.20, 0, 1)` with two or more qualifying sources; 0.7 otherwise. | | `confidence.timing` | 0.5 if late in the window, else 1. | | `confidence.sources` | Number of venues blended (each venue of a merged options reading counts). | | `confidence.independent_sources` | Readings blended once the window's options venues are merged into one; `depth` runs over these. | | `confidence.qualifying_sources` | Number of independent sources with quality ≥ 0.2. | | `confidence.divergence` | Same as `divergence` above (nullable). | ### Venue reading | Field | Meaning | | --- | --- | | `prob_up`, `score`, `direction` | As above, for this venue alone. `prob_up` is the bid/ask midpoint, or last trade if the spread > 0.10. | | `spread` | Best ask − best bid of the Up share; `null` for AMM venues. | | `volume_usd` | Volume as the venue's own site reports it, refreshed every tick (Polymarket: Gamma market `volumeNum`; Kalshi: `volume_fp`, a contract count where each contract pays $1; Limitless: `volumeFormatted` in USDC; Gemini: the event's `volume` in USD; options venues: the 24 h USD volume of the calls and puts at the two strikes around the reference). | | `depth_usd` | Resting order size within 0.05 of the mid, both sides, in USD, from data the venue already returns per tick: Polymarket full book, Kalshi top of book; `null` for Limitless, Gemini and the options venues. Feeds `depth_score`. | | `liquidity_usd` | Pool liquidity for AMM venues, else `null`. | | `source_type` | `prediction_market` (a yes/no market priced directly) or `options` (implied from call/put prices at the nearest daily expiry: Binance, Deribit, Bybit and OKX options). Options readings cover the 1-day window ending 08:00 UTC and are never blended with prediction-market windows. | | `derived` | Interpolated from a strike ladder (Kalshi hourly, all options readings) rather than a direct Up/Down market. | | `stale` | Quote could not be refreshed; previous values shown; quality 0. | | `late_window` | As above. | | `market_url` | The market on the venue's site. | | `bid`, `ask`, `options` | The Up share's best bid and ask where the venue quotes the window directly (Polymarket, Kalshi 15m, Limitless, Gemini Up/Down; `null` for ladders and options). Options venues add `options: { p_at_spot, atm_iv, rr25, forward_usd, basis, method }`: P(close > spot) read off the market's own price curve (the digital at spot, skew included), the mark IV of the call nearest the forward (or spot), IV of the 25-delta call − IV of the 25-delta put, the expiry's forward and forward ÷ spot − 1, each `null` where the venue gives no input (no delta on Deribit, no forward on Binance); and `method`, how `prob_up` is read: `iv` (N(d₂) from the median mark implied vol of the strikes nearest the price, the forward or spot, and the time to expiry) or `curve` (the price differences of adjacent calls, used when fewer than two strikes carry an IV, there is no forward or spot, or the expiry has passed). | | `flags` | Present only on a reading that is published but never blended: `outlier` (an options venue more than 0.30 from the median of the other options venues on its window) or `degenerate_curve` (the option ladder's implied probabilities span less than 0.50, so `prob_up` is null). | | `confidence` | `{ score, label, quality, timing }`; `score = round(100 × quality × timing)`. `quality = spread_score × activity_score` (× 0.8 if derived; 0 if stale/empty), `spread_score = clamp(1 − spread/0.10, 0, 1)` (AMMs: `clamp(liquidity/20000, 0, 1)`), `activity_score = max(volume_score, depth_score)`, `volume_score = min(1, √(volume/10000))`, `depth_score = min(1, √(depth_usd/5000))` (null depth = 0). | ### Per coin `overall` = confidence-weighted mean of the combined `current` scores of the 1h, 4h and 1d timeframes (late and empty readings skipped; 5m and 15m are left out), with `confidence` = the same weighted mean of the cell confidences. `venue_overall.{venue}` = the same, computed from that venue's own current readings only (also returned as `coins.{COIN}.overall` in `venues/{venue}.json`). `spot_usd` is a display-only spot from Coinbase. When no source clears the quality bar (typically a thin window with zero reported volume) the combined `prob_up` is still filled from the available prices (equal-weighted; non-stale first) with `confidence.score` 0 and `depth` 0, so every priced window shows a direction. Likewise an `overall` whose cells all have confidence 0 is an equal-weighted mean at confidence 0. `regime` is the daily regime strategy's call (methodology, "Daily regime strategy"), decided once a day at 00:05 UTC on the Coinbase close of the day that just ended; it uses no venue data. `null` for a coin with fewer than 200 daily closes or without that day's close (signals.json only). A coin no venue lists but the regime rule calls (every tracked coin with a Coinbase USD market gets a call; many coins are tracked for this rule only) appears in signals.json with `timeframes: {}`, `overall: null` and its `regime` block, and is left out of combined.json, the venue files and the history files. | Field | Meaning | | --- | --- | | `as_of` | The UTC day whose close decided it. | | `call` | `long` once the close is above `upper`, `flat` once it is below `lower`; unchanged in between. | | `close` | That day's close: the Coinbase Exchange last trade (else the Coinbase spot rate) at the collector's first run after 00:00 UTC; the daily candle's close for backfilled days. | | `sma50`, `sigma20` | Mean of the last 50 daily closes; sample standard deviation of the last 20 daily log returns (a fraction). | | `upper`, `lower` | `sma50 × (1 + sigma20)` and `sma50 × (1 − sigma20)`. | | `distance_sigmas` | `(close ÷ sma50 − 1) ÷ sigma20`, 2 decimals. | | `days_in_state` | Consecutive days with this call, this one included, over the coin's whole history: the rule's own calls stand in for the days before PolySignals.net stored any. | | `next_decision_at` | When the next call is decided. | | `intraday` | The four-hour paper variant (strategy `regime_4h`, not relayed): `{ call, as_of }`, the live Coinbase Exchange last trade checked against the same `upper` and `lower` at every four-hour UTC boundary (00, 04, …, 20), `long` above `upper`, `flat` below `lower`, unchanged in between, starting from the daily call. `as_of` is the boundary this call was taken at. `null` before the coin's first check. | ### venues/{venue}.json `{ version: 2, venue, generated_at, coins: { BTC: { overall, timeframes: { "15m": { current, next }, … } } } }` with venue readings as above. ## history/{coin}.json ```json { "coin": "BTC", "generated_at": "2026-09-30T20:35:00Z", "resolution": { "combined": "1m", "venues": "5m" }, "range_hours": 48, "timeframes": { "1h": { "combined": [{ "t": "2026-09-30T20:34:00Z", "prob_up": 0.51, "score": 2, "confidence": 97 }], "venues": { "kalshi": [{ "t": "2026-09-30T20:30:00Z", "prob_up": 0.51, "score": 2, "confidence": 72 }] } } } } ``` Minutes with no usable reading are absent. Files are rebuilt every 5 minutes. ## results.json What following the calls would have returned, per coin, trading the coin against USD on Coinbase. Eight strategies per coin: follow the combined reading of one window (`5m`, `15m`, `1h`, `4h`, `1d`), the coin's `overall` call, its daily `regime` call (long read as Buy, flat as Sell; a coin without a `regime` block holds), or that block's four-hour `intraday` call (`regime_4h`, a paper variant read the same way; a coin without one holds). Long-only: a Buy call moves everything from USD into the coin, a Sell call moves it back, Hold leaves it. A call read at minute t fills at minute t + 1 (the next minute with a quote), buying at the Coinbase Exchange best ask and selling at the best bid, with the fee in `fee` per fill (0.006 = 0.6 %), no slippage past the best price. Equity is marked at the last trade price. The rules are in the methodology ("Results"). Fractions throughout (0.034 = 3.4 %). Coins without a Coinbase USD market (TRX, MNT) are absent. `meta.json` `thresholds.results` carries `fee`, `fillDelayMin` (1, the minutes from a call to its fill) and `altFee` (0.001, the cheaper fee the Results page also shows the same fills at: `(1 + gross_return) × (1 − altFee)^fills − 1`). This is results version 2: it restarted the simulation from scratch, and the numbers of version 1 (filled at a Coinbase index rate read before the minute's readings, up to a minute stale) were dropped. ```json { "version": 1, "results_version": "2", "methodology_version": "5", "generated_at": "2026-10-12T15:20:00Z", "fee": 0.006, "strategies": ["5m", "15m", "1h", "4h", "1d", "overall", "regime", "regime_4h"], "coins": { "BTC": { "started_at": "2026-10-12T15:00:00Z", "last_seen": "2026-10-12T15:20:00Z", "spot_usd": 82918.5, "buy_hold": { "return": 0.0041, "max_drawdown": 0.012 }, "strategies": { "overall": { "return": 0.0027, "gross_return": 0.0148, "trades": 1, "fills": 2, "win_rate": 1, "avg_trade": 0.0027, "max_drawdown": 0.006, "time_in_market": 0.55, "fees_paid": 0.012, "position": "usd", "since": null, "entry_price": null, "call": "hold" } } } } } ``` | Field | Meaning | | --- | --- | | `return` | Equity now ÷ starting capital − 1, net of fees. `buy_hold.return` bought at `started_at`'s ask with one fee and held. | | `gross_return` | The same fills without fees: equity ÷ starting capital − 1 before fees (the bid/ask spread still paid). | | `fills` | Fills so far, buys plus sells; each paid the fee. | | `trades` | Completed round trips (a buy followed by a sell). `win_rate` and `avg_trade` are over those; `null` without one. | | `max_drawdown` | Largest peak-to-trough fall of the equity. | | `time_in_market` | Share of priced minutes spent holding the coin. | | `fees_paid` | Fees paid so far, in units of the starting capital. | | `position`, `since`, `entry_price` | The open position: `coin` since `since` at `entry_price`, or `usd`. | | `call` | The call the strategy holds right now (Buy / Sell / Hold after the dead band). | | `results_version` | The simulation's rules (`"2"`); when they change the simulation restarts from scratch. | | `methodology_version` | The methodology the calls are read by (`"5"`). | ## results/{coin}.json The coin's block from `results.json` plus `trades` (per strategy, the last 100 fills, oldest first: `{ side, t, price, equity, return? }`, `price` being the ask for a buy and the bid for a sell) and `curve`, whose `columns` name the row entries: `[t, spot, 5m, 15m, 1h, 4h, 1d, overall, regime, regime_4h, buy_hold, 5m_gross, 15m_gross, 1h_gross, 4h_gross, 1d_gross, overall_gross, regime_gross, regime_4h_gross]`: the net equity per strategy, buy & hold, then each strategy's gross (fee-free) equity. Rows come every 15 minutes for the last 48 hours, hourly up to 30 days, daily beyond. In rows written before `regime_4h` existed, its columns repeat `regime`'s: the variant started as a copy of the daily strategy's books. ## markets.json ```json { "version": 1, "generated_at": "2026-10-20T11:29:40Z", "exchanges": [{ "key": "kraken2", "name": "Kraken", "markets": 235, "coins": { "BTC": ["XBT/USD", "XBT/USDT", "XBT/EUR"], "SOL": ["SOL/USD", "SOL/EUR"] } }] } ``` `key` is Cryptohopper's exchange key (as in its signal API), `name` its name there, `markets` the exchange's count, and `coins` each coin's markets in the exchange's own format (`BTCUSDT`, `BTC-USD`, `XBT/EUR`), dollar-pegged quotes first. Exchanges are listed larger first. A hopper acts on a signal only when its exchange and market match one listed here. ## Examples ```bash curl -s https://polysignals.net/api/v1/combined.json | jq '.coins.BTC.overall' curl -s https://polysignals.net/api/v1/signals.json | jq '.coins.ETH.timeframes["1h"].combined.current.confidence' curl -s https://polysignals.net/api/v1/signals.json | jq '.coins.BTC.regime' curl -s https://polysignals.net/api/v1/venues/kalshi.json | jq '.coins.SOL["15m"].current.prob_up' curl -s https://polysignals.net/api/v1/history/SOL.json | jq '.timeframes["4h"].combined | length' curl -s https://polysignals.net/api/v1/markets.json | jq '.exchanges[] | select(.key == "kraken2") | .coins.SOL' curl -s https://polysignals.net/api/v1/live/trend.md ``` --- # PolySignals.net methodology (version 5, in effect from its deploy; `meta.json` carries `methodology_changed_at`) ## What the score is The score is `200 × (prob_up − 0.5)`, where `prob_up` is the crowd's price for "Up" in the window that is running now: will the coin close this window above the price it opened at? Once the window has opened, that price mostly restates the move since the open, scaled by the time left: a two-parameter model fed only the move since the open divided by the volatility expected over the time left (a probit on move ÷ (σ × √time left)) reproduces 73–97 % of it. So the score is a readout of how far the coin has already moved in this window, not a forecast of where it goes next. It is not a prediction of the next window, not a trend or momentum measure, and not a trading signal: in a 12-day study no timeframe, venue or variant of following it had an edge, and following the calls mostly paid fees (see Results below). ## Where the numbers come from Prediction markets run rolling "Up or Down" markets for each coin: will the price at the end of a 5-minute, 15-minute, 1-hour, 4-hour or 1-day window be higher than at its start? Each market has an "Up" share that trades between $0 and $1; its price is the crowd's implied probability that the window closes up. PolySignals.net reads these markets on Polymarket, Kalshi, Limitless and Gemini about twice a minute. Coins: whatever the venues list, discovered at runtime (for example BTC, ETH, SOL, XRP, DOGE, HYPE, BNB, ZEC, NEAR, AVAX, TRX, SHIB, MNT; `meta.json` has the live list). Timeframes: 5m, 15m, 1h, 4h, 1d. ## From one market to one reading - **prob_up**: midpoint of the best bid and best ask for the Up share. If the spread is wider than 0.10, the last traded price is used instead. If there is neither, the reading is null. - **score**: `round((prob_up − 0.5) × 200)`, −100 to +100. - **direction**: `strong_up` (≥ 60), `up` (20–59), `neutral` (−19–19), `down` (−59 to −20), `strong_down` (≤ −60). - **quality** (0–1): `spread_score × activity_score`, with `spread_score = clamp(1 − spread/0.10, 0, 1)` and `activity_score = max(volume_score, depth_score)` where `volume_score = min(1, √(volume / 10000))` and `depth_score = min(1, √(depth_usd / 5000))`. `depth_usd` is the resting order size within 0.05 of the mid, both sides, in USD, taken from data the venue already returns (Polymarket's full book, Kalshi's top of book; Limitless and Gemini give none, so their depth is null and counts as 0). Depth keeps a window that opened seconds ago from scoring as dead. Markets without an order book (AMMs) use `clamp(liquidity / 20000, 0, 1)` instead of the spread score. Derived readings are multiplied by 0.8; stale or empty readings have quality 0. ## Derived readings from strike ladders Kalshi's hourly markets are a ladder of strikes ("above $83,700?", "above $83,800?" …) rather than one Up/Down question. The Up probability is P(close > reference), interpolated linearly between the two strikes that bracket the window's reference price. The reference is the price at the window start: Kalshi publishes it for its 15-minute markets, and the quarter-hour that opens at the top of the hour is used; failing that, PolySignals.net's own spot price captured when the window opened. Such readings are marked `derived: true`. Two more ladders are read the same way. Kalshi's **daily** ladder closes at 5 pm US Eastern, so its 1-day window runs from the previous 5 pm ET (21:00 UTC in summer, 22:00 in winter); the reference is the price at that moment, from the quarter-hour market that opened then, else PolySignals.net's spot at the open, else the opening 1-hour candle. Gemini's **hourly** ladders use the same top-of-the-hour reference as Kalshi's. Gemini reports volume per event rather than per strike, so its ladder readings carry the whole ladder's volume. ## Options-implied readings (Binance, Deribit, Bybit and OKX options) These are **not prediction markets**: every reading carries `source_type: "options"`. `prob_up` is P(close > K) at the nearest daily expiry (08:00 UTC on all four exchanges), with K the window's opening price (the 08:00 UTC spot, stored when the window opens or backfilled from the opening 1-hour candle), read from the venue's mark implied volatility: `N(d₂)`, `d₂ = (ln(F/K) − σ²T/2) / (σ√T)`, where σ is the median mark IV of the strikes nearest K (two below and two above where they exist; a put's IV where the call has none), F the expiry's forward (Deribit, Bybit and OKX publish one; on Binance, which does not, our spot: the one-day carry is under 0.1 %, but a venue's own index can sit about as far from our spot, and the other venues' forwards sat up to 0.2 % from it on 2026-10-13; at the money one day out, 0.1 % in F moves N(d₂) by 0.01 to 0.03) and T the time to expiry in years. The price difference between adjacent calls per unit of strike, `(C(K₁) − C(K₂)) / (K₂ − K₁)`, also implies P(close > K) at each strike; made non-increasing in strike by a monotone fit (pool-adjacent-violators: where these values rise with strike, the run is replaced by its average) and clamped to 0..1, that curve is only the fallback, interpolated at K when fewer than two strikes carry an IV, there is neither a forward nor a spot, or the expiry has passed, and the input to the quarantine below. The reason: one stale mark moves a price difference by its full error but the median vol hardly at all, and an at-the-money N(d₂) barely depends on the vol. `options.method` says which was used (`iv` or `curve`). `options.p_at_spot` stays on the market's own price curve, read at spot: it is the market's digital there, skew included, which the `iv` route at spot would only restate as the at-the-money vol. Coin-settled books (Deribit's and OKX's BTC and ETH options, which are priced in the coin) are converted to dollars first, with the expiry's forward price on Deribit and the spot price on OKX. The window is the 24 h ending at the expiry, so it never coincides with a prediction market's daily window and is never blended with one; readings are `derived: true`, `spread` is null (a digital built from vanilla bid/ask gaps is far wider in probability terms than the market really is), `volume_usd` and `liquidity_usd` are the bracketing strikes' 24 h volume, so quality follows the AMM path (liquidity ÷ 20,000), and `depth_usd` is null. It is a risk-neutral probability with the options market's drift and its vol at K in it (N(d₂) leaves out the slope of the smile, which on the recorded BTC chains was worth under 0.02): a liquid, independent cross-check, not a crowd's bet on direction. ## Reference prices Every combined window publishes its price at the open as `reference_price`, with `reference_source`: `venue` (the price to beat of the Kalshi or Gemini quarter-hour market that opens an hourly window, and Kalshi's for its daily windows; 5- and 15-minute windows have no venue reference), `collector_spot` (the Coinbase price PolySignals.net took in the window's first 3 minutes, never over a venue's) or `candle` (the open of the hourly spot candle that started a daily window). Beside it are `move_since_open` and `time_left`, the two inputs the score mostly restates. A reference taken in a minute applies from that minute's next update. The per-minute history kept for evaluation (90 days, not published beyond the 48-hour files) stores every venue every minute with the Up share's bid and ask, each window's bounds, sources and reference, the overall, the Coinbase Exchange ticker fetched right after the readings, the options venues' `options` block every 5 minutes, and a just-closed window's outcome where Polymarket or Kalshi report it in a request already made. ## Matching windows across venues Two venue readings belong to the same window only when start and end match within 5 seconds. Windows with different boundaries (e.g. daily markets rolling at different hours) are never blended. Which window is shown when several are in play is under "Independent sources" below. A single-source cell is still a valid signal, with lower confidence. ## Independent sources The four options venues (Binance, Deribit, Bybit and OKX options) all price the same 08:00 UTC daily expiry, so on a given day they share one window and say one thing up to four times. Before blending, their priced, fresh readings on a window are merged into one source: the median `prob_up`, at the best member's quality, with the summed volume. `merged_options` lists the venues merged; `sources` still names each of them. Confidence counts independent sources: `depth` runs over them, `confidence.independent_sources` is their number, and the merged options reading is one qualifying source, not four. When a coin and timeframe has more than one window in play, the one with the most independent sources that carry weight (priced, quality > 0, flagged readings not counted) is shown; ties go to the window with more prediction markets, then the one containing Polymarket, then the earliest start. With Polymarket and Limitless both carrying weight on their daily window (priced, quality > 0), that window beats the options window: two independent sources against one. Priced at quality 0, they count as none. ## Quarantine Some readings are published, with a `flags` list, but never blended and never counted in the window choice or the confidence: - `degenerate_curve`: the option ladder's implied probabilities span less than 0.50 from its lowest strike to its highest (for example p = 1 at every strike), so the curve says nothing about the window; the reading is published with `prob_up: null`. Binance's XRP options read p = 1.000 on every mark this way until their prices, quoted per 100 XRP, were divided by the contract size. - `outlier`: an options venue more than 0.30 from the median of the other options venues on its window is left out of their merge. A market is also given a timeframe only when its length is within 2 % of it (Gemini, Limitless), so Gemini's 25-hour ladder is not a daily window and is left out. ## The combined signal `prob_up` = quality-weighted mean of the venue probabilities (sources with quality 0 excluded; if none clears the bar the priced sources are averaged equally at confidence 0). Score and direction use the mapping above. `sources` lists the venues blended. **Qualifying** sources are those with quality ≥ 0.2: only they corroborate each other. `divergence` = max − min `prob_up` among qualifying sources, `null` when fewer than two qualify. `raw_divergence` = the same gap across every priced source regardless of quality, `null` when fewer than two are priced. ## Confidence Combined: `depth = 1 − Π(1 − quality_i)` over the independent blended sources (the options venues merged into one); `agreement = clamp(1 − divergence / 0.20, 0, 1)` when two or more sources qualify, otherwise 0.7 (nothing corroborates the reading, however many thin venues were blended); `timing = 0.5` if late in the window, else 1. `confidence.score = round(100 × depth × agreement × timing)`; labels high ≥ 70, medium ≥ 40, low below. `confidence.sources` is the number of venues blended, `confidence.independent_sources` the number of readings once the options venues are merged, and `confidence.qualifying_sources` the number that qualify. Per venue: `round(100 × quality × timing)`. ## Late-window caveat When less than 20% of a window remains the reading is marked `late_window: true`: the market mostly reflects the move that has already happened inside the window. Late readings are shown faded, halved in confidence, and excluded from the overall score. ## Overall score per coin Confidence-weighted mean of the combined current-window scores of the 1h, 4h and 1d timeframes; its own confidence is the same weighted mean of the cell confidences. Late and null readings are skipped. Null if nothing qualifies. `venue_overall` reads the same three timeframes of one venue. The 5m and 15m cells are published but left out, because they dominated the overall: before version 4 it equalled the Kalshi 15-minute cell for 7 of 9 coins. ## Results: following the calls The Results page is a live backtest, not a forecast: from the first minute a coin had both a Coinbase Exchange quote and a scored reading, PolySignals.net simulates following its calls on the coin's Coinbase USD market, one minute at a time, and keeps the books. These are the rules of results version 2 (`results_version: "2"`). - **Restart.** Version 2 restarted the simulation from scratch; the earlier numbers were dropped, not archived. Version 1 filled at the Coinbase `exchange-rates` index read before the minute's readings were built; that index refreshes once a minute and lags the exchange by 30–60 s, so fills were up to a minute stale and gross returns were overstated by about 9 bp a fill. Before the restart, following the calls made 25 to 41 round trips a day per coin and lost about 98 % of capital to fees in two weeks. - **Strategies.** Eight per coin: the daily regime strategy (below), the headline, and its four-hour paper variant; follow the coin's overall call; or follow the combined reading of one window (5m, 15m, 1h, 4h, 1d). The regime strategy covers every tracked coin with a Coinbase USD market, plus TRON and Mantle on Bybit: 127 coins, chosen from the most liquid Coinbase USD markets, each called once it has 200 daily closes. TRON and Mantle, which Coinbase does not list, take their daily closes and their fills from Bybit's spot USDT market. 104 of them are tracked for the regime rule only: no venue lists them, so they have no window or overall reading, and the Results page lists them under the two regime strategies alone (`results.json` still carries their other six strategies, which never trade). - **Long-only.** Each strategy starts with 1 unit of USD. A Buy call moves everything into the coin, a Sell call moves it back to USD, Hold leaves whatever is held. No shorting, no leverage. - **When a call changes.** The same rule as the live announcements (`shared/src/calls.ts`): the score must clear a direction threshold by a margin of 4, so Hold becomes Buy at +24 and a Buy only falls back to Hold below +16 (symmetric for Sell at −24 and −16); a Buy jumps straight to Sell past −24. Readings with confidence below 40, late-window readings and missing readings leave the held call unchanged. A window rolling over is not special: its first reading settles against the held call like any other. A strategy trades whenever its held call disagrees with its position, so the first call is acted on. - **Fills.** A call read at minute t is filled at minute t + 1, never at the price it was read with: a buy at the Coinbase Exchange best ask, a sell at the best bid, from tickers fetched only after minute t + 1's readings are written. A coin without a ticker in a minute fills nothing and waits for the next minute that has one. No slippage past the best price, no partial fills. Equity is marked at the last trade price (the Coinbase spot rate in a minute without a ticker). - **Gross and net.** Every strategy also keeps a gross track: the same fills with no fee (the bid/ask spread still paid), published as `gross_return` and as fee-free equity in the curve. `return` is net at 0.6 % of the amount per fill (`fee`). The Results page also shows the same fills net at 0.1 % a fill (`thresholds.results.altFee`), as `(1 + gross_return) × (1 − 0.001)^fills − 1`, next to the fees paid and the fills a day (`fills`, buys plus sells, per day since the start). - **Benchmark.** Buy and hold from the same start, bought at that minute's ask with one fee. - **Metrics.** Return (equity − 1), gross return, buy & hold return, fills, round trips, win rate and average return over completed round trips, maximum drawdown, share of priced minutes in the market, fees paid, and the open position. Coins without a Coinbase USD market (TRX, MNT) are not simulated. A coin absent from `signals.json` (no venue quote and no regime call) for 30 days leaves the results; a change of these rules (`results_version`) restarts the simulation. ## Daily regime strategy The seventh strategy of the live backtest, and the one the Cryptohopper signaler relays. It uses no market data from the venues above: only each coin's Coinbase daily closes (UTC days). - **Rule.** At each day's close: `sma50` = the mean of the last 50 closes, `sigma20` = the sample standard deviation (n − 1) of the last 20 daily log returns. A flat call turns long when the close is above `sma50 × (1 + sigma20)`; a long call turns flat when the close is below `sma50 × (1 − sigma20)`; in between the call stays. Long-only: flat means in USD. - **Decision time.** Once a day at 00:05 UTC, on the close of the day that just ended. That close is the coin's Coinbase Exchange last trade (else the Coinbase spot rate) at the collector's first run after 00:00 UTC; backfilled days use the daily candle's close. A coin needs 200 daily closes before it gets a call. Each day's call is stored with its close, so the next decision continues from it; the first decision continues from the call the rule reaches on the stored closes, run from the 50th close as in the measurements below. `signals.json` publishes the call per coin as `regime` (with `sma50`, `sigma20`, the band, the distance in sigmas, the days in the current state and the next decision time); `null` for a coin without enough closes. - **Fills.** The backtest reads long as Buy and flat as Sell at full confidence, so a change of call is filled one minute after it appears, at the Coinbase Exchange ask or bid with the same 0.6 % fee, like every other strategy. - **No per-coin tuning.** The same 50 days, 20 days and one sigma for every coin. - **Measured, 2021-2026.** On Coinbase daily closes, deciding and filling at the close with 0.6 % per fill: BTC +134 % (55 fills) and ETH +150 % (48 fills) net from 2021-04-23 to 2026-10-12, SOL +276 % (45 fills) from 2022-01-03; about five round trips a year per coin. Buy and hold over the same spans, before its fee: +60 %, +5 % and −39 %. - **Caveats.** Statistically marginal: its daily return in excess of holding the coin for the same share of the time has t-statistics of 1.4 to 1.9, below the usual bar of 2, and the rule was picked among several variants on these same three coins. Drawdowns of 52 to 87 % on the nine coins tested over three and a half years or more. It lags buy and hold in bull years (2023 and 2024 for all three coins). It is a bet that a trend premium persists, not a proven edge. **Four-hour variant (paper).** The eighth strategy (`regime_4h`) uses the same band, from the coin's latest daily decision, but checks it every four hours (00, 04, 08, 12, 16 and 20 UTC, at the collector's first run at or after each) against the live price, the Coinbase Exchange last trade: long above the upper band, flat below the lower, unchanged in between. A coin's first check starts from its daily call, and each check continues from the previous one. `signals.json` publishes it as `regime.intraday`; it is filled like every other strategy, and it is on the Results page to be measured, not relayed to Cryptohopper. Each coin's variant started as a copy of its daily regime strategy's books when it was added, so the two agree until their calls first differ. On five years of hourly Coinbase candles it traded 20-30 % more than the daily rule, with returns within noise of it. ## Signals on Cryptohopper PolySignals.net is also a signaler on [Cryptohopper](https://www.cryptohopper.com/). It relays the fills of the daily regime strategy above, whose fills and returns are on the [Results page](https://polysignals.net/results/): about five round trips a year per coin, on 114 coins (91 of them tracked for this rule only, with no venue readings). Every fill (a turn to long while in USD, a turn to flat while in the coin) is sent as a buy or sell signal, for every Cryptohopper exchange and every market of the coin against a USD-pegged quote currency (USD, USDT, USDC, FDUSD and the like) or a fiat currency (EUR, GBP, TRY, JPY, BRL, AUD and others) on every Cryptohopper exchange (the current list and count, per exchange and coin, is `markets.json`). Buy signals carry a stop-loss hint of 15 % (`stop_loss=15`), which a hopper applies only when its owner has enabled it. Disable the hopper's own take-profit: the strategy leaves a position when its call turns flat, not at a profit target, and a take-profit would sell out of the trend early. Cryptohopper applies its own limits (30 signals a minute, 60 in five minutes), so a burst of fills is sent in priority order over the following minutes: oldest fill first, larger exchanges first, USD before USDT before USDC, then EUR and the other fiat currencies. A signal still waiting four hours after its fill is dropped (a day on which many coins turn can queue hundreds). When Cryptohopper answers that it cannot price a market ("Unable to get market rate"), the signal is tried twice more, 15 minutes apart, before it counts as refused. A market whose name Cryptohopper cannot split into coin and quote ("Unable to get quote currency and coin from market", first seen for Binance PUMPU on 2026-10-22) is left out of the market lists from then on. Binance.US and Kraken markets without a trade in the last 24 hours (their public 24-hour tickers, read at each daily refresh) are left out too: Cryptohopper answers "Unable to get market rate" for them even after the retries. On Binance.US, Kraken, Binance, Bybit and HTX, markets whose best bid and ask are more than 2% apart (each exchange's public tickers, read at the same refresh) are left out too, since a hopper's market order there would fill far from the price. Cryptohopper takes its own price at receipt; a hopper only receives a signal when its exchange and quote currency match exactly. What to expect: this is a trend rule, so a coin goes weeks between signals and a position is held for weeks to months, about five round trips a year per coin on the 2021-2026 backtest; every buy carries the 15 % stop-loss hint. The regime board on the Results page shows every coin's state after each day's close, even on days when nothing is sent. ## How a strategy is judged The Results page shows what following each strategy did. Whether a strategy has an edge worth following is judged offline, by `npm run evaluate` (`scripts/evaluate.ts`, `shared/src/evaluation.ts`) on the minutes the collector logs. - **One decision per window.** A decision is a change of call to Buy or Sell, timestamped at its fill one minute later; for the daily regime strategy it is each day's call. Its edge is its direction times the log return from the price at the fill to the end of the window it was read from (60 minutes for the overall call, a day for regime). The same calls are also replayed through the live backtest's books, for gross and net bp per round trip, hit rate, fills a day and time in market. - **Non-overlapping.** A coin's decision inside the horizon of its last counted decision is dropped, and the report says how many, so no statistic counts the same move twice. - **Clustered by timestamp across coins.** The coins move together, so the decisions whose horizon ends on the same UTC day form one cluster (two days for 1d, whose windows differ by coin and last a day); a day is long enough that samples overlapping in time almost always share one. The standard error is cluster-robust with a G/(G−1) correction, and intervals and the gate read it on a Student t with G − 1 degrees of freedom: nine coins moving as one count as one coin, not nine. - **Regime-adjusted.** In each half of the dates, the edge less what holding the coin made over the decision's signed share of that half, so a long-only rule is not credited for a rising market. - **Controls.** Every strategy is also run sign-flipped. A rule that reads a window's reference price is to be re-run with the reference shifted by ±10 bp, since an edge that grows with the shift is an artefact. The 1h and 1d cells, and overall through them, read it: the Kalshi and Gemini ladders and the options venues are P(close > reference). The log keeps those venues' readings, not the ladders and option chains behind them, so the harness cannot yet re-derive them at a shifted reference and does not run this control; its report says so. - **The gate.** A strategy is worth following only with at least 30 decisions on each of at least 5 coins over at least 60 days, and then only when the mean edge per decision is at least 1.5 × the hurdle, its 95 % lower bound clears the hurdle, and the regime-adjusted t clears the bar in both halves. The hurdle is what the strategy paid in fees per decision it made, fee × fills ÷ decisions: one fee (60 bp at 0.6 %) for a cell strategy that fills on each of its calls, about 1.6 bp at 0.6 % for regime, which decides every day and fills about ten times a year. The bar is the one-sided tail of t = 2 after a Bonferroni factor of fourteen, the seven strategies the log replays (not the four-hour regime variant, whose checks are not logged) and their controls, however many a run includes: 2.94 on a normal, 3.21 on a Student t with 30 clusters. With less data the verdict is `insufficient_data`, whatever the numbers say; the history table keeps minutes for 90 days, so on the live table that is every verdict until the log is kept longer. ## Staleness If a venue cannot be refreshed its previous value is kept, marked `stale: true`, and given quality 0 (it no longer influences the blend). If the whole feed is older than 3 update intervals (`generated_at` vs `tick_seconds`), treat it as delayed. ## What this is not A prediction-market price is a snapshot of what traders are willing to pay, often in thin markets, over very short horizons. Blending several makes the snapshot steadier; it does not make it a forecast. Nothing here is financial advice. PolySignals.net is not affiliated with Polymarket, Kalshi, Limitless, Gemini, Binance, Deribit, Bybit, OKX or any other venue.