NautilusTrader
Integrations

Betfair

Founded in 2000, Betfair operates the world's largest online betting exchange. This integration supports instrument discovery, live market data, account state, order management, and execution updates through the Betfair Betting, Accounts, and Exchange Streaming APIs.

The adapter is implemented in Rust and exposed to Python at nautilus_trader.adapters.betfair, so data and execution have the same behavior from either language.

Overview

The adapter includes several components, which can be used separately or together:

  • BetfairHttpClient: Low‑level Betting and Accounts API connectivity.
  • BetfairStreamClient: Low‑level Exchange Streaming API connectivity for the market and order streams.
  • BetfairRaceStreamClient: Low‑level connectivity for the race and cricket data streams.
  • BetfairInstrumentProvider: Loads Betfair markets and converts them into Nautilus instruments.
  • BetfairDataClient: Market data feed manager.
  • BetfairExecutionClient: Account management and bet execution gateway.
  • BetfairDataClientFactory: Factory for Betfair data clients.
  • BetfairExecutionClientFactory: Factory for Betfair execution clients.

Most users will define a configuration for a live trading node, and won't need to work directly with these lower-level components. The Python examples show a complete LiveNode.builder(...) configuration for data and execution clients.

Installation

Install NautilusTrader using the installation guide. The Betfair adapter is included in the Python package; no adapter‑specific extra is required.

Examples

Betfair documentation

Credentials

Betfair requires an application key to authenticate API requests. After registering and funding your account, obtain your key with the API-NG Developer AppKeys Tool. Betfair assigns two keys per account: a Live key, which requires a one‑time activation fee, and a Delayed key for development and testing.

Supply the account credentials through configuration or environment variables:

export BETFAIR_USERNAME=<your_username>
export BETFAIR_PASSWORD=<your_password>
export BETFAIR_APP_KEY=<your_app_key>

The adapter uses Betfair's interactive login endpoint. It does not use client certificates.

Timestamp policy

The adapter keeps venue event time separate from local initialization time:

  • ts_event records when Betfair says the event occurred.
  • ts_init records when the live adapter received the containing stream message.

Each live stream callback reads the real‑time atomic clock once, before decoding the message. Every output decoded from that message shares the same ts_init.

Inputts_event sourcets_init source
Market change (mcm)Message publish time (pt).Local receipt time.
Race change (rcm)Runner or race feed time (ft), falling back to the message publish time (pt) when ft is absent.Local receipt time.
Cricket change (ccm)Message publish time (pt).Local receipt time.
Order change (ocm)The relevant order lifecycle time. Acceptance uses pd; fills use md, falling back to pt; status and cancel events use the latest of md, cd, or ld, falling back to pt. OCM‑level custom data uses pt.Local receipt time.
Historical data loaderThe same feed‑time rules as live data.Message publish time (pt), because recorded data has no local receipt time.

When an OCM arrives during post‑reconnect reconciliation, the adapter buffers the message together with its captured ts_init. Draining the buffer preserves the original receipt time instead of using the later replay time.

Orders capability

Betfair is a betting exchange, so several concepts from traditional financial venues do not apply.

Order types

Order TypeSupportedNotes
MARKET✓*Supports AT_THE_CLOSE, which maps to Betfair MARKET_ON_CLOSE.
LIMITSupports regular limit orders and BSP on‑close limit orders.
STOP_MARKET-Not supported.
STOP_LIMIT-Not supported.
MARKET_IF_TOUCHED-Not supported.
LIMIT_IF_TOUCHED-Not supported.
TRAILING_STOP_MARKET-Not supported.

Submitting a MARKET order with any time in force other than AT_THE_CLOSE is rejected, because Betfair has no immediate market order.

BSP on‑close instructions carry a liability, not a stake. For MARKET_ON_CLOSE and LIMIT_ON_CLOSE orders, the adapter sends the order quantity as the Betfair liability. Size a BSP order by the amount you are prepared to lose, not by the stake you want matched.

Time in force

Time in forceSupportedNotes
GTCMaps to Betfair PERSIST.
DAYMaps to Betfair LAPSE.
FOKMaps to Betfair FILL_OR_KILL.
IOCMaps to FILL_OR_KILL with min_fill_size=0.
AT_THE_CLOSEUsed for Betfair BSP LIMIT_ON_CLOSE and MARKET_ON_CLOSE.
GTD-Not supported; the expiry is ignored and maps to LAPSE.

A LIMIT order in AT_THE_OPEN mode also routes to LIMIT_ON_CLOSE, because Betfair has no at‑the‑open instruction.

Execution instructions

InstructionSupportedNotes
post_only-Not applicable to a betting exchange.
reduce_only-Not applicable to a betting exchange.

Advanced order features

FeatureSupportedNotes
Order ModificationPrice and size change separately.
Bracket/OCO Orders-Not supported.
Iceberg Orders-Not supported.

Batch operations

OperationSupportedNotes
Batch SubmitImplemented through SubmitOrderList.
Batch Modify-Not supported.
Batch CancelImplemented through BatchCancelOrders.

Position management

FeatureSupportedNotes
Query positions-Exposure is tracked per bet, not per position.
Position mode-Not applicable to a betting exchange.
Leverage control-No leverage on a betting exchange.
Margin mode-No margin on a betting exchange.

Set position_check_interval_secs=None on LiveExecEngineConfig, because Betfair reports no venue-side positions to check against.

Order querying

FeatureSupportedNotes
Query open ordersBuilt from listCurrentOrders.
Order status updatesReal‑time bet state changes from the order stream.
Fill reportsMatched sizes and prices from listCurrentOrders.
Cleared order history-The adapter does not request settlement history.

Execution control flow

Startup:

  1. Connect the HTTP client and fetch initial account funds.
  2. Seed OCM state from cached orders.
  3. Connect the Betfair execution stream and subscribe to order updates.
  4. Generate startup mass status from listCurrentOrders.
  5. Reconcile order and fill reports into the execution engine.

On every stream reconnect, the adapter repeats the order‑and‑fill mass‑status fetch over a recent window. It halts new‑order submissions after transport loss or a server connectionClosed status until the latest recovery generation dispatches its mass status.

For the full transition sequence, see post‑reconnect reconciliation.

Reconciliation behavior:

  • stream_market_ids_filter filters live OCM updates.
  • Reconciliation uses reconcile_market_ids only when reconcile_market_ids_only=True and reconcile_market_ids is set.
  • In every other case, including reconcile_market_ids_only=True with no reconcile_market_ids, the adapter falls back to stream_market_ids_filter for reconciliation scope.
  • ignore_external_orders=True skips OCM updates with no rfo.

Session management and reconnection

Betfair expires session tokens, so the adapter renews them rather than waiting for a failure. It handles renewal and recovery through four mechanisms:

MechanismTriggerAction
Periodic keep‑aliveEvery 10 hours (36,000 seconds).Renew the session token and update retained stream authentication without reconnecting.
Keep‑alive fallbackKeep‑alive returns LoginFailed.Re‑login, update all active stream authentication, then request replacement stream transports.
Stream reconnectConnection message after initial connect.Try keep‑alive. LoginFailed triggers full re‑login; other failures retain the existing session.
HTTP report recoveryA report query returns a session error.Try keep‑alive and retry once; any keep‑alive failure falls back to full re‑login before that retry.

The periodic keep‑alive tasks and data stream reconnect handler log and skip transient keep‑alive errors such as network timeouts and 5xx responses. The execution reconnect handler also preserves the existing session token, but continues report reconciliation. At the periodic or handler‑level keep‑alive step, only LoginFailed triggers full re‑login. HTTP report recovery differs: after a session error, any keep‑alive failure falls back to full re‑login before the report‑level retry.

Both the data and execution clients use the same session‑renewal policy. Each spawns:

  • A keep‑alive task that periodically attempts renewal. An ordinary successful keep‑alive updates retained authentication without replacing the transport.
  • A reconnect handler that listens for Connection messages after a stream reconnect and attempts to refresh the session.

After a full re‑login, the adapter updates authentication for every affected active stream before it requests any reconnect. Each replacement connection sends the latest authentication before retained subscriptions or traffic buffered during the reconnect. Market and order streams also retain their subscription IDs and clk/initialClk resume values.

The data client applies the same update to active market, race, and cricket streams. A periodic keep‑alive fallback requests replacement transports immediately after updating authentication. An HTTP report recovery requests an execution stream replacement after the query finishes. When full re‑login occurs inside the execution stream reconnect handler, that handler first fetches and dispatches mass status, then requests a replacement execution stream. The replacement stream's Connection message starts another handler iteration; a successful keep‑alive updates retained authentication without requesting another replacement. This ordering prevents a reconnect loop.

Post-reconnect reconciliation

After the initial handshake, a Betfair execution transport loss immediately halts new‑order submissions. This applies to automatic network reconnects and replacements requested after a full re‑login. The adapter assumes the cache may have diverged while the previous transport was unavailable. In particular, fills can complete and roll off the unmatched book before the post‑reconnect stream image arrives. The adapter therefore fetches and dispatches a mass status over a recent window before allowing new submissions.

StepTriggerAction
1Transport loss or a server connectionClosed status.Advances the reconciliation generation and halts new submissions immediately.
2Replacement Connection message.Advances the generation again, raises pending_resync, and queues that generation.
3Reconnect task receives the generation.Attempts a session refresh, publishes authentication after a successful refresh, requests getAccountFunds, then queries orders and fills in sequence.
4Both listCurrentOrders queries succeed.Builds and dispatches ExecutionReport::MassStatus through the execution event channel.
5Recovery task finishes.Requests a replacement after full re‑login. Clears the halt if mass status was dispatched and the generation is current.

The account‑state refresh is best effort: a request or parse failure is logged but does not prevent mass‑status dispatch or reopening the gate. A keep‑alive failure other than LoginFailed continues with the retained session because the report queries retain their own retry and session‑recovery logic. A failed full re‑login or either report query leaves the gate halted until a later reconnect succeeds or the client disconnects. This fail‑closed behavior also covers the interval where the replacement socket is active but Betfair has not sent its Connection message.

Mass‑status dispatch is the completion boundary for the handled generation. The gate does not wait for a separate acknowledgement that the execution engine has applied the report to its cache.

While the execution stream is unavailable or reconciliation is in progress:

  • submit_order and submit_order_list emit OrderDenied with reason STREAM_RECONCILING: execution stream unavailable or recovering, retry after recovery.
  • cancel_order, batch_cancel_orders, and modify_order pass through unchanged.
  • pending_resync buffers OCMs received after the replacement Connection message. Connectivity polling and command or report entry points invoke process_pending_resync on the engine thread, which synchronizes OCM state from the cache and drains the buffer.

If the client disconnects while a reconciliation is still in flight, clear_resync_state clears the active halt so a subsequent connect/submit cycle starts clean.

The lookback window for the mass-status fetch is stream_gap_recovery_lookback_mins (default 10). It should comfortably exceed the longest expected reconnect duration so a fill that completed mid-gap is still captured.

Tick scheme and pricing

Betfair uses a tiered tick scheme with varying increments across price ranges:

Price rangeTick size
1.01 - 2.000.01
2.00 - 3.000.02
3.00 - 4.000.05
4.00 - 6.000.10
6.00 - 10.000.20
10.00 - 20.000.50
20.00 - 30.001.00
30.00 - 50.002.00
50.00 - 100.005.00
100.00 - 1000.0010.00

Minimum price is 1.01, maximum is 1000.00.

Order modification

  • Price and size cannot change atomically; these require separate operations.
  • Price modification uses ReplaceOrders (cancel + new order at new price).
  • Size reduction uses CancelOrders with a size_reduction parameter.
  • Size increase is not supported; submit a new order instead.

A replace operation generates both a cancel event for the original order and an accepted event for the replacement. The adapter tracks pending replacements to suppress synthetic cancel events.

Order stream fill handling

The execution client processes order updates from the Betfair Exchange Streaming API. Two configuration options control how updates are filtered:

  • stream_market_ids_filter: filters at the market level (early exit, silent skip).
  • ignore_external_orders: filters at the order level (skips OCM updates with no rfo).

After both filters pass, the adapter emits only the outputs that apply to the update. Market‑level filtering exits before any per‑runner work, and neither filter logs a warning.

If you set stream_market_ids_filter, ensure it includes every market you trade. Orders placed on markets excluded from the filter miss live fill and cancel updates from the stream.

Fill handling

The adapter handles several edge cases when processing fills from the stream:

  • Incremental fills: Betfair reports cumulative matched sizes. The adapter calculates incremental fills by tracking the last known filled quantity per order.
  • Overfill protection: fills that would exceed the order quantity are rejected.
  • Race conditions: when stream fills arrive before the HTTP order response, the adapter caches the venue order ID immediately to ensure correct order matching.
  • Ambiguous submission recovery: a network failure, timeout, HTTP 5xx response, or Betfair TIMEOUT report can leave the placement outcome unknown. The adapter leaves the order in SUBMITTED status and retains the customer order reference because the venue may have accepted it. A matching OCM can confirm the order on the active stream or after a reconnect. Other non‑ambiguous errors and explicit Betfair failure reports reject immediately.
  • Gap-window fills: a fill that completes and rolls off the unmatched book during a stream disconnect is recovered by the post-reconnect mass-status reconciliation; see Post-reconnect reconciliation.

