tlab-trading-toolkit
Version:
A trading toolkit for building advanced trading bots on the GDAX platform
436 lines (435 loc) • 15.4 kB
JavaScript
;
/***************************************************************************************************************************
* @license *
* Copyright 2017 Coinbase, Inc. *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on *
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
* License for the specific language governing permissions and limitations under the License. *
***************************************************************************************************************************/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const LiveOrderbook_1 = require("../../src/core/LiveOrderbook");
const GDAXFeed_1 = require("../../src/exchanges/gdax/GDAXFeed");
const Logger_1 = require("../../src/utils/Logger");
const events_1 = require("events");
const types_1 = require("../../src/lib/types");
const BookBuilder_1 = require("../../src/lib/BookBuilder");
const ExchangeFeed_1 = require("../../src/exchanges/ExchangeFeed");
const helper_1 = require("../../test/helper");
const simple = require("simple-mock");
const assert = require('assert');
const mockSocket = new events_1.EventEmitter();
mockSocket.send = () => { };
mockSocket.close = () => {
mockSocket.removeAllListeners();
};
describe('GDAX Message feed', () => {
before(() => __awaiter(this, void 0, void 0, function* () {
return helper_1.prepareProductMap();
}));
const messages = [
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '10.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '95.0', '5.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '98.0', '2.5']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '15.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['sell', '200.0', '15.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '5.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '11.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['sell', '115.0', '5.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['sell', '111.0', '1.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['sell', '115.0', '3.0']]
},
{
type: 'match',
product_id: 'BTC-USD',
sequence: 10,
time: '2017-01-01 15:00Z',
price: '100.0',
size: '4.0',
side: 'buy',
maker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad30006',
taker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad0010',
trade_id: '1'
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '7.0']]
},
{
type: 'match',
product_id: 'BTC-USD',
sequence: 12,
time: '2017-01-01 15:00Z',
price: '111.0',
size: '1.0',
side: 'sell',
maker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad30008',
taker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad00800',
trade_id: '2'
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['sell', '111.0', '0.0']]
},
{
type: 'match',
product_id: 'BTC-USD',
sequence: 14,
time: '2017-01-01 15:00Z',
price: '115.0',
size: '1.0',
side: 'sell',
maker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad30007',
taker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad00800',
trade_id: '3'
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['sell', '115.0', '2.0']]
},
{
type: 'match',
product_id: 'BTC-USD',
sequence: 16,
time: '2017-01-01 15:00Z',
price: '200.0',
size: '3.0',
side: 'sell',
maker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad30004',
taker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad01600',
trade_id: '4'
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '10.0']]
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '10.1']]
},
{
type: 'match',
product_id: 'BTC-USD',
sequence: 20,
time: '2017-01-01 15:00Z',
price: '100.0',
size: '2.0',
side: 'buy',
maker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad30006',
taker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad00801',
trade_id: '4'
},
{
type: 'match',
product_id: 'BTC-USD',
sequence: 21,
time: '2017-01-01 15:00Z',
price: '100.0',
size: '4.0',
side: 'buy',
maker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad30018',
taker_order_id: '9e8aa3b8-60bd-11e7-907b-a6006ad00802',
trade_id: '5'
},
{
type: 'l2update',
product_id: 'BTC-USD',
changes: [['buy', '100.0', '5.1']]
}
];
const expectedState = {
sequence: 17,
orderPool: {
'100.0': { id: '100.0', side: 'buy', price: types_1.Big(100), size: types_1.Big(4.1) },
'115.0': { id: '115.0', side: 'sell', price: types_1.Big(115), size: types_1.Big(2) },
'200.0': { id: '200.0', side: 'sell', price: types_1.Big(200), size: types_1.Big(15) },
'98.0': { id: '98.0', side: 'buy', price: types_1.Big(98), size: types_1.Big(2.5) },
'95.0': { id: '95.0', side: 'buy', price: types_1.Big(95), size: types_1.Big(5) }
},
bids: [
{
price: types_1.Big(100),
totalSize: types_1.Big(5.1),
orders: [{ id: '100.0', side: 'buy', price: types_1.Big(100), size: types_1.Big(5.1) }]
},
{
price: types_1.Big(98),
totalSize: types_1.Big(2.5),
orders: [{ id: '98.0', side: 'buy', price: types_1.Big(98), size: types_1.Big(2.5) }
]
},
{
price: types_1.Big(95),
totalSize: types_1.Big(5),
orders: [{ id: '95.0', side: 'buy', price: types_1.Big(95), size: types_1.Big(5) }]
}
],
asks: [
{
price: types_1.Big(200),
totalSize: types_1.Big(15),
orders: [{ id: '200.0', side: 'sell', price: types_1.Big(200), size: types_1.Big(15) }]
},
{
price: types_1.Big(115),
totalSize: types_1.Big(2),
orders: [{ id: '115.0', side: 'sell', price: types_1.Big(115), size: types_1.Big(2) }]
}
]
};
let book;
let gdaxFeed;
before(() => {
simple.mock(ExchangeFeed_1.hooks, 'WebSocket').callFn(() => mockSocket);
gdaxFeed = new GDAXFeed_1.GDAXFeed({
logger: null,
auth: null,
apiUrl: '',
wsUrl: '',
channels: null
});
book = new LiveOrderbook_1.LiveOrderbook({
logger: Logger_1.NullLogger,
strictMode: true,
product: 'BTC/USD'
});
gdaxFeed.pipe(book);
});
after(() => {
gdaxFeed.disconnect();
});
it('produces a matching live GDAX book', (done) => {
book.once('LiveOrderbook.snapshot', () => {
snapshot = true;
});
let snapshot = false;
mockSocket.emit('open');
mockSocket.emit('message', JSON.stringify({
type: 'snapshot',
product_id: 'BTC-USD',
sequence: 0,
bids: [],
asks: []
}));
for (const message of messages) {
mockSocket.emit('message', JSON.stringify(message));
}
setImmediate(() => {
assert.equal(book.sequence, 17, 'Did not process all relevant messages');
assert.ok(snapshot, 'Did not receive snapshot');
const expectedBook = new BookBuilder_1.BookBuilder(Logger_1.NullLogger);
expectedBook.fromState(expectedState);
assert.deepEqual(book.state(), expectedBook.state());
done();
});
});
after(() => {
simple.restore();
});
});
describe('GDAX feed with channels', () => {
let book;
let gdaxFeed;
const messages = [
{
type: 'snapshot',
product_id: 'BTC-EUR',
bids: [['100', '10'], ['90', '5']],
asks: [['110', '1'], ['115', '2']]
},
{
type: 'l2update',
product_id: 'BTC-EUR',
from_sequence: 1000,
to_sequence: 1010,
changes: [['buy', '100', '5'], ['sell', '110', '3']]
},
{
type: 'l2update',
product_id: 'BTC-EUR',
from_sequence: 1010,
to_sequence: 1011,
changes: [['buy', '90', '0']]
},
{
type: 'l2update',
product_id: 'BTC-EUR',
from_sequence: 1011,
to_sequence: 1025,
changes: [['sell', '200', '1']]
}
];
const expectedState = {
sequence: 5,
orderPool: {
100: {
id: '100',
price: types_1.Big(100),
size: types_1.Big(5),
side: 'buy'
},
115: {
id: '115',
price: types_1.Big(115),
size: types_1.Big(2),
side: 'sell'
},
110: {
id: '110',
price: types_1.Big(110),
size: types_1.Big(3),
side: 'sell'
},
200: {
id: '200',
price: types_1.Big(200),
size: types_1.Big(1),
side: 'sell'
}
},
bids: [
{
price: types_1.Big(100),
totalSize: types_1.Big(5),
orders: [
{
id: '100',
price: types_1.Big(100),
size: types_1.Big(5),
side: 'buy'
}
]
}
],
asks: [
{
price: types_1.Big(110),
totalSize: types_1.Big(3),
orders: [
{
id: '110',
price: types_1.Big(110),
size: types_1.Big(3),
side: 'sell'
}
]
},
{
price: types_1.Big(115),
totalSize: types_1.Big(2),
orders: [
{
id: '115',
price: types_1.Big(115),
size: types_1.Big(2),
side: 'sell'
}
]
},
{
price: types_1.Big(200),
totalSize: types_1.Big(1),
orders: [
{
id: '200',
price: types_1.Big(200),
size: types_1.Big(1),
side: 'sell'
}
]
}
]
};
before(() => {
simple.mock(ExchangeFeed_1.hooks, 'WebSocket').callFn(() => mockSocket);
gdaxFeed = new GDAXFeed_1.GDAXFeed({
logger: Logger_1.NullLogger,
apiUrl: '',
auth: null,
wsUrl: GDAXFeed_1.GDAX_WS_FEED,
channels: null
});
gdaxFeed.queueing['BTC-EUR'] = false;
book = new LiveOrderbook_1.LiveOrderbook({
logger: Logger_1.NullLogger,
strictMode: true,
product: 'BTC/EUR'
});
gdaxFeed.pipe(book);
});
it('produces a matching live GDAX book', (done) => {
book.once('LiveOrderbook.snapshot', () => {
snapshot = true;
});
let snapshot = false;
mockSocket.emit('open');
for (const message of messages) {
mockSocket.emit('message', JSON.stringify(message));
}
setImmediate(() => {
assert.equal(book.sequence, 5, 'Did not process all relevant messages');
assert.ok(snapshot, 'Did not receive snapshot');
const expectedBook = new BookBuilder_1.BookBuilder(Logger_1.NullLogger);
expectedBook.fromState(expectedState);
assert.deepEqual(book.state(), expectedBook.state());
done();
});
});
});