NautilusTrader
Integrations
These docs track the unreleased nightly build and may change without notice. Switch to the latest stable docs.

Polymarket

Founded in 2020, Polymarket is a decentralized prediction market platform that enables traders to speculate on event outcomes by buying and selling outcome tokens.

NautilusTrader provides a venue integration for data and execution via Polymarket's Central Limit Order Book (CLOB) API.

This page documents the V2 integration. The adapter is implemented in Rust and exposed to Python through PyO3 at nautilus_trader.adapters.polymarket; data, execution, signing, and WebSocket operations therefore have the same behavior from Rust and Python.

NautilusTrader supports multiple Polymarket signature types for order signing, which gives flexibility for different wallet configurations while NautilusTrader handles signing and order preparation.

Installation

The Python v2 package includes the Polymarket adapter; no adapter-specific extra is required.

To install the latest Python v2 release candidate:

uv pip install --pre nautilus_trader

To build Python v2 from source, run from the repository root:

make build-debug-v2

For branch development wheels and source-build prerequisites, see Python v2 installation.

Examples

The maintained V2 examples are available in crates/adapters/polymarket/examples for Rust and python/examples/polymarket for Python. The exec tester configurations apply the close precision needed for Polymarket market SELL orders.

Binary options

A binary option is a type of financial exotic option contract in which traders bet on the outcome of a yes-or-no proposition. If the prediction is correct, the trader receives a fixed payout; otherwise, they receive nothing. NautilusTrader represents Polymarket outcome tokens as BinaryOption instruments.

Polymarket uses pUSD as the collateral token for trading, see below for more information.

Polymarket documentation

Polymarket offers resources for different audiences:

  • Polymarket Learn: Educational content and guides for users to understand the platform and how to engage with it.
  • Polymarket CLOB API: Technical documentation for developers interacting with the Polymarket CLOB API.

Overview

This guide assumes a trader is setting up for both live market data feeds and trade execution. The Polymarket integration adapter includes multiple components, which can be used together or separately depending on the use case.

  • PolymarketWebSocketClient: Low-level WebSocket API connectivity (built on top of the Nautilus WebSocketClient written in Rust).
  • PolymarketInstrumentProvider: Instrument parsing and loading functionality for BinaryOption instruments.
  • PolymarketDataClient: A market data feed manager.
  • PolymarketExecutionClient: A trade execution gateway.
  • PolymarketDataClientFactory: Factory for Polymarket data clients (used by the live node builder).
  • PolymarketExecutionClientFactory: Factory for Polymarket execution clients (used by the live node builder).

Most users will define a configuration for a live trading node (as below), and won't need to work with these lower-level components directly.

pUSD

pUSD is the collateral token used for trading on Polymarket. It is a standard ERC-20 token on Polygon, backed by USDC.

The proxy contract address is 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB on Polygon. Direct on-chain funding wraps Polygon USDC.e (bridged USDC) into pUSD through the CollateralOnramp. The Bridge API can also deposit supported assets from other chains and credit pUSD after conversion.

Wallets and accounts

To interact with Polymarket via NautilusTrader, you'll need a Polygon-compatible wallet (such as MetaMask).

Signature types

Polymarket supports multiple signature types for order signing and verification:

Signature TypeWallet TypeDescriptionUse Case
0EOA (Externally Owned Account)Standard EIP712 signatures from wallets with direct private key control.Default. Direct wallet connections (MetaMask, hardware wallets, etc.).
1Email/Magic Wallet ProxySmart contract wallet for email‑based accounts (Magic Link).Polymarket Proxy associated with Email/Magic accounts. Requires funder address.
2Browser Wallet ProxyModified Gnosis Safe (1-of-1 multisig) for browser wallets.Polymarket Proxy associated with browser wallets. Enables UI verification. Requires funder address.
3Deposit WalletERC-1271 deposit wallet flow for new API users.Requires deposit wallet funder; API credentials stay bound to the signer.

See also: Proxy wallet in the Polymarket documentation for more details about signature types and proxy wallet infrastructure.

NautilusTrader defaults to signature type 0 (EOA) but can be configured to use any of the supported signature types via the signature_type configuration parameter.

A single wallet address is supported per trader instance when using environment variables, or multiple wallets could be configured with multiple PolymarketExecutionClient instances.

Ensure your wallet is funded with pUSD, otherwise you will encounter the "not enough balance or allowance" API error when submitting orders.

Setting EOA allowances

The v2 crate includes a direct on‑chain allowance command for EOA accounts. Use it only when the funding wallet is the signer (SignatureType::Eoa). Fund the EOA with POL for gas, set POLYMARKET_PK, and run:

cargo run -p nautilus-polymarket --bin polymarket-set-allowances

The command grants maximum pUSD and CTF approvals to the CLOB Exchange, Neg Risk CTF Exchange, and current Neg Risk Adapter. It uses https://polygon.drpc.org by default; set POLYGON_RPC_URL to use another Polygon RPC endpoint. Run it again if Polymarket changes the required contracts.

Setting smart-wallet allowances

Do not run the EOA command for a proxy, Safe, or Deposit Wallet funder. It signs transactions from the EOA key and cannot grant approvals from a smart contract wallet.

Use Polymarket's wallet and authentication flow to submit the approvals from the account wallet. Deposit Wallet approvals use an ordered WALLET batch authorized by the signer and submitted through the Relayer. Safe and Proxy Wallet approvals need their wallet‑specific SDK payloads.

After the approval transaction confirms, refresh the CLOB cache. Rust callers can use PolymarketClobHttpClient::update_balance_allowance with AssetType::Collateral for pUSD. Use AssetType::Conditional with a conditional token ID for a conditional‑token allowance. Both forms also need the account's signature type. The authenticated request maps to GET /balance-allowance/update. Use SignatureType::Poly1271 for a Deposit Wallet.

API keys

The v2 execution client requires CLOB L2 credentials. Create or derive them with Polymarket's API authentication flow. The v2 crate provides a command that reads POLYMARKET_PK and prints the created or derived credentials:

cargo run -p nautilus-polymarket --bin polymarket-create-api-key

Set the returned values as:

  • POLYMARKET_API_KEY
  • POLYMARKET_API_SECRET
  • POLYMARKET_PASSPHRASE

The credentials authenticate the private‑key signer, not a proxy or Deposit Wallet funder. The public v2 data client does not require these credentials.

Configuration

When setting up NautilusTrader to work with Polymarket, it's crucial to properly configure the necessary parameters, particularly the private key.

Key parameters:

  • private_key: The private key for your wallet used to sign orders. The interpretation depends on your signature_type configuration. If not explicitly provided in the configuration, it will automatically source the POLYMARKET_PK environment variable.
  • funder: The pUSD funding wallet address used for funding trades. If not provided, will source the POLYMARKET_FUNDER environment variable.
  • API credentials: You will need to provide the following API credentials to interact with the Polymarket CLOB:
    • api_key: If not provided, will source the POLYMARKET_API_KEY environment variable.
    • api_secret: If not provided, will source the POLYMARKET_API_SECRET environment variable.
    • passphrase: If not provided, will source the POLYMARKET_PASSPHRASE environment variable. API credentials are created from the private-key signer for L2 authentication. For POLY_1271, the deposit wallet remains the funder, but it is not the L2 auth address.
  • auto_load_missing_instruments (default True): Controls whether subscribe and request commands for an instrument that is not already in the cache trigger an ad-hoc load via the Gamma API. When disabled, subscribing to an uncached instrument returns an error. See Runtime instrument loading.
  • auto_load_debounce_ms (default 100): The window (milliseconds) over which concurrent auto-load requests are coalesced into a single batched Gamma call.

We recommend using environment variables to manage your credentials.

Data capability

Polymarket supports live L2_MBP order book deltas, quotes, and trades. Instrument definitions are published by bootstrap, configured refreshes, new-market discovery, and tick-size changes.

Orders capability

Polymarket operates as a prediction market with a more limited set of order types and instructions compared to traditional exchanges.

For Polymarket live execution, set both the disconnection timeout and post‑stop delay to 30 seconds with with_timeout_disconnection_secs(30) and with_delay_post_stop_secs(30). The delay allows residual order and cancellation events to arrive before disconnection, while the timeout gives each client time to shut down cleanly.

Order types

Order TypeBinary OptionsNotes
MARKETBUY orders require quote quantity, SELL orders require base quantity.
LIMIT
STOP_MARKET-Not supported by Polymarket.
STOP_LIMIT-Not supported by Polymarket.
MARKET_IF_TOUCHED-Not supported by Polymarket.
LIMIT_IF_TOUCHED-Not supported by Polymarket.
TRAILING_STOP_MARKET-Not supported by Polymarket.

Quantity semantics

Polymarket interprets order quantities differently depending on the order type and side:

  • Limit orders interpret quantity as the number of conditional tokens (base units).
  • Market SELL orders also use base-unit quantities.
  • Market BUY orders interpret quantity as quote notional in pUSD.

As a result, a market buy order submitted with a base-denominated quantity will execute far more size than intended.

When submitting market BUY orders, set quote_quantity=True on the order. The adapter converts the quote amount (pUSD) to the signed base-unit share amount before posting to the CLOB. The Polymarket execution client denies base-denominated market buys to prevent unintended fills.

# Market BUY with quote quantity (spend $10 pUSD)
order = strategy.order_factory.market(
    instrument_id=instrument_id,
    order_side=OrderSide.BUY,
    quantity=instrument.make_qty(10.0),
    time_in_force=TimeInForce.IOC,  # Maps to Polymarket FAK
    quote_quantity=True,  # Interpret as pUSD notional
)
strategy.submit_order(order)

Execution instructions

InstructionBinary OptionsNotes
post_onlySupported for limit orders with GTC or GTD only.
reduce_only-Not supported by Polymarket.

Time-in-force options

Polymarket calls the POST /order field orderType. In NautilusTrader, this maps to TimeInForce. The valid combinations depend on the Nautilus order type:

Nautilus TIFPolymarket orderTypeNautilus order scopeNotes
GTCGTCLIMIT onlyGood‑Til‑Cancelled; rests on the book.
GTDGTDLIMIT onlyGood‑Til‑Date; rests until expiration, fill, or cancel.
FOKFOKLIMIT or MARKETFill the full size immediately or cancel the whole order.
IOCFAKLIMIT or MARKETFill available size immediately and cancel the remainder.

Polymarket uses FAK (Fill-And-Kill) for the semantics NautilusTrader calls IOC (Immediate or Cancel). Polymarket docs classify FOK and FAK as market order types, while GTC and GTD are limit order types. For Nautilus MARKET orders, the adapter accepts only IOC and FOK; GTC and GTD are valid for resting LIMIT orders only.

A marketable order (any FOK/FAK order, or a BUY that crosses the book) must be worth at least 1 pUSD in notional value, otherwise the venue rejects it with invalid amount for a marketable BUY order … min size: $1. Resting GTC/GTD limit orders are bounded only by the 5‑share minimum.

Set GTD expiry at least three minutes after submission. Polymarket applies an expiration buffer of roughly one minute, so the order rests for about a minute less than the requested duration. The venue reports expiry as an OrderCanceled event, not OrderExpired.

Advanced order features

FeatureBinary OptionsNotes
Order modification-Cancellation functionality only.
Bracket/OCO orders-Not supported by Polymarket.
Iceberg orders-Not supported by Polymarket.

Batch operations

OperationBinary OptionsNotes
Batch SubmitThe adapter uses POST /orders for independent limit‑order batches (max 15 orders per request). See Batch submit.
Batch Modify-Not supported by Polymarket.
Batch CancelThe adapter uses DELETE /orders. See Batch cancel.

Batch submit

SubmitOrderList commands are routed to Polymarket's POST /orders endpoint. The endpoint accepts at most 15 orders per request (BATCH_ORDER_LIMIT); larger lists are split into sequential 15‑order chunks.

  • Only LIMIT orders are batched. MARKET orders inside the list are routed to the single-order path, which signs a marketable order and submits it with FAK or FOK based on Nautilus time_in_force.
  • reduce_only orders, quote_quantity orders, and post_only with market TIF (IOC or FOK) are rejected before submission.
  • A single eligible order falls through to POST /order so it keeps the single‑order retry semantics; the batch path deliberately disables retry because the venue does not expose an idempotency key.
  • If the batch response omits a leg, that order stays submitted for reconciliation. The adapter registers the signed order's expected hash so later WebSocket events and cancels still resolve to the local order. An omitted response cannot prove that the venue rejected the order.

Batch cancel

BatchCancelOrders and CancelAllOrders commands with resolved venue order IDs use Polymarket's DELETE /orders endpoint. The adapter sends sequential chunks and chooses each new chunk from the smaller of the endpoint's 1,000‑ID limit and the signer's current cancellation burst. A signer starts with the Standard 120‑token burst, and a tier reported by one response applies to the next new chunk.

Each chunk retries independently with the same order IDs unless a lower reported tier requires smaller chunks before the retry. The adapter merges the completed responses and processes each requested order once after every chunk succeeds. If a later chunk exhausts its retries, earlier chunks may already have changed venue state, but the adapter emits no partial per‑order results; reconciliation resolves the unknown overall outcome.

Submit error handling

Polymarket's public documentation describes successful POST /order responses with success, orderID, status, and errorMsg, and documents API errors as structured error responses. It does not document statusless client exceptions or transport failures as venue rejections.