Voided fills

Betfair can void matched bets after reporting them, for example after an integrity ruling or a VAR decision. The order stream carries the running total in sv (size voided). Voids caused by runner removal settle instead of streaming, so they do not reach this path.

The adapter allocates each sv increase to locally applied fill lots newest-first and emits one cumulative OrderFillVoided per affected trade_id. A first-seen snapshot seeds its cumulative void state without reversing exposure Nautilus never applied, so a reconnect does not double-correct. Any sv increase also triggers an account refresh.

An EXECUTION_COMPLETE update with no locally applied fill lots takes the terminal path instead: one correction under a synthetic VOID-{bet_id} trade ID that carries the order to VOIDED. That status resolves only when sv is positive and both cancelled and lapsed quantities are zero, so a mixed update carrying sc or sl alongside sv emits no correction. Betfair voids never set is_reopened, so VOIDED is final.

The adapter also publishes the BetfairOrderVoided custom data type carrying the venue's raw void detail.

Rate limiting

The adapter uses separate rate limit buckets so that account state polling and reconciliation do not throttle order placement:

BucketDefaultEndpointsConfigurable
General5/sAccount state, reconciliation, keep‑alive.request_rate_per_second.
Orders20/splaceOrders, replaceOrders, cancelOrders.order_request_rate_per_second.

Each Betting API call uses the general HTTP retry budget, with up to three retries by default. After that call returns a session or rate‑limit error, the order status and fill report paths make one additional report‑level attempt. A session error first tries keep‑alive and falls back to full re‑login after any keep‑alive failure. Full re‑login updates execution stream authentication and requests a replacement after the query finishes. A TOO_MANY_REQUESTS error waits 5 seconds before the report‑level retry.

Betfair's own API limits are more nuanced than a single request rate:

CategoryLimitNotes
Order operations1,000 transactions/sTotal instructions across placeOrders, cancelOrders, replaceOrders.
Order projection queries3 concurrentlistMarketBook (with OrderProjection), listCurrentOrders, listMarketProfitAndLoss.
Best practice5 requests/sRecommended for listMarketBook per market.

See Why am I receiving the TOO_MANY_REQUESTS error? for how Betfair applies these limits.

Market version price protection

Betfair carries a version on the market definition. It changes when the market itself is redefined, for example when a runner is removed or the market status changes. It does not track ordinary price updates or matched volume. Attaching that version to an order asks Betfair to lapse the bet rather than match it into a market that has since been redefined.

use_market_version provides no protection today. The adapter reads the market version from the instrument's info dictionary, but it constructs every Betfair instrument with info unset, so no version is ever attached to a placeOrders or replaceOrders request. Setting use_market_version=True currently changes nothing; do not rely on it for price protection.

Custom data types

The adapter emits custom data through the market, order, race, and cricket streams. Market custom data flows automatically when subscribed to markets.

TypeStreamMetadata keyDescription
BetfairTickerMarketinstrument_idLast traded price, traded volume, BSP indicators.
BetfairStartingPriceMarketinstrument_idRealized BSP after market close.
BetfairBspBookDeltaMarketinstrument_idBSP projected book updates.
BetfairSequenceCompletedMarketMarks end of a market change sequence.
BetfairOrderVoidedOrderinstrument_idVoided order details (size voided, price, side).
BetfairRaceRunnerDataRaceselection_idLive GPS tracking per runner (TPD).
BetfairRaceProgressRacerace_idSectional times, running order, jump data.
BetfairCricketMatchCricketevent_idFixture, team, match statistic, and incident data.

Subscribe by type name from an actor or strategy. Every type in the table above carries its metadata key on the published topic, so the subscription must supply that key and the value it is scoped to. BetfairSequenceCompleted is the exception: it publishes without metadata, so it is subscribed by type name alone.

from nautilus_trader.model import DataType

# One runner's GPS data
self.subscribe_data(DataType("BetfairRaceRunnerData", metadata={"selection_id": 49411491}))

# One race's progress
self.subscribe_data(DataType("BetfairRaceProgress", metadata={"race_id": "35278018.1617"}))

# Sequence markers carry no metadata
self.subscribe_data(DataType("BetfairSequenceCompleted"))

Race data requires Total Performance Data (TPD) coverage and a Betfair API key with TPD access. Enable with subscribe_race_data=True. Not every race has GPS tracking. Cricket data requires subscribe_cricket_data=True.

Historical data

