A comprehensive Node.js TypeScript library for the Betfair Exchange API, providing both JSON-RPC API integration and real-time Exchange Stream API support for automated betting and trading applications. Features advanced market recording capabilities for capturing raw TLS stream data and structured market summaries.
- Authentication: Interactive login with username/password (certificate auth coming soon)
- Market Data: List markets, get market books, runner details, and pricing information
- Order Management: Place, cancel, replace, and update bets with comprehensive validation
- Account Operations: Get account details, balance, statement, and currency rates
- Live Market Data: Subscribe to real-time market updates with low latency
- Order Stream: Real-time order updates, unmatched orders, and matched positions
- Message Segmentation: Automatic handling of segmented messages for optimal performance
- Stream Decoding: Automatic deserialization and caching of market/order deltas
- Connection Management: Robust connection handling with automatic reconnection
- Heartbeat Monitoring: Built-in connection health monitoring
- Full TypeScript Support: Complete type definitions for all API responses
- Functional API: Modern functional programming approach with immutable state
- Error Handling: Comprehensive error handling and validation
- Flexible Configuration: Support for different currencies, locales, and regions
- Price Ladder Display: Real-time price ladder visualization
- Price Ladder Utilities: Complete Betfair price ladder generation and tick navigation
- Market Caching: Efficient delta-based market state management
- Request Conflation: Batch multiple market subscriptions for efficiency
- Currency Conversion: Automatic currency rate handling and conversion
- Raw Data Recording: Capture pure TLS stream transmissions before any processing
- Structured Market Summaries: Automatic BSP, winner, and trading volume extraction
- Market-Specific Files: Individual files per market for organized data storage
- Production-Ready Monitoring: Robust heartbeat and error handling for long recordings
npm install betfair-node
Create a .env
file in your project root:
BETFAIR_APP_KEY=your_application_key_here
BETFAIR_USERNAME=your_username_here
BETFAIR_PASSWORD=your_password_here
import { createBetfairApiState, login, listMarketCatalogue } from 'betfair-node';
async function main() {
// Create API state
const apiState = createBetfairApiState(
'en', // locale
'AUD', // currency
500, // conflateMs
5000, // heartbeatMs
() => {} // marketChangeCallback (not used in this example)
);
// Login to Betfair
const authenticatedState = await login(
apiState,
process.env.BETFAIR_APP_KEY!,
process.env.BETFAIR_USERNAME!,
process.env.BETFAIR_PASSWORD!
);
// List horse racing markets
const marketFilter = {
eventTypeIds: ['7'], // Horse Racing
marketCountries: ['AU'],
marketTypeCodes: ['WIN']
};
const markets = await listMarketCatalogue(
authenticatedState,
marketFilter,
['MARKET_START_TIME', 'RUNNER_DESCRIPTION'],
'FIRST_TO_START',
10
);
console.log(`Found ${markets.data.result.length} markets`);
}
import {
createBetfairApiState,
login,
createAndConnectStream,
subscribeToMarkets
} from 'betfair-node';
async function streamMarketData() {
// Setup and authenticate
const apiState = createBetfairApiState(
'en', // locale
'AUD', // currency
500, // conflateMs
5000, // heartbeatMs
(marketCache, deltas) => {
// Handle real-time market updates
console.log('Market update received:', Object.keys(marketCache));
}
);
const authenticatedState = await login(apiState, appKey, username, password);
// Create stream connection
const streamState = await createAndConnectStream(
authenticatedState.sessionKey!,
authenticatedState.appKey!,
false, // segmentationEnabled
500, // conflateMs
5000, // heartbeatMs
{ currencyCode: 'AUD', rate: 1.0 }, // audCurrencyRate
(marketCache, deltas) => {
// Handle real-time market updates
console.log('Market update received:', Object.keys(marketCache));
}
);
// Subscribe to specific markets
await subscribeToMarkets(
streamState,
['1.234567890'], // market IDs
['EX_BEST_OFFERS'] // price data fields
);
}
import {
createBetfairApiState,
login,
placeOrders,
validateOrderParameters
} from 'betfair-node';
async function placeBet() {
const apiState = createBetfairApiState(
'en', // locale
'AUD', // currency
500, // conflateMs
5000, // heartbeatMs
() => {} // marketChangeCallback (not used in this example)
);
const authenticatedState = await login(apiState, appKey, username, password);
const marketId = '1.234567890';
const selectionId = 123456;
const price = 2.50;
const size = 10.00;
// Validate before placing
const validation = validateOrderParameters(marketId, selectionId, price, size);
if (validation.isValid) {
const placeInstruction = {
orderType: 'LIMIT' as const,
selectionId: selectionId,
side: 'BACK' as const,
limitOrder: {
size: size,
price: price,
persistenceType: 'LAPSE' as const
}
};
const result = await placeOrders(
authenticatedState,
marketId,
[placeInstruction],
'my-bet-ref',
undefined, // marketVersion
undefined, // customerStrategyRef
false // async
);
console.log('Bet placed:', result);
}
}
import {
getTickSize,
getNextTick,
getPreviousTick,
getNearestValidPrice,
generatePriceLadder
} from 'betfair-node';
// Get tick sizes for different price ranges
console.log(getTickSize(1.5)); // 0.01 (1.01-2 range)
console.log(getTickSize(3.35)); // 0.05 (3-4 range)
console.log(getTickSize(120)); // 10 (100-1000 range)
// Navigate price ladder
console.log(getNextTick(120)); // 130
console.log(getNextTick(3.35)); // 3.4
console.log(getPreviousTick(130)); // 120
console.log(getPreviousTick(3.4)); // 3.35
// Round invalid prices to valid ladder prices
console.log(getNearestValidPrice(1.234)); // 1.23 (nearest)
console.log(getNearestValidPrice(3.123, 'up')); // 3.15 (round up)
console.log(getNearestValidPrice(3.149, 'down')); // 3.1 (round down)
// Generate complete price ladder (memoized for performance)
const ladder = generatePriceLadder(1.90, 2.10);
console.log(ladder);
// [1.9, 1.91, 1.92, ..., 1.99, 2, 2.02, 2.04, ..., 2.1]
// Betfair price ranges and increments:
// 1.01β2: 0.01 | 2β3: 0.02 | 3β4: 0.05 | 4β6: 0.1 | 6β10: 0.2
// 10β20: 0.5 | 20β30: 1 | 30β50: 2 | 50β100: 5 | 100β1000: 10
import {
createBetfairApiState,
login,
listMarketCatalogue,
createAndConnectRecordingStream,
subscribeToMarkets,
createMarketRecorderState,
startRecording,
createRecordingMarketChangeCallback,
createRawDataCallback
} from 'betfair-node';
async function recordMarketData() {
// Setup authentication
const apiState = createBetfairApiState('en', 'AUD', 250, 5000, () => {});
const authenticatedState = await login(apiState, appKey, username, password);
// Configure recording (both raw and structured data)
const recorderState = createMarketRecorderState({
outputDirectory: './recordings',
enableBasicRecording: true, // Structured summaries
enableRawRecording: true, // Pure TLS stream data
rawFilePrefix: '', // Files: {marketId}.txt
basicFilePrefix: 'basic_', // Files: basic_{marketId}.json
});
// Find markets and start recording
const markets = await listMarketCatalogue(/* ... */);
const marketIds = markets.data.result.map(m => m.marketId);
startRecording(recorderState, marketIds);
// Connect with recording-optimized stream (30s heartbeat)
const streamState = await createAndConnectRecordingStream(
authenticatedState.sessionKey!,
appKey,
false, // segmentationEnabled
250, // conflateMs
{ currencyCode: 'AUD', rate: 1.0 },
createRecordingMarketChangeCallback(recorderState), // Basic recording
createRawDataCallback(recorderState) // Raw recording
);
subscribeToMarkets(streamState, marketIds);
// Records automatically to:
// - {marketId}.txt (raw TLS transmissions)
// - basic_{marketId}.json (market summaries with BSP, winners, etc.)
}
createBetfairApiState(locale, currencyCode, conflateMs, heartbeatMs, marketChangeCallback)
- Create initial API statelogin(state, appKey, username, password)
- Authenticate with Betfair and return updated statelogout(sessionKey)
- End session
listEventTypes(state, filter)
- Get available sports/event typeslistMarketCatalogue(state, filter, projections, sort, maxResults)
- Search for marketslistMarketBook(state, marketIds, priceProjection)
- Get market pricing datalistCurrentOrders(state, betIds?, marketIds?)
- Get current orders
placeOrders(state, marketId, instructions, customerRef?)
- Place new betscancelOrders(state, marketId, instructions, customerRef?)
- Cancel existing betsreplaceOrders(state, marketId, instructions, customerRef?)
- Replace existing betsupdateOrders(state, marketId, instructions, customerRef?)
- Update existing bets
getAccountFunds(state, wallet?)
- Get account balancegetAccountDetails(state)
- Get account informationgetAccountStatement(state, options)
- Get account statement
createMarketRecorderState(config)
- Initialize market recorder with configurationstartRecording(state, marketIds)
- Start recording for specified marketsstopRecording(state)
- Stop recording and save all data to filescreateRecordingMarketChangeCallback(state, originalCallback?)
- Create callback for structured recordingcreateRawDataCallback(state)
- Create callback for raw TLS stream recordinggetRecordingStatus(state, marketId)
- Get current recording status for a marketloadBasicRecord(config, marketId)
- Load previously saved market summarylistRecordedMarkets(config)
- List all recorded markets in output directory
createAndConnectStream(authToken, appKey, segmentationEnabled, conflateMs, heartbeatMs, audCurrencyRate, marketChangeCallback, rawDataCallback?)
- Connect to streamcreateAndConnectRecordingStream(authToken, appKey, segmentationEnabled, conflateMs, audCurrencyRate, marketChangeCallback, rawDataCallback?)
- Connect with recording optimization (30s heartbeat)closeStream(state)
- Disconnect from stream
subscribeToMarkets(state, marketIds, fields, segmentationEnabled?)
- Subscribe to marketsresubscribeToMarkets(state, marketIds, fields?)
- Update subscriptionunsubscribeFromMarkets(state, marketIds?)
- Unsubscribe from markets
subscribeToOrders(state, orderFilter?)
- Subscribe to order updates with optional filteringgetOrderStreamCache(state)
- Get current order cache from stream
Betfair's Exchange Stream API supports message segmentation to handle large data payloads efficiently. When enabled, large messages are broken into smaller segments that are automatically reassembled by the library.
When segmentationEnabled: true
is set:
-
Large messages are segmented into multiple parts:
SEG_START
- First segment of a message- Middle segments (no segmentationType) - Additional data segments
SEG_END
- Final segment containing remaining data
-
Automatic reassembly - The library automatically:
- Buffers segments until complete message received
- Merges all market/order changes from all segments
- Processes the complete message only after reassembly
- Maintains separate buffers for market and order messages
-
Performance benefits:
- Improved end-to-end performance and latency
- Faster time to first and last byte
- Reduced memory pressure for large messages
// Enable segmentation for better performance with large messages
const streamState = await createAndConnectStream(
authToken,
appKey,
true, // segmentationEnabled - IMPORTANT for order streams
500, // conflateMs
5000, // heartbeatMs
currencyRate,
marketChangeCallback,
orderChangeCallback // Order callback for order stream functionality
);
// Subscribe to order updates (requires segmentation for full functionality)
const orderFilter = {
includeOverallPosition: true, // Include all position data
customerStrategyRefs: ['MyStrategy'], // Optional: filter by strategy
partitionMatchedByStrategyRef: false // Optional: partition data
};
const updatedState = subscribeToOrders(streamState, orderFilter);
- Order streams require segmentation enabled for full functionality
- Segmentation is automatically handled - no manual intervention needed
- Non-segmented messages are processed immediately
- Segment buffers are automatically cleaned up after processing
- Multiple concurrent segmented message streams are supported
validateOrderParameters(marketId, selectionId, price, size)
- Validate bet parameterscalculateBackProfit(stake, odds)
- Calculate back bet profitcalculateLayLiability(stake, odds)
- Calculate lay bet liabilityfindCurrencyRate(rates, currency)
- Find currency conversion rate
getTickSize(price)
- Get the appropriate tick increment for any pricegeneratePriceLadder(minPrice?, maxPrice?)
- Generate complete price ladder with memoizationgetNextTick(price)
- Get the next valid tick price (or null if at maximum)getPreviousTick(price)
- Get the previous valid tick price (or null if at minimum)getNearestValidPrice(price, direction?)
- Round to nearest valid price ('up', 'down', or 'nearest')
The examples/
directory contains comprehensive examples:
Lists Australian greyhound racing markets with detailed information including venues, race times, and runner details.
Demonstrates placing a real back bet with validation, error handling, and success reporting.
Real-time order stream monitoring showing your live betting positions and order updates. Features:
- Complete Order View: Shows all current orders via JSON-RPC API for comparison
- Real-time Updates: Live order status changes, matches, and position updates
- Segmentation Support: Demonstrates proper message segmentation handling
- Strategy Filtering: Examples of filtering orders by customer strategy references
Professional real-time trading interface showing live price ladders, similar to trading software. Features:
- Real-time price updates (500ms refresh)
- Color-coded price changes
- Best back/lay prices and volumes
- Clean, professional display
Comprehensive market data recording system that captures both raw TLS transmissions and structured market summaries. Features:
- Dual Recording: Raw TLS stream data + structured market summaries
- Production Ready: 30-second heartbeat, graceful error handling
- Market-Specific Files: Individual files per market for organized storage
- Complete Lifecycle: Records from market open through settlement
Streamlined recording example with two modes:
- Finite mode: Records until all markets complete (default)
- Timeout mode: Records for 30 seconds for quick testing (
--timeout
flag)
Advanced perpetual recording system that continuously records ALL greyhound markets:
- Auto-discovery: Finds new markets every 5 minutes
- Dynamic recording: Adds new markets without interrupting existing recordings
- Multi-country: Records from AU, GB, IE, US greyhound tracks
- Production ready: Runs indefinitely until manually stopped
Analyze historical Betfair Exchange data files to generate market and runner-level analytics for research and strategy testing.
β’ Simple Backtest (npm run example:backtest
)
- Easy to run, processes a historical file from
examples/
- Outputs JSON records and a summary report in
backtest_results/
β’ Advanced Backtest (npm run example:backtest-verbose
)
- Programmatic API for custom analysis with verbose logging options
Input format: line-delimited JSON with Betfair Exchange Stream mcm
messages. Place a file (e.g., examples/1.216777904
) in the examples directory.
Outputs: JSON records per market and an aggregated backtest_summary.json
in backtest_results/
.
See detailed docs: examples/BACKTEST_README.md
.
π Detailed Documentation: See
examples/MARKET_RECORDER_README.md
for comprehensive market recorder documentation including architecture, data flow, and advanced usage.
# List greyhound markets
npm run example:greyhounds
# Place a bet (uses real money!)
npm run example:bet
# Live market view (specify market ID)
npm run example:live -- marketid=1.234567890
# Record market data (stops when all markets complete)
npm run example:recorder
# Quick recording test - finite mode (stops when markets complete)
npm run example:recorder-simple
# Quick recording test - timeout mode (stops after 30 seconds)
npm run example:recorder-simple --timeout
# Perpetual greyhound recording (runs forever, auto-discovers new markets)
npm run example:recorder-perpetual
# Simple historical backtest (reads file from examples/)
npm run example:backtest
# Advanced/verbose backtest
npm run example:backtest-verbose
- BetfairApi: Main API client for JSON-RPC endpoints
- BetfairExchangeStreamApi: Real-time streaming client
- BetfairStreamDecoder: Handles stream data deserialization and caching
- MarketRecorder: Dual-layer recording system for raw TLS data and structured summaries
- Heartbeat: Connection monitoring and health checking
- Utils: Helper functions for validation and calculations
- Functional Programming: Immutable state management with pure functions
- Type Safety: Comprehensive TypeScript definitions for all API responses
- Error Handling: Explicit error handling with detailed error messages
- Performance: Efficient caching and delta processing for real-time data
- Flexibility: Support for different regions, currencies, and market types
Variable | Required | Default | Description |
---|---|---|---|
BETFAIR_APP_KEY |
β Yes | - | Your Betfair application key |
BETFAIR_USERNAME |
β Yes | - | Your Betfair account username |
BETFAIR_PASSWORD |
β Yes | - | Your Betfair account password |
TIMEZONE |
β No | Australia/Sydney |
Timezone for time-based operations |
LOCALE |
β No | en |
Locale for API responses |
CURRENCY |
β No | AUD |
Default currency for amounts |
- Create a Betfair Account: Sign up at betfair.com
- Get Developer Access: Apply for API access at developer.betfair.com
- Create an Application: Generate your App Key from the developer portal
- Fund Your Account: Add funds to place real bets (required for full API access)
# Install dependencies
npm install
# Run tests
npm test
# Build the project
npm run build
# Run examples
npm run example:greyhounds
npm run example:bet
npm run example:live -- marketid=1.234567890
npm run example:recorder
npm run example:recorder-simple
npm run example:recorder-perpetual
# Development mode
npm run dev
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- π Certificate-based authentication support
- π Advanced market analysis utilities
- π― Strategy backtesting framework using recorded market data
- π Historical data integration and replay functionality
- π Enhanced request conflation
- π± React/Vue.js integration examples
- ποΈ Market recording database integration (PostgreSQL/MongoDB)
- π Real-time recording dashboard and monitoring
This project is licensed under the MIT License - see the LICENSE file for details.
This software is for educational and development purposes. Users are responsible for complying with Betfair's terms of service and applicable gambling regulations. The authors are not responsible for any financial losses incurred through the use of this software.
Author: Felix McCuaig
Email: felixmccuaig@gmail.com
GitHub: betfair-node
For questions, issues, or feature requests, please use the GitHub issues page.