The adapter rejects only when the response proves the order was not accepted, such as success=false, a documented order processing error, or another non-retryable client/API error. Transport failures, timeouts, ambiguous retry exhaustion, statusless PolyApiException, malformed responses, and server-side failures keep the order submitted. The batch endpoint reports a rejected leg as success=true with an empty orderID and the reason in errorMsg (for example a naked sell the venue cannot accept): the adapter rejects that leg with the venue reason. A leg with no orderID and no reason stays submitted for reconciliation.

Once any single-order submit attempt has an ambiguous outcome, a later retry error cannot prove that the first attempt failed. The adapter therefore keeps the order submitted even if a later attempt returns a client error such as an already-existing order.

Failures before the adapter sends POST /order emit OrderDenied, not OrderRejected. This includes a failed pUSD balance lookup needed to adjust a market BUY for fees.

When a rejection reason reports a post-only order crossing the book, the OrderRejected event sets due_post_only=true so strategies can distinguish it from other venue rejections.

For unknown outcomes, the adapter derives the expected Polymarket order hash from the signed EIP-712 order when possible and caches it as the VenueOrderId. Later WebSocket order events (or reconciliation reports) then attach to the local ClientOrderId instead of becoming external orders.

Quote-quantity market BUY orders still apply the signed quote-to-base quantity update on the unknown path. Cancels requested while submit outcome is unknown are deferred until the expected venue order ID is known, and fill tracking is registered under that ID.

Position management

FeatureBinary OptionsNotes
Query positionsCurrent user positions from the Polymarket Data API.
Position mode-Binary outcome positions only.
Leverage control-No leverage available.
Margin mode-No margin trading.

Order querying

FeatureBinary OptionsNotes
Query open ordersActive orders only.
Query order historyLimited historical data.
Order status updatesReal‑time order state changes.
Trade historyExecution and fill reports.

Contingent orders

FeatureBinary OptionsNotes
Order lists-Independent order batches exist, but linked contingency semantics do not.
OCO orders-Not supported by Polymarket.
Bracket orders-Not supported by Polymarket.
Conditional orders-Not supported by Polymarket.

Precision limits

Polymarket enforces different precision constraints based on tick size and orderType.

Binary Option instruments typically support up to 6 decimal places for amounts (with 0.0001 tick size), but market orders (FAK and FOK) have stricter precision requirements:

  • Market order types (FAK and FOK):

    • The direct maker amount is limited to 2 decimal places.
    • The computed taker amount uses the market tick precision plus two size decimals.
    • A limit order submitted with FAK or FOK must also satisfy the stricter market-order amount validation. The venue rejects values that are valid for a resting order but not for that market-order type.
    • For a limit BUY, quantity is the nominal share quantity at the limit price. With FAK or FOK, Polymarket spends the resulting pUSD maker budget, so price improvement can return more shares; the adapter updates the order quantity to the actual fill.
    • The adapter denies the order before signing when quantity * price is not an exact cent amount. It does not round and recompute the nominal share quantity because that would change the signed price/amount ratio.
  • Resting limit order types (GTC and GTD): More flexible precision based on market tick size.

Tick size precision hierarchy

Tick SizePrice DecimalsSize DecimalsAmount Decimals
0.1123
0.01224
0.0025426
0.001325
0.0001426
  • The adapter validates tick size before signing. It also denies limit FAK or FOK BUYs whose maker amount has more than two decimal places. This applies to single and batch submissions.
  • Resting GTC and GTD limit orders and all SELL orders keep their tick-derived amount precision.
  • The adapter rejects limit prices outside the current market's tick_size to 1 - tick_size range before signing.
  • The published BinaryOption advertises min_price and max_price equal to tick_size and 1 - tick_size, so consumers that clamp to the instrument bounds stay within that accepted range.
  • Market-order precision limits include two decimals for the sell size plus tick-derived bounds for the computed amount.
  • Tick sizes can change dynamically during market conditions, particularly when markets become one-sided.

Tick size change handling

When a market's tick size changes (tick_size_change WebSocket event), old book levels can be invalid on the new grid (for example 0.505 fits a 0.001 tick but not a 0.01 tick). To keep old-grid prices out of the new epoch, the adapter treats the change as a book epoch transition:

  1. Publish the updated BinaryOption with the new price_increment, price_precision, and tick-relative min_price/max_price bounds.
  2. Drop the local order book for the instrument.
  3. Mark the instrument as awaiting a fresh snapshot.
  4. Drop incremental price_change book deltas until the snapshot arrives.
  5. Reseed the book from the snapshot and resume normal processing.

Trade ticks and the instrument update flow through unchanged. Quote handling follows drop_quotes_missing_side: when enabled, quote ticks require both bid and ask prices; when disabled, missing sides use Polymarket boundary prices with zero size. The adapter can keep quotes flowing during the gap by reading best_bid and best_ask from each price_change.

Trades

Trades on Polymarket can have the following statuses:

  • MATCHED: Trade has been matched and sent to the executor service. The executor submits it as a transaction to the Exchange contract.
  • MINED: Trade is observed to be mined into the chain, and no finality threshold is established.
  • CONFIRMED: Trade has achieved strong probabilistic finality and was successful.
  • RETRYING: Trade transaction has failed (revert or reorg) and is being retried/resubmitted by the operator.
  • FAILED: Trade has failed and is not being retried.

Once a trade is initially matched, subsequent status updates arrive through the user WebSocket. The execution adapter emits one OrderFilled at MATCHED. It treats MINED and RETRYING as settlement updates without emitting another fill. CONFIRMED records finality and refreshes the account. If the trade reaches FAILED, the adapter emits one OrderFillVoided for each locally applied fill and refreshes the account. The correction does not relist the failed quantity, but it preserves any maker-order remainder that was already working. An execution-complete order becomes VOIDED. Matched WebSocket fills retain the raw trade fields in the info field of the OrderFilled event.

Trade ID derivation

Polymarket does not publish a trade ID on last_trade_price market-data events. The adapter derives a deterministic TradeId from the asset ID, side, price, size, and timestamp via the Rust determine_trade_id function using FNV-1a. For execution fills, taker reports use the venue's trade id in both REST reconciliation and the user WebSocket, so the same fill deduplicates across sources. A maker trade can fill more than one of the user's resting orders, so maker reports combine the venue trade ID with the maker venue order ID. The same venue event yields the same trade ID across replays. For historical Data API trades, the loader uses {transactionHash[-24:]}-{asset[-4:]}-{seq:06d} to distinguish fills in one transaction.

Fees

The adapter reads each instrument's fee_schedule and applies its rate and exponent as:

platform fee = shares * rate * (price * (1 - price)) ^ exponent

The current public schedule uses exponent 1, which is Polymarket's published C * feeRate * p * (1 - p) formula. Platform fees peak at p = 0.50, decrease symmetrically toward the extremes, and apply only to taker fills.

CategoryTaker feeRateMaker feeRateMaker rebate
Crypto0.07020%
Sports0.05015%
Finance0.04025%
Politics0.04025%
Economics0.05025%
Culture0.05025%
Weather0.05025%
Other / General0.05025%
Mentions0.04025%
Tech0.04025%
Geopolitics00-

Every order signed by the adapter carries the hard‑coded Nautilus builder code. Its builder fee rate is fixed at zero and is not configurable.

FillReport.commission is denominated in pUSD and rounds the platform fee to five decimal places.

For the latest public schedule, see Polymarket's Fees documentation.

Backtest fee model

Use ProbabilityPriceFeeModel for the current exponent 1 schedule. It reads maker and taker rates from the binary option instrument and applies the same probability‑price curve:

from nautilus_trader.execution import ProbabilityPriceFeeModel

fee_model = ProbabilityPriceFeeModel()

Pass this object to BacktestVenueConfig.fee_model. It does not support other fee exponents or future maker‑rebate distributions, so state those assumptions explicitly in the backtest configuration.

Reconciliation

The Polymarket API returns either all active (open) orders or specific orders when queried by the Polymarket order ID (venue_order_id). The execution reconciliation procedure for Polymarket is as follows:

  • Generate order reports for all instruments with active (open) orders, as reported by Polymarket.
  • Generate position reports from current user positions reported by Polymarket's Data API.
  • Compare these reports with Nautilus execution state.
  • Generate missing orders to bring Nautilus execution state in line with positions reported by Polymarket.

Polymarket does not directly return orders that are no longer active. The V2 adapter recovers a cached individual order from trade history when its terminal WebSocket update is missed. Only CONFIRMED trades contribute to recovered fills; pending and failed settlement states do not.

Mass-status reconciliation pairs each order report with its venue fill reports. It applies the real fills first to preserve trade IDs and commissions, then infers only any residual quantity needed to reach the venue-reported status. REST order reports cap matched quantity to the greater of locally applied fills and authenticated CONFIRMED trade history, so pending settlement cannot create an inferred fill. Runtime order checks fetch confirmed trade history when the venue reports more matched quantity than the local order and WebSocket fill tracker contain. Unpaired fill reports retain the normal fill-only path.

Single-order recovery from trades

/data/order/{id} only returns active orders, so a Filled or Canceled order returns an empty response. To avoid the engine resolving a local ACCEPTED order as REJECTED (which discards fills that already happened at the venue), generate_order_status_report falls back to /data/trades filtered by the venue order ID. The cached order is resolved via client_order_id, falling back to the cache's venue_order_id index when only the venue ID is known. Recovery is keyed on the cached order; without one the recovery defers to the engine rather than synthesizing an external order from trade history alone:

  • Cached order + recovered fills covering the cached quantity (within DUST_SNAP_THRESHOLD for CLOB cent-tick truncation): returns Filled. The engine reconciles any delta over the cached filled_qty via inferred fill.
  • Cached order + recovered fills that fall short of the cached quantity by more than dust: returns Canceled with the recovered filled_qty. The engine's CANCELED branch transitions the order at the cached filled_qty, so any newly recovered fills that arrived only via REST (not WS) are not applied in this rare partial-cancel case. Closing the order is preferred over leaving it stuck open; if exact fill metadata matters in this scenario the venue trade history can be reviewed manually.
  • Cached order, no trades: returns Canceled with cancel_reason="ORDER_NOT_FOUND_AT_VENUE".
  • Cached order with any MATCHED, MINED, or RETRYING trade: a singular order query preserves the locally applied matched quantity while terminal REST recovery waits for CONFIRMED or FAILED.
  • No cached order (regardless of trades): returns None; the engine's not-found-at-venue path resolves the local entry.

The bulk open-order check cannot use this fallback for matched orders omitted by GET /orders. With the default open_check_open_only=true, the engine leaves those cached orders open for later reconciliation. With open_check_open_only=false, missing-order retries can mark an order rejected before its pending settlement confirms. A singular order query or the next startup reconciliation recovers the settled quantity from confirmed trade history.

Fill quantity normalization

Polymarket wire amounts use six-decimal fixed-point mantissas. Market SELL signing truncates the share-denominated makerAmount to two decimal places, while market BUY quote conversion can leave a few microshares of drift between the registered and filled quantities. Both effects are fixed in absolute share terms, so the adapter uses DUST_SNAP_THRESHOLD = 0.01 shares. Anything at or above that threshold remains a real partial fill or overfill.

DirectionSourceAdapter behavior
OverfillMarket BUY quote conversion (microshares)Snap fill down to submitted_qty
UnderfillSigned or venue quantity truncation (< 0.01)Normalize atomic FOK; cancel a FAK remainder

Terminal quantity normalization triggers from the MATCHED order update for resting maker orders, or directly on the confirming taker trade for atomic FOK orders. It emits a reconciliation OrderUpdated which lowers the order quantity to the cumulative venue fill. It does not emit a fill and does not change positions, balances, or commissions.

IOC maps to venue FAK. Once a taker trade confirms, every positive difference between original_size and size_matched is an unfilled remainder which the venue has killed. The adapter therefore emits OrderCanceled after the real fills instead of normalizing quantity or leaving the order partially filled. REST reports apply the same rule when a MATCHED FAK has size_matched < original_size. The same terminal handling runs after buffered fills drain when a confirmed trade arrives before the submit response. A buffered Canceled, Expired, or Rejected report takes precedence.

FillReport.commission always reflects the venue-reported size, not the snapped quantity. The few-ulp difference is sub-microcent in pUSD.

The fill tracker is keyed by venue_order_id and registered on order accept, so fill reports for orders placed in another session pass through unchanged. DUST_SNAP_THRESHOLD is not configurable per-strategy; it lives in nautilus_polymarket::common::consts.

Exec tester close residuals

close_positions_qty_precision is a general v2 ExecTesterConfig option. It defaults to None, which submits the full position quantity. The Rust and Python v2 Polymarket examples set it to 2 because market order maker amounts allow two decimals. Legacy v1 testers are unchanged. The examples also set close_positions_time_in_force=IOC; custom configurations must use IOC or FOK because Polymarket rejects GTC market orders.

On stop, the v2 tester truncates only the submitted market SELL quantity to the configured decimal precision and logs the exact difference at WARN level. It does not round the position state or create a synthetic fill.

A 5 pUSD BUY that fills 5.1975 shares therefore submits a 5.19‑share close. After the venue fills that order, the position remains open at exactly 0.0075 shares. If the whole position is below 0.01 shares, the tester warns and submits no zero‑quantity order. Treat close‑on‑stop as best‑effort and check the position and warning before assuming the account is flat. A non‑zero close must also meet the 1 pUSD marketable‑order minimum; rejection leaves the full position open. See the position reporting limitation for sub‑0.01‑share venue reports.

WebSockets

The PolymarketWebSocketClient is built on top of the high-performance Nautilus WebSocketClient base class, written in Rust.

Data

The data adapter opens market subscriptions dynamically as instruments are requested. It spreads those subscriptions across a pool of market WebSocket connections so that no single connection carries more than ws_max_subscriptions assets. The pool grows lazily (a universe below the cap stays on one connection) and closes a secondary connection once it owns no assets. Each connection replays only its own assets on reconnect.

A single price_change payload can contain interleaved updates for several assets. The adapter groups updates by instrument and publishes one atomic order book delta batch per instrument, while quote processing remains in the venue payload order.

RTDS custom data

The data client also supports Polymarket's real‑time data (RTDS) crypto and equity topics. Subscribe through generic custom data with a required, non‑empty symbol metadata value:

from nautilus_trader.adapters.polymarket import POLYMARKET_CLIENT_ID
from nautilus_trader.adapters.polymarket import PolymarketRtdsCryptoPrice
from nautilus_trader.adapters.polymarket import PolymarketRtdsEquityPrice
from nautilus_trader.model import DataType

crypto_type = DataType(
    PolymarketRtdsCryptoPrice.__name__,
    metadata={"symbol": "btcusdt"},
)
equity_type = DataType(
    PolymarketRtdsEquityPrice.__name__,
    metadata={"symbol": "AAPL"},
)

strategy.subscribe_data(crypto_type, client_id=POLYMARKET_CLIENT_ID)
strategy.subscribe_data(equity_type, client_id=POLYMARKET_CLIENT_ID)

Symbol matching is case‑insensitive, and published symbols are lowercase. Crypto RTDS uses the crypto_prices topic; equity RTDS uses equity_prices. Equity updates prefer full_accuracy_value when the venue supplies it and fall back to value for snapshots or updates that omit it.

Runtime instrument loading

Polymarket lists thousands of active markets and new markets appear throughout the day, so preloading the full universe at startup is rarely practical. The data adapter auto-loads missing instruments on demand so that strategies can subscribe to markets that are not in the cache:

  • When a strategy issues subscribe_quote_ticks, subscribe_trade_ticks, subscribe_order_book_deltas, or request_instrument for an instrument that is not cached, the adapter registers the request and waits auto_load_debounce_ms (default 100 ms) so that concurrent requests coalesce.
  • It then issues a single batched Gamma API call. Batches larger than the Gamma condition_ids query ceiling (about 100) are split across multiple calls and merged.
  • Once the instruments are loaded, they are published to the data engine (populating the cache) and the deferred subscriptions open their WebSocket subscriptions atomically. A strategy that unsubscribes while the auto-load is in flight does not see a spurious subscription opened.

The feature is enabled by default. Disable it by setting auto_load_missing_instruments=False on PolymarketDataClientConfig. To preload a known set of markets at startup instead, supply load_ids, event_slugs, market_slugs, or event_slug_builder on PolymarketInstrumentProviderConfig.

Newly-minted markets pass through a CLOB hydration window of several minutes during which Gamma reports active=true but GET /markets/{cid} returns either a 404 or a 200 with empty token_id strings. The adapter classifies these as transient and retries auto-load with bounded exponential backoff plus jitter. Tune the cadence with auto_load_max_retries (default 12), auto_load_retry_delay_initial_secs (default 5.0), and auto_load_retry_delay_max_secs (default 15.0); the defaults cap the retry window near 3 minutes. Set auto_load_max_retries=0 to disable retry. 5-minute markets (e.g. updown crypto) can expire before the venue finishes hydrating, so budget for that or raise the cap. After the retry budget is exhausted, a condition still missing on Gamma is logged as a terminal miss and the caller must resubscribe after the market becomes available.

Market resolution events

The Rust data client tracks Polymarket exposure at condition_id level so both YES and NO legs close together when the venue resolves the market. Position events add open Polymarket binary option instruments to an internal watchlist. Once a watched condition expires, the data client waits resolve_poll_grace_secs, then polls Gamma every resolve_poll_interval_secs until the condition resolves or resolve_poll_max_wait_secs elapses.

Resolution uses strict winner inference:

  • Gamma must return a closed binary market with exactly two token IDs, two outcomes, and a binary outcomePrices shape.
  • If Gamma does not provide a strict result for the condition, the client falls back to CLOB GET /markets/{condition_id} and uses tokens[].winner.
  • Non-binary, ambiguous, malformed, or still-unresolved payloads are skipped. They remain on the watchlist until the poll window times out or a manual request resolves them.

When the client applies a resolution, it emits one InstrumentStatus close and one InstrumentClose per tracked leg. The winner leg closes at 1, and the losing leg closes at 0. The close type is InstrumentCloseType.ContractExpired. This event closes Nautilus exposure and does not redeem tokens or claim funds on-chain.

The same apply path handles WebSocket market_resolved events, automatic polling, and manual requests. After resolve_poll_max_wait_secs, automatic polling pauses the watched condition and logs it for manual recovery. Manual requests can still retry the condition later.

Manual resolution requests

Use request_data() with data type PolymarketResolveRequest to force a resolution check. The request accepts any of these params:

ParamTypeDescription
condition_idstrResolve one Polymarket condition.
condition_idsstr or list[str]Resolve one or more Polymarket conditions.
instrument_idsstr or list[str]Resolve Polymarket instrument IDs; other venues are ignored.

If a request omits all selectors, the client uses the watchlist. With automatic polling enabled, the fallback selects paused or timed-out entries. With automatic polling disabled, it selects all expired eligible entries, so operators can run the recovery flow manually.

The response payload is custom data with this dictionary shape:

KeyMeaning
requested_condition_idsDeduplicated condition IDs checked by the request.
fetched_marketsGamma markets returned across the batched lookup.
resolved_marketsConditions with a strict Gamma result or successful CLOB fallback result.
skipped_non_binary_marketsGamma markets skipped for non‑binary or ambiguous resolution shape.
clob_fallback_successesConditions resolved through the CLOB fallback path.
emitted_condition_idsConditions that emitted at least one InstrumentClose.
failed_condition_idsConditions where both Gamma and CLOB lookup failed.
used_watchlist_fallbackWhether the request selected conditions from the watchlist.
timed_out_watchlistTimed‑out watchlist entries seen during fallback selection.
errorFirst summary error, if one occurred.

Redemption is a separate account or execution workflow. Do not extend the data client resolution path to claim funds; it only publishes market-outcome close events into Nautilus.

Purging instruments at runtime

Polymarket auto-loads instruments on demand, so a long-running session keeps growing the cache as markets resolve, new markets appear, and strategies cycle through events. Use cache.purge_instrument to drop markets the strategy no longer tracks. The call removes the instrument record and every cache-owned map keyed by it (order book, quotes, trades, bars).

class PolymarketHousekeeping(Strategy):
    def on_position_closed(self, event: PositionClosed) -> None:
        # Drop the market once the position is closed and you have no further interest.
        instrument_id = event.instrument_id
        self.unsubscribe_quote_ticks(instrument_id)
        self.unsubscribe_order_book_deltas(instrument_id)
        self.cache.purge_instrument(instrument_id)

