Skip to main content

supagamma

Official Python SDK for the SupaGamma API — institutional-grade historical data for prediction markets.

pip install supagamma
from supagamma import SupaGamma

client = SupaGamma(api_key="sg_...")        # or set SUPAGAMMA_API_KEY

for market in client.markets.auto_paginate(limit=500):
    print(market["id"], market["question"])

Async works identically:

from supagamma import AsyncSupaGamma

async with AsyncSupaGamma() as client:
    bars = await client.trades.ohlcv(market_id="546814", timeframe="1d")

Getting a key

Create one in your dashboard. Keys look like sg_ followed by 32 hex characters, and carry scopes — read for browsing, download for anything that spends credits. The SDK validates the format locally so a typo fails immediately instead of costing a round trip.

What's here

Namespace What it does
client.markets Market catalogue, stats, cost estimates
client.trades Raw fills, OHLCV bars, recent trades
client.series The stream catalogue and its estimates
client.download Paid data delivery — see below
client.orders Cart checkout with idempotency
client.billing Balance, transactions, subscription
client.account Identity, usage, API keys, GDPR export
client.system /health, platform stats
client.public_markets Public market metadata (usually disabled)

Typed responses

Every response is still the plain dict the server sent. Where the API publishes a response schema, the SDK also declares its shape as a TypedDict in supagamma.types, so your editor and type checker know every field:

from supagamma.types import Market

market: Market = client.markets.get("1254468")
market["trade_count"]      # int: the data you can buy
market["volume"]           # Optional[float]: None when zero OR unknown

Two rules hold for every model. A declared key is always present (a missing value is None, never an absent key), and timestamps are ISO-8601 strings, as sent. The models are pinned to a snapshot of the API's published OpenAPI spec, so a server-side change fails this SDK's CI instead of drifting. Routes the API publishes no schema for, such as markets.stats() and the subscription endpoints, still return Dict[str, Any].

Backtesting

supagamma.backtest is a dependency-free harness for scoring prediction-market strategies against resolved markets — plus the calibration analysis behind the "does the favourite-longshot bias exist?" study. The engine takes no network, so it unit-tests offline; the data helper bridges it to a live client.

from supagamma import SupaGamma
from supagamma.backtest import Backtest, BetFavourite, calibration
from supagamma.backtest.data import resolved_markets, calibration_pairs

client = SupaGamma(api_key="sg_...")
universe = list(resolved_markets(client, max_markets=1_000))

# Is the market's price an honest probability?
print(calibration(calibration_pairs(universe)).as_table())

# Does backing the favourite beat the book?
result = Backtest(bankroll=1_000).run(universe, BetFavourite(stake=10))
print(result.summary())

The strategy only ever sees a MarketView with no outcome field, so it cannot peek at the answer — look-ahead safety is enforced, not trusted. Write your own by returning an Order(side, stake) (or None) from any callable. A full worked example lives in examples/calibration.py.

It's a research tool, not investment advice, and makes no performance promise: it shows what did happen in historical data. Fees, liquidity, and slippage make live results different.

Four things worth knowing

These are properties of the API, not of this library, and the SDK surfaces them rather than hiding them.

Downloads spend money, so they are never retried

Every client.download.* call except the two estimates debits your balance. The SDK sets a no-retry policy on those routes regardless of how you configure max_retries.

The reason is specific. The server debits after serialising your data but before the body finishes arriving, and the only protection against paying twice is a 7-day entitlement waiver matched on an exact parameter tuple. A retry that re-derives end=datetime.now() looks like a different request to that matcher and is charged again in full. If you retry a download yourself, freeze your parameters first and replay them byte-identically:

start, end = window()          # compute ONCE
try:
    result = client.download.trades(market_id="546814", start=start, end=end)
except supagamma.APITimeoutError:
    time.sleep(2)              # the entitlement row is written in the background
    result = client.download.trades(market_id="546814", start=start, end=end)

429 means two different things

try:
    client.download.orderbook(market_id="546814")
except supagamma.RateLimitError as e:
    time.sleep(e.retry_after)   # transient — the limiter
except supagamma.QuotaExceededError:
    ...                         # a billing cap; retrying can never succeed

RateLimitError clears after retry_after seconds. QuotaExceededError — your monthly fair-use or free-tier cap — clears on a billing-period boundary, carries no Retry-After, and retrying it just burns limiter budget on top. They share a status code and nothing else, which is why they are separate classes.

