Skip to content
This repository has been archived by the owner on Jan 20, 2020. It is now read-only.

Commit

Permalink
lib/clients/websocket: use utility function for determining product IDs;
Browse files Browse the repository at this point in the history
lib/orderbook_sync: accept multiple product IDs and maintain multiple product books;
lib/utilities: file for common utilities such as determining product IDs;
README.md: document changes to lib/orderbook_sync;
CHANGELOG.md: document chnages to lib/orderbook_sync;
  • Loading branch information
cilphex authored and fb55 committed May 23, 2017
1 parent c8e3f44 commit 20a1322
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 59 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Change Log

## Unreleased

### Changed

- Modify `lib/orderbook_sync` to be initialized with an array of product IDs instead of one product ID, and have it keep track of multiple books for these products in `orderbookSync.books`. `orderbookSync.book` is removed.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ publicClient.getTime(callback);
The [private exchange API
endpoints](https://docs.gdax.com/#private) require you to
authenticate with an API key. You can create a new API key [in your exchange
account's settings](https://gdax.com/settings). You can also specify the
account's settings](https://gdax.com/settings). You can also specify the
API uri.

```javascript
Expand Down Expand Up @@ -342,8 +342,8 @@ The orderbook has the following methods:

```javascript
var Gdax = require('gdax');
var orderbookSync = new Gdax.OrderbookSync();
console.log(orderbookSync.book.state());
var orderbookSync = new Gdax.OrderbookSync(['BTC-USD', 'ETH-USD']);
console.log(orderbookSync.books['ETH-USD'].state());

This comment has been minimized.

Copy link
@amazingandyyy

amazingandyyy Jun 19, 2017

this is for verions 0.4.3, but the public version on npm is currently 0.4.2, I follow this docs and spent(wasted) a huge amount of time on this incorrect documentation.

```

## Testing
Expand Down
19 changes: 3 additions & 16 deletions lib/clients/websocket.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var EventEmitter = require('events').EventEmitter;
var Websocket = require('ws');
var Utils = require('../utilities.js');
var util = require('util');
var _ = {assign: require('lodash.assign')};
var signRequest = require('../../lib/request_signer').signRequest;
Expand All @@ -12,7 +13,7 @@ var signRequest = require('../../lib/request_signer').signRequest;
*/
var WebsocketClient = function(productIDs, websocketURI, auth) {
var self = this;
self.productIDs = self._determineProductIDs(productIDs);
self.productIDs = Utils.determineProductIDs(productIDs);

This comment has been minimized.

Copy link
@amazingandyyy

amazingandyyy Jun 19, 2017

good improvement :) thanks

self.websocketURI = websocketURI || 'wss://ws-feed.gdax.com';
if (auth && !(auth.secret && auth.key && auth.passphrase)) {
throw new Error('Invalid or incomplete authentication credentials. You should either provide all of the secret, key and passphrase fields, or leave auth null');
Expand Down Expand Up @@ -105,20 +106,6 @@ _.assign(WebsocketClient.prototype, new function() {

self.emit('error', err);
};

prototype._determineProductIDs = function(productIDs) {
if (!productIDs || !productIDs.length) {
return ['BTC-USD'];
}

if (Array.isArray(productIDs)) {
return productIDs;
}

// If we got this far, it means it's a string.
// Return an array for backwards compatibility.
return [productIDs];
}

});

module.exports = exports = WebsocketClient;
76 changes: 45 additions & 31 deletions lib/orderbook_sync.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
var WebsocketClient = require('./clients/websocket.js');
var PublicClient = require('./clients/public.js');
var Orderbook = require('./orderbook.js');
var Utils = require('./utilities.js');
var util = require('util');
var _ = {
forEach: require('lodash.foreach'),
assign: require('lodash.assign'),
};

// Orderbook syncing
var OrderbookSync = function(productID, apiURI, websocketURI, authenticatedClient) {
var OrderbookSync = function(productIDs, apiURI, websocketURI, authenticatedClient) {
var self = this;

self.productID = productID || 'BTC-USD';
self.productIDs = Utils.determineProductIDs(productIDs);
self.apiURI = apiURI || 'https://api.gdax.com';
self.websocketURI = websocketURI || 'wss://ws-feed.gdax.com';
self.authenticatedClient = authenticatedClient;

self._queue = [];
self._sequence = -1;
self._queues = {}; // []
self._sequences = {}; // -1
self._public_clients = {};
self.books = {}

WebsocketClient.call(self, self.productID, self.websocketURI);
self.loadOrderbook();
_.forEach(self.productIDs, function(productID) {
self._queues[productID] = [];
self._sequences[productID] = -1;
self.books[productID] = new Orderbook();
self.loadOrderbook(productID);
});

WebsocketClient.call(self, self.productIDs, self.websocketURI);
};

util.inherits(OrderbookSync, WebsocketClient);
Expand All @@ -33,29 +42,29 @@ _.assign(OrderbookSync.prototype, new function() {
data = JSON.parse(data);
self.emit('message', data);

if (self._sequence === -1) {
var product_id = data.product_id;

if (self._sequences[product_id] === -1) {
// Orderbook snapshot not loaded yet
self._queue.push(data);
self._queues[product_id].push(data);
} else {
self.processMessage(data);
}
};

prototype.loadOrderbook = function() {
prototype.loadOrderbook = function(productID) {
var self = this;
var bookLevel = 3;
var args = { 'level': bookLevel };

self.book = new Orderbook();

if (self.authenticatedClient) {
self.authenticatedClient.getProductOrderBook(args, self.productID, cb);
self.authenticatedClient.getProductOrderBook(args, productID, cb);
}
else {
if (!self.publicClient) {
self.publicClient = new PublicClient(self.productID, self.apiURI);
if (!self._public_clients[productID]) {
self._public_clients[productID] = new PublicClient(productID, self.apiURI);
}
self.publicClient.getProductOrderBook(args, cb);
self._public_clients[productID].getProductOrderBook(args, cb);
}

function cb(err, response, body) {
Expand All @@ -67,57 +76,62 @@ _.assign(OrderbookSync.prototype, new function() {
throw 'Failed to load orderbook: ' + response.statusCode;
}

if (!self.books[productID]) {
return;
}

var data = JSON.parse(response.body);
self.book.state(data);
self.books[productID].state(data);

self._sequence = data.sequence;
_.forEach(self._queue, self.processMessage.bind(self));
self._queue = [];
self._sequences[productID] = data.sequence;
_.forEach(self._queues[productID], self.processMessage.bind(self));
self._queues[productID] = [];
};
};

prototype.processMessage = function(data) {
var self = this;
var product_id = data.product_id;

if (self._sequence == -1) {
if (self._sequences[product_id] == -1) {
// Resync is in process
return;
}
if (data.sequence <= self._sequence) {
if (data.sequence <= self._sequences[product_id]) {
// Skip this one, since it was already processed
return;
}

if (data.sequence != self._sequence + 1) {
if (data.sequence != self._sequences[product_id] + 1) {
// Dropped a message, start a resync process
self._queue = [];
self._sequence = -1;
self._queues[product_id] = [];
self._sequences[product_id] = -1;

self.loadOrderbook();
self.loadOrderbook(product_id);
return;
}

self._sequence = data.sequence;
self._sequences[product_id] = data.sequence;
var book = self.books[product_id];

switch (data.type) {
case 'open':
self.book.add(data);
book.add(data);
break;

case 'done':
self.book.remove(data.order_id);
book.remove(data.order_id);
break;

case 'match':
self.book.match(data);
book.match(data);
break;

case 'change':
self.book.change(data);
book.change(data);
break;
}
};

});

module.exports = exports = OrderbookSync;
17 changes: 17 additions & 0 deletions lib/utilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function determineProductIDs(productIDs) {
if (!productIDs || !productIDs.length) {
return ['BTC-USD'];
}

if (Array.isArray(productIDs)) {
return productIDs;
}

// If we got this far, it means it's a string.
// Return an array for backwards compatibility.
return [productIDs];
}

module.exports = {
determineProductIDs,
};
14 changes: 8 additions & 6 deletions tests/authenticated.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ var EXCHANGE_API_URL = 'https://api.gdax.com';

var authClient = new Gdax.AuthenticatedClient(key, secret, passphrase);

suite('AuthenticatedClient');

test('._getSignature', function() {
var method = 'PUT';
var relativeURI = '/orders';
var opts = {
method : 'PUT',
uri : 'https://api.gdax.com/orders'
}

var sig = authClient._getSignature(method, relativeURI, opts)

assert.equal(sig['CB-ACCESS-KEY'], key);
Expand All @@ -34,7 +36,7 @@ test('get account', function(done) {
"balance": "1.100",
"holds": "0.100",
"available": "1.00",
"currency": "USD"
"currency": "USD"
}

nock(EXCHANGE_API_URL)
Expand All @@ -56,7 +58,7 @@ test('get accounts', function(done) {
"balance": "1.100",
"holds": "0.100",
"available": "1.00",
"currency": "USD"
"currency": "USD"
}]

nock(EXCHANGE_API_URL)
Expand Down Expand Up @@ -190,7 +192,7 @@ test('get product orderbook', function(done) {
assert(data);
done();
});
})
})

test('cancel all orders', function(done) {
// nock three requests to delete /orders
Expand Down Expand Up @@ -404,7 +406,7 @@ test('close position', function(done) {
test('deposit', function(done) {
var transfer = {
"amount" : 10480,
"coinbase_account_id": 'test-id'
"coinbase_account_id": 'test-id'
}

expectedTransfer = transfer;
Expand All @@ -425,7 +427,7 @@ test('deposit', function(done) {
test('withdraw', function(done) {
var transfer = {
"amount" : 10480,
"coinbase_account_id": 'test-id'
"coinbase_account_id": 'test-id'
}

expectedTransfer = transfer;
Expand Down
2 changes: 2 additions & 0 deletions tests/orderbook.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ var checkState = function(state, exp) {
assert.deepEqual(JSON.parse(JSON.stringify(state)), exp);
};

suite('Orderbook');

test('add new orders', function() {
var state = {
bids: [
Expand Down
33 changes: 33 additions & 0 deletions tests/orderbook_sync_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,37 @@ describe('OrderbookSync', function() {
socket.send(JSON.stringify({test: true}));
});
});

test('builds specified books', function(done) {
nock(EXCHANGE_API_URL)
.get('/products/BTC-USD/book?level=3')
.times(2)
.reply(200, {
asks: [],
bids: []
});

nock(EXCHANGE_API_URL)
.get('/products/ETH-USD/book?level=3')
.times(2)
.reply(200, {
asks: [],
bids: []
});

var server = testserver(++port, function() {
var orderbookSync = new Gdax.OrderbookSync(['BTC-USD', 'ETH-USD'], EXCHANGE_API_URL, 'ws://localhost:' + port);
var btc_usd_state = orderbookSync.books['BTC-USD'].state();
var eth_usd_state = orderbookSync.books['ETH-USD'].state();

assert.deepEqual(btc_usd_state, { asks: [], bids: [] });
assert.deepEqual(eth_usd_state, { asks: [], bids: [] });
assert.equal(orderbookSync.books['ETH-BTC'], undefined);
});

server.on('connection', function(socket) {
server.close();
done();
});
});
});
8 changes: 5 additions & 3 deletions tests/public_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ var publicClient = new Gdax.PublicClient();

var EXCHANGE_API_URL = 'https://api.gdax.com';

suite('PublicClient');

test('get product trades', function(done) {
var expectedResponse = [{
"time": "2014-11-07T22:19:28.578544Z",
Expand Down Expand Up @@ -35,7 +37,7 @@ test('get product trades', function(done) {
});

test('public client should return values', function(done) {

nock(EXCHANGE_API_URL)
.get('/products/BTC-USD/ticker')
.reply(200, {
Expand All @@ -50,8 +52,8 @@ test('public client should return values', function(done) {
assert.equal(data.trade_id, 'test-id');
assert(data.price, '9.00');
assert(data.size, '5');
nock.cleanAll();

nock.cleanAll();
done();
});
});
Expand Down

0 comments on commit 20a1322

Please sign in to comment.