Common triggers on Polymarket:

  • A market resolves and produces no further trades.
  • An event ends and the strategy rotates off its markets.
  • The strategy rotates a fixed-size watchlist and drops the oldest entry.

The purge skips any instrument that still has non-terminal orders (initialized, submitted, accepted, emulated, released, or inflight) or non-closed positions, so it is safe to call without coordinating with the execution client. Active WebSocket subscriptions belong to the data engine. Unsubscribe before purging if you no longer want updates.

The cache also exposes purge_order, purge_position, purge_closed_orders, purge_closed_positions, and purge_account_events for trimming closed execution state. For long-running Polymarket nodes, schedule the bulk purges from LiveExecEngineConfig (15 min interval, 60 min buffer is a sensible default). See Cache: purging cached data for the full set.

The caller decides when an instrument is no longer needed. Purging an instrument that another actor, strategy, or engine still relies on causes missing instrument lookups and loses market-data history.

Execution

The execution adapter keeps a user channel connection for order and trade events and manages market subscriptions as needed for instruments seen during trading.

The adapter supports dynamic WebSocket subscribe and unsubscribe operations. Matched WebSocket fills and their corrections are restored from cached order history and deduplicated across reconnects. If a trade arrives before its instrument is available, the adapter leaves it out of the dedup state. A redelivered event or later REST reconciliation can apply it after instrument loading completes. For a fully matched order, terminal quantity normalization waits for every trade ID in the order's associate_trades list to confirm before lowering the order quantity to its actual fills. If a confirmed trade is recovered through REST after a WebSocket gap, reconciliation applies the same order-only normalization. If a MATCHED WebSocket update omits associate_trades, the adapter does not infer that settlement is final; the next REST reconciliation recovers the residual after the trade reaches CONFIRMED.

Subscription limits

Polymarket does not publish a WebSocket subscription cap in its current rate-limit documentation. ws_max_subscriptions (default 200) is therefore a conservative, self-chosen per-connection reliability bound rather than a venue-enforced limit: high per-connection subscription counts have been observed to silently stall a connection. The adapter enforces the bound by sharding asset subscriptions across a pool of market connections, opening a new connection only when the existing ones are full and closing a secondary connection once it owns no assets.

Rate limiting

Polymarket applies Cloudflare IP limits to its APIs and separate per-signer token buckets to CLOB order and cancellation requests. The V2 adapter enforces the signer limits in process. All clients for one signer use the same limiter, which has independent order and cancellation buckets.

Per-signer CLOB trading limits

The adapter starts each signer at the Standard tier. Polymarket determines tier eligibility from the maker wallet's cumulative 30-day trading volume, even when the maker differs from the signer, and refreshes assignments every three hours. The adapter does not calculate eligibility: a recognized Poly-RateLimit-Tier response header selects one of these encoded profiles and updates both buckets, while an unknown tier is logged and ignored.

Tier30-day maker volumeOrder rate (tokens/s)Order burstCancel rate (tokens/s)Cancel burstNegative cancel balance
Standard-406080120Yes
Copper$30,000+6090120180Yes
Bronze$50,000+80120160240Yes
Silver$100,000+200300400600Yes
Gold$500,000+4006008001,200Yes
Platinum$2.5M+4506759001,350No
Diamond$5M+5257871,0501,575No
Elite$10M+6009001,2001,800No

Covered requests consume:

BucketRequestToken cost
OrderPOST /order1
OrderPOST /ordersNumber of orders
CancellationDELETE /order1
CancellationDELETE /ordersNumber of submitted order IDs
CancellationDELETE /cancel-all1 plus successful cancellations
CancellationDELETE /cancel-market-orders1 plus successful matching cancellations

A request waits for its full token cost and is rejected locally only when that cost exceeds the current tier's burst. Before each new DELETE /orders chunk, the adapter recomputes its cap from the smaller of the endpoint's 1,000‑ID limit and that burst. Cancel‑all and cancel‑market requests debit one token before the request, then debit each successful cancellation after the response. Standard through Gold tiers can enter cancellation debt; Platinum through Elite tiers floor the balance at zero.

Poly-RateLimit-Remaining can lower the local balance, and Poly-RateLimit-Reset extends a rejected or indebted bucket's wait. The adapter logs Poly-RateLimit-Warning responses with the endpoint, token cost, tier, remaining balance, and reset time.

A 429 Too Many Requests response with Retry-After blocks the applicable bucket for at least that delay and can then be retried; without Retry-After, the adapter does not retry it automatically. A standalone 429 is a definitive venue rejection. Transport failures, timeouts, and any submit with an earlier ambiguous attempt remain ambiguous outcomes.

Selected IP-based REST limits

Polymarket changes these quotas over time. As of 2026-07-10, the official limits are:

EndpointBurst (10s)Sustained (10 min)Notes
General rate limiting15,000-Global documented rate limit.
Health check (/ok)100-Health endpoint.
CLOB general9,000-Aggregate across CLOB endpoints.
CLOB POST /order5,000120,000Single‑order submit.
CLOB POST /orders2,00021,000Batch submit (up to 15 orders per request).
CLOB DELETE /order5,000120,000Single‑order cancel.
CLOB DELETE /orders2,00015,000Batch cancel.
CLOB DELETE /cancel-all2506,000Cancel all orders.
CLOB DELETE /cancel-market-orders1,50021,000Cancel orders for one market.
CLOB GET /balance-allowance200-Balance and allowance queries.
CLOB API key endpoints100-Key management.
Gamma general4,000-Aggregate across Gamma endpoints.
Gamma /markets300-Market metadata.
Gamma /events500-Event metadata.
Data general1,000-Aggregate across Data API endpoints.
Data /trades200-Trade history.
Data /positions150-Current positions.

WebSocket limits

The WebSocket quotas are not part of the published REST rate-limits table. The V2 adapter enforces ws_max_subscriptions (default 200) by sharding subscriptions across a pool of market connections.

Exceeding the IP-based limits triggers Cloudflare throttling. Requests are queued using sliding windows rather than rejected immediately, but sustained overshoot can result in HTTP 429 responses or temporary blocking.

For the latest limits, see the official Polymarket CLOB trading rate limits and general rate limits.

Limitations and considerations

The following limitations are currently known:

  • Reduce-only orders are not supported.
  • Batch submit (POST /orders) accepts at most 15 orders per request; the adapter splits larger SubmitOrderList commands into sequential 15-order chunks.
  • Batch cancel (DELETE /orders) accepts at most 1,000 order IDs per request; the adapter also limits each new chunk to the signer's current cancellation burst and recomputes that limit before the chunk.
  • Position reports omit balances below 0.01 shares. Do not treat an omitted report as proof that a dust position is flat; a sub-minimum residual cannot be exited through the CLOB's five-share minimum order size. Position reconciliation therefore tolerates differences through 0.009999 shares and reconciles differences of 0.01 shares or more.

V2 client configuration

Rust structs and PyO3 classes expose the same V2 client configuration. The only Rust-only fields are the programmatic filters and new_market_filter trait objects on PolymarketDataClientConfig.

Data client options

Class/struct: PolymarketDataClientConfig.

OptionDefaultDescription
instrument_configNoneBootstrap scope, passed as PolymarketInstrumentProviderConfig.
base_url_http, base_url_wsNoneOverride the CLOB HTTP or WebSocket endpoint.
base_url_gamma, base_url_data_apiNoneOverride the Gamma or Data API endpoint.
base_url_rtdsNoneOverride the RTDS endpoint.
proxy_urlNoneHTTP or HTTPS proxy for every data transport.
http_timeout_secs, ws_timeout_secs60, 30HTTP and WebSocket timeout in seconds.
ws_max_subscriptions200Per‑connection subscription cap; the market pool shards across connections at this bound.
update_instruments_interval_mins60Instrument catalogue refresh interval; pass None to disable it.
subscribe_new_marketsfalseSubscribe to new‑market discovery events.
drop_quotes_missing_sidetrueDrop quotes that do not contain both a bid and an ask.
new_market_fetch_max_concurrency8Bound concurrent market fetches from discovery events.
auto_load_missing_instrumentstrueLoad unknown instruments for supported requests and subscriptions.
auto_load_debounce_ms100Coalesce concurrent auto‑load requests.
auto_load_max_retries12Retry transient CLOB hydration misses; 0 disables retry.
auto_load_retry_delay_initial_secs5.0Initial auto‑load retry delay.
auto_load_retry_delay_max_secs15.0Maximum auto‑load retry delay.
resolve_poll_enabledtruePoll expired watched conditions for resolution.
resolve_poll_interval_secs30Resolution polling interval.
resolve_poll_grace_secs10Delay after expiry before polling begins.
resolve_poll_max_wait_secs1800Pause automatic polling after this wait.
transport_backendSockudoWebSocket transport implementation.

Execution client options

Class/struct: PolymarketExecClientConfig.

OptionDefaultDescription
trader_iddefault TraderIdTrader identifier registered by the client.
account_idPOLYMARKET-001Account identifier for this execution client.
private_keyPOLYMARKET_PKEIP-712 signing key.
api_key, api_secret, passphraseenvironment variablesCLOB L2 authentication credentials.
funderPOLYMARKET_FUNDERFunding wallet; proxy and deposit‑wallet signatures require it to differ from the signing address.
signature_typeEoaEoa, PolyProxy, PolyGnosisSafe, or Poly1271.
base_url_http, base_url_ws, base_url_data_apiNoneOverride the respective production endpoint.
proxy_urlNoneHTTP or HTTPS proxy for every execution transport.
http_timeout_secs60HTTP timeout in seconds.
max_retries3Retries for single‑order submit/cancel requests and for each batch‑cancel chunk.
retry_delay_initial_ms1000Initial retry delay.
retry_delay_max_ms10000Maximum retry delay.
heartbeat_enabledfalseSend an authenticated order‑safety heartbeat immediately after execution readiness and every five seconds thereafter.
transport_backendSockudoWebSocket transport implementation.

Enabling heartbeat_enabled opts the account into Polymarket's order-safety heartbeat contract. The adapter sends the first empty heartbeat ID, chains each returned ID, and uses a replacement ID from an HTTP 400 response to resynchronize. Polymarket cancels open orders when it does not receive a heartbeat within 10 seconds, with an additional 5-second buffer. Authentication or venue rejection, or two consecutive retryable request failures, makes the execution client report as disconnected until it is explicitly disconnected and reconnected.

Proxy routing

Set proxy_url to apply one HTTP or HTTPS proxy to every transport owned by that client. The data client routes CLOB HTTP, Gamma HTTP, Data API HTTP, the market WebSocket pool, and RTDS through the proxy. The execution client routes authenticated CLOB HTTP, Data API HTTP, and the authenticated user WebSocket through it. Configure the same value on both clients when running data and execution together.

SOCKS URLs and malformed URLs fail configuration validation. When proxy_url is None, the adapter does not configure an explicit proxy: HTTP retains reqwest's environment-proxy behavior and WebSockets connect directly. Treat credential-bearing proxy URLs as secrets because serialized configs contain the supplied URL. Python exposes only has_proxy_url; configuration Debug output and transport diagnostics redact proxy credentials.

Batch submissions never retry because Polymarket does not expose an idempotency key. Proxy signature clients fail during construction unless funder is present and differs from the signing address.

Instrument provider options

Pass PolymarketInstrumentProviderConfig as instrument_config on the data client config.

OptionDefaultDescription
load_allfalseLoad the full venue catalogue at startup.
load_idsNoneLoad exact Nautilus instrument IDs.
filtersNoneValidated Gamma market keyset filters.
event_slugsNoneResolve all markets for the listed events at bootstrap.
market_slugsNoneLoad the listed Gamma market slugs at bootstrap.
event_slug_builderNoneRust‑backed Up/Down event‑slug generator.
log_warningstrueEmit provider warnings.
use_gamma_marketsfalseCompatibility field with no additional V2 behavior.

Gamma query filters

The Rust v2 adapter uses the Gamma market and event keyset endpoints. It validates filters before the first HTTP request, follows next_cursor, and applies the endpoint page ceilings of 100 markets and 500 events.

Market keyset fields:

ClassFields
Scalarlimit, order, ascending, closed, decimalized, liquidity_num_min, liquidity_num_max, volume_num_min, volume_num_max, start_date_min, start_date_max, end_date_min, end_date_max, related_tags, tag_match, cyom, rfq_enabled, uma_resolution_status, game_id, include_tag, locale
Repeatedid, slug, clob_token_ids, condition_ids, question_ids, market_maker_address, tag_id, sports_market_types
Compatibilityactive, archived
Aliasis_active
Client onlyoffset, max_markets

The provider filters dictionary accepts only market fields. Rust callers configure event discovery with EventParamsFilter and GetGammaEventsParams; event-only fields such as live or tag_slug are not valid provider dictionary keys.

Event keyset fields:

ClassFields
Scalarlimit, order, ascending, closed, live, featured, cyom, title_search, liquidity_min, liquidity_max, volume_min, volume_max, start_date_min, start_date_max, end_date_min, end_date_max, start_time_min, start_time_max, tag_slug, related_tags, tag_match, event_date, event_week, featured_order, recurrence, parent_event_id, include_children, partner_slug, include_chat, include_template, include_best_lines, locale
Repeatedid, slug, tag_id, exclude_tag_id, series_id, game_id, created_by
Compatibilityactive, archived
Client onlyoffset, max_events

Repeated fields are sent as repeated query keys. offset is applied across returned keyset pages and is never sent to Gamma. max_markets caps markets locally, with each binary market normally producing two instruments. max_events caps events locally; each event can contain many markets. condition_ids accepts at most 100 values, and event tag_id values cannot overlap exclude_tag_id values.

The provider filters dictionary accepts strings in the native Rust config and also accepts Python bool, int, finite float, string, or lists of those scalar values when converting a legacy Python-shaped config. The legacy-shaped conversion ignores None entries; native config entries must be strings. is_active=true supplies active=true, archived=false, and closed=false; explicit values override those defaults. Unknown keys, malformed values, empty lists, invalid date or numeric bounds, and invalid combinations raise ValueError during Python config conversion.

See the official market keyset and event keyset references for the venue contract.

Event slug builder

The Rust Python v2 adapter treats Python as a configuration, factory, and user strategy boundary. Provider, data, and execution operations run in Rust. event_slug_builder therefore accepts a Rust-backed PolymarketUpDownEventSlugConfig; it does not accept Python callable paths.

Use this for predictable Polymarket Up/Down event slugs without downloading the full venue catalogue. The builder emits slugs with the pattern {asset}-updown-{interval_mins}m-{unix_timestamp} for the configured window of aligned periods.

from nautilus_trader.adapters.polymarket import PolymarketInstrumentProviderConfig
from nautilus_trader.adapters.polymarket import PolymarketUpDownEventSlugConfig

instrument_config = PolymarketInstrumentProviderConfig(
    event_slug_builder=PolymarketUpDownEventSlugConfig(
        assets=["btc"],
        interval_mins=5,
        periods=3,
        start_offset_periods=0,
    ),
)

For custom event patterns, pass explicit event_slugs, pass direct market_slugs, or add a Rust filter or builder. The Rust v2 adapter rejects Python callable event_slug_builder values so adapter operations do not cross into Python during live trading.

Python v2 discovery and historical data

The Python v2 package exports a Rust-backed PolymarketDataLoader for public discovery, instrument construction, and historical trades. It uses the Rust Gamma, CLOB, and Data API clients, so it does not require trading credentials or run networking in Python.

All network methods are asynchronous. Build a loader from a market slug and select its outcome token by index:

from nautilus_trader.adapters.polymarket import PolymarketDataLoader

loader = await PolymarketDataLoader.from_market_slug(
    "gta-vi-released-before-june-2026",
    token_index=0,
)

instrument = loader.instrument
token_id = loader.token_id
condition_id = loader.condition_id

instrument is a normalized BinaryOption. Resolution-bearing fields never enter instrument.info. Read them separately after a backtest or simulation:

metadata = loader.resolution_metadata
winner = next(
    (token["outcome"] for token in metadata["tokens"] if token["winner"]),
    None,
)

An event factory returns one loader for each market in the event:

loaders = await PolymarketDataLoader.from_event_slug(
    "highest-temperature-in-nyc-on-january-26",
    token_index=1,
)

A negative token index or an index outside a market's token list raises ValueError. Construction also fails clearly when Gamma has no matching slug or CLOB has not populated usable token IDs.

Public discovery

Static query methods return stable Python mappings and lists while Rust owns validation and pagination:

market = await PolymarketDataLoader.query_market_by_slug("some-market")
details = await PolymarketDataLoader.query_market_details(market["conditionId"])
event = await PolymarketDataLoader.query_event_by_slug("some-event")

markets = await PolymarketDataLoader.query_markets(
    filters={
        "is_active": True,
        "tag_id": [21, 42],
        "order": "volume",
        "max_markets": 200,
    },
)
events = await PolymarketDataLoader.query_events(
    filters={
        "active": True,
        "closed": False,
        "max_events": 100,
    },
)
tags = await PolymarketDataLoader.query_tags()
results = await PolymarketDataLoader.query_search(
    "bitcoin",
    events_status="active",
    limit_per_type=20,
)

Market and event filter dictionaries use the fields listed under Gamma query filters. The provider config accepts only the market fields, while query_events accepts the event fields. Unknown or malformed filters raise ValueError before any request.

Historical trades

load_trades returns normalized TradeTick objects in chronological order:

from datetime import UTC, datetime, timedelta

end = datetime.now(UTC)
start = end - timedelta(days=1)

trades = await loader.load_trades(
    start=start,
    end=end,
    limit=1_000,
)

The window is inclusive. The Data API records trade timestamps in whole seconds, so Rust keeps all trades in the start and end boundary seconds. With start, limit keeps the earliest matching trades in the window. Without start, it keeps the most recent matching trades. The public API caps offset-based pagination at 10,000; if that ceiling is reached, an unanchored request returns the available partial result and logs a warning. A start-anchored request raises an error at the ceiling because Rust cannot guarantee complete results from the requested start; narrow the time window and retry.

The legacy v1 loader also exposes lower-level raw fetch and parse methods, Python HTTP injection, and convenience scripts. Those v1-only APIs remain under the top-level legacy package and are not part of the Python v2 facade.

Developer test matrix

Generic subscription commands

Test IDCommandDispositionMatrix
TC-D02Singular instrumentSupportedRun
TC-D12OrderBookDepth10UnsupportedSkip
TC-D60Instrument statusUnsupportedSkip
TC-D61Instrument closeUnsupportedSkip
  • TC-D02 receives live definition publications from the shared instrument sources. It does not replay a cached definition. Unsubscribe removes the per-instrument data-engine handler without stopping bootstrap, refresh, new-market, or tick-size-change publishers.
  • TC-D12 has no separate Polymarket feed. Use managed L2_MBP deltas; the adapter does not synthesize a second book stream from its local book.
  • TC-D60 cannot own delivery: new-market status belongs to configured discovery, while resolution status belongs to open-position tracking. A generic command cannot start or stop either source.
  • TC-D61 cannot own delivery: resolution close events belong to open-position tracking and must remain active until exposure closes. A generic unsubscribe cannot stop that source.

The unsupported commands return an explicit error when called directly. This does not remove the resolution behavior described in Market resolution events: the data client still emits InstrumentStatus and InstrumentClose for position-tracked legs.

For DataTesterConfig and live capability matrices:

  • Enable subscribe_instrument for TC-D02 and set update_instruments_interval_mins=1 so the matrix observes a real Gamma refresh rather than a cached replay.
  • Record TC-D12 as skipped. Exercise the supported book contract with subscribe_book_deltas=true and manage_book=true; set book_levels_to_print=10 when only the top ten levels need display.
  • Record TC-D60 and TC-D61 as skipped. Leave subscribe_instrument_status and subscribe_instrument_close disabled because their resolution events require position-owned lifecycle state rather than generic subscription ownership.

Contributing

For additional features or to contribute to the Polymarket adapter, please see our contributing guide.

On this page