BetfairDataLoader converts recorded Betfair stream files into instruments, order book deltas, trade ticks, and instrument status and close events, along with the market, race, and cricket custom data types above. Files hold newline-delimited JSON, either plain or compressed with gzip (.gz) or bzip2 (.bz2). The loader parses mcm, rcm, and ccm messages and skips the rest, so it produces no BetfairOrderVoided because that type comes from the order stream. Use load_instruments when only the instrument definitions are needed, because it skips all other parsing.

Trade ticks are derived from cumulative traded volumes, so the loader keeps that state across lines within a file. Call reset before loading an unrelated file to clear cached volumes and instruments. See the Rust examples for loading a file and running it through a backtest.

Multi-node deployment

When multiple trading nodes share a single Betfair account across different markets:

  1. Set stream_market_ids_filter to include only that node's markets.
  2. Set reconcile_market_ids_only=True with reconcile_market_ids to limit reconciliation scope.
  3. Set ignore_external_orders=True to drop bets placed outside NautilusTrader.

Market isolation between nodes comes from stream_market_ids_filter and the reconciliation scope, not from ignore_external_orders. Every bet this adapter submits carries a customer order reference, so another node's bets pass that filter; only bets with no reference, such as those placed on the Betfair site, are dropped. Without the market filters, each node reconciles and reports the whole account.

Configuration

Data client configuration

OptionDefaultNotes
account_currencyGBPBetfair account currency.
usernameNoneFalls back to BETFAIR_USERNAME.
passwordNoneFalls back to BETFAIR_PASSWORD.
app_keyNoneFalls back to BETFAIR_APP_KEY.
proxy_urlNoneOptional proxy URL for HTTP requests.
request_rate_per_second5General HTTP rate limit.
default_min_notionalNoneOptional minimum notional override.
event_type_idsNoneOptional navigation filter.
event_type_namesNoneOptional navigation filter.
event_idsNoneOptional navigation filter.
country_codesNoneOptional navigation filter.
market_typesNoneOptional navigation filter.
market_idsNoneOptional navigation filter.
min_market_start_timeNoneOptional navigation filter.
max_market_start_timeNoneOptional navigation filter.
stream_hostNoneOptional stream host override.
stream_portNoneOptional stream port override.
stream_heartbeat_secs5Interval between stream heartbeats.
stream_heartbeat_timeout_secs60Dead‑peer timeout before reconnect.
stream_reconnect_delay_initial_ms2,000Initial reconnect delay.
stream_reconnect_delay_max_ms30,000Maximum reconnect delay.
stream_use_tlsTrueUse TLS for the stream connection.
stream_conflate_msNoneExplicit conflation setting.
subscription_delay_secs3Delay before the first market subscription.
subscribe_race_dataFalseSubscribe to RCM updates.
subscribe_cricket_dataFalseSubscribe to cricket CCM updates.

When stream_conflate_ms is None, the adapter omits conflateMs from the subscription and leaves the conflation rate to Betfair. Set stream_conflate_ms=0 to request no conflation explicitly and receive every price update.

Execution client configuration

OptionDefaultNotes
trader_idTRADER-001Trader ID for the client core.
account_idBETFAIR-001Account ID for the client core.
account_currencyGBPBetfair account currency.
usernameNoneFalls back to BETFAIR_USERNAME.
passwordNoneFalls back to BETFAIR_PASSWORD.
app_keyNoneFalls back to BETFAIR_APP_KEY.
proxy_urlNoneOptional proxy URL for HTTP requests.
request_rate_per_second5General HTTP rate limit.
order_request_rate_per_second20Order endpoint rate limit.
stream_hostNoneOptional stream host override.
stream_portNoneOptional stream port override.
stream_heartbeat_secs5Interval between stream heartbeats.
stream_heartbeat_timeout_secs60Dead‑peer timeout before reconnect.
stream_reconnect_delay_initial_ms2,000Initial reconnect delay.
stream_reconnect_delay_max_ms30,000Maximum reconnect delay.
stream_use_tlsTrueUse TLS for the stream connection.
stream_market_ids_filterNoneOptional live OCM market filter.
ignore_external_ordersFalseOnly skips OCM updates with no rfo.
calculate_account_stateTrueEnables periodic account state polling.
request_account_state_secs300Poll interval for account funds (0 disables).
reconcile_market_ids_onlyFalseWhen True, use reconcile_market_ids.
reconcile_market_idsNoneExplicit startup reconciliation market IDs.
use_market_versionFalseAttach market version to orders; currently has no effect.
stream_gap_recovery_lookback_mins10Lookback window for the post‑reconnect mass‑status reconciliation.

Contributing

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

On this page