Skip to content

Futures Calendar Spread

Pair dated futures on one venue — every adjacent near/far pair, plus every pair of monthly and quarterly contracts at least 7 days apart — and rank them by the post-fee APR of locking in the spread between the two expiries.


GET/v1/arbitrage/futures-calendar-spread

List calendar spread rows

The board covers Binance (USDT-M and COIN-M), OKX, Bybit (linear and inverse) and Deribit; both legs share one venue and one margin type. While a venue's quotes in the 5-minute dated-quotes snapshot (dataset arbitrage_dated_quotes, written by the arbitrage-boards cron with top-20 book walks and VWAP fills at the default $10,000 notional) are at most 10 minutes old, the board prices that venue from it, so rows can be executable at any minute of the hour; the answer is then dated by the snapshot (dataset_id: "arbitrage_dated_quotes", stale after 10 minutes) and cached up to s-maxage=100, stale-while-revalidate=200. A venue the snapshot lacks, or holds only older quotes for, is served from the hourly term-structure snapshot (written at :03 UTC, stale after 2 hours), its pairs indicative. When the stored snapshot is past its 2-hour SLA, empty, or holds only reference quotes, the scanner reads the venues live (meta.source: "live") and merges per venue: a venue that answered replaces its stored rows, a venue that failed keeps its stored rows. A failed data-store read returns 503 (service_unavailable, Retry-After: 30).

Order: pairs meeting both tenor floors (near leg at least 3 days out, legs at least 7 days apart) first, then belowTenorFloor pairs; within each group executable pairs with a positive netRollApyPct, then positive pairs executable on a live read (indicativeReason: snapshot_quote), then every other pair, each by netRollApyPct, highest first. An executable pair at a post-fee loss never ranks above a positive spread.

Query parameters

  • Name
    coin
    Type
    string
    Description
    Optional base coin. Supported launch coins: BTC, ETH, SOL, XRP, DOGE, MNT, XAUT.
  • Name
    exchanges
    Type
    string
    Description
    Comma-separated exchange filter: Binance, OKX, Bybit, Deribit.
  • Name
    minApr
    Type
    number
    Description
    Minimum netRollApyPct in percentage points.
  • Name
    minOiUsd
    Type
    number
    Description
    Minimum of near/far open interest in USD.
  • Name
    minVolumeUsd
    Type
    number
    Description
    Minimum of near/far 24h futures volume in USD.
  • Name
    minDepthUsd
    Type
    number
    Description
    Minimum of near/far depth in USD when depth is available.
  • Name
    marginType
    Type
    string
    Description
    Futures margin filter: linear, inverse, or both.
  • Name
    notional
    Type
    number
    Description
    Position notional in USD. Defaults to 10000. A pair is executable only when the depth behind both legs, the smaller leg's reported open interest and the thinner leg's 24h volume are at least this notional; a smaller notional widens the executable set. minDepthUsd filters rows out instead.
  • Name
    limit
    Type
    integer
    Description
    Rows per page. Defaults to 100.
  • Name
    cursor
    Type
    string
    Description
    Opaque cursor from the previous page.

Request

GET
/v1/arbitrage/futures-calendar-spread
curl -G https://www.sharpe.ai/api/v1/arbitrage/futures-calendar-spread \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -d exchanges=OKX \
  -d coin=BTC

Response

{
  "data": {
    "rows": [
      {
        "rank": 1,
        "coin": "BTC",
        "exchange": "OKX",
        "nearContract": "BTC-USDT-261225",
        "nearExpiry": "2026-12-25T08:00:00.000Z",
        "farContract": "BTC-USDT-270326",
        "farExpiry": "2027-03-26T08:00:00.000Z",
        "nearBasisPct": 1.470868,
        "farBasisPct": 2.89074,
        "forwardYieldPct": 5.531205,
        "curve": "Contango",
        "direction": "Buy Near / Sell Far",
        "netRollApyPct": 3.926809,
        "minOpenInterestUsd": 300000000,
        "minDepthUsd": 400000,
        "executionStatus": "indicative",
        "indicativeReason": "snapshot_quote",
        "nearDays": 88.979167,
        "gapDays": 91,
        "belowTenorFloor": false,
        "referenceForwardYieldPct": 5.612539,
        "priceReference": "mark",
        "feesPct": 0.4,
        "updatedAt": "2026-09-27T08:03:00.000Z",
        "isStale": false
      }
    ],
    "scannerMeta": {
      "kind": "futures-calendar-spread",
      "status": "ok",
      "source": "supabase",
      "freshnessSlaSeconds": 7200,
      "notionalUsd": 10000
    },
    "pagination": { "cursor": null, "has_more": false, "total": 1 }
  },
  "meta": {
    "request_id": "req_abc123def456ghij",
    "timestamp": "2026-09-27T08:30:00.000Z",
    "elapsed_ms": 17
  }
}

Formula

referenceForwardYieldPct = (F_far / F_near − 1) × 100 × 365 / gapDays, on each contract's mark price, else its book mid (priceReference); never the last trade, which goes stale on an illiquid contract. It sets curve and direction: a far contract above the near one buys near and sells far.

forwardYieldPct = (sellBid / buyAsk − 1) × 100 × 365 / gapDays: the executable sides the direction trades (contango buys the near ask and sells the far bid; backwardation buys the far ask and sells the near bid).

feesPct = (4 × futures taker fee + 2 × spot taker fee) × 100: the lock-in's fills at the venue's standard rates (0.40 on OKX). Not annualized.

netRollApyPct = (sellBid / buyAsk − 1 − feesPct / 100) × 100 × 365 / gapDays. A losing post-fee spread stays negative rather than being clamped to zero.

gapDays is the fractional gap between the expiries, nearDays the fractional days from the request to the near expiry. nearBasisPct and farBasisPct are each contract's reference price against the venue's spot reference. minDepthUsd is the smaller depth behind the two legs (the top 20 levels where level 1 is thinner than $10,000); market impact within those levels is not modelled.

A pair is executable only when all of these hold; otherwise it is indicative and indicativeReason names the first check that failed:

indicativeReasonThe check that failed
stale_snapshotBoth legs' snapshots are inside the 2-hour term-structure SLA.
no_depthThe depth behind both legs is known.
depth_below_notionalThat depth is at least notional.
oi_below_notionalThe smaller leg's open interest, when reported, is at least notional.
no_volumeThe thinner leg's 24h volume is known and at least notional.
snapshot_quoteEvery other check passes, but the quotes are older than 10 minutes: executable on a live read.

Tenor floors: a pair whose near leg is under 3 days from expiry, or whose legs expire under 7 days apart, is served with belowTenorFloor: true and ranked after every pair meeting both floors (a 1-day Deribit daily spread annualizes a few basis points into −70% or worse).

Was this page helpful?