UNPKG

betfair-emulator

Version:
371 lines (325 loc) 15.5 kB
'use strict'; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var _ = require('lodash'); var utils = require('./utils.js'); var EmulatorBet = require('./emulator_bet.js'); var EmulatorMarket = function () { function EmulatorMarket(logger, marketId) { _classCallCheck(this, EmulatorMarket); this.log = logger; this.marketId = marketId; this.initialized = false; this.runners = {}; this.matchedBets = new Map(); this.unmatchedBets = new Map(); } // match bets using new price data _createClass(EmulatorMarket, [{ key: '_matchBets', value: function _matchBets() { var _this = this; this.log.debug('try match bets'); var matchedBetIds = []; this.unmatchedBets.forEach(function (bet, betId) { _this.log.info('_matchBets betId=', betId, bet.toString()); var runner = _this.runners[bet.selectionId]; switch (bet.side) { case 'LAY': var toLay = runner.layAvailability; if (toLay.length == 0) { // no offers return; } var worstLay = _.last(toLay); console.log(worstLay, toLay); if (bet.price > worstLay.price) { bet.match(worstLay.price, 'marketWorstLayPrice=' + worstLay.price + ', price=' + bet.price); matchedBetIds.push(bet.betId); } _.forEach(toLay, function (offer) { if (bet.price >= offer.price && bet.size < offer.size) { bet.match(offer.price, 'marketPrice=' + offer.price + ', marketSize=' + offer.size + ' price=' + bet.price); matchedBetIds.push(bet.betId); return false; } return true; }); break; case 'BACK': var toBack = runner.backAvailability; if (toBack.length == 0) { // no offers return; } var worstBack = _.last(toBack); if (bet.price < worstBack.price) { bet.match(worstBack.price, 'marketWorstBackPrice=' + worstBack.price + ', price=' + bet.price); matchedBetIds.push(bet.betId); } _.forEach(toBack, function (offer) { if (bet.price <= offer.price && bet.size < offer.size) { bet.match(offer.price, 'marketPrice=' + offer.price + ', marketSize=' + offer.size + ' price=' + bet.price); matchedBetIds.push(bet.betId); return false; } return true; }); break; } }); // place matched bets to this.matchedBets, remove from this.unmatchedBets _.forEach(matchedBetIds, function (betId) { var bet = _this.unmatchedBets.get(betId); _this.matchedBets.set(betId, bet); _this.unmatchedBets.delete(betId); }); return; } // handle market go inplay }, { key: '_onGoInplay', value: function _onGoInplay() { var _this2 = this; this.log.debug('marketId: ' + this.marketId + ' go inplay'); // cancel all the bets that has persistence LAPSE var lapsedIds = []; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = this.unmatchedBets[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var tuple = _step.value; var _tuple = _slicedToArray(tuple, 2); var betId = _tuple[0]; var bet = _tuple[1]; if (bet.limitOrder.persistenceType == 'LAPSE') { bet.lapse(); lapsedIds.push(betId); } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } _.each(lapsedIds, function (id) { _this2.unmatchedBets.delete(id); }); } // update listMarketBook with emulator orders }, { key: '_updateOrders', value: function _updateOrders(runner, orders) { this.log.debug('update orders for runnerId' + runner.selectionId); if (!_.isArray(runner.orders)) { runner.orders = []; } var selectionId = runner.selectionId; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = orders[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var tuple = _step2.value; var _tuple2 = _slicedToArray(tuple, 2); var betId = _tuple2[0]; var bet = _tuple2[1]; if (selectionId == bet.selectionId) { runner.orders.push(bet.getOrder()); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } } }, { key: 'onListMarketBook', value: function onListMarketBook(params, marketBook) { var _this3 = this; if (marketBook.marketId != this.marketId) { throw new Error('onListMarketBook marketId mismatch'); } this.log.debug('onListMarketBook for market: ' + this.marketId, params); if (!this.inplay && marketBook.inplay) { this._onGoInplay(); } this.status = marketBook.status; this.betDelay = marketBook.betDelay; this.inplay = marketBook.inplay; this.version = marketBook.version; // update prices _.each(marketBook.runners, function (runner) { // avaliable to BACK var availableToBack = _.cloneDeep(runner.ex.availableToBack); var backAvailability = []; _.reduce(availableToBack, function (acc, item) { acc += item.size; item.size = utils.normalizeSize(acc); backAvailability.push(item); return acc; }, 0); _this3.log.debug('back availability', backAvailability); // avaliable to lAY var availableToLay = _.cloneDeep(runner.ex.availableToLay); var layAvailability = []; _.reduce(availableToLay, function (acc, item) { acc += item.size; item.size = utils.normalizeSize(acc); layAvailability.push(item); return acc; }, 0); _this3.log.debug('lay availability', layAvailability); // store _this3.runners[runner.selectionId] = { selectionId: runner.selectionId, backAvailability: backAvailability, layAvailability: layAvailability }; }); // try match bets this._matchBets(); // put orders into response _.each(marketBook.runners, function (runner) { // update orders/matches if (params.orderProjection == 'ALL' || params.orderProjection == 'EXECUTABLE') { _this3.log.debug('update unmatched orders'); _this3._updateOrders(runner, _this3.unmatchedBets); } if (params.orderProjection == 'ALL' || params.orderProjection == 'EXECUTION_COMPLETE') { _this3.log.debug('update matched orders'); _this3._updateOrders(runner, _this3.matchedBets); } }); this.initialized = true; } }, { key: 'placeOrders', value: function placeOrders(params) { var _this4 = this; var cb = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; if (params.marketId != this.marketId) { throw new Error('placeOrders marketId mismatch'); } if (!_.isArray(params.instructions) || _.isEmpty(params.instructions)) { throw new Error('placeOrders: bad or empty instructions'); } this.log.debug('placeOrders params:', params); // TODO - limitOrder is hardcoded var marketId = params.marketId; var bets = []; _.each(params.instructions, function (instruction) { var bet = new EmulatorBet(_this4.log, instruction.selectionId, instruction.side, instruction.limitOrder.price, instruction.limitOrder.size, { orderType: instruction.orderType, persistenceType: instruction.limitOrder.persistenceType }); _this4.unmatchedBets.set(bet.betId, bet); bets.push(bet); }); // result var result = { customerRef: params.customerRef, status: "SUCCESS", marketId: marketId, id: 2, // UNDOCUMENTED parameter instructionReports: _.map(bets, function (bet) { return { status: "SUCCESS", instruction: bet.getInstruction(), betId: bet.betId, placedDate: bet.placedDate, averagePriceMatched: bet.averagePriceMatched, sizeMatched: bet.sizeMatched, isEmulatorBet: true }; }) }; this.log.debug('placeOrders result:', result); cb(null, result); } }, { key: 'cancelOrders', value: function cancelOrders(params) { var _this5 = this; var cb = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; if (params.marketId != this.marketId) { throw new Error('cancelOrders marketId mismatch'); } var instructions = _.cloneDeep(params.instructions); if (!instructions) { var ids = Array.from(this.unmatchedBets.keys()); instructions = ids.map(function (id) { return { betId: id }; }); } if (instructions.length == 0) { // no bets to cancel cb(null, { customerRef: params.customerRef, status: 'SUCCESS', marketId: params.marketId, instructionReports: [] }); return; } var allErrors = true; var hasErrors = false; var result = []; _.each(instructions, function (instruction) { var bet = _this5.unmatchedBets.get(instruction.betId); if (!bet) { result.push({ status: "FAILURE", errorCode: "BET_TAKEN_OR_LAPSED", instruction: instruction }); hasErrors = true; return; } var sizeCancelled = bet.cancel(instruction.sizeReduction); result.push({ status: "SUCCESS", instruction: instruction, sizeCancelled: sizeCancelled, cancelledDate: new Date() }); allErrors = false; if (bet.sizeRemaining == 0) { _this5.unmatchedBets.delete(bet.betId); } }); var status = allErrors ? 'FAILURE' : hasErrors ? "PROCESSED_WITH_ERRORS" : "SUCCESS"; var errorCode = allErrors ? 'BET_ACTION_ERROR' : hasErrors ? "PROCESSED_WITH_ERRORS" : undefined; cb(null, { customerRef: params.customerRef, status: status, errorCode: errorCode, marketId: params.marketId, instructionReports: result }); } }]); return EmulatorMarket; }(); module.exports = EmulatorMarket;