Skip to content
This repository has been archived by the owner on Feb 15, 2022. It is now read-only.

Add basic websocket support for GDAX #970

Merged
merged 4 commits into from
Dec 28, 2017

Conversation

krystophv
Copy link
Contributor

This PR makes a first step into reading data from the websocket stream for GDAX instead of continually polling their REST APIs. It doesn't feed the websocket reports directly into the system, but rather sets up a couple of objects/arrays that will act as a buffer or cache for the incoming websocket data and then when the exchange level API functions run, they're set to check the websocket cache (or websocket fed live orderbook) before making a REST call. This should initially alleviate REST calls from getTrades and getQuote. At a minimum, should also be able to write a little more processing code and set up a similar cache for getOrder in the near future.

@fuzzyTew
Copy link
Contributor

This is great! It might help people review the changes if they were squashed and rebased onto master with only relevent commit names retained (requires a git push --force to the branch to change the history).

if (!websocket_client[product_id]) {
// OrderbookSync extends WebsocketClient and subscribes to the 'full' channel, so we can use it like one
var auth = null
if (c.gdax && !c.gdax.key && c.gdax.key !== 'YOUR-API-KEY') {
Copy link
Contributor

@fuzzyTew fuzzyTew Dec 26, 2017

Choose a reason for hiding this comment

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

Typo here: should be && c.gdax.key && -- right now the logic is ignoring authentication.

I tried doing websocket authentication locally and the exchange rejected my ip, even though I'd put it in the whitelist. Others' mileage may vary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're correct!

public_client[product_id] = new Gdax.PublicClient(product_id, c.gdax.apiURI)
}
return public_client[product_id]
}

function websocketClient (product_id) {
Copy link
Contributor

@fuzzyTew fuzzyTew Dec 26, 2017

Choose a reason for hiding this comment

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

The gdax websocket api reportedly barfs if too many connections are opened at once; instead they recommend opening one connection and subscribing to multiple channels. Unfortunately, the node api doesn't support subscribing after connection yet.

I propose submitting a pull request to the gdax node project adding a feature that allows subscription after connection (assuming the api server allows such behavior) and subscribing to all products at once in the first connection in the interim. I'm interested in this feature and may begin working on that pull request if I can find the time.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to follow up, I added this pull request at coinbase/coinbase-pro-node#179 such that some day it may be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Subscribing to all the products at once in the websocket is going to be a firehose worth of information coming down, with the vast majority of it being irrelevant to the use case at hand. The OrderbookSync will be doing a lot of work to maintain all the orderbook status' for each product when it doesn't need to, and the trade cache will need to filter out any matches that aren't what we're really looking for or else it'll continually push irrelevant trades into that array. My presumption is that you're running multiple instances of zenbot against different products. In that case, even if you do subscribe to multiple products, a single instance of zenbot can only trade against a single product and they can't share information across instances so I'm not sure how it'd help that use case. The rate limit for websocket connections is 1/IP address/4 seconds. If you wait 4 seconds between starting zenbot instances, I think you'll be ok.

Copy link
Contributor

Choose a reason for hiding this comment

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

You've got a point. My hope is actually different -- I want to use zenbot to archive the orderbook data, so I plan to connect to multiple exchanges at once. But others shouldn't have to bear the processing for that. Maybe if I'm lucky my patch will be merged.

I sent you a pull request so auth works at krystophv#1 . It works with my credentials now.

package.json Outdated
@@ -30,7 +30,7 @@
"ccxt": "^1.10.171",
"express": "^4.16.2",
"forex.analytics": "mkmarek/forex.analytics#7bc278987700d4204e959af17de61495941d1a14",
"gdax": "^0.4.2",
"gdax": "coinbase/gdax-node",
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be a little more resilient to say coinbase/gdax-node#semver:^0.5.0

Copy link
Contributor Author

@krystophv krystophv Dec 26, 2017

Choose a reason for hiding this comment

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

Not a bad suggestion at all, yarn chokes on that though:
error Invalid hosted git fragment "^0.5.0".

@Fredrik81
Copy link

Fredrik81 commented Dec 27, 2017

I got an issue testing this change. I could reproduce it like this:

  1. git pull latest zenbot
  2. pull this PR
  3. create/copy in conf.js with API keys for gdax
  4. try to backfill (i tried gdax.ETH-BTC)

It will fail with message that not all keys are filled in. Nothing wrong with the the conf-file it works in live trading.
If i remove the conf.js it works to backfill.

@fuzzyTew
Copy link
Contributor

fuzzyTew commented Dec 27, 2017

@Fredrik81, I thought that issue was fixed in 22c569e 17 hours ago. When did you perform this test?

Backfill works fine for me. I haven't verified the data for correctness, though.

@fuzzyTew
Copy link
Contributor

I was reading the gdax docs and found https://docs.gdax.com/#sequence-numbers stating that some messages will be dropped or sent out of order. They say at https://docs.gdax.com/#the-code-classprettyprintmatchescode-channel that the heartbeat channel should be used to identify and resync when information is lost, using either sequence numbers or trade ids. I don't know if this is a huge issue if a trade is dropped here and there, but it's perhaps the next thing missing.

@luponata
Copy link

luponata commented Dec 27, 2017

With same steps of Fredrik81 i get:

(node:4844) DeprecationWarning: new PublicClient() no longer accepts a product ID as the first argument.
(node:4844) DeprecationWarning: getProductTrades() now requires a product ID as the first argument. Attempting to use PublicClient#productID (LTC-EUR) instead

while backfilling

same for --manual on live trade

tested now, with corrected exchange.json of 22c569e

@Fredrik81
Copy link

Hello,
Tested same steps again using a new git pull so all the latest merged PR's included.
Give same result as @luponata.
I hope your able to fix it :-)

zenbot$ ./zenbot.sh backfill gdax.ETH-BTC --days=2
node-telegram-bot-api deprecated Automatic enabling of cancellation of promises is deprecated.
In the future, you will have to enable it yourself.
See yagop/node-telegram-bot-api#319. module.js:635:30
(node:6907) DeprecationWarning: new PublicClient() no longer accepts a product ID as the first argument.
(node:6907) DeprecationWarning: getProductTrades() now requires a product ID as the first argument. Attempting to use PublicClient#productID (ETH-BTC) instead.

gdax.ETH-BTC saved 100 trades 2 days left
(node:6907) DeprecationWarning: getProductTrades() now requires a product ID as the first argument. Attempting to use PublicClient#productID (ETH-BTC) instead.
.(node:6907) DeprecationWarning: getProductTrades() now requires a product ID as the first argument. Attempting to use PublicClient#productID (ETH-BTC) instead.
websocket connection to ETH-BTC opened
.(node:6907) DeprecationWarning: getProductTrades() now requires a product ID as the first argument. Attempting to use PublicClient#productID (ETH-BTC) instead.
.(node:6907) DeprecationWarning: getProductTrades() now requires a product ID as the first argument. Attempting to use PublicClient#productID (ETH-BTC) instead.
.(node:6907) DeprecationWarning: getProductTrades() now requires a product ID as the first argument. Attempting to use PublicClient#productID (ETH-BTC) instead.

@luponata
Copy link

luponata commented Dec 27, 2017

Anyway i don't know if these tests should be done after the successful merge of coinbase/coinbase-pro-node#179

@fuzzyTew
Copy link
Contributor

@luponata @Fredrik81 I don't get those warnings. This PR updates the version of gdax-node depended on in package.json -- have you run npm install to download that updated version?

@luponata
Copy link

yes i did :/

@fuzzyTew
Copy link
Contributor

Looks like they changed the API last night with coinbase/coinbase-pro-node#178

@fuzzyTew
Copy link
Contributor

Pinned the dependency to the old API for now with krystophv#2


function publicClient (product_id) {
if (!public_client[product_id]) {
websocketClient(product_id)
public_client[product_id] = new Gdax.PublicClient(product_id, c.gdax.apiURI)

Choose a reason for hiding this comment

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

To use new API we can change: Gdax.PublicClient(product_id, c.gdax.apiURI) to Gdax.PublicClient(c.gdax.apiURI)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'll probably just pin the dependency for now (via PR from @fuzzyTew above). The idea of going to the new API does appeal to me, but as we just experienced, it's much better to have a consistent working install than to have the dependency update underneath us and potentially break things for people who might not have the expertise to diagnose such issues.

Choose a reason for hiding this comment

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

yeah it's a good change to set specific API version so stuff doesn't break uncontrolled.

cache.trades = cache.trades.slice(fromIndex)
cache.trade_ids = cache.trade_ids.slice(fromIndex)
return
}
client.getProductTrades(args, function (err, resp, body) {

Choose a reason for hiding this comment

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

Need to append product_id here change: client.getProductTrades(args, function (err, resp, body) to client.getProductTrades(opts.product_id, args, function (err, resp, body)

Copy link
Owner

Choose a reason for hiding this comment

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

@krystophv is this something you're still looking at?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@DeviaVir it's not an issue any longer since we pinned the dependency instead

Pin gdax-node dependency to version 0.5.0
@krystophv
Copy link
Contributor Author

Thanks everyone for the interest and testing. I merged @fuzzyTew's PR pinning the dependency to a tag so the dependency doesn't update with breaking changes unexpectedly in the future. I guess we'll just need to check up on the dependency periodically to see if it's got a new tag (and at that point update it). Hopefully they just start using NPM properly, but that might be a bit much to hope for :/

@fuzzyTew
Copy link
Contributor

fuzzyTew commented Dec 27, 2017

I've tried to update the code for the development changes in gdax-node at https://github.com/fuzzyTew/zenbot/tree/gdax_websocket_subscribe mostly so I can use the subscribe/unsubscribe change, but it also has changes equivalent to @Fredrik81's now. I haven't really tested it.

@Fredrik81
Copy link

It updates/download data super quick. this will be a big improvement when live.

@krystophv
Copy link
Contributor Author

Once this lands, I'll have another PR up shortly that will build out the local orders cache based on the websocket feed so we can avoid making getOrder calls too.

@Fredrik81
Copy link

I can confirm that with the last API version change the problem i faced is gone.

@luponata
Copy link

luponata commented Dec 28, 2017

solved

@krystophv
Copy link
Contributor Author

krystophv commented Dec 28, 2017

@luponata that's clearly a rate limit exceeded error. and not entirely relevant to this PR discussion. if you feel it's an issue with fuzzy's changes, then maybe making an issue on their branch would be appropriate, or perhaps asking around in the Discord. I'd recommend turning down the poll rate.

@luponata
Copy link

luponata commented Dec 28, 2017

my fault, was using a copy of conf-sample.js from another repository with too low ms on poll-trades..
instead of copying every times my api keys

@DeviaVir DeviaVir closed this Dec 28, 2017
@DeviaVir DeviaVir reopened this Dec 28, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants