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

Gdax adding user channel #160

Merged
merged 7 commits into from
Oct 8, 2018

Conversation

bryantharris
Copy link
Contributor

I noticed gdax only was accessing public information (tickers, trades) and wasn't in anyway retrieving user information (such as fills or order changes).

Receiving real time fill information is one of the most compelling reasons to use streaming.

The changes I've made are 100% backwards compatible. However, if you include user credentials when connecting to the stream, the stream will now connect to the 'user' channel and will receive fill information.

This means that some Trade object will now be UserTrade objects, which includes info tying the trade back to the original Order. UserTrade extends Trade so this is backwards compatible, however now you can detect these UserTrade objects and use the information to track when trades execute.

Also, the implementation approach of the streaming for GDAX (Possibly others haven't looked) doesn't mirror the 2 level implementation approach used in the XChange project.

In XChange There's a level 1 API which 100% mirrors the raw exposed API of the exchange. Then there's a level 2 API, which is implemented in terms of calls to the Level 1 API to expose a common/universal interface.

I think xchange-stream could benefit from a similar implementation approach. GDAX supports events for live updates to a user's Orders as they progress through to actually trading. If there's no generic call back for receiving Order changes, it would be nice for all GDAX low level events to have raw callbacks to allow you to connect in to all the capabilities of the raw API, which may not have an equivalent at the generic level.

The current implementation just supports a level 2 (generic) API directly off the messages coming off the websocket.

If there's interest, I can start morphing the implementation to be a two level approach, exposing 100% of GDAXs streaming API in a Level 1 (Raw) API, and maintain the existing Level 2 (generic) API.

I think overall, this change applied to all the subproject would improve the flexibility and usability of the API.

@derDeidra
Copy link

+1, would like to see gdax fill support

@dozd dozd self-requested a review May 10, 2018 13:01
@dozd dozd self-assigned this May 10, 2018
dozd
dozd previously requested changes May 10, 2018

private WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler = null;

public GDAXStreamingService(String apiUrl) {
public GDAXStreamingService(String apiUrl, GDAXStreamingExchange exchange) {
Copy link
Member

Choose a reason for hiding this comment

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

I am not a big fan of cyclic class dependencies like this. There should contract "parent" <- "child" in this scenario. So if StreamingService needs something in particular, give it to it. But don't give it main object that holds everything - that leads to spaghetti.

@@ -25,11 +30,13 @@
private static final String SHARE_CHANNEL_NAME = "ALL";
private final Map<String, Observable<JsonNode>> subscriptions = new HashMap<>();
private ProductSubscription product = null;
GDAXStreamingExchange exchange;
Copy link
Member

Choose a reason for hiding this comment

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

Use private final

GDAXWebsocketAuthData authData = null;
if ( exchangeSpec.getApiKey() != null ) {
GDAXAccountServiceRaw rawAccountService = (GDAXAccountServiceRaw) exchange.getAccountService();
authData = rawAccountService.getWebsocketAuthData();
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer not to include getWebsocketAuthData call into this particual method as well xchange-stream. User can call this by her own. Just let the user to pass websocet auth data to the xchange-stream (probably in connect() method?). By this you will have easier time with solving cyclic dependency issue as well as more flexible usage of the library.

@dozd
Copy link
Member

dozd commented May 10, 2018

Hello, just two minor findings with the PR. Thanks for that. I think that two level approach is the better way to go as you proposed :)

@bryantharris
Copy link
Contributor Author

Okay, I'll take a look, sorry I got the sense you weren't going to look at the pull request so I stopped checking so frequently. I'll make the proposed changes and then perhaps will consider the two layer approach that the base project XChange uses. If you like it, shouldn't be too much work to one by one refactor the other projects.

It gives the maximum flexibility of a simple generic API as well as 100% exchange support.

@bryantharris
Copy link
Contributor Author

Changes made based off of feedback.

Copy link
Contributor Author

@bryantharris bryantharris left a comment

Choose a reason for hiding this comment

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

Changes made.

Copy link
Contributor Author

@bryantharris bryantharris left a comment

Choose a reason for hiding this comment

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

These changes have been made for several months now.

@badgerwithagun
Copy link
Collaborator

develop merges into this branch fine, but I don't have permission to do so.

@dozd, can you please confirm that the changes @bryantharris made match what you were expecting? The changes look OK from here.

I'm just about to run some basic testing and if it looks good, I think should be merged.

ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(subscribeMessage);
}

@Override
public String getUnsubscribeMessage(String channelName) throws IOException {
GDAXWebSocketSubscriptionMessage subscribeMessage =
new GDAXWebSocketSubscriptionMessage(UNSUBSCRIBE, new String[]{"level2", "matches", "ticker"});
new GDAXWebSocketSubscriptionMessage(UNSUBSCRIBE, new String[]{"level2", "matches", "ticker"}, authData);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this unsubscribe message also include "user" if authData != null? Not seen any side-effects myself in use but it seems symmetrical.

Trades adaptedTrades = adaptTrades(new GDAXTrade[]{s.toGDAXTrade()}, currencyPair);
Trades adaptedTrades = null;
if ( s.getUserId() != null )
adaptedTrades = adaptTradeHistory(new GDAXFill[]{s.toGDAXFill()});
Copy link
Collaborator

@badgerwithagun badgerwithagun Sep 17, 2018

Choose a reason for hiding this comment

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

I'm working on something similar for Binance right now, and this bit gave me pause. Are you getting duplicated trades appearing where you get both the Trade from the matches channel and the UserTrade from the user channel?

I certainly do with Binance and it itched. The only way I could think of to resolve the issue looks something like this:

// To avoid duplication of user and public trades, we delay the public trades by one second
// and skip any trade ids which have recently been streamed as user trades. This isn't
// perfect, of course...
Cache<String, ? super Trade> recentTrades = CacheBuilder.newBuilder().maximumSize(50).build();
return publicTrades
  .concatMap(t -> Observable.just(t).delay(1, TimeUnit.SECONDS))
  .skipWhile(t -> recentTrades.asMap().containsKey(t.getId()))
  .mergeWith(
    getUserTrades()
      .filter(t -> t.getCurrencyPair().equals(currencyPair))
      .doOnNext(t -> recentTrades.put(t.getId(), t))
  );

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 the code replaces the Trade object with a UserTrade object so there wouldn't be a duplicate.

Honestly in my usage (external to this project) I'm just looking for the fills coming in (UserTrade) so it never hurt my bot if there was or wasn't a Trade reported via the regular trade feed.

I suppose if the record came in via the trade feed with no user information there would be a duplicate.

@badgerwithagun
Copy link
Collaborator

Been working with this a lot and have found something else: if the socket disconnects, the reconnection attempt uses an out-of-date timestamp for the resubscribe:

INFO  [2018-09-24 09:16:05,320] info.bitrich.xchangestream.gdax.GDAXStreamingService: Reopening websocket because it was closed by the host
INFO  [2018-09-24 09:16:07,524] info.bitrich.xchangestream.gdax.GDAXStreamingService: Connecting to wss://ws-feed-public.sandbox.pro.coinbase.com:-1
INFO  [2018-09-24 09:16:07,525] info.bitrich.xchangestream.gdax.GDAXStreamingService: Registering GDAXWebSocketClientHandler
INFO  [2018-09-24 09:16:08,051] info.bitrich.xchangestream.service.netty.WebSocketClientHandler: WebSocket Client connected!
WARN  [2018-09-24 09:16:08,051] info.bitrich.xchangestream.gdax.GDAXStreamingService: Resubscribing channels
DEBUG [2018-09-24 09:16:08,051] info.bitrich.xchangestream.gdax.GDAXStreamingService: Sending message: {"type":"subscribe","channels":[{"name":"ticker","product_ids":["BTC-USD"]}],"signature":"REDACTED","key":"REDACTED","passphrase":"REDACTED","timestamp":"REDACTED"}
DEBUG [2018-09-24 09:16:08,185] info.bitrich.xchangestream.service.netty.JsonNettyStreamingService: Received message: {"type":"error","message":"Sorry, you could not be authenticated: Bad Request","reason":"request timestamp expired"}

Seeing if I can fix this now.

@badgerwithagun
Copy link
Collaborator

Yup. Fixed. @bryantharris: I can't merge my changes into your branch, but if you could grab them from https://github.com/badgerwithagun/xchange-stream.git and see if you're happy that'd be great. I'm not worried about the UserTrade/Trade duplication for now; may fix that with another PR.

@bryantharris
Copy link
Contributor Author

@badgerwithagun Are you asking me to review the changes on your repo or to merge them in with mine? The bug you found sounds like one that probably existed in the main code as my changes were unrelated right? Happy to review it or to try to merge it.

Though the real question is, is there any interest in pulling in my changes at all. I mean I think I submitted these like 6 months ago. I've been using them in a bot during that time and I know they work, but it just doesn't feel like there's an appetite for user specific events in this project based on the utter lack of interest in folding in what are ultimately some pretty minor changes.

@badgerwithagun
Copy link
Collaborator

@bryantharris: I'm in the same position as you. I'm not able to merge PRs, just trying to help. Unless @dozd gives me merge permission, all I can do is to test and review.

I appreciate that it's frustrating.

I have a slew of PRs of my own that I'm waiting to get merged and am actively using yours daily, so would very much like it merged. It's getting harder and harder to keep my fork synchronised with the main repo.

@badgerwithagun
Copy link
Collaborator

@bryantharris - just realised I didn't answer your question. No, the bug was introduced by your code (at least here). Auth was never previously needed by the websocket handler code. Your code re-uses the same authData for all reconnections even after it has expired. My change just simply makes it derive new authData for each reconnection. Should make your bot more stable.

Don't know if you're following #224 but there are quite a few people trying to get this stuff moving. In the meantime there are at least three other people that really want your PR - it's a great solution - so it'd be awesome if you could stick with it for a bit longer.

badgerwithagun added a commit to badgerwithagun/xchange-stream that referenced this pull request Oct 6, 2018
Copy link
Collaborator

@badgerwithagun badgerwithagun left a comment

Choose a reason for hiding this comment

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

There is an issue with reconnection as noted above, but I will submit that as another PR. Approved.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants