Skip to content

Commit

Permalink
Add support for reqHistoricalTicks & reqTickByTickData (#127)
Browse files Browse the repository at this point in the history
* fix assert typos

* add reqHistoricalTicks (requires TWS build 968+)

https://interactivebrokers.github.io/tws-api/historical_time_and_sales.html

* adds support for reqTickByTickData (time & sales)

https://interactivebrokers.github.io/tws-api/tick_data.html
  • Loading branch information
mvberg authored and pilwon committed Jul 14, 2018
1 parent 11fecbd commit 9a4dc7b
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 2 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
60 changes: 60 additions & 0 deletions examples/reqHistTickData.js
Original file line number Diff line number Diff line change
@@ -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
);
});
68 changes: 68 additions & 0 deletions examples/reqTickByTickData.js
Original file line number Diff line number Diff line change
@@ -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
//
12 changes: 10 additions & 2 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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
};


Expand Down
122 changes: 122 additions & 0 deletions lib/incoming.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
38 changes: 38 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading

0 comments on commit 9a4dc7b

Please sign in to comment.