diff --git a/README.md b/README.md index 2a2bf20..77b563d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ ib.connect() .cancelPositionsMulti(requestId) .cancelRealTimeBars(tickerId) .cancelScannerSubscription(tickerId) +.cancelTickByTickData(tickerId) .exerciseOptions(tickerId, contract, exerciseAction, exerciseQuantity, account, override) .placeOrder(id, contract, order) .replaceFA(faDataType, xml) @@ -83,6 +84,8 @@ ib.connect() .reqFundamentalData(reqId, contract, reportType) .reqGlobalCancel() .reqHistoricalData(tickerId, contract, endDateTime, durationStr, barSizeSetting, whatToShow, useRTH, formatDate, keepUpToDate) +.reqHistoricalTicks(tickerId, contract, startDateTime, endDateTime, numberOfTicks, whatToShow, useRTH, ignoreSize) +.reqTickByTickData(tickerId, contract, tickType, numberOfTicks, ignoreSize) .reqIds(numIds) .reqManagedAccts() .reqMarketDataType(marketDataType) @@ -134,6 +137,12 @@ ib.connect() .on('execDetailsEnd', function (reqId)) .on('fundamentalData', function (reqId, data)) .on('historicalData', function (reqId, date, open, high, low, close, volume, count, WAP, hasGaps)) +.on('historicalTickTradeData', (reqId, timestamp, mask, price, size, exchange, specialConditions)) +.on('historicalTickBidAskData', (reqId, timestamp, mask, priceBid, priceAsk, sizeBid, sizeAsk)) +.on('historicalTickMidPointData', (reqId, timestamp, price, size)) +.on('tickByTickAllLast', reqId, tickType, time, price, size, { pastLimit, unreported }, exchange, specialConditions) +.on('tickByTickBidAsk', reqId, time, bidPrice, askPrice, bidSize, askSize, { bidPastLow, askPastHigh }) +.on('tickByTickMidPoint', reqId, time, midPoint)) .on('managedAccounts', function (accountsList)) .on('marketDataType', function (reqId, marketDataType)) .on('nextValidId', function (orderId)) diff --git a/examples/reqHistTickData.js b/examples/reqHistTickData.js new file mode 100644 index 0000000..2b042c4 --- /dev/null +++ b/examples/reqHistTickData.js @@ -0,0 +1,60 @@ +var _ = require('lodash'); +var chalk = require('chalk'); + +var ib = new(require('..'))({ + // clientId: 0, + // host: '127.0.0.1', + // port: 4003 +}).on('error', (err) => { + console.error(chalk.red(err.message)); +}).on('historicalTickTradeData', (...data) => { + console.log( + '%s %s%d %s%s %s%d %s%d %s%d %s%s %s%s', + chalk.cyan('[historicalTickTradeData]'), + chalk.bold('reqId='), data[0], + chalk.bold('timestamp='), data[1], + chalk.bold('mask='), data[2], + chalk.bold('price='), data[3], + chalk.bold('size='), data[4], + chalk.bold('exchange='), data[5], + chalk.bold('specialConditions='), data[6], + ); +}).on('historicalTickBidAskData', (...data) => { + console.log( + '%s %s%d %s%s %s%d %s%d %s%d %s%d %s%d', + chalk.cyan('[historicalTickBidAskData]'), + chalk.bold('reqId='), data[0], + chalk.bold('timestamp='), data[1], + chalk.bold('mask='), data[2], + chalk.bold('priceBid='), data[3], + chalk.bold('priceAsk='), data[4], + chalk.bold('sizeBid='), data[5], + chalk.bold('sizeAsk='), data[6], + ); +}).on('historicalTickMidPointData', (...data) => { + console.log( + '%s %s%d %s%s %s%d %s%d', + chalk.cyan('[historicalTickMidPointData]'), + chalk.bold('reqId='), data[0], + chalk.bold('timestamp='), data[1], + chalk.bold('price='), data[2], + chalk.bold('size='), data[3] + ); + }); + + +ib.connect(); + +// tickerId, contract, startDateTime, endDateTime, numberOfTicks, whatToShow, useRTH, ignoreSize +ib.reqHistoricalTicks(1, ib.contract.stock('SPY', 'SMART', 'USD'), '20180711 12:00:00', null, 10, 'TRADES', 1, false); +ib.reqHistoricalTicks(2, ib.contract.stock('SPY', 'SMART', 'USD'), '20180711 12:00:00', null, 10, 'BID_ASK', 1, false); +ib.reqHistoricalTicks(3, ib.contract.stock('SPY', 'SMART', 'USD'), '20180711 12:00:00', null, 10, 'MIDPOINT', 1, false); + + +ib.on('historicalTickDataEnd', (reqId) => { + console.log( + '%s %s%d', + chalk.cyan('[historicalTickDataEnd]'), + chalk.bold('reqId='), reqId + ); +}); diff --git a/examples/reqTickByTickData.js b/examples/reqTickByTickData.js new file mode 100644 index 0000000..c76660e --- /dev/null +++ b/examples/reqTickByTickData.js @@ -0,0 +1,68 @@ +var _ = require('lodash'); +var chalk = require('chalk'); + +var ib = new(require('..'))({ + // clientId: 0, + // host: '127.0.0.1', + // port: 4003 +}).on('error', (err) => { + console.error(chalk.red(err.message)); +}).on('tickByTickAllLast', (...data) => { + console.log( + '%s %s%d %s%d %s%d %s%d %s%s %s%s %s%s', + chalk.cyan('[tickByTickAllLast]'), + chalk.bold('reqId='), data[0], + chalk.bold('tickType='), data[1], + chalk.bold('timestamp='), data[2], + chalk.bold('price='), data[3], + chalk.bold('size='), data[4], + chalk.bold('attributes='), JSON.stringify(data[5]), + chalk.bold('exchange='), data[6], + chalk.bold('specialConditions='), data[7], + ); +}).on('tickByTickBidAsk', (...data) => { + console.log( + '%s %s%d %s%s %s%d %s%d %s%d %s%d %s%s', + chalk.cyan('[tickByTickBidAsk]'), + chalk.bold('reqId='), data[0], + chalk.bold('timestamp='), data[1], + chalk.bold('priceBid='), data[2], + chalk.bold('priceAsk='), data[3], + chalk.bold('sizeBid='), data[4], + chalk.bold('sizeAsk='), data[5], + chalk.bold('attributes='), JSON.stringify(data[6]), + ); +}).on('tickByTickMidPoint', (...data) => { + console.log( + '%s %s%d %s%s %s%d', + chalk.cyan('[tickByTickMidPoint]'), + chalk.bold('reqId='), data[0], + chalk.bold('timestamp='), data[1], + chalk.bold('midpoint='), data[2], + ); +}); + +ib.connect(); + +// tickerId, contract, tick type (BidAsk, Last, AllLast, MidPoint), numberOfTicks, ignoreSize +ib.reqTickByTickData(1, ib.contract.stock('SPY', 'SMART', 'USD'), 'BidAsk', 0, false) +//ib.reqTickByTickData(1, ib.contract.stock('SPY', 'SMART', 'USD'), 'AllLast', 0, false) +//ib.reqTickByTickData(1, ib.contract.stock('SPY', 'SMART', 'USD'), 'Last', 0, false) +//ib.reqTickByTickData(1, ib.contract.stock('SPY', 'SMART', 'USD'), 'MidPoint', 0, false) + +setTimeout(() => { + console.log( + '%s %s%d', + chalk.cyan('sending [cancelTickByTickData]'), + chalk.bold('reqId='), 1); + + ib.cancelTickByTickData(1); + ib.disconnect(); +}, 5000); + +// +// [tickByTickBidAsk] reqId=1 timestamp=1531426792 priceBid=279.15 priceAsk=279.18 sizeBid=200 sizeAsk=3100 attributes={"bidPastLow":false,"askPastHigh":false} +// [tickByTickAllLast] reqId=1 tickType=2 timestamp=1531426753 price=279.15 size=200 attributes={"pastLimit":false,"unreported":false} exchange=BATS specialConditions= T +// [tickByTickAllLast] reqId=1 tickType=1 timestamp=1531426672 price=279.15 size=213 attributes={"pastLimit":false,"unreported":false} exchange=BATS specialConditions= FT +// [tickByTickMidPoint] reqId=1 timestamp=1531426697 midpoint=279.16 +// \ No newline at end of file diff --git a/lib/constants.js b/lib/constants.js index 44ed691..e7e19f0 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -75,6 +75,10 @@ exports.INCOMING = { HISTORICAL_NEWS_END : 87, HEAD_TIMESTAMP : 88, HISTOGRAM_DATA : 89, + HISTORICAL_TICKS : 96, + HISTORICAL_TICKS_BID_ASK : 97, + HISTORICAL_TICKS_LAST : 98, + TICK_BY_TICK : 99 }; // outgoing msg id's @@ -143,7 +147,10 @@ exports.OUTGOING = { REQ_HISTORICAL_NEWS : 86, REQ_HEAD_TIMESTAMP : 87, REQ_HISTOGRAM_DATA : 88, - CANCEL_HISTOGRAM_DATA : 89 + CANCEL_HISTOGRAM_DATA : 89, + REQ_HISTORICAL_TICKS : 96, + REQ_TICK_BY_TICK_DATA : 97, + CANCEL_TICK_BY_TICK_DATA : 98 }; exports.MIN_SERVER_VER = { @@ -219,7 +226,8 @@ exports.MIN_SERVER_VER = { MARKET_RULES : 126, PNL : 127, NEWS_QUERY_ORIGINS : 128, - SERVER_VER_UNREALIZED_PNL : 129 + SERVER_VER_UNREALIZED_PNL : 129, + MIN_SERVER_VER_HISTORICAL_TICKS: 130 }; diff --git a/lib/incoming.js b/lib/incoming.js index 70f3a13..0ddae40 100644 --- a/lib/incoming.js +++ b/lib/incoming.js @@ -420,6 +420,128 @@ Incoming.prototype._HISTORICAL_DATA = function () { this._emit('historicalData', reqId, completedIndicator, -1, -1, -1, -1, -1, -1, -1, false); }; +Incoming.prototype._HISTORICAL_TICKS_LAST = function() { + var reqId = this.dequeueInt(); + var tickCount = this.dequeueInt(); + + var date; + var mask; + var price; + var size; + var exchange; + var specialConditions; + + while (tickCount--) { + date = this.dequeue(); + mask = this.dequeueInt(); + price = this.dequeueFloat(); + size = this.dequeueInt(); + exchange = this.dequeue(); + specialConditions = this.dequeue(); + + this._emit('historicalTickTradeData', reqId, date, mask, price, size, exchange, specialConditions); + } + + var done = this.dequeueBool(); + + if (done) { + this._emit('historicalTickDataEnd', reqId); + } +}; + +Incoming.prototype._HISTORICAL_TICKS_BID_ASK = function() { + var reqId = this.dequeueInt(); + var tickCount = this.dequeueInt(); + + var date; + var mask; + var priceBid; + var sizeBid; + var priceAsk; + var sizeAsk; + + while (tickCount--) { + date = this.dequeue(); + mask = this.dequeueInt(); + priceBid = this.dequeueFloat(); + sizeBid = this.dequeueInt(); + priceAsk = this.dequeueFloat(); + sizeAsk = this.dequeueInt(); + this._emit('historicalTickBidAskData', reqId, date, mask, priceBid, priceAsk, sizeBid, sizeAsk); + } + + var done = this.dequeueBool(); + + if (done) { + this._emit('historicalTickDataEnd', reqId); + } +}; + +Incoming.prototype._HISTORICAL_TICKS = function() { // MIDPOINT (size appears to always be zero) + var reqId = this.dequeueInt(); + var tickCount = this.dequeueInt(); + + var date; + var price; + var size; + + while (tickCount--) { + date = this.dequeue(); + this.dequeueInt();//for consistency + price = this.dequeueFloat(); + size = this.dequeueInt(); + + this._emit('historicalTickMidPointData', reqId, date, price, size); + } + + var done = this.dequeueBool(); + + if (done) { + this._emit('historicalTickDataEnd', reqId); + } +}; + +Incoming.prototype._TICK_BY_TICK = function () { + var reqId = this.dequeueInt(); + var tickType = this.dequeueInt(); + var time = this.dequeue(); + + var mask; + + switch (tickType){ + case 0: // None + break; + case 1: // Last + case 2: // Alllast + var price = this.dequeueFloat(); + var size = this.dequeueInt(); + mask = this.dequeueInt(); + var pastLimit = (mask & (1 << 0)) !== 0; + var unreported = (mask & (1 << 1)) !== 0; + var exchange = this.dequeue(); + var specialConditions = this.dequeue(); + + this._emit('tickByTickAllLast', reqId, tickType, time, price, size, { pastLimit, unreported }, exchange, specialConditions); + break; + case 3: // BidAsk + var bidPrice = this.dequeueFloat(); + var askPrice = this.dequeueFloat(); + var bidSize = this.dequeueInt(); + var askSize = this.dequeueInt(); + mask = this.dequeueInt(); + var bidPastLow = (mask & (1 << 0)) !== 0; + var askPastHigh = (mask & (1 << 1)) !== 0; + + this._emit('tickByTickBidAsk', reqId, time, bidPrice, askPrice, bidSize, askSize, { bidPastLow, askPastHigh }); + break; + case 4: // MidPoint + var midPoint = this.dequeueFloat(); + + this._emit('tickByTickMidPoint', reqId, time, midPoint); + break; + } +}; + Incoming.prototype._HEAD_TIMESTAMP = function() { var reqId = this.dequeueInt(); var headTimestamp = this.dequeue(); diff --git a/lib/index.js b/lib/index.js index 431be9c..981fa41 100644 --- a/lib/index.js +++ b/lib/index.js @@ -313,6 +313,44 @@ IB.prototype.reqHistoricalData = function (tickerId, contract, endDateTime, dura return this; }; +IB.prototype.reqHistoricalTicks = function (tickerId, contract, startDateTime, endDateTime, numberOfTicks, + whatToShow, useRTH, ignoreSize){ + assert(_.isNumber(tickerId), '"tickerId" must be an integer - ' + tickerId); + assert(_.isPlainObject(contract), '"contract" must be a plain object - ' + contract); + if (startDateTime && endDateTime || !startDateTime && !endDateTime) { + assert.fail('specify one of "startDateTime" or "endDateTime" (as a string) but not both - ' + startDateTime + ':' + endDateTime); + } + assert(_.isNumber(numberOfTicks), '"numberOfTicks" must be a number - ' + numberOfTicks); + assert(_.isString(whatToShow), '"whatToShow" must be a string - ' + whatToShow); + assert(_.isNumber(useRTH), '"useRTH" must be an integer - ' + useRTH); + assert(_.isBoolean(ignoreSize), '"ignoreSize" must be an boolean - ' + ignoreSize); + + this._send('reqHistoricalTicks', tickerId, contract, startDateTime, endDateTime, numberOfTicks, + whatToShow, useRTH, ignoreSize); + + return this; +}; + +IB.prototype.reqTickByTickData = function (tickerId, contract, tickType, numberOfTicks, ignoreSize) { + assert(_.isNumber(tickerId), '"tickerId" must be an integer - ' + tickerId); + assert(_.isPlainObject(contract), '"contract" must be a plain object - ' + contract); + assert(_.isString(tickType), '"tickType" must be a string - ' + tickType); + assert(_.isNumber(numberOfTicks), '"numberOfTicks" must be a number - ' + numberOfTicks); + assert(_.isBoolean(ignoreSize), '"ignoreSize" must be an boolean - ' + ignoreSize); + + this._send('reqTickByTickData', tickerId, contract, tickType, numberOfTicks, ignoreSize); + + return this; +}; + +IB.prototype.cancelTickByTickData = function (tickerId) { + assert(_.isNumber(tickerId), '"tickerId" must be an integer - ' + tickerId); + + this._send('cancelTickByTickData', tickerId); + + return this; +}; + IB.prototype.reqIds = function (numIds) { assert(_.isNumber(numIds), '"numIds" must be an integer - ' + numIds); diff --git a/lib/outgoing.js b/lib/outgoing.js index e6341c3..1964af3 100644 --- a/lib/outgoing.js +++ b/lib/outgoing.js @@ -1050,6 +1050,103 @@ Outgoing.prototype.reqHistoricalData = function (tickerId, contract, endDateTime this._send(args); }; +Outgoing.prototype.reqHistoricalTicks = function (tickerId, contract, startDateTime, endDateTime, + numberOfTicks, whatToShow, useRTH, ignoreSize) { + + if (this._controller._serverVersion < 73) { + return this._controller.emitError('It does not support historical tick data backfill.'); + } + + var args = [C.OUTGOING.REQ_HISTORICAL_TICKS, tickerId]; + + // send contract fields + if (this._controller._serverVersion >= C.MIN_SERVER_VER.TRADING_CLASS) { + args.push(contract.conId); + } + + args.push(contract.symbol); + args.push(contract.secType); + args.push(contract.expiry); + args.push(contract.strike); + args.push(contract.right); + args.push(contract.multiplier); + args.push(contract.exchange); + args.push(contract.primaryExch); + args.push(contract.currency); + args.push(contract.localSymbol); + args.push(contract.tradingClass); + args.push(contract.includeExpired); + + args.push(startDateTime); + args.push(endDateTime); + + args.push(numberOfTicks); + args.push(whatToShow); + args.push(useRTH); + + if (_.isString(contract.secType) && + C.BAG_SEC_TYPE.toUpperCase() === contract.secType.toUpperCase()) { + if (!_.isArray(contract.comboLegs)) { + args.push(0); + } else { + args.push(contract.comboLegs.length); + + contract.comboLegs.forEach(function (comboLeg) { + args.push(comboLeg.conId); + args.push(comboLeg.ratio); + args.push(comboLeg.action); + args.push(comboLeg.exchange); + }); + } + } + + args.push(ignoreSize); + + if (this._controller._serverVersion >= C.MIN_SERVER_VER.LINKING) { + args.push(''); + } + + this._send(args); +}; + +Outgoing.prototype.reqTickByTickData = function (tickerId, contract, tickType, numberOfTicks, ignoreSize){ + if (this._controller._serverVersion < 73) { + return this._controller.emitError('It does not support tick by tick data.'); + } + + var args = [C.OUTGOING.REQ_TICK_BY_TICK_DATA, tickerId]; + + // send contract fields + args.push(contract.conId); + args.push(contract.symbol); + args.push(contract.secType); + args.push(contract.expiry); + args.push(contract.strike); + args.push(contract.right); + args.push(contract.multiplier); + args.push(contract.exchange); + args.push(contract.primaryExch); + args.push(contract.currency); + args.push(contract.localSymbol); + args.push(contract.tradingClass); + + args.push(tickType); + args.push(numberOfTicks); + args.push(ignoreSize); + + this._send(args); +}; + +Outgoing.prototype.cancelTickByTickData = function (tickerId) { + if (this._controller._serverVersion < 73) { + return this._controller.emitError('It does not support tick by tick data.'); + } + + var args = [C.OUTGOING.CANCEL_TICK_BY_TICK_DATA, tickerId]; + + this._send(args); +}; + Outgoing.prototype.reqIds = function (numIds) { var version = 1;