Truncation is silent

A download that hits its row cap looks exactly like a complete one: no flag, no header, no marker. When completeness matters, estimate first:

est = client.download.raw_estimate(data_type="polymarket_l2_deltas", start=start, end=end)
if est["capped_by_limit"]:
    ...   # narrow the window; paging cannot reach the rest

Downloads have no offset. A dataset larger than the cap is reachable only by narrowing start/end.

Orderbook data is expensive

At roughly 2 KB per row and $5/MB, the default 100,000-row orderbook pull is on the order of $990. The SDK warns above $25 and lets you gate it:

client.download.confirm_cost = lambda usd: usd < 50    # abort anything pricier

Errors

Everything derives from supagamma.SupaGammaError. The ones you will actually branch on:

Exception Meaning
InsufficientCreditsError 402 — .shortfall is exactly what to top up
SubscriptionRequiredError / UpgradeRequiredError 402 — plan doesn't cover this
RateLimitError 429 — transient, honour .retry_after
QuotaExceededError 429 — billing cap, do not retry
NoDataInRangeError 404 — the id is fine, the window is empty
OrderStatusUnknownError 502 on order creation — replay the same idempotency_key
OriginBlockedError 403 — you pointed base_url at the origin, not the API

Every exception carries .status_code, .code, .request_id and the raw .detail. Quote request_id to support; it is the only correlation handle.

Orders and idempotency

client.orders.create() is the one route with real idempotency protection, and it is a body field, not an Idempotency-Key header. The SDK generates a key for you and returns it:

order = client.orders.create([
    supagamma.resources.orders.OrderItem(data_type="trades", market_id="546814"),
])

# On an ambiguous failure, replay with the SAME key — the server returns the
# original order instead of charging again.
client.orders.create(items, idempotency_key=order["idempotency_key"])

Configuration

SupaGamma(
    api_key=None,               # env SUPAGAMMA_API_KEY
    jwt=None,                   # env SUPAGAMMA_JWT — mutually exclusive with api_key
    base_url="https://api.supagamma.com",
    timeout=httpx.Timeout(connect=10, read=300, write=30, pool=10),
    max_retries=3,              # applies only to safe reads
    max_retry_wait_seconds=60,  # refuse to block longer than this on a 429
)

Pass api_key or jwt, never both — sending both makes the server silently use the key and ignore the JWT, so the SDK refuses it up front.

Changes

0.2.0 (not yet on PyPI)

  • supagamma.backtest: the prediction-market backtesting harness.
  • Typed responses in supagamma.types, plus a py.typed marker so type checkers actually read the SDK's annotations. 0.1.0 advertised Typing :: Typed without the marker, so mypy and pyright ignored its types entirely.
  • client.markets, client.trades and the other namespaces are now visible to type checkers. They are attached at runtime, and every call on them used to resolve as Any.
  • markets.list(tag=...) now raises ValueError. The API removed the tag filter on 2026-08-26 and ignores the parameter, so the call had been silently returning an unfiltered list.

Requirements

Python 3.9+. The only runtime dependency is httpx.

Licence

MIT — see LICENSE.

Release files for supagamma 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for supagamma 0.2.0
File Size Uploaded
supagamma-0.2.0.tar.gz 83.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for supagamma 0.2.0
File Interpreter ABI Platform
supagamma-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 154.3 kB

Release files / supagamma-0.2.0.tar.gz

Download URL supagamma-0.2.0.tar.gz
Size 83.8 kB
Tags Source
SHA-256 checksum
How to use checksums
850479a1d71b2e6dbf416b2cf08adb5a7daae33bd33aa2cfe7c92890261c58eb
BLAKE2b-256 checksum
How to use checksums
dd5eb4933c5f9d2962f75508e6a9789a70c67a80bf7092ec7219ae08a92db27e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / supagamma-0.2.0-py3-none-any.whl

Download URL supagamma-0.2.0-py3-none-any.whl
Size 70.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ebecc425faa152f3a278eaabe562b1eed688af0633d1d8e4aed93479b523d70b
BLAKE2b-256 checksum
How to use checksums
40745c44dbdc8350bc2cf7dc22da755e69ffde49717a6c24965cee7d5d5a419c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page