Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reqHistoricalTicks & reqTickByTickData #127

Merged
merged 4 commits into from
Jul 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name historicalTickTradeData doesn't match the IB API documentation for TRADE data ticks. It should be historicalTicksLast. I've spent quite a bit of time trying to figure out this mismatch, because node-ib doesn't have documentation and points at the official IB API docs.

}

var done = this.dequeueBool();

if (done) {
this._emit('historicalTickDataEnd', reqId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful event but no correspondence with the IB API. By reading the documentation, I expected two parameters - an array of ticks, and a Boolean done. I agree it's helpful to do this sort of work for the user, but this breaks the expectations of those who read the official IB API docs.

}
};

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be historicalTicksBidAsk.

}

var done = this.dequeueBool();

if (done) {
this._emit('historicalTickDataEnd', reqId);
}
};

Incoming.prototype._HISTORICAL_TICKS = function() { // MIDPOINT (size appears to always be zero)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here :-(

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be historicalTicks for conformance with the IB API documentation.

}

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