Back to Nautilus Trader

Betfair v2

docs/integrations/betfair_v2.md

1.226.018.8 KB
Original Source

Betfair v2

The Betfair Rust adapter is in active parity work. This page tracks the current Rust behavior and the planned cutover from the stable guide in Betfair.

This page mirrors the main section order from Betfair. When the Rust adapter becomes the primary Betfair path, this file can replace betfair.md with small edits instead of a full rewrite.

Scope

  • Source of truth for this page: crates/adapters/betfair
  • Stable guide today: Betfair
  • Purpose of this page: track the current Rust surface, the known gaps, and the cutover path

Current Rust status

AreaCurrent Rust behaviorDifference from betfair.md todayCutover work
Order typesMARKET only supports AT_THE_CLOSE; LIMIT supports BSP on close flows.Stable guide is still Python shaped in this area.Decide final Betfair market order model.
Batch operationsSubmitOrderList and BatchCancelOrders are implemented.Stable guide used to mark these as unsupported.Keep and promote.
Reconciliation scopereconcile_market_ids_only uses reconcile_market_ids; otherwise falls back to stream_market_ids_filter.Stable guide says stream filtering and reconciliation are separate.Decide if Rust keeps or removes this coupling.
Full image cache checksRust uses generate_mass_status() at startup and does not run check_cache_against_order_image.Stable guide describes the Python full image cache check.Add parity or document the Rust path as final.
External order filteringignore_external_orders only skips OCM updates with no rfo.Python also uses it during full image cache checks.Decide final filtering behavior.
Config surfaceNo certs_dir, no instrument_config, fixed keep alive, required heartbeat value.Stable guide still documents the Python config surface.Decide whether to add parity or bless Rust surface.
SSL certificatesStream client currently hardcodes certs_dir=None.Stable guide documents certificate configuration and BETFAIR_CERTS_DIR.Add support or remove from the future guide.

Orders capability

Order types

Order TypeSupportedNotes
MARKET✓*Rust only supports AT_THE_CLOSE, which maps to Betfair MARKET_ON_CLOSE.
LIMITRust supports 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.

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.

Rust currently also accepts LIMIT orders in AT_THE_OPEN mode and routes them through Betfair LIMIT_ON_CLOSE instructions. Treat that as current behavior, not a settled public contract.

Batch operations

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

Execution control flow

The current Rust execution path is:

  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.

Current Rust notes:

  • stream_market_ids_filter filters live OCM updates.
  • reconcile_market_ids_only=True uses explicit reconcile_market_ids.
  • When reconcile_market_ids_only=False and reconcile_market_ids is unset, Rust currently falls back to stream_market_ids_filter for startup reconciliation.
  • Rust does not yet implement the Python check_cache_against_order_image full-image cache check.
  • ignore_external_orders=True currently skips only OCM updates with no rfo.

Session management and reconnection

Betfair sessions expire every 12-24 hours. The Rust adapter handles session recovery automatically through three mechanisms:

MechanismTriggerAction
Periodic keep‑aliveEvery 10 hours.Renew session token, push to all stream watch channels.
Keep‑alive fallbackKeep‑alive returns LoginFailed.Full re‑login via reconnect(), push fresh token to streams.
Stream reconnectConnection message after drop.Try keep‑alive, fall back to re‑login on LoginFailed, update auth.

Transient errors (network timeouts, 5xx responses) during keep-alive are logged and skipped. The existing session token is preserved and the next keep-alive interval retries. Only LoginFailed errors (session expiry) trigger a full re-login.

Both the data and execution clients run identical reconnection logic. Each spawns:

  • A keep-alive task that periodically refreshes the session and pushes updated auth bytes to the stream watch channels.
  • A reconnect handler that listens for Connection messages after a stream reconnect, refreshes the session, and pushes the new token.

The stream client stores auth bytes in a tokio::sync::watch channel. The post_reconnection closure reads from this channel on each TCP reconnect, so a token refreshed by either the keep-alive task or reconnect handler is picked up on the next connection attempt.

The data client reconnect handler also updates the race stream auth when a race stream is active.

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 - 100010.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).

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.
  • Network error recovery: when an HTTP order submission fails with a network error (timeout, connection reset), the order may still have been placed on the venue. The adapter leaves the order in SUBMITTED status and retains the customer order reference so the stream can confirm the order when it reconnects. API errors (where Betfair explicitly rejected) reject immediately.

Rate limiting

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

BucketDefaultEndpoints
General5/sAccount state, reconciliation, keep‑alive.
Orders20/splaceOrders, replaceOrders, cancelOrders.

Order status and fill report queries retry once on session errors after refreshing the session. TOO_MANY_REQUESTS errors retry after a 5-second delay.

Market version price protection

When use_market_version=True, each order request includes the market version last seen by the adapter. If the market has advanced beyond that version by the time Betfair processes the order, Betfair lapses the bet rather than matching it against a changed book.

The adapter reads the market version from the instrument's info dictionary, which the Exchange Streaming API's MarketDefinition updates populate. Orders submitted before the first MarketDefinition is received do not include a version.

Custom data types

The Rust adapter emits the same custom data types as the Python adapter through the market and race streams. All custom data flows automatically when subscribed to markets.

TypeStreamDescription
BetfairTickerMarketLast traded price, traded volume, BSP indicators.
BetfairStartingPriceMarketRealized BSP after market close.
BetfairSequenceCompletedMarketMarks end of a market change sequence.
BetfairOrderVoidedOrderVoided order details (size voided, price, side).
BetfairRaceRunnerDataRaceLive GPS tracking per runner (TPD).
BetfairRaceProgressRaceSectional times, running order, jump data.

Race data requires Total Performance Data (TPD) coverage and a Betfair API key with TPD access. Enable with subscribe_race_data=True.

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 ignore_external_orders=True to suppress warnings about orders from other nodes.
  3. Set reconcile_market_ids_only=True to limit reconciliation scope.

Current Rust configuration

Data client configuration

OptionDefaultNotes
account_currencyRequiredBetfair 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_ms5,000Required in Rust today.
stream_idle_timeout_ms60,000Idle 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.

Rust does not yet expose certs_dir or instrument_config. Rust also uses a fixed 36,000 second keep-alive interval.

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_ms5,000Required in Rust today.
stream_idle_timeout_ms60,000Idle 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_stateTrueGates periodic account state polling in Rust today.
request_account_state_secs300Poll interval for account funds.
reconcile_market_ids_onlyFalseWhen True, use reconcile_market_ids.
reconcile_market_idsNoneExplicit startup reconciliation market IDs.
use_market_versionFalseAttach market version to place and replace requests.

Rust does not yet expose certs_dir or instrument_config.

Cutover plan

Use this page as the transition tracker until the Rust adapter becomes the primary Betfair path.

At cutover:

  1. Decide whether Rust keeps its current reconciliation filter behavior or matches the Python split.
  2. Decide whether Rust adds certificate configuration and other Python config fields.
  3. Decide whether Rust keeps BSP-only MARKET orders or adds the Python aggressive-limit path.
  4. Promote this file to betfair.md.
  5. Move any remaining Python-only notes into a short legacy note or release note.