UNPKG

betfair-node

Version:

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.

596 lines 22.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateOrderParameters = exports.calculateLayLiability = exports.calculateBackProfit = exports.isValidBetId = exports.createUpdateInstruction = exports.createReplaceInstruction = exports.createCancelInstruction = exports.updateOrders = exports.replaceOrders = exports.cancelOrders = exports.listClearedOrders = exports.listCurrentOrders = exports.betfairStandardizeLocation = exports.findCurrencyRate = exports.listCurrencyRates = exports.placeOrders = exports.listMarketProfitAndLoss = exports.listVenues = exports.listCountries = exports.listMarketTypes = exports.listEvents = exports.listTimeRanges = exports.listCompetitions = exports.listEventTypes = exports.listMarketBook = exports.listMarketCatalogue = exports.keepAlive = exports.logout = exports.login = exports.isAuthenticated = exports.ensureAuthenticated = exports.createBetfairApiState = void 0; const querystring = __importStar(require("querystring")); const axios_1 = __importDefault(require("axios")); const betfair_api_types_1 = require("./betfair-api-types"); const utils_1 = require("./utils"); // Constants const AUTH_URLS = { interactiveLogin: 'https://identitysso.betfair.com.au:443/api/login', botLogin: 'https://identitysso-api.betfair.com.au:443/api/certlogin', logout: 'https://identitysso.betfair.com.au:443/api/logout', keepAlive: 'https://identitysso.betfair.com.au:443/api/keepAlive', }; const BETTING_URLS = { exchange: 'https://api.betfair.com:443/exchange/betting/json-rpc/v1', }; const ACCOUNT_URLS = { accounts: 'https://api.betfair.com/exchange/account/json-rpc/v1', }; // Core API State Management const createBetfairApiState = (locale, currencyCode, conflateMs, heartbeatMs, marketChangeCallback) => ({ locale, targetCurrency: currencyCode, conflateMs, heartbeatMs, marketChangeCallback, }); exports.createBetfairApiState = createBetfairApiState; // Authentication utilities const ensureAuthenticated = (state) => { if (!state.sessionKey || !state.appKey) { throw new Error('Not authenticated. Call login() first.'); } return { sessionKey: state.sessionKey, appKey: state.appKey }; }; exports.ensureAuthenticated = ensureAuthenticated; const isAuthenticated = (state) => { return !!(state.sessionKey && state.appKey); }; exports.isAuthenticated = isAuthenticated; // HTTP Request utilities const performLogin = async (appKey, username, password) => { const formData = querystring.stringify({ username, password, login: true, redirectMethod: 'POST', product: 'home.betfair.int', url: 'https://www.betfair.com/', }); return (0, axios_1.default)({ method: 'post', url: AUTH_URLS.interactiveLogin, headers: { accept: 'application/json', 'content-type': 'application/x-www-form-urlencoded', 'content-length': formData.length, 'x-application': appKey, }, data: formData, }); }; const makeRequest = async (url, data, sessionKey, appKey) => { return (0, axios_1.default)({ method: 'post', url, headers: { accept: 'application/json', 'content-type': 'application/json', 'content-length': data.length, 'x-authentication': sessionKey, 'x-application': appKey, }, data, }); }; const makeBettingApiRequest = async (method, params, sessionKey, appKey) => { const requestPayload = { jsonrpc: '2.0', method: `SportsAPING/v1.0/${method}`, params, id: (0, utils_1.generatePacketId)(), }; return makeRequest(BETTING_URLS.exchange, JSON.stringify(requestPayload), sessionKey, appKey); }; const makeAccountApiRequest = async (method, params, sessionKey, appKey) => { const requestPayload = { jsonrpc: '2.0', method: `AccountAPING/v1.0/${method}`, params, id: (0, utils_1.generatePacketId)(), }; return makeRequest(ACCOUNT_URLS.accounts, JSON.stringify(requestPayload), sessionKey, appKey); }; // Core Authentication Functions const login = async (state, appKey, username, password) => { try { const authResponse = await performLogin(appKey, username, password); if (authResponse.data.status !== 'SUCCESS') { throw new Error('Login failed!'); } const updatedState = { ...state, appKey, sessionKey: authResponse.data.token, }; const currencyResponse = await (0, exports.listCurrencyRates)(updatedState, 'GBP'); if (currencyResponse.status !== 200) { throw new Error('Error listing currency rates'); } return { ...updatedState, currencyRates: currencyResponse.data.result, }; } catch (error) { throw new Error(`Login failed: ${error}`); } }; exports.login = login; const logout = async (sessionKey) => { const formData = querystring.stringify({ product: 'home.betfair.int', url: 'https://www.betfair.com/', }); return (0, axios_1.default)({ method: 'post', url: AUTH_URLS.logout, headers: { accept: 'application/json', 'content-type': 'application/x-www-form-urlencoded', 'content-length': formData.length, 'x-authentication': sessionKey, }, data: formData, }); }; exports.logout = logout; const keepAlive = async (sessionKey) => { const formData = querystring.stringify({ product: 'home.betfair.int', url: 'https://www.betfair.com/', }); return (0, axios_1.default)({ method: 'post', url: AUTH_URLS.keepAlive, headers: { accept: 'application/json', 'content-type': 'application/x-www-form-urlencoded', 'content-length': formData.length, 'x-authentication': sessionKey, }, data: formData, }); }; exports.keepAlive = keepAlive; // Market Data Functions const listMarketCatalogue = async (state, filter, marketProjection, sort, maxResults) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); return makeBettingApiRequest('listMarketCatalogue', { filter, marketProjection, sort, maxResults, locale: state.locale, }, sessionKey, appKey); }; exports.listMarketCatalogue = listMarketCatalogue; const listMarketBook = async (state, params) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); return makeBettingApiRequest('listMarketBook', params, sessionKey, appKey); }; exports.listMarketBook = listMarketBook; const makeDevApiRequest = async (state, method, filter) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); return makeBettingApiRequest(method, { filter, locale: state.locale, }, sessionKey, appKey); }; const listEventTypes = async (state, filter) => { return makeDevApiRequest(state, 'listEventTypes', filter); }; exports.listEventTypes = listEventTypes; const listCompetitions = async (state, filter) => { return makeDevApiRequest(state, 'listCompetitions', filter); }; exports.listCompetitions = listCompetitions; const listTimeRanges = async (state, filter) => { return makeDevApiRequest(state, 'listTimeRanges', filter); }; exports.listTimeRanges = listTimeRanges; const listEvents = async (state, filter) => { return makeDevApiRequest(state, 'listEvents', filter); }; exports.listEvents = listEvents; const listMarketTypes = async (state, filter) => { return makeDevApiRequest(state, 'listMarketTypes', filter); }; exports.listMarketTypes = listMarketTypes; const listCountries = async (state, filter) => { return makeDevApiRequest(state, 'listCountries', filter); }; exports.listCountries = listCountries; const listVenues = async (state, filter) => { return makeDevApiRequest(state, 'listVenues', filter); }; exports.listVenues = listVenues; const listMarketProfitAndLoss = async (state, marketIds, includeSettledBets, includeBspBets, netOfCommission) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); return makeBettingApiRequest('listMarketProfitAndLoss', { marketIds, includeSettledBets, includeBspBets, netOfCommission, locale: state.locale, }, sessionKey, appKey); }; exports.listMarketProfitAndLoss = listMarketProfitAndLoss; // Betting Functions const placeOrders = async (state, marketId, instructions, customerRef, marketVersion, customerStrategyRef, async) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); // Build params object with only non-empty values const params = { marketId, instructions, }; if (customerRef) { params.customerRef = customerRef; } if (marketVersion) { params.marketVersion = marketVersion; } if (customerStrategyRef) { params.customerStrategyRef = customerStrategyRef; } // Only add async if it's true (Betfair might not expect false) if (async) { params.async = async; } return makeBettingApiRequest('placeOrders', params, sessionKey, appKey); }; exports.placeOrders = placeOrders; // Account Functions const listCurrencyRates = async (state, fromCurrency) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); return makeAccountApiRequest('listCurrencyRates', { fromCurrency }, sessionKey, appKey); }; exports.listCurrencyRates = listCurrencyRates; // Utility Functions const findCurrencyRate = (currencyRates, currencyCode) => { return currencyRates.find(rate => rate.currencyCode === currencyCode); }; exports.findCurrencyRate = findCurrencyRate; /** * Standardizes location names for Betfair API consistency * @param location - The location string to standardize * @returns Standardized location string */ const betfairStandardizeLocation = (location) => { // Basic location standardization - can be extended with more rules const standardizedLocation = location .trim() .toLowerCase() .replace(/[^a-z0-9\s]/g, '') // Remove special characters .replace(/\s+/g, ' ') // Normalize whitespace .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); return standardizedLocation; }; exports.betfairStandardizeLocation = betfairStandardizeLocation; /** * Lists current orders for the authenticated account * @param state - Current API state * @param betIds - Optional list of bet IDs to filter by * @param marketIds - Optional list of market IDs to filter by * @param orderProjection - What order data to include in the response * @param placedDateRange - Optional date range for when orders were placed * @param orderBy - How to order the results * @param sortDir - Sort direction (earliest to latest or latest to earliest) * @param fromRecord - Record index to start from (for pagination) * @param recordCount - Number of records to return * @returns Promise with current orders response */ const listCurrentOrders = async (state, betIds, marketIds, orderProjection = betfair_api_types_1.OrderProjection.ALL, placedDateRange, orderBy, sortDir, fromRecord, recordCount) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); const params = { orderProjection, locale: state.locale, }; if (betIds && betIds.length > 0) { params.betIds = betIds; } if (marketIds && marketIds.length > 0) { params.marketIds = marketIds; } if (placedDateRange) { params.placedDateRange = placedDateRange; } if (orderBy) { params.orderBy = orderBy; } if (sortDir) { params.sortDir = sortDir; } if (fromRecord !== undefined) { params.fromRecord = fromRecord; } if (recordCount !== undefined) { params.recordCount = recordCount; } return makeBettingApiRequest('listCurrentOrders', params, sessionKey, appKey); }; exports.listCurrentOrders = listCurrentOrders; /** * Lists cleared (settled) orders for the authenticated account * @param state - Current API state * @param betStatus - Status of the bet (e.g., SETTLED, VOIDED, LAPSED, CANCELLED) * @param eventTypeIds - Optional list of event type IDs to filter by * @param eventIds - Optional list of event IDs to filter by * @param marketIds - Optional list of market IDs to filter by * @param runnerIds - Optional list of runner selection IDs to filter by * @param betIds - Optional list of bet IDs to filter by * @param side - Optional side filter (BACK or LAY) * @param settledDateRange - Optional date range for when bets were settled * @param groupBy - How to group the results * @param includeItemDescription - Whether to include item descriptions * @param fromRecord - Record index to start from (for pagination) * @param recordCount - Number of records to return * @returns Promise with cleared orders response */ const listClearedOrders = async (state, betStatus = betfair_api_types_1.BetStatus.SETTLED, eventTypeIds, eventIds, marketIds, runnerIds, betIds, side, settledDateRange, groupBy, includeItemDescription, fromRecord, recordCount) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); const params = { betStatus, locale: state.locale, }; if (eventTypeIds && eventTypeIds.length > 0) { params.eventTypeIds = eventTypeIds; } if (eventIds && eventIds.length > 0) { params.eventIds = eventIds; } if (marketIds && marketIds.length > 0) { params.marketIds = marketIds; } if (runnerIds && runnerIds.length > 0) { params.runnerIds = runnerIds; } if (betIds && betIds.length > 0) { params.betIds = betIds; } if (side) { params.side = side; } if (settledDateRange) { params.settledDateRange = settledDateRange; } if (groupBy) { params.groupBy = groupBy; } if (includeItemDescription !== undefined) { params.includeItemDescription = includeItemDescription; } if (fromRecord !== undefined) { params.fromRecord = fromRecord; } if (recordCount !== undefined) { params.recordCount = recordCount; } return makeBettingApiRequest('listClearedOrders', params, sessionKey, appKey); }; exports.listClearedOrders = listClearedOrders; /** * Cancels orders on the exchange * @param state - Current API state * @param marketId - The market ID where the orders are placed * @param instructions - Array of cancel instructions * @param customerRef - Optional customer reference for the transaction * @returns Promise with cancel execution report */ const cancelOrders = async (state, marketId, instructions, customerRef) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); if (!instructions || instructions.length === 0) { throw new Error('Cancel instructions are required'); } if (instructions.length > 60) { throw new Error('Maximum 60 cancel instructions allowed per request'); } const params = { marketId, instructions, locale: state.locale, }; if (customerRef) { params.customerRef = customerRef; } return makeBettingApiRequest('cancelOrders', params, sessionKey, appKey); }; exports.cancelOrders = cancelOrders; /** * Replaces orders on the exchange * @param state - Current API state * @param marketId - The market ID where the orders are placed * @param instructions - Array of replace instructions * @param customerRef - Optional customer reference for the transaction * @param marketVersion - Market version (optional but recommended) * @param async - Whether to process asynchronously * @returns Promise with replace execution report */ const replaceOrders = async (state, marketId, instructions, customerRef, marketVersion, async) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); if (!instructions || instructions.length === 0) { throw new Error('Replace instructions are required'); } if (instructions.length > 60) { throw new Error('Maximum 60 replace instructions allowed per request'); } const params = { marketId, instructions, locale: state.locale, }; if (customerRef) { params.customerRef = customerRef; } if (marketVersion !== undefined) { params.marketVersion = marketVersion; } if (async !== undefined) { params.async = async; } return makeBettingApiRequest('replaceOrders', params, sessionKey, appKey); }; exports.replaceOrders = replaceOrders; /** * Updates orders on the exchange * @param state - Current API state * @param marketId - The market ID where the orders are placed * @param instructions - Array of update instructions * @param customerRef - Optional customer reference for the transaction * @returns Promise with update execution report */ const updateOrders = async (state, marketId, instructions, customerRef) => { const { sessionKey, appKey } = (0, exports.ensureAuthenticated)(state); if (!instructions || instructions.length === 0) { throw new Error('Update instructions are required'); } if (instructions.length > 60) { throw new Error('Maximum 60 update instructions allowed per request'); } const params = { marketId, instructions, locale: state.locale, }; if (customerRef) { params.customerRef = customerRef; } return makeBettingApiRequest('updateOrders', params, sessionKey, appKey); }; exports.updateOrders = updateOrders; // Additional utility functions for order management /** * Creates a cancel instruction for a specific bet * @param betId - The bet ID to cancel * @param sizeReduction - Optional size reduction instead of full cancellation * @returns Cancel instruction */ const createCancelInstruction = (betId, sizeReduction) => ({ betId, ...(sizeReduction !== undefined && { sizeReduction }), }); exports.createCancelInstruction = createCancelInstruction; /** * Creates a replace instruction for a specific bet * @param betId - The bet ID to replace * @param newPrice - The new price for the bet * @returns Replace instruction */ const createReplaceInstruction = (betId, newPrice) => ({ betId, newPrice, }); exports.createReplaceInstruction = createReplaceInstruction; /** * Creates an update instruction for a specific bet * @param betId - The bet ID to update * @param newPersistenceType - The new persistence type * @returns Update instruction */ const createUpdateInstruction = (betId, newPersistenceType) => ({ betId, newPersistenceType, }); exports.createUpdateInstruction = createUpdateInstruction; /** * Validates if a bet ID is in correct format * @param betId - The bet ID to validate * @returns True if valid, false otherwise */ const isValidBetId = (betId) => { // Bet IDs are typically numeric strings return /^\d+$/.test(betId); }; exports.isValidBetId = isValidBetId; /** * Calculates potential profit for a back bet * @param stake - The stake amount * @param odds - The odds * @returns Potential profit */ const calculateBackProfit = (stake, odds) => { return stake * (odds - 1); }; exports.calculateBackProfit = calculateBackProfit; /** * Calculates liability for a lay bet * @param stake - The stake amount (what you'll win) * @param odds - The odds * @returns Liability (what you'll lose if the bet wins) */ const calculateLayLiability = (stake, odds) => { return stake * (odds - 1); }; exports.calculateLayLiability = calculateLayLiability; /** * Validates order parameters for common issues * @param marketId - Market ID * @param selectionId - Selection ID * @param price - Odds price * @param size - Bet size * @returns Object with validation result and any error messages */ const validateOrderParameters = (marketId, selectionId, price, size) => { const errors = []; if (!marketId || !marketId.match(/^1\.\d+$/)) { errors.push('Invalid market ID format'); } if (!Number.isInteger(selectionId) || selectionId <= 0) { errors.push('Selection ID must be a positive integer'); } if (price < 1.01 || price > 1000) { errors.push('Price must be between 1.01 and 1000'); } if (size < 0.01) { errors.push('Size must be at least 0.01'); } if (size > 100000) { errors.push('Size cannot exceed 100,000'); } return { isValid: errors.length === 0, errors, }; }; exports.validateOrderParameters = validateOrderParameters; //# sourceMappingURL=betfair-api.js.map