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
JavaScript
;
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