diff --git a/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingExchange.java b/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingExchange.java index 165e62c04..dc6fe7b33 100644 --- a/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingExchange.java +++ b/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingExchange.java @@ -9,63 +9,64 @@ import org.knowm.xchange.bankera.BankeraExchange; import org.knowm.xchange.bankera.service.BankeraMarketDataService; - public class BankeraStreamingExchange extends BankeraExchange implements StreamingExchange { - private static final String WS_URI = "wss://api-exchange.bankera.com/ws"; - private BankeraStreamingService streamingService; + private static final String WS_URI = "wss://api-exchange.bankera.com/ws"; + private BankeraStreamingService streamingService; private BankeraStreamingMarketDataService streamingMarketDataService; - public BankeraStreamingExchange() { + public BankeraStreamingExchange() { this.streamingService = new BankeraStreamingService(WS_URI); } - @Override - protected void initServices() { - super.initServices(); - streamingMarketDataService = new BankeraStreamingMarketDataService( - streamingService, (BankeraMarketDataService) marketDataService); - } - - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } + @Override + protected void initServices() { + super.initServices(); + streamingMarketDataService = + new BankeraStreamingMarketDataService( + streamingService, (BankeraMarketDataService) marketDataService); + } - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } - return spec; - } + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } + return spec; + } - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingMarketDataService.java b/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingMarketDataService.java index bc70cdcff..fad983728 100644 --- a/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingMarketDataService.java +++ b/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingMarketDataService.java @@ -2,6 +2,9 @@ import info.bitrich.xchangestream.core.StreamingMarketDataService; import io.reactivex.Observable; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.*; import org.knowm.xchange.bankera.BankeraAdapters; import org.knowm.xchange.bankera.dto.BankeraException; import org.knowm.xchange.bankera.dto.marketdata.*; @@ -13,17 +16,13 @@ import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.exceptions.NotAvailableFromExchangeException; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.*; - - public class BankeraStreamingMarketDataService implements StreamingMarketDataService { private final BankeraStreamingService service; private final BankeraMarketDataService marketDataService; - public BankeraStreamingMarketDataService(BankeraStreamingService service, BankeraMarketDataService marketDataService) { + public BankeraStreamingMarketDataService( + BankeraStreamingService service, BankeraMarketDataService marketDataService) { this.service = service; this.marketDataService = marketDataService; } @@ -31,18 +30,29 @@ public BankeraStreamingMarketDataService(BankeraStreamingService service, Banker @Override public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { BankeraMarket market = getMarketInfo(currencyPair); - return service.subscribeChannel("market-orderbook", market.getId()) - .map(o -> { - List listBids = new ArrayList<>(); - List listAsks = new ArrayList<>(); - o.get("data").get("bids") - .forEach(b -> listBids.add(new BankeraOrderBook.OrderBookOrder( - 0, b.get("price").asText(), b.get("amount").asText()))); - o.get("data").get("asks") - .forEach(b -> listAsks.add(new BankeraOrderBook.OrderBookOrder( - 0, b.get("price").asText(), b.get("amount").asText()))); - return BankeraAdapters.adaptOrderBook(new BankeraOrderBook(listBids, listAsks), currencyPair); - }); + return service + .subscribeChannel("market-orderbook", market.getId()) + .map( + o -> { + List listBids = new ArrayList<>(); + List listAsks = new ArrayList<>(); + o.get("data") + .get("bids") + .forEach( + b -> + listBids.add( + new BankeraOrderBook.OrderBookOrder( + 0, b.get("price").asText(), b.get("amount").asText()))); + o.get("data") + .get("asks") + .forEach( + b -> + listAsks.add( + new BankeraOrderBook.OrderBookOrder( + 0, b.get("price").asText(), b.get("amount").asText()))); + return BankeraAdapters.adaptOrderBook( + new BankeraOrderBook(listBids, listAsks), currencyPair); + }); } @Override @@ -50,28 +60,33 @@ public Observable getTicker(CurrencyPair currencyPair, Object... args) { throw new NotAvailableFromExchangeException(); } - @Override public Observable getTrades(CurrencyPair currencyPair, Object... args) { BankeraMarket market = getMarketInfo(currencyPair); - return service.subscribeChannel("market-trade", market.getId()) - .map(t -> new Trade.Builder() - .currencyPair(currencyPair) - .id("-1") - .price(new BigDecimal(t.get("data").get("price").asText())) - .originalAmount(new BigDecimal(t.get("data").get("amount").asText())) - .timestamp(new Date(t.get("data").get("time").asLong())) - .type(t.get("data").get("side").asText().equals("SELL") ? Order.OrderType.ASK : Order.OrderType.BID) - .build() - ); + return service + .subscribeChannel("market-trade", market.getId()) + .map( + t -> + new Trade.Builder() + .currencyPair(currencyPair) + .id("-1") + .price(new BigDecimal(t.get("data").get("price").asText())) + .originalAmount(new BigDecimal(t.get("data").get("amount").asText())) + .timestamp(new Date(t.get("data").get("time").asLong())) + .type( + t.get("data").get("side").asText().equals("SELL") + ? Order.OrderType.ASK + : Order.OrderType.BID) + .build()); } private BankeraMarket getMarketInfo(CurrencyPair currencyPair) { try { BankeraMarketInfo info = this.marketDataService.getMarketInfo(); - Optional market = info.getMarkets().stream().filter( - m -> m.getName().equals(currencyPair.toString().replace("/", "-")) - ).findFirst(); + Optional market = + info.getMarkets().stream() + .filter(m -> m.getName().equals(currencyPair.toString().replace("/", "-"))) + .findFirst(); if (market.isPresent()) { return market.get(); diff --git a/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingService.java b/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingService.java index 605597ee2..638eb0eee 100644 --- a/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingService.java +++ b/xchange-stream-bankera/src/main/java/info/bitrich/xchangestream/bankera/BankeraStreamingService.java @@ -4,36 +4,34 @@ import info.bitrich.xchangestream.bankera.dto.BankeraWebSocketSubscriptionMessage; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; - import java.io.IOException; public class BankeraStreamingService extends JsonNettyStreamingService { - public BankeraStreamingService(String uri) { - super(uri, Integer.MAX_VALUE); - } + public BankeraStreamingService(String uri) { + super(uri, Integer.MAX_VALUE); + } - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { return message.get("type").asText(); - } + } - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { if (args.length != 1) throw new IOException("SubscribeMessage: Insufficient arguments"); BankeraWebSocketSubscriptionMessage subscribeMessage = new BankeraWebSocketSubscriptionMessage(String.valueOf(args[0])); return objectMapper.writeValueAsString(subscribeMessage); - } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - return null; - } + } - @Override - protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { - return null; - } + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + return null; + } + @Override + protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { + return null; + } } diff --git a/xchange-stream-bankera/src/test/java/info/bitrich/xchangestream/bankera/BankeraManualExample.java b/xchange-stream-bankera/src/test/java/info/bitrich/xchangestream/bankera/BankeraManualExample.java index 8fdd70313..4a3d4cf5e 100644 --- a/xchange-stream-bankera/src/test/java/info/bitrich/xchangestream/bankera/BankeraManualExample.java +++ b/xchange-stream-bankera/src/test/java/info/bitrich/xchangestream/bankera/BankeraManualExample.java @@ -7,30 +7,33 @@ import org.slf4j.LoggerFactory; public class BankeraManualExample { - private static final Logger LOGGER = LoggerFactory.getLogger(BankeraManualExample.class); - - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE - .createExchange(BankeraStreamingExchange.class.getName()); - - exchange.connect().blockingAwait(); - exchange.getStreamingMarketDataService() - .getOrderBook(CurrencyPair.ETH_BTC) - .subscribe(orderBook -> LOGGER.debug("ORDERBOOK: {}", orderBook.toString()), - throwable -> LOGGER.error("ERROR in getting order book: ", throwable)); - - exchange.getStreamingMarketDataService() - .getTrades(CurrencyPair.ETH_BTC) - .subscribe(trade ->LOGGER.debug("TRADES: {}", trade.toString()), - throwable -> LOGGER.error("ERROR in getting trade ", throwable)); - - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - exchange.disconnect().subscribe(() -> LOGGER.info("Disconnected")); - + private static final Logger LOGGER = LoggerFactory.getLogger(BankeraManualExample.class); + + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(BankeraStreamingExchange.class.getName()); + + exchange.connect().blockingAwait(); + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.ETH_BTC) + .subscribe( + orderBook -> LOGGER.debug("ORDERBOOK: {}", orderBook.toString()), + throwable -> LOGGER.error("ERROR in getting order book: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.ETH_BTC) + .subscribe( + trade -> LOGGER.debug("TRADES: {}", trade.toString()), + throwable -> LOGGER.error("ERROR in getting trade ", throwable)); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + + exchange.disconnect().subscribe(() -> LOGGER.info("Disconnected")); + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingAccountService.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingAccountService.java index 2003882e9..3b52934f4 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingAccountService.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingAccountService.java @@ -2,96 +2,99 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - import info.bitrich.xchangestream.binance.dto.BaseBinanceWebSocketTransaction; import info.bitrich.xchangestream.binance.dto.BinanceWebsocketBalance; import info.bitrich.xchangestream.binance.dto.OutboundAccountInfoBinanceWebsocketTransaction; import info.bitrich.xchangestream.core.StreamingAccountService; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; - import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.subjects.BehaviorSubject; import io.reactivex.subjects.Subject; - +import java.util.List; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.exceptions.ExchangeException; import org.knowm.xchange.exceptions.ExchangeSecurityException; -import java.util.List; - -public class BinanceStreamingAccountService implements StreamingAccountService { - - private final BehaviorSubject accountInfoLast = BehaviorSubject.create(); - private final Subject accountInfoPublisher = accountInfoLast.toSerialized(); - - private volatile Disposable accountInfo; - private volatile BinanceUserDataStreamingService binanceUserDataStreamingService; - - private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - public BinanceStreamingAccountService(BinanceUserDataStreamingService binanceUserDataStreamingService) { - this.binanceUserDataStreamingService = binanceUserDataStreamingService; - } - - public Observable getRawAccountInfo() { - checkConnected(); - return accountInfoPublisher; +public class BinanceStreamingAccountService implements StreamingAccountService { + + private final BehaviorSubject accountInfoLast = + BehaviorSubject.create(); + private final Subject accountInfoPublisher = + accountInfoLast.toSerialized(); + + private volatile Disposable accountInfo; + private volatile BinanceUserDataStreamingService binanceUserDataStreamingService; + + private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + public BinanceStreamingAccountService( + BinanceUserDataStreamingService binanceUserDataStreamingService) { + this.binanceUserDataStreamingService = binanceUserDataStreamingService; + } + + public Observable getRawAccountInfo() { + checkConnected(); + return accountInfoPublisher; + } + + public Observable getBalanceChanges() { + checkConnected(); + return getRawAccountInfo() + .map(OutboundAccountInfoBinanceWebsocketTransaction::getBalances) + .flatMap((List balances) -> Observable.fromIterable(balances)) + .map(BinanceWebsocketBalance::toBalance); + } + + private void checkConnected() { + if (binanceUserDataStreamingService == null || !binanceUserDataStreamingService.isSocketOpen()) + throw new ExchangeSecurityException("Not authenticated"); + } + + @Override + public Observable getBalanceChanges(Currency currency, Object... args) { + return getBalanceChanges().filter(t -> t.getCurrency().equals(currency)); + } + + /** + * Registers subsriptions with the streaming service for the given products. + * + *

As we receive messages as soon as the connection is open, we need to register subscribers to + * handle these before the first messages arrive. + */ + public void openSubscriptions() { + if (binanceUserDataStreamingService != null) { + accountInfo = + binanceUserDataStreamingService + .subscribeChannel( + BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.OUTBOUND_ACCOUNT_INFO) + .map(this::accountInfo) + .filter( + m -> + accountInfoLast.getValue() == null + || accountInfoLast.getValue().getEventTime().before(m.getEventTime())) + .subscribe(accountInfoPublisher::onNext); } - - public Observable getBalanceChanges() { - checkConnected(); - return getRawAccountInfo() - .map(OutboundAccountInfoBinanceWebsocketTransaction::getBalances) - .flatMap((List balances) -> Observable.fromIterable(balances)) - .map(BinanceWebsocketBalance::toBalance); - } - - private void checkConnected() { - if (binanceUserDataStreamingService == null || !binanceUserDataStreamingService.isSocketOpen()) - throw new ExchangeSecurityException("Not authenticated"); - } - - @Override - public Observable getBalanceChanges(Currency currency, Object... args) { - return getBalanceChanges().filter(t -> t.getCurrency().equals(currency)); - } - - /** - * Registers subsriptions with the streaming service for the given products. - * - * As we receive messages as soon as the connection is open, we need to register subscribers to handle these before the - * first messages arrive. - */ - public void openSubscriptions() { - if (binanceUserDataStreamingService != null) { - accountInfo = binanceUserDataStreamingService - .subscribeChannel(BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.OUTBOUND_ACCOUNT_INFO) - .map(this::accountInfo) - .filter(m -> accountInfoLast.getValue() == null || - accountInfoLast.getValue().getEventTime().before(m.getEventTime())) - .subscribe(accountInfoPublisher::onNext); - } - } - - /** - * User data subscriptions may have to persist across multiple socket connections to different - * URLs and therefore must act in a publisher fashion so that subscribers get an uninterrupted - * stream. - */ - void setUserDataStreamingService(BinanceUserDataStreamingService binanceUserDataStreamingService) { - if (accountInfo != null && !accountInfo.isDisposed()) - accountInfo.dispose(); - this.binanceUserDataStreamingService = binanceUserDataStreamingService; - openSubscriptions(); - } - - private OutboundAccountInfoBinanceWebsocketTransaction accountInfo(JsonNode json) { - try { - return mapper.treeToValue(json, OutboundAccountInfoBinanceWebsocketTransaction.class); - } catch (Exception e) { - throw new ExchangeException("Unable to parse account info", e); - } + } + + /** + * User data subscriptions may have to persist across multiple socket connections to different + * URLs and therefore must act in a publisher fashion so that subscribers get an uninterrupted + * stream. + */ + void setUserDataStreamingService( + BinanceUserDataStreamingService binanceUserDataStreamingService) { + if (accountInfo != null && !accountInfo.isDisposed()) accountInfo.dispose(); + this.binanceUserDataStreamingService = binanceUserDataStreamingService; + openSubscriptions(); + } + + private OutboundAccountInfoBinanceWebsocketTransaction accountInfo(JsonNode json) { + try { + return mapper.treeToValue(json, OutboundAccountInfoBinanceWebsocketTransaction.class); + } catch (Exception e) { + throw new ExchangeException("Unable to parse account info", e); } -} \ No newline at end of file + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingExchange.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingExchange.java index 9f28a3450..d9c2f7018 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingExchange.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingExchange.java @@ -7,6 +7,10 @@ import info.bitrich.xchangestream.util.Events; import io.reactivex.Completable; import io.reactivex.Observable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.knowm.xchange.binance.BinanceAuthenticated; import org.knowm.xchange.binance.BinanceExchange; import org.knowm.xchange.binance.service.BinanceMarketDataService; @@ -16,190 +20,210 @@ import org.slf4j.LoggerFactory; import si.mazi.rescu.RestProxyFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - public class BinanceStreamingExchange extends BinanceExchange implements StreamingExchange { - private static final Logger LOG = LoggerFactory.getLogger(BinanceStreamingExchange.class); - private static final String API_BASE_URI = "wss://stream.binance.com:9443/"; - protected final static String USE_HIGHER_UPDATE_FREQUENCY = "Binance_Orderbook_Use_Higher_Frequency"; - - - private BinanceStreamingService streamingService; - private BinanceUserDataStreamingService userDataStreamingService; - - private BinanceStreamingMarketDataService streamingMarketDataService; - private BinanceStreamingAccountService streamingAccountService; - private BinanceStreamingTradeService streamingTradeService; - - private BinanceUserDataChannel userDataChannel; - private Runnable onApiCall; - private String orderBookUpdateFrequencyParameter = ""; - - @Override - protected void initServices() { - super.initServices(); - this.onApiCall = Events.onApiCall(exchangeSpecification); - Boolean userHigherFrequency = MoreObjects.firstNonNull( - (Boolean) exchangeSpecification.getExchangeSpecificParametersItem(USE_HIGHER_UPDATE_FREQUENCY), - Boolean.FALSE); - - if (userHigherFrequency) { - orderBookUpdateFrequencyParameter = "@100ms"; - } - } - - /** - * Binance streaming API expects connections to multiple channels to be defined at connection time. To define the channels for this - * connection pass a `ProductSubscription` in at connection time. - * - * @param args A single `ProductSubscription` to define the subscriptions required to be available during this connection. - * @return A completable which fulfils once connection is complete. - */ - @Override - public Completable connect(ProductSubscription... args) { - if (args == null || args.length == 0) { - throw new IllegalArgumentException("Subscriptions must be made at connection time"); - } - if (streamingService != null) { - throw new UnsupportedOperationException("Exchange only handles a single connection - disconnect the current connection."); - } - - ProductSubscription subscriptions = args[0]; - streamingService = createStreamingService(subscriptions); - - List completables = new ArrayList<>(); - - if (subscriptions.hasUnauthenticated()) { - completables.add(streamingService.connect()); - } - - if (subscriptions.hasAuthenticated()) { - if (exchangeSpecification.getApiKey() == null) { - throw new IllegalArgumentException("API key required for authenticated streams"); - } - - LOG.info("Connecting to authenticated web socket"); - BinanceAuthenticated binance = RestProxyFactory.createProxy( - BinanceAuthenticated.class, - getExchangeSpecification().getSslUri(), - new BaseExchangeService(this) {}.getClientConfig() - ); - userDataChannel = new BinanceUserDataChannel(binance, exchangeSpecification.getApiKey(), onApiCall); - try { - completables.add(createAndConnectUserDataService(userDataChannel.getListenKey())); - } catch (NoActiveChannelException e) { - throw new IllegalStateException("Failed to establish user data channel", e); - } - } - - streamingMarketDataService = new BinanceStreamingMarketDataService(streamingService, - (BinanceMarketDataService) marketDataService, onApiCall, orderBookUpdateFrequencyParameter); - streamingAccountService = new BinanceStreamingAccountService(userDataStreamingService); - streamingTradeService = new BinanceStreamingTradeService(userDataStreamingService); - - return Completable.concat(completables) - .doOnComplete(() -> streamingMarketDataService.openSubscriptions(subscriptions)) - .doOnComplete(() -> streamingAccountService.openSubscriptions()) - .doOnComplete(() -> streamingTradeService.openSubscriptions()); - } - - private Completable createAndConnectUserDataService(String listenKey) { - userDataStreamingService = BinanceUserDataStreamingService.create(listenKey); - return userDataStreamingService.connect().doOnComplete(() -> { - LOG.info("Connected to authenticated web socket"); - userDataChannel.onChangeListenKey(newListenKey -> { - userDataStreamingService.disconnect().doOnComplete(() -> { - createAndConnectUserDataService(newListenKey).doOnComplete(() -> { - streamingAccountService.setUserDataStreamingService(userDataStreamingService); - streamingTradeService.setUserDataStreamingService(userDataStreamingService); - }); - }); - }); - }); - } - - @Override - public Completable disconnect() { - List completables = new ArrayList<>(); - completables.add(streamingService.disconnect()); - streamingService = null; - if (userDataStreamingService != null) { - completables.add(userDataStreamingService.disconnect()); - userDataStreamingService = null; - } - if (userDataChannel != null) { - userDataChannel.close(); - userDataChannel = null; - } - streamingMarketDataService = null; - return Completable.concat(completables); - } - - @Override - public boolean isAlive() { - return streamingService!= null && streamingService.isSocketOpen(); + private static final Logger LOG = LoggerFactory.getLogger(BinanceStreamingExchange.class); + private static final String API_BASE_URI = "wss://stream.binance.com:9443/"; + protected static final String USE_HIGHER_UPDATE_FREQUENCY = + "Binance_Orderbook_Use_Higher_Frequency"; + + private BinanceStreamingService streamingService; + private BinanceUserDataStreamingService userDataStreamingService; + + private BinanceStreamingMarketDataService streamingMarketDataService; + private BinanceStreamingAccountService streamingAccountService; + private BinanceStreamingTradeService streamingTradeService; + + private BinanceUserDataChannel userDataChannel; + private Runnable onApiCall; + private String orderBookUpdateFrequencyParameter = ""; + + @Override + protected void initServices() { + super.initServices(); + this.onApiCall = Events.onApiCall(exchangeSpecification); + Boolean userHigherFrequency = + MoreObjects.firstNonNull( + (Boolean) + exchangeSpecification.getExchangeSpecificParametersItem( + USE_HIGHER_UPDATE_FREQUENCY), + Boolean.FALSE); + + if (userHigherFrequency) { + orderBookUpdateFrequencyParameter = "@100ms"; } - - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); + } + + /** + * Binance streaming API expects connections to multiple channels to be defined at connection + * time. To define the channels for this connection pass a `ProductSubscription` in at connection + * time. + * + * @param args A single `ProductSubscription` to define the subscriptions required to be available + * during this connection. + * @return A completable which fulfils once connection is complete. + */ + @Override + public Completable connect(ProductSubscription... args) { + if (args == null || args.length == 0) { + throw new IllegalArgumentException("Subscriptions must be made at connection time"); } - - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); + if (streamingService != null) { + throw new UnsupportedOperationException( + "Exchange only handles a single connection - disconnect the current connection."); } - @Override - public BinanceStreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } + ProductSubscription subscriptions = args[0]; + streamingService = createStreamingService(subscriptions); - @Override - public BinanceStreamingAccountService getStreamingAccountService() { - return streamingAccountService; - } + List completables = new ArrayList<>(); - @Override - public BinanceStreamingTradeService getStreamingTradeService() { - return streamingTradeService; + if (subscriptions.hasUnauthenticated()) { + completables.add(streamingService.connect()); } - private BinanceStreamingService createStreamingService(ProductSubscription subscription) { - String path = API_BASE_URI + "stream?streams=" + buildSubscriptionStreams(subscription); - return new BinanceStreamingService(path, subscription); + if (subscriptions.hasAuthenticated()) { + if (exchangeSpecification.getApiKey() == null) { + throw new IllegalArgumentException("API key required for authenticated streams"); + } + + LOG.info("Connecting to authenticated web socket"); + BinanceAuthenticated binance = + RestProxyFactory.createProxy( + BinanceAuthenticated.class, + getExchangeSpecification().getSslUri(), + new BaseExchangeService(this) {}.getClientConfig()); + userDataChannel = + new BinanceUserDataChannel(binance, exchangeSpecification.getApiKey(), onApiCall); + try { + completables.add(createAndConnectUserDataService(userDataChannel.getListenKey())); + } catch (NoActiveChannelException e) { + throw new IllegalStateException("Failed to establish user data channel", e); + } } - public String buildSubscriptionStreams(ProductSubscription subscription) { - return Stream.of(buildSubscriptionStrings(subscription.getTicker(), "ticker"), - buildSubscriptionStrings(subscription.getOrderBook(), "depth"), - buildSubscriptionStrings(subscription.getTrades(), "trade")) - .filter(s -> !s.isEmpty()) - .collect(Collectors.joining("/")); + streamingMarketDataService = + new BinanceStreamingMarketDataService( + streamingService, + (BinanceMarketDataService) marketDataService, + onApiCall, + orderBookUpdateFrequencyParameter); + streamingAccountService = new BinanceStreamingAccountService(userDataStreamingService); + streamingTradeService = new BinanceStreamingTradeService(userDataStreamingService); + + return Completable.concat(completables) + .doOnComplete(() -> streamingMarketDataService.openSubscriptions(subscriptions)) + .doOnComplete(() -> streamingAccountService.openSubscriptions()) + .doOnComplete(() -> streamingTradeService.openSubscriptions()); + } + + private Completable createAndConnectUserDataService(String listenKey) { + userDataStreamingService = BinanceUserDataStreamingService.create(listenKey); + return userDataStreamingService + .connect() + .doOnComplete( + () -> { + LOG.info("Connected to authenticated web socket"); + userDataChannel.onChangeListenKey( + newListenKey -> { + userDataStreamingService + .disconnect() + .doOnComplete( + () -> { + createAndConnectUserDataService(newListenKey) + .doOnComplete( + () -> { + streamingAccountService.setUserDataStreamingService( + userDataStreamingService); + streamingTradeService.setUserDataStreamingService( + userDataStreamingService); + }); + }); + }); + }); + } + + @Override + public Completable disconnect() { + List completables = new ArrayList<>(); + completables.add(streamingService.disconnect()); + streamingService = null; + if (userDataStreamingService != null) { + completables.add(userDataStreamingService.disconnect()); + userDataStreamingService = null; } - - private String buildSubscriptionStrings(List currencyPairs, String subscriptionType) { - if ("depth".equals(subscriptionType)) { - return subscriptionStrings(currencyPairs) - .map(s -> s + "@" + subscriptionType + orderBookUpdateFrequencyParameter) - .collect(Collectors.joining("/")); - } else { - return subscriptionStrings(currencyPairs).map(s -> s + "@" + subscriptionType) - .collect(Collectors.joining("/")); - } + if (userDataChannel != null) { + userDataChannel.close(); + userDataChannel = null; } - - private static Stream subscriptionStrings(List currencyPairs) { - return currencyPairs.stream() - .map(pair -> String.join("", pair.toString().split("/")).toLowerCase()); + streamingMarketDataService = null; + return Completable.concat(completables); + } + + @Override + public boolean isAlive() { + return streamingService != null && streamingService.isSocketOpen(); + } + + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } + + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } + + @Override + public BinanceStreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public BinanceStreamingAccountService getStreamingAccountService() { + return streamingAccountService; + } + + @Override + public BinanceStreamingTradeService getStreamingTradeService() { + return streamingTradeService; + } + + private BinanceStreamingService createStreamingService(ProductSubscription subscription) { + String path = API_BASE_URI + "stream?streams=" + buildSubscriptionStreams(subscription); + return new BinanceStreamingService(path, subscription); + } + + public String buildSubscriptionStreams(ProductSubscription subscription) { + return Stream.of( + buildSubscriptionStrings(subscription.getTicker(), "ticker"), + buildSubscriptionStrings(subscription.getOrderBook(), "depth"), + buildSubscriptionStrings(subscription.getTrades(), "trade")) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining("/")); + } + + private String buildSubscriptionStrings( + List currencyPairs, String subscriptionType) { + if ("depth".equals(subscriptionType)) { + return subscriptionStrings(currencyPairs) + .map(s -> s + "@" + subscriptionType + orderBookUpdateFrequencyParameter) + .collect(Collectors.joining("/")); + } else { + return subscriptionStrings(currencyPairs) + .map(s -> s + "@" + subscriptionType) + .collect(Collectors.joining("/")); } + } - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + private static Stream subscriptionStrings(List currencyPairs) { + return currencyPairs.stream() + .map(pair -> String.join("", pair.toString().split("/")).toLowerCase()); + } + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } - diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingMarketDataService.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingMarketDataService.java index cc12ac87b..26beaa7fc 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingMarketDataService.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingMarketDataService.java @@ -1,5 +1,7 @@ package info.bitrich.xchangestream.binance; +import static info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper.getObjectMapper; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; @@ -15,6 +17,13 @@ import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; import io.reactivex.functions.Consumer; +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import org.knowm.xchange.binance.BinanceAdapters; import org.knowm.xchange.binance.BinanceErrorAdapter; import org.knowm.xchange.binance.dto.BinanceException; @@ -32,300 +41,345 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -import static info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper.getObjectMapper; - public class BinanceStreamingMarketDataService implements StreamingMarketDataService { - private static final Logger LOG = LoggerFactory.getLogger(BinanceStreamingMarketDataService.class); - - private static final JavaType TICKER_TYPE = getObjectMapper() - .getTypeFactory() - .constructType(new TypeReference>() {}); - private static final JavaType TRADE_TYPE = getObjectMapper() - .getTypeFactory() - .constructType(new TypeReference>() {}); - private static final JavaType DEPTH_TYPE = getObjectMapper() - .getTypeFactory() - .constructType(new TypeReference>() {}); - - private final BinanceStreamingService service; - private final String orderBookUpdateFrequencyParameter; - - private final Map orderbooks = new HashMap<>(); - private final Map> tickerSubscriptions = new HashMap<>(); - private final Map> orderbookSubscriptions = new HashMap<>(); - private final Map> tradeSubscriptions = new HashMap<>(); - - private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - private final BinanceMarketDataService marketDataService; - private final Runnable onApiCall; - - private final AtomicBoolean fallenBack = new AtomicBoolean(); - private final AtomicReference fallbackOnApiCall = new AtomicReference<>(() -> {}); - - public BinanceStreamingMarketDataService(BinanceStreamingService service, BinanceMarketDataService marketDataService, Runnable onApiCall, - final String orderBookUpdateFrequencyParameter) { - this.service = service; - this.orderBookUpdateFrequencyParameter = orderBookUpdateFrequencyParameter; - this.marketDataService = marketDataService; - this.onApiCall = onApiCall; - } - - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - if (!service.getProductSubscription().getOrderBook().contains(currencyPair)) { - throw new UnsupportedOperationException("Binance exchange only supports up front subscriptions - subscribe at connect time"); - } - return orderbookSubscriptions.get(currencyPair); - } - - public Observable getRawTicker(CurrencyPair currencyPair, Object... args) { - if (!service.getProductSubscription().getTicker().contains(currencyPair)) { - throw new UnsupportedOperationException("Binance exchange only supports up front subscriptions - subscribe at connect time"); - } - return tickerSubscriptions.get(currencyPair); - } - - public Observable getRawTrades(CurrencyPair currencyPair, Object... args) { - if (!service.getProductSubscription().getTrades().contains(currencyPair)) { - throw new UnsupportedOperationException("Binance exchange only supports up front subscriptions - subscribe at connect time"); - } - return tradeSubscriptions.get(currencyPair); + private static final Logger LOG = + LoggerFactory.getLogger(BinanceStreamingMarketDataService.class); + + private static final JavaType TICKER_TYPE = + getObjectMapper() + .getTypeFactory() + .constructType( + new TypeReference< + BinanceWebsocketTransaction>() {}); + private static final JavaType TRADE_TYPE = + getObjectMapper() + .getTypeFactory() + .constructType( + new TypeReference< + BinanceWebsocketTransaction>() {}); + private static final JavaType DEPTH_TYPE = + getObjectMapper() + .getTypeFactory() + .constructType( + new TypeReference< + BinanceWebsocketTransaction>() {}); + + private final BinanceStreamingService service; + private final String orderBookUpdateFrequencyParameter; + + private final Map orderbooks = new HashMap<>(); + private final Map> tickerSubscriptions = + new HashMap<>(); + private final Map> orderbookSubscriptions = new HashMap<>(); + private final Map> tradeSubscriptions = new HashMap<>(); + + private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + private final BinanceMarketDataService marketDataService; + private final Runnable onApiCall; + + private final AtomicBoolean fallenBack = new AtomicBoolean(); + private final AtomicReference fallbackOnApiCall = new AtomicReference<>(() -> {}); + + public BinanceStreamingMarketDataService( + BinanceStreamingService service, + BinanceMarketDataService marketDataService, + Runnable onApiCall, + final String orderBookUpdateFrequencyParameter) { + this.service = service; + this.orderBookUpdateFrequencyParameter = orderBookUpdateFrequencyParameter; + this.marketDataService = marketDataService; + this.onApiCall = onApiCall; + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + if (!service.getProductSubscription().getOrderBook().contains(currencyPair)) { + throw new UnsupportedOperationException( + "Binance exchange only supports up front subscriptions - subscribe at connect time"); } + return orderbookSubscriptions.get(currencyPair); + } - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - return getRawTicker(currencyPair) - .map(BinanceTicker24h::toTicker); + public Observable getRawTicker(CurrencyPair currencyPair, Object... args) { + if (!service.getProductSubscription().getTicker().contains(currencyPair)) { + throw new UnsupportedOperationException( + "Binance exchange only supports up front subscriptions - subscribe at connect time"); } + return tickerSubscriptions.get(currencyPair); + } - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - return getRawTrades(currencyPair, args) - .map(rawTrade -> new Trade.Builder() - .type(BinanceAdapters.convertType(rawTrade.isBuyerMarketMaker())) - .originalAmount(rawTrade.getQuantity()) - .currencyPair(currencyPair) - .price(rawTrade.getPrice()) - .timestamp(new Date(rawTrade.getTimestamp())) - .id(String.valueOf(rawTrade.getTradeId())) - .build()); + public Observable getRawTrades(CurrencyPair currencyPair, Object... args) { + if (!service.getProductSubscription().getTrades().contains(currencyPair)) { + throw new UnsupportedOperationException( + "Binance exchange only supports up front subscriptions - subscribe at connect time"); } - - private String channelFromCurrency(CurrencyPair currencyPair, String subscriptionType) { - String currency = String.join("", currencyPair.toString().split("/")).toLowerCase(); - String currencyChannel = currency + "@" + subscriptionType; - - if ("depth".equals(subscriptionType)) { - return currencyChannel + orderBookUpdateFrequencyParameter; - } else { - return currencyChannel; - } + return tradeSubscriptions.get(currencyPair); + } + + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + return getRawTicker(currencyPair).map(BinanceTicker24h::toTicker); + } + + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + return getRawTrades(currencyPair, args) + .map( + rawTrade -> + new Trade.Builder() + .type(BinanceAdapters.convertType(rawTrade.isBuyerMarketMaker())) + .originalAmount(rawTrade.getQuantity()) + .currencyPair(currencyPair) + .price(rawTrade.getPrice()) + .timestamp(new Date(rawTrade.getTimestamp())) + .id(String.valueOf(rawTrade.getTradeId())) + .build()); + } + + private String channelFromCurrency(CurrencyPair currencyPair, String subscriptionType) { + String currency = String.join("", currencyPair.toString().split("/")).toLowerCase(); + String currencyChannel = currency + "@" + subscriptionType; + + if ("depth".equals(subscriptionType)) { + return currencyChannel + orderBookUpdateFrequencyParameter; + } else { + return currencyChannel; } - - /** - * Registers subsriptions with the streaming service for the given products. - * - * As we receive messages as soon as the connection is open, we need to register subscribers to handle these before the - * first messages arrive. - */ - public void openSubscriptions(ProductSubscription productSubscription) { - productSubscription.getTicker() - .forEach(currencyPair -> - tickerSubscriptions.put(currencyPair, triggerObservableBody(rawTickerStream(currencyPair).share()))); - productSubscription.getOrderBook() - .forEach(currencyPair -> - orderbookSubscriptions.put(currencyPair, triggerObservableBody(orderBookStream(currencyPair).share()))); - productSubscription.getTrades() - .forEach(currencyPair -> - tradeSubscriptions.put(currencyPair, triggerObservableBody(rawTradeStream(currencyPair).share()))); + } + + /** + * Registers subsriptions with the streaming service for the given products. + * + *

As we receive messages as soon as the connection is open, we need to register subscribers to + * handle these before the first messages arrive. + */ + public void openSubscriptions(ProductSubscription productSubscription) { + productSubscription + .getTicker() + .forEach( + currencyPair -> + tickerSubscriptions.put( + currencyPair, triggerObservableBody(rawTickerStream(currencyPair).share()))); + productSubscription + .getOrderBook() + .forEach( + currencyPair -> + orderbookSubscriptions.put( + currencyPair, triggerObservableBody(orderBookStream(currencyPair).share()))); + productSubscription + .getTrades() + .forEach( + currencyPair -> + tradeSubscriptions.put( + currencyPair, triggerObservableBody(rawTradeStream(currencyPair).share()))); + } + + private Observable rawTickerStream(CurrencyPair currencyPair) { + return service + .subscribeChannel(channelFromCurrency(currencyPair, "ticker")) + .map(this::tickerTransaction) + .filter(transaction -> transaction.getData().getCurrencyPair().equals(currencyPair)) + .map(transaction -> transaction.getData().getTicker()); + } + + private final class OrderbookSubscription { + long snapshotlastUpdateId; + AtomicLong lastUpdateId = new AtomicLong(0L); + OrderBook orderBook; + Observable> stream; + + void invalidateSnapshot() { + snapshotlastUpdateId = 0L; } - private Observable rawTickerStream(CurrencyPair currencyPair) { - return service.subscribeChannel(channelFromCurrency(currencyPair, "ticker")) - .map(this::tickerTransaction) - .filter(transaction -> transaction.getData().getCurrencyPair().equals(currencyPair)) - .map(transaction -> transaction.getData().getTicker()); + void initSnapshotIfInvalid(CurrencyPair currencyPair) { + if (snapshotlastUpdateId != 0L) return; + try { + LOG.info("Fetching initial orderbook snapshot for {} ", currencyPair); + onApiCall.run(); + fallbackOnApiCall.get().run(); + BinanceOrderbook book = fetchBinanceOrderBook(currencyPair); + snapshotlastUpdateId = book.lastUpdateId; + lastUpdateId.set(book.lastUpdateId); + orderBook = BinanceMarketDataService.convertOrderBook(book, currencyPair); + } catch (Exception e) { + LOG.error("Failed to fetch initial order book for " + currencyPair, e); + snapshotlastUpdateId = 0L; + lastUpdateId.set(0L); + orderBook = null; + } } - private final class OrderbookSubscription { - long snapshotlastUpdateId; - AtomicLong lastUpdateId = new AtomicLong(0L); - OrderBook orderBook; - Observable> stream; - - void invalidateSnapshot() { - snapshotlastUpdateId = 0L; - } - - void initSnapshotIfInvalid(CurrencyPair currencyPair) { - if (snapshotlastUpdateId != 0L) - return; - try { - LOG.info("Fetching initial orderbook snapshot for {} ", currencyPair); - onApiCall.run(); - fallbackOnApiCall.get().run(); - BinanceOrderbook book = fetchBinanceOrderBook(currencyPair); - snapshotlastUpdateId = book.lastUpdateId; - lastUpdateId.set(book.lastUpdateId); - orderBook = BinanceMarketDataService.convertOrderBook(book, currencyPair); - } catch (Exception e) { - LOG.error("Failed to fetch initial order book for " + currencyPair, e); - snapshotlastUpdateId = 0L; - lastUpdateId.set(0L); - orderBook = null; - } - - } - - private BinanceOrderbook fetchBinanceOrderBook(CurrencyPair currencyPair) throws IOException, InterruptedException { - try { - return marketDataService.getBinanceOrderbook(currencyPair, 1000); - } catch (BinanceException e) { - if (BinanceErrorAdapter.adapt(e) instanceof RateLimitExceededException) { - if (fallenBack.compareAndSet(false, true)) { - LOG.error("API Rate limit was hit when fetching Binance order book snapshot. Provide a \n" - + "rate limiter. Apache Commons and Google Guava provide the TimedSemaphore\n" - + "and RateLimiter classes which are effective for this purpose. Example:\n" - + "\n" - + " exchangeSpecification.setExchangeSpecificParametersItem(\n" - + " info.bitrich.xchangestream.util.Events.BEFORE_API_CALL_HANDLER,\n" - + " () -> rateLimiter.acquire())\n" - + "\n" - + "Pausing for 15sec and falling back to one call per three seconds, but you\n" - + "will get more optimal performance by handling your own rate limiting."); - RateLimiter rateLimiter = RateLimiter.create(0.333); - fallbackOnApiCall.set(rateLimiter::acquire); - Thread.sleep(15000); - } - } - throw e; - } + private BinanceOrderbook fetchBinanceOrderBook(CurrencyPair currencyPair) + throws IOException, InterruptedException { + try { + return marketDataService.getBinanceOrderbook(currencyPair, 1000); + } catch (BinanceException e) { + if (BinanceErrorAdapter.adapt(e) instanceof RateLimitExceededException) { + if (fallenBack.compareAndSet(false, true)) { + LOG.error( + "API Rate limit was hit when fetching Binance order book snapshot. Provide a \n" + + "rate limiter. Apache Commons and Google Guava provide the TimedSemaphore\n" + + "and RateLimiter classes which are effective for this purpose. Example:\n" + + "\n" + + " exchangeSpecification.setExchangeSpecificParametersItem(\n" + + " info.bitrich.xchangestream.util.Events.BEFORE_API_CALL_HANDLER,\n" + + " () -> rateLimiter.acquire())\n" + + "\n" + + "Pausing for 15sec and falling back to one call per three seconds, but you\n" + + "will get more optimal performance by handling your own rate limiting."); + RateLimiter rateLimiter = RateLimiter.create(0.333); + fallbackOnApiCall.set(rateLimiter::acquire); + Thread.sleep(15000); + } } + throw e; + } } - - private OrderbookSubscription connectOrderBook(CurrencyPair currencyPair) { - OrderbookSubscription subscription = new OrderbookSubscription(); - - // 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth - // 2. Buffer the events you receive from the stream. - - subscription.stream = service.subscribeChannel(channelFromCurrency(currencyPair, "depth")) - .map(this::depthTransaction) - .filter(transaction -> transaction.getData().getCurrencyPair().equals(currencyPair)); - return subscription; + } + + private OrderbookSubscription connectOrderBook(CurrencyPair currencyPair) { + OrderbookSubscription subscription = new OrderbookSubscription(); + + // 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth + // 2. Buffer the events you receive from the stream. + + subscription.stream = + service + .subscribeChannel(channelFromCurrency(currencyPair, "depth")) + .map(this::depthTransaction) + .filter(transaction -> transaction.getData().getCurrencyPair().equals(currencyPair)); + return subscription; + } + + private Observable orderBookStream(CurrencyPair currencyPair) { + OrderbookSubscription subscription = + orderbooks.computeIfAbsent(currencyPair, this::connectOrderBook); + + return subscription + .stream + + // 3. Get a depth snapshot from + // https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 + // (we do this if we don't already have one or we've invalidated a previous one) + .doOnNext(transaction -> subscription.initSnapshotIfInvalid(currencyPair)) + + // If we failed, don't return anything. Just keep trying until it works + .filter(transaction -> subscription.snapshotlastUpdateId > 0L) + .map(BinanceWebsocketTransaction::getData) + + // 4. Drop any event where u is <= lastUpdateId in the snapshot + .filter(depth -> depth.getLastUpdateId() > subscription.snapshotlastUpdateId) + + // 5. The first processed should have U <= lastUpdateId+1 AND u >= lastUpdateId+1, and + // subsequent events would + // normally have u == lastUpdateId + 1 which is stricter version of the above - let's be + // more relaxed + // each update has absolute numbers so even if there's an overlap it does no harm + .filter( + depth -> { + long lastUpdateId = subscription.lastUpdateId.get(); + boolean result; + if (lastUpdateId == 0L) { + result = true; + } else { + result = + depth.getFirstUpdateId() <= lastUpdateId + 1 + && depth.getLastUpdateId() >= lastUpdateId + 1; + } + if (result) { + subscription.lastUpdateId.set(depth.getLastUpdateId()); + } else { + // If not, we re-sync. This will commonly occur a few times when starting up, since + // given update ids 1,2,3,4,5,6,7,8,9, Binance may sometimes return a snapshot + // as of 5, but update events covering 1-3, 4-6 and 7-9. We can't apply the 4-6 + // update event without double-counting 5, and we can't apply the 7-9 update without + // missing 6. The only thing we can do is to keep requesting a fresh snapshot until + // we get to a situation where the snapshot and an update event precisely line up. + LOG.info( + "Orderbook snapshot for {} out of date (last={}, U={}, u={}). This is normal. Re-syncing.", + currencyPair, + lastUpdateId, + depth.getFirstUpdateId(), + depth.getLastUpdateId()); + subscription.invalidateSnapshot(); + } + return result; + }) + + // 7. The data in each event is the absolute quantity for a price level + // 8. If the quantity is 0, remove the price level + // 9. Receiving an event that removes a price level that is not in your local order book can + // happen and is normal. + .map( + depth -> { + BinanceOrderbook ob = depth.getOrderBook(); + ob.bids.forEach( + (key, value) -> + subscription.orderBook.update( + new OrderBookUpdate( + OrderType.BID, + null, + currencyPair, + key, + depth.getEventTime(), + value))); + ob.asks.forEach( + (key, value) -> + subscription.orderBook.update( + new OrderBookUpdate( + OrderType.ASK, + null, + currencyPair, + key, + depth.getEventTime(), + value))); + return subscription.orderBook; + }); + } + + private Observable rawTradeStream(CurrencyPair currencyPair) { + return service + .subscribeChannel(channelFromCurrency(currencyPair, "trade")) + .map(this::tradeTransaction) + .filter(transaction -> transaction.getData().getCurrencyPair().equals(currencyPair)) + .map(transaction -> transaction.getData().getRawTrade()); + } + + /** + * Force observable to execute its body, this way we get `BinanceStreamingService` to register the + * observables emitter ready for our message arrivals. + */ + private Observable triggerObservableBody(Observable observable) { + Consumer NOOP = whatever -> {}; + observable.subscribe(NOOP); + return observable; + } + + private BinanceWebsocketTransaction tickerTransaction( + JsonNode node) { + try { + return mapper.readValue(mapper.treeAsTokens(node), TICKER_TYPE); + } catch (IOException e) { + throw new ExchangeException("Unable to parse ticker transaction", e); } - - private Observable orderBookStream(CurrencyPair currencyPair) { - OrderbookSubscription subscription = orderbooks.computeIfAbsent(currencyPair, this::connectOrderBook); - - return subscription.stream - - // 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 - // (we do this if we don't already have one or we've invalidated a previous one) - .doOnNext(transaction -> subscription.initSnapshotIfInvalid(currencyPair)) - - // If we failed, don't return anything. Just keep trying until it works - .filter(transaction -> subscription.snapshotlastUpdateId > 0L) - - .map(BinanceWebsocketTransaction::getData) - - // 4. Drop any event where u is <= lastUpdateId in the snapshot - .filter(depth -> depth.getLastUpdateId() > subscription.snapshotlastUpdateId) - - // 5. The first processed should have U <= lastUpdateId+1 AND u >= lastUpdateId+1, and subsequent events would - // normally have u == lastUpdateId + 1 which is stricter version of the above - let's be more relaxed - // each update has absolute numbers so even if there's an overlap it does no harm - .filter(depth -> { - long lastUpdateId = subscription.lastUpdateId.get(); - boolean result; - if (lastUpdateId == 0L) { - result = true; - } else { - result = depth.getFirstUpdateId() <= lastUpdateId + 1 && - depth.getLastUpdateId() >= lastUpdateId + 1; - } - if (result) { - subscription.lastUpdateId.set(depth.getLastUpdateId()); - } else { - // If not, we re-sync. This will commonly occur a few times when starting up, since - // given update ids 1,2,3,4,5,6,7,8,9, Binance may sometimes return a snapshot - // as of 5, but update events covering 1-3, 4-6 and 7-9. We can't apply the 4-6 - // update event without double-counting 5, and we can't apply the 7-9 update without - // missing 6. The only thing we can do is to keep requesting a fresh snapshot until - // we get to a situation where the snapshot and an update event precisely line up. - LOG.info("Orderbook snapshot for {} out of date (last={}, U={}, u={}). This is normal. Re-syncing.", currencyPair, lastUpdateId, depth.getFirstUpdateId(), depth.getLastUpdateId()); - subscription.invalidateSnapshot(); - } - return result; - }) - - // 7. The data in each event is the absolute quantity for a price level - // 8. If the quantity is 0, remove the price level - // 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal. - .map(depth -> { - BinanceOrderbook ob = depth.getOrderBook(); - ob.bids.forEach((key, value) -> subscription.orderBook.update(new OrderBookUpdate( - OrderType.BID, - null, - currencyPair, - key, - depth.getEventTime(), - value))); - ob.asks.forEach((key, value) -> subscription.orderBook.update(new OrderBookUpdate( - OrderType.ASK, - null, - currencyPair, - key, - depth.getEventTime(), - value))); - return subscription.orderBook; - }); - } - - private Observable rawTradeStream(CurrencyPair currencyPair) { - return service.subscribeChannel(channelFromCurrency(currencyPair, "trade")) - .map(this::tradeTransaction) - .filter(transaction -> transaction.getData().getCurrencyPair().equals(currencyPair)) - .map(transaction -> transaction.getData().getRawTrade()); + } + + private BinanceWebsocketTransaction depthTransaction( + JsonNode node) { + try { + return mapper.readValue(mapper.treeAsTokens(node), DEPTH_TYPE); + } catch (IOException e) { + throw new ExchangeException("Unable to parse order book transaction", e); } - - /** Force observable to execute its body, this way we get `BinanceStreamingService` to register the observables emitter - * ready for our message arrivals. */ - private Observable triggerObservableBody(Observable observable) { - Consumer NOOP = whatever -> {}; - observable.subscribe(NOOP); - return observable; - } - - private BinanceWebsocketTransaction tickerTransaction(JsonNode node) { - try { - return mapper.readValue(mapper.treeAsTokens(node), TICKER_TYPE); - } catch (IOException e) { - throw new ExchangeException("Unable to parse ticker transaction", e); - } - } - - private BinanceWebsocketTransaction depthTransaction(JsonNode node) { - try { - return mapper.readValue(mapper.treeAsTokens(node), DEPTH_TYPE); - } catch (IOException e) { - throw new ExchangeException("Unable to parse order book transaction", e); - } - } - - private BinanceWebsocketTransaction tradeTransaction(JsonNode node) { - try { - return mapper.readValue(mapper.treeAsTokens(node), TRADE_TYPE); - } catch (IOException e) { - throw new ExchangeException("Unable to parse trade transaction", e); - } + } + + private BinanceWebsocketTransaction tradeTransaction( + JsonNode node) { + try { + return mapper.readValue(mapper.treeAsTokens(node), TRADE_TYPE); + } catch (IOException e) { + throw new ExchangeException("Unable to parse trade transaction", e); } -} \ No newline at end of file + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingService.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingService.java index bde87acbc..766e91c3f 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingService.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingService.java @@ -1,58 +1,57 @@ package info.bitrich.xchangestream.binance; -import java.io.IOException; - import com.fasterxml.jackson.databind.JsonNode; - import info.bitrich.xchangestream.core.ProductSubscription; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; +import java.io.IOException; public class BinanceStreamingService extends JsonNettyStreamingService { - private final ProductSubscription productSubscription; - - public BinanceStreamingService(String baseUri, ProductSubscription productSubscription) { - super(baseUri, Integer.MAX_VALUE); - this.productSubscription = productSubscription; - } - - @Override - public void messageHandler(String message) { - super.messageHandler(message); - } - - @Override - protected void handleMessage(JsonNode message) { - super.handleMessage(message); - } - - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - return message.get("stream").asText(); - } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - // No op. Disconnecting from the web socket will cancel subscriptions. - return null; - } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - // No op. Disconnecting from the web socket will cancel subscriptions. - return null; - } - - @Override - public void sendMessage(String message) { - // Subscriptions are made upon connection - no messages are sent. - } - - /** - * The available subscriptions for this streaming service. - * @return The subscriptions for the currently open connection. - */ - public ProductSubscription getProductSubscription() { - return productSubscription; - } -} \ No newline at end of file + private final ProductSubscription productSubscription; + + public BinanceStreamingService(String baseUri, ProductSubscription productSubscription) { + super(baseUri, Integer.MAX_VALUE); + this.productSubscription = productSubscription; + } + + @Override + public void messageHandler(String message) { + super.messageHandler(message); + } + + @Override + protected void handleMessage(JsonNode message) { + super.handleMessage(message); + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + return message.get("stream").asText(); + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + // No op. Disconnecting from the web socket will cancel subscriptions. + return null; + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + // No op. Disconnecting from the web socket will cancel subscriptions. + return null; + } + + @Override + public void sendMessage(String message) { + // Subscriptions are made upon connection - no messages are sent. + } + + /** + * The available subscriptions for this streaming service. + * + * @return The subscriptions for the currently open connection. + */ + public ProductSubscription getProductSubscription() { + return productSubscription; + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingTradeService.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingTradeService.java index 249bbc5f0..de2fa615b 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingTradeService.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceStreamingTradeService.java @@ -2,97 +2,94 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - import info.bitrich.xchangestream.binance.dto.BaseBinanceWebSocketTransaction; import info.bitrich.xchangestream.binance.dto.ExecutionReportBinanceUserTransaction; import info.bitrich.xchangestream.binance.dto.ExecutionReportBinanceUserTransaction.ExecutionType; import info.bitrich.xchangestream.core.StreamingTradeService; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; - import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.Subject; - +import java.io.IOException; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.UserTrade; import org.knowm.xchange.exceptions.ExchangeException; import org.knowm.xchange.exceptions.ExchangeSecurityException; -import java.io.IOException; - public class BinanceStreamingTradeService implements StreamingTradeService { - private final Subject executionReportsPublisher = PublishSubject.create().toSerialized(); - - private volatile Disposable executionReports; - private volatile BinanceUserDataStreamingService binanceUserDataStreamingService; - - private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - public BinanceStreamingTradeService(BinanceUserDataStreamingService binanceUserDataStreamingService) { - this.binanceUserDataStreamingService = binanceUserDataStreamingService; - } - - public Observable getRawExecutionReports() { - if (binanceUserDataStreamingService == null || !binanceUserDataStreamingService.isSocketOpen()) - throw new ExchangeSecurityException("Not authenticated"); - return executionReportsPublisher; - } - - public Observable getOrderChanges() { - return getRawExecutionReports() - .filter(r -> !r.getExecutionType().equals(ExecutionType.REJECTED)) - .map(ExecutionReportBinanceUserTransaction::toOrder); - } - - @Override - public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { - return getOrderChanges() - .filter(oc -> currencyPair.equals(oc.getCurrencyPair())); - } - - public Observable getUserTrades() { - return getRawExecutionReports() - .filter(r -> r.getExecutionType().equals(ExecutionType.TRADE)) - .map(ExecutionReportBinanceUserTransaction::toUserTrade); - } - - @Override - public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { - return getUserTrades().filter(t -> t.getCurrencyPair().equals(currencyPair)); - } - - /** - * Registers subsriptions with the streaming service for the given products. - */ - public void openSubscriptions() { - if (binanceUserDataStreamingService != null) { - executionReports = binanceUserDataStreamingService - .subscribeChannel(BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.EXECUTION_REPORT) - .map(this::executionReport) - .subscribe(executionReportsPublisher::onNext); - } + private final Subject executionReportsPublisher = + PublishSubject.create().toSerialized(); + + private volatile Disposable executionReports; + private volatile BinanceUserDataStreamingService binanceUserDataStreamingService; + + private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + public BinanceStreamingTradeService( + BinanceUserDataStreamingService binanceUserDataStreamingService) { + this.binanceUserDataStreamingService = binanceUserDataStreamingService; + } + + public Observable getRawExecutionReports() { + if (binanceUserDataStreamingService == null || !binanceUserDataStreamingService.isSocketOpen()) + throw new ExchangeSecurityException("Not authenticated"); + return executionReportsPublisher; + } + + public Observable getOrderChanges() { + return getRawExecutionReports() + .filter(r -> !r.getExecutionType().equals(ExecutionType.REJECTED)) + .map(ExecutionReportBinanceUserTransaction::toOrder); + } + + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + return getOrderChanges().filter(oc -> currencyPair.equals(oc.getCurrencyPair())); + } + + public Observable getUserTrades() { + return getRawExecutionReports() + .filter(r -> r.getExecutionType().equals(ExecutionType.TRADE)) + .map(ExecutionReportBinanceUserTransaction::toUserTrade); + } + + @Override + public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + return getUserTrades().filter(t -> t.getCurrencyPair().equals(currencyPair)); + } + + /** Registers subsriptions with the streaming service for the given products. */ + public void openSubscriptions() { + if (binanceUserDataStreamingService != null) { + executionReports = + binanceUserDataStreamingService + .subscribeChannel( + BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.EXECUTION_REPORT) + .map(this::executionReport) + .subscribe(executionReportsPublisher::onNext); } - - /** - * User data subscriptions may have to persist across multiple socket connections to different - * URLs and therefore must act in a publisher fashion so that subscribers get an uninterrupted - * stream. - */ - void setUserDataStreamingService(BinanceUserDataStreamingService binanceUserDataStreamingService) { - if (executionReports != null && !executionReports.isDisposed()) - executionReports.dispose(); - this.binanceUserDataStreamingService = binanceUserDataStreamingService; - openSubscriptions(); - } - - private ExecutionReportBinanceUserTransaction executionReport(JsonNode json) { - try { - return mapper.treeToValue(json, ExecutionReportBinanceUserTransaction.class); - } catch (IOException e) { - throw new ExchangeException("Unable to parse execution report", e); - } + } + + /** + * User data subscriptions may have to persist across multiple socket connections to different + * URLs and therefore must act in a publisher fashion so that subscribers get an uninterrupted + * stream. + */ + void setUserDataStreamingService( + BinanceUserDataStreamingService binanceUserDataStreamingService) { + if (executionReports != null && !executionReports.isDisposed()) executionReports.dispose(); + this.binanceUserDataStreamingService = binanceUserDataStreamingService; + openSubscriptions(); + } + + private ExecutionReportBinanceUserTransaction executionReport(JsonNode json) { + try { + return mapper.treeToValue(json, ExecutionReportBinanceUserTransaction.class); + } catch (IOException e) { + throw new ExchangeException("Unable to parse execution report", e); } -} \ No newline at end of file + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataChannel.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataChannel.java index 4ff8bba41..7d5866613 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataChannel.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataChannel.java @@ -1,137 +1,128 @@ package info.bitrich.xchangestream.binance; +import static java.util.concurrent.TimeUnit.SECONDS; + import io.reactivex.Observable; import io.reactivex.disposables.Disposable; - -import org.knowm.xchange.binance.BinanceAuthenticated; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; - -import static java.util.concurrent.TimeUnit.SECONDS; +import org.knowm.xchange.binance.BinanceAuthenticated; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Binance user data streams must be established by first requesting a unique - * "listen key" via authenticated REST API, which is then used to create an - * obscured WS URI (rather than authenticating the web socket). This class - * handles the initial request for a listen key, but also the 30-minute - * keepalive REST calls necessary to keep the socket open. It also allows for - * the possibility that extended downtime might cause the listen key to expire - * without being able to renew it. In this case, a new listen key is requested - * and a caller can be alerted via asynchronous callback to re-establish the - * socket with the new listen key. + * Binance user data streams must be established by first requesting a unique "listen key" via + * authenticated REST API, which is then used to create an obscured WS URI (rather than + * authenticating the web socket). This class handles the initial request for a listen key, but also + * the 30-minute keepalive REST calls necessary to keep the socket open. It also allows for the + * possibility that extended downtime might cause the listen key to expire without being able to + * renew it. In this case, a new listen key is requested and a caller can be alerted via + * asynchronous callback to re-establish the socket with the new listen key. * * @author Graham Crockford */ class BinanceUserDataChannel implements AutoCloseable { - private static final Logger LOG = LoggerFactory.getLogger(BinanceUserDataChannel.class); - - private final BinanceAuthenticated binance; - private final String apiKey; - private final Runnable onApiCall; - private final Disposable keepAlive; - - private String listenKey; - private Consumer onChangeListenKey; - - /** - * Creates the channel, establishing a listen key - * (immediately available from {@link #getListenKey()}) and - * starting timers to ensure the channel is kept alive. - * - * @param binance Access to binance services. - * @param apiKey The API key. - * @param onApiCall A callback to perform prior to any service calls. - */ - BinanceUserDataChannel(BinanceAuthenticated binance, String apiKey, Runnable onApiCall) { - this.binance = binance; - this.apiKey = apiKey; - this.onApiCall = onApiCall; - openChannel(); - // Send a keepalive every 30 minutes as recommended by Binance - this.keepAlive = Observable.interval(30, TimeUnit.MINUTES) - .subscribe(x -> keepAlive()); - } - - /** - * Set this callback to get notified if the current listen key is revoked. - * - * @param onChangeListenKey The callback. - */ - void onChangeListenKey(Consumer onChangeListenKey) { - this.onChangeListenKey = onChangeListenKey; - } - - private void keepAlive() { - if (listenKey == null) - return; - try { - LOG.debug("Keeping user data channel alive"); - onApiCall.run(); - binance.keepAliveUserDataStream(apiKey, listenKey); - LOG.debug("User data channel keepalive sent successfully"); - } catch (Exception e) { - LOG.error("User data channel keepalive failed.", e); - this.listenKey = null; - reconnect(); - } + private static final Logger LOG = LoggerFactory.getLogger(BinanceUserDataChannel.class); + + private final BinanceAuthenticated binance; + private final String apiKey; + private final Runnable onApiCall; + private final Disposable keepAlive; + + private String listenKey; + private Consumer onChangeListenKey; + + /** + * Creates the channel, establishing a listen key (immediately available from {@link + * #getListenKey()}) and starting timers to ensure the channel is kept alive. + * + * @param binance Access to binance services. + * @param apiKey The API key. + * @param onApiCall A callback to perform prior to any service calls. + */ + BinanceUserDataChannel(BinanceAuthenticated binance, String apiKey, Runnable onApiCall) { + this.binance = binance; + this.apiKey = apiKey; + this.onApiCall = onApiCall; + openChannel(); + // Send a keepalive every 30 minutes as recommended by Binance + this.keepAlive = Observable.interval(30, TimeUnit.MINUTES).subscribe(x -> keepAlive()); + } + + /** + * Set this callback to get notified if the current listen key is revoked. + * + * @param onChangeListenKey The callback. + */ + void onChangeListenKey(Consumer onChangeListenKey) { + this.onChangeListenKey = onChangeListenKey; + } + + private void keepAlive() { + if (listenKey == null) return; + try { + LOG.debug("Keeping user data channel alive"); + onApiCall.run(); + binance.keepAliveUserDataStream(apiKey, listenKey); + LOG.debug("User data channel keepalive sent successfully"); + } catch (Exception e) { + LOG.error("User data channel keepalive failed.", e); + this.listenKey = null; + reconnect(); } - - private void reconnect() { - try { - openChannel(); - if (onChangeListenKey != null) { - onChangeListenKey.accept(this.listenKey); - } - } catch (Exception e) { - LOG.error("Failed to reconnect. Will retry in 15 seconds.", e); - Observable.timer(15, SECONDS).subscribe(x -> reconnect()); - } + } + + private void reconnect() { + try { + openChannel(); + if (onChangeListenKey != null) { + onChangeListenKey.accept(this.listenKey); + } + } catch (Exception e) { + LOG.error("Failed to reconnect. Will retry in 15 seconds.", e); + Observable.timer(15, SECONDS).subscribe(x -> reconnect()); } - - private void openChannel() { - try { - LOG.debug("Opening new user data channel"); - onApiCall.run(); - this.listenKey = binance.startUserDataStream(apiKey).getListenKey(); - LOG.debug("Opened new user data channel"); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * @return The current listen key. - * @throws NoActiveChannelException If no listen key is currently available. - */ - String getListenKey() throws NoActiveChannelException { - if (listenKey == null) - throw new NoActiveChannelException(); - return listenKey; + } + + private void openChannel() { + try { + LOG.debug("Opening new user data channel"); + onApiCall.run(); + this.listenKey = binance.startUserDataStream(apiKey).getListenKey(); + LOG.debug("Opened new user data channel"); + } catch (IOException e) { + throw new RuntimeException(e); } - - @Override - public void close() { - keepAlive.dispose(); - } - - - /** - * Thrown on calls to {@link BinanceUserDataChannel#getListenKey()} if no - * channel is currently open. - * - * @author Graham Crockford - */ - static final class NoActiveChannelException extends Exception { - - private static final long serialVersionUID = -8161003286845820286L; - - NoActiveChannelException() { - super(); - } + } + + /** + * @return The current listen key. + * @throws NoActiveChannelException If no listen key is currently available. + */ + String getListenKey() throws NoActiveChannelException { + if (listenKey == null) throw new NoActiveChannelException(); + return listenKey; + } + + @Override + public void close() { + keepAlive.dispose(); + } + + /** + * Thrown on calls to {@link BinanceUserDataChannel#getListenKey()} if no channel is currently + * open. + * + * @author Graham Crockford + */ + static final class NoActiveChannelException extends Exception { + + private static final long serialVersionUID = -8161003286845820286L; + + NoActiveChannelException() { + super(); } -} \ No newline at end of file + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataStreamingService.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataStreamingService.java index afba71b04..90ed409af 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataStreamingService.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceUserDataStreamingService.java @@ -1,70 +1,66 @@ package info.bitrich.xchangestream.binance; import com.fasterxml.jackson.databind.JsonNode; - import info.bitrich.xchangestream.binance.dto.BaseBinanceWebSocketTransaction.BinanceWebSocketTypes; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; - import io.reactivex.Observable; - +import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; - public class BinanceUserDataStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(BinanceUserDataStreamingService.class); - - private static final String USER_API_BASE_URI = "wss://stream.binance.com:9443/ws/"; - - public static BinanceUserDataStreamingService create(String listenKey) { - return new BinanceUserDataStreamingService(USER_API_BASE_URI + listenKey); - } - - private BinanceUserDataStreamingService(String url) { - super(url, Integer.MAX_VALUE); - } + private static final Logger LOG = LoggerFactory.getLogger(BinanceUserDataStreamingService.class); - public Observable subscribeChannel(BinanceWebSocketTypes eventType) { - return super.subscribeChannel(eventType.getSerializedValue()); - } + private static final String USER_API_BASE_URI = "wss://stream.binance.com:9443/ws/"; - @Override - public void messageHandler(String message) { - LOG.debug("Received message: {}", message); - super.messageHandler(message); - } + public static BinanceUserDataStreamingService create(String listenKey) { + return new BinanceUserDataStreamingService(USER_API_BASE_URI + listenKey); + } - @Override - protected void handleMessage(JsonNode message) { - try { - super.handleMessage(message); - } catch (Exception e) { - LOG.error("Error handling message: " + message, e); - return; - } - } + private BinanceUserDataStreamingService(String url) { + super(url, Integer.MAX_VALUE); + } - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - return message.get("e").asText(); - } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - // No op. Disconnecting from the web socket will cancel subscriptions. - return null; - } + public Observable subscribeChannel(BinanceWebSocketTypes eventType) { + return super.subscribeChannel(eventType.getSerializedValue()); + } - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - // No op. Disconnecting from the web socket will cancel subscriptions. - return null; - } + @Override + public void messageHandler(String message) { + LOG.debug("Received message: {}", message); + super.messageHandler(message); + } - @Override - public void sendMessage(String message) { - // Subscriptions are made upon connection - no messages are sent. + @Override + protected void handleMessage(JsonNode message) { + try { + super.handleMessage(message); + } catch (Exception e) { + LOG.error("Error handling message: " + message, e); + return; } + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + return message.get("e").asText(); + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + // No op. Disconnecting from the web socket will cancel subscriptions. + return null; + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + // No op. Disconnecting from the web socket will cancel subscriptions. + return null; + } + + @Override + public void sendMessage(String message) { + // Subscriptions are made upon connection - no messages are sent. + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BaseBinanceWebSocketTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BaseBinanceWebSocketTransaction.java index 0a6349649..c8362e4b1 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BaseBinanceWebSocketTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BaseBinanceWebSocketTransaction.java @@ -1,60 +1,59 @@ package info.bitrich.xchangestream.binance.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Date; public class BaseBinanceWebSocketTransaction { - public enum BinanceWebSocketTypes { - DEPTH_UPDATE("depthUpdate"), - TICKER_24_HR("24hrTicker"), - KLINE("kline"), - AGG_TRADE("aggTrade"), - TRADE("trade"), - OUTBOUND_ACCOUNT_INFO("outboundAccountInfo"), - EXECUTION_REPORT("executionReport"); - - /** - * Get a type from the `type` string of a `ProductBinanceWebSocketTransaction`. - * @param value The string representation. - * @return THe enum value. - */ - public static BinanceWebSocketTypes fromTransactionValue(String value) { - for (BinanceWebSocketTypes type : BinanceWebSocketTypes.values()) { - if (type.serializedValue.equals(value)) { - return type; - } - } - return null; + public enum BinanceWebSocketTypes { + DEPTH_UPDATE("depthUpdate"), + TICKER_24_HR("24hrTicker"), + KLINE("kline"), + AGG_TRADE("aggTrade"), + TRADE("trade"), + OUTBOUND_ACCOUNT_INFO("outboundAccountInfo"), + EXECUTION_REPORT("executionReport"); + + /** + * Get a type from the `type` string of a `ProductBinanceWebSocketTransaction`. + * + * @param value The string representation. + * @return THe enum value. + */ + public static BinanceWebSocketTypes fromTransactionValue(String value) { + for (BinanceWebSocketTypes type : BinanceWebSocketTypes.values()) { + if (type.serializedValue.equals(value)) { + return type; } + } + return null; + } - private String serializedValue; + private String serializedValue; - BinanceWebSocketTypes(String serializedValue) { - this.serializedValue = serializedValue; - } + BinanceWebSocketTypes(String serializedValue) { + this.serializedValue = serializedValue; + } - public String getSerializedValue() { - return serializedValue; - } + public String getSerializedValue() { + return serializedValue; } + } - protected final BinanceWebSocketTypes eventType; - protected final Date eventTime; + protected final BinanceWebSocketTypes eventType; + protected final Date eventTime; - public BaseBinanceWebSocketTransaction( - @JsonProperty("e") String _eventType, - @JsonProperty("E") String _eventTime) { - eventType = BinanceWebSocketTypes.fromTransactionValue(_eventType); - eventTime = new Date(Long.parseLong(_eventTime)); - } + public BaseBinanceWebSocketTransaction( + @JsonProperty("e") String _eventType, @JsonProperty("E") String _eventTime) { + eventType = BinanceWebSocketTypes.fromTransactionValue(_eventType); + eventTime = new Date(Long.parseLong(_eventTime)); + } - public BinanceWebSocketTypes getEventType() { - return eventType; - } + public BinanceWebSocketTypes getEventType() { + return eventType; + } - public Date getEventTime() { - return eventTime; - } + public Date getEventTime() { + return eventTime; + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceRawTrade.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceRawTrade.java index 9605678e8..cdfaf9d86 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceRawTrade.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceRawTrade.java @@ -3,75 +3,84 @@ import java.math.BigDecimal; public class BinanceRawTrade { - private final String eventType; - private final String eventTime; - private final String symbol; - private final long tradeId; - private final BigDecimal price; - private final BigDecimal quantity; - private final long buyerOrderId; - private final long sellerOrderId; - private final long timestamp; - private final boolean buyerMarketMaker; - private final boolean ignore; + private final String eventType; + private final String eventTime; + private final String symbol; + private final long tradeId; + private final BigDecimal price; + private final BigDecimal quantity; + private final long buyerOrderId; + private final long sellerOrderId; + private final long timestamp; + private final boolean buyerMarketMaker; + private final boolean ignore; - public BinanceRawTrade(String eventType, String eventTime, String symbol, long tradeId, BigDecimal price, - BigDecimal quantity, long buyerOrderId, long sellerOrderId, long timestamp, - boolean buyerMarketMaker, boolean ignore) { - this.eventType = eventType; - this.eventTime = eventTime; - this.symbol = symbol; - this.tradeId = tradeId; - this.price = price; - this.quantity = quantity; - this.buyerOrderId = buyerOrderId; - this.sellerOrderId = sellerOrderId; - this.timestamp = timestamp; - this.buyerMarketMaker = buyerMarketMaker; - this.ignore = ignore; - } + public BinanceRawTrade( + String eventType, + String eventTime, + String symbol, + long tradeId, + BigDecimal price, + BigDecimal quantity, + long buyerOrderId, + long sellerOrderId, + long timestamp, + boolean buyerMarketMaker, + boolean ignore) { + this.eventType = eventType; + this.eventTime = eventTime; + this.symbol = symbol; + this.tradeId = tradeId; + this.price = price; + this.quantity = quantity; + this.buyerOrderId = buyerOrderId; + this.sellerOrderId = sellerOrderId; + this.timestamp = timestamp; + this.buyerMarketMaker = buyerMarketMaker; + this.ignore = ignore; + } - public String getEventType() { - return eventType; - } + public String getEventType() { + return eventType; + } - public String getEventTime() { - return eventTime; - } + public String getEventTime() { + return eventTime; + } - public String getSymbol() { - return symbol; - } + public String getSymbol() { + return symbol; + } - public long getTradeId() { - return tradeId; - } + public long getTradeId() { + return tradeId; + } - public BigDecimal getPrice() { - return price; - } + public BigDecimal getPrice() { + return price; + } - public BigDecimal getQuantity() { - return quantity; - } + public BigDecimal getQuantity() { + return quantity; + } - public long getBuyerOrderId() { - return buyerOrderId; - } + public long getBuyerOrderId() { + return buyerOrderId; + } - public long getSellerOrderId() { - return sellerOrderId; - } + public long getSellerOrderId() { + return sellerOrderId; + } - public long getTimestamp() { - return timestamp; - } + public long getTimestamp() { + return timestamp; + } - public boolean isBuyerMarketMaker() { - return buyerMarketMaker; - } + public boolean isBuyerMarketMaker() { + return buyerMarketMaker; + } - public boolean isIgnore() { - return ignore; - } + public boolean isIgnore() { + return ignore; + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketBalance.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketBalance.java index 2621f4363..c7319ac07 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketBalance.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketBalance.java @@ -1,47 +1,47 @@ package info.bitrich.xchangestream.binance.dto; import com.fasterxml.jackson.annotation.JsonProperty; - +import java.math.BigDecimal; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.account.Balance; -import java.math.BigDecimal; - public final class BinanceWebsocketBalance { - private final Currency currency; - private final BigDecimal free; - private final BigDecimal locked; - - public BinanceWebsocketBalance(@JsonProperty("a") String asset, @JsonProperty("f") BigDecimal free, - @JsonProperty("l") BigDecimal locked) { - this.currency = Currency.getInstance(asset); - this.locked = locked; - this.free = free; - } - - public Currency getCurrency() { - return currency; - } - - public BigDecimal getTotal() { - return free.add(locked); - } - - public BigDecimal getAvailable() { - return free; - } - - public BigDecimal getLocked() { - return locked; - } - - public Balance toBalance() { - return new Balance(currency, getTotal(), getAvailable(), getLocked()); - } - - @Override - public String toString() { - return "Balance[currency=" + currency + ", free=" + free + ", locked=" + locked + "]"; - } -} \ No newline at end of file + private final Currency currency; + private final BigDecimal free; + private final BigDecimal locked; + + public BinanceWebsocketBalance( + @JsonProperty("a") String asset, + @JsonProperty("f") BigDecimal free, + @JsonProperty("l") BigDecimal locked) { + this.currency = Currency.getInstance(asset); + this.locked = locked; + this.free = free; + } + + public Currency getCurrency() { + return currency; + } + + public BigDecimal getTotal() { + return free.add(locked); + } + + public BigDecimal getAvailable() { + return free; + } + + public BigDecimal getLocked() { + return locked; + } + + public Balance toBalance() { + return new Balance(currency, getTotal(), getAvailable(), getLocked()); + } + + @Override + public String toString() { + return "Balance[currency=" + currency + ", free=" + free + ", locked=" + locked + "]"; + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketTransaction.java index dd4deb29c..7b9e5077f 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/BinanceWebsocketTransaction.java @@ -3,22 +3,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class BinanceWebsocketTransaction { - private String stream; - private T data; + private String stream; + private T data; - public BinanceWebsocketTransaction( - @JsonProperty("stream") String stream, - @JsonProperty("data") T data - ) { - this.stream = stream; - this.data = data; - } + public BinanceWebsocketTransaction( + @JsonProperty("stream") String stream, @JsonProperty("data") T data) { + this.stream = stream; + this.data = data; + } - public String getStream() { - return stream; - } + public String getStream() { + return stream; + } - public T getData() { - return data; - } + public T getData() { + return data; + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransaction.java index cd4b049be..3b666e19f 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransaction.java @@ -1,40 +1,38 @@ package info.bitrich.xchangestream.binance.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.binance.dto.marketdata.BinanceOrderbook; - import java.util.List; +import org.knowm.xchange.binance.dto.marketdata.BinanceOrderbook; public class DepthBinanceWebSocketTransaction extends ProductBinanceWebSocketTransaction { - private final BinanceOrderbook orderBook; - private final long lastUpdateId; - private final long firstUpdateId; - - public DepthBinanceWebSocketTransaction( - @JsonProperty("e") String eventType, - @JsonProperty("E") String eventTime, - @JsonProperty("s") String symbol, - @JsonProperty("U") long firstUpdateId, - @JsonProperty("u") long lastUpdateId, - @JsonProperty("b") List _bids, - @JsonProperty("a") List _asks - ) { - super(eventType, eventTime, symbol); - this.firstUpdateId = firstUpdateId; - this.lastUpdateId = lastUpdateId; - orderBook = new BinanceOrderbook(lastUpdateId, _bids, _asks); - } - - public BinanceOrderbook getOrderBook() { - return orderBook; - } - - public long getFirstUpdateId() { - return firstUpdateId; - } - - public long getLastUpdateId() { - return lastUpdateId; - } + private final BinanceOrderbook orderBook; + private final long lastUpdateId; + private final long firstUpdateId; + + public DepthBinanceWebSocketTransaction( + @JsonProperty("e") String eventType, + @JsonProperty("E") String eventTime, + @JsonProperty("s") String symbol, + @JsonProperty("U") long firstUpdateId, + @JsonProperty("u") long lastUpdateId, + @JsonProperty("b") List _bids, + @JsonProperty("a") List _asks) { + super(eventType, eventTime, symbol); + this.firstUpdateId = firstUpdateId; + this.lastUpdateId = lastUpdateId; + orderBook = new BinanceOrderbook(lastUpdateId, _bids, _asks); + } + + public BinanceOrderbook getOrderBook() { + return orderBook; + } + + public long getFirstUpdateId() { + return firstUpdateId; + } + + public long getLastUpdateId() { + return lastUpdateId; + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ExecutionReportBinanceUserTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ExecutionReportBinanceUserTransaction.java index b4bc5c162..239708e1d 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ExecutionReportBinanceUserTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ExecutionReportBinanceUserTransaction.java @@ -1,7 +1,7 @@ package info.bitrich.xchangestream.binance.dto; import com.fasterxml.jackson.annotation.JsonProperty; - +import java.math.BigDecimal; import org.knowm.xchange.binance.BinanceAdapters; import org.knowm.xchange.binance.dto.trade.BinanceOrder; import org.knowm.xchange.binance.dto.trade.OrderSide; @@ -12,225 +12,263 @@ import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.UserTrade; -import java.math.BigDecimal; - public class ExecutionReportBinanceUserTransaction extends ProductBinanceWebSocketTransaction { - public enum ExecutionType { - NEW, CANCELED, REPLACED, REJECTED, TRADE, EXPIRED - } - - private final String clientOrderId; - private final OrderSide side; - private final OrderType orderType; - private final TimeInForce timeInForce; - private final BigDecimal orderQuantity; - private final BigDecimal orderPrice; - private final BigDecimal stopPrice; - private final BigDecimal icebergQuantity; - private final ExecutionType executionType; - private final OrderStatus currentOrderStatus; - private final String orderRejectReason; - private final long orderId; - private final BigDecimal lastExecutedQuantity; - private final BigDecimal cumulativeFilledQuantity; - private final BigDecimal lastExecutedPrice; - private final BigDecimal commissionAmount; - private final String commissionAsset; - private final long timestamp; - private final long tradeId; - private final boolean working; - private final boolean buyerMarketMaker; - private final BigDecimal cumulativeQuoteAssetTransactedQuantity; - - public ExecutionReportBinanceUserTransaction( - @JsonProperty("e") String eventType, - @JsonProperty("E") String eventTime, - @JsonProperty("s") String symbol, - @JsonProperty("c") String clientOrderId, - @JsonProperty("S") String side, - @JsonProperty("o") String orderType, - @JsonProperty("f") String timeInForce, - @JsonProperty("q") BigDecimal quantity, - @JsonProperty("p") BigDecimal price, - @JsonProperty("P") BigDecimal stopPrice, - @JsonProperty("F") BigDecimal icebergQuantity, - @JsonProperty("x") String currentExecutionType, - @JsonProperty("X") String currentOrderStatus, - @JsonProperty("r") String orderRejectReason, - @JsonProperty("i") long orderId, - @JsonProperty("l") BigDecimal lastExecutedQuantity, - @JsonProperty("z") BigDecimal cumulativeFilledQuantity, - @JsonProperty("L") BigDecimal lastExecutedPrice, - @JsonProperty("n") BigDecimal commissionAmount, - @JsonProperty("N") String commissionAsset, - @JsonProperty("T") long timestamp, - @JsonProperty("t") long tradeId, - @JsonProperty("w") boolean working, - @JsonProperty("m") boolean buyerMarketMaker, - @JsonProperty("Z") BigDecimal cumulativeQuoteAssetTransactedQuantity) - { - super(eventType, eventTime, symbol); - this.clientOrderId = clientOrderId; - this.side = OrderSide.valueOf(side); - this.orderType = OrderType.valueOf(orderType); - this.timeInForce = TimeInForce.valueOf(timeInForce); - this.orderQuantity = quantity; - this.orderPrice = price; - this.stopPrice = stopPrice; - this.icebergQuantity = icebergQuantity; - this.executionType = ExecutionType.valueOf(currentExecutionType); - this.currentOrderStatus = OrderStatus.valueOf(currentOrderStatus); - this.orderRejectReason = orderRejectReason; - this.orderId = orderId; - this.lastExecutedQuantity = lastExecutedQuantity; - this.cumulativeFilledQuantity = cumulativeFilledQuantity; - this.lastExecutedPrice = lastExecutedPrice; - this.commissionAmount = commissionAmount; - this.commissionAsset = commissionAsset; - this.timestamp = timestamp; - this.tradeId = tradeId; - this.working = working; - this.buyerMarketMaker = buyerMarketMaker; - this.cumulativeQuoteAssetTransactedQuantity = cumulativeQuoteAssetTransactedQuantity; - } - - public String getClientOrderId() { - return clientOrderId; - } - - public OrderSide getSide() { - return side; - } - - public OrderType getOrderType() { - return orderType; - } - - public TimeInForce getTimeInForce() { - return timeInForce; - } - - public BigDecimal getOrderQuantity() { - return orderQuantity; - } - - public BigDecimal getOrderPrice() { - return orderPrice; - } - - public BigDecimal getStopPrice() { - return stopPrice; - } - - public BigDecimal getIcebergQuantity() { - return icebergQuantity; - } - - public ExecutionType getExecutionType() { - return executionType; - } - - public OrderStatus getCurrentOrderStatus() { - return currentOrderStatus; - } - - public String getOrderRejectReason() { - return orderRejectReason; - } - - public long getOrderId() { - return orderId; - } - - public BigDecimal getLastExecutedQuantity() { - return lastExecutedQuantity; - } - - public BigDecimal getCumulativeFilledQuantity() { - return cumulativeFilledQuantity; - } - - public BigDecimal getLastExecutedPrice() { - return lastExecutedPrice; - } - - public BigDecimal getCommissionAmount() { - return commissionAmount; - } - - public String getCommissionAsset() { - return commissionAsset; - } - - public long getTimestamp() { - return timestamp; - } - - public long getTradeId() { - return tradeId; - } - - public boolean isWorking() { - return working; - } - - public boolean isBuyerMarketMaker() { - return buyerMarketMaker; - } - - public BigDecimal getCumulativeQuoteAssetTransactedQuantity() { - return cumulativeQuoteAssetTransactedQuantity; - } - - public UserTrade toUserTrade() { - if (executionType != ExecutionType.TRADE) - throw new IllegalStateException("Not a trade"); - return new UserTrade.Builder() - .type(BinanceAdapters.convert(side)) - .originalAmount(lastExecutedQuantity) - .currencyPair(currencyPair) - .price(lastExecutedPrice) - .timestamp(getEventTime()) - .id(Long.toString(tradeId)) - .orderId(Long.toString(orderId)) - .feeAmount(commissionAmount) - .feeCurrency(Currency.getInstance(commissionAsset)) - .build(); - } - - public Order toOrder() { - return BinanceAdapters.adaptOrder( - new BinanceOrder( - BinanceAdapters.toSymbol(getCurrencyPair()), - orderId, - clientOrderId, - orderPrice, - orderQuantity, - lastExecutedQuantity, - cumulativeFilledQuantity, - currentOrderStatus, - timeInForce, - orderType, - side, - stopPrice, - BigDecimal.ZERO, - timestamp - ) - ); - } - - @Override - public String toString() { - return "ExecutionReportBinanceUserTransaction [eventTime=" + getEventTime() + ", currencyPair=" - + getCurrencyPair() + ", clientOrderId=" + clientOrderId + ", side=" + side + ", orderType=" + orderType - + ", timeInForce=" + timeInForce + ", quantity=" + orderQuantity + ", price=" + orderPrice - + ", stopPrice=" + stopPrice + ", icebergQuantity=" + icebergQuantity + ", executionType=" - + executionType + ", currentOrderStatus=" + currentOrderStatus + ", orderRejectReason=" - + orderRejectReason + ", orderId=" + orderId + ", lastExecutedQuantity=" + lastExecutedQuantity - + ", cumulativeFilledQuantity=" + cumulativeFilledQuantity + ", lastExecutedPrice=" + lastExecutedPrice - + ", commissionAmount=" + commissionAmount + ", commissionAsset=" + commissionAsset + ", timestamp=" - + timestamp + ", tradeId=" + tradeId + ", working=" + working + ", buyerMarketMaker=" + buyerMarketMaker - + ", cumulativeQuoteAssetTransactedQuantity=" + cumulativeQuoteAssetTransactedQuantity + "]"; - } -} \ No newline at end of file + public enum ExecutionType { + NEW, + CANCELED, + REPLACED, + REJECTED, + TRADE, + EXPIRED + } + + private final String clientOrderId; + private final OrderSide side; + private final OrderType orderType; + private final TimeInForce timeInForce; + private final BigDecimal orderQuantity; + private final BigDecimal orderPrice; + private final BigDecimal stopPrice; + private final BigDecimal icebergQuantity; + private final ExecutionType executionType; + private final OrderStatus currentOrderStatus; + private final String orderRejectReason; + private final long orderId; + private final BigDecimal lastExecutedQuantity; + private final BigDecimal cumulativeFilledQuantity; + private final BigDecimal lastExecutedPrice; + private final BigDecimal commissionAmount; + private final String commissionAsset; + private final long timestamp; + private final long tradeId; + private final boolean working; + private final boolean buyerMarketMaker; + private final BigDecimal cumulativeQuoteAssetTransactedQuantity; + + public ExecutionReportBinanceUserTransaction( + @JsonProperty("e") String eventType, + @JsonProperty("E") String eventTime, + @JsonProperty("s") String symbol, + @JsonProperty("c") String clientOrderId, + @JsonProperty("S") String side, + @JsonProperty("o") String orderType, + @JsonProperty("f") String timeInForce, + @JsonProperty("q") BigDecimal quantity, + @JsonProperty("p") BigDecimal price, + @JsonProperty("P") BigDecimal stopPrice, + @JsonProperty("F") BigDecimal icebergQuantity, + @JsonProperty("x") String currentExecutionType, + @JsonProperty("X") String currentOrderStatus, + @JsonProperty("r") String orderRejectReason, + @JsonProperty("i") long orderId, + @JsonProperty("l") BigDecimal lastExecutedQuantity, + @JsonProperty("z") BigDecimal cumulativeFilledQuantity, + @JsonProperty("L") BigDecimal lastExecutedPrice, + @JsonProperty("n") BigDecimal commissionAmount, + @JsonProperty("N") String commissionAsset, + @JsonProperty("T") long timestamp, + @JsonProperty("t") long tradeId, + @JsonProperty("w") boolean working, + @JsonProperty("m") boolean buyerMarketMaker, + @JsonProperty("Z") BigDecimal cumulativeQuoteAssetTransactedQuantity) { + super(eventType, eventTime, symbol); + this.clientOrderId = clientOrderId; + this.side = OrderSide.valueOf(side); + this.orderType = OrderType.valueOf(orderType); + this.timeInForce = TimeInForce.valueOf(timeInForce); + this.orderQuantity = quantity; + this.orderPrice = price; + this.stopPrice = stopPrice; + this.icebergQuantity = icebergQuantity; + this.executionType = ExecutionType.valueOf(currentExecutionType); + this.currentOrderStatus = OrderStatus.valueOf(currentOrderStatus); + this.orderRejectReason = orderRejectReason; + this.orderId = orderId; + this.lastExecutedQuantity = lastExecutedQuantity; + this.cumulativeFilledQuantity = cumulativeFilledQuantity; + this.lastExecutedPrice = lastExecutedPrice; + this.commissionAmount = commissionAmount; + this.commissionAsset = commissionAsset; + this.timestamp = timestamp; + this.tradeId = tradeId; + this.working = working; + this.buyerMarketMaker = buyerMarketMaker; + this.cumulativeQuoteAssetTransactedQuantity = cumulativeQuoteAssetTransactedQuantity; + } + + public String getClientOrderId() { + return clientOrderId; + } + + public OrderSide getSide() { + return side; + } + + public OrderType getOrderType() { + return orderType; + } + + public TimeInForce getTimeInForce() { + return timeInForce; + } + + public BigDecimal getOrderQuantity() { + return orderQuantity; + } + + public BigDecimal getOrderPrice() { + return orderPrice; + } + + public BigDecimal getStopPrice() { + return stopPrice; + } + + public BigDecimal getIcebergQuantity() { + return icebergQuantity; + } + + public ExecutionType getExecutionType() { + return executionType; + } + + public OrderStatus getCurrentOrderStatus() { + return currentOrderStatus; + } + + public String getOrderRejectReason() { + return orderRejectReason; + } + + public long getOrderId() { + return orderId; + } + + public BigDecimal getLastExecutedQuantity() { + return lastExecutedQuantity; + } + + public BigDecimal getCumulativeFilledQuantity() { + return cumulativeFilledQuantity; + } + + public BigDecimal getLastExecutedPrice() { + return lastExecutedPrice; + } + + public BigDecimal getCommissionAmount() { + return commissionAmount; + } + + public String getCommissionAsset() { + return commissionAsset; + } + + public long getTimestamp() { + return timestamp; + } + + public long getTradeId() { + return tradeId; + } + + public boolean isWorking() { + return working; + } + + public boolean isBuyerMarketMaker() { + return buyerMarketMaker; + } + + public BigDecimal getCumulativeQuoteAssetTransactedQuantity() { + return cumulativeQuoteAssetTransactedQuantity; + } + + public UserTrade toUserTrade() { + if (executionType != ExecutionType.TRADE) throw new IllegalStateException("Not a trade"); + return new UserTrade.Builder() + .type(BinanceAdapters.convert(side)) + .originalAmount(lastExecutedQuantity) + .currencyPair(currencyPair) + .price(lastExecutedPrice) + .timestamp(getEventTime()) + .id(Long.toString(tradeId)) + .orderId(Long.toString(orderId)) + .feeAmount(commissionAmount) + .feeCurrency(Currency.getInstance(commissionAsset)) + .build(); + } + + public Order toOrder() { + return BinanceAdapters.adaptOrder( + new BinanceOrder( + BinanceAdapters.toSymbol(getCurrencyPair()), + orderId, + clientOrderId, + orderPrice, + orderQuantity, + lastExecutedQuantity, + cumulativeFilledQuantity, + currentOrderStatus, + timeInForce, + orderType, + side, + stopPrice, + BigDecimal.ZERO, + timestamp)); + } + + @Override + public String toString() { + return "ExecutionReportBinanceUserTransaction [eventTime=" + + getEventTime() + + ", currencyPair=" + + getCurrencyPair() + + ", clientOrderId=" + + clientOrderId + + ", side=" + + side + + ", orderType=" + + orderType + + ", timeInForce=" + + timeInForce + + ", quantity=" + + orderQuantity + + ", price=" + + orderPrice + + ", stopPrice=" + + stopPrice + + ", icebergQuantity=" + + icebergQuantity + + ", executionType=" + + executionType + + ", currentOrderStatus=" + + currentOrderStatus + + ", orderRejectReason=" + + orderRejectReason + + ", orderId=" + + orderId + + ", lastExecutedQuantity=" + + lastExecutedQuantity + + ", cumulativeFilledQuantity=" + + cumulativeFilledQuantity + + ", lastExecutedPrice=" + + lastExecutedPrice + + ", commissionAmount=" + + commissionAmount + + ", commissionAsset=" + + commissionAsset + + ", timestamp=" + + timestamp + + ", tradeId=" + + tradeId + + ", working=" + + working + + ", buyerMarketMaker=" + + buyerMarketMaker + + ", cumulativeQuoteAssetTransactedQuantity=" + + cumulativeQuoteAssetTransactedQuantity + + "]"; + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/OutboundAccountInfoBinanceWebsocketTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/OutboundAccountInfoBinanceWebsocketTransaction.java index a609109e4..8c5282efa 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/OutboundAccountInfoBinanceWebsocketTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/OutboundAccountInfoBinanceWebsocketTransaction.java @@ -1,89 +1,102 @@ package info.bitrich.xchangestream.binance.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; import java.util.List; -public class OutboundAccountInfoBinanceWebsocketTransaction extends BaseBinanceWebSocketTransaction { +public class OutboundAccountInfoBinanceWebsocketTransaction + extends BaseBinanceWebSocketTransaction { - private final BigDecimal makerCommissionRate; - private final BigDecimal takerCommissionRate; - private final BigDecimal buyerCommissionRate; - private final BigDecimal sellerCommissionRate; - private final boolean canTrade; - private final boolean canWithdraw; - private final boolean canDeposit; - private final long lastUpdateTimestamp; - private final List balances; + private final BigDecimal makerCommissionRate; + private final BigDecimal takerCommissionRate; + private final BigDecimal buyerCommissionRate; + private final BigDecimal sellerCommissionRate; + private final boolean canTrade; + private final boolean canWithdraw; + private final boolean canDeposit; + private final long lastUpdateTimestamp; + private final List balances; - public OutboundAccountInfoBinanceWebsocketTransaction( - @JsonProperty("e") String eventType, - @JsonProperty("E") String eventTime, - @JsonProperty("m") BigDecimal makerCommissionRate, - @JsonProperty("t") BigDecimal takerCommissionRate, - @JsonProperty("b") BigDecimal buyerCommissionRate, - @JsonProperty("s") BigDecimal sellerCommissionRate, - @JsonProperty("T") boolean canTrade, - @JsonProperty("W") boolean canWithdraw, - @JsonProperty("D") boolean canDeposit, - @JsonProperty("u") long lastUpdateTimestamp, - @JsonProperty("B") List balances) - { - super(eventType, eventTime); - this.makerCommissionRate = makerCommissionRate; - this.takerCommissionRate = takerCommissionRate; - this.buyerCommissionRate = buyerCommissionRate; - this.sellerCommissionRate = sellerCommissionRate; - this.canTrade = canTrade; - this.canWithdraw = canWithdraw; - this.canDeposit = canDeposit; - this.lastUpdateTimestamp = lastUpdateTimestamp; - this.balances = balances; - } + public OutboundAccountInfoBinanceWebsocketTransaction( + @JsonProperty("e") String eventType, + @JsonProperty("E") String eventTime, + @JsonProperty("m") BigDecimal makerCommissionRate, + @JsonProperty("t") BigDecimal takerCommissionRate, + @JsonProperty("b") BigDecimal buyerCommissionRate, + @JsonProperty("s") BigDecimal sellerCommissionRate, + @JsonProperty("T") boolean canTrade, + @JsonProperty("W") boolean canWithdraw, + @JsonProperty("D") boolean canDeposit, + @JsonProperty("u") long lastUpdateTimestamp, + @JsonProperty("B") List balances) { + super(eventType, eventTime); + this.makerCommissionRate = makerCommissionRate; + this.takerCommissionRate = takerCommissionRate; + this.buyerCommissionRate = buyerCommissionRate; + this.sellerCommissionRate = sellerCommissionRate; + this.canTrade = canTrade; + this.canWithdraw = canWithdraw; + this.canDeposit = canDeposit; + this.lastUpdateTimestamp = lastUpdateTimestamp; + this.balances = balances; + } - public BigDecimal getMakerCommissionRate() { - return makerCommissionRate; - } + public BigDecimal getMakerCommissionRate() { + return makerCommissionRate; + } - public BigDecimal getTakerCommissionRate() { - return takerCommissionRate; - } + public BigDecimal getTakerCommissionRate() { + return takerCommissionRate; + } - public BigDecimal getBuyerCommissionRate() { - return buyerCommissionRate; - } + public BigDecimal getBuyerCommissionRate() { + return buyerCommissionRate; + } - public BigDecimal getSellerCommissionRate() { - return sellerCommissionRate; - } + public BigDecimal getSellerCommissionRate() { + return sellerCommissionRate; + } - public boolean isCanTrade() { - return canTrade; - } + public boolean isCanTrade() { + return canTrade; + } - public boolean isCanWithdraw() { - return canWithdraw; - } + public boolean isCanWithdraw() { + return canWithdraw; + } - public boolean isCanDeposit() { - return canDeposit; - } + public boolean isCanDeposit() { + return canDeposit; + } - public long getLastUpdateTimestamp() { - return lastUpdateTimestamp; - } + public long getLastUpdateTimestamp() { + return lastUpdateTimestamp; + } - public List getBalances() { - return balances; - } + public List getBalances() { + return balances; + } - @Override - public String toString() { - return "OutboundAccountInfoBinanceWebsocketTransaction [makerCommissionRate=" + makerCommissionRate - + ", takerCommissionRate=" + takerCommissionRate + ", buyerCommissionRate=" + buyerCommissionRate - + ", sellerCommissionRate=" + sellerCommissionRate + ", canTrade=" + canTrade + ", canWithdraw=" - + canWithdraw + ", canDeposit=" + canDeposit + ", lastUpdateTimestamp=" + lastUpdateTimestamp - + ", balances=" + balances + "]"; - } + @Override + public String toString() { + return "OutboundAccountInfoBinanceWebsocketTransaction [makerCommissionRate=" + + makerCommissionRate + + ", takerCommissionRate=" + + takerCommissionRate + + ", buyerCommissionRate=" + + buyerCommissionRate + + ", sellerCommissionRate=" + + sellerCommissionRate + + ", canTrade=" + + canTrade + + ", canWithdraw=" + + canWithdraw + + ", canDeposit=" + + canDeposit + + ", lastUpdateTimestamp=" + + lastUpdateTimestamp + + ", balances=" + + balances + + "]"; + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ProductBinanceWebSocketTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ProductBinanceWebSocketTransaction.java index 6284e2c91..bebf5b4a1 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ProductBinanceWebSocketTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/ProductBinanceWebSocketTransaction.java @@ -6,17 +6,17 @@ public class ProductBinanceWebSocketTransaction extends BaseBinanceWebSocketTransaction { - protected final CurrencyPair currencyPair; + protected final CurrencyPair currencyPair; - public ProductBinanceWebSocketTransaction( - @JsonProperty("e") String eventType, - @JsonProperty("E") String eventTime, - @JsonProperty("s") String symbol) { - super(eventType, eventTime); - currencyPair = BinanceAdapters.adaptSymbol(symbol); - } + public ProductBinanceWebSocketTransaction( + @JsonProperty("e") String eventType, + @JsonProperty("E") String eventTime, + @JsonProperty("s") String symbol) { + super(eventType, eventTime); + currencyPair = BinanceAdapters.adaptSymbol(symbol); + } - public CurrencyPair getCurrencyPair() { - return currencyPair; - } + public CurrencyPair getCurrencyPair() { + return currencyPair; + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransaction.java index 243906a6a..5907d73b8 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransaction.java @@ -1,69 +1,67 @@ package info.bitrich.xchangestream.binance.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; - import org.knowm.xchange.binance.dto.marketdata.BinanceTicker24h; public class TickerBinanceWebsocketTransaction extends ProductBinanceWebSocketTransaction { - private final BinanceTicker24h ticker; - - public TickerBinanceWebsocketTransaction( - @JsonProperty("e") String eventType, - @JsonProperty("E") String eventTime, - @JsonProperty("s") String symbol, - @JsonProperty("p") BigDecimal priceChange, - @JsonProperty("P") BigDecimal priceChangePercent, - @JsonProperty("w") BigDecimal weightedAvgPrice, - @JsonProperty("x") BigDecimal prevClosePrice, - @JsonProperty("c") BigDecimal lastPrice, - @JsonProperty("Q") BigDecimal lastQty, - @JsonProperty("b") BigDecimal bidPrice, - @JsonProperty("B") BigDecimal bidQty, - @JsonProperty("a") BigDecimal askPrice, - @JsonProperty("A") BigDecimal askQty, - @JsonProperty("o") BigDecimal openPrice, - @JsonProperty("h") BigDecimal highPrice, - @JsonProperty("l") BigDecimal lowPrice, - @JsonProperty("v") BigDecimal volume, - @JsonProperty("q") BigDecimal quoteVolume, - @JsonProperty("O") Long openTime, - @JsonProperty("C") Long closeTime, - @JsonProperty("F") Long firstId, - @JsonProperty("L") Long lastId, - @JsonProperty("n") Long count) { + private final BinanceTicker24h ticker; - super(eventType, eventTime, symbol); + public TickerBinanceWebsocketTransaction( + @JsonProperty("e") String eventType, + @JsonProperty("E") String eventTime, + @JsonProperty("s") String symbol, + @JsonProperty("p") BigDecimal priceChange, + @JsonProperty("P") BigDecimal priceChangePercent, + @JsonProperty("w") BigDecimal weightedAvgPrice, + @JsonProperty("x") BigDecimal prevClosePrice, + @JsonProperty("c") BigDecimal lastPrice, + @JsonProperty("Q") BigDecimal lastQty, + @JsonProperty("b") BigDecimal bidPrice, + @JsonProperty("B") BigDecimal bidQty, + @JsonProperty("a") BigDecimal askPrice, + @JsonProperty("A") BigDecimal askQty, + @JsonProperty("o") BigDecimal openPrice, + @JsonProperty("h") BigDecimal highPrice, + @JsonProperty("l") BigDecimal lowPrice, + @JsonProperty("v") BigDecimal volume, + @JsonProperty("q") BigDecimal quoteVolume, + @JsonProperty("O") Long openTime, + @JsonProperty("C") Long closeTime, + @JsonProperty("F") Long firstId, + @JsonProperty("L") Long lastId, + @JsonProperty("n") Long count) { - ticker = new BinanceTicker24h( - priceChange, - priceChangePercent, - weightedAvgPrice, - prevClosePrice, - lastPrice, - lastQty, - bidPrice, - bidQty, - askPrice, - askQty, - openPrice, - highPrice, - lowPrice, - volume, - quoteVolume, - openTime, - closeTime, - firstId, - lastId, - count, - symbol); - ticker.setCurrencyPair(currencyPair); - } + super(eventType, eventTime, symbol); - public BinanceTicker24h getTicker() { - return ticker; - } + ticker = + new BinanceTicker24h( + priceChange, + priceChangePercent, + weightedAvgPrice, + prevClosePrice, + lastPrice, + lastQty, + bidPrice, + bidQty, + askPrice, + askQty, + openPrice, + highPrice, + lowPrice, + volume, + quoteVolume, + openTime, + closeTime, + firstId, + lastId, + count, + symbol); + ticker.setCurrencyPair(currencyPair); + } + public BinanceTicker24h getTicker() { + return ticker; + } } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebsocketTransaction.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebsocketTransaction.java index cb9851822..04cb38dfa 100644 --- a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebsocketTransaction.java +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebsocketTransaction.java @@ -1,44 +1,42 @@ package info.bitrich.xchangestream.binance.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; public class TradeBinanceWebsocketTransaction extends ProductBinanceWebSocketTransaction { - private final BinanceRawTrade rawTrade; - - public TradeBinanceWebsocketTransaction( - @JsonProperty("e") String eventType, - @JsonProperty("E") String eventTime, - @JsonProperty("s") String symbol, - @JsonProperty("t") long tradeId, - @JsonProperty("p") BigDecimal price, - @JsonProperty("q") BigDecimal quantity, - @JsonProperty("b") long buyerOrderId, - @JsonProperty("a") long sellerOrderId, - @JsonProperty("T") long timestamp, - @JsonProperty("m") boolean buyerMarketMaker, - @JsonProperty("M") boolean ignore) - { - super(eventType, eventTime, symbol); - - rawTrade = new BinanceRawTrade( - eventType, - eventTime, - symbol, - tradeId, - price, - quantity, - buyerOrderId, - sellerOrderId, - timestamp, - buyerMarketMaker, - ignore); - } - - public BinanceRawTrade getRawTrade() { - return rawTrade; - } - + private final BinanceRawTrade rawTrade; + + public TradeBinanceWebsocketTransaction( + @JsonProperty("e") String eventType, + @JsonProperty("E") String eventTime, + @JsonProperty("s") String symbol, + @JsonProperty("t") long tradeId, + @JsonProperty("p") BigDecimal price, + @JsonProperty("q") BigDecimal quantity, + @JsonProperty("b") long buyerOrderId, + @JsonProperty("a") long sellerOrderId, + @JsonProperty("T") long timestamp, + @JsonProperty("m") boolean buyerMarketMaker, + @JsonProperty("M") boolean ignore) { + super(eventType, eventTime, symbol); + + rawTrade = + new BinanceRawTrade( + eventType, + eventTime, + symbol, + tradeId, + price, + quantity, + buyerOrderId, + sellerOrderId, + timestamp, + buyerMarketMaker, + ignore); + } + + public BinanceRawTrade getRawTrade() { + return rawTrade; + } } diff --git a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceManualExample.java b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceManualExample.java index 55218c57e..e19496720 100644 --- a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceManualExample.java +++ b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceManualExample.java @@ -3,121 +3,144 @@ import info.bitrich.xchangestream.core.ProductSubscription; import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingExchangeFactory; - import io.reactivex.disposables.Disposable; - import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.currency.CurrencyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class BinanceManualExample { - private static final Logger LOG = LoggerFactory.getLogger(BinanceManualExample.class); - - public static void main(String[] args) throws InterruptedException { - // Far safer than temporarily adding these to code that might get committed to VCS - String apiKey = System.getProperty("binance-api-key"); - String apiSecret = System.getProperty("binance-api-secret"); - - ExchangeSpecification spec = StreamingExchangeFactory.INSTANCE.createExchange( - BinanceStreamingExchange.class.getName()).getDefaultExchangeSpecification(); - spec.setApiKey(apiKey); - spec.setSecretKey(apiSecret); - BinanceStreamingExchange exchange = (BinanceStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); - - ProductSubscription subscription = ProductSubscription.create() - .addTicker(CurrencyPair.ETH_BTC) - .addTicker(CurrencyPair.LTC_BTC) - .addOrderbook(CurrencyPair.LTC_BTC) - .addTrades(CurrencyPair.BTC_USDT) - .build(); - - exchange.connect(subscription).blockingAwait(); - - LOG.info("Subscribing public channels"); - - Disposable tickers = exchange.getStreamingMarketDataService() - .getTicker(CurrencyPair.ETH_BTC) - .subscribe(ticker -> { - LOG.info("Ticker: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - Disposable trades = exchange.getStreamingMarketDataService() - .getTrades(CurrencyPair.BTC_USDT) - .subscribe(trade -> { - LOG.info("Trade: {}", trade); + private static final Logger LOG = LoggerFactory.getLogger(BinanceManualExample.class); + + public static void main(String[] args) throws InterruptedException { + // Far safer than temporarily adding these to code that might get committed to VCS + String apiKey = System.getProperty("binance-api-key"); + String apiSecret = System.getProperty("binance-api-secret"); + + ExchangeSpecification spec = + StreamingExchangeFactory.INSTANCE + .createExchange(BinanceStreamingExchange.class.getName()) + .getDefaultExchangeSpecification(); + spec.setApiKey(apiKey); + spec.setSecretKey(apiSecret); + BinanceStreamingExchange exchange = + (BinanceStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); + + ProductSubscription subscription = + ProductSubscription.create() + .addTicker(CurrencyPair.ETH_BTC) + .addTicker(CurrencyPair.LTC_BTC) + .addOrderbook(CurrencyPair.LTC_BTC) + .addTrades(CurrencyPair.BTC_USDT) + .build(); + + exchange.connect(subscription).blockingAwait(); + + LOG.info("Subscribing public channels"); + + Disposable tickers = + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.ETH_BTC) + .subscribe( + ticker -> { + LOG.info("Ticker: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + Disposable trades = + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USDT) + .subscribe( + trade -> { + LOG.info("Trade: {}", trade); }); - Disposable orderChanges = null; - Disposable userTrades = null; - Disposable balances = null; - Disposable accountInfo = null; - Disposable executionReports = null; - - if (apiKey != null) { - - LOG.info("Subscribing authenticated channels"); - - // Level 1 (generic) APIs - orderChanges = exchange.getStreamingTradeService() - .getOrderChanges() - .subscribe(oc -> LOG.info("Order change: {}", oc)); - userTrades = exchange.getStreamingTradeService() - .getUserTrades() - .subscribe(trade -> LOG.info("User trade: {}", trade)); - balances = exchange.getStreamingAccountService() - .getBalanceChanges() - .subscribe(trade -> LOG.info("Balance: {}", trade), e -> LOG.error("Error in balance stream", e)); - - // Level 2 (exchange-specific) APIs - executionReports = exchange.getStreamingTradeService() - .getRawExecutionReports() - .subscribe(report -> LOG.info("Subscriber got execution report: {}", report)); - accountInfo = exchange.getStreamingAccountService() - .getRawAccountInfo() - .subscribe(accInfo -> LOG.info("Subscriber got account Info (not printing, often causes console issues in IDEs)")); - - } - - Disposable orderbooks = orderbooks(exchange, "one"); - Thread.sleep(5000); - Disposable orderbooks2 = orderbooks(exchange, "two"); - - Thread.sleep(1000000); - - tickers.dispose(); - trades.dispose(); - orderbooks.dispose(); - orderbooks2.dispose(); - - if (apiKey != null) { - orderChanges.dispose(); - userTrades.dispose(); - balances.dispose(); - accountInfo.dispose(); - executionReports.dispose(); - } - - exchange.disconnect().blockingAwait(); + Disposable orderChanges = null; + Disposable userTrades = null; + Disposable balances = null; + Disposable accountInfo = null; + Disposable executionReports = null; + + if (apiKey != null) { + + LOG.info("Subscribing authenticated channels"); + + // Level 1 (generic) APIs + orderChanges = + exchange + .getStreamingTradeService() + .getOrderChanges() + .subscribe(oc -> LOG.info("Order change: {}", oc)); + userTrades = + exchange + .getStreamingTradeService() + .getUserTrades() + .subscribe(trade -> LOG.info("User trade: {}", trade)); + balances = + exchange + .getStreamingAccountService() + .getBalanceChanges() + .subscribe( + trade -> LOG.info("Balance: {}", trade), + e -> LOG.error("Error in balance stream", e)); + + // Level 2 (exchange-specific) APIs + executionReports = + exchange + .getStreamingTradeService() + .getRawExecutionReports() + .subscribe(report -> LOG.info("Subscriber got execution report: {}", report)); + accountInfo = + exchange + .getStreamingAccountService() + .getRawAccountInfo() + .subscribe( + accInfo -> + LOG.info( + "Subscriber got account Info (not printing, often causes console issues in IDEs)")); } - private static Disposable orderbooks(StreamingExchange exchange, String identifier) { - return exchange.getStreamingMarketDataService() - .getOrderBook(CurrencyPair.LTC_BTC) - .subscribe(orderBook -> { - LOG.info( - "Order Book ({}): askDepth={} ask={} askSize={} bidDepth={}. bid={}, bidSize={}", - identifier, - orderBook.getAsks().size(), - orderBook.getAsks().get(0).getLimitPrice(), - orderBook.getAsks().get(0).getRemainingAmount(), - orderBook.getBids().size(), - orderBook.getBids().get(0).getLimitPrice(), - orderBook.getBids().get(0).getRemainingAmount() - ); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); + Disposable orderbooks = orderbooks(exchange, "one"); + Thread.sleep(5000); + Disposable orderbooks2 = orderbooks(exchange, "two"); + + Thread.sleep(1000000); + + tickers.dispose(); + trades.dispose(); + orderbooks.dispose(); + orderbooks2.dispose(); + + if (apiKey != null) { + orderChanges.dispose(); + userTrades.dispose(); + balances.dispose(); + accountInfo.dispose(); + executionReports.dispose(); } + + exchange.disconnect().blockingAwait(); + } + + private static Disposable orderbooks(StreamingExchange exchange, String identifier) { + return exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.LTC_BTC) + .subscribe( + orderBook -> { + LOG.info( + "Order Book ({}): askDepth={} ask={} askSize={} bidDepth={}. bid={}, bidSize={}", + identifier, + orderBook.getAsks().size(), + orderBook.getAsks().get(0).getLimitPrice(), + orderBook.getAsks().get(0).getRemainingAmount(), + orderBook.getBids().size(), + orderBook.getBids().get(0).getLimitPrice(), + orderBook.getBids().get(0).getRemainingAmount()); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); + } } diff --git a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceOrderbookHighVolumeExample.java b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceOrderbookHighVolumeExample.java index ba7d241eb..a9a916730 100644 --- a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceOrderbookHighVolumeExample.java +++ b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceOrderbookHighVolumeExample.java @@ -1,29 +1,33 @@ package info.bitrich.xchangestream.binance; -import org.knowm.xchange.ExchangeSpecification; -import info.bitrich.xchangestream.binance.BinanceStreamingExchange; import info.bitrich.xchangestream.core.ProductSubscription; import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; /** - * This is a useful test for profiling behaviour of the orderbook stream under load. - * Run this with a profiler to ensure that processing is efficient and free of memory leaks + * This is a useful test for profiling behaviour of the orderbook stream under load. Run this with a + * profiler to ensure that processing is efficient and free of memory leaks */ public class BinanceOrderbookHighVolumeExample { public static void main(String[] args) throws InterruptedException { - final ExchangeSpecification exchangeSpecification = new ExchangeSpecification(BinanceStreamingExchange.class); + final ExchangeSpecification exchangeSpecification = + new ExchangeSpecification(BinanceStreamingExchange.class); exchangeSpecification.setShouldLoadRemoteMetaData(true); - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); - ProductSubscription subscription = exchange.getExchangeSymbols().stream().limit(50) - .reduce(ProductSubscription.create(), ProductSubscription.ProductSubscriptionBuilder::addOrderbook, - (productSubscriptionBuilder, productSubscriptionBuilder2) -> { - throw new UnsupportedOperationException(); - }) - .build(); + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + ProductSubscription subscription = + exchange.getExchangeSymbols().stream() + .limit(50) + .reduce( + ProductSubscription.create(), + ProductSubscription.ProductSubscriptionBuilder::addOrderbook, + (productSubscriptionBuilder, productSubscriptionBuilder2) -> { + throw new UnsupportedOperationException(); + }) + .build(); exchange.connect(subscription).blockingAwait(); Thread.sleep(Long.MAX_VALUE); } - -} \ No newline at end of file +} diff --git a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceTest.java b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceTest.java index 4dd63665d..bf2d75b6c 100644 --- a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceTest.java +++ b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/BinanceTest.java @@ -1,5 +1,7 @@ package info.bitrich.xchangestream.binance; +import static info.bitrich.xchangestream.binance.BinanceStreamingExchange.USE_HIGHER_UPDATE_FREQUENCY; + import info.bitrich.xchangestream.core.ProductSubscription; import info.bitrich.xchangestream.core.StreamingExchangeFactory; import org.junit.Assert; @@ -7,35 +9,44 @@ import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.currency.CurrencyPair; -import static info.bitrich.xchangestream.binance.BinanceStreamingExchange.USE_HIGHER_UPDATE_FREQUENCY; - public class BinanceTest { - @Test - public void channelCreateUrlTest() { - BinanceStreamingExchange exchange = (BinanceStreamingExchange) StreamingExchangeFactory.INSTANCE - .createExchange(BinanceStreamingExchange.class.getName()); - ProductSubscription.ProductSubscriptionBuilder builder = ProductSubscription.create(); - builder.addTicker(CurrencyPair.BTC_USD).addTicker(CurrencyPair.DASH_BTC); - String buildSubscriptionStreams = exchange.buildSubscriptionStreams(builder.build()); - Assert.assertEquals("btcusd@ticker/dashbtc@ticker", buildSubscriptionStreams); + @Test + public void channelCreateUrlTest() { + BinanceStreamingExchange exchange = + (BinanceStreamingExchange) + StreamingExchangeFactory.INSTANCE.createExchange( + BinanceStreamingExchange.class.getName()); + ProductSubscription.ProductSubscriptionBuilder builder = ProductSubscription.create(); + builder.addTicker(CurrencyPair.BTC_USD).addTicker(CurrencyPair.DASH_BTC); + String buildSubscriptionStreams = exchange.buildSubscriptionStreams(builder.build()); + Assert.assertEquals("btcusd@ticker/dashbtc@ticker", buildSubscriptionStreams); - ProductSubscription.ProductSubscriptionBuilder builder2 = ProductSubscription.create(); - builder2.addTicker(CurrencyPair.BTC_USD).addTicker(CurrencyPair.DASH_BTC).addOrderbook(CurrencyPair.ETH_BTC); - String buildSubscriptionStreams2 = exchange.buildSubscriptionStreams(builder2.build()); - Assert.assertEquals("btcusd@ticker/dashbtc@ticker/ethbtc@depth", buildSubscriptionStreams2); - } + ProductSubscription.ProductSubscriptionBuilder builder2 = ProductSubscription.create(); + builder2 + .addTicker(CurrencyPair.BTC_USD) + .addTicker(CurrencyPair.DASH_BTC) + .addOrderbook(CurrencyPair.ETH_BTC); + String buildSubscriptionStreams2 = exchange.buildSubscriptionStreams(builder2.build()); + Assert.assertEquals("btcusd@ticker/dashbtc@ticker/ethbtc@depth", buildSubscriptionStreams2); + } - @Test - public void channelCreateUrlWithUpdateFrequencyTest() { - ProductSubscription.ProductSubscriptionBuilder builder = ProductSubscription.create(); - builder.addTicker(CurrencyPair.BTC_USD).addTicker(CurrencyPair.DASH_BTC).addOrderbook(CurrencyPair.ETH_BTC); - ExchangeSpecification spec = StreamingExchangeFactory.INSTANCE.createExchange( - BinanceStreamingExchange.class.getName()).getDefaultExchangeSpecification(); - spec.setExchangeSpecificParametersItem(USE_HIGHER_UPDATE_FREQUENCY, true); - BinanceStreamingExchange exchange = (BinanceStreamingExchange) StreamingExchangeFactory.INSTANCE - .createExchange(spec); - String buildSubscriptionStreams = exchange.buildSubscriptionStreams(builder.build()); - Assert.assertEquals("btcusd@ticker/dashbtc@ticker/ethbtc@depth@100ms", buildSubscriptionStreams); - } + @Test + public void channelCreateUrlWithUpdateFrequencyTest() { + ProductSubscription.ProductSubscriptionBuilder builder = ProductSubscription.create(); + builder + .addTicker(CurrencyPair.BTC_USD) + .addTicker(CurrencyPair.DASH_BTC) + .addOrderbook(CurrencyPair.ETH_BTC); + ExchangeSpecification spec = + StreamingExchangeFactory.INSTANCE + .createExchange(BinanceStreamingExchange.class.getName()) + .getDefaultExchangeSpecification(); + spec.setExchangeSpecificParametersItem(USE_HIGHER_UPDATE_FREQUENCY, true); + BinanceStreamingExchange exchange = + (BinanceStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); + String buildSubscriptionStreams = exchange.buildSubscriptionStreams(builder.build()); + Assert.assertEquals( + "btcusd@ticker/dashbtc@ticker/ethbtc@depth@100ms", buildSubscriptionStreams); + } } diff --git a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransactionTest.java b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransactionTest.java index c5c965d97..ccc3ec4eb 100644 --- a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransactionTest.java +++ b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/DepthBinanceWebSocketTransactionTest.java @@ -1,50 +1,52 @@ package info.bitrich.xchangestream.binance.dto; +import static org.junit.Assert.assertEquals; + import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper; - -import org.junit.BeforeClass; -import org.junit.Test; -import org.knowm.xchange.binance.dto.marketdata.BinanceOrderbook; - import java.io.InputStream; import java.math.BigDecimal; import java.util.Iterator; import java.util.Map.Entry; - -import static org.junit.Assert.assertEquals; +import org.junit.BeforeClass; +import org.junit.Test; +import org.knowm.xchange.binance.dto.marketdata.BinanceOrderbook; public class DepthBinanceWebSocketTransactionTest { - private static ObjectMapper mapper; - - @BeforeClass - public static void setupClass() { - JsonFactory jf = new JsonFactory(); - jf.enable(JsonParser.Feature.ALLOW_COMMENTS); - mapper = new ObjectMapper(jf); - } - - @Test - public void testMapping() throws Exception { - InputStream stream = this.getClass().getResourceAsStream("testDepthEvent.json"); - DepthBinanceWebSocketTransaction transaction = mapper.readValue(stream, DepthBinanceWebSocketTransaction.class); - assertEquals(BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.DEPTH_UPDATE, transaction.getEventType()); - - BinanceOrderbook orderBook = transaction.getOrderBook(); - - Iterator> bidIterator = orderBook.bids.entrySet().iterator(); - assertOrderBookEntry(bidIterator, 0.10376590, 59.15767010); - - Iterator> askIterator = orderBook.asks.entrySet().iterator(); - assertOrderBookEntry(askIterator, 0.10376586, 159.15767010); - assertOrderBookEntry(askIterator, 0.10383109, 345.86845230); - assertOrderBookEntry(askIterator, 0.10490700, 0.00000000); - } - - private void assertOrderBookEntry(Iterator> entryIterator, double price, double volume) { - Entry firstAskEntry = entryIterator.next(); - assertEquals(price, firstAskEntry.getKey().doubleValue(), 0.0); - assertEquals(volume, firstAskEntry.getValue().doubleValue(), 0.0); - } + private static ObjectMapper mapper; + + @BeforeClass + public static void setupClass() { + JsonFactory jf = new JsonFactory(); + jf.enable(JsonParser.Feature.ALLOW_COMMENTS); + mapper = new ObjectMapper(jf); + } + + @Test + public void testMapping() throws Exception { + InputStream stream = this.getClass().getResourceAsStream("testDepthEvent.json"); + DepthBinanceWebSocketTransaction transaction = + mapper.readValue(stream, DepthBinanceWebSocketTransaction.class); + assertEquals( + BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.DEPTH_UPDATE, + transaction.getEventType()); + + BinanceOrderbook orderBook = transaction.getOrderBook(); + + Iterator> bidIterator = orderBook.bids.entrySet().iterator(); + assertOrderBookEntry(bidIterator, 0.10376590, 59.15767010); + + Iterator> askIterator = orderBook.asks.entrySet().iterator(); + assertOrderBookEntry(askIterator, 0.10376586, 159.15767010); + assertOrderBookEntry(askIterator, 0.10383109, 345.86845230); + assertOrderBookEntry(askIterator, 0.10490700, 0.00000000); + } + + private void assertOrderBookEntry( + Iterator> entryIterator, double price, double volume) { + Entry firstAskEntry = entryIterator.next(); + assertEquals(price, firstAskEntry.getKey().doubleValue(), 0.0); + assertEquals(volume, firstAskEntry.getValue().doubleValue(), 0.0); + } } diff --git a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransactionTest.java b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransactionTest.java index 6f99a9cbd..3ab97412b 100644 --- a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransactionTest.java +++ b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TickerBinanceWebsocketTransactionTest.java @@ -5,59 +5,60 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; import org.junit.BeforeClass; import org.junit.Test; import org.knowm.xchange.binance.dto.marketdata.BinanceTicker24h; import org.knowm.xchange.currency.CurrencyPair; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; - public class TickerBinanceWebsocketTransactionTest { - private static ObjectMapper mapper; + private static ObjectMapper mapper; - @BeforeClass - public static void setupClass() { - mapper = new ObjectMapper(); - } + @BeforeClass + public static void setupClass() { + mapper = new ObjectMapper(); + } - @Test - public void test_deserialization_of_transaction_message() throws IOException { - InputStream stream = TickerBinanceWebsocketTransactionTest.class.getResourceAsStream("testTickerEvent.json"); - BinanceWebsocketTransaction transaction = mapper.readValue(stream, + @Test + public void test_deserialization_of_transaction_message() throws IOException { + InputStream stream = + TickerBinanceWebsocketTransactionTest.class.getResourceAsStream("testTickerEvent.json"); + BinanceWebsocketTransaction transaction = + mapper.readValue( + stream, new TypeReference>() {}); - TickerBinanceWebsocketTransaction tickerTransaction = transaction.getData(); - - assertThat(tickerTransaction).isNotNull(); - assertThat(tickerTransaction.eventType).isEqualTo(TICKER_24_HR); - assertThat(tickerTransaction.getEventTime().getTime()).isEqualTo(1516135684559L); - assertThat(tickerTransaction.getCurrencyPair()).isEqualTo(CurrencyPair.ETH_BTC); - - BinanceTicker24h ticker = tickerTransaction.getTicker(); - assertThat(ticker.getPriceChange()).isEqualByComparingTo(BigDecimal.valueOf(-0.00271700)); - assertThat(ticker.getPriceChangePercent()).isEqualByComparingTo(BigDecimal.valueOf(-2.875)); - assertThat(ticker.getWeightedAvgPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09158373)); - - assertThat(ticker.getPrevClosePrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09448900)); - assertThat(ticker.getLastPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09177200)); - assertThat(ticker.getLastQty()).isEqualByComparingTo(BigDecimal.valueOf(0.06100000)); - assertThat(ticker.getBidPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09155300)); - assertThat(ticker.getBidQty()).isEqualByComparingTo(BigDecimal.valueOf(1.75300000)); - assertThat(ticker.getAskPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09177200)); - assertThat(ticker.getAskQty()).isEqualByComparingTo(BigDecimal.valueOf(1.83900000)); - assertThat(ticker.getOpenPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09448900)); - assertThat(ticker.getHighPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09575800)); - assertThat(ticker.getLowPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.08100000)); - assertThat(ticker.getVolume()).isEqualByComparingTo(BigDecimal.valueOf(287542.79200000)); - assertThat(ticker.getQuoteVolume()).isEqualByComparingTo(BigDecimal.valueOf(26334.24227973)); - assertThat(ticker.getOpenTime().getTime()).isEqualTo(1516049284557L); - assertThat(ticker.getCloseTime().getTime()).isEqualTo(1516135684557L); - assertThat(ticker.getFirstTradeId()).isEqualTo(21702040L); - assertThat(ticker.getLastTradeId()).isEqualTo(22120714L); - assertThat(ticker.getTradeCount()).isEqualTo(418675L); - } - -} \ No newline at end of file + TickerBinanceWebsocketTransaction tickerTransaction = transaction.getData(); + + assertThat(tickerTransaction).isNotNull(); + assertThat(tickerTransaction.eventType).isEqualTo(TICKER_24_HR); + assertThat(tickerTransaction.getEventTime().getTime()).isEqualTo(1516135684559L); + assertThat(tickerTransaction.getCurrencyPair()).isEqualTo(CurrencyPair.ETH_BTC); + + BinanceTicker24h ticker = tickerTransaction.getTicker(); + assertThat(ticker.getPriceChange()).isEqualByComparingTo(BigDecimal.valueOf(-0.00271700)); + assertThat(ticker.getPriceChangePercent()).isEqualByComparingTo(BigDecimal.valueOf(-2.875)); + assertThat(ticker.getWeightedAvgPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09158373)); + + assertThat(ticker.getPrevClosePrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09448900)); + assertThat(ticker.getLastPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09177200)); + assertThat(ticker.getLastQty()).isEqualByComparingTo(BigDecimal.valueOf(0.06100000)); + assertThat(ticker.getBidPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09155300)); + assertThat(ticker.getBidQty()).isEqualByComparingTo(BigDecimal.valueOf(1.75300000)); + assertThat(ticker.getAskPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09177200)); + assertThat(ticker.getAskQty()).isEqualByComparingTo(BigDecimal.valueOf(1.83900000)); + assertThat(ticker.getOpenPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09448900)); + assertThat(ticker.getHighPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.09575800)); + assertThat(ticker.getLowPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.08100000)); + assertThat(ticker.getVolume()).isEqualByComparingTo(BigDecimal.valueOf(287542.79200000)); + assertThat(ticker.getQuoteVolume()).isEqualByComparingTo(BigDecimal.valueOf(26334.24227973)); + assertThat(ticker.getOpenTime().getTime()).isEqualTo(1516049284557L); + assertThat(ticker.getCloseTime().getTime()).isEqualTo(1516135684557L); + assertThat(ticker.getFirstTradeId()).isEqualTo(21702040L); + assertThat(ticker.getLastTradeId()).isEqualTo(22120714L); + assertThat(ticker.getTradeCount()).isEqualTo(418675L); + } +} diff --git a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebSocketTransactionTest.java b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebSocketTransactionTest.java index 9e26dde06..faa6ce3fc 100644 --- a/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebSocketTransactionTest.java +++ b/xchange-stream-binance/src/test/java/info/bitrich/xchangestream/binance/dto/TradeBinanceWebSocketTransactionTest.java @@ -1,47 +1,47 @@ package info.bitrich.xchangestream.binance.dto; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper; - -import org.junit.BeforeClass; -import org.junit.Test; - import java.io.InputStream; import java.math.BigDecimal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; +import org.junit.BeforeClass; +import org.junit.Test; public class TradeBinanceWebSocketTransactionTest { - private static ObjectMapper mapper; - - @BeforeClass - public static void setupClass() { - JsonFactory jf = new JsonFactory(); - jf.enable(JsonParser.Feature.ALLOW_COMMENTS); - mapper = new ObjectMapper(jf); - } - - @Test - public void testMapping() throws Exception { - InputStream stream = this.getClass().getResourceAsStream("testTradeEvent.json"); - TradeBinanceWebsocketTransaction transaction = mapper.readValue(stream, TradeBinanceWebsocketTransaction.class); - assertEquals(BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.TRADE, transaction.getEventType()); - - BinanceRawTrade rawTrade = transaction.getRawTrade(); - - assertThat(rawTrade.getEventType()).isEqualTo("trade"); - assertThat(rawTrade.getEventTime()).isEqualTo("123456789"); - assertThat(rawTrade.getSymbol()).isEqualTo("BNBBTC"); - assertThat(rawTrade.getTradeId()).isEqualByComparingTo(12345L); - - assertThat(rawTrade.getPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.001)); - assertThat(rawTrade.getQuantity()).isEqualByComparingTo(BigDecimal.valueOf(100)); - assertThat(rawTrade.getBuyerOrderId()).isEqualByComparingTo(88L); - assertThat(rawTrade.getSellerOrderId()).isEqualByComparingTo(50L); - assertThat(rawTrade.getTimestamp()).isEqualByComparingTo(123456785L); - assertThat(rawTrade.isBuyerMarketMaker()); - assertThat(rawTrade.isIgnore()); - } + private static ObjectMapper mapper; + + @BeforeClass + public static void setupClass() { + JsonFactory jf = new JsonFactory(); + jf.enable(JsonParser.Feature.ALLOW_COMMENTS); + mapper = new ObjectMapper(jf); + } + + @Test + public void testMapping() throws Exception { + InputStream stream = this.getClass().getResourceAsStream("testTradeEvent.json"); + TradeBinanceWebsocketTransaction transaction = + mapper.readValue(stream, TradeBinanceWebsocketTransaction.class); + assertEquals( + BaseBinanceWebSocketTransaction.BinanceWebSocketTypes.TRADE, transaction.getEventType()); + + BinanceRawTrade rawTrade = transaction.getRawTrade(); + + assertThat(rawTrade.getEventType()).isEqualTo("trade"); + assertThat(rawTrade.getEventTime()).isEqualTo("123456789"); + assertThat(rawTrade.getSymbol()).isEqualTo("BNBBTC"); + assertThat(rawTrade.getTradeId()).isEqualByComparingTo(12345L); + + assertThat(rawTrade.getPrice()).isEqualByComparingTo(BigDecimal.valueOf(0.001)); + assertThat(rawTrade.getQuantity()).isEqualByComparingTo(BigDecimal.valueOf(100)); + assertThat(rawTrade.getBuyerOrderId()).isEqualByComparingTo(88L); + assertThat(rawTrade.getSellerOrderId()).isEqualByComparingTo(50L); + assertThat(rawTrade.getTimestamp()).isEqualByComparingTo(123456785L); + assertThat(rawTrade.isBuyerMarketMaker()); + assertThat(rawTrade.isIgnore()); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAccountService.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAccountService.java index 9a5efc9c7..ffb808eb6 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAccountService.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAccountService.java @@ -2,9 +2,7 @@ import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthBalance; import info.bitrich.xchangestream.core.StreamingAccountService; - import io.reactivex.Observable; - import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.exceptions.ExchangeException; @@ -14,38 +12,42 @@ public class BitfinexStreamingAccountService implements StreamingAccountService { - private static final Logger LOG = LoggerFactory.getLogger(BitfinexStreamingAccountService.class); + private static final Logger LOG = LoggerFactory.getLogger(BitfinexStreamingAccountService.class); - private final BitfinexStreamingService service; + private final BitfinexStreamingService service; - public BitfinexStreamingAccountService(BitfinexStreamingService service) { - this.service = service; - } + public BitfinexStreamingAccountService(BitfinexStreamingService service) { + this.service = service; + } - @Override - public Observable getBalanceChanges(Currency currency, Object... args) { - if (args.length == 0 || !String.class.isInstance(args[0])) { - throw new ExchangeException("Specify wallet id to monitor balance stream"); - } - String walletId = (String) args[0]; - return getRawAuthenticatedBalances() - .filter(b -> b.getWalletType().equalsIgnoreCase(walletId)) - .filter(b -> currency.getCurrencyCode().equals(b.getCurrency())) - .filter(b -> { - if (b.getBalanceAvailable() == null) { - LOG.debug("Ignoring uncalculated balance on {}-{}, scheduling calculated fetch", walletId, b.getCurrency()); - service.scheduleCalculatedBalanceFetch(b.getCurrency()); - return false; - } - return true; - }) - .map(BitfinexStreamingAdapters::adaptBalance); + @Override + public Observable getBalanceChanges(Currency currency, Object... args) { + if (args.length == 0 || !String.class.isInstance(args[0])) { + throw new ExchangeException("Specify wallet id to monitor balance stream"); } - - public Observable getRawAuthenticatedBalances() { - if (!service.isAuthenticated()) { - throw new ExchangeSecurityException("Not authenticated"); - } - return service.getAuthenticatedBalances(); + String walletId = (String) args[0]; + return getRawAuthenticatedBalances() + .filter(b -> b.getWalletType().equalsIgnoreCase(walletId)) + .filter(b -> currency.getCurrencyCode().equals(b.getCurrency())) + .filter( + b -> { + if (b.getBalanceAvailable() == null) { + LOG.debug( + "Ignoring uncalculated balance on {}-{}, scheduling calculated fetch", + walletId, + b.getCurrency()); + service.scheduleCalculatedBalanceFetch(b.getCurrency()); + return false; + } + return true; + }) + .map(BitfinexStreamingAdapters::adaptBalance); + } + + public Observable getRawAuthenticatedBalances() { + if (!service.isAuthenticated()) { + throw new ExchangeSecurityException("Not authenticated"); } + return service.getAuthenticatedBalances(); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdapters.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdapters.java index 02a798f22..4152f6968 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdapters.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdapters.java @@ -1,14 +1,17 @@ package info.bitrich.xchangestream.bitfinex; -import com.fasterxml.jackson.databind.JsonNode; +import static java.util.stream.StreamSupport.stream; +import static org.knowm.xchange.dto.Order.OrderType.ASK; +import static org.knowm.xchange.dto.Order.OrderType.BID; +import com.fasterxml.jackson.databind.JsonNode; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthBalance; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthOrder; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthPreTrade; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthTrade; - import io.reactivex.annotations.Nullable; - +import java.math.BigDecimal; +import java.util.stream.Stream; import org.knowm.xchange.bitfinex.service.BitfinexAdapters; import org.knowm.xchange.bitfinex.v1.BitfinexOrderType; import org.knowm.xchange.bitfinex.v1.dto.trade.BitfinexOrderStatusResponse; @@ -21,226 +24,260 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.math.BigDecimal; -import java.util.stream.Stream; +class BitfinexStreamingAdapters { -import static java.util.stream.StreamSupport.stream; -import static org.knowm.xchange.dto.Order.OrderType.ASK; -import static org.knowm.xchange.dto.Order.OrderType.BID; + private static final Logger LOG = LoggerFactory.getLogger(BitfinexStreamingAdapters.class); -class BitfinexStreamingAdapters { + private static final BigDecimal THOUSAND = new BigDecimal(1000); - private static final Logger LOG = LoggerFactory.getLogger(BitfinexStreamingAdapters.class); - - private static final BigDecimal THOUSAND = new BigDecimal(1000); - - - @Nullable - static BitfinexWebSocketAuthPreTrade adaptPreTrade(JsonNode preTrade) { - if (preTrade.size() < 12) { - LOG.error("addPreTrade unexpected record size={}, record={}", preTrade.size(), preTrade.toString()); - return null; - } - long id = preTrade.get(0).longValue(); - String pair = preTrade.get(1).textValue(); - long mtsCreate = preTrade.get(2).longValue(); - long orderId = preTrade.get(3).longValue(); - BigDecimal execAmount = preTrade.get(4).decimalValue(); - BigDecimal execPrice = preTrade.get(5).decimalValue(); - String orderType = preTrade.get(6).textValue(); - BigDecimal orderPrice = preTrade.get(7).decimalValue(); - int maker = preTrade.get(8).intValue(); - BitfinexWebSocketAuthPreTrade preTradeObject = new BitfinexWebSocketAuthPreTrade(id, pair, mtsCreate, orderId, - execAmount, execPrice, orderType, orderPrice, maker); - LOG.debug("New pre trade: {}", preTradeObject); - return preTradeObject; + @Nullable + static BitfinexWebSocketAuthPreTrade adaptPreTrade(JsonNode preTrade) { + if (preTrade.size() < 12) { + LOG.error( + "addPreTrade unexpected record size={}, record={}", preTrade.size(), preTrade.toString()); + return null; } + long id = preTrade.get(0).longValue(); + String pair = preTrade.get(1).textValue(); + long mtsCreate = preTrade.get(2).longValue(); + long orderId = preTrade.get(3).longValue(); + BigDecimal execAmount = preTrade.get(4).decimalValue(); + BigDecimal execPrice = preTrade.get(5).decimalValue(); + String orderType = preTrade.get(6).textValue(); + BigDecimal orderPrice = preTrade.get(7).decimalValue(); + int maker = preTrade.get(8).intValue(); + BitfinexWebSocketAuthPreTrade preTradeObject = + new BitfinexWebSocketAuthPreTrade( + id, pair, mtsCreate, orderId, execAmount, execPrice, orderType, orderPrice, maker); + LOG.debug("New pre trade: {}", preTradeObject); + return preTradeObject; + } - @Nullable - static BitfinexWebSocketAuthTrade adaptTrade(JsonNode trade) { - if (trade.size() < 11) { - LOG.error("addTrade unexpected record size={}, record={}", trade.size(), trade.toString()); - return null; - } - long id = trade.get(0).longValue(); - String pair = trade.get(1).textValue(); - long mtsCreate = trade.get(2).longValue(); - long orderId = trade.get(3).longValue(); - BigDecimal execAmount = trade.get(4).decimalValue(); - BigDecimal execPrice = trade.get(5).decimalValue(); - String orderType = trade.get(6).textValue(); - BigDecimal orderPrice = trade.get(7).decimalValue(); - int maker = trade.get(8).intValue(); - BigDecimal fee = trade.get(9).decimalValue(); - String currency = trade.get(10).textValue(); - BitfinexWebSocketAuthTrade tradeObject = new BitfinexWebSocketAuthTrade( - id, pair, mtsCreate, orderId, execAmount, execPrice, orderType, orderPrice, maker, fee, currency - ); - LOG.debug("New trade: {}", tradeObject); - return tradeObject; + @Nullable + static BitfinexWebSocketAuthTrade adaptTrade(JsonNode trade) { + if (trade.size() < 11) { + LOG.error("addTrade unexpected record size={}, record={}", trade.size(), trade.toString()); + return null; } + long id = trade.get(0).longValue(); + String pair = trade.get(1).textValue(); + long mtsCreate = trade.get(2).longValue(); + long orderId = trade.get(3).longValue(); + BigDecimal execAmount = trade.get(4).decimalValue(); + BigDecimal execPrice = trade.get(5).decimalValue(); + String orderType = trade.get(6).textValue(); + BigDecimal orderPrice = trade.get(7).decimalValue(); + int maker = trade.get(8).intValue(); + BigDecimal fee = trade.get(9).decimalValue(); + String currency = trade.get(10).textValue(); + BitfinexWebSocketAuthTrade tradeObject = + new BitfinexWebSocketAuthTrade( + id, + pair, + mtsCreate, + orderId, + execAmount, + execPrice, + orderType, + orderPrice, + maker, + fee, + currency); + LOG.debug("New trade: {}", tradeObject); + return tradeObject; + } - static Stream adaptOrders(JsonNode orders) { - Iterable iterator = () -> orders.iterator(); - return stream(iterator.spliterator(), false) - .filter(o -> o.size() >= 32) - .map(BitfinexStreamingAdapters::createOrderObject) - .peek(o -> LOG.debug("New order: {}", o)); - } + static Stream adaptOrders(JsonNode orders) { + Iterable iterator = () -> orders.iterator(); + return stream(iterator.spliterator(), false) + .filter(o -> o.size() >= 32) + .map(BitfinexStreamingAdapters::createOrderObject) + .peek(o -> LOG.debug("New order: {}", o)); + } - @Nullable - static BitfinexWebSocketAuthOrder adaptOrder(JsonNode order) { - BitfinexWebSocketAuthOrder orderObject = createOrderObject(order); - if (orderObject == null) { - return null; - } - LOG.debug("Updated order: {}", orderObject); - return orderObject; + @Nullable + static BitfinexWebSocketAuthOrder adaptOrder(JsonNode order) { + BitfinexWebSocketAuthOrder orderObject = createOrderObject(order); + if (orderObject == null) { + return null; } + LOG.debug("Updated order: {}", orderObject); + return orderObject; + } - @Nullable - static BitfinexWebSocketAuthBalance adaptBalance(JsonNode balance) { - BitfinexWebSocketAuthBalance balanceObject = createBalanceObject(balance); - if (balanceObject == null) { - return null; - } - LOG.debug("Balance: {}", balanceObject); - return balanceObject; + @Nullable + static BitfinexWebSocketAuthBalance adaptBalance(JsonNode balance) { + BitfinexWebSocketAuthBalance balanceObject = createBalanceObject(balance); + if (balanceObject == null) { + return null; } + LOG.debug("Balance: {}", balanceObject); + return balanceObject; + } + + static Stream adaptBalances(JsonNode balances) { + Iterable iterator = () -> balances.iterator(); + return stream(iterator.spliterator(), false) + .filter(o -> o.size() >= 5) + .map(BitfinexStreamingAdapters::createBalanceObject) + .peek(o -> LOG.debug("Balance: {}", o)); + } - static Stream adaptBalances(JsonNode balances) { - Iterable iterator = () -> balances.iterator(); - return stream(iterator.spliterator(), false) - .filter(o -> o.size() >= 5) - .map(BitfinexStreamingAdapters::createBalanceObject) - .peek(o -> LOG.debug("Balance: {}", o)); + @Nullable + private static BitfinexWebSocketAuthBalance createBalanceObject(JsonNode balance) { + if (balance.size() < 5) { + LOG.error( + "createBalanceObject unexpected record size={}, record={}", + balance.size(), + balance.toString()); + return null; } - @Nullable - static private BitfinexWebSocketAuthBalance createBalanceObject(JsonNode balance) { - if (balance.size() < 5) { - LOG.error("createBalanceObject unexpected record size={}, record={}", balance.size(), balance.toString()); - return null; - } + String walletType = balance.get(0).textValue(); + String currency = balance.get(1).textValue(); + BigDecimal balanceValue = balance.get(2).decimalValue(); + BigDecimal unsettledInterest = balance.get(3).decimalValue(); + BigDecimal balanceAvailable = + balance.get(4).asText().equals("null") ? null : balance.get(4).decimalValue(); - String walletType = balance.get(0).textValue(); - String currency = balance.get(1).textValue(); - BigDecimal balanceValue = balance.get(2).decimalValue(); - BigDecimal unsettledInterest = balance.get(3).decimalValue(); - BigDecimal balanceAvailable = balance.get(4).asText().equals("null") ? null : balance.get(4).decimalValue(); + return new BitfinexWebSocketAuthBalance( + walletType, currency, balanceValue, unsettledInterest, balanceAvailable); + } - return new BitfinexWebSocketAuthBalance(walletType, currency, balanceValue, unsettledInterest, balanceAvailable); + @Nullable + private static BitfinexWebSocketAuthOrder createOrderObject(JsonNode order) { + if (order.size() < 32) { + LOG.error( + "createOrderObject unexpected record size={}, record={}", order.size(), order.toString()); + return null; } - @Nullable - static private BitfinexWebSocketAuthOrder createOrderObject(JsonNode order) { - if (order.size() < 32) { - LOG.error("createOrderObject unexpected record size={}, record={}", order.size(), order.toString()); - return null; - } - - long id = order.get(0).longValue(); - long groupId = order.get(1).longValue(); - long cid = order.get(2).longValue(); - String symbol = order.get(3).textValue(); - long mtsCreate = order.get(4).longValue(); - long mtsUpdate = order.get(5).longValue(); - BigDecimal amount = order.get(6).decimalValue(); - BigDecimal amountOrig = order.get(7).decimalValue(); - String type = order.get(8).textValue(); - String typePrev = order.get(9).textValue(); - int flags = order.get(12).intValue(); - String orderStatus = order.get(13).textValue(); - BigDecimal price = order.get(16).decimalValue(); - BigDecimal priceAvg = order.get(17).decimalValue(); - BigDecimal priceTrailing = order.get(18).decimalValue(); - BigDecimal priceAuxLimit = order.get(19).decimalValue(); - long placedId = order.get(25).longValue(); - - return new BitfinexWebSocketAuthOrder( - id, groupId, cid, symbol, mtsCreate, mtsUpdate, amount, amountOrig, - type, typePrev, orderStatus, price, priceAvg, priceTrailing, - priceAuxLimit, placedId, flags - ); - } + long id = order.get(0).longValue(); + long groupId = order.get(1).longValue(); + long cid = order.get(2).longValue(); + String symbol = order.get(3).textValue(); + long mtsCreate = order.get(4).longValue(); + long mtsUpdate = order.get(5).longValue(); + BigDecimal amount = order.get(6).decimalValue(); + BigDecimal amountOrig = order.get(7).decimalValue(); + String type = order.get(8).textValue(); + String typePrev = order.get(9).textValue(); + int flags = order.get(12).intValue(); + String orderStatus = order.get(13).textValue(); + BigDecimal price = order.get(16).decimalValue(); + BigDecimal priceAvg = order.get(17).decimalValue(); + BigDecimal priceTrailing = order.get(18).decimalValue(); + BigDecimal priceAuxLimit = order.get(19).decimalValue(); + long placedId = order.get(25).longValue(); - private static BitfinexOrderType adaptV2OrderTypeToV1(String orderType) { - switch(orderType) { - case "LIMIT": return BitfinexOrderType.MARGIN_LIMIT; - case "MARKET": return BitfinexOrderType.MARGIN_MARKET; - case "STOP": return BitfinexOrderType.MARGIN_STOP; - case "TRAILING STOP": return BitfinexOrderType.MARGIN_TRAILING_STOP; - case "EXCHANGE MARKET": return BitfinexOrderType.MARKET; - case "EXCHANGE LIMIT": return BitfinexOrderType.LIMIT; - case "EXCHANGE STOP": return BitfinexOrderType.STOP; - case "EXCHANGE TRAILING STOP": return BitfinexOrderType.TRAILING_STOP; - case "FOK": return BitfinexOrderType.MARGIN_FILL_OR_KILL; - case "EXCHANGE FOK": return BitfinexOrderType.FILL_OR_KILL; - default: return BitfinexOrderType.LIMIT; // Safe fallback - } - } + return new BitfinexWebSocketAuthOrder( + id, + groupId, + cid, + symbol, + mtsCreate, + mtsUpdate, + amount, + amountOrig, + type, + typePrev, + orderStatus, + price, + priceAvg, + priceTrailing, + priceAuxLimit, + placedId, + flags); + } - private static String adaptV2SymbolToV1(String symbol) { - return symbol.substring(1); + private static BitfinexOrderType adaptV2OrderTypeToV1(String orderType) { + switch (orderType) { + case "LIMIT": + return BitfinexOrderType.MARGIN_LIMIT; + case "MARKET": + return BitfinexOrderType.MARGIN_MARKET; + case "STOP": + return BitfinexOrderType.MARGIN_STOP; + case "TRAILING STOP": + return BitfinexOrderType.MARGIN_TRAILING_STOP; + case "EXCHANGE MARKET": + return BitfinexOrderType.MARKET; + case "EXCHANGE LIMIT": + return BitfinexOrderType.LIMIT; + case "EXCHANGE STOP": + return BitfinexOrderType.STOP; + case "EXCHANGE TRAILING STOP": + return BitfinexOrderType.TRAILING_STOP; + case "FOK": + return BitfinexOrderType.MARGIN_FILL_OR_KILL; + case "EXCHANGE FOK": + return BitfinexOrderType.FILL_OR_KILL; + default: + return BitfinexOrderType.LIMIT; // Safe fallback } + } - /** - * We adapt the websocket message to what we expect from the V1 REST API, so that - * we don't re-implement the complex logic which works out whether we need - * limit orders, stop orders, market orders etc. - */ - private static BitfinexOrderStatusResponse adaptOrderToRestResponse(BitfinexWebSocketAuthOrder authOrder) { - int signum = authOrder.getAmountOrig().signum(); - return new BitfinexOrderStatusResponse( - authOrder.getId(), - adaptV2SymbolToV1(authOrder.getSymbol()), - authOrder.getPrice(), - authOrder.getPriceAvg(), - signum > 0 ? "buy" : "sell", - adaptV2OrderTypeToV1(authOrder.getType()).getValue(), - new BigDecimal(authOrder.getMtsCreate()).divide(THOUSAND), - "ACTIVE".equals(authOrder.getOrderStatus()), - "CANCELED".equals(authOrder.getOrderStatus()) || "FILLORKILL CANCELED".equals(authOrder.getOrderStatus()), - false, //wasForced, - signum >= 0 ? authOrder.getAmountOrig() : authOrder.getAmountOrig().negate(), - signum >= 0 ? authOrder.getAmount() : authOrder.getAmount().negate(), - signum >= 0 - ? authOrder.getAmountOrig().subtract(authOrder.getAmount()) - : authOrder.getAmountOrig().subtract(authOrder.getAmount()).negate() - ); - } + private static String adaptV2SymbolToV1(String symbol) { + return symbol.substring(1); + } - static Order adaptOrder(BitfinexWebSocketAuthOrder authOrder) { - BitfinexOrderStatusResponse[] orderStatus = { adaptOrderToRestResponse(authOrder)}; - OpenOrders orders = BitfinexAdapters.adaptOrders(orderStatus); - if (orders.getOpenOrders().isEmpty()) { - if (orders.getHiddenOrders().isEmpty()) { - throw new IllegalStateException("No order in message"); - } - return orders.getHiddenOrders().get(0); - } - return orders.getOpenOrders().get(0); - } + /** + * We adapt the websocket message to what we expect from the V1 REST API, so that we don't + * re-implement the complex logic which works out whether we need limit orders, stop orders, + * market orders etc. + */ + private static BitfinexOrderStatusResponse adaptOrderToRestResponse( + BitfinexWebSocketAuthOrder authOrder) { + int signum = authOrder.getAmountOrig().signum(); + return new BitfinexOrderStatusResponse( + authOrder.getId(), + adaptV2SymbolToV1(authOrder.getSymbol()), + authOrder.getPrice(), + authOrder.getPriceAvg(), + signum > 0 ? "buy" : "sell", + adaptV2OrderTypeToV1(authOrder.getType()).getValue(), + new BigDecimal(authOrder.getMtsCreate()).divide(THOUSAND), + "ACTIVE".equals(authOrder.getOrderStatus()), + "CANCELED".equals(authOrder.getOrderStatus()) + || "FILLORKILL CANCELED".equals(authOrder.getOrderStatus()), + false, // wasForced, + signum >= 0 ? authOrder.getAmountOrig() : authOrder.getAmountOrig().negate(), + signum >= 0 ? authOrder.getAmount() : authOrder.getAmount().negate(), + signum >= 0 + ? authOrder.getAmountOrig().subtract(authOrder.getAmount()) + : authOrder.getAmountOrig().subtract(authOrder.getAmount()).negate()); + } - static UserTrade adaptUserTrade(BitfinexWebSocketAuthTrade authTrade) { - return new UserTrade.Builder() - .currencyPair(BitfinexAdapters.adaptCurrencyPair(adaptV2SymbolToV1(authTrade.getPair()))) - .feeAmount(authTrade.getFee().abs()) - .feeCurrency(Currency.getInstance(authTrade.getFeeCurrency())) - .id(Long.toString(authTrade.getId())) - .orderId(Long.toString(authTrade.getOrderId())) - .originalAmount(authTrade.getExecAmount().abs()) - .price(authTrade.getExecPrice()) - .timestamp(DateUtils.fromMillisUtc(authTrade.getMtsCreate())) - .type(authTrade.getExecAmount().signum() == 1 ? BID : ASK) - .build(); + static Order adaptOrder(BitfinexWebSocketAuthOrder authOrder) { + BitfinexOrderStatusResponse[] orderStatus = {adaptOrderToRestResponse(authOrder)}; + OpenOrders orders = BitfinexAdapters.adaptOrders(orderStatus); + if (orders.getOpenOrders().isEmpty()) { + if (orders.getHiddenOrders().isEmpty()) { + throw new IllegalStateException("No order in message"); + } + return orders.getHiddenOrders().get(0); } + return orders.getOpenOrders().get(0); + } - static Balance adaptBalance(BitfinexWebSocketAuthBalance authBalance) { - return new Balance( - Currency.getInstance(authBalance.getCurrency()), - authBalance.getBalance(), - authBalance.getBalanceAvailable() - ); - } + static UserTrade adaptUserTrade(BitfinexWebSocketAuthTrade authTrade) { + return new UserTrade.Builder() + .currencyPair(BitfinexAdapters.adaptCurrencyPair(adaptV2SymbolToV1(authTrade.getPair()))) + .feeAmount(authTrade.getFee().abs()) + .feeCurrency(Currency.getInstance(authTrade.getFeeCurrency())) + .id(Long.toString(authTrade.getId())) + .orderId(Long.toString(authTrade.getOrderId())) + .originalAmount(authTrade.getExecAmount().abs()) + .price(authTrade.getExecPrice()) + .timestamp(DateUtils.fromMillisUtc(authTrade.getMtsCreate())) + .type(authTrade.getExecAmount().signum() == 1 ? BID : ASK) + .build(); + } + + static Balance adaptBalance(BitfinexWebSocketAuthBalance authBalance) { + return new Balance( + Currency.getInstance(authBalance.getCurrency()), + authBalance.getBalance(), + authBalance.getBalanceAvailable()); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingExchange.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingExchange.java index 05967aa1f..604f4cb1c 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingExchange.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingExchange.java @@ -8,94 +8,95 @@ import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.bitfinex.BitfinexExchange; -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ public class BitfinexStreamingExchange extends BitfinexExchange implements StreamingExchange { - static final String API_URI = "wss://api.bitfinex.com/ws/2"; - - private BitfinexStreamingService streamingService; - private BitfinexStreamingMarketDataService streamingMarketDataService; - private BitfinexStreamingTradeService streamingTradeService; - private BitfinexStreamingAccountService streamingAccountService; - - @Override - protected void initServices() { - super.initServices(); - this.streamingService = createStreamingService(); - this.streamingMarketDataService = new BitfinexStreamingMarketDataService(streamingService); - this.streamingTradeService = new BitfinexStreamingTradeService(streamingService); - this.streamingAccountService = new BitfinexStreamingAccountService(streamingService); - } - - private BitfinexStreamingService createStreamingService() { - BitfinexStreamingService streamingService = new BitfinexStreamingService(API_URI, getNonceFactory()); - applyStreamingSpecification(getExchangeSpecification(), streamingService); - if (StringUtils.isNotEmpty(exchangeSpecification.getApiKey())) { - streamingService.setApiKey(exchangeSpecification.getApiKey()); - streamingService.setApiSecret(exchangeSpecification.getSecretKey()); - } - return streamingService; - } - - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } - - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } - - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } - - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } - - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } - - @Override - public Observable connectionIdle() { - return streamingService.subscribeIdle(); - } - - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); - - return spec; - } - - @Override - public BitfinexStreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public BitfinexStreamingAccountService getStreamingAccountService() { - return streamingAccountService; - } - - @Override - public BitfinexStreamingTradeService getStreamingTradeService() { - return streamingTradeService; - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } - - public boolean isAuthenticatedAlive() { - return streamingService != null && streamingService.isAuthenticated(); + static final String API_URI = "wss://api.bitfinex.com/ws/2"; + + private BitfinexStreamingService streamingService; + private BitfinexStreamingMarketDataService streamingMarketDataService; + private BitfinexStreamingTradeService streamingTradeService; + private BitfinexStreamingAccountService streamingAccountService; + + @Override + protected void initServices() { + super.initServices(); + this.streamingService = createStreamingService(); + this.streamingMarketDataService = new BitfinexStreamingMarketDataService(streamingService); + this.streamingTradeService = new BitfinexStreamingTradeService(streamingService); + this.streamingAccountService = new BitfinexStreamingAccountService(streamingService); + } + + private BitfinexStreamingService createStreamingService() { + BitfinexStreamingService streamingService = + new BitfinexStreamingService(API_URI, getNonceFactory()); + applyStreamingSpecification(getExchangeSpecification(), streamingService); + if (StringUtils.isNotEmpty(exchangeSpecification.getApiKey())) { + streamingService.setApiKey(exchangeSpecification.getApiKey()); + streamingService.setApiSecret(exchangeSpecification.getSecretKey()); } + return streamingService; + } + + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } + + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } + + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } + + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } + + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } + + @Override + public Observable connectionIdle() { + return streamingService.subscribeIdle(); + } + + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); + + return spec; + } + + @Override + public BitfinexStreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public BitfinexStreamingAccountService getStreamingAccountService() { + return streamingAccountService; + } + + @Override + public BitfinexStreamingTradeService getStreamingTradeService() { + return streamingTradeService; + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } + + public boolean isAuthenticatedAlive() { + return streamingService != null && streamingService.isAuthenticated(); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingMarketDataService.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingMarketDataService.java index 137399d20..198e6462f 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingMarketDataService.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingMarketDataService.java @@ -1,7 +1,10 @@ package info.bitrich.xchangestream.bitfinex; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.knowm.xchange.bitfinex.service.BitfinexAdapters.adaptOrderBook; +import static org.knowm.xchange.bitfinex.service.BitfinexAdapters.adaptTicker; +import static org.knowm.xchange.bitfinex.service.BitfinexAdapters.adaptTrades; +import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.bitfinex.dto.BitfinexOrderbook; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketOrderbookTransaction; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketSnapshotOrderbook; @@ -12,95 +15,90 @@ import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebsocketUpdateTrade; import info.bitrich.xchangestream.core.StreamingMarketDataService; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; - import io.reactivex.Observable; - +import java.util.HashMap; +import java.util.Map; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.dto.marketdata.Trades; -import java.util.HashMap; -import java.util.Map; - -import static org.knowm.xchange.bitfinex.service.BitfinexAdapters.adaptOrderBook; -import static org.knowm.xchange.bitfinex.service.BitfinexAdapters.adaptTicker; -import static org.knowm.xchange.bitfinex.service.BitfinexAdapters.adaptTrades; - -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ public class BitfinexStreamingMarketDataService implements StreamingMarketDataService { - private final BitfinexStreamingService service; - - private final Map orderbooks = new HashMap<>(); - - public BitfinexStreamingMarketDataService(BitfinexStreamingService service) { - this.service = service; - } - - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String channelName = "book"; - final String depth = args.length > 0 ? args[0].toString() : "100"; - String pair = currencyPair.base.toString() + currencyPair.counter.toString(); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - Observable subscribedChannel = service.subscribeChannel(channelName, - new Object[]{pair, "P0", depth}) - .map(s -> { - if (s.get(1).get(0).isArray()) return mapper.treeToValue(s, - BitfinexWebSocketSnapshotOrderbook.class); - else return mapper.treeToValue(s, BitfinexWebSocketUpdateOrderbook.class); + private final BitfinexStreamingService service; + + private final Map orderbooks = new HashMap<>(); + + public BitfinexStreamingMarketDataService(BitfinexStreamingService service) { + this.service = service; + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + String channelName = "book"; + final String depth = args.length > 0 ? args[0].toString() : "100"; + String pair = currencyPair.base.toString() + currencyPair.counter.toString(); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + Observable subscribedChannel = + service + .subscribeChannel(channelName, new Object[] {pair, "P0", depth}) + .map( + s -> { + if (s.get(1).get(0).isArray()) + return mapper.treeToValue(s, BitfinexWebSocketSnapshotOrderbook.class); + else return mapper.treeToValue(s, BitfinexWebSocketUpdateOrderbook.class); }); - return subscribedChannel - .map(s -> { - BitfinexOrderbook bitfinexOrderbook = s.toBitfinexOrderBook(orderbooks.getOrDefault(currencyPair, - null)); - orderbooks.put(currencyPair, bitfinexOrderbook); - return adaptOrderBook(bitfinexOrderbook.toBitfinexDepth(), currencyPair); - }); - } - - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - String channelName = "ticker"; - - String pair = currencyPair.base.toString() + currencyPair.counter.toString(); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - Observable subscribedChannel = service.subscribeChannel(channelName, - new Object[]{pair}) - .map(s -> mapper.treeToValue(s, BitfinexWebSocketTickerTransaction.class)); - - return subscribedChannel - .map(s -> adaptTicker(s.toBitfinexTicker(), currencyPair)); - } - - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channelName = "trades"; - final String tradeType = args.length > 0 ? args[0].toString() : "te"; - - String pair = currencyPair.base.toString() + currencyPair.counter.toString(); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - Observable subscribedChannel = service.subscribeChannel(channelName, - new Object[]{pair}) - .filter(s -> s.get(1).asText().equals(tradeType)) - .map(s -> { - if (s.get(1).asText().equals("te") || s.get(1).asText().equals("tu")) { - return mapper.treeToValue(s, BitfinexWebsocketUpdateTrade.class); - } else return mapper.treeToValue(s, BitfinexWebSocketSnapshotTrades.class); + return subscribedChannel.map( + s -> { + BitfinexOrderbook bitfinexOrderbook = + s.toBitfinexOrderBook(orderbooks.getOrDefault(currencyPair, null)); + orderbooks.put(currencyPair, bitfinexOrderbook); + return adaptOrderBook(bitfinexOrderbook.toBitfinexDepth(), currencyPair); + }); + } + + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + String channelName = "ticker"; + + String pair = currencyPair.base.toString() + currencyPair.counter.toString(); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + Observable subscribedChannel = + service + .subscribeChannel(channelName, new Object[] {pair}) + .map(s -> mapper.treeToValue(s, BitfinexWebSocketTickerTransaction.class)); + + return subscribedChannel.map(s -> adaptTicker(s.toBitfinexTicker(), currencyPair)); + } + + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channelName = "trades"; + final String tradeType = args.length > 0 ? args[0].toString() : "te"; + + String pair = currencyPair.base.toString() + currencyPair.counter.toString(); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + Observable subscribedChannel = + service + .subscribeChannel(channelName, new Object[] {pair}) + .filter(s -> s.get(1).asText().equals(tradeType)) + .map( + s -> { + if (s.get(1).asText().equals("te") || s.get(1).asText().equals("tu")) { + return mapper.treeToValue(s, BitfinexWebsocketUpdateTrade.class); + } else return mapper.treeToValue(s, BitfinexWebSocketSnapshotTrades.class); }); - return subscribedChannel - .flatMapIterable(s -> { - Trades adaptedTrades = adaptTrades(s.toBitfinexTrades(), currencyPair); - return adaptedTrades.getTrades(); - }); - } -} \ No newline at end of file + return subscribedChannel.flatMapIterable( + s -> { + Trades adaptedTrades = adaptTrades(s.toBitfinexTrades(), currencyPair); + return adaptedTrades.getTrades(); + }); + } +} diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingService.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingService.java index 71f2e3702..8c324c23b 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingService.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingService.java @@ -1,5 +1,7 @@ package info.bitrich.xchangestream.bitfinex; +import static org.knowm.xchange.service.BaseParamsDigest.HMAC_SHA_384; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.bitfinex.dto.BitfinexAuthRequestStatus; @@ -17,16 +19,6 @@ import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.subjects.PublishSubject; -import org.apache.commons.lang3.StringUtils; -import org.knowm.xchange.bitfinex.service.BitfinexAdapters; -import org.knowm.xchange.exceptions.ExchangeException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import si.mazi.rescu.SynchronizedValueFactory; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import javax.xml.bind.DatatypeConverter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; @@ -40,349 +32,355 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; +import org.apache.commons.lang3.StringUtils; +import org.knowm.xchange.bitfinex.service.BitfinexAdapters; +import org.knowm.xchange.exceptions.ExchangeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import si.mazi.rescu.SynchronizedValueFactory; -import static org.knowm.xchange.service.BaseParamsDigest.HMAC_SHA_384; - -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ public class BitfinexStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(BitfinexStreamingService.class); - - static final String CHANNEL_USER_POSITIONS = "userPositions"; - static final String CHANNEL_USER_BALANCE_UPDATES = "userBalanceUpdates"; - static final String CHANNEL_USER_BALANCES = "userBalances"; - static final String CHANNEL_USER_ORDER_UPDATES = "userOrderUpdates"; - static final String CHANNEL_USER_ORDERS = "userOrders"; - static final String CHANNEL_USER_TRADES = "userTrades"; - static final String CHANNEL_USER_PRE_TRADES = "userPreTrades"; - - private static final String INFO = "info"; - private static final String ERROR = "error"; - private static final String CHANNEL_ID = "chanId"; - private static final String SUBSCRIBED = "subscribed"; - private static final String UNSUBSCRIBED = "unsubscribed"; - private static final String ERROR_CODE = "code"; - private static final String AUTH = "auth"; - private static final String STATUS = "status"; - private static final String MESSAGE = "msg"; - private static final String EVENT = "event"; - private static final String VERSION = "version"; - - private static final int CALCULATION_BATCH_SIZE = 8; - private static final List WALLETS = Arrays.asList("exchange", "margin", "funding"); - - private final PublishSubject subjectPreTrade = PublishSubject.create(); - private final PublishSubject subjectTrade = PublishSubject.create(); - private final PublishSubject subjectOrder = PublishSubject.create(); - private final PublishSubject subjectBalance = PublishSubject.create(); - - private static final int SUBSCRIPTION_FAILED = 10300; - private static final int SUBSCRIPTION_DUP = 10301; - - private String apiKey; - private String apiSecret; - - private final Map subscribedChannels = new HashMap<>(); - private final SynchronizedValueFactory nonceFactory; - - private final BlockingQueue calculationQueue = new LinkedBlockingQueue<>(); - private Disposable calculator; - - public BitfinexStreamingService(String apiUrl, - SynchronizedValueFactory nonceFactory) { - super(apiUrl, Integer.MAX_VALUE, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION, 30); - this.nonceFactory = nonceFactory; - } - - @Override - public Completable connect() { - return super.connect().doOnComplete(() -> { - this.calculator = Observable.interval(1, TimeUnit.SECONDS).subscribe(x -> requestCalcs()); - }); - } - - @Override - public Completable disconnect() { - if (calculator != null) - calculator.dispose(); - return super.disconnect(); - } - - @Override - protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { - return null; + private static final Logger LOG = LoggerFactory.getLogger(BitfinexStreamingService.class); + + static final String CHANNEL_USER_POSITIONS = "userPositions"; + static final String CHANNEL_USER_BALANCE_UPDATES = "userBalanceUpdates"; + static final String CHANNEL_USER_BALANCES = "userBalances"; + static final String CHANNEL_USER_ORDER_UPDATES = "userOrderUpdates"; + static final String CHANNEL_USER_ORDERS = "userOrders"; + static final String CHANNEL_USER_TRADES = "userTrades"; + static final String CHANNEL_USER_PRE_TRADES = "userPreTrades"; + + private static final String INFO = "info"; + private static final String ERROR = "error"; + private static final String CHANNEL_ID = "chanId"; + private static final String SUBSCRIBED = "subscribed"; + private static final String UNSUBSCRIBED = "unsubscribed"; + private static final String ERROR_CODE = "code"; + private static final String AUTH = "auth"; + private static final String STATUS = "status"; + private static final String MESSAGE = "msg"; + private static final String EVENT = "event"; + private static final String VERSION = "version"; + + private static final int CALCULATION_BATCH_SIZE = 8; + private static final List WALLETS = Arrays.asList("exchange", "margin", "funding"); + + private final PublishSubject subjectPreTrade = + PublishSubject.create(); + private final PublishSubject subjectTrade = PublishSubject.create(); + private final PublishSubject subjectOrder = PublishSubject.create(); + private final PublishSubject subjectBalance = + PublishSubject.create(); + + private static final int SUBSCRIPTION_FAILED = 10300; + private static final int SUBSCRIPTION_DUP = 10301; + + private String apiKey; + private String apiSecret; + + private final Map subscribedChannels = new HashMap<>(); + private final SynchronizedValueFactory nonceFactory; + + private final BlockingQueue calculationQueue = new LinkedBlockingQueue<>(); + private Disposable calculator; + + public BitfinexStreamingService(String apiUrl, SynchronizedValueFactory nonceFactory) { + super(apiUrl, Integer.MAX_VALUE, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION, 30); + this.nonceFactory = nonceFactory; + } + + @Override + public Completable connect() { + return super.connect() + .doOnComplete( + () -> { + this.calculator = + Observable.interval(1, TimeUnit.SECONDS).subscribe(x -> requestCalcs()); + }); + } + + @Override + public Completable disconnect() { + if (calculator != null) calculator.dispose(); + return super.disconnect(); + } + + @Override + protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { + return null; + } + + @Override + public boolean processArrayMassageSeparately() { + return false; + } + + @Override + protected void handleMessage(JsonNode message) { + + if (message.isArray()) { + String type = message.get(1).asText(); + if (type.equals("hb")) { + return; + } } - @Override - public boolean processArrayMassageSeparately() { - return false; - } - - @Override - protected void handleMessage(JsonNode message) { - - if (message.isArray()) { - String type = message.get(1).asText(); - if (type.equals("hb")) { - return; - } - } - - JsonNode event = message.get(EVENT); - if (event != null) { - switch (event.textValue()) { - case INFO: - JsonNode version = message.get(VERSION); - if (version != null) { - LOG.debug("Bitfinex websocket API version: {}.", version.intValue()); - } - if (isAuthenticated()) - auth(); - break; - case AUTH: - if (message.get(STATUS).textValue().equals(BitfinexAuthRequestStatus.FAILED.name())) { - LOG.error("Authentication error: {}", message.get(MESSAGE)); - } - if (message.get(STATUS).textValue().equals(BitfinexAuthRequestStatus.OK.name())) { - LOG.info("Authenticated successfully"); - } - break; - case SUBSCRIBED: { - String channel = message.get("channel").asText(); - String pair = message.get("pair").asText(); - String channelId = message.get(CHANNEL_ID).asText(); - try { - String subscriptionUniqueId = getSubscriptionUniqueId(channel, pair); - subscribedChannels.put(channelId, subscriptionUniqueId); - LOG.debug("Register channel {}: {}", subscriptionUniqueId, channelId); - } catch (Exception e) { - LOG.error(e.getMessage()); - } - break; - } - case UNSUBSCRIBED: { - String channelId = message.get(CHANNEL_ID).asText(); - subscribedChannels.remove(channelId); - break; - } - case ERROR: - if (message.get("code").asInt() == SUBSCRIPTION_FAILED) { - LOG.error("Error with message: " + message.get("symbol") + " " + message.get("msg")); - return; - } - // {"channel":"ticker","pair":"BTCUSD","event":"error","msg":"subscribe: dup","code":10301} - if (message.get("code").asInt() == SUBSCRIPTION_DUP) { - LOG.warn("Already subscribed: " + message.toString()); - return; - } - super.handleError(message, new ExchangeException("Error code: " + message.get(ERROR_CODE).asText())); - break; - } - } else { + JsonNode event = message.get(EVENT); + if (event != null) { + switch (event.textValue()) { + case INFO: + JsonNode version = message.get(VERSION); + if (version != null) { + LOG.debug("Bitfinex websocket API version: {}.", version.intValue()); + } + if (isAuthenticated()) auth(); + break; + case AUTH: + if (message.get(STATUS).textValue().equals(BitfinexAuthRequestStatus.FAILED.name())) { + LOG.error("Authentication error: {}", message.get(MESSAGE)); + } + if (message.get(STATUS).textValue().equals(BitfinexAuthRequestStatus.OK.name())) { + LOG.info("Authenticated successfully"); + } + break; + case SUBSCRIBED: + { + String channel = message.get("channel").asText(); + String pair = message.get("pair").asText(); + String channelId = message.get(CHANNEL_ID).asText(); try { - if ("0".equals(getChannelNameFromMessage(message)) && message.isArray() && message.size() == 3) { - processAuthenticatedMessage(message); - return; - } - } catch (IOException e) { - throw new RuntimeException("Failed to get channel name from message", e); + String subscriptionUniqueId = getSubscriptionUniqueId(channel, pair); + subscribedChannels.put(channelId, subscriptionUniqueId); + LOG.debug("Register channel {}: {}", subscriptionUniqueId, channelId); + } catch (Exception e) { + LOG.error(e.getMessage()); } - super.handleMessage(message); - } - } - - private void processAuthenticatedMessage(JsonNode message) { - String type = message.get(1).asText(); - JsonNode object = message.get(2); - switch (type) { - case "te": - BitfinexWebSocketAuthPreTrade preTrade = BitfinexStreamingAdapters.adaptPreTrade(object); - if (preTrade != null) - subjectPreTrade.onNext(preTrade); - break; - case "tu": - BitfinexWebSocketAuthTrade trade = BitfinexStreamingAdapters.adaptTrade(object); - if (trade != null) - subjectTrade.onNext(trade); - break; - case "os": - BitfinexStreamingAdapters.adaptOrders(object).forEach(subjectOrder::onNext); - break; - case "on": - case "ou": - case "oc": - BitfinexWebSocketAuthOrder order = BitfinexStreamingAdapters.adaptOrder(object); - if (order != null) - subjectOrder.onNext(order); - break; - case "ws": - BitfinexStreamingAdapters.adaptBalances(object).forEach(subjectBalance::onNext); - break; - case "wu": - BitfinexWebSocketAuthBalance balance = BitfinexStreamingAdapters.adaptBalance(object); - if (balance != null) - subjectBalance.onNext(balance); - break; - default: - LOG.debug("Unknown Bitfinex authenticated message type {}. Content=", type, object); - } - } - - @Override - public String getSubscriptionUniqueId(String channelName, Object... args) { - if (args.length > 0) { - return channelName + "-" + args[0].toString(); - } else { - return channelName; - } - } - - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - String chanId = null; - if (message.has(CHANNEL_ID)) { - chanId = message.get(CHANNEL_ID).asText(); - } else { - JsonNode jsonNode = message.get(0); - if (jsonNode != null) { - chanId = message.get(0).asText(); - } - } - if (chanId == null) throw new IOException("Can't find CHANNEL_ID value in socket message: " + message.toString()); - String subscribedChannel = subscribedChannels.get(chanId); - if (subscribedChannel != null) - return subscribedChannel; - return chanId; // In case bitfinex adds new channels, just fallback to the name in the message - } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - BitfinexWebSocketSubscriptionMessage subscribeMessage = null; - if (args.length == 1) { - subscribeMessage = - new BitfinexWebSocketSubscriptionMessage(channelName, (String) args[0]); - } else if (args.length == 3) { - subscribeMessage = - new BitfinexWebSocketSubscriptionMessage(channelName, (String) args[0], (String) args[1], - (String) args[2]); - } - if (subscribeMessage == null) throw new IOException("SubscribeMessage: Insufficient arguments"); - - return objectMapper.writeValueAsString(subscribeMessage); - } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - String channelId = null; - for (Map.Entry entry : subscribedChannels.entrySet()) { - if (entry.getValue().equals(channelName)) { - channelId = entry.getKey(); - break; - } - } - - if (channelId == null) throw new IOException("Can't find channel unique name"); - - BitfinexWebSocketUnSubscriptionMessage subscribeMessage = - new BitfinexWebSocketUnSubscriptionMessage(channelId); - ObjectMapper objectMapper = StreamingObjectMapperHelper.getObjectMapper(); - return objectMapper.writeValueAsString(subscribeMessage); - } - - void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - void setApiSecret(String apiSecret) { - this.apiSecret = apiSecret; - } - - boolean isAuthenticated() { - return StringUtils.isNotEmpty(apiKey); - } - - private void auth() { - long nonce = nonceFactory.createValue(); - String payload = "AUTH" + nonce; - String signature; - try { - Mac macEncoder = Mac.getInstance(HMAC_SHA_384); - SecretKeySpec secretKeySpec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA_384); - macEncoder.init(secretKeySpec); - byte[] result = macEncoder.doFinal(payload.getBytes(StandardCharsets.UTF_8)); - signature = DatatypeConverter.printHexBinary(result); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - LOG.error("auth. Sign failed error={}", e.getMessage()); + break; + } + case UNSUBSCRIBED: + { + String channelId = message.get(CHANNEL_ID).asText(); + subscribedChannels.remove(channelId); + break; + } + case ERROR: + if (message.get("code").asInt() == SUBSCRIPTION_FAILED) { + LOG.error("Error with message: " + message.get("symbol") + " " + message.get("msg")); + return; + } + // {"channel":"ticker","pair":"BTCUSD","event":"error","msg":"subscribe: + // dup","code":10301} + if (message.get("code").asInt() == SUBSCRIPTION_DUP) { + LOG.warn("Already subscribed: " + message.toString()); return; + } + super.handleError( + message, new ExchangeException("Error code: " + message.get(ERROR_CODE).asText())); + break; + } + } else { + try { + if ("0".equals(getChannelNameFromMessage(message)) + && message.isArray() + && message.size() == 3) { + processAuthenticatedMessage(message); + return; } - BitfinexWebSocketAuth message = new BitfinexWebSocketAuth( - apiKey, payload, String.valueOf(nonce), signature.toLowerCase() - ); - sendObjectMessage(message); + } catch (IOException e) { + throw new RuntimeException("Failed to get channel name from message", e); + } + super.handleMessage(message); } - - Observable getAuthenticatedOrders() { - return subjectOrder.share(); + } + + private void processAuthenticatedMessage(JsonNode message) { + String type = message.get(1).asText(); + JsonNode object = message.get(2); + switch (type) { + case "te": + BitfinexWebSocketAuthPreTrade preTrade = BitfinexStreamingAdapters.adaptPreTrade(object); + if (preTrade != null) subjectPreTrade.onNext(preTrade); + break; + case "tu": + BitfinexWebSocketAuthTrade trade = BitfinexStreamingAdapters.adaptTrade(object); + if (trade != null) subjectTrade.onNext(trade); + break; + case "os": + BitfinexStreamingAdapters.adaptOrders(object).forEach(subjectOrder::onNext); + break; + case "on": + case "ou": + case "oc": + BitfinexWebSocketAuthOrder order = BitfinexStreamingAdapters.adaptOrder(object); + if (order != null) subjectOrder.onNext(order); + break; + case "ws": + BitfinexStreamingAdapters.adaptBalances(object).forEach(subjectBalance::onNext); + break; + case "wu": + BitfinexWebSocketAuthBalance balance = BitfinexStreamingAdapters.adaptBalance(object); + if (balance != null) subjectBalance.onNext(balance); + break; + default: + LOG.debug("Unknown Bitfinex authenticated message type {}. Content=", type, object); } - - Observable getAuthenticatedPreTrades() { - return subjectPreTrade.share(); + } + + @Override + public String getSubscriptionUniqueId(String channelName, Object... args) { + if (args.length > 0) { + return channelName + "-" + args[0].toString(); + } else { + return channelName; } - - Observable getAuthenticatedTrades() { - return subjectTrade.share(); + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + String chanId = null; + if (message.has(CHANNEL_ID)) { + chanId = message.get(CHANNEL_ID).asText(); + } else { + JsonNode jsonNode = message.get(0); + if (jsonNode != null) { + chanId = message.get(0).asText(); + } } - - Observable getAuthenticatedBalances() { - return subjectBalance.share(); + if (chanId == null) + throw new IOException("Can't find CHANNEL_ID value in socket message: " + message.toString()); + String subscribedChannel = subscribedChannels.get(chanId); + if (subscribedChannel != null) return subscribedChannel; + return chanId; // In case bitfinex adds new channels, just fallback to the name in the message + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + BitfinexWebSocketSubscriptionMessage subscribeMessage = null; + if (args.length == 1) { + subscribeMessage = new BitfinexWebSocketSubscriptionMessage(channelName, (String) args[0]); + } else if (args.length == 3) { + subscribeMessage = + new BitfinexWebSocketSubscriptionMessage( + channelName, (String) args[0], (String) args[1], (String) args[2]); } - - /** - * Call on receipt of a partial balance (missing available amount) to - * schedule the release of a full calculated amount at some point - * shortly. - * - * @param currency The currency code. - */ - void scheduleCalculatedBalanceFetch(String currency) { - LOG.debug("Scheduling request for full calculated balances for: {}", currency); - calculationQueue.add(currency); + if (subscribeMessage == null) throw new IOException("SubscribeMessage: Insufficient arguments"); + + return objectMapper.writeValueAsString(subscribeMessage); + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + String channelId = null; + for (Map.Entry entry : subscribedChannels.entrySet()) { + if (entry.getValue().equals(channelName)) { + channelId = entry.getKey(); + break; + } } - /** - * Bitfinex generally doesn't supply calculated data, such as the available amount - * in a balance, unless this is specifically requested. You have to send a message - * down the socket requesting the full information. However, this is rate limited - * to 8 calculations a second and 30 per batch, so we queue up requests and dispatch - * them in batches of 8, once a second. See {@link #scheduleCalculatedBalanceFetch(String)}. - * - *

Details: https://docs.bitfinex.com/v2/docs/changelog#section--calc-input-message

- */ - private void requestCalcs() { - Set currencies = new HashSet<>(); - do { - String nextRequest = calculationQueue.poll(); - if (nextRequest == null) - break; - if (currencies.size() >= CALCULATION_BATCH_SIZE) - break; - currencies.add(nextRequest); - } while (true); - - if (currencies.isEmpty()) - return; - - Object[] subscriptions = currencies.stream() + if (channelId == null) throw new IOException("Can't find channel unique name"); + + BitfinexWebSocketUnSubscriptionMessage subscribeMessage = + new BitfinexWebSocketUnSubscriptionMessage(channelId); + ObjectMapper objectMapper = StreamingObjectMapperHelper.getObjectMapper(); + return objectMapper.writeValueAsString(subscribeMessage); + } + + void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + void setApiSecret(String apiSecret) { + this.apiSecret = apiSecret; + } + + boolean isAuthenticated() { + return StringUtils.isNotEmpty(apiKey); + } + + private void auth() { + long nonce = nonceFactory.createValue(); + String payload = "AUTH" + nonce; + String signature; + try { + Mac macEncoder = Mac.getInstance(HMAC_SHA_384); + SecretKeySpec secretKeySpec = + new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA_384); + macEncoder.init(secretKeySpec); + byte[] result = macEncoder.doFinal(payload.getBytes(StandardCharsets.UTF_8)); + signature = DatatypeConverter.printHexBinary(result); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + LOG.error("auth. Sign failed error={}", e.getMessage()); + return; + } + BitfinexWebSocketAuth message = + new BitfinexWebSocketAuth(apiKey, payload, String.valueOf(nonce), signature.toLowerCase()); + sendObjectMessage(message); + } + + Observable getAuthenticatedOrders() { + return subjectOrder.share(); + } + + Observable getAuthenticatedPreTrades() { + return subjectPreTrade.share(); + } + + Observable getAuthenticatedTrades() { + return subjectTrade.share(); + } + + Observable getAuthenticatedBalances() { + return subjectBalance.share(); + } + + /** + * Call on receipt of a partial balance (missing available amount) to schedule the release of a + * full calculated amount at some point shortly. + * + * @param currency The currency code. + */ + void scheduleCalculatedBalanceFetch(String currency) { + LOG.debug("Scheduling request for full calculated balances for: {}", currency); + calculationQueue.add(currency); + } + + /** + * Bitfinex generally doesn't supply calculated data, such as the available amount in a balance, + * unless this is specifically requested. You have to send a message down the socket requesting + * the full information. However, this is rate limited to 8 calculations a second and 30 per + * batch, so we queue up requests and dispatch them in batches of 8, once a second. See {@link + * #scheduleCalculatedBalanceFetch(String)}. + * + *

Details: https://docs.bitfinex.com/v2/docs/changelog#section--calc-input-message + */ + private void requestCalcs() { + Set currencies = new HashSet<>(); + do { + String nextRequest = calculationQueue.poll(); + if (nextRequest == null) break; + if (currencies.size() >= CALCULATION_BATCH_SIZE) break; + currencies.add(nextRequest); + } while (true); + + if (currencies.isEmpty()) return; + + Object[] subscriptions = + currencies.stream() .map(BitfinexAdapters::adaptBitfinexCurrency) - .flatMap(currency -> WALLETS.stream().map(wallet -> "wallet_" + wallet + "_" + currency)) - .map(calcName -> new String[] { calcName }) + .flatMap( + currency -> WALLETS.stream().map(wallet -> "wallet_" + wallet + "_" + currency)) + .map(calcName -> new String[] {calcName}) .toArray(); - Object[] message = new Object[] {0, "calc", null, subscriptions}; + Object[] message = new Object[] {0, "calc", null, subscriptions}; - LOG.debug("Requesting full calculated balances for: {} in {}", currencies, WALLETS); + LOG.debug("Requesting full calculated balances for: {} in {}", currencies, WALLETS); - sendObjectMessage(message); - } + sendObjectMessage(message); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingTradeService.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingTradeService.java index 9c3077b5f..4ed77a566 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingTradeService.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingTradeService.java @@ -4,77 +4,75 @@ import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthPreTrade; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthTrade; import info.bitrich.xchangestream.core.StreamingTradeService; - import io.reactivex.Observable; - +import java.util.function.Function; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.UserTrade; import org.knowm.xchange.exceptions.ExchangeSecurityException; -import java.util.function.Function; - public class BitfinexStreamingTradeService implements StreamingTradeService { - private final BitfinexStreamingService service; + private final BitfinexStreamingService service; - public BitfinexStreamingTradeService(BitfinexStreamingService service) { - this.service = service; - } + public BitfinexStreamingTradeService(BitfinexStreamingService service) { + this.service = service; + } - public Observable getOrderChanges() { - return getRawAuthenticatedOrders() - .filter(o -> o.getId() != 0) - .map(BitfinexStreamingAdapters::adaptOrder) - .doOnNext(o -> { - service.scheduleCalculatedBalanceFetch(o.getCurrencyPair().base.getCurrencyCode()); - service.scheduleCalculatedBalanceFetch(o.getCurrencyPair().counter.getCurrencyCode()); - }); - } + public Observable getOrderChanges() { + return getRawAuthenticatedOrders() + .filter(o -> o.getId() != 0) + .map(BitfinexStreamingAdapters::adaptOrder) + .doOnNext( + o -> { + service.scheduleCalculatedBalanceFetch(o.getCurrencyPair().base.getCurrencyCode()); + service.scheduleCalculatedBalanceFetch(o.getCurrencyPair().counter.getCurrencyCode()); + }); + } - @Override - public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { - return getOrderChanges() - .filter(o -> currencyPair.equals(o.getCurrencyPair())); - } + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + return getOrderChanges().filter(o -> currencyPair.equals(o.getCurrencyPair())); + } - /** - * Gets a stream of all user trades to which we are subscribed. - * - * @return The stream of user trades. - */ - public Observable getUserTrades() { - return getRawAuthenticatedTrades() - .filter(o -> o.getId() != 0) - .map(BitfinexStreamingAdapters::adaptUserTrade) - .doOnNext(t -> { - service.scheduleCalculatedBalanceFetch(t.getCurrencyPair().base.getCurrencyCode()); - service.scheduleCalculatedBalanceFetch(t.getCurrencyPair().counter.getCurrencyCode()); - }); - } + /** + * Gets a stream of all user trades to which we are subscribed. + * + * @return The stream of user trades. + */ + public Observable getUserTrades() { + return getRawAuthenticatedTrades() + .filter(o -> o.getId() != 0) + .map(BitfinexStreamingAdapters::adaptUserTrade) + .doOnNext( + t -> { + service.scheduleCalculatedBalanceFetch(t.getCurrencyPair().base.getCurrencyCode()); + service.scheduleCalculatedBalanceFetch(t.getCurrencyPair().counter.getCurrencyCode()); + }); + } - @Override - public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { - return getUserTrades() - .filter(t -> currencyPair.equals(t.getCurrencyPair())); - } + @Override + public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + return getUserTrades().filter(t -> currencyPair.equals(t.getCurrencyPair())); + } - public Observable getRawAuthenticatedOrders() { - return withAuthenticatedService(BitfinexStreamingService::getAuthenticatedOrders); - } + public Observable getRawAuthenticatedOrders() { + return withAuthenticatedService(BitfinexStreamingService::getAuthenticatedOrders); + } - public Observable getRawAuthenticatedPreTrades() { - return withAuthenticatedService(BitfinexStreamingService::getAuthenticatedPreTrades); - } + public Observable getRawAuthenticatedPreTrades() { + return withAuthenticatedService(BitfinexStreamingService::getAuthenticatedPreTrades); + } - public Observable getRawAuthenticatedTrades() { - return withAuthenticatedService(BitfinexStreamingService::getAuthenticatedTrades); - } + public Observable getRawAuthenticatedTrades() { + return withAuthenticatedService(BitfinexStreamingService::getAuthenticatedTrades); + } - private Observable withAuthenticatedService(Function> serviceConsumer) { - if (!service.isAuthenticated()) { - throw new ExchangeSecurityException("Not authenticated"); - } - return serviceConsumer.apply(service); + private Observable withAuthenticatedService( + Function> serviceConsumer) { + if (!service.isAuthenticated()) { + throw new ExchangeSecurityException("Not authenticated"); } + return serviceConsumer.apply(service); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexAuthRequestStatus.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexAuthRequestStatus.java index f095ae0db..96daf9512 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexAuthRequestStatus.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexAuthRequestStatus.java @@ -1,6 +1,6 @@ package info.bitrich.xchangestream.bitfinex.dto; public enum BitfinexAuthRequestStatus { - OK, - FAILED + OK, + FAILED } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbook.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbook.java index 775c5db64..01c231839 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbook.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbook.java @@ -1,93 +1,84 @@ package info.bitrich.xchangestream.bitfinex.dto; -import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexDepth; -import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexLevel; +import static java.math.BigDecimal.ZERO; import java.math.BigDecimal; import java.util.*; +import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexDepth; +import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexLevel; -import static java.math.BigDecimal.ZERO; - -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ public class BitfinexOrderbook { - private Map asks; - private Map bids; + private Map asks; + private Map bids; + + public BitfinexOrderbook(BitfinexOrderbookLevel[] levels) { + createFromLevels(levels); + } - public BitfinexOrderbook(BitfinexOrderbookLevel[] levels) { - createFromLevels(levels); + private void createFromLevels(BitfinexOrderbookLevel[] levels) { + this.asks = new HashMap<>(levels.length / 2); + this.bids = new HashMap<>(levels.length / 2); + + for (BitfinexOrderbookLevel level : levels) { + + if (level.getCount().compareTo(ZERO) == 0) continue; + + if (level.getAmount().compareTo(ZERO) > 0) bids.put(level.getPrice(), level); + else + asks.put( + level.getPrice(), + new BitfinexOrderbookLevel( + level.getPrice(), level.getCount(), level.getAmount().abs())); + } + } + + public synchronized BitfinexDepth toBitfinexDepth() { + SortedMap bitfinexLevelAsks = new TreeMap<>(); + SortedMap bitfinexLevelBids = + new TreeMap<>(java.util.Collections.reverseOrder()); + + for (Map.Entry level : asks.entrySet()) { + bitfinexLevelAsks.put(level.getValue().getPrice(), level.getValue()); } - private void createFromLevels(BitfinexOrderbookLevel[] levels) { - this.asks = new HashMap<>(levels.length / 2); - this.bids = new HashMap<>(levels.length / 2); - - for (BitfinexOrderbookLevel level : levels) { - - if(level.getCount().compareTo(ZERO) == 0) - continue; - - if (level.getAmount().compareTo(ZERO) > 0) - bids.put(level.getPrice(), level); - else - asks.put(level.getPrice(), - new BitfinexOrderbookLevel( - level.getPrice(), - level.getCount(), - level.getAmount().abs() - )); - } + for (Map.Entry level : bids.entrySet()) { + bitfinexLevelBids.put(level.getValue().getPrice(), level.getValue()); } - public synchronized BitfinexDepth toBitfinexDepth() { - SortedMap bitfinexLevelAsks = new TreeMap<>(); - SortedMap bitfinexLevelBids = new TreeMap<>(java.util.Collections.reverseOrder()); - - for (Map.Entry level : asks.entrySet()) { - bitfinexLevelAsks.put(level.getValue().getPrice(), level.getValue()); - } - - for (Map.Entry level : bids.entrySet()) { - bitfinexLevelBids.put(level.getValue().getPrice(), level.getValue()); - } - - List askLevels = new ArrayList<>(asks.size()); - List bidLevels = new ArrayList<>(bids.size()); - for (Map.Entry level : bitfinexLevelAsks.entrySet()) { - askLevels.add(level.getValue().toBitfinexLevel()); - } - for (Map.Entry level : bitfinexLevelBids.entrySet()) { - bidLevels.add(level.getValue().toBitfinexLevel()); - } - - return new BitfinexDepth(askLevels.toArray(new BitfinexLevel[askLevels.size()]), - bidLevels.toArray(new BitfinexLevel[bidLevels.size()])); + List askLevels = new ArrayList<>(asks.size()); + List bidLevels = new ArrayList<>(bids.size()); + for (Map.Entry level : bitfinexLevelAsks.entrySet()) { + askLevels.add(level.getValue().toBitfinexLevel()); + } + for (Map.Entry level : bitfinexLevelBids.entrySet()) { + bidLevels.add(level.getValue().toBitfinexLevel()); } - public synchronized void updateLevel(BitfinexOrderbookLevel level) { + return new BitfinexDepth( + askLevels.toArray(new BitfinexLevel[askLevels.size()]), + bidLevels.toArray(new BitfinexLevel[bidLevels.size()])); + } + public synchronized void updateLevel(BitfinexOrderbookLevel level) { - Map side; + Map side; - // Determine side and normalize negative ask amount values - BitfinexOrderbookLevel bidAskLevel = level; - if(level.getAmount().compareTo(ZERO) < 0) { - side = asks; - bidAskLevel = new BitfinexOrderbookLevel( - level.getPrice(), - level.getCount(), - level.getAmount().abs() - ); - } else { - side = bids; - } + // Determine side and normalize negative ask amount values + BitfinexOrderbookLevel bidAskLevel = level; + if (level.getAmount().compareTo(ZERO) < 0) { + side = asks; + bidAskLevel = + new BitfinexOrderbookLevel(level.getPrice(), level.getCount(), level.getAmount().abs()); + } else { + side = bids; + } - boolean shouldDelete = bidAskLevel.getCount().compareTo(ZERO) == 0; + boolean shouldDelete = bidAskLevel.getCount().compareTo(ZERO) == 0; - side.remove(bidAskLevel.getPrice()); - if (!shouldDelete) { - side.put(bidAskLevel.getPrice(), bidAskLevel); - } + side.remove(bidAskLevel.getPrice()); + if (!shouldDelete) { + side.put(bidAskLevel.getPrice(), bidAskLevel); } + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookLevel.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookLevel.java index b3e92c2e4..5a246889b 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookLevel.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookLevel.java @@ -2,46 +2,42 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexLevel; - import java.math.BigDecimal; +import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexLevel; -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) -@JsonPropertyOrder({"price","count","amount"}) +@JsonPropertyOrder({"price", "count", "amount"}) public class BitfinexOrderbookLevel { - public BigDecimal price; + public BigDecimal price; - public BigDecimal count; + public BigDecimal count; - public BigDecimal amount; + public BigDecimal amount; - public BitfinexOrderbookLevel() { - } + public BitfinexOrderbookLevel() {} - public BitfinexOrderbookLevel(BigDecimal price, BigDecimal count, BigDecimal amount) { - this.price = price; - this.amount = amount; - this.count = count; - } + public BitfinexOrderbookLevel(BigDecimal price, BigDecimal count, BigDecimal amount) { + this.price = price; + this.amount = amount; + this.count = count; + } - public BigDecimal getPrice() { - return price; - } + public BigDecimal getPrice() { + return price; + } - public BigDecimal getAmount() { - return amount; - } + public BigDecimal getAmount() { + return amount; + } - public BigDecimal getCount() { - return count; - } + public BigDecimal getCount() { + return count; + } - public BitfinexLevel toBitfinexLevel() { - // Xchange-bitfinex adapter expects the timestamp to be seconds since Epoch. - return new BitfinexLevel(price, amount, new BigDecimal(System.currentTimeMillis() / 1000)); - } + public BitfinexLevel toBitfinexLevel() { + // Xchange-bitfinex adapter expects the timestamp to be seconds since Epoch. + return new BitfinexLevel(price, amount, new BigDecimal(System.currentTimeMillis() / 1000)); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuth.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuth.java index b0aba70dc..b2ca41fcb 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuth.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuth.java @@ -4,49 +4,62 @@ public class BitfinexWebSocketAuth { - private final String apiKey; - private final String authPayload; - private final String authNonce; - private final String authSig; - private final String event; - - public BitfinexWebSocketAuth(@JsonProperty("apiKey") String apiKey, @JsonProperty("authPayload") String authPayload, - @JsonProperty("authNonce") String authNonce, @JsonProperty("authSig") String authSig) { - this.apiKey = apiKey; - this.event = "auth"; - this.authPayload = authPayload; - this.authNonce = authNonce; - this.authSig = authSig; - } - - @Override - public String toString() { - return "BitfinexWebSocketAuth{" + - "apiKey='" + apiKey + '\'' + - ", authPayload='" + authPayload + '\'' + - ", authNonce='" + authNonce + '\'' + - ", authSig='" + authSig + '\'' + - ", event='" + event + '\'' + - '}'; - } - - public String getApiKey() { - return apiKey; - } - - public String getAuthPayload() { - return authPayload; - } - - public String getAuthNonce() { - return authNonce; - } - - public String getAuthSig() { - return authSig; - } - - public String getEvent() { - return event; - } + private final String apiKey; + private final String authPayload; + private final String authNonce; + private final String authSig; + private final String event; + + public BitfinexWebSocketAuth( + @JsonProperty("apiKey") String apiKey, + @JsonProperty("authPayload") String authPayload, + @JsonProperty("authNonce") String authNonce, + @JsonProperty("authSig") String authSig) { + this.apiKey = apiKey; + this.event = "auth"; + this.authPayload = authPayload; + this.authNonce = authNonce; + this.authSig = authSig; + } + + @Override + public String toString() { + return "BitfinexWebSocketAuth{" + + "apiKey='" + + apiKey + + '\'' + + ", authPayload='" + + authPayload + + '\'' + + ", authNonce='" + + authNonce + + '\'' + + ", authSig='" + + authSig + + '\'' + + ", event='" + + event + + '\'' + + '}'; + } + + public String getApiKey() { + return apiKey; + } + + public String getAuthPayload() { + return authPayload; + } + + public String getAuthNonce() { + return authNonce; + } + + public String getAuthSig() { + return authSig; + } + + public String getEvent() { + return event; + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthBalance.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthBalance.java index d079ff357..41ab26547 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthBalance.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthBalance.java @@ -4,67 +4,78 @@ import java.util.Objects; public class BitfinexWebSocketAuthBalance { - private String walletType; - private String currency; - private BigDecimal balance; - private BigDecimal unsettledInterest; - private BigDecimal balanceAvailable; + private String walletType; + private String currency; + private BigDecimal balance; + private BigDecimal unsettledInterest; + private BigDecimal balanceAvailable; - public BitfinexWebSocketAuthBalance(String walletType, String currency, BigDecimal balance, - BigDecimal unsettledInterest, BigDecimal balanceAvailable) { - this.walletType = walletType; - this.currency = currency; - this.balance = balance; - this.unsettledInterest = unsettledInterest; - this.balanceAvailable = balanceAvailable; - } + public BitfinexWebSocketAuthBalance( + String walletType, + String currency, + BigDecimal balance, + BigDecimal unsettledInterest, + BigDecimal balanceAvailable) { + this.walletType = walletType; + this.currency = currency; + this.balance = balance; + this.unsettledInterest = unsettledInterest; + this.balanceAvailable = balanceAvailable; + } - public String getWalletType() { - return walletType; - } + public String getWalletType() { + return walletType; + } - public String getCurrency() { - return currency; - } + public String getCurrency() { + return currency; + } - public BigDecimal getBalance() { - return balance; - } + public BigDecimal getBalance() { + return balance; + } - public BigDecimal getUnsettledInterest() { - return unsettledInterest; - } + public BigDecimal getUnsettledInterest() { + return unsettledInterest; + } - public BigDecimal getBalanceAvailable() { - return balanceAvailable; - } + public BigDecimal getBalanceAvailable() { + return balanceAvailable; + } - @Override - public String toString() { - return "BitfinexWebSocketAuthBalance{" + - "walletType='" + walletType + '\'' + - ", currency='" + currency + '\'' + - ", balance=" + balance + - ", unsettledInterest=" + unsettledInterest + - ", balanceAvailable=" + balanceAvailable + - '}'; - } + @Override + public String toString() { + return "BitfinexWebSocketAuthBalance{" + + "walletType='" + + walletType + + '\'' + + ", currency='" + + currency + + '\'' + + ", balance=" + + balance + + ", unsettledInterest=" + + unsettledInterest + + ", balanceAvailable=" + + balanceAvailable + + '}'; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BitfinexWebSocketAuthBalance)) return false; - BitfinexWebSocketAuthBalance that = (BitfinexWebSocketAuthBalance) o; - return Objects.equals(walletType, that.walletType) && - Objects.equals(currency, that.currency) && - Objects.equals(balance, that.balance) && - Objects.equals(unsettledInterest, that.unsettledInterest) && - Objects.equals(balanceAvailable, that.balanceAvailable); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BitfinexWebSocketAuthBalance)) return false; + BitfinexWebSocketAuthBalance that = (BitfinexWebSocketAuthBalance) o; + return Objects.equals(walletType, that.walletType) + && Objects.equals(currency, that.currency) + && Objects.equals(balance, that.balance) + && Objects.equals(unsettledInterest, that.unsettledInterest) + && Objects.equals(balanceAvailable, that.balanceAvailable); + } - @Override - public int hashCode() { + @Override + public int hashCode() { - return Objects.hash(walletType, currency, balance, unsettledInterest, balanceAvailable); - } + return Objects.hash(walletType, currency, balance, unsettledInterest, balanceAvailable); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthOrder.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthOrder.java index e94a8f24c..a9e1d44cb 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthOrder.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthOrder.java @@ -4,163 +4,217 @@ import java.util.Objects; public class BitfinexWebSocketAuthOrder { - private long id; - private long groupId; - private long cid; - private String symbol; - private long mtsCreate; - private long mtsUpdate; - private BigDecimal amount; - private BigDecimal amountOrig; - private String type; - private String typePrev; - private String orderStatus; - private BigDecimal price; - private BigDecimal priceAvg; - private BigDecimal priceTrailing; - private BigDecimal priceAuxLimit; - private long placedId; - private int flags; - - public BitfinexWebSocketAuthOrder(long id, long groupId, long cid, String symbol, long mtsCreate, long mtsUpdate, BigDecimal amount, BigDecimal amountOrig, String type, String typePrev, - String orderStatus, BigDecimal price, BigDecimal priceAvg, BigDecimal priceTrailing, BigDecimal priceAuxLimit, long placedId, int flags) { - this.id = id; - this.groupId = groupId; - this.cid = cid; - this.symbol = symbol; - this.mtsCreate = mtsCreate; - this.mtsUpdate = mtsUpdate; - this.amount = amount; - this.amountOrig = amountOrig; - this.type = type; - this.typePrev = typePrev; - this.orderStatus = orderStatus; - this.price = price; - this.priceAvg = priceAvg; - this.priceTrailing = priceTrailing; - this.priceAuxLimit = priceAuxLimit; - this.placedId = placedId; - this.flags = flags; - } - - @Override - public String toString() { - return "BitfinexWebSocketAuthenticatedOrder{" + - "id=" + id + - ", groupId=" + groupId + - ", cid=" + cid + - ", symbol='" + symbol + '\'' + - ", mtsCreate=" + mtsCreate + - ", mtsUpdate=" + mtsUpdate + - ", amount=" + amount + - ", amountOrig=" + amountOrig + - ", type='" + type + '\'' + - ", typePrev='" + typePrev + '\'' + - ", orderStatus='" + orderStatus + '\'' + - ", price=" + price + - ", priceAvg=" + priceAvg + - ", priceTrailing=" + priceTrailing + - ", priceAuxLimit=" + priceAuxLimit + - ", placedId=" + placedId + - ", flags=" + flags + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BitfinexWebSocketAuthOrder)) return false; - BitfinexWebSocketAuthOrder that = (BitfinexWebSocketAuthOrder) o; - return id == that.id && - groupId == that.groupId && - cid == that.cid && - mtsCreate == that.mtsCreate && - mtsUpdate == that.mtsUpdate && - placedId == that.placedId && - flags == that.flags && - Objects.equals(symbol, that.symbol) && - Objects.equals(amount, that.amount) && - Objects.equals(amountOrig, that.amountOrig) && - Objects.equals(type, that.type) && - Objects.equals(typePrev, that.typePrev) && - Objects.equals(orderStatus, that.orderStatus) && - Objects.equals(price, that.price) && - Objects.equals(priceAvg, that.priceAvg) && - Objects.equals(priceTrailing, that.priceTrailing) && - Objects.equals(priceAuxLimit, that.priceAuxLimit); - } - - @Override - public int hashCode() { - - return Objects.hash(id, groupId, cid, symbol, mtsCreate, mtsUpdate, amount, amountOrig, type, typePrev, orderStatus, price, priceAvg, priceTrailing, priceAuxLimit, placedId, flags); - } - - public long getId() { - return id; - } - - public long getGroupId() { - return groupId; - } - - public long getCid() { - return cid; - } - - public String getSymbol() { - return symbol; - } - - public long getMtsCreate() { - return mtsCreate; - } - - public long getMtsUpdate() { - return mtsUpdate; - } - - public BigDecimal getAmount() { - return amount; - } - - public BigDecimal getAmountOrig() { - return amountOrig; - } - - public String getType() { - return type; - } - - public String getTypePrev() { - return typePrev; - } - - public String getOrderStatus() { - return orderStatus; - } - - public BigDecimal getPrice() { - return price; - } - - public BigDecimal getPriceAvg() { - return priceAvg; - } - - public BigDecimal getPriceTrailing() { - return priceTrailing; - } - - public BigDecimal getPriceAuxLimit() { - return priceAuxLimit; - } - - public long getPlacedId() { - return placedId; - } - - public int getFlags() { - return flags; - } + private long id; + private long groupId; + private long cid; + private String symbol; + private long mtsCreate; + private long mtsUpdate; + private BigDecimal amount; + private BigDecimal amountOrig; + private String type; + private String typePrev; + private String orderStatus; + private BigDecimal price; + private BigDecimal priceAvg; + private BigDecimal priceTrailing; + private BigDecimal priceAuxLimit; + private long placedId; + private int flags; + + public BitfinexWebSocketAuthOrder( + long id, + long groupId, + long cid, + String symbol, + long mtsCreate, + long mtsUpdate, + BigDecimal amount, + BigDecimal amountOrig, + String type, + String typePrev, + String orderStatus, + BigDecimal price, + BigDecimal priceAvg, + BigDecimal priceTrailing, + BigDecimal priceAuxLimit, + long placedId, + int flags) { + this.id = id; + this.groupId = groupId; + this.cid = cid; + this.symbol = symbol; + this.mtsCreate = mtsCreate; + this.mtsUpdate = mtsUpdate; + this.amount = amount; + this.amountOrig = amountOrig; + this.type = type; + this.typePrev = typePrev; + this.orderStatus = orderStatus; + this.price = price; + this.priceAvg = priceAvg; + this.priceTrailing = priceTrailing; + this.priceAuxLimit = priceAuxLimit; + this.placedId = placedId; + this.flags = flags; + } + + @Override + public String toString() { + return "BitfinexWebSocketAuthenticatedOrder{" + + "id=" + + id + + ", groupId=" + + groupId + + ", cid=" + + cid + + ", symbol='" + + symbol + + '\'' + + ", mtsCreate=" + + mtsCreate + + ", mtsUpdate=" + + mtsUpdate + + ", amount=" + + amount + + ", amountOrig=" + + amountOrig + + ", type='" + + type + + '\'' + + ", typePrev='" + + typePrev + + '\'' + + ", orderStatus='" + + orderStatus + + '\'' + + ", price=" + + price + + ", priceAvg=" + + priceAvg + + ", priceTrailing=" + + priceTrailing + + ", priceAuxLimit=" + + priceAuxLimit + + ", placedId=" + + placedId + + ", flags=" + + flags + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BitfinexWebSocketAuthOrder)) return false; + BitfinexWebSocketAuthOrder that = (BitfinexWebSocketAuthOrder) o; + return id == that.id + && groupId == that.groupId + && cid == that.cid + && mtsCreate == that.mtsCreate + && mtsUpdate == that.mtsUpdate + && placedId == that.placedId + && flags == that.flags + && Objects.equals(symbol, that.symbol) + && Objects.equals(amount, that.amount) + && Objects.equals(amountOrig, that.amountOrig) + && Objects.equals(type, that.type) + && Objects.equals(typePrev, that.typePrev) + && Objects.equals(orderStatus, that.orderStatus) + && Objects.equals(price, that.price) + && Objects.equals(priceAvg, that.priceAvg) + && Objects.equals(priceTrailing, that.priceTrailing) + && Objects.equals(priceAuxLimit, that.priceAuxLimit); + } + + @Override + public int hashCode() { + + return Objects.hash( + id, + groupId, + cid, + symbol, + mtsCreate, + mtsUpdate, + amount, + amountOrig, + type, + typePrev, + orderStatus, + price, + priceAvg, + priceTrailing, + priceAuxLimit, + placedId, + flags); + } + + public long getId() { + return id; + } + + public long getGroupId() { + return groupId; + } + + public long getCid() { + return cid; + } + + public String getSymbol() { + return symbol; + } + + public long getMtsCreate() { + return mtsCreate; + } + + public long getMtsUpdate() { + return mtsUpdate; + } + + public BigDecimal getAmount() { + return amount; + } + + public BigDecimal getAmountOrig() { + return amountOrig; + } + + public String getType() { + return type; + } + + public String getTypePrev() { + return typePrev; + } + + public String getOrderStatus() { + return orderStatus; + } + + public BigDecimal getPrice() { + return price; + } + + public BigDecimal getPriceAvg() { + return priceAvg; + } + + public BigDecimal getPriceTrailing() { + return priceTrailing; + } + + public BigDecimal getPriceAuxLimit() { + return priceAuxLimit; + } + + public long getPlacedId() { + return placedId; + } + + public int getFlags() { + return flags; + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthPreTrade.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthPreTrade.java index 7e1ee6343..4783becdb 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthPreTrade.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthPreTrade.java @@ -4,99 +4,126 @@ import java.util.Objects; public class BitfinexWebSocketAuthPreTrade { - private final long id; - private final String pair; - private final long mtsCreate; - private final long orderId; - private final BigDecimal execAmount; - private final BigDecimal execPrice; - private final String orderType; - private final BigDecimal orderPrice; - private final long maker; + private final long id; + private final String pair; + private final long mtsCreate; + private final long orderId; + private final BigDecimal execAmount; + private final BigDecimal execPrice; + private final String orderType; + private final BigDecimal orderPrice; + private final long maker; - public BitfinexWebSocketAuthPreTrade(long id, String pair, long mtsCreate, long orderId, - BigDecimal execAmount, BigDecimal execPrice, - String orderType, BigDecimal orderPrice, long maker) { - this.id = id; - this.pair = pair; - this.mtsCreate = mtsCreate; - this.orderId = orderId; - this.execAmount = execAmount; - this.execPrice = execPrice; - this.orderType = orderType; - this.orderPrice = orderPrice; - this.maker = maker; - } + public BitfinexWebSocketAuthPreTrade( + long id, + String pair, + long mtsCreate, + long orderId, + BigDecimal execAmount, + BigDecimal execPrice, + String orderType, + BigDecimal orderPrice, + long maker) { + this.id = id; + this.pair = pair; + this.mtsCreate = mtsCreate; + this.orderId = orderId; + this.execAmount = execAmount; + this.execPrice = execPrice; + this.orderType = orderType; + this.orderPrice = orderPrice; + this.maker = maker; + } - public long getId() { - return id; - } + public long getId() { + return id; + } - public String getPair() { - return pair; - } + public String getPair() { + return pair; + } - public long getMtsCreate() { - return mtsCreate; - } + public long getMtsCreate() { + return mtsCreate; + } - public long getOrderId() { - return orderId; - } + public long getOrderId() { + return orderId; + } - public BigDecimal getExecAmount() { - return execAmount; - } + public BigDecimal getExecAmount() { + return execAmount; + } - public BigDecimal getExecPrice() { - return execPrice; - } + public BigDecimal getExecPrice() { + return execPrice; + } - public String getOrderType() { - return orderType; - } + public String getOrderType() { + return orderType; + } - public BigDecimal getOrderPrice() { - return orderPrice; - } + public BigDecimal getOrderPrice() { + return orderPrice; + } - public long getMaker() { - return maker; - } + public long getMaker() { + return maker; + } - @Override - public String toString() { - return "BitfinexWebSocketAuthPreTrade{" + - "id=" + id + - ", pair='" + pair + '\'' + - ", mtsCreate=" + mtsCreate + - ", orderId=" + orderId + - ", execAmount=" + execAmount + - ", execPrice=" + execPrice + - ", orderType='" + orderType + '\'' + - ", orderPrice=" + orderPrice + - ", maker=" + maker + - '}'; - } + @Override + public String toString() { + return "BitfinexWebSocketAuthPreTrade{" + + "id=" + + id + + ", pair='" + + pair + + '\'' + + ", mtsCreate=" + + mtsCreate + + ", orderId=" + + orderId + + ", execAmount=" + + execAmount + + ", execPrice=" + + execPrice + + ", orderType='" + + orderType + + '\'' + + ", orderPrice=" + + orderPrice + + ", maker=" + + maker + + '}'; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BitfinexWebSocketAuthPreTrade)) return false; - BitfinexWebSocketAuthPreTrade that = (BitfinexWebSocketAuthPreTrade) o; - return getId() == that.getId() && - getMtsCreate() == that.getMtsCreate() && - getOrderId() == that.getOrderId() && - getMaker() == that.getMaker() && - Objects.equals(getPair(), that.getPair()) && - Objects.equals(getExecAmount(), that.getExecAmount()) && - Objects.equals(getExecPrice(), that.getExecPrice()) && - Objects.equals(getOrderType(), that.getOrderType()) && - Objects.equals(getOrderPrice(), that.getOrderPrice()); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BitfinexWebSocketAuthPreTrade)) return false; + BitfinexWebSocketAuthPreTrade that = (BitfinexWebSocketAuthPreTrade) o; + return getId() == that.getId() + && getMtsCreate() == that.getMtsCreate() + && getOrderId() == that.getOrderId() + && getMaker() == that.getMaker() + && Objects.equals(getPair(), that.getPair()) + && Objects.equals(getExecAmount(), that.getExecAmount()) + && Objects.equals(getExecPrice(), that.getExecPrice()) + && Objects.equals(getOrderType(), that.getOrderType()) + && Objects.equals(getOrderPrice(), that.getOrderPrice()); + } - @Override - public int hashCode() { - return Objects.hash(getId(), getPair(), getMtsCreate(), getOrderId(), getExecAmount(), getExecPrice(), getOrderType(), getOrderPrice(), getMaker()); - } + @Override + public int hashCode() { + return Objects.hash( + getId(), + getPair(), + getMtsCreate(), + getOrderId(), + getExecAmount(), + getExecPrice(), + getOrderType(), + getOrderPrice(), + getMaker()); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthTrade.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthTrade.java index 5e1072624..5af2bb06c 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthTrade.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketAuthTrade.java @@ -4,55 +4,76 @@ import java.util.Objects; public class BitfinexWebSocketAuthTrade extends BitfinexWebSocketAuthPreTrade { - private final BigDecimal fee; - private final String feeCurrency; - - public BitfinexWebSocketAuthTrade(long id, String pair, long mtsCreate, - long orderId, BigDecimal execAmount, BigDecimal execPrice, - String orderType, BigDecimal orderPrice, - long maker, BigDecimal fee, String feeCurrency) { - super(id, pair, mtsCreate, orderId, execAmount, execPrice, orderType, orderPrice, maker); - this.fee = fee; - this.feeCurrency = feeCurrency; - } - - public BigDecimal getFee() { - return fee; - } - - public String getFeeCurrency() { - return feeCurrency; - } - - @Override - public String toString() { - return "BitfinexWebSocketAuthenticatedTrade{" + - "id=" + getId() + - ", pair='" + getPair() + '\'' + - ", mtsCreate=" + getMtsCreate() + - ", orderId=" + getOrderId() + - ", execAmount=" + getExecAmount() + - ", execPrice=" + getExecPrice() + - ", orderType='" + getOrderType() + '\'' + - ", orderPrice=" + getOrderPrice() + - ", maker=" + getMtsCreate() + - ", fee=" + fee + - ", feeCurrency='" + feeCurrency + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BitfinexWebSocketAuthTrade)) return false; - if (!super.equals(o)) return false; - BitfinexWebSocketAuthTrade that = (BitfinexWebSocketAuthTrade) o; - return Objects.equals(fee, that.fee) && - Objects.equals(feeCurrency, that.feeCurrency); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), fee, feeCurrency); - } + private final BigDecimal fee; + private final String feeCurrency; + + public BitfinexWebSocketAuthTrade( + long id, + String pair, + long mtsCreate, + long orderId, + BigDecimal execAmount, + BigDecimal execPrice, + String orderType, + BigDecimal orderPrice, + long maker, + BigDecimal fee, + String feeCurrency) { + super(id, pair, mtsCreate, orderId, execAmount, execPrice, orderType, orderPrice, maker); + this.fee = fee; + this.feeCurrency = feeCurrency; + } + + public BigDecimal getFee() { + return fee; + } + + public String getFeeCurrency() { + return feeCurrency; + } + + @Override + public String toString() { + return "BitfinexWebSocketAuthenticatedTrade{" + + "id=" + + getId() + + ", pair='" + + getPair() + + '\'' + + ", mtsCreate=" + + getMtsCreate() + + ", orderId=" + + getOrderId() + + ", execAmount=" + + getExecAmount() + + ", execPrice=" + + getExecPrice() + + ", orderType='" + + getOrderType() + + '\'' + + ", orderPrice=" + + getOrderPrice() + + ", maker=" + + getMtsCreate() + + ", fee=" + + fee + + ", feeCurrency='" + + feeCurrency + + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BitfinexWebSocketAuthTrade)) return false; + if (!super.equals(o)) return false; + BitfinexWebSocketAuthTrade that = (BitfinexWebSocketAuthTrade) o; + return Objects.equals(fee, that.fee) && Objects.equals(feeCurrency, that.feeCurrency); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), fee, feeCurrency); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketOrderbookTransaction.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketOrderbookTransaction.java index 3771efb6e..38f18cd7a 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketOrderbookTransaction.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketOrderbookTransaction.java @@ -1,21 +1,18 @@ package info.bitrich.xchangestream.bitfinex.dto; -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ public abstract class BitfinexWebSocketOrderbookTransaction { - public String channelId; + public String channelId; - public BitfinexWebSocketOrderbookTransaction() { - } + public BitfinexWebSocketOrderbookTransaction() {} - public BitfinexWebSocketOrderbookTransaction(String channelId) { - this.channelId = channelId; - } + public BitfinexWebSocketOrderbookTransaction(String channelId) { + this.channelId = channelId; + } - public String getChannelId() { - return channelId; - } + public String getChannelId() { + return channelId; + } - public abstract BitfinexOrderbook toBitfinexOrderBook(BitfinexOrderbook orderbook); + public abstract BitfinexOrderbook toBitfinexOrderBook(BitfinexOrderbook orderbook); } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotOrderbook.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotOrderbook.java index c1bf6ece4..b43a2dcb8 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotOrderbook.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotOrderbook.java @@ -2,15 +2,13 @@ import com.fasterxml.jackson.annotation.JsonFormat; -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class BitfinexWebSocketSnapshotOrderbook extends BitfinexWebSocketOrderbookTransaction { - public BitfinexOrderbookLevel[] levels; + public BitfinexOrderbookLevel[] levels; - @Override - public BitfinexOrderbook toBitfinexOrderBook(BitfinexOrderbook orderbook) { - return new BitfinexOrderbook(levels); - } + @Override + public BitfinexOrderbook toBitfinexOrderBook(BitfinexOrderbook orderbook) { + return new BitfinexOrderbook(levels); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotTrades.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotTrades.java index cfe3eded5..44cf7b9b3 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotTrades.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSnapshotTrades.java @@ -1,36 +1,32 @@ package info.bitrich.xchangestream.bitfinex.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTrade; - import java.util.ArrayList; import java.util.List; +import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTrade; -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class BitfinexWebSocketSnapshotTrades extends BitfinexWebSocketTradesTransaction { - public BitfinexWebSocketTrade[] trades; - - public BitfinexWebSocketSnapshotTrades() { - } + public BitfinexWebSocketTrade[] trades; - public BitfinexWebSocketSnapshotTrades(String channelId, BitfinexWebSocketTrade[] trades) { - super(channelId); - this.trades = trades; - } + public BitfinexWebSocketSnapshotTrades() {} - public BitfinexWebSocketTrade[] getTrades() { - return trades; - } + public BitfinexWebSocketSnapshotTrades(String channelId, BitfinexWebSocketTrade[] trades) { + super(channelId); + this.trades = trades; + } - public BitfinexTrade[] toBitfinexTrades() { - List bitfinexTrades = new ArrayList<>(getTrades().length); - for (BitfinexWebSocketTrade websocketTrade : trades) { - bitfinexTrades.add(websocketTrade.toBitfinexTrade()); - } + public BitfinexWebSocketTrade[] getTrades() { + return trades; + } - return bitfinexTrades.toArray(new BitfinexTrade[bitfinexTrades.size()]); + public BitfinexTrade[] toBitfinexTrades() { + List bitfinexTrades = new ArrayList<>(getTrades().length); + for (BitfinexWebSocketTrade websocketTrade : trades) { + bitfinexTrades.add(websocketTrade.toBitfinexTrade()); } + + return bitfinexTrades.toArray(new BitfinexTrade[bitfinexTrades.size()]); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSubscriptionMessage.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSubscriptionMessage.java index dbb932a9e..259b34984 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSubscriptionMessage.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketSubscriptionMessage.java @@ -2,63 +2,62 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ public class BitfinexWebSocketSubscriptionMessage { - private static final String EVENT = "event"; - private static final String CHANNEL = "channel"; - private static final String PAIR = "pair"; - private static final String PREC = "prec"; - private static final String LEN = "len"; - - @JsonProperty(EVENT) - private String event; - - @JsonProperty(CHANNEL) - private String channel; - - @JsonProperty(PAIR) - private String pair; - - @JsonProperty(PREC) - private String prec; - - @JsonProperty(LEN) - private String len; - - public BitfinexWebSocketSubscriptionMessage(String channel, String pair) { - this.event = "subscribe"; - this.channel = channel; - this.pair = pair; - } - - public BitfinexWebSocketSubscriptionMessage(String channel, String pair, String prec, String len) { - this.event = "subscribe"; - this.channel = channel; - this.pair = pair; - this.prec = prec; - this.len = len; - } - - public String getEvent() { - return event; - } - - public String getChannel() { - return channel; - } - - public String getPair() { - return pair; - } - - public String getPrec() { - return prec; - } - - public String getLen() { - return len; - } + private static final String EVENT = "event"; + private static final String CHANNEL = "channel"; + private static final String PAIR = "pair"; + private static final String PREC = "prec"; + private static final String LEN = "len"; + + @JsonProperty(EVENT) + private String event; + + @JsonProperty(CHANNEL) + private String channel; + + @JsonProperty(PAIR) + private String pair; + + @JsonProperty(PREC) + private String prec; + + @JsonProperty(LEN) + private String len; + + public BitfinexWebSocketSubscriptionMessage(String channel, String pair) { + this.event = "subscribe"; + this.channel = channel; + this.pair = pair; + } + + public BitfinexWebSocketSubscriptionMessage( + String channel, String pair, String prec, String len) { + this.event = "subscribe"; + this.channel = channel; + this.pair = pair; + this.prec = prec; + this.len = len; + } + + public String getEvent() { + return event; + } + + public String getChannel() { + return channel; + } + + public String getPair() { + return pair; + } + + public String getPrec() { + return prec; + } + + public String getLen() { + return len; + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTickerTransaction.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTickerTransaction.java index d4bb88301..fc78b35c5 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTickerTransaction.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTickerTransaction.java @@ -1,42 +1,38 @@ package info.bitrich.xchangestream.bitfinex.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTicker; - import java.math.BigDecimal; +import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTicker; -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class BitfinexWebSocketTickerTransaction { - public String channelId; - public String[] tickerArr; - - public BitfinexWebSocketTickerTransaction() { - } - - public BitfinexWebSocketTickerTransaction(String channelId, String[] tickerArr) { - this.channelId = channelId; - this.tickerArr = tickerArr; - } - - public String getChannelId() { - return channelId; - } - - public BitfinexTicker toBitfinexTicker() { - BigDecimal bid = new BigDecimal(tickerArr[0]); - BigDecimal ask = new BigDecimal(tickerArr[2]); - BigDecimal mid = ask.subtract(bid); - BigDecimal low = new BigDecimal(tickerArr[9]); - BigDecimal high = new BigDecimal(tickerArr[8]); - BigDecimal last = new BigDecimal(tickerArr[6]); - // Xchange-bitfinex adapter expects the timestamp to be seconds since Epoch. - double timestamp = System.currentTimeMillis() / 1000; - BigDecimal volume = new BigDecimal(tickerArr[7]); - - return new BitfinexTicker(mid, bid, ask, low, high, last, timestamp, volume); - } + public String channelId; + public String[] tickerArr; + + public BitfinexWebSocketTickerTransaction() {} + + public BitfinexWebSocketTickerTransaction(String channelId, String[] tickerArr) { + this.channelId = channelId; + this.tickerArr = tickerArr; + } + + public String getChannelId() { + return channelId; + } + + public BitfinexTicker toBitfinexTicker() { + BigDecimal bid = new BigDecimal(tickerArr[0]); + BigDecimal ask = new BigDecimal(tickerArr[2]); + BigDecimal mid = ask.subtract(bid); + BigDecimal low = new BigDecimal(tickerArr[9]); + BigDecimal high = new BigDecimal(tickerArr[8]); + BigDecimal last = new BigDecimal(tickerArr[6]); + // Xchange-bitfinex adapter expects the timestamp to be seconds since Epoch. + double timestamp = System.currentTimeMillis() / 1000; + BigDecimal volume = new BigDecimal(tickerArr[7]); + + return new BitfinexTicker(mid, bid, ask, low, high, last, timestamp, volume); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTrade.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTrade.java index 16ff273e5..a60163ad2 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTrade.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTrade.java @@ -1,53 +1,49 @@ package info.bitrich.xchangestream.bitfinex.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTrade; - import java.math.BigDecimal; +import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTrade; -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class BitfinexWebSocketTrade { - public long tradeId; - public long timestamp; - public BigDecimal amount; - public BigDecimal price; - - public BitfinexWebSocketTrade() { - } - - public BitfinexWebSocketTrade(long tradeId, long timestamp, BigDecimal amount, BigDecimal price) { - this.tradeId = tradeId; - this.timestamp = timestamp; - this.amount = amount; - this.price = price; - } - - public long getTradeId() { - return tradeId; - } - - public long getTimestamp() { - return timestamp; - } - - public BigDecimal getAmount() { - return amount; - } - - public BigDecimal getPrice() { - return price; - } - - public BitfinexTrade toBitfinexTrade() { - String type; - if (amount.compareTo(BigDecimal.ZERO) < 0) { - type = "sell"; - } else { - type = "buy"; - } - return new BitfinexTrade(price, amount.abs(), timestamp / 1000, "bitfinex", tradeId, type); + public long tradeId; + public long timestamp; + public BigDecimal amount; + public BigDecimal price; + + public BitfinexWebSocketTrade() {} + + public BitfinexWebSocketTrade(long tradeId, long timestamp, BigDecimal amount, BigDecimal price) { + this.tradeId = tradeId; + this.timestamp = timestamp; + this.amount = amount; + this.price = price; + } + + public long getTradeId() { + return tradeId; + } + + public long getTimestamp() { + return timestamp; + } + + public BigDecimal getAmount() { + return amount; + } + + public BigDecimal getPrice() { + return price; + } + + public BitfinexTrade toBitfinexTrade() { + String type; + if (amount.compareTo(BigDecimal.ZERO) < 0) { + type = "sell"; + } else { + type = "buy"; } + return new BitfinexTrade(price, amount.abs(), timestamp / 1000, "bitfinex", tradeId, type); + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTradesTransaction.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTradesTransaction.java index b2ef6678c..d919dc6a0 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTradesTransaction.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketTradesTransaction.java @@ -2,22 +2,19 @@ import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTrade; -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ public abstract class BitfinexWebSocketTradesTransaction { - public String channelId; + public String channelId; - public BitfinexWebSocketTradesTransaction() { - } + public BitfinexWebSocketTradesTransaction() {} - public BitfinexWebSocketTradesTransaction(String channelId) { - this.channelId = channelId; - } + public BitfinexWebSocketTradesTransaction(String channelId) { + this.channelId = channelId; + } - public String getChannelId() { - return channelId; - } + public String getChannelId() { + return channelId; + } - public abstract BitfinexTrade[] toBitfinexTrades(); + public abstract BitfinexTrade[] toBitfinexTrades(); } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUnSubscriptionMessage.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUnSubscriptionMessage.java index 8df301141..e0e1650f2 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUnSubscriptionMessage.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUnSubscriptionMessage.java @@ -2,29 +2,27 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ public class BitfinexWebSocketUnSubscriptionMessage { - private static final String EVENT = "event"; - private static final String CHANNEL_ID = "chanId"; + private static final String EVENT = "event"; + private static final String CHANNEL_ID = "chanId"; - @JsonProperty(EVENT) - private String event; + @JsonProperty(EVENT) + private String event; - @JsonProperty(CHANNEL_ID) - private String channelId; + @JsonProperty(CHANNEL_ID) + private String channelId; - public BitfinexWebSocketUnSubscriptionMessage(String channelId) { - this.event = "unsubscribe"; - this.channelId = channelId; - } + public BitfinexWebSocketUnSubscriptionMessage(String channelId) { + this.event = "unsubscribe"; + this.channelId = channelId; + } - public String getEvent() { - return event; - } + public String getEvent() { + return event; + } - public String getChannelId() { - return channelId; - } + public String getChannelId() { + return channelId; + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUpdateOrderbook.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUpdateOrderbook.java index fed8aff70..15f686ad3 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUpdateOrderbook.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebSocketUpdateOrderbook.java @@ -2,23 +2,20 @@ import com.fasterxml.jackson.annotation.JsonFormat; -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class BitfinexWebSocketUpdateOrderbook extends BitfinexWebSocketOrderbookTransaction { - public BitfinexOrderbookLevel level; + public BitfinexOrderbookLevel level; - public BitfinexWebSocketUpdateOrderbook() { - } + public BitfinexWebSocketUpdateOrderbook() {} - public BitfinexWebSocketUpdateOrderbook(BitfinexOrderbookLevel level) { - this.level = level; - } + public BitfinexWebSocketUpdateOrderbook(BitfinexOrderbookLevel level) { + this.level = level; + } - @Override - public BitfinexOrderbook toBitfinexOrderBook(BitfinexOrderbook orderbook) { - orderbook.updateLevel(level); - return orderbook; - } + @Override + public BitfinexOrderbook toBitfinexOrderBook(BitfinexOrderbook orderbook) { + orderbook.updateLevel(level); + return orderbook; + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebsocketUpdateTrade.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebsocketUpdateTrade.java index 9f3492b22..eaf10372e 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebsocketUpdateTrade.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexWebsocketUpdateTrade.java @@ -3,32 +3,29 @@ import com.fasterxml.jackson.annotation.JsonFormat; import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTrade; -/** - * Created by Lukas Zaoralek on 8.11.17. - */ +/** Created by Lukas Zaoralek on 8.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class BitfinexWebsocketUpdateTrade extends BitfinexWebSocketTradesTransaction { - public String type; - public BitfinexWebSocketTrade trade; + public String type; + public BitfinexWebSocketTrade trade; - public BitfinexWebsocketUpdateTrade() { - } + public BitfinexWebsocketUpdateTrade() {} - public BitfinexWebsocketUpdateTrade(String channelId, String type, BitfinexWebSocketTrade trade) { - super(channelId); - this.type = type; - this.trade = trade; - } + public BitfinexWebsocketUpdateTrade(String channelId, String type, BitfinexWebSocketTrade trade) { + super(channelId); + this.type = type; + this.trade = trade; + } - public String getType() { - return type; - } + public String getType() { + return type; + } - public BitfinexWebSocketTrade getTrade() { - return trade; - } + public BitfinexWebSocketTrade getTrade() { + return trade; + } - public BitfinexTrade[] toBitfinexTrades() { - return new BitfinexTrade[]{trade.toBitfinexTrade()}; - } + public BitfinexTrade[] toBitfinexTrades() { + return new BitfinexTrade[] {trade.toBitfinexTrade()}; + } } diff --git a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/jackson/BitfinexWebSocketUpdateOrderbookDeserializer.java b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/jackson/BitfinexWebSocketUpdateOrderbookDeserializer.java index f0fd31a49..92729b8d0 100644 --- a/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/jackson/BitfinexWebSocketUpdateOrderbookDeserializer.java +++ b/xchange-stream-bitfinex/src/main/java/info/bitrich/xchangestream/bitfinex/dto/jackson/BitfinexWebSocketUpdateOrderbookDeserializer.java @@ -4,21 +4,22 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; - import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketUpdateOrderbook; - import java.io.IOException; -public class BitfinexWebSocketUpdateOrderbookDeserializer extends StdDeserializer { +public class BitfinexWebSocketUpdateOrderbookDeserializer + extends StdDeserializer { - private static final long serialVersionUID = -5932404683677998182L; + private static final long serialVersionUID = -5932404683677998182L; - protected BitfinexWebSocketUpdateOrderbookDeserializer(Class vc) { - super(vc); - } + protected BitfinexWebSocketUpdateOrderbookDeserializer(Class vc) { + super(vc); + } - @Override - public BitfinexWebSocketUpdateOrderbook deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - return null; - } + @Override + public BitfinexWebSocketUpdateOrderbook deserialize( + JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException, JsonProcessingException { + return null; + } } diff --git a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualAuthExample.java b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualAuthExample.java index 80fb9bf97..ce987513b 100644 --- a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualAuthExample.java +++ b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualAuthExample.java @@ -1,7 +1,6 @@ package info.bitrich.xchangestream.bitfinex; import info.bitrich.xchangestream.core.StreamingExchangeFactory; - import org.apache.commons.lang3.StringUtils; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.currency.Currency; @@ -10,79 +9,96 @@ import org.slf4j.LoggerFactory; public class BitfinexManualAuthExample { - private static final Logger LOG = LoggerFactory.getLogger(BitfinexManualAuthExample.class); + private static final Logger LOG = LoggerFactory.getLogger(BitfinexManualAuthExample.class); - public static void main(String[] args) { + public static void main(String[] args) { - // Far safer than temporarily adding these to code that might get committed to VCS - String apiKey = System.getProperty("bitfinex-api-key"); - String apiSecret = System.getProperty("bitfinex-api-secret"); - if (StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(apiSecret)) { - throw new IllegalArgumentException("Supply api details in VM args"); - } + // Far safer than temporarily adding these to code that might get committed to VCS + String apiKey = System.getProperty("bitfinex-api-key"); + String apiSecret = System.getProperty("bitfinex-api-secret"); + if (StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(apiSecret)) { + throw new IllegalArgumentException("Supply api details in VM args"); + } - ExchangeSpecification spec = StreamingExchangeFactory.INSTANCE.createExchange( - BitfinexStreamingExchange.class.getName()).getDefaultExchangeSpecification(); - spec.setApiKey(apiKey); - spec.setSecretKey(apiSecret); - BitfinexStreamingExchange exchange = (BitfinexStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); + ExchangeSpecification spec = + StreamingExchangeFactory.INSTANCE + .createExchange(BitfinexStreamingExchange.class.getName()) + .getDefaultExchangeSpecification(); + spec.setApiKey(apiKey); + spec.setSecretKey(apiSecret); + BitfinexStreamingExchange exchange = + (BitfinexStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); - exchange.connect().blockingAwait(); - try { + exchange.connect().blockingAwait(); + try { - // L1 (generic) APIs - exchange.getStreamingTradeService().getUserTrades().subscribe( - t -> LOG.info("GENERIC USER TRADE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); - exchange.getStreamingTradeService().getOrderChanges().subscribe( - t -> LOG.info("GENERIC ORDER: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); - exchange.getStreamingAccountService().getBalanceChanges(Currency.BTC, "exchange").subscribe( - t -> LOG.info("GENERIC EXCHANGE BALANCE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); - exchange.getStreamingAccountService().getBalanceChanges(Currency.BTC, "margin").subscribe( - t -> LOG.info("GENERIC MARGIN BALANCE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); - exchange.getStreamingAccountService().getBalanceChanges(Currency.BTC, "funding").subscribe( - t -> LOG.info("GENERIC FUNDING BALANCE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); + // L1 (generic) APIs + exchange + .getStreamingTradeService() + .getUserTrades() + .subscribe( + t -> LOG.info("GENERIC USER TRADE: {}", t), + throwable -> LOG.error("ERROR: ", throwable)); + exchange + .getStreamingTradeService() + .getOrderChanges() + .subscribe( + t -> LOG.info("GENERIC ORDER: {}", t), throwable -> LOG.error("ERROR: ", throwable)); + exchange + .getStreamingAccountService() + .getBalanceChanges(Currency.BTC, "exchange") + .subscribe( + t -> LOG.info("GENERIC EXCHANGE BALANCE: {}", t), + throwable -> LOG.error("ERROR: ", throwable)); + exchange + .getStreamingAccountService() + .getBalanceChanges(Currency.BTC, "margin") + .subscribe( + t -> LOG.info("GENERIC MARGIN BALANCE: {}", t), + throwable -> LOG.error("ERROR: ", throwable)); + exchange + .getStreamingAccountService() + .getBalanceChanges(Currency.BTC, "funding") + .subscribe( + t -> LOG.info("GENERIC FUNDING BALANCE: {}", t), + throwable -> LOG.error("ERROR: ", throwable)); - // L2 (exchange specific) APIs - exchange.getStreamingTradeService().getRawAuthenticatedTrades().subscribe( - t -> LOG.info("AUTH TRADE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); - exchange.getStreamingTradeService().getRawAuthenticatedPreTrades().subscribe( - t -> LOG.info("AUTH PRE TRADE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); - exchange.getStreamingTradeService().getRawAuthenticatedOrders().subscribe( - t -> LOG.info("AUTH ORDER: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); - exchange.getStreamingAccountService().getRawAuthenticatedBalances().subscribe( - t -> LOG.info("AUTH BALANCE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); + // L2 (exchange specific) APIs + exchange + .getStreamingTradeService() + .getRawAuthenticatedTrades() + .subscribe( + t -> LOG.info("AUTH TRADE: {}", t), throwable -> LOG.error("ERROR: ", throwable)); + exchange + .getStreamingTradeService() + .getRawAuthenticatedPreTrades() + .subscribe( + t -> LOG.info("AUTH PRE TRADE: {}", t), throwable -> LOG.error("ERROR: ", throwable)); + exchange + .getStreamingTradeService() + .getRawAuthenticatedOrders() + .subscribe( + t -> LOG.info("AUTH ORDER: {}", t), throwable -> LOG.error("ERROR: ", throwable)); + exchange + .getStreamingAccountService() + .getRawAuthenticatedBalances() + .subscribe( + t -> LOG.info("AUTH BALANCE: {}", t), throwable -> LOG.error("ERROR: ", throwable)); - // Make sure we can still get unauthenticated data on the same socket - exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe( - t -> LOG.info("PUBLIC TRADE: {}", t), - throwable -> LOG.error("ERROR: ", throwable) - ); + // Make sure we can still get unauthenticated data on the same socket + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + t -> LOG.info("PUBLIC TRADE: {}", t), throwable -> LOG.error("ERROR: ", throwable)); - try { - Thread.sleep(100000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } finally { - exchange.disconnect().blockingAwait(); - } + try { + Thread.sleep(100000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } finally { + exchange.disconnect().blockingAwait(); } + } } diff --git a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualExample.java b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualExample.java index 15ef58398..b767fc40b 100644 --- a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualExample.java +++ b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexManualExample.java @@ -1,5 +1,7 @@ package info.bitrich.xchangestream.bitfinex; +import static java.util.concurrent.TimeUnit.MINUTES; + import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingExchangeFactory; import info.bitrich.xchangestream.service.ConnectableService; @@ -13,74 +15,88 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.util.concurrent.TimeUnit.MINUTES; - -/** - * Created by Lukas Zaoralek on 7.11.17. - */ +/** Created by Lukas Zaoralek on 7.11.17. */ public class BitfinexManualExample { - private static final Logger LOG = LoggerFactory.getLogger(BitfinexManualExample.class); - - private static final TimedSemaphore rateLimiter = new TimedSemaphore(1, MINUTES, 15); - private static void rateLimit() { - try { - rateLimiter.acquire(); - } catch (InterruptedException e) { - LOG.warn("Bitfinex connection throttle control has been interrupted"); - } - } + private static final Logger LOG = LoggerFactory.getLogger(BitfinexManualExample.class); - public static void main(String[] args) throws Exception { - CertHelper.trustAllCerts(); - - ExchangeSpecification defaultExchangeSpecification = new ExchangeSpecification(BitfinexStreamingExchange.class); - defaultExchangeSpecification.setExchangeSpecificParametersItem(ConnectableService.BEFORE_CONNECTION_HANDLER, (Runnable) BitfinexManualExample::rateLimit); - - defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); -/* - defaultExchangeSpecification.setProxyHost("localhost"); - defaultExchangeSpecification.setProxyPort(9999); - - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); - - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.USE_SANDBOX, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ACCEPT_ALL_CERITICATES, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); -*/ - - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(defaultExchangeSpecification); - exchange.connect().blockingAwait(); - - Observable orderBookObserver = exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD); - Disposable orderBookSubscriber = orderBookObserver.subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> { - LOG.error("ERROR in getting order book: ", throwable); - }); - - Disposable tickerSubscriber = exchange.getStreamingMarketDataService().getTicker(CurrencyPair.BTC_EUR).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - Disposable tradesSubscriber = exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD) - .subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> { - LOG.error("ERROR in getting trade: ", throwable); - }); + private static final TimedSemaphore rateLimiter = new TimedSemaphore(1, MINUTES, 15); - Thread.sleep(10000); + private static void rateLimit() { + try { + rateLimiter.acquire(); + } catch (InterruptedException e) { + LOG.warn("Bitfinex connection throttle control has been interrupted"); + } + } + + public static void main(String[] args) throws Exception { + CertHelper.trustAllCerts(); + + ExchangeSpecification defaultExchangeSpecification = + new ExchangeSpecification(BitfinexStreamingExchange.class); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + ConnectableService.BEFORE_CONNECTION_HANDLER, (Runnable) BitfinexManualExample::rateLimit); + + defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); + /* + defaultExchangeSpecification.setProxyHost("localhost"); + defaultExchangeSpecification.setProxyPort(9999); + + defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); + defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); + + defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.USE_SANDBOX, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ACCEPT_ALL_CERITICATES, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); + */ + + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(defaultExchangeSpecification); + exchange.connect().blockingAwait(); + + Observable orderBookObserver = + exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD); + Disposable orderBookSubscriber = + orderBookObserver.subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> { + LOG.error("ERROR in getting order book: ", throwable); + }); + + Disposable tickerSubscriber = + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.BTC_EUR) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + Disposable tradesSubscriber = + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> { + LOG.error("ERROR in getting trade: ", throwable); + }); - tickerSubscriber.dispose(); - tradesSubscriber.dispose(); - orderBookSubscriber.dispose(); + Thread.sleep(10000); - LOG.info("disconnecting..."); - exchange.disconnect().subscribe(() -> LOG.info("disconnected")); + tickerSubscriber.dispose(); + tradesSubscriber.dispose(); + orderBookSubscriber.dispose(); - rateLimiter.shutdown(); - } + LOG.info("disconnecting..."); + exchange.disconnect().subscribe(() -> LOG.info("disconnected")); + rateLimiter.shutdown(); + } } diff --git a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdaptersTest.java b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdaptersTest.java index c25c93a71..26b7d21e0 100644 --- a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdaptersTest.java +++ b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingAdaptersTest.java @@ -1,8 +1,12 @@ package info.bitrich.xchangestream.bitfinex; +import static org.junit.Assert.assertEquals; + import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthOrder; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthTrade; - +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Date; import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; @@ -11,306 +15,309 @@ import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.dto.trade.UserTrade; -import java.math.BigDecimal; -import java.util.Collections; -import java.util.Date; - -import static org.junit.Assert.assertEquals; - public class BitfinexStreamingAdaptersTest { - @Test - public void testMarketOrder() { - BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = new BitfinexWebSocketAuthOrder( - 123123123L, //id, - 0L, //groupId, - 456456456L, //cid, - "tBTCUSD", //symbol, - 1548674205259L, //mtsCreateamount - 1548674205259L, //mtsUpdate - new BigDecimal("0.000"), //amount - new BigDecimal("0.004"), //amountOrig + @Test + public void testMarketOrder() { + BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = + new BitfinexWebSocketAuthOrder( + 123123123L, // id, + 0L, // groupId, + 456456456L, // cid, + "tBTCUSD", // symbol, + 1548674205259L, // mtsCreateamount + 1548674205259L, // mtsUpdate + new BigDecimal("0.000"), // amount + new BigDecimal("0.004"), // amountOrig "MARKET", // type - null, //typePrev - "EXECUTED @ 3495.1(0.004)", //orderStatus - new BigDecimal("3495.2"), //price - new BigDecimal("3495.2"), //priceAvg - BigDecimal.ZERO, //priceTrailing - BigDecimal.ZERO, //priceAuxLimit - 0, //placedId - 0 //flags - ); - - // TODO awaiting https://github.com/knowm/XChange/pull/2907 then I can add market order - // support to XChange itself. In the meantime these are returned as limit orders. - - Order adaptedOrder = BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); - - assertEquals("123123123", adaptedOrder.getId()); - assertEquals(Order.OrderType.BID, adaptedOrder.getType()); - assertEquals(new BigDecimal("3495.2"), adaptedOrder.getAveragePrice()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getCumulativeAmount()); - assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); - - // TODO see above. should be: - //assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); - - assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); - assertEquals(new BigDecimal("0.000"), adaptedOrder.getRemainingAmount()); - assertEquals(OrderStatus.FILLED, adaptedOrder.getStatus()); - assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); - } - - @Test - public void testStopOrder() { - BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = new BitfinexWebSocketAuthOrder( - 123123123L, //id, - 0L, //groupId, - 456456456L, //cid, - "tBTCUSD", //symbol, - 1548674205259L, //mtsCreateamount - 1548674205259L, //mtsUpdate - new BigDecimal("0.000"), //amount - new BigDecimal("0.004"), //amountOrig + null, // typePrev + "EXECUTED @ 3495.1(0.004)", // orderStatus + new BigDecimal("3495.2"), // price + new BigDecimal("3495.2"), // priceAvg + BigDecimal.ZERO, // priceTrailing + BigDecimal.ZERO, // priceAuxLimit + 0, // placedId + 0 // flags + ); + + // TODO awaiting https://github.com/knowm/XChange/pull/2907 then I can add market order + // support to XChange itself. In the meantime these are returned as limit orders. + + Order adaptedOrder = BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); + + assertEquals("123123123", adaptedOrder.getId()); + assertEquals(Order.OrderType.BID, adaptedOrder.getType()); + assertEquals(new BigDecimal("3495.2"), adaptedOrder.getAveragePrice()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getCumulativeAmount()); + assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); + + // TODO see above. should be: + // assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); + + assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); + assertEquals(new BigDecimal("0.000"), adaptedOrder.getRemainingAmount()); + assertEquals(OrderStatus.FILLED, adaptedOrder.getStatus()); + assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); + } + + @Test + public void testStopOrder() { + BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = + new BitfinexWebSocketAuthOrder( + 123123123L, // id, + 0L, // groupId, + 456456456L, // cid, + "tBTCUSD", // symbol, + 1548674205259L, // mtsCreateamount + 1548674205259L, // mtsUpdate + new BigDecimal("0.000"), // amount + new BigDecimal("0.004"), // amountOrig "STOP", // type - null, //typePrev - "EXECUTED @ 3495.1(0.004)", //orderStatus - new BigDecimal("3495.2"), //price - new BigDecimal("3495.2"), //priceAvg - BigDecimal.ZERO, //priceTrailing - BigDecimal.ZERO, //priceAuxLimit - 0, //placedId - 0 //flags - ); - - // TODO awaiting https://github.com/knowm/XChange/pull/2907 then I can add market order - // support to XChange itself. In the meantime these are returned as limit orders. - - Order adaptedOrder = BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); - - assertEquals("123123123", adaptedOrder.getId()); - assertEquals(Order.OrderType.BID, adaptedOrder.getType()); - assertEquals(new BigDecimal("3495.2"), adaptedOrder.getAveragePrice()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getCumulativeAmount()); - assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); - - // TODO see above. should be: - //assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); - - assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); - assertEquals(new BigDecimal("0.000"), adaptedOrder.getRemainingAmount()); - assertEquals(OrderStatus.FILLED, adaptedOrder.getStatus()); - assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); - } - - @Test - public void testNewLimitOrder() { - BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = new BitfinexWebSocketAuthOrder( - 123123123L, //id, - 0L, //groupId, - 456456456L, //cid, - "tBTCUSD", //symbol, - 1548674205259L, //mtsCreateamount - 1548674205267L, //mtsUpdate - new BigDecimal("0.004"), //amount - new BigDecimal("0.004"), //amountOrig + null, // typePrev + "EXECUTED @ 3495.1(0.004)", // orderStatus + new BigDecimal("3495.2"), // price + new BigDecimal("3495.2"), // priceAvg + BigDecimal.ZERO, // priceTrailing + BigDecimal.ZERO, // priceAuxLimit + 0, // placedId + 0 // flags + ); + + // TODO awaiting https://github.com/knowm/XChange/pull/2907 then I can add market order + // support to XChange itself. In the meantime these are returned as limit orders. + + Order adaptedOrder = BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); + + assertEquals("123123123", adaptedOrder.getId()); + assertEquals(Order.OrderType.BID, adaptedOrder.getType()); + assertEquals(new BigDecimal("3495.2"), adaptedOrder.getAveragePrice()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getCumulativeAmount()); + assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); + + // TODO see above. should be: + // assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); + + assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); + assertEquals(new BigDecimal("0.000"), adaptedOrder.getRemainingAmount()); + assertEquals(OrderStatus.FILLED, adaptedOrder.getStatus()); + assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); + } + + @Test + public void testNewLimitOrder() { + BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = + new BitfinexWebSocketAuthOrder( + 123123123L, // id, + 0L, // groupId, + 456456456L, // cid, + "tBTCUSD", // symbol, + 1548674205259L, // mtsCreateamount + 1548674205267L, // mtsUpdate + new BigDecimal("0.004"), // amount + new BigDecimal("0.004"), // amountOrig "EXCHANGE LIMIT", // type - null, //typePrev - "ACTIVE", //orderStatus - new BigDecimal("3495.2"), //price - BigDecimal.ZERO, //priceAvg - BigDecimal.ZERO, //priceTrailing - BigDecimal.ZERO, //priceAuxLimit - 0, //placedId - 0 //flags - ); - - LimitOrder adaptedOrder = (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); - - assertEquals("123123123", adaptedOrder.getId()); - assertEquals(Order.OrderType.BID, adaptedOrder.getType()); - assertEquals(BigDecimal.ZERO, adaptedOrder.getAveragePrice()); - assertEquals(0, BigDecimal.ZERO.compareTo(adaptedOrder.getCumulativeAmount())); - assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); - assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); - assertEquals(Collections.emptySet(), adaptedOrder.getOrderFlags()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getRemainingAmount()); - assertEquals(OrderStatus.NEW, adaptedOrder.getStatus()); - assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); - } - - - @Test - public void testCancelledLimitOrder() { - BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = new BitfinexWebSocketAuthOrder( - 123123123L, //id, - 0L, //groupId, - 456456456L, //cid, - "tBTCUSD", //symbol, - 1548674205259L, //mtsCreateamount - 1548674205267L, //mtsUpdate - new BigDecimal("-0.004"), //amount - new BigDecimal("-0.004"), //amountOrig + null, // typePrev + "ACTIVE", // orderStatus + new BigDecimal("3495.2"), // price + BigDecimal.ZERO, // priceAvg + BigDecimal.ZERO, // priceTrailing + BigDecimal.ZERO, // priceAuxLimit + 0, // placedId + 0 // flags + ); + + LimitOrder adaptedOrder = + (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); + + assertEquals("123123123", adaptedOrder.getId()); + assertEquals(Order.OrderType.BID, adaptedOrder.getType()); + assertEquals(BigDecimal.ZERO, adaptedOrder.getAveragePrice()); + assertEquals(0, BigDecimal.ZERO.compareTo(adaptedOrder.getCumulativeAmount())); + assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); + assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); + assertEquals(Collections.emptySet(), adaptedOrder.getOrderFlags()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getRemainingAmount()); + assertEquals(OrderStatus.NEW, adaptedOrder.getStatus()); + assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); + } + + @Test + public void testCancelledLimitOrder() { + BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = + new BitfinexWebSocketAuthOrder( + 123123123L, // id, + 0L, // groupId, + 456456456L, // cid, + "tBTCUSD", // symbol, + 1548674205259L, // mtsCreateamount + 1548674205267L, // mtsUpdate + new BigDecimal("-0.004"), // amount + new BigDecimal("-0.004"), // amountOrig "LIMIT", // type - null, //typePrev - "CANCELED", //orderStatus - new BigDecimal("3495.2"), //price - BigDecimal.ZERO, //priceAvg - BigDecimal.ZERO, //priceTrailing - BigDecimal.ZERO, //priceAuxLimit - 0, //placedId - 0 //flags - ); - - LimitOrder adaptedOrder = (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); - - assertEquals("123123123", adaptedOrder.getId()); - assertEquals(Order.OrderType.ASK, adaptedOrder.getType()); - assertEquals(BigDecimal.ZERO, adaptedOrder.getAveragePrice()); - assertEquals(0, BigDecimal.ZERO.compareTo(adaptedOrder.getCumulativeAmount())); - assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); - assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getRemainingAmount()); - - // TODO see above. should be: - //assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); - - assertEquals(OrderStatus.CANCELED, adaptedOrder.getStatus()); - assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); - } - - @Test - public void testPartiallyFilledLimitOrder() { - BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = new BitfinexWebSocketAuthOrder( - 123123123L, //id, - 0L, //groupId, - 456456456L, //cid, - "tBTCUSD", //symbol, - 1548674205259L, //mtsCreateamount - 1548674205267L, //mtsUpdate - new BigDecimal("-0.001"), //amount - new BigDecimal("-0.004"), //amountOrig + null, // typePrev + "CANCELED", // orderStatus + new BigDecimal("3495.2"), // price + BigDecimal.ZERO, // priceAvg + BigDecimal.ZERO, // priceTrailing + BigDecimal.ZERO, // priceAuxLimit + 0, // placedId + 0 // flags + ); + + LimitOrder adaptedOrder = + (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); + + assertEquals("123123123", adaptedOrder.getId()); + assertEquals(Order.OrderType.ASK, adaptedOrder.getType()); + assertEquals(BigDecimal.ZERO, adaptedOrder.getAveragePrice()); + assertEquals(0, BigDecimal.ZERO.compareTo(adaptedOrder.getCumulativeAmount())); + assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); + assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getRemainingAmount()); + + // TODO see above. should be: + // assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); + + assertEquals(OrderStatus.CANCELED, adaptedOrder.getStatus()); + assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); + } + + @Test + public void testPartiallyFilledLimitOrder() { + BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = + new BitfinexWebSocketAuthOrder( + 123123123L, // id, + 0L, // groupId, + 456456456L, // cid, + "tBTCUSD", // symbol, + 1548674205259L, // mtsCreateamount + 1548674205267L, // mtsUpdate + new BigDecimal("-0.001"), // amount + new BigDecimal("-0.004"), // amountOrig "LIMIT", // type - null, //typePrev - "PARTIALLY FILLED @ 3495.1(0.003)", //orderStatus - new BigDecimal("3495.2"), //price - new BigDecimal("3495.1"), //priceAvg - BigDecimal.ZERO, //priceTrailing - BigDecimal.ZERO, //priceAuxLimit - 0, //placedId - 0 //flags - ); - - LimitOrder adaptedOrder = (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); - - assertEquals("123123123", adaptedOrder.getId()); - assertEquals(Order.OrderType.ASK, adaptedOrder.getType()); - assertEquals(new BigDecimal("3495.1"), adaptedOrder.getAveragePrice()); - assertEquals(new BigDecimal("0.003"), adaptedOrder.getCumulativeAmount()); - assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); - assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); - assertEquals(new BigDecimal("0.001"), adaptedOrder.getRemainingAmount()); - - // TODO see above. should be: - //assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); - - assertEquals(OrderStatus.PARTIALLY_FILLED, adaptedOrder.getStatus()); - assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); - } - - - @Test - public void testExecutedLimitOrder() { - BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = new BitfinexWebSocketAuthOrder( - 123123123L, //id, - 0L, //groupId, - 456456456L, //cid, - "tBTCUSD", //symbol, - 1548674205259L, //mtsCreateamount - 1548674205267L, //mtsUpdate - BigDecimal.ZERO, //amount - new BigDecimal("0.004"), //amountOrig + null, // typePrev + "PARTIALLY FILLED @ 3495.1(0.003)", // orderStatus + new BigDecimal("3495.2"), // price + new BigDecimal("3495.1"), // priceAvg + BigDecimal.ZERO, // priceTrailing + BigDecimal.ZERO, // priceAuxLimit + 0, // placedId + 0 // flags + ); + + LimitOrder adaptedOrder = + (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); + + assertEquals("123123123", adaptedOrder.getId()); + assertEquals(Order.OrderType.ASK, adaptedOrder.getType()); + assertEquals(new BigDecimal("3495.1"), adaptedOrder.getAveragePrice()); + assertEquals(new BigDecimal("0.003"), adaptedOrder.getCumulativeAmount()); + assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); + assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); + assertEquals(new BigDecimal("0.001"), adaptedOrder.getRemainingAmount()); + + // TODO see above. should be: + // assertEquals(Collections.singleton(BitfinexOrderFlags.MARGIN), adaptedOrder.getOrderFlags()); + + assertEquals(OrderStatus.PARTIALLY_FILLED, adaptedOrder.getStatus()); + assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); + } + + @Test + public void testExecutedLimitOrder() { + BitfinexWebSocketAuthOrder bitfinexWebSocketAuthOrder = + new BitfinexWebSocketAuthOrder( + 123123123L, // id, + 0L, // groupId, + 456456456L, // cid, + "tBTCUSD", // symbol, + 1548674205259L, // mtsCreateamount + 1548674205267L, // mtsUpdate + BigDecimal.ZERO, // amount + new BigDecimal("0.004"), // amountOrig "EXCHANGE LIMIT", // type - null, //typePrev - "EXECUTED @ 3495.1(0.004): was PARTIALLY FILLED @ 3495.1(0.003)", //orderStatus - new BigDecimal("3495.2"), //price - new BigDecimal("3495.1"), //priceAvg - BigDecimal.ZERO, //priceTrailing - BigDecimal.ZERO, //priceAuxLimit - 0, //placedId - 0 //flags - ); - - LimitOrder adaptedOrder = (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); - - assertEquals("123123123", adaptedOrder.getId()); - assertEquals(Order.OrderType.BID, adaptedOrder.getType()); - assertEquals(new BigDecimal("3495.1"), adaptedOrder.getAveragePrice()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getCumulativeAmount()); - assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); - assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); - assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); - assertEquals(new BigDecimal("0.000"), adaptedOrder.getRemainingAmount()); - assertEquals(Collections.emptySet(), adaptedOrder.getOrderFlags()); - assertEquals(OrderStatus.FILLED, adaptedOrder.getStatus()); - assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); - } - - @Test - public void testTradeBuy() { - BitfinexWebSocketAuthTrade bitfinexWebSocketAuthTrade = new BitfinexWebSocketAuthTrade( - 335015622L, //id - "tBTCUSD", //pair - 1548674247684L, //mtsCreate - 21895093123L, //orderId - new BigDecimal("0.00341448"), //execAmount - new BigDecimal("3495.4"), //execPrice - "SHOULDNT MATTER", //orderType - new BigDecimal("3495.9"), //orderPrice - 1548674247683L, //maker - new BigDecimal("-0.00000682896"), //fee - "BTC" //feeCurrency - ); - UserTrade adapted = BitfinexStreamingAdapters.adaptUserTrade(bitfinexWebSocketAuthTrade); - assertEquals(CurrencyPair.BTC_USD, adapted.getCurrencyPair()); - assertEquals(new BigDecimal("0.00000682896"), adapted.getFeeAmount()); - assertEquals(CurrencyPair.BTC_USD.base, adapted.getFeeCurrency()); - assertEquals("335015622", adapted.getId()); - assertEquals("21895093123", adapted.getOrderId()); - assertEquals(new BigDecimal("0.00341448"), adapted.getOriginalAmount()); - assertEquals(new BigDecimal("3495.4"), adapted.getPrice()); - assertEquals(new Date(1548674247684L).getTime(), adapted.getTimestamp().getTime()); - assertEquals(OrderType.BID, adapted.getType()); - } - - - @Test - public void testTradeSell() { - BitfinexWebSocketAuthTrade bitfinexWebSocketAuthTrade = new BitfinexWebSocketAuthTrade( - 335015622L, //id - "tBTCUSD", //pair - 1548674247684L, //mtsCreate - 21895093123L, //orderId - new BigDecimal("-0.00341448"), //execAmount - new BigDecimal("3495.4"), //execPrice - "SHOULDNT MATTER", //orderType - new BigDecimal("3495.9"), //orderPrice - 1548674247683L, //maker - new BigDecimal("0.00000682896"), //fee - "BTC" //feeCurrency - ); - UserTrade adapted = BitfinexStreamingAdapters.adaptUserTrade(bitfinexWebSocketAuthTrade); - assertEquals(CurrencyPair.BTC_USD, adapted.getCurrencyPair()); - assertEquals(new BigDecimal("0.00000682896"), adapted.getFeeAmount()); - assertEquals(CurrencyPair.BTC_USD.base, adapted.getFeeCurrency()); - assertEquals("335015622", adapted.getId()); - assertEquals("21895093123", adapted.getOrderId()); - assertEquals(new BigDecimal("0.00341448"), adapted.getOriginalAmount()); - assertEquals(new BigDecimal("3495.4"), adapted.getPrice()); - assertEquals(new Date(1548674247684L).getTime(), adapted.getTimestamp().getTime()); - assertEquals(OrderType.ASK, adapted.getType()); - } -} \ No newline at end of file + null, // typePrev + "EXECUTED @ 3495.1(0.004): was PARTIALLY FILLED @ 3495.1(0.003)", // orderStatus + new BigDecimal("3495.2"), // price + new BigDecimal("3495.1"), // priceAvg + BigDecimal.ZERO, // priceTrailing + BigDecimal.ZERO, // priceAuxLimit + 0, // placedId + 0 // flags + ); + + LimitOrder adaptedOrder = + (LimitOrder) BitfinexStreamingAdapters.adaptOrder(bitfinexWebSocketAuthOrder); + + assertEquals("123123123", adaptedOrder.getId()); + assertEquals(Order.OrderType.BID, adaptedOrder.getType()); + assertEquals(new BigDecimal("3495.1"), adaptedOrder.getAveragePrice()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getCumulativeAmount()); + assertEquals(CurrencyPair.BTC_USD, adaptedOrder.getCurrencyPair()); + assertEquals(new BigDecimal("3495.2"), adaptedOrder.getLimitPrice()); + assertEquals(new BigDecimal("0.004"), adaptedOrder.getOriginalAmount()); + assertEquals(new BigDecimal("0.000"), adaptedOrder.getRemainingAmount()); + assertEquals(Collections.emptySet(), adaptedOrder.getOrderFlags()); + assertEquals(OrderStatus.FILLED, adaptedOrder.getStatus()); + assertEquals(new Date(1548674205259L).getTime(), adaptedOrder.getTimestamp().getTime()); + } + + @Test + public void testTradeBuy() { + BitfinexWebSocketAuthTrade bitfinexWebSocketAuthTrade = + new BitfinexWebSocketAuthTrade( + 335015622L, // id + "tBTCUSD", // pair + 1548674247684L, // mtsCreate + 21895093123L, // orderId + new BigDecimal("0.00341448"), // execAmount + new BigDecimal("3495.4"), // execPrice + "SHOULDNT MATTER", // orderType + new BigDecimal("3495.9"), // orderPrice + 1548674247683L, // maker + new BigDecimal("-0.00000682896"), // fee + "BTC" // feeCurrency + ); + UserTrade adapted = BitfinexStreamingAdapters.adaptUserTrade(bitfinexWebSocketAuthTrade); + assertEquals(CurrencyPair.BTC_USD, adapted.getCurrencyPair()); + assertEquals(new BigDecimal("0.00000682896"), adapted.getFeeAmount()); + assertEquals(CurrencyPair.BTC_USD.base, adapted.getFeeCurrency()); + assertEquals("335015622", adapted.getId()); + assertEquals("21895093123", adapted.getOrderId()); + assertEquals(new BigDecimal("0.00341448"), adapted.getOriginalAmount()); + assertEquals(new BigDecimal("3495.4"), adapted.getPrice()); + assertEquals(new Date(1548674247684L).getTime(), adapted.getTimestamp().getTime()); + assertEquals(OrderType.BID, adapted.getType()); + } + + @Test + public void testTradeSell() { + BitfinexWebSocketAuthTrade bitfinexWebSocketAuthTrade = + new BitfinexWebSocketAuthTrade( + 335015622L, // id + "tBTCUSD", // pair + 1548674247684L, // mtsCreate + 21895093123L, // orderId + new BigDecimal("-0.00341448"), // execAmount + new BigDecimal("3495.4"), // execPrice + "SHOULDNT MATTER", // orderType + new BigDecimal("3495.9"), // orderPrice + 1548674247683L, // maker + new BigDecimal("0.00000682896"), // fee + "BTC" // feeCurrency + ); + UserTrade adapted = BitfinexStreamingAdapters.adaptUserTrade(bitfinexWebSocketAuthTrade); + assertEquals(CurrencyPair.BTC_USD, adapted.getCurrencyPair()); + assertEquals(new BigDecimal("0.00000682896"), adapted.getFeeAmount()); + assertEquals(CurrencyPair.BTC_USD.base, adapted.getFeeCurrency()); + assertEquals("335015622", adapted.getId()); + assertEquals("21895093123", adapted.getOrderId()); + assertEquals(new BigDecimal("0.00341448"), adapted.getOriginalAmount()); + assertEquals(new BigDecimal("3495.4"), adapted.getPrice()); + assertEquals(new Date(1548674247684L).getTime(), adapted.getTimestamp().getTime()); + assertEquals(OrderType.ASK, adapted.getType()); + } +} diff --git a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingServiceTest.java b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingServiceTest.java index 5c399fb12..9be00430d 100644 --- a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingServiceTest.java +++ b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/BitfinexStreamingServiceTest.java @@ -1,163 +1,153 @@ package info.bitrich.xchangestream.bitfinex; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthBalance; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthOrder; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthPreTrade; import info.bitrich.xchangestream.bitfinex.dto.BitfinexWebSocketAuthTrade; - import io.reactivex.observers.TestObserver; - +import java.io.IOException; +import java.math.BigDecimal; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; - -import java.io.IOException; -import java.math.BigDecimal; - -import static org.assertj.core.api.Assertions.assertThat; - import si.mazi.rescu.SynchronizedValueFactory; public class BitfinexStreamingServiceTest { - private BitfinexStreamingService service; - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Mock SynchronizedValueFactory nonceFactory; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - service = new BitfinexStreamingService(BitfinexStreamingExchange.API_URI, nonceFactory); - } - - @Test - public void testGetOrders() throws Exception { - - JsonNode jsonNode = objectMapper.readTree(ClassLoader.getSystemClassLoader().getResourceAsStream("orders.json")); - - TestObserver test = service.getAuthenticatedOrders().test(); - - service.handleMessage(jsonNode); - BitfinexWebSocketAuthOrder expected = - new BitfinexWebSocketAuthOrder( - 13759731408L, - 0, - 50999677532L, - "tETHUSD", - 1530108599707L, - 1530108599726L, - new BigDecimal("-0.02"), - new BigDecimal("-0.02"), - "EXCHANGE LIMIT", - null, - "ACTIVE", - new BigDecimal("431.19"), - BigDecimal.ZERO, - BigDecimal.ZERO, - BigDecimal.ZERO, - 0L, - 0); - test.assertValue(expected); - } - - @Test - public void testGetPreTrades() throws Exception { - - JsonNode jsonNode = objectMapper.readTree(ClassLoader.getSystemClassLoader().getResourceAsStream("preTrade.json")); - - TestObserver test = service.getAuthenticatedPreTrades().test(); - - service.handleMessage(jsonNode); - - BitfinexWebSocketAuthPreTrade expected = - new BitfinexWebSocketAuthPreTrade( - 262861164L, - "tETHUSD", - 1530187145559L, - 13787457748L, - new BigDecimal("-0.04"), - new BigDecimal("435.8"), - "EXCHANGE LIMIT", - new BigDecimal("435.8"), - 1); - test.assertValue(expected); - } - - @Test - public void testGetTrades() throws Exception { - - JsonNode jsonNode = objectMapper.readTree(ClassLoader.getSystemClassLoader().getResourceAsStream("trade.json")); - - TestObserver test = service.getAuthenticatedTrades().test(); - - service.handleMessage(jsonNode); - - BitfinexWebSocketAuthTrade expected = - new BitfinexWebSocketAuthTrade( - 262861164L, - "tETHUSD", - 1530187145559L, - 13787457748L, - new BigDecimal("-0.04"), - new BigDecimal("435.8"), - "EXCHANGE LIMIT", - new BigDecimal("435.8"), - 1, - new BigDecimal("-0.0104592"), - "USD"); - test.assertValue(expected); - } - - @Test - public void testGetBalances() throws Exception { - JsonNode jsonNode = objectMapper.readTree(ClassLoader.getSystemClassLoader().getResourceAsStream("balances.json")); - - TestObserver test = service.getAuthenticatedBalances().test(); - - service.handleMessage(jsonNode); - - BitfinexWebSocketAuthBalance expected = - new BitfinexWebSocketAuthBalance( - "exchange", - "ETH", - new BigDecimal("0.38772"), - BigDecimal.ZERO, - null - ); - - BitfinexWebSocketAuthBalance expected1 = - new BitfinexWebSocketAuthBalance( - "exchange", - "USD", - new BigDecimal("69.4747619"), - BigDecimal.ZERO, - null - ); - test.assertNoErrors(); - test.assertValueCount(2); - assertThat(test.values().contains(expected)); - assertThat(test.values().contains(expected1)); - } - - @Test - public void testGetBalance() throws IOException { - JsonNode jsonNode = objectMapper.readTree(ClassLoader.getSystemClassLoader().getResourceAsStream("balance.json")); - TestObserver test = service.getAuthenticatedBalances().test(); - service.handleMessage(jsonNode); - - BitfinexWebSocketAuthBalance balance = new BitfinexWebSocketAuthBalance( - "exchange", - "USD", - new BigDecimal("78.5441867"), - BigDecimal.ZERO, - null - ); - - test.assertValue(balance); - } + private BitfinexStreamingService service; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Mock SynchronizedValueFactory nonceFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + service = new BitfinexStreamingService(BitfinexStreamingExchange.API_URI, nonceFactory); + } + + @Test + public void testGetOrders() throws Exception { + + JsonNode jsonNode = + objectMapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("orders.json")); + + TestObserver test = service.getAuthenticatedOrders().test(); + + service.handleMessage(jsonNode); + BitfinexWebSocketAuthOrder expected = + new BitfinexWebSocketAuthOrder( + 13759731408L, + 0, + 50999677532L, + "tETHUSD", + 1530108599707L, + 1530108599726L, + new BigDecimal("-0.02"), + new BigDecimal("-0.02"), + "EXCHANGE LIMIT", + null, + "ACTIVE", + new BigDecimal("431.19"), + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO, + 0L, + 0); + test.assertValue(expected); + } + + @Test + public void testGetPreTrades() throws Exception { + + JsonNode jsonNode = + objectMapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("preTrade.json")); + + TestObserver test = service.getAuthenticatedPreTrades().test(); + + service.handleMessage(jsonNode); + + BitfinexWebSocketAuthPreTrade expected = + new BitfinexWebSocketAuthPreTrade( + 262861164L, + "tETHUSD", + 1530187145559L, + 13787457748L, + new BigDecimal("-0.04"), + new BigDecimal("435.8"), + "EXCHANGE LIMIT", + new BigDecimal("435.8"), + 1); + test.assertValue(expected); + } + + @Test + public void testGetTrades() throws Exception { + + JsonNode jsonNode = + objectMapper.readTree(ClassLoader.getSystemClassLoader().getResourceAsStream("trade.json")); + + TestObserver test = service.getAuthenticatedTrades().test(); + + service.handleMessage(jsonNode); + + BitfinexWebSocketAuthTrade expected = + new BitfinexWebSocketAuthTrade( + 262861164L, + "tETHUSD", + 1530187145559L, + 13787457748L, + new BigDecimal("-0.04"), + new BigDecimal("435.8"), + "EXCHANGE LIMIT", + new BigDecimal("435.8"), + 1, + new BigDecimal("-0.0104592"), + "USD"); + test.assertValue(expected); + } + + @Test + public void testGetBalances() throws Exception { + JsonNode jsonNode = + objectMapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("balances.json")); + + TestObserver test = service.getAuthenticatedBalances().test(); + + service.handleMessage(jsonNode); + + BitfinexWebSocketAuthBalance expected = + new BitfinexWebSocketAuthBalance( + "exchange", "ETH", new BigDecimal("0.38772"), BigDecimal.ZERO, null); + + BitfinexWebSocketAuthBalance expected1 = + new BitfinexWebSocketAuthBalance( + "exchange", "USD", new BigDecimal("69.4747619"), BigDecimal.ZERO, null); + test.assertNoErrors(); + test.assertValueCount(2); + assertThat(test.values().contains(expected)); + assertThat(test.values().contains(expected1)); + } + + @Test + public void testGetBalance() throws IOException { + JsonNode jsonNode = + objectMapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("balance.json")); + TestObserver test = service.getAuthenticatedBalances().test(); + service.handleMessage(jsonNode); + + BitfinexWebSocketAuthBalance balance = + new BitfinexWebSocketAuthBalance( + "exchange", "USD", new BigDecimal("78.5441867"), BigDecimal.ZERO, null); + + test.assertValue(balance); + } } diff --git a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookTest.java b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookTest.java index 59a64431e..027345be0 100644 --- a/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookTest.java +++ b/xchange-stream-bitfinex/src/test/java/info/bitrich/xchangestream/bitfinex/dto/BitfinexOrderbookTest.java @@ -1,29 +1,32 @@ package info.bitrich.xchangestream.bitfinex.dto; +import static java.math.BigDecimal.ONE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.knowm.xchange.currency.CurrencyPair.BTC_USD; + +import java.util.Date; import org.junit.Test; import org.knowm.xchange.bitfinex.service.BitfinexAdapters; import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexDepth; import org.knowm.xchange.dto.marketdata.OrderBook; -import java.util.Date; - -import static java.math.BigDecimal.ONE; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.knowm.xchange.currency.CurrencyPair.BTC_USD; - public class BitfinexOrderbookTest { - @Test - public void timestampShouldBeInSeconds() { - BitfinexDepth depth = new BitfinexOrderbook(new BitfinexOrderbookLevel[]{ - new BitfinexOrderbookLevel(ONE, ONE, ONE), - new BitfinexOrderbookLevel(ONE, ONE, ONE) - }).toBitfinexDepth(); + @Test + public void timestampShouldBeInSeconds() { + BitfinexDepth depth = + new BitfinexOrderbook( + new BitfinexOrderbookLevel[] { + new BitfinexOrderbookLevel(ONE, ONE, ONE), + new BitfinexOrderbookLevel(ONE, ONE, ONE) + }) + .toBitfinexDepth(); - OrderBook orderBook = BitfinexAdapters.adaptOrderBook(depth, BTC_USD); + OrderBook orderBook = BitfinexAdapters.adaptOrderBook(depth, BTC_USD); - // What is the time now... after order books created? - assertThat("The timestamp should be a value less than now, but was: " + orderBook.getTimeStamp(), - !orderBook.getTimeStamp().after(new Date())); - } -} \ No newline at end of file + // What is the time now... after order books created? + assertThat( + "The timestamp should be a value less than now, but was: " + orderBook.getTimeStamp(), + !orderBook.getTimeStamp().after(new Date())); + } +} diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingExchange.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingExchange.java index 511ac51e6..38624429b 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingExchange.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingExchange.java @@ -7,61 +7,59 @@ import io.reactivex.Completable; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.bitflyer.BitflyerExchange; -import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import si.mazi.rescu.SynchronizedValueFactory; -/** - * Created by Lukas Zaoralek on 14.11.17. - */ +/** Created by Lukas Zaoralek on 14.11.17. */ public class BitflyerStreamingExchange extends BitflyerExchange implements StreamingExchange { - private static final String API_KEY = "sub-c-52a9ab50-291b-11e5-baaa-0619f8945a4f"; + private static final String API_KEY = "sub-c-52a9ab50-291b-11e5-baaa-0619f8945a4f"; - private final PubnubStreamingService streamingService; - private BitflyerStreamingMarketDataService streamingMarketDataService; + private final PubnubStreamingService streamingService; + private BitflyerStreamingMarketDataService streamingMarketDataService; - public BitflyerStreamingExchange() { - this.streamingService = new PubnubStreamingService(API_KEY); - } + public BitflyerStreamingExchange() { + this.streamingService = new PubnubStreamingService(API_KEY); + } - @Override - protected void initServices() { - streamingMarketDataService = new BitflyerStreamingMarketDataService(streamingService); - super.initServices(); - } + @Override + protected void initServices() { + streamingMarketDataService = new BitflyerStreamingMarketDataService(streamingService); + super.initServices(); + } - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } - @Override - public SynchronizedValueFactory getNonceFactory() { - return null; - } + @Override + public SynchronizedValueFactory getNonceFactory() { + return null; + } - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); - return spec; - } + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); + return spec; + } - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } - @Override - public boolean isAlive() { - return streamingService.isAlive(); - } + @Override + public boolean isAlive() { + return streamingService.isAlive(); + } - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } - diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingMarketDataService.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingMarketDataService.java index d9044c050..41bafc6b5 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingMarketDataService.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/BitflyerStreamingMarketDataService.java @@ -1,12 +1,13 @@ package info.bitrich.xchangestream.bitflyer; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.bitflyer.dto.*; import info.bitrich.xchangestream.core.StreamingMarketDataService; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import info.bitrich.xchangestream.service.pubnub.PubnubStreamingService; import io.reactivex.Observable; +import java.util.HashMap; +import java.util.Map; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.marketdata.OrderBook; @@ -15,73 +16,96 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; -import java.util.Map; - -/** - * Created by Lukas Zaoralek on 14.11.17. - */ +/** Created by Lukas Zaoralek on 14.11.17. */ public class BitflyerStreamingMarketDataService implements StreamingMarketDataService { - private static final Logger LOG = LoggerFactory.getLogger(BitflyerStreamingMarketDataService.class); + private static final Logger LOG = + LoggerFactory.getLogger(BitflyerStreamingMarketDataService.class); - private final PubnubStreamingService streamingService; + private final PubnubStreamingService streamingService; - private final Map orderbooks = new HashMap<>(); - private final ObjectMapper mapper=StreamingObjectMapperHelper.getObjectMapper(); + private final Map orderbooks = new HashMap<>(); + private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - public BitflyerStreamingMarketDataService(PubnubStreamingService streamingService) { - this.streamingService = streamingService; - } + public BitflyerStreamingMarketDataService(PubnubStreamingService streamingService) { + this.streamingService = streamingService; + } - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String channelOrderbookSnapshotName = "lightning_board_snapshot_" + currencyPair.base.toString() + "_" + - currencyPair.counter.toString(); - String channelOrderbookUpdatesName = "lightning_board_" + currencyPair.base.toString() + "_" + - currencyPair.counter.toString(); + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + String channelOrderbookSnapshotName = + "lightning_board_snapshot_" + + currencyPair.base.toString() + + "_" + + currencyPair.counter.toString(); + String channelOrderbookUpdatesName = + "lightning_board_" + currencyPair.base.toString() + "_" + currencyPair.counter.toString(); - Observable snapshotTransactions = streamingService.subscribeChannel - (channelOrderbookSnapshotName).map(s -> { - BitflyerPubNubOrderbookTransaction transaction = mapper.treeToValue(s, BitflyerPubNubOrderbookTransaction.class); - BitflyerOrderbook bitflyerOrderbook = transaction.toBitflyerOrderbook(currencyPair); - orderbooks.put(currencyPair, bitflyerOrderbook); - return bitflyerOrderbook; - }); + Observable snapshotTransactions = + streamingService + .subscribeChannel(channelOrderbookSnapshotName) + .map( + s -> { + BitflyerPubNubOrderbookTransaction transaction = + mapper.treeToValue(s, BitflyerPubNubOrderbookTransaction.class); + BitflyerOrderbook bitflyerOrderbook = + transaction.toBitflyerOrderbook(currencyPair); + orderbooks.put(currencyPair, bitflyerOrderbook); + return bitflyerOrderbook; + }); - Observable updateTransactions = streamingService.subscribeChannel(channelOrderbookUpdatesName) - .filter(s -> orderbooks.containsKey(currencyPair)) - .map(s -> { - BitflyerOrderbook bitflyerOrderbook = orderbooks.get(currencyPair); - BitflyerPubNubOrderbookTransaction transaction = mapper.treeToValue(s, BitflyerPubNubOrderbookTransaction.class); - BitflyerLimitOrder[] asks = transaction.getAsks(); - BitflyerLimitOrder[] bids = transaction.getBids(); - bitflyerOrderbook.updateLevels(asks, Order.OrderType.ASK); - bitflyerOrderbook.updateLevels(bids, Order.OrderType.BID); - return bitflyerOrderbook; + Observable updateTransactions = + streamingService + .subscribeChannel(channelOrderbookUpdatesName) + .filter(s -> orderbooks.containsKey(currencyPair)) + .map( + s -> { + BitflyerOrderbook bitflyerOrderbook = orderbooks.get(currencyPair); + BitflyerPubNubOrderbookTransaction transaction = + mapper.treeToValue(s, BitflyerPubNubOrderbookTransaction.class); + BitflyerLimitOrder[] asks = transaction.getAsks(); + BitflyerLimitOrder[] bids = transaction.getBids(); + bitflyerOrderbook.updateLevels(asks, Order.OrderType.ASK); + bitflyerOrderbook.updateLevels(bids, Order.OrderType.BID); + return bitflyerOrderbook; }); - return updateTransactions.mergeWith(snapshotTransactions).map(BitflyerOrderbook::toOrderBook); - } + return updateTransactions.mergeWith(snapshotTransactions).map(BitflyerOrderbook::toOrderBook); + } - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - String channelName = "lightning_ticker_" + currencyPair.base.toString() + "_" + currencyPair.counter.toString(); - Observable tickerTransactions = streamingService.subscribeChannel(channelName).map(s -> { - BitflyerPubNubTickerTransaction transaction = mapper.treeToValue(s, BitflyerPubNubTickerTransaction.class); - return transaction.toBitflyerTicker(); - }); + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + String channelName = + "lightning_ticker_" + currencyPair.base.toString() + "_" + currencyPair.counter.toString(); + Observable tickerTransactions = + streamingService + .subscribeChannel(channelName) + .map( + s -> { + BitflyerPubNubTickerTransaction transaction = + mapper.treeToValue(s, BitflyerPubNubTickerTransaction.class); + return transaction.toBitflyerTicker(); + }); - return tickerTransactions.map(BitflyerTicker::toTicker); - } + return tickerTransactions.map(BitflyerTicker::toTicker); + } - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channelName = "lightning_executions_" + currencyPair.base.toString() + "_" + currencyPair.counter.toString(); - Observable tradeTransactions = streamingService.subscribeChannel(channelName).flatMapIterable(s -> { - BitflyerPubNubTradesTransaction transaction = new BitflyerPubNubTradesTransaction(s); - return transaction.toBitflyerTrades(); - }); + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channelName = + "lightning_executions_" + + currencyPair.base.toString() + + "_" + + currencyPair.counter.toString(); + Observable tradeTransactions = + streamingService + .subscribeChannel(channelName) + .flatMapIterable( + s -> { + BitflyerPubNubTradesTransaction transaction = + new BitflyerPubNubTradesTransaction(s); + return transaction.toBitflyerTrades(); + }); - return tradeTransactions.map(s -> s.toTrade(currencyPair)); - } + return tradeTransactions.map(s -> s.toTrade(currencyPair)); + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerLimitOrder.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerLimitOrder.java index 71560d051..f6884ff11 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerLimitOrder.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerLimitOrder.java @@ -1,34 +1,31 @@ package info.bitrich.xchangestream.bitflyer.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.LimitOrder; -import java.math.BigDecimal; - -/** - * Created by Lukas Zaoralek on 14.11.17. - */ +/** Created by Lukas Zaoralek on 14.11.17. */ public class BitflyerLimitOrder { - private final BigDecimal price; - private final BigDecimal size; + private final BigDecimal price; + private final BigDecimal size; - public BitflyerLimitOrder(@JsonProperty("price") BigDecimal price, - @JsonProperty("size") BigDecimal size) { - this.price = price; - this.size = size; - } + public BitflyerLimitOrder( + @JsonProperty("price") BigDecimal price, @JsonProperty("size") BigDecimal size) { + this.price = price; + this.size = size; + } - public BigDecimal getPrice() { - return price; - } + public BigDecimal getPrice() { + return price; + } - public BigDecimal getSize() { - return size; - } + public BigDecimal getSize() { + return size; + } - public LimitOrder toLimitOrder(CurrencyPair pair, Order.OrderType side) { - return new LimitOrder(side, size, pair, "", null, price); - } + public LimitOrder toLimitOrder(CurrencyPair pair, Order.OrderType side) { + return new LimitOrder(side, size, pair, "", null, price); + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerMarketEvent.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerMarketEvent.java index d9a8ec104..4e2d8c107 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerMarketEvent.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerMarketEvent.java @@ -4,29 +4,27 @@ import java.text.SimpleDateFormat; import java.util.Date; -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public abstract class BitflyerMarketEvent { - protected final String timestamp; + protected final String timestamp; - BitflyerMarketEvent(String timestamp) { - this.timestamp = timestamp; - } + BitflyerMarketEvent(String timestamp) { + this.timestamp = timestamp; + } - public String getTimestamp() { - return timestamp; - } + public String getTimestamp() { + return timestamp; + } - public Date getDate() { - SimpleDateFormat formatter; - formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - Date date = null; - try { - date = formatter.parse(timestamp.substring(0, 23)); - } catch (ParseException e) { - e.printStackTrace(); - } - return date; + public Date getDate() { + SimpleDateFormat formatter; + formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + Date date = null; + try { + date = formatter.parse(timestamp.substring(0, 23)); + } catch (ParseException e) { + e.printStackTrace(); } + return date; + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerOrderbook.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerOrderbook.java index dbd89297e..ba6410d14 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerOrderbook.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerOrderbook.java @@ -1,84 +1,86 @@ package info.bitrich.xchangestream.bitflyer.dto; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.dto.marketdata.OrderBook; -import org.knowm.xchange.dto.trade.LimitOrder; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.trade.LimitOrder; -/** - * Created by Lukas Zaoralek on 14.11.17. - */ +/** Created by Lukas Zaoralek on 14.11.17. */ public class BitflyerOrderbook { - private final SortedMap asks; - private final SortedMap bids; - private final CurrencyPair pair; - private final BigDecimal zero = new BigDecimal(0); + private final SortedMap asks; + private final SortedMap bids; + private final CurrencyPair pair; + private final BigDecimal zero = new BigDecimal(0); - public BitflyerOrderbook(CurrencyPair pair) { - this.asks = new TreeMap<>(); - this.bids = new TreeMap<>(java.util.Collections.reverseOrder()); - this.pair = pair; - } - - public BitflyerOrderbook(CurrencyPair pair, BitflyerLimitOrder[] asks, BitflyerLimitOrder[] bids) { - this(pair); - createFromLevels(asks, Order.OrderType.ASK); - createFromLevels(bids, Order.OrderType.BID); - } + public BitflyerOrderbook(CurrencyPair pair) { + this.asks = new TreeMap<>(); + this.bids = new TreeMap<>(java.util.Collections.reverseOrder()); + this.pair = pair; + } - public void createFromLevels(BitflyerLimitOrder[] levels, Order.OrderType side) { - SortedMap orderbookLevels = side == Order.OrderType.ASK ? asks : bids; - for (BitflyerLimitOrder level : levels) { - orderbookLevels.put(level.getPrice(), level); - } - } + public BitflyerOrderbook( + CurrencyPair pair, BitflyerLimitOrder[] asks, BitflyerLimitOrder[] bids) { + this(pair); + createFromLevels(asks, Order.OrderType.ASK); + createFromLevels(bids, Order.OrderType.BID); + } - public BitflyerLimitOrder[] getLevels(Order.OrderType side) { - SortedMap orderBookSide = side == Order.OrderType.ASK ? asks : bids; - return orderBookSide.values().toArray(new BitflyerLimitOrder[orderBookSide.size()]); + public void createFromLevels(BitflyerLimitOrder[] levels, Order.OrderType side) { + SortedMap orderbookLevels = + side == Order.OrderType.ASK ? asks : bids; + for (BitflyerLimitOrder level : levels) { + orderbookLevels.put(level.getPrice(), level); } + } - public BitflyerLimitOrder[] getAsks() { - return getLevels(Order.OrderType.ASK); - } + public BitflyerLimitOrder[] getLevels(Order.OrderType side) { + SortedMap orderBookSide = + side == Order.OrderType.ASK ? asks : bids; + return orderBookSide.values().toArray(new BitflyerLimitOrder[orderBookSide.size()]); + } - public BitflyerLimitOrder[] getBids() { - return getLevels(Order.OrderType.BID); - } + public BitflyerLimitOrder[] getAsks() { + return getLevels(Order.OrderType.ASK); + } - public static List toLimitOrders(BitflyerLimitOrder[] levels, Order.OrderType side, CurrencyPair pair) { - if (levels == null || levels.length == 0) return null; + public BitflyerLimitOrder[] getBids() { + return getLevels(Order.OrderType.BID); + } - List limitOrders = new ArrayList<>(levels.length); - for (BitflyerLimitOrder level : levels) { - LimitOrder limitOrder = level.toLimitOrder(pair, side); - limitOrders.add(limitOrder); - } + public static List toLimitOrders( + BitflyerLimitOrder[] levels, Order.OrderType side, CurrencyPair pair) { + if (levels == null || levels.length == 0) return null; - return limitOrders; + List limitOrders = new ArrayList<>(levels.length); + for (BitflyerLimitOrder level : levels) { + LimitOrder limitOrder = level.toLimitOrder(pair, side); + limitOrders.add(limitOrder); } - public OrderBook toOrderBook() { - List orderbookAsks = toLimitOrders(getAsks(), Order.OrderType.ASK, pair); - List orderbookBids = toLimitOrders(getBids(), Order.OrderType.BID, pair); - return new OrderBook(null, orderbookAsks, orderbookBids); - } + return limitOrders; + } + + public OrderBook toOrderBook() { + List orderbookAsks = toLimitOrders(getAsks(), Order.OrderType.ASK, pair); + List orderbookBids = toLimitOrders(getBids(), Order.OrderType.BID, pair); + return new OrderBook(null, orderbookAsks, orderbookBids); + } - public void updateLevels(BitflyerLimitOrder[] levels, Order.OrderType side) { - SortedMap orderBookSide = side == Order.OrderType.ASK ? asks : bids; - for (BitflyerLimitOrder level : levels) { - BigDecimal price = level.getPrice(); - boolean shouldDelete = level.getSize().compareTo(zero) == 0; - orderBookSide.remove(price); - if (!shouldDelete) { - orderBookSide.put(price, level); - } - } + public void updateLevels(BitflyerLimitOrder[] levels, Order.OrderType side) { + SortedMap orderBookSide = + side == Order.OrderType.ASK ? asks : bids; + for (BitflyerLimitOrder level : levels) { + BigDecimal price = level.getPrice(); + boolean shouldDelete = level.getSize().compareTo(zero) == 0; + orderBookSide.remove(price); + if (!shouldDelete) { + orderBookSide.put(price, level); + } } + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubOrderbookTransaction.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubOrderbookTransaction.java index 79ad8348f..e64e614b6 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubOrderbookTransaction.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubOrderbookTransaction.java @@ -1,39 +1,37 @@ package info.bitrich.xchangestream.bitflyer.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.currency.CurrencyPair; - import java.math.BigDecimal; +import org.knowm.xchange.currency.CurrencyPair; -/** - * Created by Lukas Zaoralek on 14.11.17. - */ +/** Created by Lukas Zaoralek on 14.11.17. */ public class BitflyerPubNubOrderbookTransaction { - private final BigDecimal midPrice; - private final BitflyerLimitOrder[] bids; - private final BitflyerLimitOrder[] asks; - - public BitflyerPubNubOrderbookTransaction(@JsonProperty("mid_price") BigDecimal midPrice, - @JsonProperty("bids") BitflyerLimitOrder[] bids, - @JsonProperty("asks") BitflyerLimitOrder[] asks) { - this.midPrice = midPrice; - this.bids = bids; - this.asks = asks; - } - - public BitflyerOrderbook toBitflyerOrderbook(CurrencyPair pair) { - return new BitflyerOrderbook(pair, asks, bids); - } - - public BigDecimal getMidPrice() { - return midPrice; - } - - public BitflyerLimitOrder[] getBids() { - return bids; - } - - public BitflyerLimitOrder[] getAsks() { - return asks; - } + private final BigDecimal midPrice; + private final BitflyerLimitOrder[] bids; + private final BitflyerLimitOrder[] asks; + + public BitflyerPubNubOrderbookTransaction( + @JsonProperty("mid_price") BigDecimal midPrice, + @JsonProperty("bids") BitflyerLimitOrder[] bids, + @JsonProperty("asks") BitflyerLimitOrder[] asks) { + this.midPrice = midPrice; + this.bids = bids; + this.asks = asks; + } + + public BitflyerOrderbook toBitflyerOrderbook(CurrencyPair pair) { + return new BitflyerOrderbook(pair, asks, bids); + } + + public BigDecimal getMidPrice() { + return midPrice; + } + + public BitflyerLimitOrder[] getBids() { + return bids; + } + + public BitflyerLimitOrder[] getAsks() { + return asks; + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTickerTransaction.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTickerTransaction.java index 3e0f88af2..3ddd7bc2c 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTickerTransaction.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTickerTransaction.java @@ -1,102 +1,111 @@ package info.bitrich.xchangestream.bitflyer.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class BitflyerPubNubTickerTransaction { - private final String productCode; - private final String timestamp; - private final String tickId; - private final BigDecimal bestBid; - private final BigDecimal bestAsk; - private final BigDecimal bestBidSize; - private final BigDecimal bestAskSize; - private final BigDecimal totalBidDepth; - private final BigDecimal totalAskDepth; - private final String ltp; - private final BigDecimal volume; - private final BigDecimal volumeByProduct; - - public BitflyerPubNubTickerTransaction(@JsonProperty("product_code") String productCode, - @JsonProperty("timestamp") String timestamp, - @JsonProperty("tick_id") String tickId, - @JsonProperty("best_bid") BigDecimal bestBid, - @JsonProperty("best_ask") BigDecimal bestAsk, - @JsonProperty("best_bid_size") BigDecimal bestBidSize, - @JsonProperty("best_ask_size") BigDecimal bestAskSize, - @JsonProperty("total_bid_depth") BigDecimal totalBidDepth, - @JsonProperty("total_ask_depth") BigDecimal totalAskDepth, - @JsonProperty("ltp") String ltp, - @JsonProperty("volume") BigDecimal volume, - @JsonProperty("volume_by_product") BigDecimal volumeByProduct) { - this.productCode = productCode; - this.timestamp = timestamp; - this.tickId = tickId; - this.bestBid = bestBid; - this.bestAsk = bestAsk; - this.bestBidSize = bestBidSize; - this.bestAskSize = bestAskSize; - this.totalBidDepth = totalBidDepth; - this.totalAskDepth = totalAskDepth; - this.ltp = ltp; - this.volume = volume; - this.volumeByProduct = volumeByProduct; - } - - public BitflyerTicker toBitflyerTicker() { - return new BitflyerTicker(productCode, timestamp, tickId, bestBid, bestAsk, bestBidSize, bestAskSize, - totalBidDepth, totalAskDepth, ltp, volume, volumeByProduct); - } - - public String getProductCode() { - return productCode; - } - - public String getTimestamp() { - return timestamp; - } - - public String getTickId() { - return tickId; - } - - public BigDecimal getBestBid() { - return bestBid; - } - - public BigDecimal getBestAsk() { - return bestAsk; - } - - public BigDecimal getBestBidSize() { - return bestBidSize; - } - - public BigDecimal getBestAskSize() { - return bestAskSize; - } - - public BigDecimal getTotalBidDepth() { - return totalBidDepth; - } - - public BigDecimal getTotalAskDepth() { - return totalAskDepth; - } - - public String getLtp() { - return ltp; - } - - public BigDecimal getVolume() { - return volume; - } - - public BigDecimal getVolumeByProduct() { - return volumeByProduct; - } + private final String productCode; + private final String timestamp; + private final String tickId; + private final BigDecimal bestBid; + private final BigDecimal bestAsk; + private final BigDecimal bestBidSize; + private final BigDecimal bestAskSize; + private final BigDecimal totalBidDepth; + private final BigDecimal totalAskDepth; + private final String ltp; + private final BigDecimal volume; + private final BigDecimal volumeByProduct; + + public BitflyerPubNubTickerTransaction( + @JsonProperty("product_code") String productCode, + @JsonProperty("timestamp") String timestamp, + @JsonProperty("tick_id") String tickId, + @JsonProperty("best_bid") BigDecimal bestBid, + @JsonProperty("best_ask") BigDecimal bestAsk, + @JsonProperty("best_bid_size") BigDecimal bestBidSize, + @JsonProperty("best_ask_size") BigDecimal bestAskSize, + @JsonProperty("total_bid_depth") BigDecimal totalBidDepth, + @JsonProperty("total_ask_depth") BigDecimal totalAskDepth, + @JsonProperty("ltp") String ltp, + @JsonProperty("volume") BigDecimal volume, + @JsonProperty("volume_by_product") BigDecimal volumeByProduct) { + this.productCode = productCode; + this.timestamp = timestamp; + this.tickId = tickId; + this.bestBid = bestBid; + this.bestAsk = bestAsk; + this.bestBidSize = bestBidSize; + this.bestAskSize = bestAskSize; + this.totalBidDepth = totalBidDepth; + this.totalAskDepth = totalAskDepth; + this.ltp = ltp; + this.volume = volume; + this.volumeByProduct = volumeByProduct; + } + + public BitflyerTicker toBitflyerTicker() { + return new BitflyerTicker( + productCode, + timestamp, + tickId, + bestBid, + bestAsk, + bestBidSize, + bestAskSize, + totalBidDepth, + totalAskDepth, + ltp, + volume, + volumeByProduct); + } + + public String getProductCode() { + return productCode; + } + + public String getTimestamp() { + return timestamp; + } + + public String getTickId() { + return tickId; + } + + public BigDecimal getBestBid() { + return bestBid; + } + + public BigDecimal getBestAsk() { + return bestAsk; + } + + public BigDecimal getBestBidSize() { + return bestBidSize; + } + + public BigDecimal getBestAskSize() { + return bestAskSize; + } + + public BigDecimal getTotalBidDepth() { + return totalBidDepth; + } + + public BigDecimal getTotalAskDepth() { + return totalAskDepth; + } + + public String getLtp() { + return ltp; + } + + public BigDecimal getVolume() { + return volume; + } + + public BigDecimal getVolumeByProduct() { + return volumeByProduct; + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTradesTransaction.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTradesTransaction.java index 742442c0c..3599fc024 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTradesTransaction.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerPubNubTradesTransaction.java @@ -1,43 +1,39 @@ package info.bitrich.xchangestream.bitflyer.dto; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; - import java.io.IOException; import java.util.ArrayList; import java.util.List; -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class BitflyerPubNubTradesTransaction { - private final JsonNode jsonTrades; - private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + private final JsonNode jsonTrades; + private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - public BitflyerPubNubTradesTransaction(JsonNode jsonTrades) { - this.jsonTrades = jsonTrades; - } + public BitflyerPubNubTradesTransaction(JsonNode jsonTrades) { + this.jsonTrades = jsonTrades; + } - public JsonNode getJsonTrades() { - return jsonTrades; - } + public JsonNode getJsonTrades() { + return jsonTrades; + } - public List toBitflyerTrades() { - List trades = new ArrayList<>(jsonTrades.size()); - if (jsonTrades.isArray()) { - for (JsonNode jsonTrade : jsonTrades) { - BitflyerTrade trade = null; - try { - trade = mapper.treeToValue(jsonTrade, BitflyerTrade.class); - } catch (IOException e) { - e.printStackTrace(); - } - trades.add(trade); - } + public List toBitflyerTrades() { + List trades = new ArrayList<>(jsonTrades.size()); + if (jsonTrades.isArray()) { + for (JsonNode jsonTrade : jsonTrades) { + BitflyerTrade trade = null; + try { + trade = mapper.treeToValue(jsonTrade, BitflyerTrade.class); + } catch (IOException e) { + e.printStackTrace(); } - - return trades; + trades.add(trade); + } } + + return trades; + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTicker.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTicker.java index 065a6ff57..0b36d4253 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTicker.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTicker.java @@ -1,107 +1,110 @@ package info.bitrich.xchangestream.bitflyer.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; -import java.math.BigDecimal; - -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class BitflyerTicker extends BitflyerMarketEvent { - private final String productCode; - private final String tickId; - private final BigDecimal bestBid; - private final BigDecimal bestAsk; - private final BigDecimal bestBidSize; - private final BigDecimal bestAskSize; - private final BigDecimal totalBidDepth; - private final BigDecimal totalAskDepth; - private final String ltp; - private final BigDecimal volume; - private final BigDecimal volumeByProduct; - - public BitflyerTicker(@JsonProperty("product_code") String productCode, - @JsonProperty("timestamp") String timestamp, - @JsonProperty("tick_id") String tickId, - @JsonProperty("best_bid") BigDecimal bestBid, - @JsonProperty("best_ask") BigDecimal bestAsk, - @JsonProperty("best_bid_size") BigDecimal bestBidSize, - @JsonProperty("best_ask_size") BigDecimal bestAskSize, - @JsonProperty("total_bid_depth") BigDecimal totalBidDepth, - @JsonProperty("total_ask_depth") BigDecimal totalAskDepth, - @JsonProperty("ltp") String ltp, - @JsonProperty("volume") BigDecimal volume, - @JsonProperty("volume_by_product") BigDecimal volumeByProduct) { - super(timestamp); - this.productCode = productCode; - this.tickId = tickId; - this.bestBid = bestBid; - this.bestAsk = bestAsk; - this.bestBidSize = bestBidSize; - this.bestAskSize = bestAskSize; - this.totalBidDepth = totalBidDepth; - this.totalAskDepth = totalAskDepth; - this.ltp = ltp; - this.volume = volume; - this.volumeByProduct = volumeByProduct; - } - - public String getProductCode() { - return productCode; - } - - public String getTickId() { - return tickId; - } - - public BigDecimal getBestBid() { - return bestBid; - } - - public BigDecimal getBestAsk() { - return bestAsk; - } - - public BigDecimal getBestBidSize() { - return bestBidSize; - } - - public BigDecimal getBestAskSize() { - return bestAskSize; - } - - public BigDecimal getTotalBidDepth() { - return totalBidDepth; - } - - public BigDecimal getTotalAskDepth() { - return totalAskDepth; - } - - public String getLtp() { - return ltp; - } - - public BigDecimal getVolume() { - return volume; - } - - public BigDecimal getVolumeByProduct() { - return volumeByProduct; - } - - public CurrencyPair getCurrencyPair() { - String[] currencies = productCode.split("_"); - String base = currencies[0]; - String counter = currencies[1]; - return new CurrencyPair(new Currency(base), new Currency(counter)); - } - - public Ticker toTicker() { - return new Ticker.Builder().ask(bestAsk).bid(bestBid).volume(volume) - .timestamp(getDate()).currencyPair(getCurrencyPair()).build(); - } + private final String productCode; + private final String tickId; + private final BigDecimal bestBid; + private final BigDecimal bestAsk; + private final BigDecimal bestBidSize; + private final BigDecimal bestAskSize; + private final BigDecimal totalBidDepth; + private final BigDecimal totalAskDepth; + private final String ltp; + private final BigDecimal volume; + private final BigDecimal volumeByProduct; + + public BitflyerTicker( + @JsonProperty("product_code") String productCode, + @JsonProperty("timestamp") String timestamp, + @JsonProperty("tick_id") String tickId, + @JsonProperty("best_bid") BigDecimal bestBid, + @JsonProperty("best_ask") BigDecimal bestAsk, + @JsonProperty("best_bid_size") BigDecimal bestBidSize, + @JsonProperty("best_ask_size") BigDecimal bestAskSize, + @JsonProperty("total_bid_depth") BigDecimal totalBidDepth, + @JsonProperty("total_ask_depth") BigDecimal totalAskDepth, + @JsonProperty("ltp") String ltp, + @JsonProperty("volume") BigDecimal volume, + @JsonProperty("volume_by_product") BigDecimal volumeByProduct) { + super(timestamp); + this.productCode = productCode; + this.tickId = tickId; + this.bestBid = bestBid; + this.bestAsk = bestAsk; + this.bestBidSize = bestBidSize; + this.bestAskSize = bestAskSize; + this.totalBidDepth = totalBidDepth; + this.totalAskDepth = totalAskDepth; + this.ltp = ltp; + this.volume = volume; + this.volumeByProduct = volumeByProduct; + } + + public String getProductCode() { + return productCode; + } + + public String getTickId() { + return tickId; + } + + public BigDecimal getBestBid() { + return bestBid; + } + + public BigDecimal getBestAsk() { + return bestAsk; + } + + public BigDecimal getBestBidSize() { + return bestBidSize; + } + + public BigDecimal getBestAskSize() { + return bestAskSize; + } + + public BigDecimal getTotalBidDepth() { + return totalBidDepth; + } + + public BigDecimal getTotalAskDepth() { + return totalAskDepth; + } + + public String getLtp() { + return ltp; + } + + public BigDecimal getVolume() { + return volume; + } + + public BigDecimal getVolumeByProduct() { + return volumeByProduct; + } + + public CurrencyPair getCurrencyPair() { + String[] currencies = productCode.split("_"); + String base = currencies[0]; + String counter = currencies[1]; + return new CurrencyPair(new Currency(base), new Currency(counter)); + } + + public Ticker toTicker() { + return new Ticker.Builder() + .ask(bestAsk) + .bid(bestBid) + .volume(volume) + .timestamp(getDate()) + .currencyPair(getCurrencyPair()) + .build(); + } } diff --git a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTrade.java b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTrade.java index 4a0bbd568..acf5ed4ad 100644 --- a/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTrade.java +++ b/xchange-stream-bitflyer/src/main/java/info/bitrich/xchangestream/bitflyer/dto/BitflyerTrade.java @@ -1,76 +1,74 @@ package info.bitrich.xchangestream.bitflyer.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.marketdata.Trade; -import java.math.BigDecimal; - -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class BitflyerTrade extends BitflyerMarketEvent { - private final String id; - private final String side; - private final BigDecimal price; - private final BigDecimal size; - private final String buy_child_order_acceptance_id; - private final String sell_child_order_acceptance_id; + private final String id; + private final String side; + private final BigDecimal price; + private final BigDecimal size; + private final String buy_child_order_acceptance_id; + private final String sell_child_order_acceptance_id; - public BitflyerTrade(@JsonProperty("id") String id, - @JsonProperty("side") String side, - @JsonProperty("price") BigDecimal price, - @JsonProperty("size") BigDecimal size, - @JsonProperty("exec_date") String timestamp, - @JsonProperty("buy_child_order_acceptance_id") String buy_child_order_acceptance_id, - @JsonProperty("sell_child_order_acceptance_id") String sell_child_order_acceptance_id) { - super(timestamp); - this.id = id; - this.side = side; - this.price = price; - this.size = size; - this.buy_child_order_acceptance_id = buy_child_order_acceptance_id; - this.sell_child_order_acceptance_id = sell_child_order_acceptance_id; - } + public BitflyerTrade( + @JsonProperty("id") String id, + @JsonProperty("side") String side, + @JsonProperty("price") BigDecimal price, + @JsonProperty("size") BigDecimal size, + @JsonProperty("exec_date") String timestamp, + @JsonProperty("buy_child_order_acceptance_id") String buy_child_order_acceptance_id, + @JsonProperty("sell_child_order_acceptance_id") String sell_child_order_acceptance_id) { + super(timestamp); + this.id = id; + this.side = side; + this.price = price; + this.size = size; + this.buy_child_order_acceptance_id = buy_child_order_acceptance_id; + this.sell_child_order_acceptance_id = sell_child_order_acceptance_id; + } - public String getId() { - return id; - } + public String getId() { + return id; + } - public String getSide() { - return side; - } + public String getSide() { + return side; + } - public BigDecimal getPrice() { - return price; - } + public BigDecimal getPrice() { + return price; + } - public BigDecimal getSize() { - return size; - } + public BigDecimal getSize() { + return size; + } - public String getBuy_child_order_acceptance_id() { - return buy_child_order_acceptance_id; - } + public String getBuy_child_order_acceptance_id() { + return buy_child_order_acceptance_id; + } - public String getSell_child_order_acceptance_id() { - return sell_child_order_acceptance_id; - } + public String getSell_child_order_acceptance_id() { + return sell_child_order_acceptance_id; + } - public Order.OrderType getOrderSide() { - return side.equals("SELL") ? Order.OrderType.ASK : Order.OrderType.BID; - } + public Order.OrderType getOrderSide() { + return side.equals("SELL") ? Order.OrderType.ASK : Order.OrderType.BID; + } - public Trade toTrade(CurrencyPair pair) { - Order.OrderType orderType = getOrderSide(); - return new Trade.Builder() - .type(orderType) - .originalAmount(size) - .currencyPair(pair) - .price(price) - .timestamp(getDate()) - .id(id) - .build(); - } + public Trade toTrade(CurrencyPair pair) { + Order.OrderType orderType = getOrderSide(); + return new Trade.Builder() + .type(orderType) + .originalAmount(size) + .currencyPair(pair) + .price(price) + .timestamp(getDate()) + .id(id) + .build(); + } } diff --git a/xchange-stream-bitflyer/src/test/java/info/bitrich/xchangestream/bitflyer/BitflyerManualExample.java b/xchange-stream-bitflyer/src/test/java/info/bitrich/xchangestream/bitflyer/BitflyerManualExample.java index dcdf0d6fb..50d4658bb 100644 --- a/xchange-stream-bitflyer/src/test/java/info/bitrich/xchangestream/bitflyer/BitflyerManualExample.java +++ b/xchange-stream-bitflyer/src/test/java/info/bitrich/xchangestream/bitflyer/BitflyerManualExample.java @@ -6,34 +6,46 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 14.11.17. - */ +/** Created by Lukas Zaoralek on 14.11.17. */ public class BitflyerManualExample { - private static final Logger LOG = LoggerFactory.getLogger(BitflyerManualExample.class); + private static final Logger LOG = LoggerFactory.getLogger(BitflyerManualExample.class); - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(BitflyerStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(BitflyerStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); - //Note that, the receiving first order book snapshot takes several seconds or minutes! - exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_JPY).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); + // Note that, the receiving first order book snapshot takes several seconds or minutes! + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_JPY) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); - exchange.getStreamingMarketDataService().getTicker(CurrencyPair.BTC_JPY).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.BTC_JPY) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_JPY) - .subscribe(trade -> LOG.info("TRADE: {}", trade), - throwable -> LOG.error("ERROR in getting trades: ", throwable)); + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_JPY) + .subscribe( + trade -> LOG.info("TRADE: {}", trade), + throwable -> LOG.error("ERROR in getting trades: ", throwable)); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticator.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticator.java index 665c0b3c2..7410f1ecf 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticator.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticator.java @@ -1,38 +1,36 @@ package info.bitrich.xchangestream.bitmex; +import java.nio.charset.Charset; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import java.nio.charset.Charset; -/** - * Created by heath on 2018/3/1. - */ +/** Created by heath on 2018/3/1. */ public class BitmexAuthenticator { - public static String getSHA256String(String str, String key) { - - try { - Charset asciiCs = Charset.forName("US-ASCII"); - SecretKeySpec signingKey = new SecretKeySpec(asciiCs.encode(key).array(), "HmacSHA256"); - Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); - sha256_HMAC.init(signingKey); - byte[] mac_data = sha256_HMAC.doFinal(asciiCs.encode(str).array()); - StringBuilder result = new StringBuilder(); - for (final byte element : mac_data) { - result.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1)); - } - // System.out.println("SHA256String Result:[" + result + "]"); - return result.toString().toUpperCase(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + public static String getSHA256String(String str, String key) { - public static String generateSignature(String secret, String verb, String url, String nonce, String data) { - String message = verb + url + nonce + data; - // System.out.println(message); - return getSHA256String(message, secret); + try { + Charset asciiCs = Charset.forName("US-ASCII"); + SecretKeySpec signingKey = new SecretKeySpec(asciiCs.encode(key).array(), "HmacSHA256"); + Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); + sha256_HMAC.init(signingKey); + byte[] mac_data = sha256_HMAC.doFinal(asciiCs.encode(str).array()); + StringBuilder result = new StringBuilder(); + for (final byte element : mac_data) { + result.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1)); + } + // System.out.println("SHA256String Result:[" + result + "]"); + return result.toString().toUpperCase(); + } catch (Exception e) { + e.printStackTrace(); } + return null; + } + public static String generateSignature( + String secret, String verb, String url, String nonce, String data) { + String message = verb + url + nonce + data; + // System.out.println(message); + return getSHA256String(message, secret); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java index 38989b62c..7a910e9ea 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java @@ -3,90 +3,90 @@ import info.bitrich.xchangestream.core.ProductSubscription; import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingMarketDataService; -import io.netty.channel.ChannelHandlerContext; import io.reactivex.Completable; import io.reactivex.Observable; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.bitmex.BitmexExchange; -/** - * Created by Lukas Zaoralek on 12.11.17. - */ +/** Created by Lukas Zaoralek on 12.11.17. */ public class BitmexStreamingExchange extends BitmexExchange implements StreamingExchange { - private static final String API_URI = "wss://www.bitmex.com/realtime"; - private static final String TESTNET_API_URI = "wss://testnet.bitmex.com/realtime"; - - private BitmexStreamingService streamingService; - private BitmexStreamingMarketDataService streamingMarketDataService; - - public BitmexStreamingExchange() { - } - - @Override - protected void initServices() { - super.initServices(); - streamingService = createStreamingService(); - streamingMarketDataService = new BitmexStreamingMarketDataService(streamingService, this); - } - - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } - - private BitmexStreamingService createStreamingService() { - ExchangeSpecification exchangeSpec = getExchangeSpecification(); - Boolean useSandbox = (Boolean) exchangeSpec.getExchangeSpecificParametersItem(USE_SANDBOX); - String uri = useSandbox == null || !useSandbox ? API_URI : TESTNET_API_URI; - BitmexStreamingService streamingService = new BitmexStreamingService(uri, exchangeSpec.getApiKey(), exchangeSpec.getSecretKey()); - applyStreamingSpecification(exchangeSpec, streamingService); - return streamingService; - } - - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } - - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); - return spec; - } - - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } - - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } - - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } - - @Override - public Observable messageDelay() { - return Observable.create(delayEmitter -> { - streamingService.addDelayEmitter(delayEmitter); + private static final String API_URI = "wss://www.bitmex.com/realtime"; + private static final String TESTNET_API_URI = "wss://testnet.bitmex.com/realtime"; + + private BitmexStreamingService streamingService; + private BitmexStreamingMarketDataService streamingMarketDataService; + + public BitmexStreamingExchange() {} + + @Override + protected void initServices() { + super.initServices(); + streamingService = createStreamingService(); + streamingMarketDataService = new BitmexStreamingMarketDataService(streamingService, this); + } + + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } + + private BitmexStreamingService createStreamingService() { + ExchangeSpecification exchangeSpec = getExchangeSpecification(); + Boolean useSandbox = (Boolean) exchangeSpec.getExchangeSpecificParametersItem(USE_SANDBOX); + String uri = useSandbox == null || !useSandbox ? API_URI : TESTNET_API_URI; + BitmexStreamingService streamingService = + new BitmexStreamingService(uri, exchangeSpec.getApiKey(), exchangeSpec.getSecretKey()); + applyStreamingSpecification(exchangeSpec, streamingService); + return streamingService; + } + + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } + + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); + return spec; + } + + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } + + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } + + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } + + @Override + public Observable messageDelay() { + return Observable.create( + delayEmitter -> { + streamingService.addDelayEmitter(delayEmitter); }); - } + } - @Override - public void resubscribeChannels() { - streamingService.resubscribeChannels(); - } + @Override + public void resubscribeChannels() { + streamingService.resubscribeChannels(); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java index 2e52ed8af..1e040533d 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java @@ -6,6 +6,8 @@ import info.bitrich.xchangestream.core.StreamingMarketDataService; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.io.IOException; +import java.util.*; import org.knowm.xchange.bitmex.BitmexExchange; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; @@ -14,129 +16,142 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.*; - -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexStreamingMarketDataService implements StreamingMarketDataService { - private static final Logger LOG = LoggerFactory.getLogger(BitmexStreamingMarketDataService.class); - - private final ObjectMapper objectMapper = StreamingObjectMapperHelper.getObjectMapper(); - - private final BitmexStreamingService streamingService; - - private final BitmexExchange bitmexExchange; - - private final SortedMap orderbooks = new TreeMap<>(); - - public BitmexStreamingMarketDataService(BitmexStreamingService streamingService, BitmexExchange bitmexExchange) { - this.streamingService = streamingService; - this.streamingService.subscribeConnectionSuccess().subscribe(o -> { - LOG.info("Bitmex connection succeeded. Clearing orderbooks."); - orderbooks.clear(); - }); - this.bitmexExchange = bitmexExchange; - } - - private String getBitmexSymbol(CurrencyPair currencyPair) { - return currencyPair.base.toString() + currencyPair.counter.toString(); - } - - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String instrument = getBitmexSymbol(currencyPair); - String channelName = String.format("orderBookL2:%s", instrument); - - return streamingService.subscribeBitmexChannel(channelName).map(s -> { - BitmexOrderbook orderbook; - String action = s.getAction(); - if (action.equals("partial")) { + private static final Logger LOG = LoggerFactory.getLogger(BitmexStreamingMarketDataService.class); + + private final ObjectMapper objectMapper = StreamingObjectMapperHelper.getObjectMapper(); + + private final BitmexStreamingService streamingService; + + private final BitmexExchange bitmexExchange; + + private final SortedMap orderbooks = new TreeMap<>(); + + public BitmexStreamingMarketDataService( + BitmexStreamingService streamingService, BitmexExchange bitmexExchange) { + this.streamingService = streamingService; + this.streamingService + .subscribeConnectionSuccess() + .subscribe( + o -> { + LOG.info("Bitmex connection succeeded. Clearing orderbooks."); + orderbooks.clear(); + }); + this.bitmexExchange = bitmexExchange; + } + + private String getBitmexSymbol(CurrencyPair currencyPair) { + return currencyPair.base.toString() + currencyPair.counter.toString(); + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + String instrument = getBitmexSymbol(currencyPair); + String channelName = String.format("orderBookL2:%s", instrument); + + return streamingService + .subscribeBitmexChannel(channelName) + .map( + s -> { + BitmexOrderbook orderbook; + String action = s.getAction(); + if (action.equals("partial")) { orderbook = s.toBitmexOrderbook(); orderbooks.put(instrument, orderbook); - } else { + } else { orderbook = orderbooks.get(instrument); - //ignore updates until first "partial" + // ignore updates until first "partial" if (orderbook == null) { - return new OrderBook(new Date(), Collections.emptyList(), Collections.emptyList()); + return new OrderBook( + new Date(), Collections.emptyList(), Collections.emptyList()); } BitmexLimitOrder[] levels = s.toBitmexOrderbookLevels(); orderbook.updateLevels(levels, action); - } - - return orderbook.toOrderbook(); - }); - } - - public Observable getRawTicker(CurrencyPair currencyPair) { - String instrument = getBitmexSymbol(currencyPair); - String channelName = String.format("quote:%s", instrument); - - return streamingService.subscribeBitmexChannel(channelName).map(s -> s.toBitmexTicker()); - } - - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - String instrument = getBitmexSymbol(currencyPair); - String channelName = String.format("quote:%s", instrument); - - return streamingService.subscribeBitmexChannel(channelName).map(s -> { - BitmexTicker bitmexTicker = s.toBitmexTicker(); - return bitmexTicker.toTicker(); - }); - } - - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String instrument = getBitmexSymbol(currencyPair); - String channelName = String.format("trade:%s", instrument); - - return streamingService.subscribeBitmexChannel(channelName).flatMapIterable(s -> { - BitmexTrade[] bitmexTrades = s.toBitmexTrades(); - List trades = new ArrayList<>(bitmexTrades.length); - for (BitmexTrade bitmexTrade : bitmexTrades) { + } + + return orderbook.toOrderbook(); + }); + } + + public Observable getRawTicker(CurrencyPair currencyPair) { + String instrument = getBitmexSymbol(currencyPair); + String channelName = String.format("quote:%s", instrument); + + return streamingService.subscribeBitmexChannel(channelName).map(s -> s.toBitmexTicker()); + } + + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + String instrument = getBitmexSymbol(currencyPair); + String channelName = String.format("quote:%s", instrument); + + return streamingService + .subscribeBitmexChannel(channelName) + .map( + s -> { + BitmexTicker bitmexTicker = s.toBitmexTicker(); + return bitmexTicker.toTicker(); + }); + } + + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String instrument = getBitmexSymbol(currencyPair); + String channelName = String.format("trade:%s", instrument); + + return streamingService + .subscribeBitmexChannel(channelName) + .flatMapIterable( + s -> { + BitmexTrade[] bitmexTrades = s.toBitmexTrades(); + List trades = new ArrayList<>(bitmexTrades.length); + for (BitmexTrade bitmexTrade : bitmexTrades) { trades.add(bitmexTrade.toTrade()); - } - return trades; - }); - } - - - public Observable getRawExecutions(String symbol) { - return streamingService.subscribeBitmexChannel("execution:" + symbol).flatMapIterable(s -> { - JsonNode executions = s.getData(); - List bitmexExecutions = new ArrayList<>(executions.size()); - for (JsonNode execution : executions) { + } + return trades; + }); + } + + public Observable getRawExecutions(String symbol) { + return streamingService + .subscribeBitmexChannel("execution:" + symbol) + .flatMapIterable( + s -> { + JsonNode executions = s.getData(); + List bitmexExecutions = new ArrayList<>(executions.size()); + for (JsonNode execution : executions) { bitmexExecutions.add(objectMapper.treeToValue(execution, BitmexExecution.class)); - } - return bitmexExecutions; - }); - } - - public void enableDeadManSwitch() throws IOException { - enableDeadManSwitch(BitmexStreamingService.DMS_RESUBSCRIBE, BitmexStreamingService.DMS_CANCEL_ALL_IN); - } - - /** - * @param rate in milliseconds to send updated - * @param timeout milliseconds from now after which orders will be cancelled - */ - public void enableDeadManSwitch(long rate, long timeout) throws IOException { - streamingService.enableDeadMansSwitch(rate, timeout); - } - - public boolean isDeadManSwitchEnabled() throws IOException { - return streamingService.isDeadMansSwitchEnabled(); - } - - public void disableDeadMansSwitch() throws IOException { - streamingService.disableDeadMansSwitch(); - } - - public Observable getRawFunding() { - String channelName = "funding"; - return streamingService.subscribeBitmexChannel(channelName).map(BitmexWebSocketTransaction::toBitmexFunding); - } - + } + return bitmexExecutions; + }); + } + + public void enableDeadManSwitch() throws IOException { + enableDeadManSwitch( + BitmexStreamingService.DMS_RESUBSCRIBE, BitmexStreamingService.DMS_CANCEL_ALL_IN); + } + + /** + * @param rate in milliseconds to send updated + * @param timeout milliseconds from now after which orders will be cancelled + */ + public void enableDeadManSwitch(long rate, long timeout) throws IOException { + streamingService.enableDeadMansSwitch(rate, timeout); + } + + public boolean isDeadManSwitchEnabled() throws IOException { + return streamingService.isDeadMansSwitchEnabled(); + } + + public void disableDeadMansSwitch() throws IOException { + streamingService.disableDeadMansSwitch(); + } + + public Observable getRawFunding() { + String channelName = "funding"; + return streamingService + .subscribeBitmexChannel(channelName) + .map(BitmexWebSocketTransaction::toBitmexFunding); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java index aba841a42..dd55003b8 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java @@ -16,216 +16,228 @@ import io.reactivex.ObservableEmitter; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.knowm.xchange.bitmex.service.BitmexDigest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.ZoneOffset; import java.util.*; import java.util.concurrent.TimeUnit; +import org.knowm.xchange.bitmex.service.BitmexDigest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(BitmexStreamingService.class); - private final ObjectMapper mapper = new ObjectMapper(); - private List> delayEmitters = new LinkedList<>(); - - private final String apiKey; - private final String secretKey; - - public static final int DMS_CANCEL_ALL_IN = 60000; - public static final int DMS_RESUBSCRIBE = 15000; - /** - * deadman's cancel time - */ - private long dmsCancelTime; - private Disposable dmsDisposable; - - public BitmexStreamingService(String apiUrl, String apiKey, String secretKey) { - super(apiUrl, Integer.MAX_VALUE); - this.apiKey = apiKey; - this.secretKey = secretKey; - } - - private void login() throws JsonProcessingException { - long expires = System.currentTimeMillis() + 30; - String path = "/realtime"; - String signature = BitmexAuthenticator.generateSignature(secretKey, - "GET", path, String.valueOf(expires), ""); - - List args = Arrays.asList(apiKey, expires, signature); - - Map cmd = new HashMap<>(); - cmd.put("op", "authKey"); - cmd.put("args", args); - this.sendMessage(mapper.writeValueAsString(cmd)); - + private static final Logger LOG = LoggerFactory.getLogger(BitmexStreamingService.class); + private final ObjectMapper mapper = new ObjectMapper(); + private List> delayEmitters = new LinkedList<>(); + + private final String apiKey; + private final String secretKey; + + public static final int DMS_CANCEL_ALL_IN = 60000; + public static final int DMS_RESUBSCRIBE = 15000; + /** deadman's cancel time */ + private long dmsCancelTime; + + private Disposable dmsDisposable; + + public BitmexStreamingService(String apiUrl, String apiKey, String secretKey) { + super(apiUrl, Integer.MAX_VALUE); + this.apiKey = apiKey; + this.secretKey = secretKey; + } + + private void login() throws JsonProcessingException { + long expires = System.currentTimeMillis() + 30; + String path = "/realtime"; + String signature = + BitmexAuthenticator.generateSignature(secretKey, "GET", path, String.valueOf(expires), ""); + + List args = Arrays.asList(apiKey, expires, signature); + + Map cmd = new HashMap<>(); + cmd.put("op", "authKey"); + cmd.put("args", args); + this.sendMessage(mapper.writeValueAsString(cmd)); + } + + @Override + public Completable connect() { + // Note that we must override connect method in streaming service instead of streaming exchange, + // because of the auto reconnect feature of NettyStreamingService. + // We must ensure the authentication message is also resend when the connection is rebuilt. + Completable conn = super.connect(); + if (apiKey == null) { + return conn; } - - @Override - public Completable connect() { - // Note that we must override connect method in streaming service instead of streaming exchange, because of the auto reconnect feature of NettyStreamingService. - // We must ensure the authentication message is also resend when the connection is rebuilt. - Completable conn = super.connect(); - if (apiKey == null) { - return conn; - } - return conn.andThen((CompletableSource) (completable) -> { - try { + return conn.andThen( + (CompletableSource) + (completable) -> { + try { login(); completable.onComplete(); - } catch (IOException e) { + } catch (IOException e) { completable.onError(e); + } + }); + } + + @Override + protected void handleMessage(JsonNode message) { + if (!delayEmitters.isEmpty() && message.has("data")) { + String table = ""; + if (message.has("table")) { + table = message.get("table").asText(); + } + JsonNode data = message.get("data"); + if (data.getNodeType().equals(JsonNodeType.ARRAY)) { + Long current = System.currentTimeMillis(); + SimpleDateFormat formatter; + formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + JsonNode d = data.get(0); + if (d != null + && d.has("timestamp") + && (!"order".equals(table) + || d.has("ordStatus") && "NEW".equals(d.get("ordStatus").asText()))) { + try { + String timestamp = d.get("timestamp").asText(); + Date date = formatter.parse(timestamp); + long delay = current - date.getTime(); + for (ObservableEmitter emitter : delayEmitters) { + emitter.onNext(delay); } - }); - } - - @Override - protected void handleMessage(JsonNode message) { - if (!delayEmitters.isEmpty() && message.has("data")) { - String table = ""; - if (message.has("table")) { - table = message.get("table").asText(); - } - JsonNode data = message.get("data"); - if (data.getNodeType().equals(JsonNodeType.ARRAY)) { - Long current = System.currentTimeMillis(); - SimpleDateFormat formatter; - formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - JsonNode d = data.get(0); - if (d != null && d.has("timestamp") && - (!"order".equals(table) || d.has("ordStatus") && "NEW".equals(d.get("ordStatus").asText()))) { - try { - String timestamp = d.get("timestamp").asText(); - Date date = formatter.parse(timestamp); - long delay = current - date.getTime(); - for (ObservableEmitter emitter : delayEmitters) { - emitter.onNext(delay); - } - } catch (ParseException e) { - LOG.error("Parsing timestamp error: ", e); - } - } - } - }if (message.has("info") || message.has("success")) { - return; - } - if (message.has("error")) { - String error = message.get("error").asText(); - LOG.error("Error with message: " + error); - return; - } - if (message.has("now") && message.has("cancelTime")) { - handleDeadMansSwitchMessage(message); - return; - - } - super.handleMessage(message); - } - - private void handleDeadMansSwitchMessage(JsonNode message) { - //handle dead man's switch confirmation - try { - String cancelTime = message.get("cancelTime").asText(); - if (cancelTime.equals("0")) { - LOG.info("Dead man's switch disabled"); - dmsDisposable.dispose(); - dmsDisposable = null; - dmsCancelTime=0; - } else { - SimpleDateFormat sdf = new SimpleDateFormat(BitmexMarketDataEvent.BITMEX_TIMESTAMP_FORMAT); - sdf.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); - long now = sdf.parse(message.get("now").asText()).getTime(); - dmsCancelTime = sdf.parse(cancelTime).getTime(); - } - } catch (ParseException e) { - LOG.error("Error parsing deadman's confirmation "); + } catch (ParseException e) { + LOG.error("Parsing timestamp error: ", e); + } } - return; + } } - - @Override - protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { - return null; + if (message.has("info") || message.has("success")) { + return; } - - public Observable subscribeBitmexChannel(String channelName) { - return subscribeChannel(channelName).map(s -> { - BitmexWebSocketTransaction transaction = objectMapper.treeToValue(s, BitmexWebSocketTransaction.class); - return transaction; - }) - .share(); + if (message.has("error")) { + String error = message.get("error").asText(); + LOG.error("Error with message: " + error); + return; } - - @Override - protected DefaultHttpHeaders getCustomHeaders() { - DefaultHttpHeaders customHeaders = super.getCustomHeaders(); - if (secretKey == null || apiKey == null) { - return customHeaders; - } - long expires = System.currentTimeMillis() / 1000 + 5; - - BitmexDigest bitmexDigester = BitmexDigest.createInstance(secretKey, apiKey); - String stringToDigest = "GET/realtime" + expires; - String signature = bitmexDigester.digestString(stringToDigest); - - customHeaders.add("api-nonce", expires); - customHeaders.add("api-key", apiKey); - customHeaders.add("api-signature", signature); - return customHeaders; + if (message.has("now") && message.has("cancelTime")) { + handleDeadMansSwitchMessage(message); + return; } - - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - String table = message.get("table").asText(); - if (table.equals("order") || table.equals("funding") || table.equals("position")) { - return table; - } - JsonNode data = message.get("data"); - String instrument = data.size() > 0 ? data.get(0).get("symbol").asText() : message.get("filter").get("symbol").asText(); - return String.format("%s:%s", table, instrument); + super.handleMessage(message); + } + + private void handleDeadMansSwitchMessage(JsonNode message) { + // handle dead man's switch confirmation + try { + String cancelTime = message.get("cancelTime").asText(); + if (cancelTime.equals("0")) { + LOG.info("Dead man's switch disabled"); + dmsDisposable.dispose(); + dmsDisposable = null; + dmsCancelTime = 0; + } else { + SimpleDateFormat sdf = new SimpleDateFormat(BitmexMarketDataEvent.BITMEX_TIMESTAMP_FORMAT); + sdf.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); + long now = sdf.parse(message.get("now").asText()).getTime(); + dmsCancelTime = sdf.parse(cancelTime).getTime(); + } + } catch (ParseException e) { + LOG.error("Error parsing deadman's confirmation "); } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - BitmexWebSocketSubscriptionMessage subscribeMessage = new BitmexWebSocketSubscriptionMessage("subscribe", new String[]{channelName}); - return objectMapper.writeValueAsString(subscribeMessage); + return; + } + + @Override + protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { + return null; + } + + public Observable subscribeBitmexChannel(String channelName) { + return subscribeChannel(channelName) + .map( + s -> { + BitmexWebSocketTransaction transaction = + objectMapper.treeToValue(s, BitmexWebSocketTransaction.class); + return transaction; + }) + .share(); + } + + @Override + protected DefaultHttpHeaders getCustomHeaders() { + DefaultHttpHeaders customHeaders = super.getCustomHeaders(); + if (secretKey == null || apiKey == null) { + return customHeaders; } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - BitmexWebSocketSubscriptionMessage subscribeMessage = new BitmexWebSocketSubscriptionMessage("unsubscribe", new String[]{channelName}); - return objectMapper.writeValueAsString(subscribeMessage); + long expires = System.currentTimeMillis() / 1000 + 5; + + BitmexDigest bitmexDigester = BitmexDigest.createInstance(secretKey, apiKey); + String stringToDigest = "GET/realtime" + expires; + String signature = bitmexDigester.digestString(stringToDigest); + + customHeaders.add("api-nonce", expires); + customHeaders.add("api-key", apiKey); + customHeaders.add("api-signature", signature); + return customHeaders; + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + String table = message.get("table").asText(); + if (table.equals("order") || table.equals("funding") || table.equals("position")) { + return table; } - - public void enableDeadMansSwitch(long rate, long timeout) throws IOException { - if (dmsDisposable != null) { - LOG.warn("You already have Dead Man's switch enabled. Doing nothing"); - return; - } - final BitmexWebSocketSubscriptionMessage subscriptionMessage = new BitmexWebSocketSubscriptionMessage("cancelAllAfter", new Object[]{timeout}); - String message = objectMapper.writeValueAsString(subscriptionMessage); - dmsDisposable = Schedulers.single().schedulePeriodicallyDirect(() -> sendMessage(message), 0, rate, TimeUnit.MILLISECONDS); - Schedulers.single().start(); - } - - public void disableDeadMansSwitch() throws IOException { - final BitmexWebSocketSubscriptionMessage subscriptionMessage = new BitmexWebSocketSubscriptionMessage("cancelAllAfter", new Object[]{0}); - String message = objectMapper.writeValueAsString(subscriptionMessage); - sendMessage(message); - } - - public boolean isDeadMansSwitchEnabled() { - return dmsCancelTime > 0 && System.currentTimeMillis() < dmsCancelTime; - } - - public void addDelayEmitter(ObservableEmitter delayEmitter) { - delayEmitters.add(delayEmitter); + JsonNode data = message.get("data"); + String instrument = + data.size() > 0 + ? data.get(0).get("symbol").asText() + : message.get("filter").get("symbol").asText(); + return String.format("%s:%s", table, instrument); + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + BitmexWebSocketSubscriptionMessage subscribeMessage = + new BitmexWebSocketSubscriptionMessage("subscribe", new String[] {channelName}); + return objectMapper.writeValueAsString(subscribeMessage); + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + BitmexWebSocketSubscriptionMessage subscribeMessage = + new BitmexWebSocketSubscriptionMessage("unsubscribe", new String[] {channelName}); + return objectMapper.writeValueAsString(subscribeMessage); + } + + public void enableDeadMansSwitch(long rate, long timeout) throws IOException { + if (dmsDisposable != null) { + LOG.warn("You already have Dead Man's switch enabled. Doing nothing"); + return; } + final BitmexWebSocketSubscriptionMessage subscriptionMessage = + new BitmexWebSocketSubscriptionMessage("cancelAllAfter", new Object[] {timeout}); + String message = objectMapper.writeValueAsString(subscriptionMessage); + dmsDisposable = + Schedulers.single() + .schedulePeriodicallyDirect(() -> sendMessage(message), 0, rate, TimeUnit.MILLISECONDS); + Schedulers.single().start(); + } + + public void disableDeadMansSwitch() throws IOException { + final BitmexWebSocketSubscriptionMessage subscriptionMessage = + new BitmexWebSocketSubscriptionMessage("cancelAllAfter", new Object[] {0}); + String message = objectMapper.writeValueAsString(subscriptionMessage); + sendMessage(message); + } + + public boolean isDeadMansSwitchEnabled() { + return dmsCancelTime > 0 && System.currentTimeMillis() < dmsCancelTime; + } + + public void addDelayEmitter(ObservableEmitter delayEmitter) { + delayEmitters.add(delayEmitter); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTradeService.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTradeService.java index 871583ad1..f601127b6 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTradeService.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTradeService.java @@ -1,36 +1,34 @@ package info.bitrich.xchangestream.bitmex; - import info.bitrich.xchangestream.bitmex.dto.BitmexOrder; import io.reactivex.Observable; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; - import java.util.Arrays; import java.util.stream.Collectors; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; - -/** - * Created by Declan - */ +/** Created by Declan */ public class BitmexStreamingTradeService { - private final BitmexStreamingService streamingService; - - public BitmexStreamingTradeService(BitmexStreamingService streamingService) { - this.streamingService = streamingService; - } - - public Observable getOrders(CurrencyPair currencyPair, Object... args) { - String channelName = "order"; - String instrument = currencyPair.base.toString() + currencyPair.counter.toString(); - return streamingService.subscribeBitmexChannel(channelName).flatMapIterable(s -> { - BitmexOrder[] bitmexOrders = s.toBitmexOrders(); - return Arrays.stream(bitmexOrders) - .filter(bitmexOrder -> bitmexOrder.getSymbol().equals(instrument)) - .filter(BitmexOrder::isNotWorkingIndicator) - .map(BitmexOrder::toOrder).collect(Collectors.toList()); - }); - } + private final BitmexStreamingService streamingService; + + public BitmexStreamingTradeService(BitmexStreamingService streamingService) { + this.streamingService = streamingService; + } + + public Observable getOrders(CurrencyPair currencyPair, Object... args) { + String channelName = "order"; + String instrument = currencyPair.base.toString() + currencyPair.counter.toString(); + return streamingService + .subscribeBitmexChannel(channelName) + .flatMapIterable( + s -> { + BitmexOrder[] bitmexOrders = s.toBitmexOrders(); + return Arrays.stream(bitmexOrders) + .filter(bitmexOrder -> bitmexOrder.getSymbol().equals(instrument)) + .filter(BitmexOrder::isNotWorkingIndicator) + .map(BitmexOrder::toOrder) + .collect(Collectors.toList()); + }); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java index 79ff0c182..8fb883047 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java @@ -2,380 +2,468 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.bitmex.dto.marketdata.BitmexPrivateOrder; -import org.knowm.xchange.bitmex.dto.trade.BitmexSide; - import java.math.BigDecimal; import java.util.Date; +import org.knowm.xchange.bitmex.dto.marketdata.BitmexPrivateOrder; +import org.knowm.xchange.bitmex.dto.trade.BitmexSide; -/** - * @author Nikita Belenkiy on 05/06/2018. - */ +/** @author Nikita Belenkiy on 05/06/2018. */ public class BitmexExecution { - protected String execID; - protected String orderID; - protected String clOrdID; - protected String clOrdLinkID; - protected long account; - protected String symbol; - protected BitmexSide side; - protected Long lastQty; - protected BigDecimal lastPx; - protected BigDecimal underlyingLastPx; - protected String lastMkt; - protected String lastLiquidityInd; - protected BigDecimal simpleOrderQty; - protected Long orderQty; - protected BigDecimal price; - protected Long displayQty; - protected BigDecimal stopPx; - protected BigDecimal pegOffsetValue; - protected String pegPriceType; - protected String currency; - protected String settlCurrency; - protected String execType; - protected String ordType; - protected String timeInForce; - protected String execInst; - protected String contingencyType; - protected String exDestination; - protected BitmexPrivateOrder.OrderStatus ordStatus; - protected String triggered; - protected Boolean workingIndicator; - protected String ordRejReason; - protected BigDecimal simpleLeavesQty; - protected Long leavesQty; - protected BigDecimal simpleCumQty; - protected BigDecimal cumQty; - protected BigDecimal avgPx; - protected BigDecimal commission; - protected String tradePublishIndicator; - protected String multiLegReportingType; - protected String text; - protected String trdMatchID; - protected Long execCost; - protected Long execComm; - protected BigDecimal homeNotional; - protected BigDecimal foreignNotional; - protected Date transactTime; - protected Date timestamp; - - @JsonCreator - public BitmexExecution(@JsonProperty("execID") String execID, @JsonProperty("orderID") String orderID, - @JsonProperty("clOrdID") String clOrdID, @JsonProperty("clOrdLinkID") String clOrdLinkID, - @JsonProperty("account") int account, @JsonProperty("symbol") String symbol, - @JsonProperty("side") BitmexSide side, - @JsonProperty("lastQty") Long lastQty, - @JsonProperty("lastPx") BigDecimal lastPx, @JsonProperty("underlyingLastPx") BigDecimal underlyingLastPx, - @JsonProperty("lastMkt") String lastMkt, @JsonProperty("lastLiquidityInd") String lastLiquidityInd, - @JsonProperty("simpleOrderQty") BigDecimal simpleOrderQty, @JsonProperty("orderQty") long orderQty, - @JsonProperty("price") BigDecimal price, @JsonProperty("displayQty") Long displayQty, @JsonProperty("stopPx") BigDecimal stopPx, - @JsonProperty("pegOffsetValue") BigDecimal pegOffsetValue, @JsonProperty("pegPriceType") String pegPriceType, - @JsonProperty("currency") String currency, @JsonProperty("settlCurrency") String settlCurrency, - @JsonProperty("execType") String execType, - @JsonProperty("ordType") String ordType, @JsonProperty("timeInForce") String timeInForce, - @JsonProperty("execInst") String execInst, @JsonProperty("contingencyType") String contingencyType, - @JsonProperty("exDestination") String exDestination, - @JsonProperty("ordStatus") BitmexPrivateOrder.OrderStatus ordStatus, @JsonProperty("triggered") String triggered, - @JsonProperty("workingIndicator") boolean workingIndicator, @JsonProperty("ordRejReason") String ordRejReason, - @JsonProperty("simpleLeavesQty") BigDecimal simpleLeavesQty, @JsonProperty("leavesQty") Long leavesQty, - @JsonProperty("simpleCumQty") BigDecimal simpleCumQty, @JsonProperty("cumQty") BigDecimal cumQty, - @JsonProperty("avgPx") BigDecimal avgPx, @JsonProperty("commission") BigDecimal commission, - @JsonProperty("tradePublishIndicator") String tradePublishIndicator, @JsonProperty("multiLegReportingType") String multiLegReportingType, - @JsonProperty("text") String text, @JsonProperty("trdMatchID") String trdMatchID, - @JsonProperty("execCost") Long execCost, @JsonProperty("execComm") Long execComm, - @JsonProperty("homeNotional") BigDecimal homeNotional, - @JsonProperty("foreignNotional") BigDecimal foreignNotional, @JsonProperty("transactTime") Date transactTime, - @JsonProperty("timestamp") Date timestamp) { - this.execID = execID; - this.orderID = orderID; - this.clOrdID = clOrdID; - this.clOrdLinkID = clOrdLinkID; - this.account = account; - this.symbol = symbol; - this.side = side; - this.lastQty = lastQty; - this.lastPx = lastPx; - this.underlyingLastPx = underlyingLastPx; - this.lastMkt = lastMkt; - this.lastLiquidityInd = lastLiquidityInd; - this.simpleOrderQty = simpleOrderQty; - this.orderQty = orderQty; - this.price = price; - this.displayQty = displayQty; - this.stopPx = stopPx; - this.pegOffsetValue = pegOffsetValue; - this.pegPriceType = pegPriceType; - this.currency = currency; - this.settlCurrency = settlCurrency; - this.execType = execType; - this.ordType = ordType; - this.timeInForce = timeInForce; - this.execInst = execInst; - this.contingencyType = contingencyType; - this.exDestination = exDestination; - this.ordStatus = ordStatus; - this.triggered = triggered; - this.workingIndicator = workingIndicator; - this.ordRejReason = ordRejReason; - this.simpleLeavesQty = simpleLeavesQty; - this.leavesQty = leavesQty; - this.simpleCumQty = simpleCumQty; - this.cumQty = cumQty; - this.avgPx = avgPx; - this.commission = commission; - this.tradePublishIndicator = tradePublishIndicator; - this.multiLegReportingType = multiLegReportingType; - this.text = text; - this.trdMatchID = trdMatchID; - this.execCost = execCost; - this.execComm = execComm; - this.homeNotional = homeNotional; - this.foreignNotional = foreignNotional; - this.transactTime = transactTime; - this.timestamp = timestamp; - } - - - public String getExecID() { - return execID; - } - - public String getOrderID() { - return orderID; - } - - public String getClOrdID() { - return clOrdID; - } - - public String getClOrdLinkID() { - return clOrdLinkID; - } - - public long getAccount() { - return account; - } - - public String getSymbol() { - return symbol; - } - - public BitmexSide getSide() { - return side; - } - - public Long getLastQty() { - return lastQty; - } - - public BigDecimal getLastPx() { - return lastPx; - } - - public BigDecimal getUnderlyingLastPx() { - return underlyingLastPx; - } - - public String getLastMkt() { - return lastMkt; - } - - public String getLastLiquidityInd() { - return lastLiquidityInd; - } - - public BigDecimal getSimpleOrderQty() { - return simpleOrderQty; - } - - public Long getOrderQty() { - return orderQty; - } - - public BigDecimal getPrice() { - return price; - } - - public Long getDisplayQty() { - return displayQty; - } - - public BigDecimal getStopPx() { - return stopPx; - } - - public BigDecimal getPegOffsetValue() { - return pegOffsetValue; - } - - public String getPegPriceType() { - return pegPriceType; - } - - public String getCurrency() { - return currency; - } - - public String getSettlCurrency() { - return settlCurrency; - } - - public String getExecType() { - return execType; - } - - public String getOrdType() { - return ordType; - } - - public String getTimeInForce() { - return timeInForce; - } - - public String getExecInst() { - return execInst; - } - - public String getContingencyType() { - return contingencyType; - } - - public String getExDestination() { - return exDestination; - } - - public BitmexPrivateOrder.OrderStatus getOrdStatus() { - return ordStatus; - } - - public String getTriggered() { - return triggered; - } - - public Boolean isWorkingIndicator() { - return workingIndicator; - } - - public String getOrdRejReason() { - return ordRejReason; - } - - public BigDecimal getSimpleLeavesQty() { - return simpleLeavesQty; - } - - public Long getLeavesQty() { - return leavesQty; - } - - public BigDecimal getSimpleCumQty() { - return simpleCumQty; - } - - public BigDecimal getCumQty() { - return cumQty; - } - - public BigDecimal getAvgPx() { - return avgPx; - } - - public BigDecimal getCommission() { - return commission; - } - - public String getTradePublishIndicator() { - return tradePublishIndicator; - } - - public String getMultiLegReportingType() { - return multiLegReportingType; - } - - public String getText() { - return text; - } - - public String getTrdMatchID() { - return trdMatchID; - } - - public Long getExecCost() { - return execCost; - } - - public Long getExecComm() { - return execComm; - } - - public BigDecimal getHomeNotional() { - return homeNotional; - } - - public BigDecimal getForeignNotional() { - return foreignNotional; - } - - public Date getTransactTime() { - return transactTime; - } - - public Date getTimestamp() { - return timestamp; - } - - @Override - public String toString() { - return "BitmexExecution{" + - "execID='" + execID + '\'' + - ", orderID='" + orderID + '\'' + - ", clOrdID='" + clOrdID + '\'' + - ", clOrdLinkID='" + clOrdLinkID + '\'' + - ", account=" + account + - ", symbol='" + symbol + '\'' + - ", side=" + side + - ", lastQty=" + lastQty + - ", lastPx=" + lastPx + - ", underlyingLastPx=" + underlyingLastPx + - ", lastMkt='" + lastMkt + '\'' + - ", lastLiquidityInd='" + lastLiquidityInd + '\'' + - ", simpleOrderQty=" + simpleOrderQty + - ", orderQty=" + orderQty + - ", price=" + price + - ", displayQty=" + displayQty + - ", stopPx=" + stopPx + - ", pegOffsetValue=" + pegOffsetValue + - ", pegPriceType='" + pegPriceType + '\'' + - ", currency='" + currency + '\'' + - ", settlCurrency='" + settlCurrency + '\'' + - ", execType='" + execType + '\'' + - ", ordType='" + ordType + '\'' + - ", timeInForce='" + timeInForce + '\'' + - ", execInst='" + execInst + '\'' + - ", contingencyType='" + contingencyType + '\'' + - ", exDestination='" + exDestination + '\'' + - ", ordStatus='" + ordStatus + '\'' + - ", triggered='" + triggered + '\'' + - ", workingIndicator=" + workingIndicator + - ", ordRejReason='" + ordRejReason + '\'' + - ", simpleLeavesQty=" + simpleLeavesQty + - ", leavesQty=" + leavesQty + - ", simpleCumQty=" + simpleCumQty + - ", cumQty=" + cumQty + - ", avgPx=" + avgPx + - ", commission=" + commission + - ", tradePublishIndicator='" + tradePublishIndicator + '\'' + - ", multiLegReportingType='" + multiLegReportingType + '\'' + - ", text='" + text + '\'' + - ", trdMatchID='" + trdMatchID + '\'' + - ", execCost=" + execCost + - ", execComm=" + execComm + - ", homeNotional=" + homeNotional + - ", foreignNotional=" + foreignNotional + - ", transactTime=" + transactTime + - ", timestamp=" + timestamp + - '}'; - } + protected String execID; + protected String orderID; + protected String clOrdID; + protected String clOrdLinkID; + protected long account; + protected String symbol; + protected BitmexSide side; + protected Long lastQty; + protected BigDecimal lastPx; + protected BigDecimal underlyingLastPx; + protected String lastMkt; + protected String lastLiquidityInd; + protected BigDecimal simpleOrderQty; + protected Long orderQty; + protected BigDecimal price; + protected Long displayQty; + protected BigDecimal stopPx; + protected BigDecimal pegOffsetValue; + protected String pegPriceType; + protected String currency; + protected String settlCurrency; + protected String execType; + protected String ordType; + protected String timeInForce; + protected String execInst; + protected String contingencyType; + protected String exDestination; + protected BitmexPrivateOrder.OrderStatus ordStatus; + protected String triggered; + protected Boolean workingIndicator; + protected String ordRejReason; + protected BigDecimal simpleLeavesQty; + protected Long leavesQty; + protected BigDecimal simpleCumQty; + protected BigDecimal cumQty; + protected BigDecimal avgPx; + protected BigDecimal commission; + protected String tradePublishIndicator; + protected String multiLegReportingType; + protected String text; + protected String trdMatchID; + protected Long execCost; + protected Long execComm; + protected BigDecimal homeNotional; + protected BigDecimal foreignNotional; + protected Date transactTime; + protected Date timestamp; + + @JsonCreator + public BitmexExecution( + @JsonProperty("execID") String execID, + @JsonProperty("orderID") String orderID, + @JsonProperty("clOrdID") String clOrdID, + @JsonProperty("clOrdLinkID") String clOrdLinkID, + @JsonProperty("account") int account, + @JsonProperty("symbol") String symbol, + @JsonProperty("side") BitmexSide side, + @JsonProperty("lastQty") Long lastQty, + @JsonProperty("lastPx") BigDecimal lastPx, + @JsonProperty("underlyingLastPx") BigDecimal underlyingLastPx, + @JsonProperty("lastMkt") String lastMkt, + @JsonProperty("lastLiquidityInd") String lastLiquidityInd, + @JsonProperty("simpleOrderQty") BigDecimal simpleOrderQty, + @JsonProperty("orderQty") long orderQty, + @JsonProperty("price") BigDecimal price, + @JsonProperty("displayQty") Long displayQty, + @JsonProperty("stopPx") BigDecimal stopPx, + @JsonProperty("pegOffsetValue") BigDecimal pegOffsetValue, + @JsonProperty("pegPriceType") String pegPriceType, + @JsonProperty("currency") String currency, + @JsonProperty("settlCurrency") String settlCurrency, + @JsonProperty("execType") String execType, + @JsonProperty("ordType") String ordType, + @JsonProperty("timeInForce") String timeInForce, + @JsonProperty("execInst") String execInst, + @JsonProperty("contingencyType") String contingencyType, + @JsonProperty("exDestination") String exDestination, + @JsonProperty("ordStatus") BitmexPrivateOrder.OrderStatus ordStatus, + @JsonProperty("triggered") String triggered, + @JsonProperty("workingIndicator") boolean workingIndicator, + @JsonProperty("ordRejReason") String ordRejReason, + @JsonProperty("simpleLeavesQty") BigDecimal simpleLeavesQty, + @JsonProperty("leavesQty") Long leavesQty, + @JsonProperty("simpleCumQty") BigDecimal simpleCumQty, + @JsonProperty("cumQty") BigDecimal cumQty, + @JsonProperty("avgPx") BigDecimal avgPx, + @JsonProperty("commission") BigDecimal commission, + @JsonProperty("tradePublishIndicator") String tradePublishIndicator, + @JsonProperty("multiLegReportingType") String multiLegReportingType, + @JsonProperty("text") String text, + @JsonProperty("trdMatchID") String trdMatchID, + @JsonProperty("execCost") Long execCost, + @JsonProperty("execComm") Long execComm, + @JsonProperty("homeNotional") BigDecimal homeNotional, + @JsonProperty("foreignNotional") BigDecimal foreignNotional, + @JsonProperty("transactTime") Date transactTime, + @JsonProperty("timestamp") Date timestamp) { + this.execID = execID; + this.orderID = orderID; + this.clOrdID = clOrdID; + this.clOrdLinkID = clOrdLinkID; + this.account = account; + this.symbol = symbol; + this.side = side; + this.lastQty = lastQty; + this.lastPx = lastPx; + this.underlyingLastPx = underlyingLastPx; + this.lastMkt = lastMkt; + this.lastLiquidityInd = lastLiquidityInd; + this.simpleOrderQty = simpleOrderQty; + this.orderQty = orderQty; + this.price = price; + this.displayQty = displayQty; + this.stopPx = stopPx; + this.pegOffsetValue = pegOffsetValue; + this.pegPriceType = pegPriceType; + this.currency = currency; + this.settlCurrency = settlCurrency; + this.execType = execType; + this.ordType = ordType; + this.timeInForce = timeInForce; + this.execInst = execInst; + this.contingencyType = contingencyType; + this.exDestination = exDestination; + this.ordStatus = ordStatus; + this.triggered = triggered; + this.workingIndicator = workingIndicator; + this.ordRejReason = ordRejReason; + this.simpleLeavesQty = simpleLeavesQty; + this.leavesQty = leavesQty; + this.simpleCumQty = simpleCumQty; + this.cumQty = cumQty; + this.avgPx = avgPx; + this.commission = commission; + this.tradePublishIndicator = tradePublishIndicator; + this.multiLegReportingType = multiLegReportingType; + this.text = text; + this.trdMatchID = trdMatchID; + this.execCost = execCost; + this.execComm = execComm; + this.homeNotional = homeNotional; + this.foreignNotional = foreignNotional; + this.transactTime = transactTime; + this.timestamp = timestamp; + } + + public String getExecID() { + return execID; + } + + public String getOrderID() { + return orderID; + } + + public String getClOrdID() { + return clOrdID; + } + + public String getClOrdLinkID() { + return clOrdLinkID; + } + + public long getAccount() { + return account; + } + + public String getSymbol() { + return symbol; + } + + public BitmexSide getSide() { + return side; + } + + public Long getLastQty() { + return lastQty; + } + + public BigDecimal getLastPx() { + return lastPx; + } + + public BigDecimal getUnderlyingLastPx() { + return underlyingLastPx; + } + + public String getLastMkt() { + return lastMkt; + } + + public String getLastLiquidityInd() { + return lastLiquidityInd; + } + + public BigDecimal getSimpleOrderQty() { + return simpleOrderQty; + } + + public Long getOrderQty() { + return orderQty; + } + + public BigDecimal getPrice() { + return price; + } + + public Long getDisplayQty() { + return displayQty; + } + + public BigDecimal getStopPx() { + return stopPx; + } + + public BigDecimal getPegOffsetValue() { + return pegOffsetValue; + } + + public String getPegPriceType() { + return pegPriceType; + } + + public String getCurrency() { + return currency; + } + + public String getSettlCurrency() { + return settlCurrency; + } + + public String getExecType() { + return execType; + } + + public String getOrdType() { + return ordType; + } + + public String getTimeInForce() { + return timeInForce; + } + + public String getExecInst() { + return execInst; + } + + public String getContingencyType() { + return contingencyType; + } + + public String getExDestination() { + return exDestination; + } + + public BitmexPrivateOrder.OrderStatus getOrdStatus() { + return ordStatus; + } + + public String getTriggered() { + return triggered; + } + + public Boolean isWorkingIndicator() { + return workingIndicator; + } + + public String getOrdRejReason() { + return ordRejReason; + } + + public BigDecimal getSimpleLeavesQty() { + return simpleLeavesQty; + } + + public Long getLeavesQty() { + return leavesQty; + } + + public BigDecimal getSimpleCumQty() { + return simpleCumQty; + } + + public BigDecimal getCumQty() { + return cumQty; + } + + public BigDecimal getAvgPx() { + return avgPx; + } + + public BigDecimal getCommission() { + return commission; + } + + public String getTradePublishIndicator() { + return tradePublishIndicator; + } + + public String getMultiLegReportingType() { + return multiLegReportingType; + } + + public String getText() { + return text; + } + + public String getTrdMatchID() { + return trdMatchID; + } + + public Long getExecCost() { + return execCost; + } + + public Long getExecComm() { + return execComm; + } + + public BigDecimal getHomeNotional() { + return homeNotional; + } + + public BigDecimal getForeignNotional() { + return foreignNotional; + } + + public Date getTransactTime() { + return transactTime; + } + + public Date getTimestamp() { + return timestamp; + } + + @Override + public String toString() { + return "BitmexExecution{" + + "execID='" + + execID + + '\'' + + ", orderID='" + + orderID + + '\'' + + ", clOrdID='" + + clOrdID + + '\'' + + ", clOrdLinkID='" + + clOrdLinkID + + '\'' + + ", account=" + + account + + ", symbol='" + + symbol + + '\'' + + ", side=" + + side + + ", lastQty=" + + lastQty + + ", lastPx=" + + lastPx + + ", underlyingLastPx=" + + underlyingLastPx + + ", lastMkt='" + + lastMkt + + '\'' + + ", lastLiquidityInd='" + + lastLiquidityInd + + '\'' + + ", simpleOrderQty=" + + simpleOrderQty + + ", orderQty=" + + orderQty + + ", price=" + + price + + ", displayQty=" + + displayQty + + ", stopPx=" + + stopPx + + ", pegOffsetValue=" + + pegOffsetValue + + ", pegPriceType='" + + pegPriceType + + '\'' + + ", currency='" + + currency + + '\'' + + ", settlCurrency='" + + settlCurrency + + '\'' + + ", execType='" + + execType + + '\'' + + ", ordType='" + + ordType + + '\'' + + ", timeInForce='" + + timeInForce + + '\'' + + ", execInst='" + + execInst + + '\'' + + ", contingencyType='" + + contingencyType + + '\'' + + ", exDestination='" + + exDestination + + '\'' + + ", ordStatus='" + + ordStatus + + '\'' + + ", triggered='" + + triggered + + '\'' + + ", workingIndicator=" + + workingIndicator + + ", ordRejReason='" + + ordRejReason + + '\'' + + ", simpleLeavesQty=" + + simpleLeavesQty + + ", leavesQty=" + + leavesQty + + ", simpleCumQty=" + + simpleCumQty + + ", cumQty=" + + cumQty + + ", avgPx=" + + avgPx + + ", commission=" + + commission + + ", tradePublishIndicator='" + + tradePublishIndicator + + '\'' + + ", multiLegReportingType='" + + multiLegReportingType + + '\'' + + ", text='" + + text + + '\'' + + ", trdMatchID='" + + trdMatchID + + '\'' + + ", execCost=" + + execCost + + ", execComm=" + + execComm + + ", homeNotional=" + + homeNotional + + ", foreignNotional=" + + foreignNotional + + ", transactTime=" + + transactTime + + ", timestamp=" + + timestamp + + '}'; + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexFunding.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexFunding.java index b5068f239..7dc385d58 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexFunding.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexFunding.java @@ -4,25 +4,25 @@ public class BitmexFunding extends BitmexMarketDataEvent { - private double fundingRate; - - private double fundingRateDaily; - - public BitmexFunding(@JsonProperty("symbol") String symbol, - @JsonProperty("timestamp") String timestamp, - @JsonProperty("fundingRate") double fundingRate, - @JsonProperty("fundingRateDaily") double fundingRateDaily) { - super(symbol, timestamp); - this.fundingRate = fundingRate; - this.fundingRateDaily = fundingRateDaily; - } - - public double getFundingRate() { - return fundingRate; - } - - public double getFundingRateDaily() { - return fundingRateDaily; - } - + private double fundingRate; + + private double fundingRateDaily; + + public BitmexFunding( + @JsonProperty("symbol") String symbol, + @JsonProperty("timestamp") String timestamp, + @JsonProperty("fundingRate") double fundingRate, + @JsonProperty("fundingRateDaily") double fundingRateDaily) { + super(symbol, timestamp); + this.fundingRate = fundingRate; + this.fundingRateDaily = fundingRateDaily; + } + + public double getFundingRate() { + return fundingRate; + } + + public double getFundingRateDaily() { + return fundingRateDaily; + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexLimitOrder.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexLimitOrder.java index 95c80076d..30303fdac 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexLimitOrder.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexLimitOrder.java @@ -2,73 +2,67 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.LimitOrder; -import java.math.BigDecimal; - -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexLimitOrder extends BitmexMarketDataEvent { - public static final String ASK_SIDE = "Sell"; - public static final String BID_SIDE = "Buy"; + public static final String ASK_SIDE = "Sell"; + public static final String BID_SIDE = "Buy"; - protected final String id; - protected final String side; - protected final BigDecimal price; - protected final BigDecimal size; + protected final String id; + protected final String side; + protected final BigDecimal price; + protected final BigDecimal size; - @JsonCreator - public BitmexLimitOrder(@JsonProperty("symbol") String symbol, - @JsonProperty("id") String id, - @JsonProperty("side") String side, - @JsonProperty("price") BigDecimal price, - @JsonProperty("size") BigDecimal size) { - super(symbol, null); - this.id = id; - this.side = side; - this.price = price; - this.size = size; - } + @JsonCreator + public BitmexLimitOrder( + @JsonProperty("symbol") String symbol, + @JsonProperty("id") String id, + @JsonProperty("side") String side, + @JsonProperty("price") BigDecimal price, + @JsonProperty("size") BigDecimal size) { + super(symbol, null); + this.id = id; + this.side = side; + this.price = price; + this.size = size; + } - public BitmexLimitOrder(String symbol, - String id, - String side, - BigDecimal price, - BigDecimal size, - String timestamp) { - super(symbol, timestamp); - this.id = id; - this.side = side; - this.price = price; - this.size = size; - } + public BitmexLimitOrder( + String symbol, String id, String side, BigDecimal price, BigDecimal size, String timestamp) { + super(symbol, timestamp); + this.id = id; + this.side = side; + this.price = price; + this.size = size; + } - public String getId() { - return id; - } + public String getId() { + return id; + } - public String getSide() { - return side; - } + public String getSide() { + return side; + } - public BigDecimal getPrice() { - return price; - } + public BigDecimal getPrice() { + return price; + } - public BigDecimal getSize() { - return size; - } + public BigDecimal getSize() { + return size; + } - public Order.OrderType getOrderSide() { - return side.equals(ASK_SIDE) ? Order.OrderType.ASK : Order.OrderType.BID; - } + public Order.OrderType getOrderSide() { + return side.equals(ASK_SIDE) ? Order.OrderType.ASK : Order.OrderType.BID; + } - public LimitOrder toLimitOrder() { - CurrencyPair pair = getCurrencyPair(); - Order.OrderType orderType = getOrderSide(); - return new LimitOrder(orderType, size, pair, id, null, price); - } + public LimitOrder toLimitOrder() { + CurrencyPair pair = getCurrencyPair(); + Order.OrderType orderType = getOrderSide(); + return new LimitOrder(orderType, size, pair, id, null, price); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexMarketDataEvent.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexMarketDataEvent.java index 00a00dfcf..bfe83b9bd 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexMarketDataEvent.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexMarketDataEvent.java @@ -1,49 +1,46 @@ package info.bitrich.xchangestream.bitmex.dto; -import org.knowm.xchange.currency.Currency; -import org.knowm.xchange.currency.CurrencyPair; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexMarketDataEvent { - public static final String BITMEX_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - protected String timestamp; - protected String symbol; - - public BitmexMarketDataEvent(String symbol, String timestamp) { - this.timestamp = timestamp; - this.symbol = symbol; - } - - public String getTimestamp() { - return timestamp; - } - - public String getSymbol() { - return symbol; - } - - public CurrencyPair getCurrencyPair() { - String base = symbol.substring(0, 3); - String counter = symbol.substring(3, 6); - return new CurrencyPair(Currency.getInstance(base), Currency.getInstance(counter)); - } - - public Date getDate() { - SimpleDateFormat formatter = new SimpleDateFormat(BITMEX_TIMESTAMP_FORMAT); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - Date date = null; - try { - date = formatter.parse(timestamp); - } catch (ParseException e) { - e.printStackTrace(); - } - return date; + public static final String BITMEX_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + protected String timestamp; + protected String symbol; + + public BitmexMarketDataEvent(String symbol, String timestamp) { + this.timestamp = timestamp; + this.symbol = symbol; + } + + public String getTimestamp() { + return timestamp; + } + + public String getSymbol() { + return symbol; + } + + public CurrencyPair getCurrencyPair() { + String base = symbol.substring(0, 3); + String counter = symbol.substring(3, 6); + return new CurrencyPair(Currency.getInstance(base), Currency.getInstance(counter)); + } + + public Date getDate() { + SimpleDateFormat formatter = new SimpleDateFormat(BITMEX_TIMESTAMP_FORMAT); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + Date date = null; + try { + date = formatter.parse(timestamp); + } catch (ParseException e) { + e.printStackTrace(); } + return date; + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrder.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrder.java index 9813630af..69db83c2d 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrder.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrder.java @@ -2,166 +2,168 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.dto.trade.MarketOrder; -import java.math.BigDecimal; - - public class BitmexOrder extends BitmexMarketDataEvent { - public enum OrderStatus { - NEW, - PARTIALLYFILLED, - FILLED, - TBD, - CANCELED, - REJECTED, - UNKNOW + public enum OrderStatus { + NEW, + PARTIALLYFILLED, + FILLED, + TBD, + CANCELED, + REJECTED, + UNKNOW + } + + private String orderID; + + private int account; + + private String side; + + private BigDecimal price; + + private BigDecimal avgPx; + + private String ordType; + + private OrderStatus ordStatus; + + private String clOrdID; + + private BigDecimal orderQty; + + private BigDecimal cumQty; + + public boolean isNotWorkingIndicator() { + return !workingIndicator; + } + + private boolean workingIndicator; + + @JsonCreator + public BitmexOrder( + @JsonProperty("symbol") String symbol, + @JsonProperty("timestamp") String timestamp, + @JsonProperty("orderID") String orderID, + @JsonProperty("account") int account, + @JsonProperty("side") String side, + @JsonProperty("price") BigDecimal price, + @JsonProperty("avgPx") BigDecimal avgPx, + @JsonProperty("ordType") String ordType, + @JsonProperty("ordStatus") String ordStatus, + @JsonProperty("clOrdID") String clOrdID, + @JsonProperty("orderQty") BigDecimal orderQty, + @JsonProperty("cumQty") BigDecimal cumQty, + @JsonProperty("workingIndicator") boolean workingIndicator) { + super(symbol, timestamp); + this.orderID = orderID; + this.account = account; + this.side = side; + this.price = price; + this.avgPx = avgPx; + this.ordType = ordType; + try { + this.ordStatus = OrderStatus.valueOf(ordStatus.toUpperCase()); + } catch (Exception e) { + this.ordStatus = OrderStatus.UNKNOW; } - - private String orderID; - - private int account; - - private String side; - - private BigDecimal price; - - private BigDecimal avgPx; - - private String ordType; - - private OrderStatus ordStatus; - - private String clOrdID; - - private BigDecimal orderQty; - - private BigDecimal cumQty; - - public boolean isNotWorkingIndicator() { - return !workingIndicator; + this.clOrdID = clOrdID; + this.orderQty = orderQty; + this.cumQty = cumQty; + this.workingIndicator = workingIndicator; + } + + public Order toOrder() { + Order.Builder order; + if (ordType.equals("Market")) { + order = + new MarketOrder.Builder( + side.equals("Buy") ? Order.OrderType.BID : Order.OrderType.ASK, + new CurrencyPair(symbol.substring(0, 3), symbol.substring(3, symbol.length()))); + } else { + order = + new LimitOrder.Builder( + side.equals("Buy") ? Order.OrderType.BID : Order.OrderType.ASK, + new CurrencyPair(symbol.substring(0, 3), symbol.substring(3, symbol.length()))); } - - private boolean workingIndicator; - - @JsonCreator - public BitmexOrder(@JsonProperty("symbol") String symbol, - @JsonProperty("timestamp") String timestamp, - @JsonProperty("orderID") String orderID, - @JsonProperty("account") int account, - @JsonProperty("side") String side, - @JsonProperty("price") BigDecimal price, - @JsonProperty("avgPx") BigDecimal avgPx, - @JsonProperty("ordType") String ordType, - @JsonProperty("ordStatus") String ordStatus, - @JsonProperty("clOrdID") String clOrdID, - @JsonProperty("orderQty") BigDecimal orderQty, - @JsonProperty("cumQty") BigDecimal cumQty, - @JsonProperty("workingIndicator") boolean workingIndicator) { - super(symbol, timestamp); - this.orderID = orderID; - this.account = account; - this.side = side; - this.price = price; - this.avgPx = avgPx; - this.ordType = ordType; - try { - this.ordStatus = OrderStatus.valueOf(ordStatus.toUpperCase()); - } catch (Exception e) { - this.ordStatus = OrderStatus.UNKNOW; - } - this.clOrdID = clOrdID; - this.orderQty = orderQty; - this.cumQty = cumQty; - this.workingIndicator = workingIndicator; + order.id(orderID).averagePrice(avgPx).originalAmount(orderQty).cumulativeAmount(cumQty); + + switch (ordStatus) { + case NEW: + order.orderStatus(Order.OrderStatus.NEW); + break; + case PARTIALLYFILLED: + order.orderStatus(Order.OrderStatus.PARTIALLY_FILLED); + break; + case FILLED: + order.orderStatus(Order.OrderStatus.FILLED); + break; + case TBD: + order.orderStatus(Order.OrderStatus.PENDING_CANCEL); + break; + case CANCELED: + order.orderStatus(Order.OrderStatus.CANCELED); + break; + case REJECTED: + order.orderStatus(Order.OrderStatus.REJECTED); + default: + order.orderStatus(Order.OrderStatus.UNKNOWN); + break; } - - public Order toOrder() { - Order.Builder order; - if (ordType.equals("Market")) { - order = new MarketOrder.Builder(side.equals("Buy") ? Order.OrderType.BID : Order.OrderType.ASK, new CurrencyPair(symbol.substring(0, 3), symbol.substring(3, symbol.length()))); - } else { - order = new LimitOrder.Builder(side.equals("Buy") ? Order.OrderType.BID : Order.OrderType.ASK, new CurrencyPair(symbol.substring(0, 3), symbol.substring(3, symbol.length()))); - } - order.id(orderID) - .averagePrice(avgPx) - .originalAmount(orderQty) - .cumulativeAmount(cumQty); - - switch (ordStatus) { - case NEW: - order.orderStatus(Order.OrderStatus.NEW); - break; - case PARTIALLYFILLED: - order.orderStatus(Order.OrderStatus.PARTIALLY_FILLED); - break; - case FILLED: - order.orderStatus(Order.OrderStatus.FILLED); - break; - case TBD: - order.orderStatus(Order.OrderStatus.PENDING_CANCEL); - break; - case CANCELED: - order.orderStatus(Order.OrderStatus.CANCELED); - break; - case REJECTED: - order.orderStatus(Order.OrderStatus.REJECTED); - default: - order.orderStatus(Order.OrderStatus.UNKNOWN); - break; - } - if (ordType.equals("Market")) { - return ((MarketOrder.Builder) order).build(); - } else { - return ((LimitOrder.Builder) order).build(); - } + if (ordType.equals("Market")) { + return ((MarketOrder.Builder) order).build(); + } else { + return ((LimitOrder.Builder) order).build(); } + } - public String getOrderID() { - return orderID; - } + public String getOrderID() { + return orderID; + } - public int getAccount() { - return account; - } + public int getAccount() { + return account; + } - public String getSide() { - return side; - } + public String getSide() { + return side; + } - public BigDecimal getPrice() { - return price; - } + public BigDecimal getPrice() { + return price; + } - public BigDecimal getAvgPx() { - return avgPx; - } + public BigDecimal getAvgPx() { + return avgPx; + } - public String getOrdType() { - return ordType; - } + public String getOrdType() { + return ordType; + } - public OrderStatus getOrdStatus() { - return ordStatus; - } + public OrderStatus getOrdStatus() { + return ordStatus; + } - public String getClOrdID() { - return clOrdID; - } + public String getClOrdID() { + return clOrdID; + } - public BigDecimal getOrderQty() { - return orderQty; - } + public BigDecimal getOrderQty() { + return orderQty; + } - public BigDecimal getCumQty() { - return cumQty; - } + public BigDecimal getCumQty() { + return cumQty; + } - public boolean isWorkingIndicator() { - return workingIndicator; - } + public boolean isWorkingIndicator() { + return workingIndicator; + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrderbook.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrderbook.java index 2722fc19a..eb48e9561 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrderbook.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexOrderbook.java @@ -1,101 +1,105 @@ package info.bitrich.xchangestream.bitmex.dto; -import org.knowm.xchange.dto.marketdata.OrderBook; -import org.knowm.xchange.dto.trade.LimitOrder; +import static info.bitrich.xchangestream.bitmex.dto.BitmexLimitOrder.ASK_SIDE; +import static info.bitrich.xchangestream.bitmex.dto.BitmexLimitOrder.BID_SIDE; import java.math.BigDecimal; import java.util.*; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.trade.LimitOrder; -import static info.bitrich.xchangestream.bitmex.dto.BitmexLimitOrder.ASK_SIDE; -import static info.bitrich.xchangestream.bitmex.dto.BitmexLimitOrder.BID_SIDE; - -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexOrderbook { - private SortedMap asks; - private SortedMap bids; - - private Map askIds; - private Map bidIds; - - public BitmexOrderbook() { - this.askIds = new HashMap<>(); - this.bidIds = new HashMap<>(); - this.asks = new TreeMap<>(); - this.bids = new TreeMap<>(java.util.Collections.reverseOrder()); + private SortedMap asks; + private SortedMap bids; + + private Map askIds; + private Map bidIds; + + public BitmexOrderbook() { + this.askIds = new HashMap<>(); + this.bidIds = new HashMap<>(); + this.asks = new TreeMap<>(); + this.bids = new TreeMap<>(java.util.Collections.reverseOrder()); + } + + public BitmexOrderbook(BitmexLimitOrder[] levels) { + this(); + createFromLevels(levels); + } + + public void createFromLevels(BitmexLimitOrder[] levels) { + for (BitmexLimitOrder level : levels) { + SortedMap orderBookSide = + level.getSide().equals(ASK_SIDE) ? asks : bids; + Map orderBookSideIds = level.getSide().equals(ASK_SIDE) ? askIds : bidIds; + orderBookSide.put(level.getPrice(), level); + orderBookSideIds.put(level.getId(), level.getPrice()); } + } - public BitmexOrderbook(BitmexLimitOrder[] levels) { - this(); - createFromLevels(levels); + public void updateLevels(BitmexLimitOrder[] levels, String action) { + for (BitmexLimitOrder level : levels) { + updateLevel(level, action); } - - public void createFromLevels(BitmexLimitOrder[] levels) { - for (BitmexLimitOrder level : levels) { - SortedMap orderBookSide = level.getSide().equals(ASK_SIDE) ? asks : bids; - Map orderBookSideIds = level.getSide().equals(ASK_SIDE) ? askIds : bidIds; - orderBookSide.put(level.getPrice(), level); - orderBookSideIds.put(level.getId(), level.getPrice()); - } + } + + public void updateLevel(BitmexLimitOrder level, String action) { + SortedMap orderBookSide = + level.getSide().equals(ASK_SIDE) ? asks : bids; + Map orderBookSideIds = level.getSide().equals(ASK_SIDE) ? askIds : bidIds; + + if (action.equals("insert")) { + orderBookSide.put(level.getPrice(), level); + orderBookSideIds.put(level.getId(), level.getPrice()); + } else if (action.equals("delete") || action.equals("update")) { + boolean shouldDelete = action.equals("delete"); + String id = level.getId(); + BigDecimal price = orderBookSideIds.get(id); + orderBookSide.remove(price); + orderBookSideIds.remove(id); + if (!shouldDelete) { + BitmexLimitOrder modifiedLevel = + new BitmexLimitOrder( + level.getSymbol(), + level.getId(), + level.getSide(), + price, + level.getSize()); // Original level doesn't have price! see bitmex doc + orderBookSide.put(price, modifiedLevel); + orderBookSideIds.put(id, price); + } } + } - public void updateLevels(BitmexLimitOrder[] levels, String action) { - for (BitmexLimitOrder level : levels) { - updateLevel(level, action); - } - } + public BitmexLimitOrder[] getLevels(String side) { + SortedMap orderBookSide = side.equals(ASK_SIDE) ? asks : bids; + return orderBookSide.values().toArray(new BitmexLimitOrder[orderBookSide.size()]); + } - public void updateLevel(BitmexLimitOrder level, String action) { - SortedMap orderBookSide = level.getSide().equals(ASK_SIDE) ? asks : bids; - Map orderBookSideIds = level.getSide().equals(ASK_SIDE) ? askIds : bidIds; - - if (action.equals("insert")) { - orderBookSide.put(level.getPrice(), level); - orderBookSideIds.put(level.getId(), level.getPrice()); - } else if (action.equals("delete") || action.equals("update")) { - boolean shouldDelete = action.equals("delete"); - String id = level.getId(); - BigDecimal price = orderBookSideIds.get(id); - orderBookSide.remove(price); - orderBookSideIds.remove(id); - if (!shouldDelete) { - BitmexLimitOrder modifiedLevel = new BitmexLimitOrder(level.getSymbol(), level.getId(), level.getSide(), price, - level.getSize()); // Original level doesn't have price! see bitmex doc - orderBookSide.put(price, modifiedLevel); - orderBookSideIds.put(id, price); - } - } - } + public BitmexLimitOrder[] getAsks() { + return getLevels(ASK_SIDE); + } - public BitmexLimitOrder[] getLevels(String side) { - SortedMap orderBookSide = side.equals(ASK_SIDE) ? asks : bids; - return orderBookSide.values().toArray(new BitmexLimitOrder[orderBookSide.size()]); - } + public BitmexLimitOrder[] getBids() { + return getLevels(BID_SIDE); + } - public BitmexLimitOrder[] getAsks() { - return getLevels(ASK_SIDE); - } + public static List toLimitOrders(BitmexLimitOrder[] levels) { + if (levels == null || levels.length == 0) return null; - public BitmexLimitOrder[] getBids() { - return getLevels(BID_SIDE); + List limitOrders = new ArrayList<>(levels.length); + for (BitmexLimitOrder level : levels) { + LimitOrder limitOrder = level.toLimitOrder(); + limitOrders.add(limitOrder); } - public static List toLimitOrders(BitmexLimitOrder[] levels) { - if (levels == null || levels.length == 0) return null; - - List limitOrders = new ArrayList<>(levels.length); - for (BitmexLimitOrder level : levels) { - LimitOrder limitOrder = level.toLimitOrder(); - limitOrders.add(limitOrder); - } + return limitOrders; + } - return limitOrders; - } - - public OrderBook toOrderbook() { - List orderbookAsks = toLimitOrders(getAsks()); - List orderbookBids = toLimitOrders(getBids()); - return new OrderBook(null, orderbookAsks, orderbookBids); - } + public OrderBook toOrderbook() { + List orderbookAsks = toLimitOrders(getAsks()); + List orderbookBids = toLimitOrders(getBids()); + return new OrderBook(null, orderbookAsks, orderbookBids); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTicker.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTicker.java index 1aff817bb..208be5a74 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTicker.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTicker.java @@ -1,65 +1,66 @@ package info.bitrich.xchangestream.bitmex.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.dto.marketdata.Ticker; - import java.math.BigDecimal; +import org.knowm.xchange.dto.marketdata.Ticker; -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexTicker extends BitmexMarketDataEvent { - private final String timestamp; - private final String symbol; - private final BigDecimal bidSize; - private final BigDecimal bidPrice; - private final BigDecimal askPrice; - private final BigDecimal askSize; + private final String timestamp; + private final String symbol; + private final BigDecimal bidSize; + private final BigDecimal bidPrice; + private final BigDecimal askPrice; + private final BigDecimal askSize; - public BitmexTicker(@JsonProperty("timestamp") String timestamp, - @JsonProperty("symbol") String symbol, - @JsonProperty("bidSize") BigDecimal bidSize, - @JsonProperty("bidPrice") BigDecimal bidPrice, - @JsonProperty("askPrice") BigDecimal askPrice, - @JsonProperty("askSize") BigDecimal askSize) { - super(symbol, timestamp); - this.timestamp = timestamp; - this.symbol = symbol; - this.bidSize = bidSize; - this.bidPrice = bidPrice; - this.askPrice = askPrice; - this.askSize = askSize; - } + public BitmexTicker( + @JsonProperty("timestamp") String timestamp, + @JsonProperty("symbol") String symbol, + @JsonProperty("bidSize") BigDecimal bidSize, + @JsonProperty("bidPrice") BigDecimal bidPrice, + @JsonProperty("askPrice") BigDecimal askPrice, + @JsonProperty("askSize") BigDecimal askSize) { + super(symbol, timestamp); + this.timestamp = timestamp; + this.symbol = symbol; + this.bidSize = bidSize; + this.bidPrice = bidPrice; + this.askPrice = askPrice; + this.askSize = askSize; + } - public String getTimestamp() { - return timestamp; - } + public String getTimestamp() { + return timestamp; + } - public String getSymbol() { - return symbol; - } + public String getSymbol() { + return symbol; + } - public BigDecimal getBidSize() { - return bidSize; - } + public BigDecimal getBidSize() { + return bidSize; + } - public BigDecimal getBidPrice() { - return bidPrice; - } + public BigDecimal getBidPrice() { + return bidPrice; + } - public BigDecimal getAskPrice() { - return askPrice; - } + public BigDecimal getAskPrice() { + return askPrice; + } - public BigDecimal getAskSize() { - return askSize; - } + public BigDecimal getAskSize() { + return askSize; + } - public Ticker toTicker() { - return new Ticker.Builder() - .ask(askPrice).bidSize(bidSize) - .bid(bidPrice).askSize(askSize) - .timestamp(getDate()).currencyPair(getCurrencyPair()) - .build(); - } + public Ticker toTicker() { + return new Ticker.Builder() + .ask(askPrice) + .bidSize(bidSize) + .bid(bidPrice) + .askSize(askSize) + .timestamp(getDate()) + .currencyPair(getCurrencyPair()) + .build(); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTrade.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTrade.java index 2f5b25b8b..3aad171e1 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTrade.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexTrade.java @@ -2,48 +2,46 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.marketdata.Trade; -import java.math.BigDecimal; - -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexTrade extends BitmexLimitOrder { - private String trdMatchID; - - @JsonCreator - public BitmexTrade(@JsonProperty("symbol") String symbol, - @JsonProperty("side") String side, - @JsonProperty("price") BigDecimal price, - @JsonProperty("size") BigDecimal size, - @JsonProperty("trdMatchID") String trdMatchID, - @JsonProperty("timestamp") String timestamp) { - super(symbol, "", side, price, size, timestamp); - this.trdMatchID = trdMatchID; - } - - public String getTimestamp() { - return timestamp; - } - - public String getTrdMatchID() { - return trdMatchID; - } - - public Trade toTrade() { - CurrencyPair pair = getCurrencyPair(); - Order.OrderType orderType = getOrderSide(); - return new Trade.Builder() - .type(orderType) - .originalAmount(size) - .currencyPair(pair) - .price(price) - .timestamp(getDate()) - .id(trdMatchID) - .build(); - } + private String trdMatchID; + + @JsonCreator + public BitmexTrade( + @JsonProperty("symbol") String symbol, + @JsonProperty("side") String side, + @JsonProperty("price") BigDecimal price, + @JsonProperty("size") BigDecimal size, + @JsonProperty("trdMatchID") String trdMatchID, + @JsonProperty("timestamp") String timestamp) { + super(symbol, "", side, price, size, timestamp); + this.trdMatchID = trdMatchID; + } + + public String getTimestamp() { + return timestamp; + } + + public String getTrdMatchID() { + return trdMatchID; + } + + public Trade toTrade() { + CurrencyPair pair = getCurrencyPair(); + Order.OrderType orderType = getOrderSide(); + return new Trade.Builder() + .type(orderType) + .originalAmount(size) + .currencyPair(pair) + .price(price) + .timestamp(getDate()) + .id(trdMatchID) + .build(); + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java index 6d82b4486..5069c0831 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java @@ -2,21 +2,19 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexWebSocketSubscriptionMessage { - private static final String OP = "op"; - private static final String ARGS = "args"; + private static final String OP = "op"; + private static final String ARGS = "args"; - @JsonProperty(OP) - private String op; + @JsonProperty(OP) + private String op; - @JsonProperty(ARGS) - private Object[] args; + @JsonProperty(ARGS) + private Object[] args; - public BitmexWebSocketSubscriptionMessage(String op, Object[] args) { - this.op = op; - this.args = args; - } + public BitmexWebSocketSubscriptionMessage(String op, Object[] args) { + this.op = op; + this.args = args; + } } diff --git a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java index a9e01d965..d9ad753c8 100644 --- a/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java +++ b/xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java @@ -4,105 +4,101 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; - import java.io.IOException; -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexWebSocketTransaction { - private static final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - private final String table; - private final String action; - private final JsonNode data; - - public BitmexWebSocketTransaction(@JsonProperty("table") String table, - @JsonProperty("action") String action, - @JsonProperty("data") JsonNode data) { - this.table = table; - this.action = action; - this.data = data; + private static final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + private final String table; + private final String action; + private final JsonNode data; + + public BitmexWebSocketTransaction( + @JsonProperty("table") String table, + @JsonProperty("action") String action, + @JsonProperty("data") JsonNode data) { + this.table = table; + this.action = action; + this.data = data; + } + + public BitmexLimitOrder[] toBitmexOrderbookLevels() { + BitmexLimitOrder[] levels = new BitmexLimitOrder[data.size()]; + for (int i = 0; i < data.size(); i++) { + JsonNode jsonLevel = data.get(i); + try { + levels[i] = mapper.treeToValue(jsonLevel, BitmexLimitOrder.class); + } catch (IOException e) { + e.printStackTrace(); + } } - public BitmexLimitOrder[] toBitmexOrderbookLevels() { - BitmexLimitOrder[] levels = new BitmexLimitOrder[data.size()]; - for (int i = 0; i < data.size(); i++) { - JsonNode jsonLevel = data.get(i); - try { - levels[i] = mapper.treeToValue(jsonLevel, BitmexLimitOrder.class); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return levels; - } + return levels; + } - public BitmexOrderbook toBitmexOrderbook() { - BitmexLimitOrder[] levels = toBitmexOrderbookLevels(); - return new BitmexOrderbook(levels); - } + public BitmexOrderbook toBitmexOrderbook() { + BitmexLimitOrder[] levels = toBitmexOrderbookLevels(); + return new BitmexOrderbook(levels); + } - public BitmexTicker toBitmexTicker() { - BitmexTicker bitmexTicker = null; - try { - bitmexTicker = mapper.treeToValue(data.get(0), BitmexTicker.class); - } catch (IOException e) { - e.printStackTrace(); - } - return bitmexTicker; + public BitmexTicker toBitmexTicker() { + BitmexTicker bitmexTicker = null; + try { + bitmexTicker = mapper.treeToValue(data.get(0), BitmexTicker.class); + } catch (IOException e) { + e.printStackTrace(); } - - public BitmexTrade[] toBitmexTrades() { - BitmexTrade[] trades = new BitmexTrade[data.size()]; - for (int i = 0; i < data.size(); i++) { - JsonNode jsonTrade = data.get(i); - try { - trades[i] = mapper.treeToValue(jsonTrade, BitmexTrade.class); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return trades; + return bitmexTicker; + } + + public BitmexTrade[] toBitmexTrades() { + BitmexTrade[] trades = new BitmexTrade[data.size()]; + for (int i = 0; i < data.size(); i++) { + JsonNode jsonTrade = data.get(i); + try { + trades[i] = mapper.treeToValue(jsonTrade, BitmexTrade.class); + } catch (IOException e) { + e.printStackTrace(); + } } - public BitmexOrder[] toBitmexOrders() { - BitmexOrder[] orders = new BitmexOrder[this.data.size()]; - for(int i = 0; i < this.data.size(); ++i) { - JsonNode jsonOrder = this.data.get(i); + return trades; + } - try { - orders[i] = (BitmexOrder) this.mapper.readValue(jsonOrder.toString(), BitmexOrder.class); - } catch (IOException var5) { - var5.printStackTrace(); - } - } + public BitmexOrder[] toBitmexOrders() { + BitmexOrder[] orders = new BitmexOrder[this.data.size()]; + for (int i = 0; i < this.data.size(); ++i) { + JsonNode jsonOrder = this.data.get(i); - return orders; + try { + orders[i] = (BitmexOrder) this.mapper.readValue(jsonOrder.toString(), BitmexOrder.class); + } catch (IOException var5) { + var5.printStackTrace(); + } } - public BitmexFunding toBitmexFunding() { - BitmexFunding funding = null; - try { - funding = this.mapper.readValue(this.data.get(0).toString(), BitmexFunding.class); - } catch (IOException var5) { - var5.printStackTrace(); - } - return funding; - } - - public String getTable() { - return table; - } + return orders; + } - public String getAction() { - return action; + public BitmexFunding toBitmexFunding() { + BitmexFunding funding = null; + try { + funding = this.mapper.readValue(this.data.get(0).toString(), BitmexFunding.class); + } catch (IOException var5) { + var5.printStackTrace(); } + return funding; + } - public JsonNode getData() { - return data; - } + public String getTable() { + return table; + } + public String getAction() { + return action; + } + public JsonNode getData() { + return data; + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java index b60073622..845cfb6c9 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java @@ -8,58 +8,65 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexAuthenticatedExample { - private static final Logger LOG = LoggerFactory.getLogger(BitmexAuthenticatedExample.class); + private static final Logger LOG = LoggerFactory.getLogger(BitmexAuthenticatedExample.class); - public static void main(String[] args) throws Exception { - CertHelper.trustAllCerts(); - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); - ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); + public static void main(String[] args) throws Exception { + CertHelper.trustAllCerts(); + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); + ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.USE_SANDBOX, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ACCEPT_ALL_CERITICATES, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.USE_SANDBOX, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ACCEPT_ALL_CERITICATES, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ENABLE_LOGGING_HANDLER, true); - defaultExchangeSpecification.setApiKey("API-KEY"); - defaultExchangeSpecification.setSecretKey("SECRET-KEY"); + defaultExchangeSpecification.setApiKey("API-KEY"); + defaultExchangeSpecification.setSecretKey("SECRET-KEY"); + exchange.applySpecification(defaultExchangeSpecification); + exchange.connect().blockingAwait(); + final BitmexStreamingMarketDataService streamingMarketDataService = + (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); + // streamingMarketDataService.authenticate(); + CurrencyPair xbtUsd = CurrencyPair.XBT_USD; + /* streamingMarketDataService.getOrderBook(xbtUsd).subscribe(orderBook -> { + if(!orderBook.getAsks().isEmpty()) + LOG.info("First ask: {}", orderBook.getAsks()); + if(!orderBook.getBids().isEmpty()) + LOG.info("First bid: {}", orderBook.getBids()); + }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); - exchange.applySpecification(defaultExchangeSpecification); - exchange.connect().blockingAwait(); - final BitmexStreamingMarketDataService streamingMarketDataService = (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); -// streamingMarketDataService.authenticate(); - CurrencyPair xbtUsd = CurrencyPair.XBT_USD; - /* streamingMarketDataService.getOrderBook(xbtUsd).subscribe(orderBook -> { - if(!orderBook.getAsks().isEmpty()) - LOG.info("First ask: {}", orderBook.getAsks()); - if(!orderBook.getBids().isEmpty()) - LOG.info("First bid: {}", orderBook.getBids()); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); + streamingMarketDataService.getRawTicker(xbtUsd).subscribe(ticker -> { + LOG.info("TICKER: {}", ticker); + }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - streamingMarketDataService.getRawTicker(xbtUsd).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - streamingMarketDataService.getTicker(xbtUsd).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); -*/ - /* streamingMarketDataService.getTrades(xbtUsd) - .subscribe(trade -> LOG.info("TRADE: {}", trade), - throwable -> LOG.error("ERROR in getting trades: ", throwable));*/ - streamingMarketDataService.getRawExecutions("XBTUSD").subscribe(bitmexExecution -> { - LOG.info("bitmexExecution = {}", bitmexExecution); - }); - try { - Thread.sleep(100_000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + streamingMarketDataService.getTicker(xbtUsd).subscribe(ticker -> { + LOG.info("TICKER: {}", ticker); + }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + */ + /* streamingMarketDataService.getTrades(xbtUsd) + .subscribe(trade -> LOG.info("TRADE: {}", trade), + throwable -> LOG.error("ERROR in getting trades: ", throwable));*/ + streamingMarketDataService + .getRawExecutions("XBTUSD") + .subscribe( + bitmexExecution -> { + LOG.info("bitmexExecution = {}", bitmexExecution); + }); + try { + Thread.sleep(100_000); + } catch (InterruptedException e) { + e.printStackTrace(); } + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java index 1510fcb6b..fbd21a53c 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java @@ -1,6 +1,9 @@ package info.bitrich.xchangestream.bitmex; +import static org.knowm.xchange.bitmex.BitmexPrompt.PERPETUAL; + import info.bitrich.xchangestream.core.StreamingExchange; +import java.math.BigDecimal; import org.junit.Ignore; import org.junit.Test; import org.knowm.xchange.ExchangeFactory; @@ -15,77 +18,89 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.math.BigDecimal; - -import static org.knowm.xchange.bitmex.BitmexPrompt.PERPETUAL; - -/** - * @author Nikita Belenkiy on 18/05/2018. - */ +/** @author Nikita Belenkiy on 18/05/2018. */ public class BitmexDeadManSwitchTest { - private static final Logger logger = LoggerFactory.getLogger(BitmexDeadManSwitchTest.class); - - @Test - @Ignore - public void testDeadmanSwitch() throws Exception { - CertHelper.trustAllCerts(); - BitmexStreamingExchange exchange = - (BitmexStreamingExchange) ExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class); - ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); - - defaultExchangeSpecification.setExchangeSpecificParametersItem("Use_Sandbox", true); - - defaultExchangeSpecification.setApiKey("QW8Ao_gx38e-8KFvDkFn-Ym4"); - defaultExchangeSpecification.setSecretKey("tn7rpzvOXSKThZD0f-xXehtydt4OTHZVf42gCCyxPixiiVOb"); - -// defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); -// defaultExchangeSpecification.setProxyHost("localhost"); -// defaultExchangeSpecification.setProxyPort(9999); - -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); - - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.USE_SANDBOX, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ACCEPT_ALL_CERITICATES, true); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); - - exchange.applySpecification(defaultExchangeSpecification); - exchange.connect().blockingAwait(); - BitmexMarketDataService marketDataService = - (BitmexMarketDataService) exchange.getMarketDataService(); - - BitmexTradeService tradeService = (BitmexTradeService)exchange.getTradeService(); - - final BitmexStreamingMarketDataService streamingMarketDataService = (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); -// streamingMarketDataService.authenticate(); - CurrencyPair xbtUsd = exchange.determineActiveContract(CurrencyPair.XBT_USD.base.toString(), CurrencyPair.XBT_USD.counter.toString(), PERPETUAL); - - streamingMarketDataService.getRawExecutions("XBTUSD").subscribe(bitmexExecution -> { - logger.info("!!!!EXECUTION!!!! = {}", bitmexExecution); - }); - - OrderBook orderBook = marketDataService.getOrderBook(xbtUsd); - // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.ADA, - // Currency.BTC), BitmexPrompt.QUARTERLY); - // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.BTC, - // Currency.USD), BitmexPrompt.BIQUARTERLY); - - System.out.println("orderBook = " + orderBook); - - streamingMarketDataService.enableDeadManSwitch(10000,30000); - - String nosOrdId = System.currentTimeMillis() + ""; - BigDecimal originalOrderSize = new BigDecimal("300"); - // BigDecimal price = new BigDecimal("10000"); - BigDecimal price = orderBook.getBids().get(0).getLimitPrice().add(new BigDecimal("100")); - LimitOrder limitOrder = new LimitOrder.Builder(Order.OrderType.ASK, CurrencyPair.XBT_USD).originalAmount(originalOrderSize).limitPrice(price).id(nosOrdId).build(); - String xbtusd = tradeService.placeLimitOrder(limitOrder); - logger.info("!!!!!PRIVATE_ORDER!!!! {}",xbtusd); - Thread.sleep(100000); - System.out.println(); - System.out.println(); - - - exchange.disconnect(); - } + private static final Logger logger = LoggerFactory.getLogger(BitmexDeadManSwitchTest.class); + + @Test + @Ignore + public void testDeadmanSwitch() throws Exception { + CertHelper.trustAllCerts(); + BitmexStreamingExchange exchange = + (BitmexStreamingExchange) + ExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class); + ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); + + defaultExchangeSpecification.setExchangeSpecificParametersItem("Use_Sandbox", true); + + defaultExchangeSpecification.setApiKey("QW8Ao_gx38e-8KFvDkFn-Ym4"); + defaultExchangeSpecification.setSecretKey("tn7rpzvOXSKThZD0f-xXehtydt4OTHZVf42gCCyxPixiiVOb"); + + // defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); + // defaultExchangeSpecification.setProxyHost("localhost"); + // defaultExchangeSpecification.setProxyPort(9999); + + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); + + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.USE_SANDBOX, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ACCEPT_ALL_CERITICATES, true); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); + + exchange.applySpecification(defaultExchangeSpecification); + exchange.connect().blockingAwait(); + BitmexMarketDataService marketDataService = + (BitmexMarketDataService) exchange.getMarketDataService(); + + BitmexTradeService tradeService = (BitmexTradeService) exchange.getTradeService(); + + final BitmexStreamingMarketDataService streamingMarketDataService = + (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); + // streamingMarketDataService.authenticate(); + CurrencyPair xbtUsd = + exchange.determineActiveContract( + CurrencyPair.XBT_USD.base.toString(), + CurrencyPair.XBT_USD.counter.toString(), + PERPETUAL); + + streamingMarketDataService + .getRawExecutions("XBTUSD") + .subscribe( + bitmexExecution -> { + logger.info("!!!!EXECUTION!!!! = {}", bitmexExecution); + }); + + OrderBook orderBook = marketDataService.getOrderBook(xbtUsd); + // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.ADA, + // Currency.BTC), BitmexPrompt.QUARTERLY); + // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.BTC, + // Currency.USD), BitmexPrompt.BIQUARTERLY); + + System.out.println("orderBook = " + orderBook); + + streamingMarketDataService.enableDeadManSwitch(10000, 30000); + + String nosOrdId = System.currentTimeMillis() + ""; + BigDecimal originalOrderSize = new BigDecimal("300"); + // BigDecimal price = new BigDecimal("10000"); + BigDecimal price = orderBook.getBids().get(0).getLimitPrice().add(new BigDecimal("100")); + LimitOrder limitOrder = + new LimitOrder.Builder(Order.OrderType.ASK, CurrencyPair.XBT_USD) + .originalAmount(originalOrderSize) + .limitPrice(price) + .id(nosOrdId) + .build(); + String xbtusd = tradeService.placeLimitOrder(limitOrder); + logger.info("!!!!!PRIVATE_ORDER!!!! {}", xbtusd); + Thread.sleep(100000); + System.out.println(); + System.out.println(); + + exchange.disconnect(); + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java index 79ad792ec..6813fc2c3 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java @@ -1,70 +1,102 @@ package info.bitrich.xchangestream.bitmex; -import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingExchangeFactory; import org.knowm.xchange.bitmex.BitmexPrompt; import org.knowm.xchange.currency.CurrencyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 13.11.17. - */ +/** Created by Lukas Zaoralek on 13.11.17. */ public class BitmexManualExample { - private static final Logger LOG = LoggerFactory.getLogger(BitmexManualExample.class); + private static final Logger LOG = LoggerFactory.getLogger(BitmexManualExample.class); - public static void main(String[] args) { - BitmexStreamingExchange exchange = (BitmexStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); + public static void main(String[] args) { + BitmexStreamingExchange exchange = + (BitmexStreamingExchange) + StreamingExchangeFactory.INSTANCE.createExchange( + BitmexStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); - exchange.messageDelay().subscribe(delay -> LOG.info("Message delay: " + delay)); + exchange.messageDelay().subscribe(delay -> LOG.info("Message delay: " + delay)); - final BitmexStreamingMarketDataService streamingMarketDataService = (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); + final BitmexStreamingMarketDataService streamingMarketDataService = + (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); - CurrencyPair xbtUsd = CurrencyPair.XBT_USD; - streamingMarketDataService.getOrderBook(xbtUsd).subscribe(orderBook -> { - if(!orderBook.getAsks().isEmpty()) { + CurrencyPair xbtUsd = CurrencyPair.XBT_USD; + streamingMarketDataService + .getOrderBook(xbtUsd) + .subscribe( + orderBook -> { + if (!orderBook.getAsks().isEmpty()) { LOG.info("First ask: {}", orderBook.getAsks()); - } - if(!orderBook.getBids().isEmpty()) { + } + if (!orderBook.getBids().isEmpty()) { LOG.info("First bid: {}", orderBook.getBids()); - } - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); + } + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); - streamingMarketDataService.getRawTicker(xbtUsd).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + streamingMarketDataService + .getRawTicker(xbtUsd) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - streamingMarketDataService.getTicker(xbtUsd).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + streamingMarketDataService + .getTicker(xbtUsd) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - exchange.getStreamingMarketDataService().getTrades(xbtUsd) - .subscribe(trade -> LOG.info("TRADE: {}", trade), - throwable -> LOG.error("ERROR in getting trades: ", throwable)); + exchange + .getStreamingMarketDataService() + .getTrades(xbtUsd) + .subscribe( + trade -> LOG.info("TRADE: {}", trade), + throwable -> LOG.error("ERROR in getting trades: ", throwable)); - // BIQUARTERLY Contract - CurrencyPair xbtUsdBiquarterly = exchange.determineActiveContract(CurrencyPair.XBT_USD.base.toString(), CurrencyPair.XBT_USD.counter.toString(), BitmexPrompt.BIQUARTERLY); - streamingMarketDataService.getOrderBook(xbtUsdBiquarterly).subscribe(orderBook -> { - LOG.info("BIQUARTERLY Contract First ask: {}", orderBook.getAsks().get(0)); - LOG.info("BIQUARTERLY Contract First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting BIQUARTERLY Contract order book: ", throwable)); + // BIQUARTERLY Contract + CurrencyPair xbtUsdBiquarterly = + exchange.determineActiveContract( + CurrencyPair.XBT_USD.base.toString(), + CurrencyPair.XBT_USD.counter.toString(), + BitmexPrompt.BIQUARTERLY); + streamingMarketDataService + .getOrderBook(xbtUsdBiquarterly) + .subscribe( + orderBook -> { + LOG.info("BIQUARTERLY Contract First ask: {}", orderBook.getAsks().get(0)); + LOG.info("BIQUARTERLY Contract First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> + LOG.error("ERROR in getting BIQUARTERLY Contract order book: ", throwable)); - streamingMarketDataService.getTicker(xbtUsdBiquarterly).subscribe(ticker -> { - LOG.info("BIQUARTERLY Contract TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting BIQUARTERLY Contract ticker: ", throwable)); + streamingMarketDataService + .getTicker(xbtUsdBiquarterly) + .subscribe( + ticker -> { + LOG.info("BIQUARTERLY Contract TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting BIQUARTERLY Contract ticker: ", throwable)); - exchange.getStreamingMarketDataService().getTrades(xbtUsdBiquarterly) - .subscribe(trade -> LOG.info("BIQUARTERLY Contract TRADE: {}", trade), - throwable -> LOG.error("ERROR in getting BIQUARTERLY Contract trades: ", throwable)); + exchange + .getStreamingMarketDataService() + .getTrades(xbtUsdBiquarterly) + .subscribe( + trade -> LOG.info("BIQUARTERLY Contract TRADE: {}", trade), + throwable -> LOG.error("ERROR in getting BIQUARTERLY Contract trades: ", throwable)); - try { - Thread.sleep(100000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - exchange.disconnect().blockingAwait(); + try { + Thread.sleep(100000); + } catch (InterruptedException e) { + e.printStackTrace(); } + + exchange.disconnect().blockingAwait(); + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java index 6e282e805..b9d0ea589 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java @@ -1,10 +1,20 @@ package info.bitrich.xchangestream.bitmex; +import static org.knowm.xchange.bitmex.BitmexPrompt.PERPETUAL; + import info.bitrich.xchangestream.bitmex.dto.BitmexExecution; import info.bitrich.xchangestream.core.StreamingExchangeFactory; import info.bitrich.xchangestream.util.LocalExchangeConfig; import info.bitrich.xchangestream.util.PropsLoader; import io.reactivex.Observable; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -21,198 +31,200 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import static org.knowm.xchange.bitmex.BitmexPrompt.PERPETUAL; - -/** - * @author Nikita Belenkiy on 18/05/2018. - */ +/** @author Nikita Belenkiy on 18/05/2018. */ public class BitmexOrderIT { - private CurrencyPair xbtUsd = CurrencyPair.XBT_USD; - private static final Logger LOG = LoggerFactory.getLogger(BitmexOrderIT.class); - - private static final BigDecimal priceShift = new BigDecimal("50"); - - private BigDecimal testAskPrice; - private BigDecimal testBidPrice; - - private BitmexTradeService tradeService; - private BitmexStreamingExchange exchange; - - @Before - public void setup() throws IOException { - LocalExchangeConfig localConfig = PropsLoader.loadKeys( - "bitmex.secret.keys", "bitmex.secret.keys.origin", "bitmex"); - exchange = (BitmexStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); - - exchange.applySpecification(BitmexTestsCommons.getExchangeSpecification(localConfig, - exchange.getDefaultExchangeSpecification())); - exchange.connect().blockingAwait(); - - xbtUsd = exchange.determineActiveContract(CurrencyPair.XBT_USD.base.toString(), CurrencyPair.XBT_USD.counter.toString(), PERPETUAL); - - BitmexMarketDataService marketDataService = - (BitmexMarketDataService) exchange.getMarketDataService(); - - OrderBook orderBook = marketDataService.getOrderBook(xbtUsd); - List asks = orderBook.getAsks(); - // todo : for the streaming service best ask is at 0 pos - BigDecimal topPriceAsk = getPrice(asks, asks.size() - 1); - BigDecimal topPriceBid = getPrice(orderBook.getBids(), 0); - - LOG.info("Got best ask = {}, best bid = {}", topPriceAsk, topPriceBid); - Assert.assertTrue("Got empty order book", topPriceAsk != null || topPriceBid != null); - - testAskPrice = topPriceAsk; - testBidPrice = topPriceBid; - - tradeService = (BitmexTradeService) exchange.getTradeService(); - } - - @After - public void tearDown() { - exchange.disconnect().blockingAwait(); - } - - private BigDecimal getPrice(List side, int pos) { - if (!side.isEmpty()) { - return side.get(pos).getLimitPrice(); - } - return null; - } - - private String generateOrderId() { - return System.currentTimeMillis() + ""; - } - - private String placeLimitOrder(String clOrdId, BigDecimal price, String size, Order.OrderType type) throws Exception { - LimitOrder limitOrder = - new LimitOrder( - type, - new BigDecimal(size), - xbtUsd, - clOrdId, - new Date(), - price); - String orderId = tradeService.placeLimitOrder(limitOrder); - LOG.info("Order was placed with id = {}", orderId); - return orderId; - } - - private BitmexPrivateOrder cancelLimitOrder(String clOrdId) { - List bitmexPrivateOrders = - tradeService.cancelBitmexOrder(null, clOrdId); - Assert.assertEquals(1, bitmexPrivateOrders.size()); - BitmexPrivateOrder order = bitmexPrivateOrders.get(0); - Assert.assertEquals(BitmexPrivateOrder.OrderStatus.Canceled, order.getOrderStatus()); - LOG.info("Order was cancelled = {}", order); - return order; - } - - private void checkPrivateOrder(String orderId, BigDecimal price, String size, BitmexSide side, - BitmexPrivateOrder bitmexPrivateOrder) { - Assert.assertEquals(orderId, bitmexPrivateOrder.getId()); - Assert.assertEquals(price, bitmexPrivateOrder.getPrice()); - Assert.assertEquals(size, bitmexPrivateOrder.getVolume().toString()); - Assert.assertEquals(side, bitmexPrivateOrder.getSide()); - } - - @Test - public void shouldPlaceLimitOrder() throws Exception { - final String clOrdId = generateOrderId(); - String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); - Assert.assertNotNull(orderId); - cancelLimitOrder(clOrdId); + private CurrencyPair xbtUsd = CurrencyPair.XBT_USD; + private static final Logger LOG = LoggerFactory.getLogger(BitmexOrderIT.class); + + private static final BigDecimal priceShift = new BigDecimal("50"); + + private BigDecimal testAskPrice; + private BigDecimal testBidPrice; + + private BitmexTradeService tradeService; + private BitmexStreamingExchange exchange; + + @Before + public void setup() throws IOException { + LocalExchangeConfig localConfig = + PropsLoader.loadKeys("bitmex.secret.keys", "bitmex.secret.keys.origin", "bitmex"); + exchange = + (BitmexStreamingExchange) + StreamingExchangeFactory.INSTANCE.createExchange( + BitmexStreamingExchange.class.getName()); + + exchange.applySpecification( + BitmexTestsCommons.getExchangeSpecification( + localConfig, exchange.getDefaultExchangeSpecification())); + exchange.connect().blockingAwait(); + + xbtUsd = + exchange.determineActiveContract( + CurrencyPair.XBT_USD.base.toString(), + CurrencyPair.XBT_USD.counter.toString(), + PERPETUAL); + + BitmexMarketDataService marketDataService = + (BitmexMarketDataService) exchange.getMarketDataService(); + + OrderBook orderBook = marketDataService.getOrderBook(xbtUsd); + List asks = orderBook.getAsks(); + // todo : for the streaming service best ask is at 0 pos + BigDecimal topPriceAsk = getPrice(asks, asks.size() - 1); + BigDecimal topPriceBid = getPrice(orderBook.getBids(), 0); + + LOG.info("Got best ask = {}, best bid = {}", topPriceAsk, topPriceBid); + Assert.assertTrue("Got empty order book", topPriceAsk != null || topPriceBid != null); + + testAskPrice = topPriceAsk; + testBidPrice = topPriceBid; + + tradeService = (BitmexTradeService) exchange.getTradeService(); + } + + @After + public void tearDown() { + exchange.disconnect().blockingAwait(); + } + + private BigDecimal getPrice(List side, int pos) { + if (!side.isEmpty()) { + return side.get(pos).getLimitPrice(); } - - @Test - public void shouldCancelOrder() throws Exception { - final String clOrdId = generateOrderId(); - String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); - BitmexPrivateOrder bitmexPrivateOrder = cancelLimitOrder(clOrdId); - - checkPrivateOrder(orderId, testAskPrice, "10", BitmexSide.SELL, bitmexPrivateOrder); - } - - @Test - public void shouldReplaceOrder() throws Exception { - final String clOrdId = generateOrderId(); - String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); - - final String replaceId = clOrdId + "replace"; - BitmexReplaceOrderParameters params = new BitmexReplaceOrderParameters.Builder() - .setOrderQuantity(new BigDecimal("5")) - .setOrderId(orderId) - .setOrigClOrdId(clOrdId) - .setClOrdId(replaceId) - .build(); - BitmexPrivateOrder bitmexPrivateOrder = - tradeService.replaceOrder(params); - LOG.info("Order was replaced = {}", bitmexPrivateOrder); - - checkPrivateOrder(orderId, testAskPrice, "5", BitmexSide.SELL, bitmexPrivateOrder); - cancelLimitOrder(replaceId); - } - - @Test - public void shouldCancelAllOrders() throws Exception { - final String clOrdId = generateOrderId(); - String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); - final String clOrdId2 = generateOrderId(); - String orderId2 = placeLimitOrder(clOrdId2, testBidPrice, "5", Order.OrderType.BID); - - List bitmexPrivateOrders = tradeService.cancelAllOrders(); - Assert.assertEquals(2, bitmexPrivateOrders.size()); - - checkPrivateOrder(orderId, testAskPrice, "10", BitmexSide.SELL, bitmexPrivateOrders.get(0)); - checkPrivateOrder(orderId2, testBidPrice, "5", BitmexSide.BUY, bitmexPrivateOrders.get(1)); - } - - @Test - public void shouldFillPlacedOrder() throws Exception { - final String clOrdId = generateOrderId(); - String orderId = placeLimitOrder(clOrdId, + return null; + } + + private String generateOrderId() { + return System.currentTimeMillis() + ""; + } + + private String placeLimitOrder( + String clOrdId, BigDecimal price, String size, Order.OrderType type) throws Exception { + LimitOrder limitOrder = + new LimitOrder(type, new BigDecimal(size), xbtUsd, clOrdId, new Date(), price); + String orderId = tradeService.placeLimitOrder(limitOrder); + LOG.info("Order was placed with id = {}", orderId); + return orderId; + } + + private BitmexPrivateOrder cancelLimitOrder(String clOrdId) { + List bitmexPrivateOrders = tradeService.cancelBitmexOrder(null, clOrdId); + Assert.assertEquals(1, bitmexPrivateOrders.size()); + BitmexPrivateOrder order = bitmexPrivateOrders.get(0); + Assert.assertEquals(BitmexPrivateOrder.OrderStatus.Canceled, order.getOrderStatus()); + LOG.info("Order was cancelled = {}", order); + return order; + } + + private void checkPrivateOrder( + String orderId, + BigDecimal price, + String size, + BitmexSide side, + BitmexPrivateOrder bitmexPrivateOrder) { + Assert.assertEquals(orderId, bitmexPrivateOrder.getId()); + Assert.assertEquals(price, bitmexPrivateOrder.getPrice()); + Assert.assertEquals(size, bitmexPrivateOrder.getVolume().toString()); + Assert.assertEquals(side, bitmexPrivateOrder.getSide()); + } + + @Test + public void shouldPlaceLimitOrder() throws Exception { + final String clOrdId = generateOrderId(); + String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); + Assert.assertNotNull(orderId); + cancelLimitOrder(clOrdId); + } + + @Test + public void shouldCancelOrder() throws Exception { + final String clOrdId = generateOrderId(); + String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); + BitmexPrivateOrder bitmexPrivateOrder = cancelLimitOrder(clOrdId); + + checkPrivateOrder(orderId, testAskPrice, "10", BitmexSide.SELL, bitmexPrivateOrder); + } + + @Test + public void shouldReplaceOrder() throws Exception { + final String clOrdId = generateOrderId(); + String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); + + final String replaceId = clOrdId + "replace"; + BitmexReplaceOrderParameters params = + new BitmexReplaceOrderParameters.Builder() + .setOrderQuantity(new BigDecimal("5")) + .setOrderId(orderId) + .setOrigClOrdId(clOrdId) + .setClOrdId(replaceId) + .build(); + BitmexPrivateOrder bitmexPrivateOrder = tradeService.replaceOrder(params); + LOG.info("Order was replaced = {}", bitmexPrivateOrder); + + checkPrivateOrder(orderId, testAskPrice, "5", BitmexSide.SELL, bitmexPrivateOrder); + cancelLimitOrder(replaceId); + } + + @Test + public void shouldCancelAllOrders() throws Exception { + final String clOrdId = generateOrderId(); + String orderId = placeLimitOrder(clOrdId, testAskPrice, "10", Order.OrderType.ASK); + final String clOrdId2 = generateOrderId(); + String orderId2 = placeLimitOrder(clOrdId2, testBidPrice, "5", Order.OrderType.BID); + + List bitmexPrivateOrders = tradeService.cancelAllOrders(); + Assert.assertEquals(2, bitmexPrivateOrders.size()); + + checkPrivateOrder(orderId, testAskPrice, "10", BitmexSide.SELL, bitmexPrivateOrders.get(0)); + checkPrivateOrder(orderId2, testBidPrice, "5", BitmexSide.BUY, bitmexPrivateOrders.get(1)); + } + + @Test + public void shouldFillPlacedOrder() throws Exception { + final String clOrdId = generateOrderId(); + String orderId = + placeLimitOrder( + clOrdId, + testBidPrice.add(priceShift.multiply(new BigDecimal("2"))), + "10", + Order.OrderType.BID); + Assert.assertNotNull(orderId); + + List bitmexPrivateOrders = tradeService.cancelBitmexOrder(null, clOrdId); + Assert.assertEquals(1, bitmexPrivateOrders.size()); + + BitmexPrivateOrder order = bitmexPrivateOrders.get(0); + Assert.assertEquals(BitmexPrivateOrder.OrderStatus.Filled, order.getOrderStatus()); + } + + @Test(expected = AssertionError.class) + public void shouldGetExecutionOnFill() { + final String clOrdId = generateOrderId(); + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.schedule( + () -> { + try { + placeLimitOrder( + clOrdId, testBidPrice.add(priceShift.multiply(new BigDecimal("2"))), - "10", Order.OrderType.BID); - Assert.assertNotNull(orderId); - - List bitmexPrivateOrders = - tradeService.cancelBitmexOrder(null, clOrdId); - Assert.assertEquals(1, bitmexPrivateOrders.size()); - - BitmexPrivateOrder order = bitmexPrivateOrders.get(0); - Assert.assertEquals(BitmexPrivateOrder.OrderStatus.Filled, order.getOrderStatus()); - } - - @Test(expected = AssertionError.class) - public void shouldGetExecutionOnFill() { - final String clOrdId = generateOrderId(); - ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - scheduler.schedule(() -> { - try { - placeLimitOrder(clOrdId, - testBidPrice.add(priceShift.multiply(new BigDecimal("2"))), - "10", Order.OrderType.BID); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - } - }, 1, TimeUnit.SECONDS); - - Observable executionObservable = ((BitmexStreamingMarketDataService) - exchange.getStreamingMarketDataService()).getRawExecutions("XBTUSD"); - executionObservable.test() - .awaitCount(5) - .assertNever(execution -> Objects.equals(execution.getClOrdID(), clOrdId)) - .dispose(); - - scheduler.shutdown(); - } -} \ No newline at end of file + "10", + Order.OrderType.BID); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + }, + 1, + TimeUnit.SECONDS); + + Observable executionObservable = + ((BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService()) + .getRawExecutions("XBTUSD"); + executionObservable + .test() + .awaitCount(5) + .assertNever(execution -> Objects.equals(execution.getClOrdID(), clOrdId)) + .dispose(); + + scheduler.shutdown(); + } +} diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java index 38bdc9ece..63f1ad674 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java @@ -1,13 +1,16 @@ package info.bitrich.xchangestream.bitmex; +import static org.knowm.xchange.bitmex.BitmexPrompt.PERPETUAL; + import info.bitrich.xchangestream.core.StreamingExchange; +import java.math.BigDecimal; +import java.util.List; import org.junit.Ignore; import org.junit.Test; import org.knowm.xchange.ExchangeFactory; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.bitmex.dto.marketdata.BitmexPrivateOrder; import org.knowm.xchange.bitmex.dto.trade.BitmexReplaceOrderParameters; -import org.knowm.xchange.bitmex.dto.trade.BitmexSide; import org.knowm.xchange.bitmex.service.BitmexMarketDataService; import org.knowm.xchange.bitmex.service.BitmexTradeService; import org.knowm.xchange.currency.CurrencyPair; @@ -18,108 +21,120 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.math.BigDecimal; -import java.util.List; - -import static org.knowm.xchange.bitmex.BitmexPrompt.PERPETUAL; - -/** - * @author Nikita Belenkiy on 18/05/2018. - */ +/** @author Nikita Belenkiy on 18/05/2018. */ public class BitmexOrderReplaceTest { - private static final Logger logger = LoggerFactory.getLogger(BitmexOrderReplaceTest.class); - - @Test - @Ignore - public void testOrderReplace() throws Exception { - CertHelper.trustAllCerts(); - BitmexStreamingExchange exchange = - (BitmexStreamingExchange) ExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class); - ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); - - defaultExchangeSpecification.setExchangeSpecificParametersItem("Use_Sandbox", true); - - defaultExchangeSpecification.setApiKey("QW8Ao_gx38e-8KFvDkFn-Ym4"); - defaultExchangeSpecification.setSecretKey("tn7rpzvOXSKThZD0f-xXehtydt4OTHZVf42gCCyxPixiiVOb"); - -// defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); -// defaultExchangeSpecification.setProxyHost("localhost"); -// defaultExchangeSpecification.setProxyPort(9999); - -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); - - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.USE_SANDBOX, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ACCEPT_ALL_CERITICATES, true); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); - - exchange.applySpecification(defaultExchangeSpecification); - exchange.connect().blockingAwait(); - BitmexMarketDataService marketDataService = - (BitmexMarketDataService) exchange.getMarketDataService(); - - BitmexTradeService tradeService = (BitmexTradeService)exchange.getTradeService(); - - final BitmexStreamingMarketDataService streamingMarketDataService = (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); -// streamingMarketDataService.authenticate(); - CurrencyPair xbtUsd = exchange.determineActiveContract(CurrencyPair.XBT_USD.base.toString(), CurrencyPair.XBT_USD.counter.toString(), PERPETUAL); - - streamingMarketDataService.getRawExecutions("XBTUSD").subscribe(bitmexExecution -> { - logger.info("!!!!EXECUTION!!!! = {}", bitmexExecution); - }); - OrderBook orderBook = marketDataService.getOrderBook(xbtUsd); - // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.ADA, - // Currency.BTC), BitmexPrompt.QUARTERLY); - // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.BTC, - // Currency.USD), BitmexPrompt.BIQUARTERLY); - - System.out.println("orderBook = " + orderBook); - - String nosOrdId = System.currentTimeMillis() + ""; - BigDecimal originalOrderSize = new BigDecimal("300"); - // BigDecimal price = new BigDecimal("10000"); - BigDecimal price = orderBook.getBids().get(0).getLimitPrice().add(new BigDecimal("100")); - LimitOrder limitOrder = new LimitOrder.Builder(Order.OrderType.ASK, CurrencyPair.XBT_USD).originalAmount(originalOrderSize).limitPrice(price).id(nosOrdId).build(); - String xbtusd = tradeService.placeLimitOrder(limitOrder); - logger.info("!!!!!PRIVATE_ORDER!!!! {}",xbtusd); - Thread.sleep(5000); - System.out.println(); - System.out.println(); - System.out.println(); - - - logger.info("Replacing"); - String replacedOrderId = nosOrdId + "replace"; - BitmexReplaceOrderParameters params = new BitmexReplaceOrderParameters.Builder() - .setOrderQuantity(originalOrderSize.divide(BigDecimal.valueOf(2))) - .setOrderId(xbtusd) - .setOrigClOrdId(nosOrdId) - .setClOrdId(replacedOrderId) - .build(); - BitmexPrivateOrder replaceBPO = tradeService.replaceOrder(params); -// logger.info("!!!!!PRIVATE_ORDER_REPLACE!!!! {}",xbtusd); - Thread.sleep(10000); - System.out.println(); - System.out.println(); - System.out.println(); - List bitmexPrivateOrders = tradeService.cancelBitmexOrder(null, replacedOrderId); - for (BitmexPrivateOrder bitmexPrivateOrder : bitmexPrivateOrders) { - logger.info("!!!!!PRIVATE_ORDER_CANCEL!!!! {}",bitmexPrivateOrder); - - } - Thread.sleep(10000); - - // BitmexPrivateOrder bitmexPrivateOrder = - // tradeService.replaceLimitOrder( - // "XBTUSD", - // originalOrderSize.divide(new BigDecimal("2")), - // null, - // orderId, - // // null, null, - // nosOrdId + "replace", - // nosOrdId); - // System.out.println("bitmexPrivateOrder = " + bitmexPrivateOrder); - tradeService.cancelAllOrders(); - exchange.disconnect(); + private static final Logger logger = LoggerFactory.getLogger(BitmexOrderReplaceTest.class); + + @Test + @Ignore + public void testOrderReplace() throws Exception { + CertHelper.trustAllCerts(); + BitmexStreamingExchange exchange = + (BitmexStreamingExchange) + ExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class); + ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); + + defaultExchangeSpecification.setExchangeSpecificParametersItem("Use_Sandbox", true); + + defaultExchangeSpecification.setApiKey("QW8Ao_gx38e-8KFvDkFn-Ym4"); + defaultExchangeSpecification.setSecretKey("tn7rpzvOXSKThZD0f-xXehtydt4OTHZVf42gCCyxPixiiVOb"); + + // defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); + // defaultExchangeSpecification.setProxyHost("localhost"); + // defaultExchangeSpecification.setProxyPort(9999); + + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); + + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.USE_SANDBOX, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ACCEPT_ALL_CERITICATES, true); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); + + exchange.applySpecification(defaultExchangeSpecification); + exchange.connect().blockingAwait(); + BitmexMarketDataService marketDataService = + (BitmexMarketDataService) exchange.getMarketDataService(); + + BitmexTradeService tradeService = (BitmexTradeService) exchange.getTradeService(); + + final BitmexStreamingMarketDataService streamingMarketDataService = + (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); + // streamingMarketDataService.authenticate(); + CurrencyPair xbtUsd = + exchange.determineActiveContract( + CurrencyPair.XBT_USD.base.toString(), + CurrencyPair.XBT_USD.counter.toString(), + PERPETUAL); + + streamingMarketDataService + .getRawExecutions("XBTUSD") + .subscribe( + bitmexExecution -> { + logger.info("!!!!EXECUTION!!!! = {}", bitmexExecution); + }); + OrderBook orderBook = marketDataService.getOrderBook(xbtUsd); + // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.ADA, + // Currency.BTC), BitmexPrompt.QUARTERLY); + // OrderBook orderBook = marketDataService.getOrderBook(new CurrencyPair(Currency.BTC, + // Currency.USD), BitmexPrompt.BIQUARTERLY); + + System.out.println("orderBook = " + orderBook); + + String nosOrdId = System.currentTimeMillis() + ""; + BigDecimal originalOrderSize = new BigDecimal("300"); + // BigDecimal price = new BigDecimal("10000"); + BigDecimal price = orderBook.getBids().get(0).getLimitPrice().add(new BigDecimal("100")); + LimitOrder limitOrder = + new LimitOrder.Builder(Order.OrderType.ASK, CurrencyPair.XBT_USD) + .originalAmount(originalOrderSize) + .limitPrice(price) + .id(nosOrdId) + .build(); + String xbtusd = tradeService.placeLimitOrder(limitOrder); + logger.info("!!!!!PRIVATE_ORDER!!!! {}", xbtusd); + Thread.sleep(5000); + System.out.println(); + System.out.println(); + System.out.println(); + + logger.info("Replacing"); + String replacedOrderId = nosOrdId + "replace"; + BitmexReplaceOrderParameters params = + new BitmexReplaceOrderParameters.Builder() + .setOrderQuantity(originalOrderSize.divide(BigDecimal.valueOf(2))) + .setOrderId(xbtusd) + .setOrigClOrdId(nosOrdId) + .setClOrdId(replacedOrderId) + .build(); + BitmexPrivateOrder replaceBPO = tradeService.replaceOrder(params); + // logger.info("!!!!!PRIVATE_ORDER_REPLACE!!!! {}",xbtusd); + Thread.sleep(10000); + System.out.println(); + System.out.println(); + System.out.println(); + List bitmexPrivateOrders = + tradeService.cancelBitmexOrder(null, replacedOrderId); + for (BitmexPrivateOrder bitmexPrivateOrder : bitmexPrivateOrders) { + logger.info("!!!!!PRIVATE_ORDER_CANCEL!!!! {}", bitmexPrivateOrder); } + Thread.sleep(10000); + + // BitmexPrivateOrder bitmexPrivateOrder = + // tradeService.replaceLimitOrder( + // "XBTUSD", + // originalOrderSize.divide(new BigDecimal("2")), + // null, + // orderId, + // // null, null, + // nosOrdId + "replace", + // nosOrdId); + // System.out.println("bitmexPrivateOrder = " + bitmexPrivateOrder); + tradeService.cancelAllOrders(); + exchange.disconnect(); + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java index e8ac86e8f..98f59d40b 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java @@ -1,30 +1,24 @@ package info.bitrich.xchangestream.bitmex; +import java.io.IOException; import org.junit.Assert; import org.junit.Test; -import java.io.IOException; - -/** - * @author Foat Akhmadeev - * 13/06/2018 - */ +/** @author Foat Akhmadeev 13/06/2018 */ public class BitmexStreamingTest { - @Test - public void shouldGetCorrectSubscribeMessage() throws IOException { - BitmexStreamingService service = - new BitmexStreamingService("url", "api", "secret"); + @Test + public void shouldGetCorrectSubscribeMessage() throws IOException { + BitmexStreamingService service = new BitmexStreamingService("url", "api", "secret"); - Assert.assertEquals("{\"op\":\"subscribe\",\"args\":[\"name\"]}", - service.getSubscribeMessage("name")); - } + Assert.assertEquals( + "{\"op\":\"subscribe\",\"args\":[\"name\"]}", service.getSubscribeMessage("name")); + } - @Test - public void shouldGetCorrectUnsubscribeMessage() throws IOException { - BitmexStreamingService service = - new BitmexStreamingService("url", "api", "secret"); + @Test + public void shouldGetCorrectUnsubscribeMessage() throws IOException { + BitmexStreamingService service = new BitmexStreamingService("url", "api", "secret"); - Assert.assertEquals("{\"op\":\"unsubscribe\",\"args\":[\"name\"]}", - service.getUnsubscribeMessage("name")); - } + Assert.assertEquals( + "{\"op\":\"unsubscribe\",\"args\":[\"name\"]}", service.getUnsubscribeMessage("name")); + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java index 2bfe918c9..96974a9aa 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java @@ -6,6 +6,7 @@ import info.bitrich.xchangestream.util.BookSanityChecker; import io.reactivex.Completable; import io.reactivex.Observable; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -17,88 +18,84 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.TimeUnit; - -/** - * @author Foat Akhmadeev - * 31/05/2018 - */ +/** @author Foat Akhmadeev 31/05/2018 */ @Ignore // Requires Bitmex to be up and contactable or the build fails. public class BitmexTest { - private static final Logger LOG = LoggerFactory.getLogger(BitmexTest.class); + private static final Logger LOG = LoggerFactory.getLogger(BitmexTest.class); - private static final CurrencyPair xbtUsd = CurrencyPair.XBT_USD; - private static final int MIN_DATA_COUNT = 2; + private static final CurrencyPair xbtUsd = CurrencyPair.XBT_USD; + private static final int MIN_DATA_COUNT = 2; - private StreamingExchange exchange; - private BitmexStreamingMarketDataService streamingMarketDataService; + private StreamingExchange exchange; + private BitmexStreamingMarketDataService streamingMarketDataService; - @Before - public void setup() { - exchange = StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); - awaitCompletable(exchange.connect()); - streamingMarketDataService = (BitmexStreamingMarketDataService) - exchange.getStreamingMarketDataService(); - } + @Before + public void setup() { + exchange = + StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); + awaitCompletable(exchange.connect()); + streamingMarketDataService = + (BitmexStreamingMarketDataService) exchange.getStreamingMarketDataService(); + } - @After - public void tearDown() { - awaitCompletable(exchange.disconnect()); - } + @After + public void tearDown() { + awaitCompletable(exchange.disconnect()); + } - private void awaitCompletable(Completable completable) { - completable.test() - .awaitDone(1, TimeUnit.MINUTES) - .assertComplete() - .assertNoErrors(); - } + private void awaitCompletable(Completable completable) { + completable.test().awaitDone(1, TimeUnit.MINUTES).assertComplete().assertNoErrors(); + } - private void awaitDataCount(Observable observable) { - observable.test() - .assertSubscribed() - .assertNoErrors() - .awaitCount(BitmexTest.MIN_DATA_COUNT) - .assertNoTimeout() - .dispose(); - } + private void awaitDataCount(Observable observable) { + observable + .test() + .assertSubscribed() + .assertNoErrors() + .awaitCount(BitmexTest.MIN_DATA_COUNT) + .assertNoTimeout() + .dispose(); + } - @Test - public void shouldReceiveBooks() { - Observable orderBookObservable = streamingMarketDataService.getOrderBook(xbtUsd); - awaitDataCount(orderBookObservable); - } + @Test + public void shouldReceiveBooks() { + Observable orderBookObservable = streamingMarketDataService.getOrderBook(xbtUsd); + awaitDataCount(orderBookObservable); + } - @Test - public void shouldReceiveRawTickers() { - Observable rawTickerObservable = streamingMarketDataService.getRawTicker(xbtUsd); - awaitDataCount(rawTickerObservable); - } + @Test + public void shouldReceiveRawTickers() { + Observable rawTickerObservable = streamingMarketDataService.getRawTicker(xbtUsd); + awaitDataCount(rawTickerObservable); + } - @Test - public void shouldReceiveTickers() { - Observable tickerObservable = streamingMarketDataService.getTicker(xbtUsd); - awaitDataCount(tickerObservable); - } + @Test + public void shouldReceiveTickers() { + Observable tickerObservable = streamingMarketDataService.getTicker(xbtUsd); + awaitDataCount(tickerObservable); + } -// @Test - public void shouldReceiveTrades() { - Observable orderBookObservable = streamingMarketDataService.getTrades(xbtUsd); - awaitDataCount(orderBookObservable); - } + // @Test + public void shouldReceiveTrades() { + Observable orderBookObservable = streamingMarketDataService.getTrades(xbtUsd); + awaitDataCount(orderBookObservable); + } - @Test - public void shouldHaveNoBookErrors() { - streamingMarketDataService.getOrderBook(xbtUsd) - .test() - .assertSubscribed() - .assertNoErrors() - .awaitCount(10) - .assertNever(book -> { - String err = BookSanityChecker.hasErrors(book); - LOG.info("err {}", err); - return err != null; - }) - .assertNoTimeout() - .dispose(); - } + @Test + public void shouldHaveNoBookErrors() { + streamingMarketDataService + .getOrderBook(xbtUsd) + .test() + .assertSubscribed() + .assertNoErrors() + .awaitCount(10) + .assertNever( + book -> { + String err = BookSanityChecker.hasErrors(book); + LOG.info("err {}", err); + return err != null; + }) + .assertNoTimeout() + .dispose(); + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java index 962ca6154..1ebd0f72a 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java @@ -4,21 +4,20 @@ import info.bitrich.xchangestream.util.LocalExchangeConfig; import org.knowm.xchange.ExchangeSpecification; -/** - * @author Foat Akhmadeev - * 19/06/2018 - */ +/** @author Foat Akhmadeev 19/06/2018 */ public class BitmexTestsCommons { - public static ExchangeSpecification getExchangeSpecification(LocalExchangeConfig localConfig, - ExchangeSpecification defaultExchangeSpecification) { - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.USE_SANDBOX, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ACCEPT_ALL_CERITICATES, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); - - defaultExchangeSpecification.setApiKey(localConfig.getApiKey()); - defaultExchangeSpecification.setSecretKey(localConfig.getSecretKey()); -// defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); - return defaultExchangeSpecification; - } + public static ExchangeSpecification getExchangeSpecification( + LocalExchangeConfig localConfig, ExchangeSpecification defaultExchangeSpecification) { + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.USE_SANDBOX, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ACCEPT_ALL_CERITICATES, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ENABLE_LOGGING_HANDLER, true); + defaultExchangeSpecification.setApiKey(localConfig.getApiKey()); + defaultExchangeSpecification.setSecretKey(localConfig.getSecretKey()); + // defaultExchangeSpecification.setShouldLoadRemoteMetaData(true); + return defaultExchangeSpecification; + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java index c0b75f727..63187a2ef 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java @@ -13,55 +13,59 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * @author Foat Akhmadeev - * 18/06/2018 - */ +/** @author Foat Akhmadeev 18/06/2018 */ public class BitmexWithProxyIT { - private static final Logger LOG = LoggerFactory.getLogger(BitmexWithProxyIT.class); + private static final Logger LOG = LoggerFactory.getLogger(BitmexWithProxyIT.class); - private StreamingExchange exchange; - private ProxyUtil proxyUtil; + private StreamingExchange exchange; + private ProxyUtil proxyUtil; - @Before - public void setup() throws Exception { - String execLine = PropsLoader.proxyExecLine(); - LOG.info("Running proxy with \"{}\" command", execLine); - proxyUtil = new ProxyUtil(execLine, 5000); - proxyUtil.startProxy(); - LocalExchangeConfig localConfig = PropsLoader.loadKeys( - "bitmex.secret.keys", "bitmex.secret.keys.origin", "bitmex"); - exchange = StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); + @Before + public void setup() throws Exception { + String execLine = PropsLoader.proxyExecLine(); + LOG.info("Running proxy with \"{}\" command", execLine); + proxyUtil = new ProxyUtil(execLine, 5000); + proxyUtil.startProxy(); + LocalExchangeConfig localConfig = + PropsLoader.loadKeys("bitmex.secret.keys", "bitmex.secret.keys.origin", "bitmex"); + exchange = + StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); - ExchangeSpecification exchangeSpecification = BitmexTestsCommons.getExchangeSpecification(localConfig, - exchange.getDefaultExchangeSpecification()); - exchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); - exchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); + ExchangeSpecification exchangeSpecification = + BitmexTestsCommons.getExchangeSpecification( + localConfig, exchange.getDefaultExchangeSpecification()); + exchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.SOCKS_PROXY_HOST, "localhost"); + exchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.SOCKS_PROXY_PORT, 8889); - exchange.applySpecification(exchangeSpecification); - exchange.connect().blockingAwait(); - } + exchange.applySpecification(exchangeSpecification); + exchange.connect().blockingAwait(); + } - @After - public void tearDown() { - exchange.disconnect().blockingAwait(); - proxyUtil.shutdown(); - } + @After + public void tearDown() { + exchange.disconnect().blockingAwait(); + proxyUtil.shutdown(); + } - @Test - public void shouldReconnectOnFailure() throws Exception { - Assert.assertTrue(exchange.isAlive()); - exchange.reconnectFailure().subscribe(e -> { - LOG.info("reconnection issue", e); - }); + @Test + public void shouldReconnectOnFailure() throws Exception { + Assert.assertTrue(exchange.isAlive()); + exchange + .reconnectFailure() + .subscribe( + e -> { + LOG.info("reconnection issue", e); + }); - proxyUtil.stopProxy(); + proxyUtil.stopProxy(); - Thread.sleep(5000); - Assert.assertFalse(exchange.isAlive()); - proxyUtil.startProxy(); + Thread.sleep(5000); + Assert.assertFalse(exchange.isAlive()); + proxyUtil.startProxy(); - Thread.sleep(15000); - Assert.assertTrue(exchange.isAlive()); - } + Thread.sleep(15000); + Assert.assertTrue(exchange.isAlive()); + } } diff --git a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java index 248d7da27..2d11b8f60 100644 --- a/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java +++ b/xchange-stream-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java @@ -1,77 +1,78 @@ package info.bitrich.xchangestream.bitmex.dto; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; -import org.knowm.xchange.bitmex.dto.marketdata.BitmexPrivateOrder; -import org.knowm.xchange.bitmex.dto.trade.BitmexSide; +import static org.junit.Assert.assertEquals; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.TimeZone; +import org.junit.Test; +import org.knowm.xchange.bitmex.dto.marketdata.BitmexPrivateOrder; +import org.knowm.xchange.bitmex.dto.trade.BitmexSide; -import static org.junit.Assert.assertEquals; - -/** - * @author Nikita Belenkiy on 05/06/2018. - */ +/** @author Nikita Belenkiy on 05/06/2018. */ public class BitmexExecutionTest { - @Test - public void testDesialization() throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("info/bitrich/xchangestream/bitmex/dto/execution.json"); - BitmexExecution bitmexExecution = objectMapper.readValue(resourceAsStream, BitmexExecution.class); + @Test + public void testDesialization() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + InputStream resourceAsStream = + getClass() + .getClassLoader() + .getResourceAsStream("info/bitrich/xchangestream/bitmex/dto/execution.json"); + BitmexExecution bitmexExecution = + objectMapper.readValue(resourceAsStream, BitmexExecution.class); - assertEquals( "b47dfbd1-3b88-5678-f6d6-b9314a96c3b8", bitmexExecution.execID); - assertEquals( "5f6c16df-4706-4548-f47d-25f2f915f149", bitmexExecution.orderID); - assertEquals( "1528259635504", bitmexExecution.clOrdID); - assertEquals( "clOrdLinkIDclOrdLinkID", bitmexExecution.clOrdLinkID); - assertEquals( 75430, bitmexExecution.account); - assertEquals( "XBTUSD", bitmexExecution.symbol); - assertEquals( BitmexSide.SELL, bitmexExecution.side); - assertEquals( new Long(30), bitmexExecution.lastQty); - assertEquals( BigDecimal.valueOf(7622.5), bitmexExecution.lastPx); - assertEquals( null, bitmexExecution.underlyingLastPx); - assertEquals( "XBME", bitmexExecution.lastMkt); - assertEquals( "AddedLiquidity", bitmexExecution.lastLiquidityInd); - assertEquals( BigDecimal.valueOf(3030), bitmexExecution.simpleOrderQty); - assertEquals( new Long(30), bitmexExecution.orderQty); - assertEquals( BigDecimal.valueOf(7622.5), bitmexExecution.price); - assertEquals( new Long(2), bitmexExecution.displayQty); - assertEquals( new BigDecimal("7622.1"), bitmexExecution.stopPx); - assertEquals( null, bitmexExecution.pegOffsetValue); - assertEquals( "", bitmexExecution.pegPriceType); - assertEquals( "USD", bitmexExecution.currency); - assertEquals( "XBt", bitmexExecution.settlCurrency); - assertEquals( "Trade", bitmexExecution.execType); - assertEquals( "Limit", bitmexExecution.ordType); - assertEquals( "GoodTillCancel", bitmexExecution.timeInForce); - assertEquals( "", bitmexExecution.execInst); - assertEquals( "", bitmexExecution.contingencyType); - assertEquals( "XBME", bitmexExecution.exDestination); - assertEquals( BitmexPrivateOrder.OrderStatus.Filled, bitmexExecution.ordStatus); - assertEquals( "", bitmexExecution.triggered); - assertEquals( false, bitmexExecution.workingIndicator); - assertEquals( "", bitmexExecution.ordRejReason); - assertEquals( BigDecimal.valueOf(10), bitmexExecution.simpleLeavesQty); - assertEquals( new Long(11), bitmexExecution.leavesQty); - assertEquals( BigDecimal.valueOf(0.0039357), bitmexExecution.simpleCumQty); - assertEquals( BigDecimal.valueOf(30), bitmexExecution.cumQty); - assertEquals( BigDecimal.valueOf(7622.5), bitmexExecution.avgPx); - assertEquals( BigDecimal.valueOf(-0.00025), bitmexExecution.commission); - assertEquals( "PublishTrade", bitmexExecution.tradePublishIndicator); - assertEquals( "SingleSecurity", bitmexExecution.multiLegReportingType); - assertEquals( "Submitted via API.", bitmexExecution.text); - assertEquals( "11bae57a-3a11-83bc-3b71-0e472b89156f", bitmexExecution.trdMatchID); - assertEquals( new Long(393570), bitmexExecution.execCost); - assertEquals( new Long(-98), bitmexExecution.execComm); - assertEquals( BigDecimal.valueOf(-0.0039357), bitmexExecution.homeNotional); - assertEquals( BigDecimal.valueOf(30), bitmexExecution.foreignNotional); - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - assertEquals("2018-06-06T04:35:04.763Z", format.format(bitmexExecution.transactTime)); - assertEquals("2018-06-06T04:35:04.763Z", format.format(bitmexExecution.timestamp)); - } + assertEquals("b47dfbd1-3b88-5678-f6d6-b9314a96c3b8", bitmexExecution.execID); + assertEquals("5f6c16df-4706-4548-f47d-25f2f915f149", bitmexExecution.orderID); + assertEquals("1528259635504", bitmexExecution.clOrdID); + assertEquals("clOrdLinkIDclOrdLinkID", bitmexExecution.clOrdLinkID); + assertEquals(75430, bitmexExecution.account); + assertEquals("XBTUSD", bitmexExecution.symbol); + assertEquals(BitmexSide.SELL, bitmexExecution.side); + assertEquals(new Long(30), bitmexExecution.lastQty); + assertEquals(BigDecimal.valueOf(7622.5), bitmexExecution.lastPx); + assertEquals(null, bitmexExecution.underlyingLastPx); + assertEquals("XBME", bitmexExecution.lastMkt); + assertEquals("AddedLiquidity", bitmexExecution.lastLiquidityInd); + assertEquals(BigDecimal.valueOf(3030), bitmexExecution.simpleOrderQty); + assertEquals(new Long(30), bitmexExecution.orderQty); + assertEquals(BigDecimal.valueOf(7622.5), bitmexExecution.price); + assertEquals(new Long(2), bitmexExecution.displayQty); + assertEquals(new BigDecimal("7622.1"), bitmexExecution.stopPx); + assertEquals(null, bitmexExecution.pegOffsetValue); + assertEquals("", bitmexExecution.pegPriceType); + assertEquals("USD", bitmexExecution.currency); + assertEquals("XBt", bitmexExecution.settlCurrency); + assertEquals("Trade", bitmexExecution.execType); + assertEquals("Limit", bitmexExecution.ordType); + assertEquals("GoodTillCancel", bitmexExecution.timeInForce); + assertEquals("", bitmexExecution.execInst); + assertEquals("", bitmexExecution.contingencyType); + assertEquals("XBME", bitmexExecution.exDestination); + assertEquals(BitmexPrivateOrder.OrderStatus.Filled, bitmexExecution.ordStatus); + assertEquals("", bitmexExecution.triggered); + assertEquals(false, bitmexExecution.workingIndicator); + assertEquals("", bitmexExecution.ordRejReason); + assertEquals(BigDecimal.valueOf(10), bitmexExecution.simpleLeavesQty); + assertEquals(new Long(11), bitmexExecution.leavesQty); + assertEquals(BigDecimal.valueOf(0.0039357), bitmexExecution.simpleCumQty); + assertEquals(BigDecimal.valueOf(30), bitmexExecution.cumQty); + assertEquals(BigDecimal.valueOf(7622.5), bitmexExecution.avgPx); + assertEquals(BigDecimal.valueOf(-0.00025), bitmexExecution.commission); + assertEquals("PublishTrade", bitmexExecution.tradePublishIndicator); + assertEquals("SingleSecurity", bitmexExecution.multiLegReportingType); + assertEquals("Submitted via API.", bitmexExecution.text); + assertEquals("11bae57a-3a11-83bc-3b71-0e472b89156f", bitmexExecution.trdMatchID); + assertEquals(new Long(393570), bitmexExecution.execCost); + assertEquals(new Long(-98), bitmexExecution.execComm); + assertEquals(BigDecimal.valueOf(-0.0039357), bitmexExecution.homeNotional); + assertEquals(BigDecimal.valueOf(30), bitmexExecution.foreignNotional); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + assertEquals("2018-06-06T04:35:04.763Z", format.format(bitmexExecution.transactTime)); + assertEquals("2018-06-06T04:35:04.763Z", format.format(bitmexExecution.timestamp)); + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingExchange.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingExchange.java index 2a96d4666..6cde8c768 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingExchange.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingExchange.java @@ -1,65 +1,67 @@ package info.bitrich.xchangestream.bitstamp; +import static info.bitrich.xchangestream.service.ConnectableService.BEFORE_CONNECTION_HANDLER; + import info.bitrich.xchangestream.core.ProductSubscription; import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingMarketDataService; import info.bitrich.xchangestream.service.pusher.PusherStreamingService; - -import org.knowm.xchange.bitstamp.BitstampExchange; - import io.reactivex.Completable; - -import static info.bitrich.xchangestream.service.ConnectableService.BEFORE_CONNECTION_HANDLER; +import org.knowm.xchange.bitstamp.BitstampExchange; /** * This Bitstamp Streaming WebSocket V1 implementation.
* From official site:
+ * *

THIS IS AN OLD IMPLEMENTATION OF WEBSOCKET AND WILL BE DISCONTINUED IN Q2 2019.
- * PLEASE USE THE NEW WEBSOCKET IMPLEMENTATION HERE: HTTPS://WWW.BITSTAMP.NET/WEBSOCKET/V2

+ * PLEASE USE THE NEW WEBSOCKET IMPLEMENTATION HERE: HTTPS://WWW.BITSTAMP.NET/WEBSOCKET/V2
* See Bitstamp WebSocket V1 * * @deprecated Use {@link info.bitrich.xchangestream.bitstamp.v2.BitstampStreamingExchange} instead. */ @Deprecated public class BitstampStreamingExchange extends BitstampExchange implements StreamingExchange { - private static final String API_KEY = "de504dc5763aeef9ff52"; - private final PusherStreamingService streamingService; + private static final String API_KEY = "de504dc5763aeef9ff52"; + private final PusherStreamingService streamingService; - private BitstampStreamingMarketDataService streamingMarketDataService; + private BitstampStreamingMarketDataService streamingMarketDataService; - public BitstampStreamingExchange() { - streamingService = new PusherStreamingService(API_KEY); - } + public BitstampStreamingExchange() { + streamingService = new PusherStreamingService(API_KEY); + } - @Override - protected void initServices() { - super.initServices(); - streamingService.setBeforeConnectionHandler((Runnable) getExchangeSpecification().getExchangeSpecificParametersItem(BEFORE_CONNECTION_HANDLER)); - streamingMarketDataService = new BitstampStreamingMarketDataService(streamingService); - } + @Override + protected void initServices() { + super.initServices(); + streamingService.setBeforeConnectionHandler( + (Runnable) + getExchangeSpecification() + .getExchangeSpecificParametersItem(BEFORE_CONNECTION_HANDLER)); + streamingMarketDataService = new BitstampStreamingMarketDataService(streamingService); + } - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } - @Override - public boolean isAlive() { - return this.streamingService.isSocketOpen(); - } + @Override + public boolean isAlive() { + return this.streamingService.isSocketOpen(); + } - @Override - public void useCompressedMessages(boolean compressedMessages) { - streamingService.useCompressedMessages(compressedMessages); - } + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataService.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataService.java index a6a265c69..c4bfc6c97 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataService.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataService.java @@ -16,54 +16,62 @@ import org.knowm.xchange.exceptions.NotAvailableFromExchangeException; public class BitstampStreamingMarketDataService implements StreamingMarketDataService { - private final PusherStreamingService service; + private final PusherStreamingService service; - BitstampStreamingMarketDataService(PusherStreamingService service) { - this.service = service; - } + BitstampStreamingMarketDataService(PusherStreamingService service) { + this.service = service; + } - public Observable getDifferentialOrderBook(CurrencyPair currencyPair, Object... args) { - return getOrderBook("diff_order_book", currencyPair, args); - } + public Observable getDifferentialOrderBook(CurrencyPair currencyPair, Object... args) { + return getOrderBook("diff_order_book", currencyPair, args); + } - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - return getOrderBook("order_book", currencyPair, args); - } + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + return getOrderBook("order_book", currencyPair, args); + } - private Observable getOrderBook(String channelPrefix, CurrencyPair currencyPair, Object... args) { - String channelName = channelPrefix + getChannelPostfix(currencyPair); + private Observable getOrderBook( + String channelPrefix, CurrencyPair currencyPair, Object... args) { + String channelName = channelPrefix + getChannelPostfix(currencyPair); - return service.subscribeChannel(channelName, BitstampStreamingService.EVENT_ORDERBOOK) - .map(s -> { - ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - BitstampOrderBook orderBook = mapper.readValue(s, BitstampOrderBook.class); - return BitstampAdapters.adaptOrderBook(orderBook, currencyPair); - }); - } + return service + .subscribeChannel(channelName, BitstampStreamingService.EVENT_ORDERBOOK) + .map( + s -> { + ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + BitstampOrderBook orderBook = mapper.readValue(s, BitstampOrderBook.class); + return BitstampAdapters.adaptOrderBook(orderBook, currencyPair); + }); + } - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - // BitStamp has no live ticker, only trades. - throw new NotAvailableFromExchangeException(); - } + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + // BitStamp has no live ticker, only trades. + throw new NotAvailableFromExchangeException(); + } - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channelName = "live_trades" + getChannelPostfix(currencyPair); + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channelName = "live_trades" + getChannelPostfix(currencyPair); - return service.subscribeChannel(channelName, BitstampStreamingService.EVENT_TRADE) - .map(s -> { - ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - BitstampWebSocketTransaction transactions = mapper.readValue(s, BitstampWebSocketTransaction.class); - return BitstampAdapters.adaptTrade(transactions, currencyPair, 1000); - }); - } + return service + .subscribeChannel(channelName, BitstampStreamingService.EVENT_TRADE) + .map( + s -> { + ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + BitstampWebSocketTransaction transactions = + mapper.readValue(s, BitstampWebSocketTransaction.class); + return BitstampAdapters.adaptTrade(transactions, currencyPair, 1000); + }); + } - private String getChannelPostfix(CurrencyPair currencyPair) { - if (currencyPair.equals(CurrencyPair.BTC_USD)) { - return ""; - } - return "_" + currencyPair.base.toString().toLowerCase() + currencyPair.counter.toString().toLowerCase(); + private String getChannelPostfix(CurrencyPair currencyPair) { + if (currencyPair.equals(CurrencyPair.BTC_USD)) { + return ""; } + return "_" + + currencyPair.base.toString().toLowerCase() + + currencyPair.counter.toString().toLowerCase(); + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/dto/BitstampWebSocketTransaction.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/dto/BitstampWebSocketTransaction.java index 4aa13ae23..75c8d3c7d 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/dto/BitstampWebSocketTransaction.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/dto/BitstampWebSocketTransaction.java @@ -1,14 +1,17 @@ package info.bitrich.xchangestream.bitstamp.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.bitstamp.dto.marketdata.BitstampTransaction; - import java.math.BigDecimal; +import org.knowm.xchange.bitstamp.dto.marketdata.BitstampTransaction; public class BitstampWebSocketTransaction extends BitstampTransaction { - public BitstampWebSocketTransaction(@JsonProperty("microtimestamp") BigDecimal microtimestamp, @JsonProperty("id") long tid, @JsonProperty("price") BigDecimal price, - @JsonProperty("amount") BigDecimal amount, @JsonProperty("order_type") int type) { + public BitstampWebSocketTransaction( + @JsonProperty("microtimestamp") BigDecimal microtimestamp, + @JsonProperty("id") long tid, + @JsonProperty("price") BigDecimal price, + @JsonProperty("amount") BigDecimal amount, + @JsonProperty("order_type") int type) { - super(microtimestamp.longValue() / 1000, tid, price, amount, type); - } + super(microtimestamp.longValue() / 1000, tid, price, amount, type); + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingExchange.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingExchange.java index 0ff91ae5a..185737a01 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingExchange.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingExchange.java @@ -9,66 +9,65 @@ import org.knowm.xchange.bitstamp.BitstampExchange; /** - * Bitstamp WebSocket V2 Streaming Exchange implementation - * Created by Pavel Chertalev on 15.03.2018. + * Bitstamp WebSocket V2 Streaming Exchange implementation Created by Pavel Chertalev on 15.03.2018. */ public class BitstampStreamingExchange extends BitstampExchange implements StreamingExchange { - private static final String API_URI = "wss://ws.bitstamp.net"; + private static final String API_URI = "wss://ws.bitstamp.net"; - private final BitstampStreamingService streamingService; - private BitstampStreamingMarketDataService streamingMarketDataService; + private final BitstampStreamingService streamingService; + private BitstampStreamingMarketDataService streamingMarketDataService; - public BitstampStreamingExchange() { - this.streamingService = new BitstampStreamingService(API_URI); - } + public BitstampStreamingExchange() { + this.streamingService = new BitstampStreamingService(API_URI); + } - @Override - protected void initServices() { - super.initServices(); - streamingMarketDataService = new BitstampStreamingMarketDataService(streamingService); - } + @Override + protected void initServices() { + super.initServices(); + streamingMarketDataService = new BitstampStreamingMarketDataService(streamingService); + } - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); - return spec; - } + return spec; + } - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } - @Override - public void useCompressedMessages(boolean compressedMessages) { - streamingService.useCompressedMessages(compressedMessages); - } + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingMarketDataService.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingMarketDataService.java index 910679c1b..83bec4f9f 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingMarketDataService.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingMarketDataService.java @@ -14,55 +14,64 @@ import org.knowm.xchange.exceptions.NotAvailableFromExchangeException; /** - * Bitstamp WebSocket V2 Streaming Market Data Service implementation - * Created by Pavel Chertalev on 15.03.2018. + * Bitstamp WebSocket V2 Streaming Market Data Service implementation Created by Pavel Chertalev on + * 15.03.2018. */ public class BitstampStreamingMarketDataService implements StreamingMarketDataService { - private final BitstampStreamingService service; + private final BitstampStreamingService service; - public BitstampStreamingMarketDataService(BitstampStreamingService service) { - this.service = service; - } + public BitstampStreamingMarketDataService(BitstampStreamingService service) { + this.service = service; + } - public Observable getFullOrderBook(CurrencyPair currencyPair, Object... args) { - return getOrderBook("diff_order_book", currencyPair, args); - } + public Observable getFullOrderBook(CurrencyPair currencyPair, Object... args) { + return getOrderBook("diff_order_book", currencyPair, args); + } - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - return getOrderBook("order_book", currencyPair, args); - } + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + return getOrderBook("order_book", currencyPair, args); + } - private Observable getOrderBook(String channelPrefix, CurrencyPair currencyPair, Object... args) { - String channelName = channelPrefix + getChannelPostfix(currencyPair); + private Observable getOrderBook( + String channelPrefix, CurrencyPair currencyPair, Object... args) { + String channelName = channelPrefix + getChannelPostfix(currencyPair); - return service.subscribeChannel(channelName, BitstampStreamingService.EVENT_ORDERBOOK) - .map(s -> { - ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - BitstampOrderBook orderBook = mapper.treeToValue(s.get("data"), BitstampOrderBook.class); - return BitstampAdapters.adaptOrderBook(orderBook, currencyPair); - }); - } + return service + .subscribeChannel(channelName, BitstampStreamingService.EVENT_ORDERBOOK) + .map( + s -> { + ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + BitstampOrderBook orderBook = + mapper.treeToValue(s.get("data"), BitstampOrderBook.class); + return BitstampAdapters.adaptOrderBook(orderBook, currencyPair); + }); + } - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - // BitStamp has no live ticker, only trades. - throw new NotAvailableFromExchangeException(); - } + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + // BitStamp has no live ticker, only trades. + throw new NotAvailableFromExchangeException(); + } - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channelName = "live_trades" + getChannelPostfix(currencyPair); + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channelName = "live_trades" + getChannelPostfix(currencyPair); - return service.subscribeChannel(channelName, BitstampStreamingService.EVENT_TRADE) - .map(s -> { - ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - BitstampWebSocketTransaction transactions = mapper.treeToValue(s.get("data"), BitstampWebSocketTransaction.class); - return BitstampAdapters.adaptTrade(transactions, currencyPair, 1); - }); - } + return service + .subscribeChannel(channelName, BitstampStreamingService.EVENT_TRADE) + .map( + s -> { + ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + BitstampWebSocketTransaction transactions = + mapper.treeToValue(s.get("data"), BitstampWebSocketTransaction.class); + return BitstampAdapters.adaptTrade(transactions, currencyPair, 1); + }); + } - private String getChannelPostfix(CurrencyPair currencyPair) { - return "_" + currencyPair.base.toString().toLowerCase() + currencyPair.counter.toString().toLowerCase(); - } + private String getChannelPostfix(CurrencyPair currencyPair) { + return "_" + + currencyPair.base.toString().toLowerCase() + + currencyPair.counter.toString().toLowerCase(); + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingService.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingService.java index b8bb1121d..c73e31ef3 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingService.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/BitstampStreamingService.java @@ -5,90 +5,96 @@ import info.bitrich.xchangestream.bitstamp.v2.dto.BitstampWebSocketSubscriptionMessage; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; +import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; - /** - * Bitstamp WebSocket V2 streaming service implementation - * Created by Pavel Chertalev on 15.03.2018. + * Bitstamp WebSocket V2 streaming service implementation Created by Pavel Chertalev on 15.03.2018. */ public class BitstampStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(BitstampStreamingService.class); + private static final Logger LOG = LoggerFactory.getLogger(BitstampStreamingService.class); - private static final String JSON_CHANNEL = "channel"; - private static final String JSON_EVENT = "event"; + private static final String JSON_CHANNEL = "channel"; + private static final String JSON_EVENT = "event"; - public static final String EVENT_ORDERBOOK = "data"; - public static final String EVENT_TRADE = "trade"; - private static final String EVENT_SUBSCRIPTION_SUCCEEDED = "bts:subscription_succeeded"; - private static final String EVENT_UNSUBSCRIPTION_SUCCEEDED = "bts:unsubscription_succeeded"; + public static final String EVENT_ORDERBOOK = "data"; + public static final String EVENT_TRADE = "trade"; + private static final String EVENT_SUBSCRIPTION_SUCCEEDED = "bts:subscription_succeeded"; + private static final String EVENT_UNSUBSCRIPTION_SUCCEEDED = "bts:unsubscription_succeeded"; - public BitstampStreamingService(String apiUrl) { - super(apiUrl, Integer.MAX_VALUE); - } + public BitstampStreamingService(String apiUrl) { + super(apiUrl, Integer.MAX_VALUE); + } - @Override - protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { - return null; - } + @Override + protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { + return null; + } - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - JsonNode jsonNode = message.get(JSON_CHANNEL); - if (jsonNode != null) { - return jsonNode.asText(); - } - throw new IOException("Channel name can't be evaluated from message"); + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + JsonNode jsonNode = message.get(JSON_CHANNEL); + if (jsonNode != null) { + return jsonNode.asText(); } + throw new IOException("Channel name can't be evaluated from message"); + } - @Override - protected void handleMessage(JsonNode message) { - JsonNode channelJsonNode = message.get(JSON_CHANNEL); - JsonNode eventJsonNode = message.get(JSON_EVENT); + @Override + protected void handleMessage(JsonNode message) { + JsonNode channelJsonNode = message.get(JSON_CHANNEL); + JsonNode eventJsonNode = message.get(JSON_EVENT); - if (channelJsonNode == null || eventJsonNode == null) { - LOG.error("Received JSON message does not contain {} and {} fields. Skipped...", JSON_CHANNEL, JSON_EVENT); - return; - } + if (channelJsonNode == null || eventJsonNode == null) { + LOG.error( + "Received JSON message does not contain {} and {} fields. Skipped...", + JSON_CHANNEL, + JSON_EVENT); + return; + } - String channel = channelJsonNode.asText(); - String event = eventJsonNode.asText(); + String channel = channelJsonNode.asText(); + String event = eventJsonNode.asText(); - switch (event) { - case EVENT_ORDERBOOK: - case EVENT_TRADE: - if (!channels.containsKey(channel)) { - LOG.warn("The message has been received from disconnected channel '{}'. Skipped.", channel); - return; - } - super.handleMessage(message); - break; - case EVENT_SUBSCRIPTION_SUCCEEDED: - LOG.info("Channel {} has been successfully subscribed", channel); - break; - case EVENT_UNSUBSCRIPTION_SUCCEEDED: - LOG.info("Channel {} has been successfully unsubscribed", channel); - break; - default: - LOG.warn("Unsupported event type {} in message {}", event, message.toString()); + switch (event) { + case EVENT_ORDERBOOK: + case EVENT_TRADE: + if (!channels.containsKey(channel)) { + LOG.warn( + "The message has been received from disconnected channel '{}'. Skipped.", channel); + return; } + super.handleMessage(message); + break; + case EVENT_SUBSCRIPTION_SUCCEEDED: + LOG.info("Channel {} has been successfully subscribed", channel); + break; + case EVENT_UNSUBSCRIPTION_SUCCEEDED: + LOG.info("Channel {} has been successfully unsubscribed", channel); + break; + default: + LOG.warn("Unsupported event type {} in message {}", event, message.toString()); } + } - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - BitstampWebSocketSubscriptionMessage subscribeMessage = generateSubscribeMessage(channelName, "bts:subscribe"); - return objectMapper.writeValueAsString(subscribeMessage); - } + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + BitstampWebSocketSubscriptionMessage subscribeMessage = + generateSubscribeMessage(channelName, "bts:subscribe"); + return objectMapper.writeValueAsString(subscribeMessage); + } - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - BitstampWebSocketSubscriptionMessage subscribeMessage = generateSubscribeMessage(channelName, "bts:unsubscribe"); - return objectMapper.writeValueAsString(subscribeMessage); - } + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + BitstampWebSocketSubscriptionMessage subscribeMessage = + generateSubscribeMessage(channelName, "bts:unsubscribe"); + return objectMapper.writeValueAsString(subscribeMessage); + } - private BitstampWebSocketSubscriptionMessage generateSubscribeMessage(String channelName, String channel) { - return new BitstampWebSocketSubscriptionMessage(channel, new BitstampWebSocketData(channelName)); - } + private BitstampWebSocketSubscriptionMessage generateSubscribeMessage( + String channelName, String channel) { + return new BitstampWebSocketSubscriptionMessage( + channel, new BitstampWebSocketData(channelName)); + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketData.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketData.java index 75ee15349..0e44338ff 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketData.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketData.java @@ -5,16 +5,16 @@ public class BitstampWebSocketData { - private static final String CHANNEL = "channel"; + private static final String CHANNEL = "channel"; - private String channel; + private String channel; - @JsonCreator - public BitstampWebSocketData(@JsonProperty(CHANNEL) String channel) { - this.channel = channel; - } + @JsonCreator + public BitstampWebSocketData(@JsonProperty(CHANNEL) String channel) { + this.channel = channel; + } - public String getChannel() { - return channel; - } + public String getChannel() { + return channel; + } } diff --git a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketSubscriptionMessage.java b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketSubscriptionMessage.java index f38b87b30..acef1f4d1 100644 --- a/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketSubscriptionMessage.java +++ b/xchange-stream-bitstamp/src/main/java/info/bitrich/xchangestream/bitstamp/v2/dto/BitstampWebSocketSubscriptionMessage.java @@ -2,41 +2,38 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Pavel Chertalev - */ +/** Created by Pavel Chertalev */ public class BitstampWebSocketSubscriptionMessage { - private static final String EVENT = "event"; - private static final String DATA = "data"; + private static final String EVENT = "event"; + private static final String DATA = "data"; - @JsonProperty(EVENT) - private String event; + @JsonProperty(EVENT) + private String event; - @JsonProperty(DATA) - private BitstampWebSocketData data; + @JsonProperty(DATA) + private BitstampWebSocketData data; - public BitstampWebSocketSubscriptionMessage() { - } + public BitstampWebSocketSubscriptionMessage() {} - public BitstampWebSocketSubscriptionMessage(String event, BitstampWebSocketData data) { - this.event = event; - this.data = data; - } + public BitstampWebSocketSubscriptionMessage(String event, BitstampWebSocketData data) { + this.event = event; + this.data = data; + } - public String getEvent() { - return event; - } + public String getEvent() { + return event; + } - public void setEvent(String event) { - this.event = event; - } + public void setEvent(String event) { + this.event = event; + } - public BitstampWebSocketData getData() { - return data; - } + public BitstampWebSocketData getData() { + return data; + } - public void setData(BitstampWebSocketData data) { - this.data = data; - } + public void setData(BitstampWebSocketData data) { + this.data = data; + } } diff --git a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampManualExample.java b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampManualExample.java index f2f0e9cc4..683785b69 100644 --- a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampManualExample.java +++ b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampManualExample.java @@ -1,5 +1,7 @@ package info.bitrich.xchangestream.bitstamp; +import static java.util.concurrent.TimeUnit.MINUTES; + import info.bitrich.xchangestream.bitstamp.v2.BitstampStreamingExchange; import info.bitrich.xchangestream.bitstamp.v2.BitstampStreamingMarketDataService; import info.bitrich.xchangestream.core.StreamingExchange; @@ -12,47 +14,58 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.util.concurrent.TimeUnit.MINUTES; - public class BitstampManualExample { - private static final Logger LOG = LoggerFactory.getLogger(BitstampManualExample.class); - - private static final TimedSemaphore rateLimiter = new TimedSemaphore(1, MINUTES, 20); - private static void rateLimit() { - try { - rateLimiter.acquire(); - } catch (InterruptedException e) { - LOG.warn("Bitstamp connection throttle control has been interrupted"); - } - } + private static final Logger LOG = LoggerFactory.getLogger(BitstampManualExample.class); - public static void main(String[] args) { + private static final TimedSemaphore rateLimiter = new TimedSemaphore(1, MINUTES, 20); - ExchangeSpecification defaultExchangeSpecification = new ExchangeSpecification(BitstampStreamingExchange.class); - defaultExchangeSpecification.setExchangeSpecificParametersItem(ConnectableService.BEFORE_CONNECTION_HANDLER, (Runnable) BitstampManualExample::rateLimit); + private static void rateLimit() { + try { + rateLimiter.acquire(); + } catch (InterruptedException e) { + LOG.warn("Bitstamp connection throttle control has been interrupted"); + } + } + + public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(defaultExchangeSpecification); - exchange.connect().blockingAwait(); + ExchangeSpecification defaultExchangeSpecification = + new ExchangeSpecification(BitstampStreamingExchange.class); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + ConnectableService.BEFORE_CONNECTION_HANDLER, (Runnable) BitstampManualExample::rateLimit); - Disposable orderBookDisposable = ((BitstampStreamingMarketDataService) exchange.getStreamingMarketDataService()).getFullOrderBook(CurrencyPair.BTC_USD).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }); + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(defaultExchangeSpecification); + exchange.connect().blockingAwait(); - Disposable tradesDisposable = exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(trade -> { - LOG.info("Trade {}", trade); - }); + Disposable orderBookDisposable = + ((BitstampStreamingMarketDataService) exchange.getStreamingMarketDataService()) + .getFullOrderBook(CurrencyPair.BTC_USD) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + Disposable tradesDisposable = + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + trade -> { + LOG.info("Trade {}", trade); + }); - orderBookDisposable.dispose(); - tradesDisposable.dispose(); - exchange.disconnect().subscribe(() -> LOG.info("Disconnected from the Exchange")); - - rateLimiter.shutdown(); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + + orderBookDisposable.dispose(); + tradesDisposable.dispose(); + exchange.disconnect().subscribe(() -> LOG.info("Disconnected from the Exchange")); + + rateLimiter.shutdown(); + } } diff --git a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceBaseTest.java b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceBaseTest.java index b5ea517bf..0f166dbbb 100644 --- a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceBaseTest.java +++ b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceBaseTest.java @@ -1,38 +1,43 @@ package info.bitrich.xchangestream.bitstamp; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.observers.TestObserver; +import java.util.List; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.dto.trade.LimitOrder; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - public class BitstampStreamingMarketDataServiceBaseTest { - protected ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - protected void validateTrades(Trade expected, TestObserver test) { - test.assertValue(trade1 -> { - assertThat(trade1.getId()).as("Id").isEqualTo(expected.getId()); - assertThat(trade1.getCurrencyPair()).as("Currency pair").isEqualTo(expected.getCurrencyPair()); - assertThat(trade1.getPrice()).as("Price").isEqualTo(expected.getPrice()); - // assertThat(trade1.getTimestamp()).as("Timestamp").isEqualTo(expected.getTimestamp()); - assertThat(trade1.getOriginalAmount()).as("Amount").isEqualTo(expected.getOriginalAmount()); - assertThat(trade1.getType()).as("Type").isEqualTo(expected.getType()); - return true; + protected ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + protected void validateTrades(Trade expected, TestObserver test) { + test.assertValue( + trade1 -> { + assertThat(trade1.getId()).as("Id").isEqualTo(expected.getId()); + assertThat(trade1.getCurrencyPair()) + .as("Currency pair") + .isEqualTo(expected.getCurrencyPair()); + assertThat(trade1.getPrice()).as("Price").isEqualTo(expected.getPrice()); + // assertThat(trade1.getTimestamp()).as("Timestamp").isEqualTo(expected.getTimestamp()); + assertThat(trade1.getOriginalAmount()) + .as("Amount") + .isEqualTo(expected.getOriginalAmount()); + assertThat(trade1.getType()).as("Type").isEqualTo(expected.getType()); + return true; }); - } - protected void validateOrderBook(List bids, List asks, TestObserver test) { - test.assertValue(orderBook1 -> { - assertThat(orderBook1.getAsks()).as("Asks").isEqualTo(asks); - assertThat(orderBook1.getBids()).as("Bids").isEqualTo(bids); - return true; + } + + protected void validateOrderBook( + List bids, List asks, TestObserver test) { + test.assertValue( + orderBook1 -> { + assertThat(orderBook1.getAsks()).as("Asks").isEqualTo(asks); + assertThat(orderBook1.getBids()).as("Bids").isEqualTo(bids); + return true; }); - } - - + } } diff --git a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceTest.java b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceTest.java index d414ea536..47756553e 100644 --- a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceTest.java +++ b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceTest.java @@ -1,8 +1,18 @@ package info.bitrich.xchangestream.bitstamp; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + import info.bitrich.xchangestream.service.pusher.PusherStreamingService; import io.reactivex.Observable; import io.reactivex.observers.TestObserver; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; @@ -14,68 +24,104 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.math.BigDecimal; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.function.Supplier; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class BitstampStreamingMarketDataServiceTest extends BitstampStreamingMarketDataServiceBaseTest { - @Mock - private PusherStreamingService streamingService; - private BitstampStreamingMarketDataService marketDataService; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - marketDataService = new BitstampStreamingMarketDataService(streamingService); - } - - public void testOrderbookCommon(String channelName, Supplier> updater) throws Exception { - // Given order book in JSON - String orderBook = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("order-book.json").toURI()))); - - when(streamingService.subscribeChannel(eq(channelName), eq("data"))).thenReturn(Observable.just(orderBook)); - - List bids = new ArrayList<>(); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.922"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("819.9"))); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.085"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("818.63"))); - - List asks = new ArrayList<>(); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("2.89"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("821.7"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("5.18"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("821.65"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.035"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("821.6"))); - - // Call get order book observable - TestObserver test = updater.get(); - - // We get order book object in correct order - validateOrderBook(bids, asks, test); - } - - @Test - public void testGetDifferentialOrderBook() throws Exception { - testOrderbookCommon("diff_order_book_btceur", () -> marketDataService.getDifferentialOrderBook(CurrencyPair.BTC_EUR).test()); - } - - @Test - public void testGetOrderBook() throws Exception { - testOrderbookCommon("order_book_btceur", () -> marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test()); - } - - @Test - public void testGetTrades() throws Exception { - // Given order book in JSON - String trade = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("trade.json").toURI()))); - - when(streamingService.subscribeChannel(eq("live_trades"), eq("trade"))).thenReturn(Observable.just(trade)); - - Trade expected = new Trade.Builder() +public class BitstampStreamingMarketDataServiceTest + extends BitstampStreamingMarketDataServiceBaseTest { + @Mock private PusherStreamingService streamingService; + private BitstampStreamingMarketDataService marketDataService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + marketDataService = new BitstampStreamingMarketDataService(streamingService); + } + + public void testOrderbookCommon(String channelName, Supplier> updater) + throws Exception { + // Given order book in JSON + String orderBook = + new String( + Files.readAllBytes( + Paths.get(ClassLoader.getSystemResource("order-book.json").toURI()))); + + when(streamingService.subscribeChannel(eq(channelName), eq("data"))) + .thenReturn(Observable.just(orderBook)); + + List bids = new ArrayList<>(); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.922"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("819.9"))); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.085"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("818.63"))); + + List asks = new ArrayList<>(); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("2.89"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("821.7"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("5.18"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("821.65"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.035"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("821.6"))); + + // Call get order book observable + TestObserver test = updater.get(); + + // We get order book object in correct order + validateOrderBook(bids, asks, test); + } + + @Test + public void testGetDifferentialOrderBook() throws Exception { + testOrderbookCommon( + "diff_order_book_btceur", + () -> marketDataService.getDifferentialOrderBook(CurrencyPair.BTC_EUR).test()); + } + + @Test + public void testGetOrderBook() throws Exception { + testOrderbookCommon( + "order_book_btceur", () -> marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test()); + } + + @Test + public void testGetTrades() throws Exception { + // Given order book in JSON + String trade = + new String( + Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("trade.json").toURI()))); + + when(streamingService.subscribeChannel(eq("live_trades"), eq("trade"))) + .thenReturn(Observable.just(trade)); + + Trade expected = + new Trade.Builder() .type(Order.OrderType.ASK) .originalAmount(new BigDecimal("34.390000000000001")) .currencyPair(CurrencyPair.BTC_USD) @@ -84,15 +130,15 @@ public void testGetTrades() throws Exception { .id("177827396") .build(); - // Call get order book observable - TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_USD).test(); + // Call get order book observable + TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_USD).test(); - // We get order book object in correct order - validateTrades(expected, test); - } + // We get order book object in correct order + validateTrades(expected, test); + } - @Test(expected = NotAvailableFromExchangeException.class) - public void testGetTicker() { - marketDataService.getTicker(CurrencyPair.BTC_EUR).test(); - } -} \ No newline at end of file + @Test(expected = NotAvailableFromExchangeException.class) + public void testGetTicker() { + marketDataService.getTicker(CurrencyPair.BTC_EUR).test(); + } +} diff --git a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceV2Test.java b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceV2Test.java index 8b5dd44e6..cdc5965aa 100644 --- a/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceV2Test.java +++ b/xchange-stream-bitstamp/src/test/java/info/bitrich/xchangestream/bitstamp/BitstampStreamingMarketDataServiceV2Test.java @@ -1,10 +1,18 @@ package info.bitrich.xchangestream.bitstamp; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + import com.fasterxml.jackson.databind.JsonNode; import info.bitrich.xchangestream.bitstamp.v2.BitstampStreamingMarketDataService; import info.bitrich.xchangestream.bitstamp.v2.BitstampStreamingService; import io.reactivex.Observable; import io.reactivex.observers.TestObserver; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; @@ -16,66 +24,99 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.function.Supplier; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class BitstampStreamingMarketDataServiceV2Test extends BitstampStreamingMarketDataServiceBaseTest { - @Mock - private BitstampStreamingService streamingService; - private BitstampStreamingMarketDataService marketDataService; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - marketDataService = new BitstampStreamingMarketDataService(streamingService); - } - - public void testOrderbookCommon(String channelName, Supplier> updater) throws Exception { - // Given order book in JSON - JsonNode orderBook = mapper.readTree(this.getClass().getResource("/order-book-v2.json")); - - when(streamingService.subscribeChannel(eq(channelName), eq("data"))).thenReturn(Observable.just(orderBook)); - - List bids = new ArrayList<>(); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.922"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("819.9"))); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.085"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("818.63"))); - - List asks = new ArrayList<>(); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("2.89"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("821.7"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("5.18"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("821.65"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.035"), CurrencyPair.BTC_EUR, "", null, new BigDecimal("821.6"))); - - // Call get order book observable - TestObserver test = updater.get(); - - // We get order book object in correct order - validateOrderBook(bids, asks, test); - } - - @Test - public void testGetDifferentialOrderBook() throws Exception { - testOrderbookCommon("diff_order_book_btceur", () -> marketDataService.getFullOrderBook(CurrencyPair.BTC_EUR).test()); - } - - @Test - public void testGetOrderBook() throws Exception { - testOrderbookCommon("order_book_btceur", () -> marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test()); - } - - @Test - public void testGetTrades() throws Exception { - // Given order book in JSON - JsonNode trade = mapper.readTree(this.getClass().getResource("/trade-v2.json")); - - when(streamingService.subscribeChannel(eq("live_trades_btcusd"), eq("trade"))).thenReturn(Observable.just(trade)); - - Trade expected = new Trade.Builder() +public class BitstampStreamingMarketDataServiceV2Test + extends BitstampStreamingMarketDataServiceBaseTest { + @Mock private BitstampStreamingService streamingService; + private BitstampStreamingMarketDataService marketDataService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + marketDataService = new BitstampStreamingMarketDataService(streamingService); + } + + public void testOrderbookCommon(String channelName, Supplier> updater) + throws Exception { + // Given order book in JSON + JsonNode orderBook = mapper.readTree(this.getClass().getResource("/order-book-v2.json")); + + when(streamingService.subscribeChannel(eq(channelName), eq("data"))) + .thenReturn(Observable.just(orderBook)); + + List bids = new ArrayList<>(); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.922"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("819.9"))); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.085"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("818.63"))); + + List asks = new ArrayList<>(); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("2.89"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("821.7"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("5.18"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("821.65"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.035"), + CurrencyPair.BTC_EUR, + "", + null, + new BigDecimal("821.6"))); + + // Call get order book observable + TestObserver test = updater.get(); + + // We get order book object in correct order + validateOrderBook(bids, asks, test); + } + + @Test + public void testGetDifferentialOrderBook() throws Exception { + testOrderbookCommon( + "diff_order_book_btceur", + () -> marketDataService.getFullOrderBook(CurrencyPair.BTC_EUR).test()); + } + + @Test + public void testGetOrderBook() throws Exception { + testOrderbookCommon( + "order_book_btceur", () -> marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test()); + } + + @Test + public void testGetTrades() throws Exception { + // Given order book in JSON + JsonNode trade = mapper.readTree(this.getClass().getResource("/trade-v2.json")); + + when(streamingService.subscribeChannel(eq("live_trades_btcusd"), eq("trade"))) + .thenReturn(Observable.just(trade)); + + Trade expected = + new Trade.Builder() .type(Order.OrderType.ASK) .originalAmount(new BigDecimal("34.390000000000001")) .currencyPair(CurrencyPair.BTC_USD) @@ -84,15 +125,15 @@ public void testGetTrades() throws Exception { .id("177827396") .build(); - // Call get order book observable - TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_USD).test(); + // Call get order book observable + TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_USD).test(); - // We get order book object in correct order - validateTrades(expected, test); - } + // We get order book object in correct order + validateTrades(expected, test); + } - @Test(expected = NotAvailableFromExchangeException.class) - public void testGetTicker() throws Exception { - marketDataService.getTicker(CurrencyPair.BTC_EUR).test(); - } -} \ No newline at end of file + @Test(expected = NotAvailableFromExchangeException.class) + public void testGetTicker() throws Exception { + marketDataService.getTicker(CurrencyPair.BTC_EUR).test(); + } +} diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingAdapters.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingAdapters.java index 1b074ca08..6600cd2c7 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingAdapters.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingAdapters.java @@ -1,11 +1,11 @@ package info.bitrich.xchangestream.coinbasepro; +import info.bitrich.xchangestream.coinbasepro.dto.CoinbaseProWebSocketTransaction; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; - import org.knowm.xchange.coinbasepro.CoinbaseProAdapters; import org.knowm.xchange.coinbasepro.dto.trade.CoinbaseProOrder; import org.knowm.xchange.currency.CurrencyPair; @@ -16,108 +16,101 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.bitrich.xchangestream.coinbasepro.dto.CoinbaseProWebSocketTransaction; - public class CoinbaseProStreamingAdapters { - private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProStreamingAdapters.class); + private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProStreamingAdapters.class); - /** - * TODO this clearly isn't good enough. We need an initial snapshot that these - * can build on. - */ - static Order adaptOrder(CoinbaseProWebSocketTransaction s) { - switch (s.getType()) { - case "activate": - case "received": - return CoinbaseProAdapters.adaptOrder( - new CoinbaseProOrder( - s.getOrderId(), - s.getPrice(), - s.getSize() == null ? BigDecimal.ZERO : s.getSize() , - s.getProductId(), - s.getSide(), - s.getTime(), //createdAt, - null, //doneAt, - BigDecimal.ZERO, // filled size - null, // fees - s.getType(), // status - TODO no clean mapping atm - false, // settled - s.getType().equals("received") ? "limit" : "stop", // type. TODO market orders - null, // doneReason - null, - null, // stop TODO no source for this - null // stopPrice - ) - ); - default: - OrderType type = s.getSide().equals("buy") ? OrderType.BID : OrderType.ASK; - CurrencyPair currencyPair = new CurrencyPair(s.getProductId().replace('-', '/')); - return new LimitOrder.Builder(type, currencyPair) - .id(s.getOrderId()) - .orderStatus(adaptOrderStatus(s)) - .build(); - } + /** TODO this clearly isn't good enough. We need an initial snapshot that these can build on. */ + static Order adaptOrder(CoinbaseProWebSocketTransaction s) { + switch (s.getType()) { + case "activate": + case "received": + return CoinbaseProAdapters.adaptOrder( + new CoinbaseProOrder( + s.getOrderId(), + s.getPrice(), + s.getSize() == null ? BigDecimal.ZERO : s.getSize(), + s.getProductId(), + s.getSide(), + s.getTime(), // createdAt, + null, // doneAt, + BigDecimal.ZERO, // filled size + null, // fees + s.getType(), // status - TODO no clean mapping atm + false, // settled + s.getType().equals("received") ? "limit" : "stop", // type. TODO market orders + null, // doneReason + null, + null, // stop TODO no source for this + null // stopPrice + )); + default: + OrderType type = s.getSide().equals("buy") ? OrderType.BID : OrderType.ASK; + CurrencyPair currencyPair = new CurrencyPair(s.getProductId().replace('-', '/')); + return new LimitOrder.Builder(type, currencyPair) + .id(s.getOrderId()) + .orderStatus(adaptOrderStatus(s)) + .build(); } + } - - private static OrderStatus adaptOrderStatus(CoinbaseProWebSocketTransaction s) { - if (s.getType().equals("done")) { - if (s.getReason().equals("canceled")) { - return OrderStatus.CANCELED; - } else { - return OrderStatus.FILLED; - } - } else if (s.getType().equals("match")) { - return OrderStatus.PARTIALLY_FILLED; - } else { - return OrderStatus.NEW; - } + private static OrderStatus adaptOrderStatus(CoinbaseProWebSocketTransaction s) { + if (s.getType().equals("done")) { + if (s.getReason().equals("canceled")) { + return OrderStatus.CANCELED; + } else { + return OrderStatus.FILLED; + } + } else if (s.getType().equals("match")) { + return OrderStatus.PARTIALLY_FILLED; + } else { + return OrderStatus.NEW; } + } - public static Date parseDate(final String rawDate) { + public static Date parseDate(final String rawDate) { - String modified; - if (rawDate.length() > 23) { - modified = rawDate.substring(0, 23); - } else if (rawDate.endsWith("Z")) { - switch (rawDate.length()) { - case 20: - modified = rawDate.substring(0, 19) + ".000"; - break; - case 22: - modified = rawDate.substring(0, 21) + "00"; - break; - case 23: - modified = rawDate.substring(0, 22) + "0"; - break; - default: - modified = rawDate; - break; - } - } else { - switch (rawDate.length()) { - case 19: - modified = rawDate + ".000"; - break; - case 21: - modified = rawDate + "00"; - break; - case 22: - modified = rawDate + "0"; - break; - default: - modified = rawDate; - break; - } + String modified; + if (rawDate.length() > 23) { + modified = rawDate.substring(0, 23); + } else if (rawDate.endsWith("Z")) { + switch (rawDate.length()) { + case 20: + modified = rawDate.substring(0, 19) + ".000"; + break; + case 22: + modified = rawDate.substring(0, 21) + "00"; + break; + case 23: + modified = rawDate.substring(0, 22) + "0"; + break; + default: + modified = rawDate; + break; } - try { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - return dateFormat.parse(modified); - } catch (ParseException e) { - LOG.warn("unable to parse rawDate={} modified={}", rawDate, modified, e); - return null; + } else { + switch (rawDate.length()) { + case 19: + modified = rawDate + ".000"; + break; + case 21: + modified = rawDate + "00"; + break; + case 22: + modified = rawDate + "0"; + break; + default: + modified = rawDate; + break; } } -} \ No newline at end of file + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat.parse(modified); + } catch (ParseException e) { + LOG.warn("unable to parse rawDate={} modified={}", rawDate, modified, e); + return null; + } + } +} diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingExchange.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingExchange.java index 90ce62178..682128aae 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingExchange.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingExchange.java @@ -12,120 +12,126 @@ import org.knowm.xchange.coinbasepro.service.CoinbaseProAccountServiceRaw; import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; -/** - * CoinbasePro Streaming Exchange. Connects to live WebSocket feed. - */ +/** CoinbasePro Streaming Exchange. Connects to live WebSocket feed. */ public class CoinbaseProStreamingExchange extends CoinbaseProExchange implements StreamingExchange { - private static final String API_URI = "wss://ws-feed.pro.coinbase.com"; - private static final String SANDBOX_API_URI = "wss://ws-feed-public.sandbox.pro.coinbase.com"; - private static final String PRIME_API_URI = "wss://ws-feed.prime.coinbase.com"; - private static final String PRIME_SANDBOX_API_URI = "wss://ws-feed-public.sandbox.prime.coinbase.com"; - - private CoinbaseProStreamingService streamingService; - private CoinbaseProStreamingMarketDataService streamingMarketDataService; - private CoinbaseProStreamingTradeService streamingTradeService; - - public CoinbaseProStreamingExchange() { } - - @Override - protected void initServices() { - super.initServices(); - } - - @Override - public Completable connect(ProductSubscription... args) { - if (args == null || args.length == 0) - throw new UnsupportedOperationException("The ProductSubscription must be defined!"); - ExchangeSpecification exchangeSpec = getExchangeSpecification(); - boolean useSandbox = Boolean.TRUE.equals(exchangeSpecification.getExchangeSpecificParametersItem("Use_Sandbox")); - boolean usePrime = Boolean.TRUE.equals(exchangeSpecification.getExchangeSpecificParametersItem("Use_Prime")); - - String apiUri; - if (useSandbox) { - apiUri = usePrime ? PRIME_SANDBOX_API_URI : SANDBOX_API_URI; - } else { - apiUri = usePrime ? PRIME_API_URI : API_URI; - } - - this.streamingService = new CoinbaseProStreamingService(apiUri, () -> authData(exchangeSpec)); - applyStreamingSpecification(exchangeSpecification, this.streamingService); - - this.streamingMarketDataService = new CoinbaseProStreamingMarketDataService(streamingService); - this.streamingTradeService = new CoinbaseProStreamingTradeService(streamingService); - streamingService.subscribeMultipleCurrencyPairs(args); - return streamingService.connect(); - } - - private CoinbaseProWebsocketAuthData authData(ExchangeSpecification exchangeSpec) { - CoinbaseProWebsocketAuthData authData = null; - if ( exchangeSpec.getApiKey() != null ) { - try { - CoinbaseProAccountServiceRaw rawAccountService = (CoinbaseProAccountServiceRaw) getAccountService(); - authData = rawAccountService.getWebsocketAuthData(); - } - catch (Exception e) { - logger.warn("Failed attempting to acquire Websocket AuthData needed for private data on" + - " websocket. Will only receive public information via API", e); - } - } - return authData; + private static final String API_URI = "wss://ws-feed.pro.coinbase.com"; + private static final String SANDBOX_API_URI = "wss://ws-feed-public.sandbox.pro.coinbase.com"; + private static final String PRIME_API_URI = "wss://ws-feed.prime.coinbase.com"; + private static final String PRIME_SANDBOX_API_URI = + "wss://ws-feed-public.sandbox.prime.coinbase.com"; + + private CoinbaseProStreamingService streamingService; + private CoinbaseProStreamingMarketDataService streamingMarketDataService; + private CoinbaseProStreamingTradeService streamingTradeService; + + public CoinbaseProStreamingExchange() {} + + @Override + protected void initServices() { + super.initServices(); + } + + @Override + public Completable connect(ProductSubscription... args) { + if (args == null || args.length == 0) + throw new UnsupportedOperationException("The ProductSubscription must be defined!"); + ExchangeSpecification exchangeSpec = getExchangeSpecification(); + boolean useSandbox = + Boolean.TRUE.equals(exchangeSpecification.getExchangeSpecificParametersItem("Use_Sandbox")); + boolean usePrime = + Boolean.TRUE.equals(exchangeSpecification.getExchangeSpecificParametersItem("Use_Prime")); + + String apiUri; + if (useSandbox) { + apiUri = usePrime ? PRIME_SANDBOX_API_URI : SANDBOX_API_URI; + } else { + apiUri = usePrime ? PRIME_API_URI : API_URI; } - @Override - public Completable disconnect() { - CoinbaseProStreamingService service = streamingService; - streamingService = null; - streamingMarketDataService = null; - return service.disconnect(); + this.streamingService = new CoinbaseProStreamingService(apiUri, () -> authData(exchangeSpec)); + applyStreamingSpecification(exchangeSpecification, this.streamingService); + + this.streamingMarketDataService = new CoinbaseProStreamingMarketDataService(streamingService); + this.streamingTradeService = new CoinbaseProStreamingTradeService(streamingService); + streamingService.subscribeMultipleCurrencyPairs(args); + return streamingService.connect(); + } + + private CoinbaseProWebsocketAuthData authData(ExchangeSpecification exchangeSpec) { + CoinbaseProWebsocketAuthData authData = null; + if (exchangeSpec.getApiKey() != null) { + try { + CoinbaseProAccountServiceRaw rawAccountService = + (CoinbaseProAccountServiceRaw) getAccountService(); + authData = rawAccountService.getWebsocketAuthData(); + } catch (Exception e) { + logger.warn( + "Failed attempting to acquire Websocket AuthData needed for private data on" + + " websocket. Will only receive public information via API", + e); + } } - - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } - - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } - - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); - - return spec; - } - - @Override - public CoinbaseProStreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public StreamingAccountService getStreamingAccountService() { - throw new NotYetImplementedForExchangeException(); - } - - @Override - public CoinbaseProStreamingTradeService getStreamingTradeService() { - return streamingTradeService; - } - - /** - * Enables the user to listen on channel inactive events and react appropriately. - * - * @param channelInactiveHandler a WebSocketMessageHandler instance. - */ - public void setChannelInactiveHandler(WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler) { - streamingService.setChannelInactiveHandler(channelInactiveHandler); - } - - @Override - public boolean isAlive() { - return streamingService != null && streamingService.isSocketOpen(); - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + return authData; + } + + @Override + public Completable disconnect() { + CoinbaseProStreamingService service = streamingService; + streamingService = null; + streamingMarketDataService = null; + return service.disconnect(); + } + + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } + + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } + + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); + + return spec; + } + + @Override + public CoinbaseProStreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public StreamingAccountService getStreamingAccountService() { + throw new NotYetImplementedForExchangeException(); + } + + @Override + public CoinbaseProStreamingTradeService getStreamingTradeService() { + return streamingTradeService; + } + + /** + * Enables the user to listen on channel inactive events and react appropriately. + * + * @param channelInactiveHandler a WebSocketMessageHandler instance. + */ + public void setChannelInactiveHandler( + WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler) { + streamingService.setChannelInactiveHandler(channelInactiveHandler); + } + + @Override + public boolean isAlive() { + return streamingService != null && streamingService.isSocketOpen(); + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingMarketDataService.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingMarketDataService.java index fb9a6f5be..84af974f2 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingMarketDataService.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingMarketDataService.java @@ -3,13 +3,15 @@ import static org.knowm.xchange.coinbasepro.CoinbaseProAdapters.adaptTicker; import static org.knowm.xchange.coinbasepro.CoinbaseProAdapters.adaptTrades; +import info.bitrich.xchangestream.coinbasepro.dto.CoinbaseProWebSocketTransaction; +import info.bitrich.xchangestream.core.StreamingMarketDataService; +import io.reactivex.Observable; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; - import org.knowm.xchange.coinbasepro.dto.marketdata.CoinbaseProProductTicker; import org.knowm.xchange.coinbasepro.dto.marketdata.CoinbaseProTrade; import org.knowm.xchange.currency.CurrencyPair; @@ -18,114 +20,119 @@ import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.dto.marketdata.Trades; -import info.bitrich.xchangestream.coinbasepro.dto.CoinbaseProWebSocketTransaction; -import info.bitrich.xchangestream.core.StreamingMarketDataService; -import io.reactivex.Observable; - -/** - * Created by luca on 4/3/17. - */ +/** Created by luca on 4/3/17. */ public class CoinbaseProStreamingMarketDataService implements StreamingMarketDataService { - private static final String SNAPSHOT = "snapshot"; - private static final String L2UPDATE = "l2update"; - private static final String TICKER = "ticker"; - private static final String MATCH = "match"; + private static final String SNAPSHOT = "snapshot"; + private static final String L2UPDATE = "l2update"; + private static final String TICKER = "ticker"; + private static final String MATCH = "match"; - private final CoinbaseProStreamingService service; + private final CoinbaseProStreamingService service; - private final Map> bids = new ConcurrentHashMap<>(); - private final Map> asks = new ConcurrentHashMap<>(); - - CoinbaseProStreamingMarketDataService(CoinbaseProStreamingService service) { - this.service = service; - } + private final Map> bids = + new ConcurrentHashMap<>(); + private final Map> asks = + new ConcurrentHashMap<>(); - private boolean containsPair(List pairs, CurrencyPair pair) { - for (CurrencyPair item : pairs) { - if (item.compareTo(pair) == 0) { - return true; - } - } + CoinbaseProStreamingMarketDataService(CoinbaseProStreamingService service) { + this.service = service; + } - return false; + private boolean containsPair(List pairs, CurrencyPair pair) { + for (CurrencyPair item : pairs) { + if (item.compareTo(pair) == 0) { + return true; + } } - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - if (!containsPair(service.getProduct().getOrderBook(), currencyPair)) - throw new UnsupportedOperationException(String.format("The currency pair %s is not subscribed for orderbook", currencyPair)); - final int maxDepth = (args.length > 0 && args[0] instanceof Number) ? ((Number) args[0]).intValue() : 100; - return getRawWebSocketTransactions(currencyPair, false) - .filter(message -> message.getType().equals(SNAPSHOT) || message.getType().equals(L2UPDATE)) - .map(s -> { - if (s.getType().equals(SNAPSHOT)) { - bids.put(currencyPair, new TreeMap<>(java.util.Collections.reverseOrder())); - asks.put(currencyPair, new TreeMap<>()); - } else { - bids.computeIfAbsent(currencyPair, k -> new TreeMap<>(java.util.Collections.reverseOrder())); - asks.computeIfAbsent(currencyPair, k -> new TreeMap<>()); - } - return s.toOrderBook( - bids.get(currencyPair), - asks.get(currencyPair), - maxDepth, - currencyPair); - }); - } - - /** - * Returns an Observable of {@link CoinbaseProProductTicker}, not converted to {@link Ticker} - * - * @param currencyPair the currency pair. - * @param args optional arguments. - * @return an Observable of {@link CoinbaseProProductTicker}. - */ - public Observable getRawTicker(CurrencyPair currencyPair, Object... args) { - if (!containsPair(service.getProduct().getTicker(), currencyPair)) - throw new UnsupportedOperationException(String.format("The currency pair %s is not subscribed for ticker", currencyPair)); - return getRawWebSocketTransactions(currencyPair, true) - .filter(message -> message.getType().equals(TICKER)) - .map(CoinbaseProWebSocketTransaction::toCoinbaseProProductTicker); - } - - /** - * Returns the CoinbasePro ticker converted to the normalized XChange object. - * CoinbasePro does not directly provide ticker data via web service. - * As stated by: https://docs.coinbasepro.com/#get-product-ticker, we can just listen for 'match' messages. - * - * @param currencyPair Currency pair of the ticker - * @param args optional parameters. - * @return an Observable of normalized Ticker objects. - */ - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - if (!containsPair(service.getProduct().getTicker(), currencyPair)) - throw new UnsupportedOperationException(String.format("The currency pair %s is not subscribed for ticker", currencyPair)); - return getRawWebSocketTransactions(currencyPair, true) - .filter(message -> message.getType().equals(TICKER)) - .map(s -> adaptTicker(s.toCoinbaseProProductTicker(), s.toCoinbaseProProductStats(), currencyPair)); - } - - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - if (!containsPair(service.getProduct().getTrades(), currencyPair)) - throw new UnsupportedOperationException(String.format("The currency pair %s is not subscribed for trades", currencyPair)); - return getRawWebSocketTransactions(currencyPair, true) - .filter(message -> message.getType().equals(MATCH)) - .filter((CoinbaseProWebSocketTransaction s) -> s.getUserId() == null) - .map((CoinbaseProWebSocketTransaction s) -> s.toCoinbaseProTrade()) - .map((CoinbaseProTrade t) -> adaptTrades(new CoinbaseProTrade[]{t}, currencyPair)) - .map((Trades h) -> h.getTrades().get(0)); - } - - /** - * Web socket transactions related to the specified currency, in their raw format. - * - * @param currencyPair The currency pair. - * @return The stream. - */ - public Observable getRawWebSocketTransactions(CurrencyPair currencyPair, boolean filterChannelName) { - return service.getRawWebSocketTransactions(currencyPair, filterChannelName); - } + return false; + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + if (!containsPair(service.getProduct().getOrderBook(), currencyPair)) + throw new UnsupportedOperationException( + String.format("The currency pair %s is not subscribed for orderbook", currencyPair)); + final int maxDepth = + (args.length > 0 && args[0] instanceof Number) ? ((Number) args[0]).intValue() : 100; + return getRawWebSocketTransactions(currencyPair, false) + .filter(message -> message.getType().equals(SNAPSHOT) || message.getType().equals(L2UPDATE)) + .map( + s -> { + if (s.getType().equals(SNAPSHOT)) { + bids.put(currencyPair, new TreeMap<>(java.util.Collections.reverseOrder())); + asks.put(currencyPair, new TreeMap<>()); + } else { + bids.computeIfAbsent( + currencyPair, k -> new TreeMap<>(java.util.Collections.reverseOrder())); + asks.computeIfAbsent(currencyPair, k -> new TreeMap<>()); + } + return s.toOrderBook( + bids.get(currencyPair), asks.get(currencyPair), maxDepth, currencyPair); + }); + } + + /** + * Returns an Observable of {@link CoinbaseProProductTicker}, not converted to {@link Ticker} + * + * @param currencyPair the currency pair. + * @param args optional arguments. + * @return an Observable of {@link CoinbaseProProductTicker}. + */ + public Observable getRawTicker( + CurrencyPair currencyPair, Object... args) { + if (!containsPair(service.getProduct().getTicker(), currencyPair)) + throw new UnsupportedOperationException( + String.format("The currency pair %s is not subscribed for ticker", currencyPair)); + return getRawWebSocketTransactions(currencyPair, true) + .filter(message -> message.getType().equals(TICKER)) + .map(CoinbaseProWebSocketTransaction::toCoinbaseProProductTicker); + } + + /** + * Returns the CoinbasePro ticker converted to the normalized XChange object. CoinbasePro does not + * directly provide ticker data via web service. As stated by: + * https://docs.coinbasepro.com/#get-product-ticker, we can just listen for 'match' messages. + * + * @param currencyPair Currency pair of the ticker + * @param args optional parameters. + * @return an Observable of normalized Ticker objects. + */ + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + if (!containsPair(service.getProduct().getTicker(), currencyPair)) + throw new UnsupportedOperationException( + String.format("The currency pair %s is not subscribed for ticker", currencyPair)); + return getRawWebSocketTransactions(currencyPair, true) + .filter(message -> message.getType().equals(TICKER)) + .map( + s -> + adaptTicker( + s.toCoinbaseProProductTicker(), s.toCoinbaseProProductStats(), currencyPair)); + } + + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + if (!containsPair(service.getProduct().getTrades(), currencyPair)) + throw new UnsupportedOperationException( + String.format("The currency pair %s is not subscribed for trades", currencyPair)); + return getRawWebSocketTransactions(currencyPair, true) + .filter(message -> message.getType().equals(MATCH)) + .filter((CoinbaseProWebSocketTransaction s) -> s.getUserId() == null) + .map((CoinbaseProWebSocketTransaction s) -> s.toCoinbaseProTrade()) + .map((CoinbaseProTrade t) -> adaptTrades(new CoinbaseProTrade[] {t}, currencyPair)) + .map((Trades h) -> h.getTrades().get(0)); + } + + /** + * Web socket transactions related to the specified currency, in their raw format. + * + * @param currencyPair The currency pair. + * @return The stream. + */ + public Observable getRawWebSocketTransactions( + CurrencyPair currencyPair, boolean filterChannelName) { + return service.getRawWebSocketTransactions(currencyPair, filterChannelName); + } } diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingService.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingService.java index 7122dcc2a..6477e28b0 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingService.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingService.java @@ -2,16 +2,6 @@ import static io.netty.util.internal.StringUtil.isNullOrEmpty; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -import org.knowm.xchange.coinbasepro.dto.account.CoinbaseProWebsocketAuthData; -import org.knowm.xchange.currency.CurrencyPair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.coinbasepro.dto.CoinbaseProWebSocketSubscriptionMessage; @@ -25,133 +15,148 @@ import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; import io.reactivex.Observable; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.knowm.xchange.coinbasepro.dto.account.CoinbaseProWebsocketAuthData; +import org.knowm.xchange.currency.CurrencyPair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class CoinbaseProStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProStreamingService.class); - private static final String SUBSCRIBE = "subscribe"; - private static final String UNSUBSCRIBE = "unsubscribe"; - private static final String SHARE_CHANNEL_NAME = "ALL"; - private final Map> subscriptions = new HashMap<>(); - private ProductSubscription product = null; - private final Supplier authData; - - private WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler = null; - - public CoinbaseProStreamingService(String apiUrl, Supplier authData) { - super(apiUrl, Integer.MAX_VALUE); - this.authData = authData; - } - - public ProductSubscription getProduct() { - return product; - } - - @Override - public String getSubscriptionUniqueId(String channelName, Object... args) { - return SHARE_CHANNEL_NAME; - } - - /** - * Subscribes to the provided channel name, maintains a cache of subscriptions, in order not to - * subscribe more than once to the same channel. - * - * @param channelName the name of the requested channel. - * @return an Observable of json objects coming from the exchange. - */ - @Override - public Observable subscribeChannel(String channelName, Object... args) { - channelName = SHARE_CHANNEL_NAME; - - if (!channels.containsKey(channelName) && !subscriptions.containsKey(channelName)) { - subscriptions.put(channelName, super.subscribeChannel(channelName, args)); - } - - return subscriptions.get(channelName); - } - - /** - * Subscribes to web socket transactions related to the specified currency, in their raw format. - * - * @param currencyPair The currency pair. - * @return The stream. - */ - public Observable getRawWebSocketTransactions(CurrencyPair currencyPair, boolean filterChannelName) { - String channelName = currencyPair.base.toString() + "-" + currencyPair.counter.toString(); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - return subscribeChannel(channelName) - .map(s -> mapper.readValue(s.toString(), CoinbaseProWebSocketTransaction.class)) - .filter(t -> channelName.equals(t.getProductId())) - .filter(t -> !isNullOrEmpty(t.getType())); - } - - boolean isAuthenticated() { - return authData.get() != null; - } - - @Override - protected String getChannelNameFromMessage(JsonNode message) { - return SHARE_CHANNEL_NAME; - } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - CoinbaseProWebSocketSubscriptionMessage subscribeMessage = new CoinbaseProWebSocketSubscriptionMessage(SUBSCRIBE, product, authData.get()); - return objectMapper.writeValueAsString(subscribeMessage); - } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - CoinbaseProWebSocketSubscriptionMessage subscribeMessage = - new CoinbaseProWebSocketSubscriptionMessage(UNSUBSCRIBE, new String[]{"level2", "matches", "ticker"}, authData.get()); - return objectMapper.writeValueAsString(subscribeMessage); + private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProStreamingService.class); + private static final String SUBSCRIBE = "subscribe"; + private static final String UNSUBSCRIBE = "unsubscribe"; + private static final String SHARE_CHANNEL_NAME = "ALL"; + private final Map> subscriptions = new HashMap<>(); + private ProductSubscription product = null; + private final Supplier authData; + + private WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler = null; + + public CoinbaseProStreamingService( + String apiUrl, Supplier authData) { + super(apiUrl, Integer.MAX_VALUE); + this.authData = authData; + } + + public ProductSubscription getProduct() { + return product; + } + + @Override + public String getSubscriptionUniqueId(String channelName, Object... args) { + return SHARE_CHANNEL_NAME; + } + + /** + * Subscribes to the provided channel name, maintains a cache of subscriptions, in order not to + * subscribe more than once to the same channel. + * + * @param channelName the name of the requested channel. + * @return an Observable of json objects coming from the exchange. + */ + @Override + public Observable subscribeChannel(String channelName, Object... args) { + channelName = SHARE_CHANNEL_NAME; + + if (!channels.containsKey(channelName) && !subscriptions.containsKey(channelName)) { + subscriptions.put(channelName, super.subscribeChannel(channelName, args)); } - @Override - protected void handleMessage(JsonNode message) { - super.handleMessage(message); + return subscriptions.get(channelName); + } + + /** + * Subscribes to web socket transactions related to the specified currency, in their raw format. + * + * @param currencyPair The currency pair. + * @return The stream. + */ + public Observable getRawWebSocketTransactions( + CurrencyPair currencyPair, boolean filterChannelName) { + String channelName = currencyPair.base.toString() + "-" + currencyPair.counter.toString(); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + return subscribeChannel(channelName) + .map(s -> mapper.readValue(s.toString(), CoinbaseProWebSocketTransaction.class)) + .filter(t -> channelName.equals(t.getProductId())) + .filter(t -> !isNullOrEmpty(t.getType())); + } + + boolean isAuthenticated() { + return authData.get() != null; + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) { + return SHARE_CHANNEL_NAME; + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + CoinbaseProWebSocketSubscriptionMessage subscribeMessage = + new CoinbaseProWebSocketSubscriptionMessage(SUBSCRIBE, product, authData.get()); + return objectMapper.writeValueAsString(subscribeMessage); + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + CoinbaseProWebSocketSubscriptionMessage subscribeMessage = + new CoinbaseProWebSocketSubscriptionMessage( + UNSUBSCRIBE, new String[] {"level2", "matches", "ticker"}, authData.get()); + return objectMapper.writeValueAsString(subscribeMessage); + } + + @Override + protected void handleMessage(JsonNode message) { + super.handleMessage(message); + } + + @Override + protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { + return WebSocketClientCompressionAllowClientNoContextHandler.INSTANCE; + } + + @Override + protected WebSocketClientHandler getWebSocketClientHandler( + WebSocketClientHandshaker handshaker, + WebSocketClientHandler.WebSocketMessageHandler handler) { + LOG.info("Registering CoinbaseProWebSocketClientHandler"); + return new CoinbaseProWebSocketClientHandler(handshaker, handler); + } + + public void setChannelInactiveHandler( + WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler) { + this.channelInactiveHandler = channelInactiveHandler; + } + + public void subscribeMultipleCurrencyPairs(ProductSubscription... products) { + this.product = products[0]; + } + + /** + * Custom client handler in order to execute an external, user-provided handler on channel events. + * This is useful because it seems CoinbasePro unexpectedly closes the web socket connection. + */ + class CoinbaseProWebSocketClientHandler extends NettyWebSocketClientHandler { + + public CoinbaseProWebSocketClientHandler( + WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { + super(handshaker, handler); } @Override - protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { - return WebSocketClientCompressionAllowClientNoContextHandler.INSTANCE; + public void channelActive(ChannelHandlerContext ctx) { + super.channelActive(ctx); } @Override - protected WebSocketClientHandler getWebSocketClientHandler(WebSocketClientHandshaker handshaker, - WebSocketClientHandler.WebSocketMessageHandler handler) { - LOG.info("Registering CoinbaseProWebSocketClientHandler"); - return new CoinbaseProWebSocketClientHandler(handshaker, handler); - } - - public void setChannelInactiveHandler(WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler) { - this.channelInactiveHandler = channelInactiveHandler; - } - - public void subscribeMultipleCurrencyPairs(ProductSubscription... products) { - this.product = products[0]; - } - - /** - * Custom client handler in order to execute an external, user-provided handler on channel events. - * This is useful because it seems CoinbasePro unexpectedly closes the web socket connection. - */ - class CoinbaseProWebSocketClientHandler extends NettyWebSocketClientHandler { - - public CoinbaseProWebSocketClientHandler(WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { - super(handshaker, handler); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) { - super.channelActive(ctx); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - super.channelInactive(ctx); - if (channelInactiveHandler != null) { - channelInactiveHandler.onMessage("WebSocket Client disconnected!"); - } - } + public void channelInactive(ChannelHandlerContext ctx) { + super.channelInactive(ctx); + if (channelInactiveHandler != null) { + channelInactiveHandler.onMessage("WebSocket Client disconnected!"); + } } + } } diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingTradeService.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingTradeService.java index d1a6063ee..ac1c8dbe6 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingTradeService.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProStreamingTradeService.java @@ -2,6 +2,10 @@ import static org.knowm.xchange.coinbasepro.CoinbaseProAdapters.adaptTradeHistory; +import info.bitrich.xchangestream.coinbasepro.dto.CoinbaseProWebSocketTransaction; +import info.bitrich.xchangestream.core.StreamingTradeService; +import io.reactivex.Observable; +import java.util.List; import org.knowm.xchange.coinbasepro.dto.trade.CoinbaseProFill; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; @@ -11,80 +15,80 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - -import info.bitrich.xchangestream.coinbasepro.dto.CoinbaseProWebSocketTransaction; -import info.bitrich.xchangestream.core.StreamingTradeService; -import io.reactivex.Observable; - public class CoinbaseProStreamingTradeService implements StreamingTradeService { - private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProStreamingTradeService.class); + private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProStreamingTradeService.class); - private static final String MATCH = "match"; + private static final String MATCH = "match"; - private final CoinbaseProStreamingService service; + private final CoinbaseProStreamingService service; - CoinbaseProStreamingTradeService(CoinbaseProStreamingService service) { - this.service = service; + CoinbaseProStreamingTradeService(CoinbaseProStreamingService service) { + this.service = service; + } + + private boolean containsPair(List pairs, CurrencyPair pair) { + for (CurrencyPair item : pairs) { + if (item.compareTo(pair) == 0) { + return true; + } } - private boolean containsPair(List pairs, CurrencyPair pair) { - for (CurrencyPair item : pairs) { - if (item.compareTo(pair) == 0) { - return true; - } - } + return false; + } - return false; + @Override + public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + if (!containsPair(service.getProduct().getUserTrades(), currencyPair)) + throw new UnsupportedOperationException( + String.format("The currency pair %s is not subscribed for user trades", currencyPair)); + if (!service.isAuthenticated()) { + throw new ExchangeSecurityException("Not authenticated"); } + return service + .getRawWebSocketTransactions(currencyPair, true) + .filter(message -> message.getType().equals(MATCH)) + .filter((CoinbaseProWebSocketTransaction s) -> s.getUserId() != null) + .map((CoinbaseProWebSocketTransaction s) -> s.toCoinbaseProFill()) + .map((CoinbaseProFill f) -> adaptTradeHistory(new CoinbaseProFill[] {f})) + .map((UserTrades h) -> h.getUserTrades().get(0)); + } - @Override - public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { - if (!containsPair(service.getProduct().getUserTrades(), currencyPair)) - throw new UnsupportedOperationException(String.format("The currency pair %s is not subscribed for user trades", currencyPair)); - if (!service.isAuthenticated()) { - throw new ExchangeSecurityException("Not authenticated"); - } - return service.getRawWebSocketTransactions(currencyPair, true) - .filter(message -> message.getType().equals(MATCH)) - .filter((CoinbaseProWebSocketTransaction s) -> s.getUserId() != null) - .map((CoinbaseProWebSocketTransaction s) -> s.toCoinbaseProFill()) - .map((CoinbaseProFill f) -> adaptTradeHistory(new CoinbaseProFill[]{f})) - .map((UserTrades h) -> h.getUserTrades().get(0)); + private boolean orderChangesWarningLogged; + /** + * Warning: the order change stream is not yet fully implemented for Coinbase + * Pro. Orders are not fully populated, containing only the values changed since the last update. + * Other values will be null. + */ + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + if (!containsPair(service.getProduct().getOrders(), currencyPair)) + throw new UnsupportedOperationException( + String.format("The currency pair %s is not subscribed for orders", currencyPair)); + if (!service.isAuthenticated()) { + throw new ExchangeSecurityException("Not authenticated"); } - - private boolean orderChangesWarningLogged; - /** - *

Warning: the order change stream is not yet fully - * implemented for Coinbase Pro. Orders are not fully populated, containing only - * the values changed since the last update. Other values will be null.

- */ - @Override - public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { - if (!containsPair(service.getProduct().getOrders(), currencyPair)) - throw new UnsupportedOperationException(String.format("The currency pair %s is not subscribed for orders", currencyPair)); - if (!service.isAuthenticated()) { - throw new ExchangeSecurityException("Not authenticated"); - } - if (!orderChangesWarningLogged) { - LOG.warn("The order change stream is not yet fully implemented for Coinbase Pro. " - + "Orders are not fully populated, containing only the values changed since " - + "the last update. Other values will be null."); - orderChangesWarningLogged = true; - } - return service.getRawWebSocketTransactions(currencyPair, true) - .filter(s -> s.getUserId() != null) - .map(CoinbaseProStreamingAdapters::adaptOrder); + if (!orderChangesWarningLogged) { + LOG.warn( + "The order change stream is not yet fully implemented for Coinbase Pro. " + + "Orders are not fully populated, containing only the values changed since " + + "the last update. Other values will be null."); + orderChangesWarningLogged = true; } + return service + .getRawWebSocketTransactions(currencyPair, true) + .filter(s -> s.getUserId() != null) + .map(CoinbaseProStreamingAdapters::adaptOrder); + } - /** - * Web socket transactions related to the specified currency, in their raw format. - * - * @param currencyPair The currency pair. - * @return The stream. - */ - public Observable getRawWebSocketTransactions(CurrencyPair currencyPair, boolean filterChannelName) { - return service.getRawWebSocketTransactions(currencyPair, filterChannelName); - } + /** + * Web socket transactions related to the specified currency, in their raw format. + * + * @param currencyPair The currency pair. + * @return The stream. + */ + public Observable getRawWebSocketTransactions( + CurrencyPair currencyPair, boolean filterChannelName) { + return service.getRawWebSocketTransactions(currencyPair, filterChannelName); + } } diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessage.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessage.java index 9d40e174a..e45f4b20b 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessage.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessage.java @@ -1,155 +1,159 @@ package info.bitrich.xchangestream.coinbasepro.dto; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import info.bitrich.xchangestream.core.ProductSubscription; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; - import org.knowm.xchange.coinbasepro.dto.account.CoinbaseProWebsocketAuthData; import org.knowm.xchange.currency.CurrencyPair; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -import info.bitrich.xchangestream.core.ProductSubscription; - -/** - * CoinbasePro subscription message. - */ +/** CoinbasePro subscription message. */ public class CoinbaseProWebSocketSubscriptionMessage { - public static final String TYPE = "type"; - public static final String CHANNELS = "channels"; - public static final String PRODUCT_IDS = "product_ids"; - public static final String NAME = "name"; + public static final String TYPE = "type"; + public static final String CHANNELS = "channels"; + public static final String PRODUCT_IDS = "product_ids"; + public static final String NAME = "name"; - // if authenticating - public static final String SIGNATURE = "signature"; - public static final String KEY = "key"; - public static final String PASSPHRASE = "passphrase"; - public static final String TIMESTAMP = "timestamp"; + // if authenticating + public static final String SIGNATURE = "signature"; + public static final String KEY = "key"; + public static final String PASSPHRASE = "passphrase"; + public static final String TIMESTAMP = "timestamp"; - class CoinbaseProProductSubsctiption { - @JsonProperty(NAME) - private final String name; + class CoinbaseProProductSubsctiption { + @JsonProperty(NAME) + private final String name; - @JsonProperty(PRODUCT_IDS) - private final String[] productIds; + @JsonProperty(PRODUCT_IDS) + private final String[] productIds; - public CoinbaseProProductSubsctiption(String name, String[] productIds, CoinbaseProWebsocketAuthData authData) { - this.name = name; - this.productIds = productIds; - } - - public String getName() { - return name; - } - - public String[] getProductIds() { - return productIds; - } + public CoinbaseProProductSubsctiption( + String name, String[] productIds, CoinbaseProWebsocketAuthData authData) { + this.name = name; + this.productIds = productIds; } - @JsonProperty(TYPE) - private final String type; - - @JsonProperty(CHANNELS) - private CoinbaseProProductSubsctiption[] channels; - - @JsonInclude(JsonInclude.Include.NON_EMPTY) - @JsonProperty(SIGNATURE) - String signature; - - @JsonInclude(JsonInclude.Include.NON_EMPTY) - @JsonProperty(KEY) - String key; - - @JsonInclude(JsonInclude.Include.NON_EMPTY) - @JsonProperty(PASSPHRASE) - String passphrase; - - @JsonInclude(JsonInclude.Include.NON_EMPTY) - @JsonProperty(TIMESTAMP) - String timestamp; - - public CoinbaseProWebSocketSubscriptionMessage(String type, ProductSubscription product, CoinbaseProWebsocketAuthData authData) { - this.type = type; - generateSubscriptionMessage(product, authData); + public String getName() { + return name; } - public CoinbaseProWebSocketSubscriptionMessage(String type, String[] channelNames, CoinbaseProWebsocketAuthData authData) { - this.type = type; - generateSubscriptionMessage(channelNames, authData); + public String[] getProductIds() { + return productIds; } - - private String[] generateProductIds(CurrencyPair[] pairs) { - List productIds = new ArrayList<>(pairs.length); - for (CurrencyPair pair : pairs) { - productIds.add(pair.base.toString() + "-" + pair.counter.toString()); - } - - return productIds.toArray(new String[productIds.size()]); + } + + @JsonProperty(TYPE) + private final String type; + + @JsonProperty(CHANNELS) + private CoinbaseProProductSubsctiption[] channels; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty(SIGNATURE) + String signature; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty(KEY) + String key; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty(PASSPHRASE) + String passphrase; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty(TIMESTAMP) + String timestamp; + + public CoinbaseProWebSocketSubscriptionMessage( + String type, ProductSubscription product, CoinbaseProWebsocketAuthData authData) { + this.type = type; + generateSubscriptionMessage(product, authData); + } + + public CoinbaseProWebSocketSubscriptionMessage( + String type, String[] channelNames, CoinbaseProWebsocketAuthData authData) { + this.type = type; + generateSubscriptionMessage(channelNames, authData); + } + + private String[] generateProductIds(CurrencyPair[] pairs) { + List productIds = new ArrayList<>(pairs.length); + for (CurrencyPair pair : pairs) { + productIds.add(pair.base.toString() + "-" + pair.counter.toString()); } - private CoinbaseProProductSubsctiption generateCoinbaseProProduct(String name, CurrencyPair[] pairs, CoinbaseProWebsocketAuthData authData) { - String[] productsIds; - productsIds = generateProductIds(pairs); - return new CoinbaseProProductSubsctiption(name, productsIds, authData); + return productIds.toArray(new String[productIds.size()]); + } + + private CoinbaseProProductSubsctiption generateCoinbaseProProduct( + String name, CurrencyPair[] pairs, CoinbaseProWebsocketAuthData authData) { + String[] productsIds; + productsIds = generateProductIds(pairs); + return new CoinbaseProProductSubsctiption(name, productsIds, authData); + } + + private void generateSubscriptionMessage( + String[] channelNames, CoinbaseProWebsocketAuthData authData) { + List channels = new ArrayList<>(3); + for (String name : channelNames) { + channels.add(new CoinbaseProProductSubsctiption(name, null, authData)); } - private void generateSubscriptionMessage(String[] channelNames, CoinbaseProWebsocketAuthData authData) { - List channels = new ArrayList<>(3); - for (String name : channelNames) { - channels.add(new CoinbaseProProductSubsctiption(name, null, authData)); - } - - this.channels = channels.toArray(new CoinbaseProProductSubsctiption[channels.size()]); + this.channels = channels.toArray(new CoinbaseProProductSubsctiption[channels.size()]); + } + + private void generateSubscriptionMessage( + ProductSubscription productSubscription, CoinbaseProWebsocketAuthData authData) { + List channels = new ArrayList<>(3); + Map> pairs = new HashMap<>(3); + + pairs.put("level2", productSubscription.getOrderBook()); + pairs.put("ticker", productSubscription.getTicker()); + pairs.put("matches", productSubscription.getTrades()); + if (authData != null) { + ArrayList userCurrencies = new ArrayList<>(); + Stream.of( + productSubscription.getUserTrades().stream(), + productSubscription.getOrders().stream()) + .flatMap(s -> s) + .distinct() + .forEach(userCurrencies::add); + pairs.put("user", userCurrencies); } - private void generateSubscriptionMessage(ProductSubscription productSubscription, CoinbaseProWebsocketAuthData authData) { - List channels = new ArrayList<>(3); - Map> pairs = new HashMap<>(3); - - pairs.put("level2", productSubscription.getOrderBook()); - pairs.put("ticker", productSubscription.getTicker()); - pairs.put("matches", productSubscription.getTrades()); - if ( authData != null ) { - ArrayList userCurrencies = new ArrayList<>(); - Stream.of( - productSubscription.getUserTrades().stream(), - productSubscription.getOrders().stream() - ) - .flatMap(s -> s) - .distinct() - .forEach(userCurrencies::add); - pairs.put("user", userCurrencies); - } - - for (Map.Entry> product : pairs.entrySet()) { - List currencyPairs = product.getValue(); - if (currencyPairs == null || currencyPairs.size() == 0) { - continue; - } - CoinbaseProProductSubsctiption gdaxProduct = generateCoinbaseProProduct(product.getKey(), product.getValue().toArray(new CurrencyPair[product.getValue().size()]), authData); - channels.add(gdaxProduct); - } - - this.channels = channels.toArray(new CoinbaseProProductSubsctiption[channels.size()]); - - if ( authData != null ) { - this.key = authData.getKey(); - this.passphrase = authData.getPassphrase(); - this.signature = authData.getSignature(); - this.timestamp = String.valueOf(authData.getTimestamp()); - } + for (Map.Entry> product : pairs.entrySet()) { + List currencyPairs = product.getValue(); + if (currencyPairs == null || currencyPairs.size() == 0) { + continue; + } + CoinbaseProProductSubsctiption gdaxProduct = + generateCoinbaseProProduct( + product.getKey(), + product.getValue().toArray(new CurrencyPair[product.getValue().size()]), + authData); + channels.add(gdaxProduct); } - public String getType() { - return type; - } + this.channels = channels.toArray(new CoinbaseProProductSubsctiption[channels.size()]); - public CoinbaseProProductSubsctiption[] getChannels() { - return channels; + if (authData != null) { + this.key = authData.getKey(); + this.passphrase = authData.getPassphrase(); + this.signature = authData.getSignature(); + this.timestamp = String.valueOf(authData.getTimestamp()); } + } + + public String getType() { + return type; + } + + public CoinbaseProProductSubsctiption[] getChannels() { + return channels; + } } diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketTransaction.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketTransaction.java index d6413d29e..f90803024 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketTransaction.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketTransaction.java @@ -1,5 +1,7 @@ package info.bitrich.xchangestream.coinbasepro.dto; +import com.fasterxml.jackson.annotation.JsonProperty; +import info.bitrich.xchangestream.coinbasepro.CoinbaseProStreamingAdapters; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Collections; @@ -10,7 +12,6 @@ import java.util.TimeZone; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.knowm.xchange.coinbasepro.dto.marketdata.CoinbaseProProductStats; import org.knowm.xchange.coinbasepro.dto.marketdata.CoinbaseProProductTicker; import org.knowm.xchange.coinbasepro.dto.marketdata.CoinbaseProTrade; @@ -20,331 +21,353 @@ import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.trade.LimitOrder; -import com.fasterxml.jackson.annotation.JsonProperty; - -import info.bitrich.xchangestream.coinbasepro.CoinbaseProStreamingAdapters; - -/** - * Domain object mapping a CoinbasePro web socket message. - */ +/** Domain object mapping a CoinbasePro web socket message. */ public class CoinbaseProWebSocketTransaction { - private final String type; - private final String orderId; - private final String orderType; - private final BigDecimal size; - private final BigDecimal remainingSize; - private final BigDecimal price; - private final BigDecimal bestBid; - private final BigDecimal bestAsk; - private final BigDecimal lastSize; - private final BigDecimal volume24h; - private final BigDecimal open24h; - private final BigDecimal low24h; - private final BigDecimal high24h; - private final String side; - private final String[][] bids; - private final String[][] asks; - private final String[][] changes; - private final String clientOid; - private final String productId; - private final long sequence; - private final String time; - private final String reason; - private final long tradeId; - private final String makerOrderId; - private final String takerOrderId; - - private final String takerUserId; - private final String userId; - private final String takerProfileId; - private final String profileId; - - public CoinbaseProWebSocketTransaction( - @JsonProperty("type") String type, - @JsonProperty("order_id") String orderId, - @JsonProperty("order_type") String orderType, - @JsonProperty("size") BigDecimal size, - @JsonProperty("remaining_size") BigDecimal remainingSize, - @JsonProperty("price") BigDecimal price, - @JsonProperty("best_bid") BigDecimal bestBid, - @JsonProperty("best_ask") BigDecimal bestAsk, - @JsonProperty("last_size") BigDecimal lastSize, - @JsonProperty("volume_24h") BigDecimal volume24h, - @JsonProperty("open_24h") BigDecimal open24h, - @JsonProperty("low_24h") BigDecimal low24h, - @JsonProperty("high_24h") BigDecimal high24h, - @JsonProperty("side") String side, - @JsonProperty("bids") String[][] bids, - @JsonProperty("asks") String[][] asks, - @JsonProperty("changes") String[][] changes, - @JsonProperty("client_oid") String clientOid, - @JsonProperty("product_id") String productId, - @JsonProperty("sequence") long sequence, - @JsonProperty("time") String time, - @JsonProperty("reason") String reason, - @JsonProperty("trade_id") long tradeId, - @JsonProperty("maker_order_id") String makerOrderId, - @JsonProperty("taker_order_id") String takerOrderId, - @JsonProperty("taker_user_id") String takerUserId, - @JsonProperty("user_id") String userId, - @JsonProperty("taker_profile_id") String takerProfileId, - @JsonProperty("profile_id") String profileId) { - - this.remainingSize = remainingSize; - this.reason = reason; - this.tradeId = tradeId; - this.makerOrderId = makerOrderId; - this.takerOrderId = takerOrderId; - this.type = type; - this.orderId = orderId; - this.orderType = orderType; - this.size = size; - this.price = price; - this.bestBid = bestBid; - this.bestAsk = bestAsk; - this.lastSize = lastSize; - this.volume24h = volume24h; - this.high24h = high24h; - this.low24h = low24h; - this.open24h = open24h; - this.side = side; - this.bids = bids; - this.asks = asks; - this.changes = changes; - this.clientOid = clientOid; - this.productId = productId; - this.sequence = sequence; - this.time = time; - this.takerUserId = takerUserId; - this.userId = userId; - this.takerProfileId = takerProfileId; - this.profileId = profileId; - } - - private List coinbaseProOrderBookChanges(String side, OrderType orderType, CurrencyPair currencyPair, String[][] changes, SortedMap sideEntries, - int maxDepth) { - if (changes.length == 0) { - return Collections.emptyList(); - } - - if (sideEntries == null) { - return Collections.emptyList(); - } - - for (String[] level : changes) { - if (level.length == 3 && !level[0].equals(side)) { - continue; - } - - BigDecimal price = new BigDecimal(level[level.length - 2]); - BigDecimal volume = new BigDecimal(level[level.length - 1]); - sideEntries.put(price, volume); - } - - Stream> stream = sideEntries.entrySet() - .stream() - .filter(level -> level.getValue().compareTo(BigDecimal.ZERO) != 0); - if (maxDepth != 0) { - stream = stream.limit(maxDepth); - } - return stream.map(level -> new LimitOrder( - orderType, - level.getValue(), - currencyPair, - "0", - null, - level.getKey())) - .collect(Collectors.toList()); - } - - public OrderBook toOrderBook(SortedMap bids, SortedMap asks, int maxDepth, CurrencyPair currencyPair) { - // For efficiency, we go straight to XChange format - List gdaxOrderBookBids = coinbaseProOrderBookChanges("buy", OrderType.BID, currencyPair, changes != null ? changes : this.bids, - bids, maxDepth); - List gdaxOrderBookAsks = coinbaseProOrderBookChanges("sell", OrderType.ASK, currencyPair, changes != null ? changes : this.asks, - asks, maxDepth); - return new OrderBook(time == null ? null : CoinbaseProStreamingAdapters.parseDate(time), gdaxOrderBookAsks, gdaxOrderBookBids, false); - } - - public CoinbaseProProductTicker toCoinbaseProProductTicker() { - String tickerTime = time; - if (tickerTime == null) { - SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - dateFormatGmt.setTimeZone(TimeZone.getTimeZone("UTC")); - tickerTime = dateFormatGmt.format(new Date()); //First ticker event doesn't have time! - } - return new CoinbaseProProductTicker(String.valueOf(tradeId), price, lastSize, bestBid, bestAsk, volume24h, tickerTime); - } - - public CoinbaseProProductStats toCoinbaseProProductStats() { - return new CoinbaseProProductStats(open24h, high24h, low24h, volume24h); - } - - public CoinbaseProTrade toCoinbaseProTrade() { - return new CoinbaseProTrade(time, tradeId, price, size, side, makerOrderId, takerOrderId); - } - - public CoinbaseProFill toCoinbaseProFill() { - boolean taker = userId != null && takerUserId != null && userId.equals(takerUserId); - // buy/sell are flipped on the taker side. - String useSide = side; - if (taker && side != null) { - if ("buy".equals(side)) { - useSide = "sell"; - } else { - useSide = "buy"; - } - } - return new CoinbaseProFill(String.valueOf(tradeId), productId, price, size, taker ? takerOrderId : makerOrderId, time, null, null, true, useSide); - } - - public String getType() { - return type; - } - - public String getOrderId() { - return orderId; - } - - public String getOrderType() { - return orderType; - } - - public BigDecimal getSize() { - return size; - } - - public BigDecimal getPrice() { - return price; - } - - public BigDecimal getBestBid() { - return bestBid; - } - - public BigDecimal getBestAsk() { - return bestAsk; - } - - public BigDecimal getLastSize() { - return lastSize; - } - - public BigDecimal getVolume24h() { - return volume24h; - } - - public BigDecimal getOpen24h() { - return open24h; - } - - public BigDecimal getLow24h() { - return low24h; - } - - public BigDecimal getHigh24h() { - return high24h; - } - - public String getSide() { - return side; - } - - public String getClientOid() { - return clientOid; - } - - public String getProductId() { - return productId; - } - - public Long getSequence() { - return sequence; - } - - public String getTime() { - return time; - } - - public BigDecimal getRemainingSize() { - return remainingSize; - } - - public String getReason() { - return reason; - } - - public long getTradeId() { - return tradeId; - } - - public String getMakerOrderId() { - return makerOrderId; - } - - /** - * @deprecated Use {@link #getTakerOrderId()} - */ - @Deprecated - public String getTakenOrderId() { - return takerOrderId; - } - - public String getTakerOrderId() { - return takerOrderId; - } - - public String getTakerUserId() { - return takerUserId; - } - - public String getUserId() { - return userId; - } - - public String getTakerProfileId() { - return takerProfileId; - } - - public String getProfileId() { - return profileId; - } - - @Override - public String toString() { - final StringBuffer sb = new StringBuffer("CoinbaseProWebSocketTransaction{"); - sb.append("type='").append(type).append('\''); - sb.append(", orderId='").append(orderId).append('\''); - sb.append(", orderType='").append(orderType).append('\''); - sb.append(", size=").append(size); - sb.append(", remainingSize=").append(remainingSize); - sb.append(", price=").append(price); - sb.append(", bestBid=").append(bestBid); - sb.append(", bestAsk=").append(bestAsk); - sb.append(", lastSize=").append(lastSize); - sb.append(", volume24h=").append(volume24h); - sb.append(", open24h=").append(open24h); - sb.append(", low24h=").append(low24h); - sb.append(", high24h=").append(high24h); - sb.append(", side='").append(side).append('\''); - sb.append(", bids=").append(bids); - sb.append(", asks=").append(asks); - sb.append(", changes=").append(asks); - sb.append(", clientOid='").append(clientOid).append('\''); - sb.append(", productId='").append(productId).append('\''); - sb.append(", sequence=").append(sequence); - sb.append(", time='").append(time).append('\''); - sb.append(", reason='").append(reason).append('\''); - sb.append(", trade_id='").append(tradeId).append('\''); - if ( userId != null ) - sb.append(", userId='").append(userId).append('\''); - if ( profileId != null ) - sb.append(", profileId='").append(profileId).append('\''); - if ( takerUserId != null ) - sb.append(", takerUserId='").append(takerUserId).append('\''); - if ( takerProfileId != null ) - sb.append(", takerProfileId='").append(takerProfileId).append('\''); - sb.append('}'); - return sb.toString(); - } + private final String type; + private final String orderId; + private final String orderType; + private final BigDecimal size; + private final BigDecimal remainingSize; + private final BigDecimal price; + private final BigDecimal bestBid; + private final BigDecimal bestAsk; + private final BigDecimal lastSize; + private final BigDecimal volume24h; + private final BigDecimal open24h; + private final BigDecimal low24h; + private final BigDecimal high24h; + private final String side; + private final String[][] bids; + private final String[][] asks; + private final String[][] changes; + private final String clientOid; + private final String productId; + private final long sequence; + private final String time; + private final String reason; + private final long tradeId; + private final String makerOrderId; + private final String takerOrderId; + + private final String takerUserId; + private final String userId; + private final String takerProfileId; + private final String profileId; + + public CoinbaseProWebSocketTransaction( + @JsonProperty("type") String type, + @JsonProperty("order_id") String orderId, + @JsonProperty("order_type") String orderType, + @JsonProperty("size") BigDecimal size, + @JsonProperty("remaining_size") BigDecimal remainingSize, + @JsonProperty("price") BigDecimal price, + @JsonProperty("best_bid") BigDecimal bestBid, + @JsonProperty("best_ask") BigDecimal bestAsk, + @JsonProperty("last_size") BigDecimal lastSize, + @JsonProperty("volume_24h") BigDecimal volume24h, + @JsonProperty("open_24h") BigDecimal open24h, + @JsonProperty("low_24h") BigDecimal low24h, + @JsonProperty("high_24h") BigDecimal high24h, + @JsonProperty("side") String side, + @JsonProperty("bids") String[][] bids, + @JsonProperty("asks") String[][] asks, + @JsonProperty("changes") String[][] changes, + @JsonProperty("client_oid") String clientOid, + @JsonProperty("product_id") String productId, + @JsonProperty("sequence") long sequence, + @JsonProperty("time") String time, + @JsonProperty("reason") String reason, + @JsonProperty("trade_id") long tradeId, + @JsonProperty("maker_order_id") String makerOrderId, + @JsonProperty("taker_order_id") String takerOrderId, + @JsonProperty("taker_user_id") String takerUserId, + @JsonProperty("user_id") String userId, + @JsonProperty("taker_profile_id") String takerProfileId, + @JsonProperty("profile_id") String profileId) { + + this.remainingSize = remainingSize; + this.reason = reason; + this.tradeId = tradeId; + this.makerOrderId = makerOrderId; + this.takerOrderId = takerOrderId; + this.type = type; + this.orderId = orderId; + this.orderType = orderType; + this.size = size; + this.price = price; + this.bestBid = bestBid; + this.bestAsk = bestAsk; + this.lastSize = lastSize; + this.volume24h = volume24h; + this.high24h = high24h; + this.low24h = low24h; + this.open24h = open24h; + this.side = side; + this.bids = bids; + this.asks = asks; + this.changes = changes; + this.clientOid = clientOid; + this.productId = productId; + this.sequence = sequence; + this.time = time; + this.takerUserId = takerUserId; + this.userId = userId; + this.takerProfileId = takerProfileId; + this.profileId = profileId; + } + + private List coinbaseProOrderBookChanges( + String side, + OrderType orderType, + CurrencyPair currencyPair, + String[][] changes, + SortedMap sideEntries, + int maxDepth) { + if (changes.length == 0) { + return Collections.emptyList(); + } + + if (sideEntries == null) { + return Collections.emptyList(); + } + + for (String[] level : changes) { + if (level.length == 3 && !level[0].equals(side)) { + continue; + } + + BigDecimal price = new BigDecimal(level[level.length - 2]); + BigDecimal volume = new BigDecimal(level[level.length - 1]); + sideEntries.put(price, volume); + } + + Stream> stream = + sideEntries.entrySet().stream() + .filter(level -> level.getValue().compareTo(BigDecimal.ZERO) != 0); + if (maxDepth != 0) { + stream = stream.limit(maxDepth); + } + return stream + .map( + level -> + new LimitOrder( + orderType, level.getValue(), currencyPair, "0", null, level.getKey())) + .collect(Collectors.toList()); + } + + public OrderBook toOrderBook( + SortedMap bids, + SortedMap asks, + int maxDepth, + CurrencyPair currencyPair) { + // For efficiency, we go straight to XChange format + List gdaxOrderBookBids = + coinbaseProOrderBookChanges( + "buy", + OrderType.BID, + currencyPair, + changes != null ? changes : this.bids, + bids, + maxDepth); + List gdaxOrderBookAsks = + coinbaseProOrderBookChanges( + "sell", + OrderType.ASK, + currencyPair, + changes != null ? changes : this.asks, + asks, + maxDepth); + return new OrderBook( + time == null ? null : CoinbaseProStreamingAdapters.parseDate(time), + gdaxOrderBookAsks, + gdaxOrderBookBids, + false); + } + + public CoinbaseProProductTicker toCoinbaseProProductTicker() { + String tickerTime = time; + if (tickerTime == null) { + SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + dateFormatGmt.setTimeZone(TimeZone.getTimeZone("UTC")); + tickerTime = dateFormatGmt.format(new Date()); // First ticker event doesn't have time! + } + return new CoinbaseProProductTicker( + String.valueOf(tradeId), price, lastSize, bestBid, bestAsk, volume24h, tickerTime); + } + + public CoinbaseProProductStats toCoinbaseProProductStats() { + return new CoinbaseProProductStats(open24h, high24h, low24h, volume24h); + } + + public CoinbaseProTrade toCoinbaseProTrade() { + return new CoinbaseProTrade(time, tradeId, price, size, side, makerOrderId, takerOrderId); + } + + public CoinbaseProFill toCoinbaseProFill() { + boolean taker = userId != null && takerUserId != null && userId.equals(takerUserId); + // buy/sell are flipped on the taker side. + String useSide = side; + if (taker && side != null) { + if ("buy".equals(side)) { + useSide = "sell"; + } else { + useSide = "buy"; + } + } + return new CoinbaseProFill( + String.valueOf(tradeId), + productId, + price, + size, + taker ? takerOrderId : makerOrderId, + time, + null, + null, + true, + useSide); + } + + public String getType() { + return type; + } + + public String getOrderId() { + return orderId; + } + + public String getOrderType() { + return orderType; + } + + public BigDecimal getSize() { + return size; + } + + public BigDecimal getPrice() { + return price; + } + + public BigDecimal getBestBid() { + return bestBid; + } + + public BigDecimal getBestAsk() { + return bestAsk; + } + + public BigDecimal getLastSize() { + return lastSize; + } + + public BigDecimal getVolume24h() { + return volume24h; + } + + public BigDecimal getOpen24h() { + return open24h; + } + + public BigDecimal getLow24h() { + return low24h; + } + + public BigDecimal getHigh24h() { + return high24h; + } + + public String getSide() { + return side; + } + + public String getClientOid() { + return clientOid; + } + + public String getProductId() { + return productId; + } + + public Long getSequence() { + return sequence; + } + + public String getTime() { + return time; + } + + public BigDecimal getRemainingSize() { + return remainingSize; + } + + public String getReason() { + return reason; + } + + public long getTradeId() { + return tradeId; + } + + public String getMakerOrderId() { + return makerOrderId; + } + + /** @deprecated Use {@link #getTakerOrderId()} */ + @Deprecated + public String getTakenOrderId() { + return takerOrderId; + } + + public String getTakerOrderId() { + return takerOrderId; + } + + public String getTakerUserId() { + return takerUserId; + } + + public String getUserId() { + return userId; + } + + public String getTakerProfileId() { + return takerProfileId; + } + + public String getProfileId() { + return profileId; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("CoinbaseProWebSocketTransaction{"); + sb.append("type='").append(type).append('\''); + sb.append(", orderId='").append(orderId).append('\''); + sb.append(", orderType='").append(orderType).append('\''); + sb.append(", size=").append(size); + sb.append(", remainingSize=").append(remainingSize); + sb.append(", price=").append(price); + sb.append(", bestBid=").append(bestBid); + sb.append(", bestAsk=").append(bestAsk); + sb.append(", lastSize=").append(lastSize); + sb.append(", volume24h=").append(volume24h); + sb.append(", open24h=").append(open24h); + sb.append(", low24h=").append(low24h); + sb.append(", high24h=").append(high24h); + sb.append(", side='").append(side).append('\''); + sb.append(", bids=").append(bids); + sb.append(", asks=").append(asks); + sb.append(", changes=").append(asks); + sb.append(", clientOid='").append(clientOid).append('\''); + sb.append(", productId='").append(productId).append('\''); + sb.append(", sequence=").append(sequence); + sb.append(", time='").append(time).append('\''); + sb.append(", reason='").append(reason).append('\''); + sb.append(", trade_id='").append(tradeId).append('\''); + if (userId != null) sb.append(", userId='").append(userId).append('\''); + if (profileId != null) sb.append(", profileId='").append(profileId).append('\''); + if (takerUserId != null) sb.append(", takerUserId='").append(takerUserId).append('\''); + if (takerProfileId != null) sb.append(", takerProfileId='").append(takerProfileId).append('\''); + sb.append('}'); + return sb.toString(); + } } diff --git a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/netty/WebSocketClientCompressionAllowClientNoContextHandler.java b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/netty/WebSocketClientCompressionAllowClientNoContextHandler.java index ba3f43c73..6d2eb25aa 100644 --- a/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/netty/WebSocketClientCompressionAllowClientNoContextHandler.java +++ b/xchange-stream-coinbasepro/src/main/java/info/bitrich/xchangestream/coinbasepro/netty/WebSocketClientCompressionAllowClientNoContextHandler.java @@ -1,25 +1,28 @@ package info.bitrich.xchangestream.coinbasepro.netty; +import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker.MAX_WINDOW_SIZE; + import io.netty.channel.ChannelHandler; import io.netty.handler.codec.compression.ZlibCodecFactory; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameClientExtensionHandshaker; import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateClientExtensionHandshaker; -import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker.MAX_WINDOW_SIZE; - /** * Custom WebSocket client extension handler. GDAX needs very specific WebSocket extensions enabled. */ @ChannelHandler.Sharable -public final class WebSocketClientCompressionAllowClientNoContextHandler extends WebSocketClientExtensionHandler { +public final class WebSocketClientCompressionAllowClientNoContextHandler + extends WebSocketClientExtensionHandler { - public static final WebSocketClientCompressionAllowClientNoContextHandler INSTANCE = - new WebSocketClientCompressionAllowClientNoContextHandler(); + public static final WebSocketClientCompressionAllowClientNoContextHandler INSTANCE = + new WebSocketClientCompressionAllowClientNoContextHandler(); - private WebSocketClientCompressionAllowClientNoContextHandler() { - super(new PerMessageDeflateClientExtensionHandshaker(6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE, true, false), - new DeflateFrameClientExtensionHandshaker(false), - new DeflateFrameClientExtensionHandshaker(true)); - } + private WebSocketClientCompressionAllowClientNoContextHandler() { + super( + new PerMessageDeflateClientExtensionHandshaker( + 6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE, true, false), + new DeflateFrameClientExtensionHandshaker(false), + new DeflateFrameClientExtensionHandshaker(true)); + } } diff --git a/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProManualExample.java b/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProManualExample.java index 7e4f8f6d7..0a61a6ba7 100644 --- a/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProManualExample.java +++ b/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/CoinbaseProManualExample.java @@ -1,80 +1,108 @@ package info.bitrich.xchangestream.coinbasepro; +import info.bitrich.xchangestream.core.ProductSubscription; +import info.bitrich.xchangestream.core.StreamingExchangeFactory; import org.apache.commons.lang3.StringUtils; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.currency.CurrencyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.bitrich.xchangestream.core.ProductSubscription; -import info.bitrich.xchangestream.core.StreamingExchangeFactory; - public class CoinbaseProManualExample { - private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProManualExample.class); - - public static void main(String[] args) { - - // Far safer than temporarily adding these to code that might get committed to VCS - String apiKey = System.getProperty("coinbasepro-api-key"); - String apiSecret = System.getProperty("coinbasepro-api-secret"); - String apiPassphrase = System.getProperty("coinbasepro-api-passphrase"); - - ProductSubscription productSubscription = ProductSubscription.create() - .addTicker(CurrencyPair.ETH_USD) - .addOrders(CurrencyPair.LTC_EUR) - .addOrderbook(CurrencyPair.BTC_USD) - .addTrades(CurrencyPair.BTC_USD) - .addUserTrades(CurrencyPair.LTC_EUR) - .build(); - - ExchangeSpecification spec = StreamingExchangeFactory.INSTANCE.createExchange( - CoinbaseProStreamingExchange.class.getName()).getDefaultExchangeSpecification(); - spec.setApiKey(apiKey); - spec.setSecretKey(apiSecret); - spec.setExchangeSpecificParametersItem("passphrase", apiPassphrase); - CoinbaseProStreamingExchange exchange = (CoinbaseProStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); - - exchange.connect(productSubscription).blockingAwait(); - - exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); - - exchange.getStreamingMarketDataService().getTicker(CurrencyPair.ETH_USD).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD) - .subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); - - if (StringUtils.isNotEmpty(apiKey)) { - - exchange.getStreamingMarketDataService().getRawWebSocketTransactions(CurrencyPair.LTC_EUR, false) - .subscribe(transaction -> { - LOG.info("RAW WEBSOCKET TRANSACTION: {}", transaction); - }, throwable -> LOG.error("ERROR in getting raw websocket transactions: ", throwable)); - - exchange.getStreamingTradeService().getUserTrades(CurrencyPair.LTC_EUR) - .subscribe(trade -> { - LOG.info("USER TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting user trade: ", throwable)); - - exchange.getStreamingTradeService().getOrderChanges(CurrencyPair.LTC_EUR) - .subscribe(order -> { - LOG.info("USER ORDER: {}", order); - }, throwable -> LOG.error("ERROR in getting user orders: ", throwable)); - - } - - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - exchange.disconnect().blockingAwait(); + private static final Logger LOG = LoggerFactory.getLogger(CoinbaseProManualExample.class); + + public static void main(String[] args) { + + // Far safer than temporarily adding these to code that might get committed to VCS + String apiKey = System.getProperty("coinbasepro-api-key"); + String apiSecret = System.getProperty("coinbasepro-api-secret"); + String apiPassphrase = System.getProperty("coinbasepro-api-passphrase"); + + ProductSubscription productSubscription = + ProductSubscription.create() + .addTicker(CurrencyPair.ETH_USD) + .addOrders(CurrencyPair.LTC_EUR) + .addOrderbook(CurrencyPair.BTC_USD) + .addTrades(CurrencyPair.BTC_USD) + .addUserTrades(CurrencyPair.LTC_EUR) + .build(); + + ExchangeSpecification spec = + StreamingExchangeFactory.INSTANCE + .createExchange(CoinbaseProStreamingExchange.class.getName()) + .getDefaultExchangeSpecification(); + spec.setApiKey(apiKey); + spec.setSecretKey(apiSecret); + spec.setExchangeSpecificParametersItem("passphrase", apiPassphrase); + CoinbaseProStreamingExchange exchange = + (CoinbaseProStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); + + exchange.connect(productSubscription).blockingAwait(); + + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_USD) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.ETH_USD) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting trades: ", throwable)); + + if (StringUtils.isNotEmpty(apiKey)) { + + exchange + .getStreamingMarketDataService() + .getRawWebSocketTransactions(CurrencyPair.LTC_EUR, false) + .subscribe( + transaction -> { + LOG.info("RAW WEBSOCKET TRANSACTION: {}", transaction); + }, + throwable -> LOG.error("ERROR in getting raw websocket transactions: ", throwable)); + + exchange + .getStreamingTradeService() + .getUserTrades(CurrencyPair.LTC_EUR) + .subscribe( + trade -> { + LOG.info("USER TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting user trade: ", throwable)); + + exchange + .getStreamingTradeService() + .getOrderChanges(CurrencyPair.LTC_EUR) + .subscribe( + order -> { + LOG.info("USER ORDER: {}", order); + }, + throwable -> LOG.error("ERROR in getting user orders: ", throwable)); + } + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + + exchange.disconnect().blockingAwait(); + } } diff --git a/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessageTest.java b/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessageTest.java index 9d1313860..3e0035145 100644 --- a/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessageTest.java +++ b/xchange-stream-coinbasepro/src/test/java/info/bitrich/xchangestream/coinbasepro/dto/CoinbaseProWebSocketSubscriptionMessageTest.java @@ -8,23 +8,28 @@ import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; -/** - * Created by luca on 5/3/17. - */ +/** Created by luca on 5/3/17. */ public class CoinbaseProWebSocketSubscriptionMessageTest { - @Test - public void testWebSocketMessageSerialization() throws JsonProcessingException { + @Test + public void testWebSocketMessageSerialization() throws JsonProcessingException { - ProductSubscription productSubscription = ProductSubscription.create().addOrderbook(CurrencyPair.BTC_USD) - .addTrades(CurrencyPair.BTC_USD).addTicker(CurrencyPair.BTC_USD).build(); - CoinbaseProWebSocketSubscriptionMessage message = new CoinbaseProWebSocketSubscriptionMessage("subscribe", productSubscription, null); + ProductSubscription productSubscription = + ProductSubscription.create() + .addOrderbook(CurrencyPair.BTC_USD) + .addTrades(CurrencyPair.BTC_USD) + .addTicker(CurrencyPair.BTC_USD) + .build(); + CoinbaseProWebSocketSubscriptionMessage message = + new CoinbaseProWebSocketSubscriptionMessage("subscribe", productSubscription, null); - final ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + final ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); - String serialized = mapper.writeValueAsString(message); + String serialized = mapper.writeValueAsString(message); - Assert.assertEquals("{\"type\":\"subscribe\",\"channels\":[{\"name\":\"matches\",\"product_ids\":[\"BTC-USD\"]},{\"name\":\"ticker\",\"product_ids\":[\"BTC-USD\"]},{\"name\":\"level2\",\"product_ids\":[\"BTC-USD\"]}]}", serialized); - } + Assert.assertEquals( + "{\"type\":\"subscribe\",\"channels\":[{\"name\":\"matches\",\"product_ids\":[\"BTC-USD\"]},{\"name\":\"ticker\",\"product_ids\":[\"BTC-USD\"]},{\"name\":\"level2\",\"product_ids\":[\"BTC-USD\"]}]}", + serialized); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAccountService.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAccountService.java index 5d2066eee..59ad86418 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAccountService.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAccountService.java @@ -6,66 +6,79 @@ import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import info.bitrich.xchangestream.service.pusher.PusherStreamingService; import io.reactivex.Observable; +import java.util.*; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.dto.account.Wallet; -import java.util.*; - public class CoinmateStreamingAccountService implements StreamingAccountService { - private final PusherStreamingService service; - private final String userId; - private final Set walletFeatures = new HashSet<>(Arrays.asList(Wallet.WalletFeature.TRADING, Wallet.WalletFeature.FUNDING)); + private final PusherStreamingService service; + private final String userId; + private final Set walletFeatures = + new HashSet<>(Arrays.asList(Wallet.WalletFeature.TRADING, Wallet.WalletFeature.FUNDING)); - public CoinmateStreamingAccountService(PusherStreamingService service, String userId) { - this.service = service; - this.userId = userId; - } + public CoinmateStreamingAccountService(PusherStreamingService service, String userId) { + this.service = service; + this.userId = userId; + } - @Override - public Observable getBalanceChanges(Currency currency, Object... args) { + @Override + public Observable getBalanceChanges(Currency currency, Object... args) { - return getCoinmateBalances().map(balanceMap -> balanceMap.get(currency.toString())) - .map((balance) -> { - return new Balance.Builder() - .currency(currency) - .total(balance.getBalance()) - .available(balance.getBalance().subtract(balance.getReserved())) - .frozen(balance.getReserved()) - .build(); - }); - } + return getCoinmateBalances() + .map(balanceMap -> balanceMap.get(currency.toString())) + .map( + (balance) -> { + return new Balance.Builder() + .currency(currency) + .total(balance.getBalance()) + .available(balance.getBalance().subtract(balance.getReserved())) + .frozen(balance.getReserved()) + .build(); + }); + } - public Observable getWalletChanges(Object... args) { + public Observable getWalletChanges(Object... args) { - return getCoinmateBalances().map((balanceMap) -> { - List balances = new ArrayList<>(); - balanceMap.forEach((s, coinmateWebsocketBalance) -> { - balances.add( + return getCoinmateBalances() + .map( + (balanceMap) -> { + List balances = new ArrayList<>(); + balanceMap.forEach( + (s, coinmateWebsocketBalance) -> { + balances.add( new Balance.Builder() - .currency(new Currency(s)) - .total(coinmateWebsocketBalance.getBalance()) - .available(coinmateWebsocketBalance.getBalance().subtract(coinmateWebsocketBalance.getReserved())) - .frozen(coinmateWebsocketBalance.getReserved()) - .build() - ); + .currency(new Currency(s)) + .total(coinmateWebsocketBalance.getBalance()) + .available( + coinmateWebsocketBalance + .getBalance() + .subtract(coinmateWebsocketBalance.getReserved())) + .frozen(coinmateWebsocketBalance.getReserved()) + .build()); + }); + return balances; + }) + .map( + (balances) -> { + return Wallet.Builder.from(balances).features(walletFeatures).id("spot").build(); }); - return balances; - }).map((balances) -> { - return Wallet.Builder.from(balances).features(walletFeatures).id("spot").build(); - }); - } + } - private Observable> getCoinmateBalances(){ - String channelName = "private-user_balances-" + userId; + private Observable> getCoinmateBalances() { + String channelName = "private-user_balances-" + userId; - return service.subscribeChannel(channelName,"user_balances") - .map((message)->{ - Map balanceMap = - StreamingObjectMapperHelper.getObjectMapper().readValue(message, new TypeReference>() {}); + return service + .subscribeChannel(channelName, "user_balances") + .map( + (message) -> { + Map balanceMap = + StreamingObjectMapperHelper.getObjectMapper() + .readValue( + message, new TypeReference>() {}); - return balanceMap; - }); - } + return balanceMap; + }); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAdapter.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAdapter.java index a2bcfd403..0a0cfb505 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAdapter.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingAdapter.java @@ -2,6 +2,11 @@ import info.bitrich.xchangestream.coinmate.dto.CoinmateWebSocketUserTrade; import info.bitrich.xchangestream.coinmate.dto.CoinmateWebsocketOpenOrder; +import java.math.BigDecimal; +import java.sql.Date; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.marketdata.Trades; @@ -10,58 +15,62 @@ import org.knowm.xchange.dto.trade.UserTrade; import org.knowm.xchange.dto.trade.UserTrades; -import java.math.BigDecimal; -import java.sql.Date; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; - public class CoinmateStreamingAdapter { - public static String getChannelPostfix(CurrencyPair currencyPair) { - return currencyPair.base.toString().toUpperCase() + "_" + currencyPair.counter.toString().toUpperCase(); - } - - public static UserTrades adaptWebSocketUserTrades(List coinmateWebSocketUserTrades, CurrencyPair currencyPair) { - List userTrades = new ArrayList<>(); - coinmateWebSocketUserTrades.forEach((coinmateWebSocketUserTrade) -> { + public static String getChannelPostfix(CurrencyPair currencyPair) { + return currencyPair.base.toString().toUpperCase() + + "_" + + currencyPair.counter.toString().toUpperCase(); + } - userTrades.add( - new UserTrade.Builder() - .type((coinmateWebSocketUserTrade.getUserOrderType().equals("SELL")) - ? Order.OrderType.ASK - : Order.OrderType.BID) - .originalAmount(BigDecimal.valueOf(coinmateWebSocketUserTrade.getAmount())) - .currencyPair(currencyPair) - .price(BigDecimal.valueOf(coinmateWebSocketUserTrade.getPrice())) - .timestamp(Date.from(Instant.ofEpochMilli(coinmateWebSocketUserTrade.getTimestamp()))) - .id(coinmateWebSocketUserTrade.getTransactionId()) - .orderId((coinmateWebSocketUserTrade.getUserOrderType().equals("SELL")) - ? coinmateWebSocketUserTrade.getSellOrderId() - : coinmateWebSocketUserTrade.getBuyOrderId()) - .feeAmount(BigDecimal.valueOf(coinmateWebSocketUserTrade.getFee())) - .feeCurrency(currencyPair.counter) - .build() - ); + public static UserTrades adaptWebSocketUserTrades( + List coinmateWebSocketUserTrades, CurrencyPair currencyPair) { + List userTrades = new ArrayList<>(); + coinmateWebSocketUserTrades.forEach( + (coinmateWebSocketUserTrade) -> { + userTrades.add( + new UserTrade.Builder() + .type( + (coinmateWebSocketUserTrade.getUserOrderType().equals("SELL")) + ? Order.OrderType.ASK + : Order.OrderType.BID) + .originalAmount(BigDecimal.valueOf(coinmateWebSocketUserTrade.getAmount())) + .currencyPair(currencyPair) + .price(BigDecimal.valueOf(coinmateWebSocketUserTrade.getPrice())) + .timestamp( + Date.from(Instant.ofEpochMilli(coinmateWebSocketUserTrade.getTimestamp()))) + .id(coinmateWebSocketUserTrade.getTransactionId()) + .orderId( + (coinmateWebSocketUserTrade.getUserOrderType().equals("SELL")) + ? coinmateWebSocketUserTrade.getSellOrderId() + : coinmateWebSocketUserTrade.getBuyOrderId()) + .feeAmount(BigDecimal.valueOf(coinmateWebSocketUserTrade.getFee())) + .feeCurrency(currencyPair.counter) + .build()); }); - return new UserTrades(userTrades, Trades.TradeSortType.SortByTimestamp); - } + return new UserTrades(userTrades, Trades.TradeSortType.SortByTimestamp); + } - public static OpenOrders adaptWebsocketOpenOrders(List coinmateWebsocketOpenOrders, CurrencyPair currencyPair) { - List openOrders = new ArrayList<>(); - coinmateWebsocketOpenOrders.forEach((coinmateWebsocketOpenOrder) -> { - openOrders.add( - new LimitOrder.Builder((coinmateWebsocketOpenOrder.getOrderType().contains("SELL")) - ? Order.OrderType.ASK - : Order.OrderType.BID,currencyPair) - .originalAmount(BigDecimal.valueOf(coinmateWebsocketOpenOrder.getAmount())) - .cumulativeAmount(BigDecimal.valueOf(coinmateWebsocketOpenOrder.getOriginalOrderSize())) - .id(coinmateWebsocketOpenOrder.getId()) - .timestamp(Date.from(Instant.ofEpochMilli(coinmateWebsocketOpenOrder.getTimestamp()))) - .limitPrice(BigDecimal.valueOf(coinmateWebsocketOpenOrder.getPrice())) - .build() - ); + public static OpenOrders adaptWebsocketOpenOrders( + List coinmateWebsocketOpenOrders, CurrencyPair currencyPair) { + List openOrders = new ArrayList<>(); + coinmateWebsocketOpenOrders.forEach( + (coinmateWebsocketOpenOrder) -> { + openOrders.add( + new LimitOrder.Builder( + (coinmateWebsocketOpenOrder.getOrderType().contains("SELL")) + ? Order.OrderType.ASK + : Order.OrderType.BID, + currencyPair) + .originalAmount(BigDecimal.valueOf(coinmateWebsocketOpenOrder.getAmount())) + .cumulativeAmount( + BigDecimal.valueOf(coinmateWebsocketOpenOrder.getOriginalOrderSize())) + .id(coinmateWebsocketOpenOrder.getId()) + .timestamp( + Date.from(Instant.ofEpochMilli(coinmateWebsocketOpenOrder.getTimestamp()))) + .limitPrice(BigDecimal.valueOf(coinmateWebsocketOpenOrder.getPrice())) + .build()); }); - return new OpenOrders(openOrders); - } + return new OpenOrders(openOrders); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingExchange.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingExchange.java index 68d73964a..93a1b5b2a 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingExchange.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingExchange.java @@ -10,75 +10,80 @@ import org.knowm.xchange.coinmate.CoinmateExchange; public class CoinmateStreamingExchange extends CoinmateExchange implements StreamingExchange { - private static final String API_KEY = "af76597b6b928970fbb0"; - private PusherStreamingService streamingService; + private static final String API_KEY = "af76597b6b928970fbb0"; + private PusherStreamingService streamingService; - private CoinmateStreamingMarketDataService streamingMarketDataService; - private CoinmateStreamingAccountService streamingAccountService; - private CoinmateStreamingTradeService streamingTradeService; + private CoinmateStreamingMarketDataService streamingMarketDataService; + private CoinmateStreamingAccountService streamingAccountService; + private CoinmateStreamingTradeService streamingTradeService; - public CoinmateStreamingExchange() {} + public CoinmateStreamingExchange() {} - private void createExchange() { - if (exchangeSpecification.getApiKey() != null) { - PusherAuthParamsObject params = new PusherAuthParamsObject( - exchangeSpecification.getSecretKey(), - exchangeSpecification.getApiKey(), - exchangeSpecification.getUserName(), - getNonceFactory() - ); - - CoinmateUrlEncodedConnectionFactory urlEncodedConnectionFactory = new CoinmateUrlEncodedConnectionFactory(params); - HttpAuthorizer authorizer = new HttpAuthorizer("https://www.coinmate.io/api/pusherAuth", urlEncodedConnectionFactory); - PusherOptions options = new PusherOptions(); - options.setAuthorizer(authorizer); - options.setCluster("mt1"); - streamingService = new PusherStreamingService(API_KEY, options); - } else { - streamingService = new PusherStreamingService(API_KEY); - } + private void createExchange() { + if (exchangeSpecification.getApiKey() != null) { + PusherAuthParamsObject params = + new PusherAuthParamsObject( + exchangeSpecification.getSecretKey(), + exchangeSpecification.getApiKey(), + exchangeSpecification.getUserName(), + getNonceFactory()); + CoinmateUrlEncodedConnectionFactory urlEncodedConnectionFactory = + new CoinmateUrlEncodedConnectionFactory(params); + HttpAuthorizer authorizer = + new HttpAuthorizer("https://www.coinmate.io/api/pusherAuth", urlEncodedConnectionFactory); + PusherOptions options = new PusherOptions(); + options.setAuthorizer(authorizer); + options.setCluster("mt1"); + streamingService = new PusherStreamingService(API_KEY, options); + } else { + streamingService = new PusherStreamingService(API_KEY); } + } - @Override - protected void initServices() { - super.initServices(); - createExchange(); - streamingMarketDataService = new CoinmateStreamingMarketDataService(streamingService); - streamingAccountService = new CoinmateStreamingAccountService(streamingService, exchangeSpecification.getUserName()); - streamingTradeService = new CoinmateStreamingTradeService(streamingService, exchangeSpecification.getUserName()); - } + @Override + protected void initServices() { + super.initServices(); + createExchange(); + streamingMarketDataService = new CoinmateStreamingMarketDataService(streamingService); + streamingAccountService = + new CoinmateStreamingAccountService(streamingService, exchangeSpecification.getUserName()); + streamingTradeService = + new CoinmateStreamingTradeService(streamingService, exchangeSpecification.getUserName()); + } - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } - @Override - public StreamingTradeService getStreamingTradeService() { - return streamingTradeService; - } + @Override + public StreamingTradeService getStreamingTradeService() { + return streamingTradeService; + } - @Override - public StreamingAccountService getStreamingAccountService() { - return streamingAccountService; - } + @Override + public StreamingAccountService getStreamingAccountService() { + return streamingAccountService; + } - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataService.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataService.java index 050137903..91f5faf7b 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataService.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataService.java @@ -7,6 +7,7 @@ import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import info.bitrich.xchangestream.service.pusher.PusherStreamingService; import io.reactivex.Observable; +import java.util.List; import org.knowm.xchange.coinmate.CoinmateAdapters; import org.knowm.xchange.coinmate.CoinmateUtils; import org.knowm.xchange.coinmate.dto.marketdata.CoinmateOrderBook; @@ -17,48 +18,55 @@ import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.exceptions.NotAvailableFromExchangeException; -import java.util.List; - public class CoinmateStreamingMarketDataService implements StreamingMarketDataService { - private final PusherStreamingService service; - - CoinmateStreamingMarketDataService(PusherStreamingService service) { - this.service = service; - } - - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String channelName = "order_book-" + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); + private final PusherStreamingService service; - return service.subscribeChannel(channelName, "order_book") - .map(s -> { - ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - CoinmateOrderBookData orderBookData = mapper.readValue(s, CoinmateOrderBookData.class); - CoinmateOrderBook coinmateOrderBook = new CoinmateOrderBook(false, null, orderBookData); + CoinmateStreamingMarketDataService(PusherStreamingService service) { + this.service = service; + } - return CoinmateAdapters.adaptOrderBook(coinmateOrderBook, currencyPair); - }); - } + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + String channelName = "order_book-" + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - // No live ticker - throw new NotAvailableFromExchangeException(); - } + return service + .subscribeChannel(channelName, "order_book") + .map( + s -> { + ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + CoinmateOrderBookData orderBookData = + mapper.readValue(s, CoinmateOrderBookData.class); + CoinmateOrderBook coinmateOrderBook = + new CoinmateOrderBook(false, null, orderBookData); - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channelName = "trades-" + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); + return CoinmateAdapters.adaptOrderBook(coinmateOrderBook, currencyPair); + }); + } - return service.subscribeChannel(channelName, "new_trades") - .map(s -> { + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + // No live ticker + throw new NotAvailableFromExchangeException(); + } - ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - List list = mapper.readValue(s, new TypeReference>() {}); - return list; - }) - .flatMapIterable(coinmateWebSocketTrades -> coinmateWebSocketTrades) - .map(coinmateWebSocketTrade -> CoinmateAdapters.adaptTrade(coinmateWebSocketTrade.toTransactionEntry(CoinmateUtils.getPair(currencyPair)))); - } + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channelName = "trades-" + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); + return service + .subscribeChannel(channelName, "new_trades") + .map( + s -> { + ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + List list = + mapper.readValue(s, new TypeReference>() {}); + return list; + }) + .flatMapIterable(coinmateWebSocketTrades -> coinmateWebSocketTrades) + .map( + coinmateWebSocketTrade -> + CoinmateAdapters.adaptTrade( + coinmateWebSocketTrade.toTransactionEntry( + CoinmateUtils.getPair(currencyPair)))); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingTradeService.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingTradeService.java index cd663b60a..aaab05e2f 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingTradeService.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingTradeService.java @@ -7,47 +7,62 @@ import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import info.bitrich.xchangestream.service.pusher.PusherStreamingService; import io.reactivex.Observable; +import java.util.List; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.OpenOrders; import org.knowm.xchange.dto.trade.UserTrade; import org.knowm.xchange.dto.trade.UserTrades; -import java.util.List; - public class CoinmateStreamingTradeService implements StreamingTradeService { - private final PusherStreamingService service; - private final String userId; - - public CoinmateStreamingTradeService(PusherStreamingService service, String userId) { - this.service = service; - this.userId = userId; - } - - @Override - public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { - String channelName = "private-open_orders-" + userId + "-" + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); - - return service.subscribePrivateChannel(channelName, "open_orders") - .map((message) -> { - List websocketOpenOrders = - StreamingObjectMapperHelper.getObjectMapper().readValue(message, new TypeReference>() {}); - return CoinmateStreamingAdapter.adaptWebsocketOpenOrders(websocketOpenOrders, currencyPair); - }) - .concatMapIterable(OpenOrders::getAllOpenOrders); - } - - @Override - public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { - String channelName = "private-user-trades-" + userId + "-" + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); - - return service.subscribePrivateChannel(channelName, "user_trades") - .map((message) -> { - List webSocketUserTrades = - StreamingObjectMapperHelper.getObjectMapper().readValue(message, new TypeReference>() {}); - return CoinmateStreamingAdapter.adaptWebSocketUserTrades(webSocketUserTrades, currencyPair); - }) - .concatMapIterable(UserTrades::getUserTrades); - } + private final PusherStreamingService service; + private final String userId; + + public CoinmateStreamingTradeService(PusherStreamingService service, String userId) { + this.service = service; + this.userId = userId; + } + + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + String channelName = + "private-open_orders-" + + userId + + "-" + + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); + + return service + .subscribePrivateChannel(channelName, "open_orders") + .map( + (message) -> { + List websocketOpenOrders = + StreamingObjectMapperHelper.getObjectMapper() + .readValue(message, new TypeReference>() {}); + return CoinmateStreamingAdapter.adaptWebsocketOpenOrders( + websocketOpenOrders, currencyPair); + }) + .concatMapIterable(OpenOrders::getAllOpenOrders); + } + + @Override + public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + String channelName = + "private-user-trades-" + + userId + + "-" + + CoinmateStreamingAdapter.getChannelPostfix(currencyPair); + + return service + .subscribePrivateChannel(channelName, "user_trades") + .map( + (message) -> { + List webSocketUserTrades = + StreamingObjectMapperHelper.getObjectMapper() + .readValue(message, new TypeReference>() {}); + return CoinmateStreamingAdapter.adaptWebSocketUserTrades( + webSocketUserTrades, currencyPair); + }) + .concatMapIterable(UserTrades::getUserTrades); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketTrade.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketTrade.java index 33aa677eb..6f89aae2e 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketTrade.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketTrade.java @@ -1,22 +1,24 @@ package info.bitrich.xchangestream.coinmate.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTransactionsEntry; - import java.math.BigDecimal; +import org.knowm.xchange.coinmate.dto.marketdata.CoinmateTransactionsEntry; public class CoinmateWebSocketTrade { - private final long timestamp; - private final BigDecimal price; - private final BigDecimal amount; + private final long timestamp; + private final BigDecimal price; + private final BigDecimal amount; - public CoinmateWebSocketTrade(@JsonProperty("date") long timestamp, @JsonProperty("price") BigDecimal price, @JsonProperty("amount") BigDecimal amount) { - this.timestamp = timestamp; - this.price = price; - this.amount = amount; - } + public CoinmateWebSocketTrade( + @JsonProperty("date") long timestamp, + @JsonProperty("price") BigDecimal price, + @JsonProperty("amount") BigDecimal amount) { + this.timestamp = timestamp; + this.price = price; + this.amount = amount; + } - public CoinmateTransactionsEntry toTransactionEntry(String currencyPair) { - return new CoinmateTransactionsEntry(timestamp, null, price, amount, currencyPair); - } + public CoinmateTransactionsEntry toTransactionEntry(String currencyPair) { + return new CoinmateTransactionsEntry(timestamp, null, price, amount, currencyPair); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketUserTrade.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketUserTrade.java index ca7f4a670..7e045b30e 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketUserTrade.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebSocketUserTrade.java @@ -5,113 +5,127 @@ public class CoinmateWebSocketUserTrade { - @JsonProperty("transactionId") - private final String transactionId; - - @JsonProperty("date") - private final long timestamp; - - @JsonProperty("amount") - private final double amount; - - @JsonProperty("price") - private final double price; - - @JsonProperty("buyOrderId") - private final String buyOrderId; - - @JsonProperty("sellOrderId") - private final String sellOrderId; - - @JsonProperty("orderType") - private final String userOrderType; - - @JsonProperty("type") - private final String takerOrderType; - - @JsonProperty("fee") - private final double fee; - - @JsonProperty("tradeFeeType") - private final String userFeeType; - - @JsonCreator - public CoinmateWebSocketUserTrade( - @JsonProperty("transactionId") String transactionId, - @JsonProperty("date") long timestamp, - @JsonProperty("price") double price, - @JsonProperty("amount") double amount, - @JsonProperty("buyOrderId") String buyOrderId, - @JsonProperty("sellOrderId") String sellOrderId, - @JsonProperty("orderType") String userOrderType, - @JsonProperty("type") String takerOrderType, - @JsonProperty("fee") double fee, - @JsonProperty("tradeFeeType") String userFeeType) { - this.transactionId = transactionId; - this.timestamp = timestamp; - this.amount = amount; - this.price = price; - this.buyOrderId = buyOrderId; - this.sellOrderId = sellOrderId; - this.userOrderType = userOrderType; - this.takerOrderType = takerOrderType; - this.fee = fee; - this.userFeeType = userFeeType; - } - - public String getTransactionId() { - return this.transactionId; - } - - public long getTimestamp() { - return this.timestamp; - } - - public double getAmount() { - return this.amount; - } - - public double getPrice() { - return this.price; - } - - public String getBuyOrderId() { - return this.buyOrderId; - } - - public String getSellOrderId() { - return this.sellOrderId; - } - - public String getUserOrderType() { - return this.userOrderType; - } - - public String getTakerOrderType() { - return this.takerOrderType; - } - - public double getFee() { - return this.fee; - } - - public String getUserFeeType() { - return this.userFeeType; - } - - @Override - public String toString() { - return "CoinmateWebSocketUserTrade{" + - "transactionId='" + transactionId + '\'' + - ", timestamp=" + timestamp + - ", amount=" + amount + - ", price=" + price + - ", buyOrderId='" + buyOrderId + '\'' + - ", sellOrderId='" + sellOrderId + '\'' + - ", userOrderType=" + userOrderType + - ", takerOrderType=" + takerOrderType + - ", fee=" + fee + - ", userFeeType='" + userFeeType + '\'' + - '}'; - } + @JsonProperty("transactionId") + private final String transactionId; + + @JsonProperty("date") + private final long timestamp; + + @JsonProperty("amount") + private final double amount; + + @JsonProperty("price") + private final double price; + + @JsonProperty("buyOrderId") + private final String buyOrderId; + + @JsonProperty("sellOrderId") + private final String sellOrderId; + + @JsonProperty("orderType") + private final String userOrderType; + + @JsonProperty("type") + private final String takerOrderType; + + @JsonProperty("fee") + private final double fee; + + @JsonProperty("tradeFeeType") + private final String userFeeType; + + @JsonCreator + public CoinmateWebSocketUserTrade( + @JsonProperty("transactionId") String transactionId, + @JsonProperty("date") long timestamp, + @JsonProperty("price") double price, + @JsonProperty("amount") double amount, + @JsonProperty("buyOrderId") String buyOrderId, + @JsonProperty("sellOrderId") String sellOrderId, + @JsonProperty("orderType") String userOrderType, + @JsonProperty("type") String takerOrderType, + @JsonProperty("fee") double fee, + @JsonProperty("tradeFeeType") String userFeeType) { + this.transactionId = transactionId; + this.timestamp = timestamp; + this.amount = amount; + this.price = price; + this.buyOrderId = buyOrderId; + this.sellOrderId = sellOrderId; + this.userOrderType = userOrderType; + this.takerOrderType = takerOrderType; + this.fee = fee; + this.userFeeType = userFeeType; + } + + public String getTransactionId() { + return this.transactionId; + } + + public long getTimestamp() { + return this.timestamp; + } + + public double getAmount() { + return this.amount; + } + + public double getPrice() { + return this.price; + } + + public String getBuyOrderId() { + return this.buyOrderId; + } + + public String getSellOrderId() { + return this.sellOrderId; + } + + public String getUserOrderType() { + return this.userOrderType; + } + + public String getTakerOrderType() { + return this.takerOrderType; + } + + public double getFee() { + return this.fee; + } + + public String getUserFeeType() { + return this.userFeeType; + } + + @Override + public String toString() { + return "CoinmateWebSocketUserTrade{" + + "transactionId='" + + transactionId + + '\'' + + ", timestamp=" + + timestamp + + ", amount=" + + amount + + ", price=" + + price + + ", buyOrderId='" + + buyOrderId + + '\'' + + ", sellOrderId='" + + sellOrderId + + '\'' + + ", userOrderType=" + + userOrderType + + ", takerOrderType=" + + takerOrderType + + ", fee=" + + fee + + ", userFeeType='" + + userFeeType + + '\'' + + '}'; + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketBalance.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketBalance.java index 1f4db9636..872cd879a 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketBalance.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketBalance.java @@ -2,37 +2,33 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; public class CoinmateWebsocketBalance { - @JsonProperty("balance") - private final BigDecimal balance; - @JsonProperty("reserved") - private final BigDecimal reserved; - - @JsonCreator - public CoinmateWebsocketBalance( - @JsonProperty("balance") BigDecimal balance, - @JsonProperty("reserved") BigDecimal reserved) { - this.balance = balance; - this.reserved = reserved; - } - - public BigDecimal getBalance() { - return this.balance; - } - - public BigDecimal getReserved() { - return this.reserved; - } - - @Override - public String toString() { - return "CoinmateWebsocketBalance{" + - "balance=" + balance + - ", reserved=" + reserved + - '}'; - } + @JsonProperty("balance") + private final BigDecimal balance; + + @JsonProperty("reserved") + private final BigDecimal reserved; + + @JsonCreator + public CoinmateWebsocketBalance( + @JsonProperty("balance") BigDecimal balance, @JsonProperty("reserved") BigDecimal reserved) { + this.balance = balance; + this.reserved = reserved; + } + + public BigDecimal getBalance() { + return this.balance; + } + + public BigDecimal getReserved() { + return this.reserved; + } + + @Override + public String toString() { + return "CoinmateWebsocketBalance{" + "balance=" + balance + ", reserved=" + reserved + '}'; + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketOpenOrder.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketOpenOrder.java index bf843e81d..5324e2b07 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketOpenOrder.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/CoinmateWebsocketOpenOrder.java @@ -3,152 +3,165 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.dto.Order; public class CoinmateWebsocketOpenOrder { - @JsonProperty("amount") - private final double amount; - - @JsonProperty("date") - private final long timestamp; - - @JsonProperty("hidden") - private final boolean isHidden; - - @JsonProperty("id") - private final String id; - - @JsonProperty("original") - private final double originalOrderSize; - - @JsonProperty("price") - private final double price; - - @JsonProperty("type") - private final String orderType; - - @JsonProperty("stopPrice") - @JsonIgnore - private final double stopPrice; - - @JsonProperty("trailing") - private final boolean isTrailing; - - @JsonProperty("originalStopPrice") - @JsonIgnore - private final double originalStopPrice; - - @JsonProperty("priceAtStopLossCreation") - @JsonIgnore - private final double priceAtStopLossCreation; - - @JsonProperty("priceAtStopLossUpdate") - @JsonIgnore - private final double priceAtStopLossUpdate; - - @JsonProperty("trailingUpdatedTimestamp") - @JsonIgnore - private final long trailingUpdatedTimestamp; - - @JsonCreator - public CoinmateWebsocketOpenOrder( - @JsonProperty("amount") double amount, - @JsonProperty("date") long timestamp, - @JsonProperty("hidden") boolean isHidden, - @JsonProperty("id") String id, - @JsonProperty("original") double originalOrderSize, - @JsonProperty("price") double price, - @JsonProperty("type") String orderType, - @JsonProperty("stopPrice") double stopPrice, - @JsonProperty("trailing") boolean isTrailing, - @JsonProperty("originalStopPrice") double originalStopPrice, - @JsonProperty("priceAtStopLossCreation") double priceAtStopLossCreation, - @JsonProperty("priceAtStopLossUpdate") double priceAtStopLossUpdate, - @JsonProperty("trailingUpdatedTimestamp") long trailingUpdatedTimestamp) { - this.amount = amount; - this.timestamp = timestamp; - this.isHidden = isHidden; - this.id = id; - this.originalOrderSize = originalOrderSize; - this.price = price; - this.orderType = orderType; - this.stopPrice = stopPrice; - this.isTrailing = isTrailing; - this.originalStopPrice = originalStopPrice; - this.priceAtStopLossCreation = priceAtStopLossCreation; - this.priceAtStopLossUpdate = priceAtStopLossUpdate; - this.trailingUpdatedTimestamp = trailingUpdatedTimestamp; - } - - public double getAmount() { - return this.amount; - } - - public long getTimestamp() { - return this.timestamp; - } - - public boolean isHidden() { - return this.isHidden; - } - - public String getId() { - return this.id; - } - - public double getOriginalOrderSize() { - return this.originalOrderSize; - } - - public double getPrice() { - return this.price; - } - - public String getOrderType() { - return this.orderType; - } - - public double getStopPrice() { - return this.stopPrice; - } - - public boolean isTrailing() { - return this.isTrailing; - } - - public double getOriginalStopPrice() { - return this.originalStopPrice; - } - - public double getPriceAtStopLossCreation() { - return this.priceAtStopLossCreation; - } - - public double getPriceAtStopLossUpdate() { - return this.priceAtStopLossUpdate; - } - - public long getTrailingUpdatedTimestamp() { - return this.trailingUpdatedTimestamp; - } - - @Override - public String toString() { - return "CoinmateWebsocketOpenOrder{" + - "amount=" + amount + - ", timestamp=" + timestamp + - ", isHidden=" + isHidden + - ", id='" + id + '\'' + - ", originalOrderSize=" + originalOrderSize + - ", price=" + price + - ", orderType=" + orderType + - ", stopPrice=" + stopPrice + - ", isTrailing=" + isTrailing + - ", originalStopPrice=" + originalStopPrice + - ", priceAtStopLossCreation=" + priceAtStopLossCreation + - ", priceAtStopLossUpdate=" + priceAtStopLossUpdate + - ", trailingUpdatedTimestamp=" + trailingUpdatedTimestamp + - '}'; - } + @JsonProperty("amount") + private final double amount; + + @JsonProperty("date") + private final long timestamp; + + @JsonProperty("hidden") + private final boolean isHidden; + + @JsonProperty("id") + private final String id; + + @JsonProperty("original") + private final double originalOrderSize; + + @JsonProperty("price") + private final double price; + + @JsonProperty("type") + private final String orderType; + + @JsonProperty("stopPrice") + @JsonIgnore + private final double stopPrice; + + @JsonProperty("trailing") + private final boolean isTrailing; + + @JsonProperty("originalStopPrice") + @JsonIgnore + private final double originalStopPrice; + + @JsonProperty("priceAtStopLossCreation") + @JsonIgnore + private final double priceAtStopLossCreation; + + @JsonProperty("priceAtStopLossUpdate") + @JsonIgnore + private final double priceAtStopLossUpdate; + + @JsonProperty("trailingUpdatedTimestamp") + @JsonIgnore + private final long trailingUpdatedTimestamp; + + @JsonCreator + public CoinmateWebsocketOpenOrder( + @JsonProperty("amount") double amount, + @JsonProperty("date") long timestamp, + @JsonProperty("hidden") boolean isHidden, + @JsonProperty("id") String id, + @JsonProperty("original") double originalOrderSize, + @JsonProperty("price") double price, + @JsonProperty("type") String orderType, + @JsonProperty("stopPrice") double stopPrice, + @JsonProperty("trailing") boolean isTrailing, + @JsonProperty("originalStopPrice") double originalStopPrice, + @JsonProperty("priceAtStopLossCreation") double priceAtStopLossCreation, + @JsonProperty("priceAtStopLossUpdate") double priceAtStopLossUpdate, + @JsonProperty("trailingUpdatedTimestamp") long trailingUpdatedTimestamp) { + this.amount = amount; + this.timestamp = timestamp; + this.isHidden = isHidden; + this.id = id; + this.originalOrderSize = originalOrderSize; + this.price = price; + this.orderType = orderType; + this.stopPrice = stopPrice; + this.isTrailing = isTrailing; + this.originalStopPrice = originalStopPrice; + this.priceAtStopLossCreation = priceAtStopLossCreation; + this.priceAtStopLossUpdate = priceAtStopLossUpdate; + this.trailingUpdatedTimestamp = trailingUpdatedTimestamp; + } + + public double getAmount() { + return this.amount; + } + + public long getTimestamp() { + return this.timestamp; + } + + public boolean isHidden() { + return this.isHidden; + } + + public String getId() { + return this.id; + } + + public double getOriginalOrderSize() { + return this.originalOrderSize; + } + + public double getPrice() { + return this.price; + } + + public String getOrderType() { + return this.orderType; + } + + public double getStopPrice() { + return this.stopPrice; + } + + public boolean isTrailing() { + return this.isTrailing; + } + + public double getOriginalStopPrice() { + return this.originalStopPrice; + } + + public double getPriceAtStopLossCreation() { + return this.priceAtStopLossCreation; + } + + public double getPriceAtStopLossUpdate() { + return this.priceAtStopLossUpdate; + } + + public long getTrailingUpdatedTimestamp() { + return this.trailingUpdatedTimestamp; + } + + @Override + public String toString() { + return "CoinmateWebsocketOpenOrder{" + + "amount=" + + amount + + ", timestamp=" + + timestamp + + ", isHidden=" + + isHidden + + ", id='" + + id + + '\'' + + ", originalOrderSize=" + + originalOrderSize + + ", price=" + + price + + ", orderType=" + + orderType + + ", stopPrice=" + + stopPrice + + ", isTrailing=" + + isTrailing + + ", originalStopPrice=" + + originalStopPrice + + ", priceAtStopLossCreation=" + + priceAtStopLossCreation + + ", priceAtStopLossUpdate=" + + priceAtStopLossUpdate + + ", trailingUpdatedTimestamp=" + + trailingUpdatedTimestamp + + '}'; + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/CoinmateUrlEncodedConnectionFactory.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/CoinmateUrlEncodedConnectionFactory.java index 91e807bd3..df73b9219 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/CoinmateUrlEncodedConnectionFactory.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/CoinmateUrlEncodedConnectionFactory.java @@ -1,49 +1,49 @@ package info.bitrich.xchangestream.coinmate.dto.auth; import com.pusher.client.util.ConnectionFactory; -import org.knowm.xchange.coinmate.CoinmateException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.net.URLEncoder; import java.util.Iterator; import java.util.Map; +import org.knowm.xchange.coinmate.CoinmateException; public class CoinmateUrlEncodedConnectionFactory extends ConnectionFactory { - private final PusherAuthParamsObject pusherAuthParamsObject; - - public CoinmateUrlEncodedConnectionFactory(PusherAuthParamsObject pusherAuthParamsObject) { - this.pusherAuthParamsObject = pusherAuthParamsObject; + private final PusherAuthParamsObject pusherAuthParamsObject; + + public CoinmateUrlEncodedConnectionFactory(PusherAuthParamsObject pusherAuthParamsObject) { + this.pusherAuthParamsObject = pusherAuthParamsObject; + } + + public String getCharset() { + return "UTF-8"; + } + + public String getContentType() { + return "application/x-www-form-urlencoded"; + } + + public String getBody() { + StringBuilder urlParameters = new StringBuilder(); + + try { + Map mQueryStringParameters = pusherAuthParamsObject.getParams(); + urlParameters + .append("channel_name=") + .append(URLEncoder.encode(getChannelName(), getCharset())); + urlParameters.append("&socket_id=").append(URLEncoder.encode(getSocketId(), getCharset())); + Iterator var2 = mQueryStringParameters.keySet().iterator(); + + while (var2.hasNext()) { + String parameterName = (String) var2.next(); + urlParameters.append("&").append(parameterName).append("="); + urlParameters.append( + URLEncoder.encode((String) mQueryStringParameters.get(parameterName), getCharset())); + } + } catch (IOException e) { + throw new CoinmateException(e.getMessage()); } - public String getCharset() { - return "UTF-8"; - } - - public String getContentType() { - return "application/x-www-form-urlencoded"; - } - - public String getBody() { - StringBuilder urlParameters = new StringBuilder(); - - try { - Map mQueryStringParameters = pusherAuthParamsObject.getParams(); - urlParameters.append("channel_name=").append(URLEncoder.encode(getChannelName(), getCharset())); - urlParameters.append("&socket_id=").append(URLEncoder.encode(getSocketId(), getCharset())); - Iterator var2 = mQueryStringParameters.keySet().iterator(); - - while(var2.hasNext()) { - String parameterName = (String)var2.next(); - urlParameters.append("&").append(parameterName).append("="); - urlParameters.append(URLEncoder.encode((String)mQueryStringParameters.get(parameterName), getCharset())); - } - } catch (IOException e) { - throw new CoinmateException(e.getMessage()); - } - - return urlParameters.toString(); - } + return urlParameters.toString(); + } } diff --git a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/PusherAuthParamsObject.java b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/PusherAuthParamsObject.java index 82847764d..a130a3f1a 100644 --- a/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/PusherAuthParamsObject.java +++ b/xchange-stream-coinmate/src/main/java/info/bitrich/xchangestream/coinmate/dto/auth/PusherAuthParamsObject.java @@ -1,13 +1,5 @@ package info.bitrich.xchangestream.coinmate.dto.auth; -import org.knowm.xchange.coinmate.CoinmateException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import si.mazi.rescu.SynchronizedValueFactory; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; @@ -15,55 +7,61 @@ import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.knowm.xchange.coinmate.CoinmateException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import si.mazi.rescu.SynchronizedValueFactory; public class PusherAuthParamsObject { - private static final Logger log = LoggerFactory.getLogger(PusherAuthParamsObject.class); - private Map params = new HashMap<>(); + private static final Logger log = LoggerFactory.getLogger(PusherAuthParamsObject.class); + private Map params = new HashMap<>(); - private final String secret; - private final String apiKey; - private final String userId; - private SynchronizedValueFactory nonce; + private final String secret; + private final String apiKey; + private final String userId; + private SynchronizedValueFactory nonce; - public PusherAuthParamsObject(String secret, String apiKey, String userId,SynchronizedValueFactory nonce) { - this.secret = secret; - this.apiKey = apiKey; - this.userId = userId; - this.nonce = nonce; - } + public PusherAuthParamsObject( + String secret, String apiKey, String userId, SynchronizedValueFactory nonce) { + this.secret = secret; + this.apiKey = apiKey; + this.userId = userId; + this.nonce = nonce; + } - public Map getParams() throws IOException{ - params = new HashMap<>(); - Long nonce1 = nonce.createValue(); - this.params.put("clientId", userId); - this.params.put("nonce", String.valueOf(nonce1)); - this.params.put("signature", signature(nonce1, userId, apiKey, secret)); - this.params.put("publicKey", apiKey); + public Map getParams() throws IOException { + params = new HashMap<>(); + Long nonce1 = nonce.createValue(); + this.params.put("clientId", userId); + this.params.put("nonce", String.valueOf(nonce1)); + this.params.put("signature", signature(nonce1, userId, apiKey, secret)); + this.params.put("publicKey", apiKey); - return params; - } + return params; + } - private String signature(Long nonce, String userId, String apiKey, String apiSecret) throws IOException { - try { - Mac mac256 = Mac.getInstance("HmacSHA256"); - SecretKey secretKey = new SecretKeySpec(apiSecret.getBytes("UTF-8"), "HmacSHA256"); - mac256.init(secretKey); - mac256.update(String.valueOf(nonce).getBytes()); - mac256.update(userId.getBytes()); - mac256.update(apiKey.getBytes()); - return String.format("%064x", new BigInteger(1, mac256.doFinal())).toUpperCase(); - } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException ex) { - log.error(ex.getMessage(), ex); - throw new CoinmateException(ex.getMessage()); - } + private String signature(Long nonce, String userId, String apiKey, String apiSecret) + throws IOException { + try { + Mac mac256 = Mac.getInstance("HmacSHA256"); + SecretKey secretKey = new SecretKeySpec(apiSecret.getBytes("UTF-8"), "HmacSHA256"); + mac256.init(secretKey); + mac256.update(String.valueOf(nonce).getBytes()); + mac256.update(userId.getBytes()); + mac256.update(apiKey.getBytes()); + return String.format("%064x", new BigInteger(1, mac256.doFinal())).toUpperCase(); + } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException ex) { + log.error(ex.getMessage(), ex); + throw new CoinmateException(ex.getMessage()); } + } - @Override - public String toString() { - return "PusherAuthParamsObject{" + - "params=" + params + - '}'; - } + @Override + public String toString() { + return "PusherAuthParamsObject{" + "params=" + params + '}'; + } } - diff --git a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateManualExample.java b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateManualExample.java index 3e7b16e6d..0a33faed0 100644 --- a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateManualExample.java +++ b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateManualExample.java @@ -7,30 +7,36 @@ import org.slf4j.LoggerFactory; public class CoinmateManualExample { - private static final Logger LOG = LoggerFactory.getLogger(CoinmateStreamingExchange.class); + private static final Logger LOG = LoggerFactory.getLogger(CoinmateStreamingExchange.class); - public static void main(String[] args) { + public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(CoinmateStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(CoinmateStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); - exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_EUR).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }); + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_EUR) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }); -// Disposable subscribe = exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(trade -> { -// LOG.info("Trade {}", trade); -// }); + // Disposable subscribe = + // exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(trade -> { + // LOG.info("Trade {}", trade); + // }); -// subscribe.dispose(); + // subscribe.dispose(); -// exchange.disconnect().subscribe(() -> LOG.info("Disconnected from the Exchange")); + // exchange.disconnect().subscribe(() -> LOG.info("Disconnected from the Exchange")); - try { - Thread.sleep(100000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + Thread.sleep(100000); + } catch (InterruptedException e) { + e.printStackTrace(); } + } } diff --git a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataServiceTest.java b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataServiceTest.java index 66685f7c0..505d62001 100644 --- a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataServiceTest.java +++ b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateStreamingMarketDataServiceTest.java @@ -1,8 +1,18 @@ package info.bitrich.xchangestream.coinmate; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + import info.bitrich.xchangestream.service.pusher.PusherStreamingService; import io.reactivex.Observable; import io.reactivex.observers.TestObserver; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import org.junit.Before; import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; @@ -14,64 +24,96 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.math.BigDecimal; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - public class CoinmateStreamingMarketDataServiceTest { - @Mock - private PusherStreamingService streamingService; - private CoinmateStreamingMarketDataService marketDataService; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - marketDataService = new CoinmateStreamingMarketDataService(streamingService); - } - - @Test - public void testGetOrderBook() throws Exception { - // Given order book in JSON - String orderBook = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("order-book.json").toURI()))); - - when(streamingService.subscribeChannel(eq("order_book-BTC_EUR"), eq("order_book"))).thenReturn(Observable.just(orderBook)); - - List bids = new ArrayList<>(); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("2.48345723"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("852.8"))); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.50521505"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("850.37"))); - - List asks = new ArrayList<>(); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.04"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("853.35"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("11.89247706"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("854.5"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.38478732"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("855.48"))); - - // Call get order book observable - TestObserver test = marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test(); - - // We get order book object in correct order - test.assertValue(orderBook1 -> { - assertThat(orderBook1.getAsks()).as("Asks").isEqualTo(asks); - assertThat(orderBook1.getBids()).as("Bids").isEqualTo(bids); - return true; + @Mock private PusherStreamingService streamingService; + private CoinmateStreamingMarketDataService marketDataService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + marketDataService = new CoinmateStreamingMarketDataService(streamingService); + } + + @Test + public void testGetOrderBook() throws Exception { + // Given order book in JSON + String orderBook = + new String( + Files.readAllBytes( + Paths.get(ClassLoader.getSystemResource("order-book.json").toURI()))); + + when(streamingService.subscribeChannel(eq("order_book-BTC_EUR"), eq("order_book"))) + .thenReturn(Observable.just(orderBook)); + + List bids = new ArrayList<>(); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("2.48345723"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("852.8"))); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.50521505"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("850.37"))); + + List asks = new ArrayList<>(); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.04"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("853.35"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("11.89247706"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("854.5"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.38478732"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("855.48"))); + + // Call get order book observable + TestObserver test = marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test(); + + // We get order book object in correct order + test.assertValue( + orderBook1 -> { + assertThat(orderBook1.getAsks()).as("Asks").isEqualTo(asks); + assertThat(orderBook1.getBids()).as("Bids").isEqualTo(bids); + return true; }); - test.assertNoErrors(); - } + test.assertNoErrors(); + } - @Test - public void testGetTrades() throws Exception { - String trade = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("trades.json").toURI()))); + @Test + public void testGetTrades() throws Exception { + String trade = + new String( + Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("trades.json").toURI()))); - when(streamingService.subscribeChannel(eq("trades-BTC_CZK"), eq("new_trades"))).thenReturn(Observable.just(trade)); + when(streamingService.subscribeChannel(eq("trades-BTC_CZK"), eq("new_trades"))) + .thenReturn(Observable.just(trade)); - Trade expected1 = new Trade.Builder() + Trade expected1 = + new Trade.Builder() .type(null) .originalAmount(new BigDecimal("0.08233888")) .currencyPair(CurrencyPair.BTC_CZK) @@ -79,7 +121,8 @@ public void testGetTrades() throws Exception { .timestamp(new Date(1484863030522L)) .build(); - Trade expected2 = new Trade.Builder() + Trade expected2 = + new Trade.Builder() .type(null) .originalAmount(new BigDecimal("0.00200428")) .currencyPair(CurrencyPair.BTC_CZK) @@ -87,33 +130,45 @@ public void testGetTrades() throws Exception { .timestamp(new Date(1484863028887L)) .build(); - TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_CZK).test(); - - test.assertValueAt(0, trade1 -> { - assertThat(trade1.getId()).as("Id").isEqualTo(expected1.getId()); - assertThat(trade1.getCurrencyPair()).as("Currency pair").isEqualTo(expected1.getCurrencyPair()); - assertThat(trade1.getPrice()).as("Price").isEqualTo(expected1.getPrice()); - assertThat(trade1.getTimestamp()).as("Timestamp").isEqualTo(expected1.getTimestamp()); - assertThat(trade1.getOriginalAmount()).as("Amount").isEqualTo(expected1.getOriginalAmount()); - assertThat(trade1.getType()).as("Type").isEqualTo(expected1.getType()); - return true; + TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_CZK).test(); + + test.assertValueAt( + 0, + trade1 -> { + assertThat(trade1.getId()).as("Id").isEqualTo(expected1.getId()); + assertThat(trade1.getCurrencyPair()) + .as("Currency pair") + .isEqualTo(expected1.getCurrencyPair()); + assertThat(trade1.getPrice()).as("Price").isEqualTo(expected1.getPrice()); + assertThat(trade1.getTimestamp()).as("Timestamp").isEqualTo(expected1.getTimestamp()); + assertThat(trade1.getOriginalAmount()) + .as("Amount") + .isEqualTo(expected1.getOriginalAmount()); + assertThat(trade1.getType()).as("Type").isEqualTo(expected1.getType()); + return true; }); - test.assertValueAt(1, trade1 -> { - assertThat(trade1.getId()).as("Id").isEqualTo(expected2.getId()); - assertThat(trade1.getCurrencyPair()).as("Currency pair").isEqualTo(expected2.getCurrencyPair()); - assertThat(trade1.getPrice()).as("Price").isEqualTo(expected2.getPrice()); - assertThat(trade1.getTimestamp()).as("Timestamp").isEqualTo(expected2.getTimestamp()); - assertThat(trade1.getOriginalAmount()).as("Amount").isEqualTo(expected2.getOriginalAmount()); - assertThat(trade1.getType()).as("Type").isEqualTo(expected2.getType()); - return true; + test.assertValueAt( + 1, + trade1 -> { + assertThat(trade1.getId()).as("Id").isEqualTo(expected2.getId()); + assertThat(trade1.getCurrencyPair()) + .as("Currency pair") + .isEqualTo(expected2.getCurrencyPair()); + assertThat(trade1.getPrice()).as("Price").isEqualTo(expected2.getPrice()); + assertThat(trade1.getTimestamp()).as("Timestamp").isEqualTo(expected2.getTimestamp()); + assertThat(trade1.getOriginalAmount()) + .as("Amount") + .isEqualTo(expected2.getOriginalAmount()); + assertThat(trade1.getType()).as("Type").isEqualTo(expected2.getType()); + return true; }); - test.assertNoErrors(); - } + test.assertNoErrors(); + } - @Test(expected = NotAvailableFromExchangeException.class) - public void testGetTicker() throws Exception { - marketDataService.getTicker(CurrencyPair.BTC_EUR).test(); - } -} \ No newline at end of file + @Test(expected = NotAvailableFromExchangeException.class) + public void testGetTicker() throws Exception { + marketDataService.getTicker(CurrencyPair.BTC_EUR).test(); + } +} diff --git a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketBalanceTest.java b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketBalanceTest.java index 2e4e5bc50..afd53732c 100644 --- a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketBalanceTest.java +++ b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketBalanceTest.java @@ -1,31 +1,33 @@ package info.bitrich.xchangestream.coinmate; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + import com.fasterxml.jackson.core.type.TypeReference; import info.bitrich.xchangestream.coinmate.dto.CoinmateWebsocketBalance; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; -import org.junit.Test; - import java.io.IOException; import java.math.BigDecimal; import java.util.Map; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import org.junit.Test; public class CoinmateWebsocketBalanceTest { - @Test - public void coinmateWebsocketOpenOrdersTest() throws IOException { - String message = StreamingObjectMapperHelper.getObjectMapper().readTree( - this.getClass().getResource("/balance.json").openStream()).toString(); + @Test + public void coinmateWebsocketOpenOrdersTest() throws IOException { + String message = + StreamingObjectMapperHelper.getObjectMapper() + .readTree(this.getClass().getResource("/balance.json").openStream()) + .toString(); - Map balanceMap = - StreamingObjectMapperHelper.getObjectMapper().readValue(message, new TypeReference>() {}); + Map balanceMap = + StreamingObjectMapperHelper.getObjectMapper() + .readValue(message, new TypeReference>() {}); - assertThat(balanceMap).isNotNull(); - assertThat(balanceMap.size()).isEqualTo(8); - assertThat(balanceMap.get("BTC").getBalance()).isEqualTo(BigDecimal.valueOf(2.445)); - assertThat(balanceMap.get("BTC").getReserved()).isEqualTo(BigDecimal.valueOf(1.222)); - assertThat(balanceMap.get("EUR").getBalance()).isEqualTo(BigDecimal.valueOf(10000)); - assertThat(balanceMap.get("EUR").getReserved()).isEqualTo(BigDecimal.valueOf(5000)); - } + assertThat(balanceMap).isNotNull(); + assertThat(balanceMap.size()).isEqualTo(8); + assertThat(balanceMap.get("BTC").getBalance()).isEqualTo(BigDecimal.valueOf(2.445)); + assertThat(balanceMap.get("BTC").getReserved()).isEqualTo(BigDecimal.valueOf(1.222)); + assertThat(balanceMap.get("EUR").getBalance()).isEqualTo(BigDecimal.valueOf(10000)); + assertThat(balanceMap.get("EUR").getReserved()).isEqualTo(BigDecimal.valueOf(5000)); + } } diff --git a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketOpenOrderTest.java b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketOpenOrderTest.java index c78f5a326..813be2f83 100644 --- a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketOpenOrderTest.java +++ b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketOpenOrderTest.java @@ -1,33 +1,36 @@ package info.bitrich.xchangestream.coinmate; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + import com.fasterxml.jackson.core.type.TypeReference; import info.bitrich.xchangestream.coinmate.dto.CoinmateWebsocketOpenOrder; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; -import org.junit.Test; - import java.io.IOException; import java.util.List; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import org.junit.Test; public class CoinmateWebsocketOpenOrderTest { - @Test - public void coinmateWebsocketOpenOrdersTest() throws IOException { - String message = StreamingObjectMapperHelper.getObjectMapper().readTree( - this.getClass().getResource("/open-orders.json").openStream()).toString(); + @Test + public void coinmateWebsocketOpenOrdersTest() throws IOException { + String message = + StreamingObjectMapperHelper.getObjectMapper() + .readTree(this.getClass().getResource("/open-orders.json").openStream()) + .toString(); - List websocketOpenOrders = StreamingObjectMapperHelper.getObjectMapper().readValue(message, new TypeReference>() {}); + List websocketOpenOrders = + StreamingObjectMapperHelper.getObjectMapper() + .readValue(message, new TypeReference>() {}); - assertThat(websocketOpenOrders).isNotNull(); - assertThat(websocketOpenOrders.size()).isEqualTo(2); - assertThat(websocketOpenOrders.get(0).getId()).isEqualTo("11111111"); - assertThat(websocketOpenOrders.get(0).getTimestamp()).isEqualTo(1567331368945L); - assertThat(websocketOpenOrders.get(0).getPrice()).isEqualTo(8780); - assertThat(websocketOpenOrders.get(0).getOriginalOrderSize()).isEqualTo(0.5); - assertThat(websocketOpenOrders.get(0).getAmount()).isEqualTo(0.5); - assertThat(websocketOpenOrders.get(0).getAmount()).isEqualTo(0.5); - assertThat(websocketOpenOrders.get(0).isTrailing()).isFalse(); - assertThat(websocketOpenOrders.get(0).isHidden()).isFalse(); - } + assertThat(websocketOpenOrders).isNotNull(); + assertThat(websocketOpenOrders.size()).isEqualTo(2); + assertThat(websocketOpenOrders.get(0).getId()).isEqualTo("11111111"); + assertThat(websocketOpenOrders.get(0).getTimestamp()).isEqualTo(1567331368945L); + assertThat(websocketOpenOrders.get(0).getPrice()).isEqualTo(8780); + assertThat(websocketOpenOrders.get(0).getOriginalOrderSize()).isEqualTo(0.5); + assertThat(websocketOpenOrders.get(0).getAmount()).isEqualTo(0.5); + assertThat(websocketOpenOrders.get(0).getAmount()).isEqualTo(0.5); + assertThat(websocketOpenOrders.get(0).isTrailing()).isFalse(); + assertThat(websocketOpenOrders.get(0).isHidden()).isFalse(); + } } diff --git a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketUserTradeTest.java b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketUserTradeTest.java index 65ec13254..3596a8a1b 100644 --- a/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketUserTradeTest.java +++ b/xchange-stream-coinmate/src/test/java/info/bitrich/xchangestream/coinmate/CoinmateWebsocketUserTradeTest.java @@ -1,35 +1,38 @@ package info.bitrich.xchangestream.coinmate; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + import com.fasterxml.jackson.core.type.TypeReference; import info.bitrich.xchangestream.coinmate.dto.CoinmateWebSocketUserTrade; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; -import org.junit.Test; - import java.io.IOException; import java.util.List; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import org.junit.Test; public class CoinmateWebsocketUserTradeTest { - @Test - public void coinmateWebsocketOpenOrdersTest() throws IOException { - String message = StreamingObjectMapperHelper.getObjectMapper().readTree( - this.getClass().getResource("/user-trade.json").openStream()).toString(); + @Test + public void coinmateWebsocketOpenOrdersTest() throws IOException { + String message = + StreamingObjectMapperHelper.getObjectMapper() + .readTree(this.getClass().getResource("/user-trade.json").openStream()) + .toString(); - List websocketUserTrades = StreamingObjectMapperHelper.getObjectMapper().readValue(message, new TypeReference>() {}); + List websocketUserTrades = + StreamingObjectMapperHelper.getObjectMapper() + .readValue(message, new TypeReference>() {}); - assertThat(websocketUserTrades).isNotNull(); - assertThat(websocketUserTrades.size()).isEqualTo(1); - assertThat(websocketUserTrades.get(0).getTransactionId()).isEqualTo("11111111"); - assertThat(websocketUserTrades.get(0).getTimestamp()).isEqualTo(1567339757594L); - assertThat(websocketUserTrades.get(0).getPrice()).isEqualTo(8741.2); - assertThat(websocketUserTrades.get(0).getAmount()).isEqualTo(0.555); - assertThat(websocketUserTrades.get(0).getBuyOrderId()).isEqualTo("11111111"); - assertThat(websocketUserTrades.get(0).getSellOrderId()).isEqualTo("11111112"); - assertThat(websocketUserTrades.get(0).getTakerOrderType()).isEqualTo("SELL"); - assertThat(websocketUserTrades.get(0).getUserOrderType()).isEqualTo("SELL"); - assertThat(websocketUserTrades.get(0).getFee()).isEqualTo(0.00600850); - assertThat(websocketUserTrades.get(0).getUserFeeType()).isEqualTo("TAKER"); - } + assertThat(websocketUserTrades).isNotNull(); + assertThat(websocketUserTrades.size()).isEqualTo(1); + assertThat(websocketUserTrades.get(0).getTransactionId()).isEqualTo("11111111"); + assertThat(websocketUserTrades.get(0).getTimestamp()).isEqualTo(1567339757594L); + assertThat(websocketUserTrades.get(0).getPrice()).isEqualTo(8741.2); + assertThat(websocketUserTrades.get(0).getAmount()).isEqualTo(0.555); + assertThat(websocketUserTrades.get(0).getBuyOrderId()).isEqualTo("11111111"); + assertThat(websocketUserTrades.get(0).getSellOrderId()).isEqualTo("11111112"); + assertThat(websocketUserTrades.get(0).getTakerOrderType()).isEqualTo("SELL"); + assertThat(websocketUserTrades.get(0).getUserOrderType()).isEqualTo("SELL"); + assertThat(websocketUserTrades.get(0).getFee()).isEqualTo(0.00600850); + assertThat(websocketUserTrades.get(0).getUserFeeType()).isEqualTo("TAKER"); + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/ProductSubscription.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/ProductSubscription.java index 79d6ecb3a..ae20d5ee2 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/ProductSubscription.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/ProductSubscription.java @@ -1,141 +1,140 @@ package info.bitrich.xchangestream.core; -import org.knowm.xchange.currency.Currency; -import org.knowm.xchange.currency.CurrencyPair; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; /** - * Use to specify subscriptions during the connect phase - * For instancing, use builder @link {@link ProductSubscriptionBuilder} + * Use to specify subscriptions during the connect phase For instancing, use builder @link {@link + * ProductSubscriptionBuilder} */ public class ProductSubscription { - private final List orderBook; - private final List trades; - private final List ticker; - private final List userTrades; - private final List orders; - private final List balances; - - private ProductSubscription(ProductSubscriptionBuilder builder) { - this.orderBook = asList(builder.orderBook); - this.trades = asList(builder.trades); - this.ticker = asList(builder.ticker); - this.orders = asList(builder.orders); - this.userTrades = asList(builder.userTrades); - this.balances = asList(builder.balances); - } - - private List asList(Iterable collection) { - List result = new ArrayList<>(); - collection.forEach(result::add); - return Collections.unmodifiableList(result); - } - - public List getOrderBook() { - return orderBook; - } - - public List getTrades() { - return trades; - } - - public List getTicker() { - return ticker; + private final List orderBook; + private final List trades; + private final List ticker; + private final List userTrades; + private final List orders; + private final List balances; + + private ProductSubscription(ProductSubscriptionBuilder builder) { + this.orderBook = asList(builder.orderBook); + this.trades = asList(builder.trades); + this.ticker = asList(builder.ticker); + this.orders = asList(builder.orders); + this.userTrades = asList(builder.userTrades); + this.balances = asList(builder.balances); + } + + private List asList(Iterable collection) { + List result = new ArrayList<>(); + collection.forEach(result::add); + return Collections.unmodifiableList(result); + } + + public List getOrderBook() { + return orderBook; + } + + public List getTrades() { + return trades; + } + + public List getTicker() { + return ticker; + } + + public List getOrders() { + return orders; + } + + public List getUserTrades() { + return userTrades; + } + + public List getBalances() { + return balances; + } + + public boolean isEmpty() { + return !hasAuthenticated() && !hasUnauthenticated(); + } + + public boolean hasAuthenticated() { + return !orders.isEmpty() || !userTrades.isEmpty() || !balances.isEmpty(); + } + + public boolean hasUnauthenticated() { + return !ticker.isEmpty() || !trades.isEmpty() || !orderBook.isEmpty(); + } + + public static ProductSubscriptionBuilder create() { + return new ProductSubscriptionBuilder(); + } + + public static class ProductSubscriptionBuilder { + private final Set orderBook; + private final Set trades; + private final Set ticker; + private final Set userTrades; + private final Set orders; + private final Set balances; + + private ProductSubscriptionBuilder() { + orderBook = new HashSet<>(); + trades = new HashSet<>(); + ticker = new HashSet<>(); + orders = new HashSet<>(); + userTrades = new HashSet<>(); + balances = new HashSet<>(); } - public List getOrders() { - return orders; + public ProductSubscriptionBuilder addOrderbook(CurrencyPair pair) { + orderBook.add(pair); + return this; } - public List getUserTrades() { - return userTrades; + public ProductSubscriptionBuilder addTrades(CurrencyPair pair) { + trades.add(pair); + return this; } - public List getBalances() { - return balances; + public ProductSubscriptionBuilder addTicker(CurrencyPair pair) { + ticker.add(pair); + return this; } - public boolean isEmpty() { - return !hasAuthenticated() && !hasUnauthenticated(); + public ProductSubscriptionBuilder addOrders(CurrencyPair pair) { + orders.add(pair); + return this; } - public boolean hasAuthenticated() { - return !orders.isEmpty() || !userTrades.isEmpty() || !balances.isEmpty(); + public ProductSubscriptionBuilder addUserTrades(CurrencyPair pair) { + userTrades.add(pair); + return this; } - public boolean hasUnauthenticated() { - return !ticker.isEmpty() || !trades.isEmpty() || !orderBook.isEmpty(); + public ProductSubscriptionBuilder addBalances(Currency pair) { + balances.add(pair); + return this; } - public static ProductSubscriptionBuilder create() { - return new ProductSubscriptionBuilder(); + public ProductSubscriptionBuilder addAll(CurrencyPair pair) { + orderBook.add(pair); + trades.add(pair); + ticker.add(pair); + orders.add(pair); + userTrades.add(pair); + balances.add(pair.base); + balances.add(pair.counter); + return this; } - public static class ProductSubscriptionBuilder { - private final Set orderBook; - private final Set trades; - private final Set ticker; - private final Set userTrades; - private final Set orders; - private final Set balances; - - private ProductSubscriptionBuilder() { - orderBook = new HashSet<>(); - trades = new HashSet<>(); - ticker = new HashSet<>(); - orders = new HashSet<>(); - userTrades = new HashSet<>(); - balances = new HashSet<>(); - } - - public ProductSubscriptionBuilder addOrderbook(CurrencyPair pair) { - orderBook.add(pair); - return this; - } - - public ProductSubscriptionBuilder addTrades(CurrencyPair pair) { - trades.add(pair); - return this; - } - - public ProductSubscriptionBuilder addTicker(CurrencyPair pair) { - ticker.add(pair); - return this; - } - - public ProductSubscriptionBuilder addOrders(CurrencyPair pair) { - orders.add(pair); - return this; - } - - public ProductSubscriptionBuilder addUserTrades(CurrencyPair pair) { - userTrades.add(pair); - return this; - } - - public ProductSubscriptionBuilder addBalances(Currency pair) { - balances.add(pair); - return this; - } - - public ProductSubscriptionBuilder addAll(CurrencyPair pair) { - orderBook.add(pair); - trades.add(pair); - ticker.add(pair); - orders.add(pair); - userTrades.add(pair); - balances.add(pair.base); - balances.add(pair.counter); - return this; - } - - public ProductSubscription build() { - return new ProductSubscription(this); - } + public ProductSubscription build() { + return new ProductSubscription(this); } -} \ No newline at end of file + } +} diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingAccountService.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingAccountService.java index 8d9d93073..f97c51034 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingAccountService.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingAccountService.java @@ -1,36 +1,35 @@ package info.bitrich.xchangestream.core; +import io.reactivex.Observable; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.exceptions.ExchangeSecurityException; import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import org.knowm.xchange.service.account.AccountService; -import io.reactivex.Observable; - public interface StreamingAccountService { - /** - * Get the changes of account balance for the logged-in user. - * - *

Warning: there are currently no guarantees that messages will - * arrive in order, that messages will not be skipped, or that any initial state - * message will be sent on connection. Most exchanges have a recommended approach - * for managing this, involving timestamps, sequence numbers and a separate REST - * API for re-sync when inconsistencies appear. You should implement these approaches, - * if required, by combining calls to this method with - * {@link AccountService#getAccountInfo()}. - * - *

Emits {@link info.bitrich.xchangestream.service.exception.NotConnectedException} When - * not connected to the WebSocket API.

- * - *

Immediately throws {@link ExchangeSecurityException} if called without - * authentication details

- * - * @param currency Currency to monitor. - * @return {@link Observable} that emits {@link Balance} when exchange sends the update. - */ - default Observable getBalanceChanges(Currency currency, Object... args) { - throw new NotYetImplementedForExchangeException(); - } -} \ No newline at end of file + /** + * Get the changes of account balance for the logged-in user. + * + *

Warning: there are currently no guarantees that messages will arrive in + * order, that messages will not be skipped, or that any initial state message will be sent on + * connection. Most exchanges have a recommended approach for managing this, involving timestamps, + * sequence numbers and a separate REST API for re-sync when inconsistencies appear. You should + * implement these approaches, if required, by combining calls to this method with {@link + * AccountService#getAccountInfo()}. + * + *

Emits {@link + * info.bitrich.xchangestream.service.exception.NotConnectedException} When not connected to the + * WebSocket API. + * + *

Immediately throws {@link ExchangeSecurityException} if called without + * authentication details + * + * @param currency Currency to monitor. + * @return {@link Observable} that emits {@link Balance} when exchange sends the update. + */ + default Observable getBalanceChanges(Currency currency, Object... args) { + throw new NotYetImplementedForExchangeException(); + } +} diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchange.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchange.java index 45cb4585e..13a36350c 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchange.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchange.java @@ -10,121 +10,123 @@ import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; public interface StreamingExchange extends Exchange { - String USE_SANDBOX = "Use_Sandbox"; - String ACCEPT_ALL_CERITICATES = "Accept_All_Ceriticates"; - String ENABLE_LOGGING_HANDLER = "Enable_Logging_Handler"; - String SOCKS_PROXY_HOST = "SOCKS_Proxy_Host"; - String SOCKS_PROXY_PORT = "SOCKS_Proxy_Port"; - - /** - * Connects to the WebSocket API of the exchange. - * - * @param args Product subscription is used only in certain exchanges where you need to specify subscriptions during the connect phase. - * @return {@link Completable} that completes upon successful connection. - */ - Completable connect(ProductSubscription... args); - - /** - * Disconnect from the WebSocket API. - * - * @return {@link Completable} that completes upon successful disconnect. - */ - Completable disconnect(); - - /** - * Checks whether connection to the exchange is alive. - * - * @return true if connection is open, otherwise false. - */ - boolean isAlive(); - - /** - * Observable for reconnection failure event. - * When this happens, it usually indicates that the server or the network is down. - * - * @return Observable with the exception during reconnection. - */ - default Observable reconnectFailure() { - throw new NotYetImplementedForExchangeException(); + String USE_SANDBOX = "Use_Sandbox"; + String ACCEPT_ALL_CERITICATES = "Accept_All_Ceriticates"; + String ENABLE_LOGGING_HANDLER = "Enable_Logging_Handler"; + String SOCKS_PROXY_HOST = "SOCKS_Proxy_Host"; + String SOCKS_PROXY_PORT = "SOCKS_Proxy_Port"; + + /** + * Connects to the WebSocket API of the exchange. + * + * @param args Product subscription is used only in certain exchanges where you need to specify + * subscriptions during the connect phase. + * @return {@link Completable} that completes upon successful connection. + */ + Completable connect(ProductSubscription... args); + + /** + * Disconnect from the WebSocket API. + * + * @return {@link Completable} that completes upon successful disconnect. + */ + Completable disconnect(); + + /** + * Checks whether connection to the exchange is alive. + * + * @return true if connection is open, otherwise false. + */ + boolean isAlive(); + + /** + * Observable for reconnection failure event. When this happens, it usually indicates that the + * server or the network is down. + * + * @return Observable with the exception during reconnection. + */ + default Observable reconnectFailure() { + throw new NotYetImplementedForExchangeException(); + } + + /** + * Observable for connection success event. When this happens, it usually indicates that the + * server or the network is down. + * + * @return Observable with the exception during reconnection. + */ + default Observable connectionSuccess() { + throw new NotYetImplementedForExchangeException(); + } + + /** + * Observable for disconnection event. + * + * @return Observable with the exception during reconnection. + */ + default Observable disconnectObservable() { + throw new NotYetImplementedForExchangeException(); + } + + /** + * Observable for message delay measure. Every time when the client received a message with a + * timestamp, the delay time is calculated and pushed to subscribers. + * + * @return Observable with the message delay measure. + */ + default Observable messageDelay() { + throw new NotYetImplementedForExchangeException(); + } + + default void resubscribeChannels() { + throw new NotYetImplementedForExchangeException(); + } + + default Observable connectionIdle() { + throw new NotYetImplementedForExchangeException(); + }; + + /** Returns service that can be used to access streaming market data. */ + StreamingMarketDataService getStreamingMarketDataService(); + + /** Returns service that can be used to access streaming account data. */ + default StreamingAccountService getStreamingAccountService() { + throw new NotYetImplementedForExchangeException(); + } + + /** Returns service that can be used to access streaming trade data. */ + default StreamingTradeService getStreamingTradeService() { + throw new NotYetImplementedForExchangeException(); + } + + /** + * Set whether or not to enable compression handler. + * + * @param compressedMessages Defaults to false + */ + void useCompressedMessages(boolean compressedMessages); + + default void applyStreamingSpecification( + ExchangeSpecification exchangeSpec, NettyStreamingService streamingService) { + streamingService.setSocksProxyHost( + (String) exchangeSpec.getExchangeSpecificParametersItem(SOCKS_PROXY_HOST)); + streamingService.setSocksProxyPort( + (Integer) exchangeSpec.getExchangeSpecificParametersItem(SOCKS_PROXY_PORT)); + streamingService.setBeforeConnectionHandler( + (Runnable) + exchangeSpec.getExchangeSpecificParametersItem( + ConnectableService.BEFORE_CONNECTION_HANDLER)); + + Boolean accept_all_ceriticates = + (Boolean) exchangeSpec.getExchangeSpecificParametersItem(ACCEPT_ALL_CERITICATES); + if (accept_all_ceriticates != null && accept_all_ceriticates) { + streamingService.setAcceptAllCertificates(true); } - /** - * Observable for connection success event. - * When this happens, it usually indicates that the server or the network is down. - * - * @return Observable with the exception during reconnection. - */ - default Observable connectionSuccess() { - throw new NotYetImplementedForExchangeException(); + Boolean enable_logging_handler = + (Boolean) exchangeSpec.getExchangeSpecificParametersItem(ENABLE_LOGGING_HANDLER); + if (enable_logging_handler != null && enable_logging_handler) { + streamingService.setEnableLoggingHandler(true); } - - /** - * Observable for disconnection event. - * - * @return Observable with the exception during reconnection. - */ - default Observable disconnectObservable() { - throw new NotYetImplementedForExchangeException(); - } - - /** - * Observable for message delay measure. - * Every time when the client received a message with a timestamp, the delay time is calculated and pushed to subscribers. - * - * @return Observable with the message delay measure. - */ - default Observable messageDelay() { - throw new NotYetImplementedForExchangeException(); - } - - default void resubscribeChannels() { - throw new NotYetImplementedForExchangeException(); - } - - default Observable connectionIdle() { - throw new NotYetImplementedForExchangeException(); - }; - - /** - * Returns service that can be used to access streaming market data. - */ - StreamingMarketDataService getStreamingMarketDataService(); - - /** - * Returns service that can be used to access streaming account data. - */ - default StreamingAccountService getStreamingAccountService() { - throw new NotYetImplementedForExchangeException(); - } - - /** - * Returns service that can be used to access streaming trade data. - */ - default StreamingTradeService getStreamingTradeService() { - throw new NotYetImplementedForExchangeException(); - } - - /** - * Set whether or not to enable compression handler. - * - * @param compressedMessages Defaults to false - */ - void useCompressedMessages(boolean compressedMessages); - - default void applyStreamingSpecification(ExchangeSpecification exchangeSpec, NettyStreamingService streamingService){ - streamingService.setSocksProxyHost((String) exchangeSpec.getExchangeSpecificParametersItem(SOCKS_PROXY_HOST)); - streamingService.setSocksProxyPort((Integer) exchangeSpec.getExchangeSpecificParametersItem(SOCKS_PROXY_PORT)); - streamingService.setBeforeConnectionHandler((Runnable) exchangeSpec.getExchangeSpecificParametersItem(ConnectableService.BEFORE_CONNECTION_HANDLER)); - - Boolean accept_all_ceriticates = (Boolean) exchangeSpec.getExchangeSpecificParametersItem(ACCEPT_ALL_CERITICATES); - if (accept_all_ceriticates != null && accept_all_ceriticates) { - streamingService.setAcceptAllCertificates(true); - } - - Boolean enable_logging_handler = (Boolean) exchangeSpec.getExchangeSpecificParametersItem(ENABLE_LOGGING_HANDLER); - if (enable_logging_handler != null && enable_logging_handler) { - streamingService.setEnableLoggingHandler(true); - } - } - + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchangeFactory.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchangeFactory.java index bb2ede6b9..59a25975e 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchangeFactory.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingExchangeFactory.java @@ -9,116 +9,112 @@ import org.slf4j.LoggerFactory; /** - *

* Factory to provide the following to {@link StreamingExchange}: - *

+ * *
    - *
  • Manages the creation of specific Exchange implementations using runtime dependencies
  • + *
  • Manages the creation of specific Exchange implementations using runtime dependencies *
*/ public enum StreamingExchangeFactory { + INSTANCE; - INSTANCE; + // flags + private final Logger LOG = LoggerFactory.getLogger(ExchangeFactory.class); - // flags - private final Logger LOG = LoggerFactory.getLogger(ExchangeFactory.class); + /** Constructor */ + private StreamingExchangeFactory() {} - /** - * Constructor - */ - private StreamingExchangeFactory() { + /** + * Create an Exchange object without default ExchangeSpecification + * + *

The factory is parameterised with the name of the exchange implementation class. This must + * be a class extending {@link org.knowm.xchange.Exchange}. + * + * @param exchangeClassName the fully-qualified class name of the exchange + * @return a new exchange instance configured with the default {@link + * org.knowm.xchange.ExchangeSpecification} + */ + public StreamingExchange createExchangeWithoutSpecification(String exchangeClassName) { - } + Assert.notNull(exchangeClassName, "exchangeClassName cannot be null"); - /** - * Create an Exchange object without default ExchangeSpecification - *

- * The factory is parameterised with the name of the exchange implementation class. This must be a class extending - * {@link org.knowm.xchange.Exchange}. - *

- * - * @param exchangeClassName the fully-qualified class name of the exchange - * @return a new exchange instance configured with the default {@link org.knowm.xchange.ExchangeSpecification} - */ - public StreamingExchange createExchangeWithoutSpecification(String exchangeClassName) { - - Assert.notNull(exchangeClassName, "exchangeClassName cannot be null"); - - LOG.debug("Creating default exchange from class name"); - - // Attempt to create an instance of the exchange provider - try { - - // Attempt to locate the exchange provider on the classpath - Class exchangeProviderClass = Class.forName(exchangeClassName); - - // Test that the class implements Exchange - if (Exchange.class.isAssignableFrom(exchangeProviderClass)) { - // Instantiate through the default constructor and use the default exchange specification - StreamingExchange exchange = (StreamingExchange) exchangeProviderClass.getConstructor().newInstance(); - return exchange; - } else { - throw new ExchangeException("Class '" + exchangeClassName + "' does not implement Exchange"); - } - } catch (ReflectiveOperationException e) { - throw new ExchangeException("Problem creating Exchange ", e); - } - - // Cannot be here due to exceptions + LOG.debug("Creating default exchange from class name"); - } + // Attempt to create an instance of the exchange provider + try { - /** - * Create an Exchange object with default ExchangeSpecification - *

- * The factory is parameterised with the name of the exchange implementation class. This must be a class extending - * {@link org.knowm.xchange.Exchange}. - *

- * - * @param exchangeClassName the fully-qualified class name of the exchange - * @return a new exchange instance configured with the default {@link org.knowm.xchange.ExchangeSpecification} - */ - public StreamingExchange createExchange(String exchangeClassName) { + // Attempt to locate the exchange provider on the classpath + Class exchangeProviderClass = Class.forName(exchangeClassName); - Assert.notNull(exchangeClassName, "exchangeClassName cannot be null"); + // Test that the class implements Exchange + if (Exchange.class.isAssignableFrom(exchangeProviderClass)) { + // Instantiate through the default constructor and use the default exchange specification + StreamingExchange exchange = + (StreamingExchange) exchangeProviderClass.getConstructor().newInstance(); + return exchange; + } else { + throw new ExchangeException( + "Class '" + exchangeClassName + "' does not implement Exchange"); + } + } catch (ReflectiveOperationException e) { + throw new ExchangeException("Problem creating Exchange ", e); + } - LOG.debug("Creating default exchange from class name"); + // Cannot be here due to exceptions - StreamingExchange exchange = createExchangeWithoutSpecification(exchangeClassName); - exchange.applySpecification(exchange.getDefaultExchangeSpecification()); - return exchange; + } - } + /** + * Create an Exchange object with default ExchangeSpecification + * + *

The factory is parameterised with the name of the exchange implementation class. This must + * be a class extending {@link org.knowm.xchange.Exchange}. + * + * @param exchangeClassName the fully-qualified class name of the exchange + * @return a new exchange instance configured with the default {@link + * org.knowm.xchange.ExchangeSpecification} + */ + public StreamingExchange createExchange(String exchangeClassName) { + + Assert.notNull(exchangeClassName, "exchangeClassName cannot be null"); - public StreamingExchange createExchange(ExchangeSpecification exchangeSpecification) { + LOG.debug("Creating default exchange from class name"); - Assert.notNull(exchangeSpecification, "exchangeSpecfication cannot be null"); + StreamingExchange exchange = createExchangeWithoutSpecification(exchangeClassName); + exchange.applySpecification(exchange.getDefaultExchangeSpecification()); + return exchange; + } - LOG.debug("Creating exchange from specification"); + public StreamingExchange createExchange(ExchangeSpecification exchangeSpecification) { - String exchangeClassName = exchangeSpecification.getExchangeClassName(); + Assert.notNull(exchangeSpecification, "exchangeSpecfication cannot be null"); - // Attempt to create an instance of the exchange provider - try { + LOG.debug("Creating exchange from specification"); - // Attempt to locate the exchange provider on the classpath - Class exchangeProviderClass = Class.forName(exchangeClassName); + String exchangeClassName = exchangeSpecification.getExchangeClassName(); - // Test that the class implements Exchange - if (Exchange.class.isAssignableFrom(exchangeProviderClass)) { - // Instantiate through the default constructor - StreamingExchange exchange = (StreamingExchange) exchangeProviderClass.getConstructor().newInstance(); - exchange.applySpecification(exchangeSpecification); - return exchange; - } else { - throw new ExchangeException("Class '" + exchangeClassName + "' does not implement Exchange"); - } - } catch (ReflectiveOperationException e) { - throw new ExchangeException("Problem starting exchange provider ", e); - } + // Attempt to create an instance of the exchange provider + try { - // Cannot be here due to exceptions + // Attempt to locate the exchange provider on the classpath + Class exchangeProviderClass = Class.forName(exchangeClassName); + // Test that the class implements Exchange + if (Exchange.class.isAssignableFrom(exchangeProviderClass)) { + // Instantiate through the default constructor + StreamingExchange exchange = + (StreamingExchange) exchangeProviderClass.getConstructor().newInstance(); + exchange.applySpecification(exchangeSpecification); + return exchange; + } else { + throw new ExchangeException( + "Class '" + exchangeClassName + "' does not implement Exchange"); + } + } catch (ReflectiveOperationException e) { + throw new ExchangeException("Problem starting exchange provider ", e); } + // Cannot be here due to exceptions + + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingMarketDataService.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingMarketDataService.java index cdbefe643..19f0703c1 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingMarketDataService.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingMarketDataService.java @@ -1,51 +1,49 @@ package info.bitrich.xchangestream.core; +import io.reactivex.Observable; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.marketdata.Trade; -import io.reactivex.Observable; - - public interface StreamingMarketDataService { - /** - * Get an order book representing the current offered exchange rates (market depth). - * - *

Warning: The library will attempt to keep the snapshots returned in sync with the - * exchange using the approaches published by that exchange. However, there are currently no guarantees - * that messages will not be skipped, or that any initial state - * message will be sent on connection.

- * - * Emits {@link info.bitrich.xchangestream.service.exception.NotConnectedException} - * when not connected to the WebSocket API. - * - * @param currencyPair Currency pair of the order book - * @return {@link Observable} that emits {@link OrderBook} when exchange sends the update. - */ - Observable getOrderBook(CurrencyPair currencyPair, Object... args); + /** + * Get an order book representing the current offered exchange rates (market depth). + * + *

Warning: The library will attempt to keep the snapshots returned in sync + * with the exchange using the approaches published by that exchange. However, there are currently + * no guarantees that messages will not be skipped, or that any initial state message will be sent + * on connection. Emits {@link info.bitrich.xchangestream.service.exception.NotConnectedException} + * when not connected to the WebSocket API. + * + * @param currencyPair Currency pair of the order book + * @return {@link Observable} that emits {@link OrderBook} when exchange sends the update. + */ + Observable getOrderBook(CurrencyPair currencyPair, Object... args); - /** - * Get a ticker representing the current exchange rate. - * Emits {@link info.bitrich.xchangestream.service.exception.NotConnectedException} When not connected to the WebSocket API. - * - *

Warning: There are currently no guarantees - * that messages will not be skipped, or that any initial state - * message will be sent on connection.

- * - * @param currencyPair Currency pair of the ticker - * @return {@link Observable} that emits {@link Ticker} when exchange sends the update. - */ - Observable getTicker(CurrencyPair currencyPair, Object... args); + /** + * Get a ticker representing the current exchange rate. Emits {@link + * info.bitrich.xchangestream.service.exception.NotConnectedException} When not connected to the + * WebSocket API. + * + *

Warning: There are currently no guarantees that messages will not be + * skipped, or that any initial state message will be sent on connection. + * + * @param currencyPair Currency pair of the ticker + * @return {@link Observable} that emits {@link Ticker} when exchange sends the update. + */ + Observable getTicker(CurrencyPair currencyPair, Object... args); - /** - * Get the trades performed by the exchange. - * Emits {@link info.bitrich.xchangestream.service.exception.NotConnectedException} When not connected to the WebSocket API. - * - *

Warning: There are currently no guarantees that messages will not be skipped.

- * - * @param currencyPair Currency pair of the trades - * @return {@link Observable} that emits {@link Trade} when exchange sends the update. - */ - Observable getTrades(CurrencyPair currencyPair, Object... args); + /** + * Get the trades performed by the exchange. Emits {@link + * info.bitrich.xchangestream.service.exception.NotConnectedException} When not connected to the + * WebSocket API. + * + *

Warning: There are currently no guarantees that messages will not be + * skipped. + * + * @param currencyPair Currency pair of the trades + * @return {@link Observable} that emits {@link Trade} when exchange sends the update. + */ + Observable getTrades(CurrencyPair currencyPair, Object... args); } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingTradeService.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingTradeService.java index 7863a3b8c..5cb3e2e06 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingTradeService.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/core/StreamingTradeService.java @@ -1,5 +1,6 @@ package info.bitrich.xchangestream.core; +import io.reactivex.Observable; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.UserTrade; @@ -7,55 +8,53 @@ import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import org.knowm.xchange.service.trade.TradeService; -import io.reactivex.Observable; - public interface StreamingTradeService { - /** - * Get the changes of order state for the logged-in user. - * - *

Warning: there are currently no guarantees that messages will - * arrive in order, that messages will not be skipped, or that any initial state - * message will be sent on connection. Most exchanges have a recommended approach - * for managing this, involving timestamps, sequence numbers and a separate REST - * API for re-sync when inconsistencies appear. You should implement these approaches, - * if required, by combining calls to this method with - * {@link TradeService#getOpenOrders()}.

- * - *

Emits {@link info.bitrich.xchangestream.service.exception.NotConnectedException} When - * not connected to the WebSocket API.

- * - *

Immediately throws {@link ExchangeSecurityException} if called without - * authentication details

- * - * @param currencyPair Currency pair of the order changes. - * @return {@link Observable} that emits {@link Order} when exchange sends the update. - */ - default Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { - throw new NotYetImplementedForExchangeException(); - } + /** + * Get the changes of order state for the logged-in user. + * + *

Warning: there are currently no guarantees that messages will arrive in + * order, that messages will not be skipped, or that any initial state message will be sent on + * connection. Most exchanges have a recommended approach for managing this, involving timestamps, + * sequence numbers and a separate REST API for re-sync when inconsistencies appear. You should + * implement these approaches, if required, by combining calls to this method with {@link + * TradeService#getOpenOrders()}. + * + *

Emits {@link + * info.bitrich.xchangestream.service.exception.NotConnectedException} When not connected to the + * WebSocket API. + * + *

Immediately throws {@link ExchangeSecurityException} if called without + * authentication details + * + * @param currencyPair Currency pair of the order changes. + * @return {@link Observable} that emits {@link Order} when exchange sends the update. + */ + default Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + throw new NotYetImplementedForExchangeException(); + } - /** - * Gets authenticated trades for the logged-in user. - * - *

Warning: there are currently no guarantees that messages will - * arrive in order, that messages will not be skipped, or that any initial state - * message will be sent on connection. Most exchanges have a recommended approach - * for managing this, involving timestamps, sequence numbers and a separate REST - * API for re-sync when inconsistencies appear. You should implement these approaches, - * if required, by combining calls to this method with - * {@link TradeService#getTradeHistory(org.knowm.xchange.service.trade.params.TradeHistoryParams)}

- * - *

Emits {@link info.bitrich.xchangestream.service.exception.NotConnectedException} When - * not connected to the WebSocket API.

- * - *

Immediately throws {@link ExchangeSecurityException} if called without - * authentication details

- * - * @param currencyPair Currency pair for which to get trades. - * @return {@link Observable} that emits {@link UserTrade} when exchange sends the update. - */ - default Observable getUserTrades(CurrencyPair currencyPair, Object... args) { - throw new NotYetImplementedForExchangeException(); - } -} \ No newline at end of file + /** + * Gets authenticated trades for the logged-in user. + * + *

Warning: there are currently no guarantees that messages will arrive in + * order, that messages will not be skipped, or that any initial state message will be sent on + * connection. Most exchanges have a recommended approach for managing this, involving timestamps, + * sequence numbers and a separate REST API for re-sync when inconsistencies appear. You should + * implement these approaches, if required, by combining calls to this method with {@link + * TradeService#getTradeHistory(org.knowm.xchange.service.trade.params.TradeHistoryParams)} + * + *

Emits {@link + * info.bitrich.xchangestream.service.exception.NotConnectedException} When not connected to the + * WebSocket API. + * + *

Immediately throws {@link ExchangeSecurityException} if called without + * authentication details + * + * @param currencyPair Currency pair for which to get trades. + * @return {@link Observable} that emits {@link UserTrade} when exchange sends the update. + */ + default Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + throw new NotYetImplementedForExchangeException(); + } +} diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/BookSanityChecker.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/BookSanityChecker.java index 05f3a656f..bb2a07b0b 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/BookSanityChecker.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/BookSanityChecker.java @@ -1,69 +1,65 @@ package info.bitrich.xchangestream.util; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.dto.marketdata.OrderBook; -import org.knowm.xchange.dto.trade.LimitOrder; +import static java.lang.String.format; import java.math.BigDecimal; import java.util.Iterator; import java.util.List; import java.util.Objects; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.trade.LimitOrder; -import static java.lang.String.format; - -/** - * @author Foat Akhmadeev - * 05/06/2018 - */ +/** @author Foat Akhmadeev 05/06/2018 */ public class BookSanityChecker { - public static String hasErrors(OrderBook book) { - List asks = book.getOrders(Order.OrderType.ASK); - if (!Objects.equals(asks, book.getAsks())) return "Asks did not match for OrderBook"; - List bids = book.getOrders(Order.OrderType.BID); - if (!Objects.equals(bids, book.getBids())) return "Bids did not match for OrderBook"; - - LimitOrder bestAsk = null; - if (!asks.isEmpty()) { - bestAsk = asks.get(0); - String askCheck = hasErrors(asks.iterator()); - if (askCheck != null) return askCheck; - } + public static String hasErrors(OrderBook book) { + List asks = book.getOrders(Order.OrderType.ASK); + if (!Objects.equals(asks, book.getAsks())) return "Asks did not match for OrderBook"; + List bids = book.getOrders(Order.OrderType.BID); + if (!Objects.equals(bids, book.getBids())) return "Bids did not match for OrderBook"; - LimitOrder bestBid = null; - if (!bids.isEmpty()) { - bestBid = bids.get(0); - String bidCheck = hasErrors(CollectionUtils.descendingIterable(bids).iterator()); - if (bidCheck != null) return bidCheck; - } + LimitOrder bestAsk = null; + if (!asks.isEmpty()) { + bestAsk = asks.get(0); + String askCheck = hasErrors(asks.iterator()); + if (askCheck != null) return askCheck; + } - if (bestAsk != null && bestBid != null - && bestAsk.getLimitPrice().compareTo(bestBid.getLimitPrice()) <= 0) - return format("Got incorrect best ask and bid %s, %s", bestAsk, bestBid); - return null; + LimitOrder bestBid = null; + if (!bids.isEmpty()) { + bestBid = bids.get(0); + String bidCheck = hasErrors(CollectionUtils.descendingIterable(bids).iterator()); + if (bidCheck != null) return bidCheck; } - public static String hasErrors(Iterator side) { - if (!side.hasNext()) - return null; + if (bestAsk != null + && bestBid != null + && bestAsk.getLimitPrice().compareTo(bestBid.getLimitPrice()) <= 0) + return format("Got incorrect best ask and bid %s, %s", bestAsk, bestBid); + return null; + } - LimitOrder prev = side.next(); - String check = hasErrors(prev); - if (check != null) return check; - while (side.hasNext()) { - LimitOrder curr = side.next(); - check = hasErrors(curr); - if (check != null) return check; - if (prev.getLimitPrice().compareTo(curr.getLimitPrice()) >= 0) - return format("Wrong price order for LimitOrders %s, %s", prev, curr); - prev = curr; - } - return null; - } + public static String hasErrors(Iterator side) { + if (!side.hasNext()) return null; - public static String hasErrors(LimitOrder limitOrder) { - if (limitOrder.getOriginalAmount().compareTo(BigDecimal.ZERO) <= 0) return - format("LimitOrder amount is <= 0 for %s", limitOrder); - else return null; + LimitOrder prev = side.next(); + String check = hasErrors(prev); + if (check != null) return check; + while (side.hasNext()) { + LimitOrder curr = side.next(); + check = hasErrors(curr); + if (check != null) return check; + if (prev.getLimitPrice().compareTo(curr.getLimitPrice()) >= 0) + return format("Wrong price order for LimitOrders %s, %s", prev, curr); + prev = curr; } + return null; + } + + public static String hasErrors(LimitOrder limitOrder) { + if (limitOrder.getOriginalAmount().compareTo(BigDecimal.ZERO) <= 0) + return format("LimitOrder amount is <= 0 for %s", limitOrder); + else return null; + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/CollectionUtils.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/CollectionUtils.java index e6449e864..4f89fa27f 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/CollectionUtils.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/CollectionUtils.java @@ -4,23 +4,20 @@ import java.util.List; import java.util.ListIterator; -/** - * @author Foat Akhmadeev - * 05/06/2018 - */ +/** @author Foat Akhmadeev 05/06/2018 */ public class CollectionUtils { - static Iterable descendingIterable(List list) { - return () -> { - ListIterator li = list.listIterator(list.size()); - return new Iterator() { - public boolean hasNext() { - return li.hasPrevious(); - } + static Iterable descendingIterable(List list) { + return () -> { + ListIterator li = list.listIterator(list.size()); + return new Iterator() { + public boolean hasNext() { + return li.hasPrevious(); + } - public T next() { - return li.previous(); - } - }; - }; - } + public T next() { + return li.previous(); + } + }; + }; + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/Events.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/Events.java index a323004b7..98492edae 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/Events.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/Events.java @@ -1,38 +1,37 @@ package info.bitrich.xchangestream.util; import info.bitrich.xchangestream.service.ConnectableService; - import org.knowm.xchange.ExchangeSpecification; public class Events { - /** - * Exchange-specific parameter used for providing a {@link Runnable} action to be run - * prior to any API calls initiated during the course of maintaining a streamed connection. - * - *

While some exchanges allow bidirectional streamed communication, such that a socket - * can be opened and then authenticated, others perform authentication by means of a - * separate API call which can count towards API rate limits. In addition, many exchanges - * require API calls to obtain initial snapshots for streamed data such as order books - * or account updates. XChange requires that the developer manage rate limits themselves, - * but this is not possible when xchange-stream has to initiate these API calls - * automatically. This parameter provides a means for the developer to get a callback - * prior to these API calls, principally to apply such rate limiting globally. However, - * in principle there are wider potential uses.

- * - * @see ConnectableService#BEFORE_CONNECTION_HANDLER which provides the same sort of hook - * for socket reconnections. This also includes example usage. - */ - public static final String BEFORE_API_CALL_HANDLER = "Before_API_Call_Event_Handler"; + /** + * Exchange-specific parameter used for providing a {@link Runnable} action to be run prior to any + * API calls initiated during the course of maintaining a streamed connection. + * + *

While some exchanges allow bidirectional streamed communication, such that a socket can be + * opened and then authenticated, others perform authentication by means of a separate API call + * which can count towards API rate limits. In addition, many exchanges require API calls to + * obtain initial snapshots for streamed data such as order books or account updates. XChange + * requires that the developer manage rate limits themselves, but this is not possible when + * xchange-stream has to initiate these API calls automatically. This parameter provides a means + * for the developer to get a callback prior to these API calls, principally to apply such rate + * limiting globally. However, in principle there are wider potential uses. + * + * @see ConnectableService#BEFORE_CONNECTION_HANDLER which provides the same sort of hook for + * socket reconnections. This also includes example usage. + */ + public static final String BEFORE_API_CALL_HANDLER = "Before_API_Call_Event_Handler"; - /** - * Returns the registered handler for the {@link #BEFORE_API_CALL_HANDLER} event. - * - * @param exchangeSpecification The exchange specification. - * @return The handler. - */ - public static Runnable onApiCall(ExchangeSpecification exchangeSpecification) { - Object onApiCall = exchangeSpecification.getExchangeSpecificParametersItem(BEFORE_API_CALL_HANDLER); - return onApiCall == null ? () -> {} : (Runnable) onApiCall; - } + /** + * Returns the registered handler for the {@link #BEFORE_API_CALL_HANDLER} event. + * + * @param exchangeSpecification The exchange specification. + * @return The handler. + */ + public static Runnable onApiCall(ExchangeSpecification exchangeSpecification) { + Object onApiCall = + exchangeSpecification.getExchangeSpecificParametersItem(BEFORE_API_CALL_HANDLER); + return onApiCall == null ? () -> {} : (Runnable) onApiCall; + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/LocalExchangeConfig.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/LocalExchangeConfig.java index a5226523b..79447160c 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/LocalExchangeConfig.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/LocalExchangeConfig.java @@ -1,56 +1,53 @@ package info.bitrich.xchangestream.util; -/** - * @author Foat Akhmadeev - * 08/06/2018 - */ +/** @author Foat Akhmadeev 08/06/2018 */ public class LocalExchangeConfig { - private String apiKey; - private String secretKey; - private String proxyHost; - private String proxyPort; - - public LocalExchangeConfig(String apiKey, String secretKey) { - this.apiKey = apiKey; - this.secretKey = secretKey; - } - - public LocalExchangeConfig(String apiKey, String secretKey, String proxyHost, String proxyPort) { - this.apiKey = apiKey; - this.secretKey = secretKey; - this.proxyHost = proxyHost; - this.proxyPort = proxyPort; - } - - public String getApiKey() { - return apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getSecretKey() { - return secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - public String getProxyHost() { - return proxyHost; - } - - public void setProxyHost(String proxyHost) { - this.proxyHost = proxyHost; - } - - public String getProxyPort() { - return proxyPort; - } - - public void setProxyPort(String proxyPort) { - this.proxyPort = proxyPort; - } + private String apiKey; + private String secretKey; + private String proxyHost; + private String proxyPort; + + public LocalExchangeConfig(String apiKey, String secretKey) { + this.apiKey = apiKey; + this.secretKey = secretKey; + } + + public LocalExchangeConfig(String apiKey, String secretKey, String proxyHost, String proxyPort) { + this.apiKey = apiKey; + this.secretKey = secretKey; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public String getProxyPort() { + return proxyPort; + } + + public void setProxyPort(String proxyPort) { + this.proxyPort = proxyPort; + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/PropsLoader.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/PropsLoader.java index 2dbd43c1d..7ff0376bf 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/PropsLoader.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/PropsLoader.java @@ -1,45 +1,43 @@ package info.bitrich.xchangestream.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * @author Foat Akhmadeev - * 08/06/2018 - */ +/** @author Foat Akhmadeev 08/06/2018 */ public class PropsLoader { - private static final Logger LOG = LoggerFactory.getLogger(PropsLoader.class); - - public static LocalExchangeConfig loadKeys(String fileName, String originName, String prefix) throws IOException { - String apiKeyName = prefix + ".api.key"; - String secretKeyName = prefix + ".secret.key"; + private static final Logger LOG = LoggerFactory.getLogger(PropsLoader.class); - String apiKey = System.getProperty(apiKeyName); - String secretKey = System.getProperty(secretKeyName); + public static LocalExchangeConfig loadKeys(String fileName, String originName, String prefix) + throws IOException { + String apiKeyName = prefix + ".api.key"; + String secretKeyName = prefix + ".secret.key"; - if (apiKey != null && secretKey != null) { - return new LocalExchangeConfig(apiKey, secretKey); - } else { - LOG.info("Not found keys in system props, loading from a file {}...", fileName); - } + String apiKey = System.getProperty(apiKeyName); + String secretKey = System.getProperty(secretKeyName); - try (FileInputStream input = new FileInputStream(fileName)) { - Properties properties = new Properties(); - properties.load(input); - return new LocalExchangeConfig(properties.getProperty(apiKeyName), properties.getProperty(secretKeyName)); - } catch (FileNotFoundException e) { - LOG.error("Please create {} file from {}", fileName, originName); - throw e; - } + if (apiKey != null && secretKey != null) { + return new LocalExchangeConfig(apiKey, secretKey); + } else { + LOG.info("Not found keys in system props, loading from a file {}...", fileName); } - // e.g. "-Dproxy.exec.line=/Applications/Charles.app/Contents/MacOS/Charles -headless" - public static String proxyExecLine() { - return System.getProperty("proxy.exec.line"); + try (FileInputStream input = new FileInputStream(fileName)) { + Properties properties = new Properties(); + properties.load(input); + return new LocalExchangeConfig( + properties.getProperty(apiKeyName), properties.getProperty(secretKeyName)); + } catch (FileNotFoundException e) { + LOG.error("Please create {} file from {}", fileName, originName); + throw e; } + } + + // e.g. "-Dproxy.exec.line=/Applications/Charles.app/Contents/MacOS/Charles -headless" + public static String proxyExecLine() { + return System.getProperty("proxy.exec.line"); + } } diff --git a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/ProxyUtil.java b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/ProxyUtil.java index c1e670407..dfaa4b165 100644 --- a/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/ProxyUtil.java +++ b/xchange-stream-core/src/main/java/info/bitrich/xchangestream/util/ProxyUtil.java @@ -1,56 +1,55 @@ package info.bitrich.xchangestream.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * @author Foat Akhmadeev - * 18/06/2018 - */ +/** @author Foat Akhmadeev 18/06/2018 */ public class ProxyUtil { - private static final Logger LOG = LoggerFactory.getLogger(ProxyUtil.class); - - private Process proxyProcess; - private ScheduledExecutorService scheduler; - - private String execLine; - private long waitTimeMillis; - - public ProxyUtil(String execLine, long waitTimeMillis) { - scheduler = Executors.newScheduledThreadPool(1); - this.execLine = execLine; - this.waitTimeMillis = waitTimeMillis; - } - - public void startProxy() throws Exception { - ScheduledFuture schedule = scheduler.schedule(() -> { - Process p = null; - try { + private static final Logger LOG = LoggerFactory.getLogger(ProxyUtil.class); + + private Process proxyProcess; + private ScheduledExecutorService scheduler; + + private String execLine; + private long waitTimeMillis; + + public ProxyUtil(String execLine, long waitTimeMillis) { + scheduler = Executors.newScheduledThreadPool(1); + this.execLine = execLine; + this.waitTimeMillis = waitTimeMillis; + } + + public void startProxy() throws Exception { + ScheduledFuture schedule = + scheduler.schedule( + () -> { + Process p = null; + try { p = Runtime.getRuntime().exec(execLine); - } catch (IOException e) { + } catch (IOException e) { LOG.error(e.getMessage(), e); - } - return p; - }, 1, TimeUnit.SECONDS); + } + return p; + }, + 1, + TimeUnit.SECONDS); - Thread.sleep(waitTimeMillis); + Thread.sleep(waitTimeMillis); - proxyProcess = schedule.get(); - } + proxyProcess = schedule.get(); + } - public void stopProxy() { - proxyProcess.destroy(); - } + public void stopProxy() { + proxyProcess.destroy(); + } - public void shutdown() { - if (proxyProcess.isAlive()) - proxyProcess.destroy(); - scheduler.shutdown(); - } + public void shutdown() { + if (proxyProcess.isAlive()) proxyProcess.destroy(); + scheduler.shutdown(); + } } diff --git a/xchange-stream-core/src/test/java/info/bitrich/xchangestream/util/BookSanityCheckerTest.java b/xchange-stream-core/src/test/java/info/bitrich/xchangestream/util/BookSanityCheckerTest.java index 2e5f394ac..4c84a5c44 100644 --- a/xchange-stream-core/src/test/java/info/bitrich/xchangestream/util/BookSanityCheckerTest.java +++ b/xchange-stream-core/src/test/java/info/bitrich/xchangestream/util/BookSanityCheckerTest.java @@ -1,24 +1,23 @@ package info.bitrich.xchangestream.util; -import info.bitrich.xchangestream.util.BookSanityChecker; +import static java.lang.String.format; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; import org.junit.Assert; import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; -import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.dto.marketdata.OrderBook; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; - -import static java.lang.String.format; +import org.knowm.xchange.dto.trade.LimitOrder; public class BookSanityCheckerTest { @Test public void testNoOrders() { - OrderBook book = new OrderBook(new Date(), new ArrayList(), new ArrayList()); + OrderBook book = + new OrderBook(new Date(), new ArrayList(), new ArrayList()); Assert.assertNull(BookSanityChecker.hasErrors(book)); } @@ -26,10 +25,22 @@ public void testNoOrders() { public void testWithAsksWithBidsNoErrors() { ArrayList asks = new ArrayList<>(); ArrayList bids = new ArrayList<>(); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal(0.02), CurrencyPair.ADA_BNB, "1", new Date(), - new BigDecimal(0.02))); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal(0.01), CurrencyPair.ADA_BNB, "2", new Date(), - new BigDecimal(0.01))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal(0.02), + CurrencyPair.ADA_BNB, + "1", + new Date(), + new BigDecimal(0.02))); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(0.01), + CurrencyPair.ADA_BNB, + "2", + new Date(), + new BigDecimal(0.01))); OrderBook book = new OrderBook(new Date(), asks, bids); Assert.assertNull(BookSanityChecker.hasErrors(book)); } @@ -38,10 +49,22 @@ public void testWithAsksWithBidsNoErrors() { public void testNoBidsNoErrors() { ArrayList asks = new ArrayList<>(); ArrayList bids = new ArrayList<>(); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal(0.01), CurrencyPair.ADA_BNB, "1", new Date(), - new BigDecimal(0.01))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal(0.02), CurrencyPair.ADA_BNB, "2", new Date(), - new BigDecimal(0.02))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal(0.01), + CurrencyPair.ADA_BNB, + "1", + new Date(), + new BigDecimal(0.01))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal(0.02), + CurrencyPair.ADA_BNB, + "2", + new Date(), + new BigDecimal(0.02))); OrderBook book = new OrderBook(new Date(), asks, bids); Assert.assertNull(BookSanityChecker.hasErrors(book)); } @@ -50,64 +73,118 @@ public void testNoBidsNoErrors() { public void testWithAsksLimitOrderError() { ArrayList asks = new ArrayList<>(); ArrayList bids = new ArrayList<>(); - LimitOrder a1 = new LimitOrder(Order.OrderType.ASK, new BigDecimal(-0.01), CurrencyPair.ADA_BNB, "1", new Date(), - new BigDecimal(0.01)); + LimitOrder a1 = + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal(-0.01), + CurrencyPair.ADA_BNB, + "1", + new Date(), + new BigDecimal(0.01)); asks.add(a1); OrderBook book = new OrderBook(new Date(), asks, bids); - Assert.assertEquals(format("LimitOrder amount is <= 0 for %s", a1), BookSanityChecker.hasErrors(book)); + Assert.assertEquals( + format("LimitOrder amount is <= 0 for %s", a1), BookSanityChecker.hasErrors(book)); } @Test public void testWithBidsLimitOrderError() { ArrayList asks = new ArrayList<>(); ArrayList bids = new ArrayList<>(); - LimitOrder b1 = new LimitOrder(Order.OrderType.BID, new BigDecimal(-0.01), CurrencyPair.ADA_BNB, "1", new Date(), - new BigDecimal(0.01)); + LimitOrder b1 = + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(-0.01), + CurrencyPair.ADA_BNB, + "1", + new Date(), + new BigDecimal(0.01)); bids.add(b1); OrderBook book = new OrderBook(new Date(), asks, bids); - Assert.assertEquals(format("LimitOrder amount is <= 0 for %s", b1), BookSanityChecker.hasErrors(book)); + Assert.assertEquals( + format("LimitOrder amount is <= 0 for %s", b1), BookSanityChecker.hasErrors(book)); } @Test public void testWithBidNoErrorOnNextOrder() { ArrayList asks = new ArrayList<>(); ArrayList bids = new ArrayList<>(); - LimitOrder b1 = new LimitOrder(Order.OrderType.BID, new BigDecimal(-0.01), CurrencyPair.ADA_BNB, "1", new Date(), - new BigDecimal(0.01)); - LimitOrder b2 = new LimitOrder(Order.OrderType.BID, new BigDecimal(0.01), CurrencyPair.ADA_BNB, "2", new Date(), - new BigDecimal(0.01)); + LimitOrder b1 = + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(-0.01), + CurrencyPair.ADA_BNB, + "1", + new Date(), + new BigDecimal(0.01)); + LimitOrder b2 = + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(0.01), + CurrencyPair.ADA_BNB, + "2", + new Date(), + new BigDecimal(0.01)); bids.add(b1); bids.add(b2); OrderBook book = new OrderBook(new Date(), asks, bids); - Assert.assertEquals(format("LimitOrder amount is <= 0 for %s", b1), BookSanityChecker.hasErrors(book)); + Assert.assertEquals( + format("LimitOrder amount is <= 0 for %s", b1), BookSanityChecker.hasErrors(book)); } @Test public void testIncorrectBestAskAndBid() { ArrayList asks = new ArrayList<>(); ArrayList bids = new ArrayList<>(); - LimitOrder a1 = new LimitOrder(Order.OrderType.ASK, new BigDecimal(0.01), CurrencyPair.ADA_BNB, "1", new Date(), - new BigDecimal(0.01)); + LimitOrder a1 = + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal(0.01), + CurrencyPair.ADA_BNB, + "1", + new Date(), + new BigDecimal(0.01)); asks.add(a1); - LimitOrder b1 = new LimitOrder(Order.OrderType.BID, new BigDecimal(0.02), CurrencyPair.ADA_BNB, "2", new Date(), - new BigDecimal(0.02)); + LimitOrder b1 = + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(0.02), + CurrencyPair.ADA_BNB, + "2", + new Date(), + new BigDecimal(0.02)); bids.add(b1); OrderBook book = new OrderBook(new Date(), asks, bids); - Assert.assertEquals(format("Got incorrect best ask and bid %s, %s", a1, b1), BookSanityChecker.hasErrors(book)); + Assert.assertEquals( + format("Got incorrect best ask and bid %s, %s", a1, b1), BookSanityChecker.hasErrors(book)); } @Test public void testWithBidsWrongPriceOrder() { ArrayList asks = new ArrayList<>(); ArrayList bids = new ArrayList<>(); - LimitOrder b1 = new LimitOrder(Order.OrderType.BID, new BigDecimal(0.01), CurrencyPair.ADA_BNB, "1", new Date(), - new BigDecimal(0.01)); - LimitOrder b2 = new LimitOrder(Order.OrderType.BID, new BigDecimal(0.02), CurrencyPair.ADA_BNB, "2", new Date(), - new BigDecimal(0.02)); + LimitOrder b1 = + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(0.01), + CurrencyPair.ADA_BNB, + "1", + new Date(), + new BigDecimal(0.01)); + LimitOrder b2 = + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(0.02), + CurrencyPair.ADA_BNB, + "2", + new Date(), + new BigDecimal(0.02)); bids.add(b1); bids.add(b2); OrderBook book = new OrderBook(new Date(), asks, bids); - Assert.assertEquals(format("Wrong price order for LimitOrders %s, %s", b2, b1), BookSanityChecker.hasErrors(book)); + Assert.assertEquals( + format("Wrong price order for LimitOrders %s, %s", b2, b1), + BookSanityChecker.hasErrors(book)); } @Test diff --git a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiProductStreamingService.java b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiProductStreamingService.java index cb4d9341c..8794f02f8 100644 --- a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiProductStreamingService.java +++ b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiProductStreamingService.java @@ -1,50 +1,47 @@ package info.bitrich.xchangestream.gemini; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; +import java.io.IOException; import org.knowm.xchange.currency.CurrencyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; - -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiProductStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(GeminiProductStreamingService.class); - - private final CurrencyPair currencyPair; - - public GeminiProductStreamingService(String symbolUrl, CurrencyPair currencyPair) { - super(symbolUrl, Integer.MAX_VALUE, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION,15); - this.currencyPair = currencyPair; - } - - @Override - public boolean processArrayMassageSeparately() { - return false; - } - - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - return currencyPair.toString(); - } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - return null; - } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - return null; - } - @Override - protected void handleIdle(ChannelHandlerContext ctx) { - ctx.writeAndFlush(new PingWebSocketFrame()); - } + private static final Logger LOG = LoggerFactory.getLogger(GeminiProductStreamingService.class); + + private final CurrencyPair currencyPair; + + public GeminiProductStreamingService(String symbolUrl, CurrencyPair currencyPair) { + super(symbolUrl, Integer.MAX_VALUE, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION, 15); + this.currencyPair = currencyPair; + } + + @Override + public boolean processArrayMassageSeparately() { + return false; + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + return currencyPair.toString(); + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + return null; + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + return null; + } + + @Override + protected void handleIdle(ChannelHandlerContext ctx) { + ctx.writeAndFlush(new PingWebSocketFrame()); + } } diff --git a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingExchange.java b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingExchange.java index 87106d008..356cbc7d9 100644 --- a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingExchange.java +++ b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingExchange.java @@ -8,45 +8,45 @@ import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import org.knowm.xchange.gemini.v1.GeminiExchange; -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiStreamingExchange extends GeminiExchange implements StreamingExchange { - private static final String API_BASE_URI = "wss://api.gemini.com/v1/marketdata/"; - - private final GeminiStreamingService streamingService; - private GeminiStreamingMarketDataService streamingMarketDataService; - - public GeminiStreamingExchange() { - this.streamingService = new GeminiStreamingService(API_BASE_URI); - } - - @Override - protected void initServices() { - super.initServices(); - streamingMarketDataService = new GeminiStreamingMarketDataService(streamingService); - } - - @Override - public Completable connect(ProductSubscription... args) { - return Completable.create(CompletableEmitter::onComplete); - } - - @Override - public Completable disconnect() { - return Completable.create(CompletableEmitter::onComplete); - } - - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public boolean isAlive() { - return streamingService.isAlive(); - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { throw new NotYetImplementedForExchangeException(); } + private static final String API_BASE_URI = "wss://api.gemini.com/v1/marketdata/"; + + private final GeminiStreamingService streamingService; + private GeminiStreamingMarketDataService streamingMarketDataService; + + public GeminiStreamingExchange() { + this.streamingService = new GeminiStreamingService(API_BASE_URI); + } + + @Override + protected void initServices() { + super.initServices(); + streamingMarketDataService = new GeminiStreamingMarketDataService(streamingService); + } + + @Override + public Completable connect(ProductSubscription... args) { + return Completable.create(CompletableEmitter::onComplete); + } + + @Override + public Completable disconnect() { + return Completable.create(CompletableEmitter::onComplete); + } + + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public boolean isAlive() { + return streamingService.isAlive(); + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + throw new NotYetImplementedForExchangeException(); + } } diff --git a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingMarketDataService.java b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingMarketDataService.java index 7cbe1e926..5a13bbde0 100644 --- a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingMarketDataService.java +++ b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingMarketDataService.java @@ -1,6 +1,7 @@ package info.bitrich.xchangestream.gemini; -import com.fasterxml.jackson.databind.DeserializationFeature; +import static org.knowm.xchange.gemini.v1.GeminiAdapters.adaptTrades; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.core.StreamingMarketDataService; @@ -9,6 +10,8 @@ import info.bitrich.xchangestream.gemini.dto.GeminiWebSocketTransaction; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.util.HashMap; +import java.util.Map; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; @@ -19,95 +22,95 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; -import java.util.Map; - -import static org.knowm.xchange.gemini.v1.GeminiAdapters.adaptTrades; - -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiStreamingMarketDataService implements StreamingMarketDataService { - private static final Logger LOG = LoggerFactory.getLogger(GeminiStreamingMarketDataService.class); - - private final GeminiStreamingService service; - private final Map orderbooks = new HashMap<>(); - - private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - - public GeminiStreamingMarketDataService(GeminiStreamingService service) { - this.service = service; - } - - private boolean filterEventsByReason(JsonNode message, String type, String reason) { - boolean hasEvents = false; - if (message.has("events")) { - for (JsonNode jsonEvent : message.get("events")) { - boolean reasonResult = reason == null || (jsonEvent.has("reason") && jsonEvent.get("reason").asText().equals(reason)); - if (jsonEvent.get("type").asText().equals(type) && reasonResult) { - hasEvents = true; - break; - } - } + private static final Logger LOG = LoggerFactory.getLogger(GeminiStreamingMarketDataService.class); + + private final GeminiStreamingService service; + private final Map orderbooks = new HashMap<>(); + + private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + public GeminiStreamingMarketDataService(GeminiStreamingService service) { + this.service = service; + } + + private boolean filterEventsByReason(JsonNode message, String type, String reason) { + boolean hasEvents = false; + if (message.has("events")) { + for (JsonNode jsonEvent : message.get("events")) { + boolean reasonResult = + reason == null + || (jsonEvent.has("reason") && jsonEvent.get("reason").asText().equals(reason)); + if (jsonEvent.get("type").asText().equals(type) && reasonResult) { + hasEvents = true; + break; } - - return hasEvents; + } } - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - - Observable subscribedOrderbookSnapshot = service.subscribeChannel(currencyPair, args) - .filter( - s -> filterEventsByReason(s, "change", "initial") || - filterEventsByReason(s, "change", "place") || - filterEventsByReason(s, "change", "cancel") || - filterEventsByReason(s, "change", "trade") - ) - .map((JsonNode s) -> { - - if(filterEventsByReason(s, "change", "initial")) { - GeminiWebSocketTransaction transaction = mapper.treeToValue(s, GeminiWebSocketTransaction.class); - GeminiOrderbook orderbook = transaction.toGeminiOrderbook(currencyPair); - orderbooks.put(currencyPair, orderbook); - return orderbook; - - } - - if(filterEventsByReason(s, "change", "place") || - filterEventsByReason(s, "change", "cancel") || - filterEventsByReason(s, "change", "trade")) { - - GeminiWebSocketTransaction transaction = mapper.treeToValue(s, GeminiWebSocketTransaction.class); - GeminiLimitOrder[] levels = transaction.toGeminiLimitOrdersUpdate(); - GeminiOrderbook orderbook = orderbooks.get(currencyPair); - orderbook.updateLevels(levels); - return orderbook; - - } - - throw new NotYetImplementedForExchangeException(" Unknown message type, even after filtering: " + s.toString()); - + return hasEvents; + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + + Observable subscribedOrderbookSnapshot = + service + .subscribeChannel(currencyPair, args) + .filter( + s -> + filterEventsByReason(s, "change", "initial") + || filterEventsByReason(s, "change", "place") + || filterEventsByReason(s, "change", "cancel") + || filterEventsByReason(s, "change", "trade")) + .map( + (JsonNode s) -> { + if (filterEventsByReason(s, "change", "initial")) { + GeminiWebSocketTransaction transaction = + mapper.treeToValue(s, GeminiWebSocketTransaction.class); + GeminiOrderbook orderbook = transaction.toGeminiOrderbook(currencyPair); + orderbooks.put(currencyPair, orderbook); + return orderbook; + } + + if (filterEventsByReason(s, "change", "place") + || filterEventsByReason(s, "change", "cancel") + || filterEventsByReason(s, "change", "trade")) { + + GeminiWebSocketTransaction transaction = + mapper.treeToValue(s, GeminiWebSocketTransaction.class); + GeminiLimitOrder[] levels = transaction.toGeminiLimitOrdersUpdate(); + GeminiOrderbook orderbook = orderbooks.get(currencyPair); + orderbook.updateLevels(levels); + return orderbook; + } + + throw new NotYetImplementedForExchangeException( + " Unknown message type, even after filtering: " + s.toString()); }); - return subscribedOrderbookSnapshot.map(GeminiOrderbook::toOrderbook); - } - - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - throw new NotAvailableFromExchangeException(); - } - - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - Observable subscribedTrades = service.subscribeChannel(currencyPair, args) - .filter(s -> filterEventsByReason(s, "trade", null)) - .map((JsonNode s) -> { - GeminiWebSocketTransaction transaction = mapper.treeToValue(s, GeminiWebSocketTransaction.class); - return transaction.toGeminiTrades(); + return subscribedOrderbookSnapshot.map(GeminiOrderbook::toOrderbook); + } + + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + throw new NotAvailableFromExchangeException(); + } + + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + Observable subscribedTrades = + service + .subscribeChannel(currencyPair, args) + .filter(s -> filterEventsByReason(s, "trade", null)) + .map( + (JsonNode s) -> { + GeminiWebSocketTransaction transaction = + mapper.treeToValue(s, GeminiWebSocketTransaction.class); + return transaction.toGeminiTrades(); }); - return subscribedTrades.flatMapIterable(s -> adaptTrades(s, currencyPair).getTrades()); - } + return subscribedTrades.flatMapIterable(s -> adaptTrades(s, currencyPair).getTrades()); + } } diff --git a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingService.java b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingService.java index 6f2b4bdd3..0b7b4ba32 100644 --- a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingService.java +++ b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/GeminiStreamingService.java @@ -2,45 +2,43 @@ import com.fasterxml.jackson.databind.JsonNode; import io.reactivex.Observable; +import java.util.HashMap; +import java.util.Map; import org.knowm.xchange.currency.CurrencyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; -import java.util.Map; - -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(GeminiStreamingService.class); - - private final String baseUri; - - private Map productStreamingServices; - private Map> productSubscriptions; - - public GeminiStreamingService(String baseUri) { - this.baseUri = baseUri; - productStreamingServices = new HashMap<>(); - productSubscriptions = new HashMap<>(); + private static final Logger LOG = LoggerFactory.getLogger(GeminiStreamingService.class); + + private final String baseUri; + + private Map productStreamingServices; + private Map> productSubscriptions; + + public GeminiStreamingService(String baseUri) { + this.baseUri = baseUri; + productStreamingServices = new HashMap<>(); + productSubscriptions = new HashMap<>(); + } + + public Observable subscribeChannel(CurrencyPair currencyPair, Object... args) { + if (!productStreamingServices.containsKey(currencyPair)) { + String symbolUri = baseUri + currencyPair.base.toString() + currencyPair.counter.toString(); + GeminiProductStreamingService productStreamingService = + new GeminiProductStreamingService(symbolUri, currencyPair); + productStreamingService.connect().blockingAwait(); + Observable productSubscription = + productStreamingService.subscribeChannel(currencyPair.toString(), args); + productStreamingServices.put(currencyPair, productStreamingService); + productSubscriptions.put(currencyPair, productSubscription); } - public Observable subscribeChannel(CurrencyPair currencyPair, Object... args) { - if (!productStreamingServices.containsKey(currencyPair)) { - String symbolUri = baseUri + currencyPair.base.toString() + currencyPair.counter.toString(); - GeminiProductStreamingService productStreamingService = new GeminiProductStreamingService(symbolUri, currencyPair); - productStreamingService.connect().blockingAwait(); - Observable productSubscription = productStreamingService.subscribeChannel(currencyPair.toString(), args); - productStreamingServices.put(currencyPair, productStreamingService); - productSubscriptions.put(currencyPair, productSubscription); - } - - return productSubscriptions.get(currencyPair); - } + return productSubscriptions.get(currencyPair); + } - public boolean isAlive() { - return productStreamingServices.values().stream() - .allMatch(ps -> ps.isSocketOpen()); - } + public boolean isAlive() { + return productStreamingServices.values().stream().allMatch(ps -> ps.isSocketOpen()); + } } diff --git a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiLimitOrder.java b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiLimitOrder.java index b54887b50..3af4ed7d7 100644 --- a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiLimitOrder.java +++ b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiLimitOrder.java @@ -1,22 +1,20 @@ package info.bitrich.xchangestream.gemini.dto; +import java.math.BigDecimal; import org.knowm.xchange.dto.Order; import org.knowm.xchange.gemini.v1.dto.marketdata.GeminiLevel; -import java.math.BigDecimal; - -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiLimitOrder extends GeminiLevel { - private final Order.OrderType side; + private final Order.OrderType side; - public GeminiLimitOrder(Order.OrderType side, BigDecimal price, BigDecimal amount, BigDecimal timestamp) { - super(price, amount, timestamp); - this.side = side; - } + public GeminiLimitOrder( + Order.OrderType side, BigDecimal price, BigDecimal amount, BigDecimal timestamp) { + super(price, amount, timestamp); + this.side = side; + } - public Order.OrderType getSide() { - return side; - } + public Order.OrderType getSide() { + return side; + } } diff --git a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiOrderbook.java b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiOrderbook.java index 5b23a6426..f470b5d8a 100644 --- a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiOrderbook.java +++ b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiOrderbook.java @@ -1,66 +1,68 @@ package info.bitrich.xchangestream.gemini.dto; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.dto.marketdata.OrderBook; -import org.knowm.xchange.dto.trade.LimitOrder; +import static org.knowm.xchange.gemini.v1.GeminiAdapters.adaptOrders; import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.trade.LimitOrder; -import static org.knowm.xchange.gemini.v1.GeminiAdapters.adaptOrders; - -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiOrderbook { - private final Map asks; - private final Map bids; + private final Map asks; + private final Map bids; - private final CurrencyPair currencyPair; + private final CurrencyPair currencyPair; - public GeminiOrderbook(CurrencyPair currencyPair) { - asks = new ConcurrentHashMap<>(); - bids = new ConcurrentHashMap<>(); - this.currencyPair = currencyPair; - } + public GeminiOrderbook(CurrencyPair currencyPair) { + asks = new ConcurrentHashMap<>(); + bids = new ConcurrentHashMap<>(); + this.currencyPair = currencyPair; + } - public void createFromLevels(GeminiLimitOrder[] levels) { - for (GeminiLimitOrder level : levels) { - Map orderBookSide = level.getSide() == Order.OrderType.ASK ? asks : bids; - orderBookSide.put(level.getPrice().stripTrailingZeros(), level); - } + public void createFromLevels(GeminiLimitOrder[] levels) { + for (GeminiLimitOrder level : levels) { + Map orderBookSide = + level.getSide() == Order.OrderType.ASK ? asks : bids; + orderBookSide.put(level.getPrice().stripTrailingZeros(), level); } + } - public void updateLevel(GeminiLimitOrder level) { - Map orderBookSide = level.getSide() == Order.OrderType.ASK ? asks : bids; - boolean shouldDelete = level.getAmount().compareTo(BigDecimal.ZERO) == 0; - // BigDecimal is immutable type (thread safe naturally), - // strip the trailing zeros to ensure the hashCode is same when two BigDecimal are equal but decimal scale are different, such as "1.1200" & "1.12" - BigDecimal price = level.getPrice().stripTrailingZeros(); - if (shouldDelete) { - orderBookSide.remove(price); - } else { - orderBookSide.put(price, level); - } + public void updateLevel(GeminiLimitOrder level) { + Map orderBookSide = + level.getSide() == Order.OrderType.ASK ? asks : bids; + boolean shouldDelete = level.getAmount().compareTo(BigDecimal.ZERO) == 0; + // BigDecimal is immutable type (thread safe naturally), + // strip the trailing zeros to ensure the hashCode is same when two BigDecimal are equal but + // decimal scale are different, such as "1.1200" & "1.12" + BigDecimal price = level.getPrice().stripTrailingZeros(); + if (shouldDelete) { + orderBookSide.remove(price); + } else { + orderBookSide.put(price, level); } + } - public void updateLevels(GeminiLimitOrder[] levels) { - for (GeminiLimitOrder level : levels) { - updateLevel(level); - } + public void updateLevels(GeminiLimitOrder[] levels) { + for (GeminiLimitOrder level : levels) { + updateLevel(level); } + } - public OrderBook toOrderbook() { - GeminiLimitOrder[] askLevels = asks.values().toArray(new GeminiLimitOrder[asks.size()]); - GeminiLimitOrder[] bidLevels = bids.values().toArray(new GeminiLimitOrder[bids.size()]); - List askOrders = adaptOrders(askLevels, currencyPair, Order.OrderType.ASK).getLimitOrders(); - List bidOrders = adaptOrders(bidLevels, currencyPair, Order.OrderType.BID).getLimitOrders(); - Collections.sort(askOrders); - Collections.sort(bidOrders); - return new OrderBook(null, askOrders, bidOrders); - } + public OrderBook toOrderbook() { + GeminiLimitOrder[] askLevels = asks.values().toArray(new GeminiLimitOrder[asks.size()]); + GeminiLimitOrder[] bidLevels = bids.values().toArray(new GeminiLimitOrder[bids.size()]); + List askOrders = + adaptOrders(askLevels, currencyPair, Order.OrderType.ASK).getLimitOrders(); + List bidOrders = + adaptOrders(bidLevels, currencyPair, Order.OrderType.BID).getLimitOrders(); + Collections.sort(askOrders); + Collections.sort(bidOrders); + return new OrderBook(null, askOrders, bidOrders); + } } diff --git a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiWebSocketTransaction.java b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiWebSocketTransaction.java index 8341a2aa9..945bfeb07 100644 --- a/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiWebSocketTransaction.java +++ b/xchange-stream-gemini/src/main/java/info/bitrich/xchangestream/gemini/dto/GeminiWebSocketTransaction.java @@ -2,113 +2,112 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.gemini.v1.dto.marketdata.GeminiTrade; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.gemini.v1.dto.marketdata.GeminiTrade; -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiWebSocketTransaction { - private String type; - private String eventId; - private String socket_sequence; - private String timestamp; - private String timestampms; - private JsonNode events; - - public GeminiWebSocketTransaction(@JsonProperty("type") String type, - @JsonProperty("eventId") String eventId, - @JsonProperty("socket_sequence") String socket_sequence, - @JsonProperty("timestamp") String timestamp, - @JsonProperty("timestampms") String timestampms, - @JsonProperty("events") JsonNode events) { - this.type = type; - this.eventId = eventId; - this.socket_sequence = socket_sequence; - this.timestamp = timestamp; - this.timestampms = timestampms; - this.events = events; - } - - public String getType() { - return type; - } - - public String getEventId() { - return eventId; - } - - public String getSocket_sequence() { - return socket_sequence; + private String type; + private String eventId; + private String socket_sequence; + private String timestamp; + private String timestampms; + private JsonNode events; + + public GeminiWebSocketTransaction( + @JsonProperty("type") String type, + @JsonProperty("eventId") String eventId, + @JsonProperty("socket_sequence") String socket_sequence, + @JsonProperty("timestamp") String timestamp, + @JsonProperty("timestampms") String timestampms, + @JsonProperty("events") JsonNode events) { + this.type = type; + this.eventId = eventId; + this.socket_sequence = socket_sequence; + this.timestamp = timestamp; + this.timestampms = timestampms; + this.events = events; + } + + public String getType() { + return type; + } + + public String getEventId() { + return eventId; + } + + public String getSocket_sequence() { + return socket_sequence; + } + + public String getTimestamp() { + return timestamp; + } + + public String getTimestampms() { + return timestampms; + } + + public JsonNode getEvents() { + return events; + } + + private static GeminiLimitOrder toGeminiLimitOrder(JsonNode jsonEvent) { + BigDecimal price = new BigDecimal(jsonEvent.get("price").asText()); + BigDecimal amount = new BigDecimal(jsonEvent.get("remaining").asText()); + BigDecimal timestamp = new BigDecimal((new Date().getTime() / 1000)); + Order.OrderType side = + jsonEvent.get("side").asText().equals("ask") ? Order.OrderType.ASK : Order.OrderType.BID; + return new GeminiLimitOrder(side, price, amount, timestamp); + } + + public GeminiLimitOrder[] toGeminiLimitOrdersUpdate() { + List levels = new ArrayList<>(1000); + for (JsonNode jsonEvent : events) { + if (!jsonEvent.has("reason")) continue; + if (jsonEvent.get("reason").asText().equals("initial") + || jsonEvent.get("reason").asText().equals("place") + || jsonEvent.get("reason").asText().equals("cancel") + || jsonEvent.get("reason").asText().equals("trade")) { + GeminiLimitOrder level = toGeminiLimitOrder(jsonEvent); + levels.add(level); + } } - public String getTimestamp() { - return timestamp; + return levels.toArray(new GeminiLimitOrder[levels.size()]); + } + + public GeminiOrderbook toGeminiOrderbook(CurrencyPair currencyPair) { + GeminiLimitOrder[] levels = toGeminiLimitOrdersUpdate(); + GeminiOrderbook orderbook = new GeminiOrderbook(currencyPair); + orderbook.createFromLevels(levels); + return orderbook; + } + + private static GeminiTrade toGeminiTrade(JsonNode jsonEvent, long timestamp) { + long tid = Long.valueOf(jsonEvent.get("tid").asText()); + BigDecimal price = new BigDecimal(jsonEvent.get("price").asText()); + BigDecimal amount = new BigDecimal(jsonEvent.get("amount").asText()); + String takerSide = jsonEvent.get("makerSide").asText().equals("ask") ? "buy" : "sell"; + return new GeminiTrade(price, amount, timestamp, "gemini", tid, takerSide); + } + + public GeminiTrade[] toGeminiTrades() { + long timestamp = Long.valueOf(this.timestamp); + List trades = new ArrayList<>(1000); + for (JsonNode jsonEvent : events) { + if (jsonEvent.get("type").asText().equals("trade")) { + GeminiTrade trade = toGeminiTrade(jsonEvent, timestamp); + trades.add(trade); + } } - public String getTimestampms() { - return timestampms; - } - - public JsonNode getEvents() { - return events; - } - - private static GeminiLimitOrder toGeminiLimitOrder(JsonNode jsonEvent) { - BigDecimal price = new BigDecimal(jsonEvent.get("price").asText()); - BigDecimal amount = new BigDecimal(jsonEvent.get("remaining").asText()); - BigDecimal timestamp = new BigDecimal((new Date().getTime() / 1000)); - Order.OrderType side = jsonEvent.get("side").asText().equals("ask") ? Order.OrderType.ASK : Order.OrderType.BID; - return new GeminiLimitOrder(side, price, amount, timestamp); - } - - public GeminiLimitOrder[] toGeminiLimitOrdersUpdate() { - List levels = new ArrayList<>(1000); - for (JsonNode jsonEvent : events) { - if (!jsonEvent.has("reason")) continue; - if (jsonEvent.get("reason").asText().equals("initial") || - jsonEvent.get("reason").asText().equals("place") || - jsonEvent.get("reason").asText().equals("cancel") || - jsonEvent.get("reason").asText().equals("trade")) { - GeminiLimitOrder level = toGeminiLimitOrder(jsonEvent); - levels.add(level); - } - } - - return levels.toArray(new GeminiLimitOrder[levels.size()]); - } - - public GeminiOrderbook toGeminiOrderbook(CurrencyPair currencyPair) { - GeminiLimitOrder[] levels = toGeminiLimitOrdersUpdate(); - GeminiOrderbook orderbook = new GeminiOrderbook(currencyPair); - orderbook.createFromLevels(levels); - return orderbook; - } - - private static GeminiTrade toGeminiTrade(JsonNode jsonEvent, long timestamp) { - long tid = Long.valueOf(jsonEvent.get("tid").asText()); - BigDecimal price = new BigDecimal(jsonEvent.get("price").asText()); - BigDecimal amount = new BigDecimal(jsonEvent.get("amount").asText()); - String takerSide = jsonEvent.get("makerSide").asText().equals("ask") ? "buy" : "sell"; - return new GeminiTrade(price, amount, timestamp, "gemini", tid, takerSide); - } - - public GeminiTrade[] toGeminiTrades() { - long timestamp = Long.valueOf(this.timestamp); - List trades = new ArrayList<>(1000); - for (JsonNode jsonEvent : events) { - if (jsonEvent.get("type").asText().equals("trade")) { - GeminiTrade trade = toGeminiTrade(jsonEvent, timestamp); - trades.add(trade); - } - } - - return trades.toArray(new GeminiTrade[trades.size()]); - } + return trades.toArray(new GeminiTrade[trades.size()]); + } } diff --git a/xchange-stream-gemini/src/test/java/info/bitrich/xchangestream/gemini/GeminiManualExample.java b/xchange-stream-gemini/src/test/java/info/bitrich/xchangestream/gemini/GeminiManualExample.java index 64fd39515..b20a80a22 100644 --- a/xchange-stream-gemini/src/test/java/info/bitrich/xchangestream/gemini/GeminiManualExample.java +++ b/xchange-stream-gemini/src/test/java/info/bitrich/xchangestream/gemini/GeminiManualExample.java @@ -6,23 +6,32 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 15.11.17. - */ +/** Created by Lukas Zaoralek on 15.11.17. */ public class GeminiManualExample { - private static final Logger LOG = LoggerFactory.getLogger(GeminiManualExample.class); + private static final Logger LOG = LoggerFactory.getLogger(GeminiManualExample.class); - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(GeminiStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(GeminiStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); - exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_USD) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); - exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); - } + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting trades: ", throwable)); + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingExchange.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingExchange.java index 413d0d173..6b050dd3b 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingExchange.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingExchange.java @@ -8,63 +8,63 @@ import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.hitbtc.v2.HitbtcExchange; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcStreamingExchange extends HitbtcExchange implements StreamingExchange { - private static final String API_URI = "wss://api.hitbtc.com/api/2/ws"; + private static final String API_URI = "wss://api.hitbtc.com/api/2/ws"; - private final HitbtcStreamingService streamingService; - private HitbtcStreamingMarketDataService streamingMarketDataService; + private final HitbtcStreamingService streamingService; + private HitbtcStreamingMarketDataService streamingMarketDataService; - public HitbtcStreamingExchange() { - this.streamingService = new HitbtcStreamingService(API_URI); - } + public HitbtcStreamingExchange() { + this.streamingService = new HitbtcStreamingService(API_URI); + } - @Override - protected void initServices() { - super.initServices(); - streamingMarketDataService = new HitbtcStreamingMarketDataService(streamingService); - } + @Override + protected void initServices() { + super.initServices(); + streamingMarketDataService = new HitbtcStreamingMarketDataService(streamingService); + } - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); - return spec; - } + return spec; + } - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataService.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataService.java index 7bc0e154d..7c3b5b8b8 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataService.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataService.java @@ -1,12 +1,15 @@ package info.bitrich.xchangestream.hitbtc; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.core.StreamingMarketDataService; import info.bitrich.xchangestream.hitbtc.dto.*; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; @@ -14,71 +17,69 @@ import org.knowm.xchange.dto.marketdata.Trades; import org.knowm.xchange.hitbtc.v2.HitbtcAdapters; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcStreamingMarketDataService implements StreamingMarketDataService { - private final HitbtcStreamingService service; - private Map orderbooks = new HashMap<>(); - - public HitbtcStreamingMarketDataService(HitbtcStreamingService service) { - this.service = service; - } + private final HitbtcStreamingService service; + private Map orderbooks = new HashMap<>(); - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String pair = currencyPair.base.toString() + currencyPair.counter.toString(); - String channelName = getChannelName("orderbook", pair); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + public HitbtcStreamingMarketDataService(HitbtcStreamingService service) { + this.service = service; + } - Observable jsonNodeObservable = service.subscribeChannel(channelName); - return jsonNodeObservable - .map(s -> mapper.readValue(s.toString(), HitbtcWebSocketOrderBookTransaction.class)) - .map(s -> { - HitbtcWebSocketOrderBook hitbtcOrderBook = s.toHitbtcOrderBook(orderbooks.getOrDefault(currencyPair, null)); - orderbooks.put(currencyPair, hitbtcOrderBook); - return HitbtcAdapters.adaptOrderBook(hitbtcOrderBook.toHitbtcOrderBook(), currencyPair); - }); - } + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + String pair = currencyPair.base.toString() + currencyPair.counter.toString(); + String channelName = getChannelName("orderbook", pair); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String pair = currencyPair.base.toString() + currencyPair.counter.toString(); - String channelName = getChannelName("trades", pair); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + Observable jsonNodeObservable = service.subscribeChannel(channelName); + return jsonNodeObservable + .map(s -> mapper.readValue(s.toString(), HitbtcWebSocketOrderBookTransaction.class)) + .map( + s -> { + HitbtcWebSocketOrderBook hitbtcOrderBook = + s.toHitbtcOrderBook(orderbooks.getOrDefault(currencyPair, null)); + orderbooks.put(currencyPair, hitbtcOrderBook); + return HitbtcAdapters.adaptOrderBook( + hitbtcOrderBook.toHitbtcOrderBook(), currencyPair); + }); + } - return service.subscribeChannel(channelName) - .map(s -> mapper.readValue(s.toString(), HitbtcWebSocketTradesTransaction.class)) - .map(HitbtcWebSocketTradesTransaction::getParams) - .filter(Objects::nonNull) - .map(HitbtcWebSocketTradeParams::getData) - .filter(Objects::nonNull) - .map(Arrays::asList) - .flatMapIterable(s -> { - Trades adaptedTrades = HitbtcAdapters.adaptTrades(s, currencyPair); - return adaptedTrades.getTrades(); - }); - } + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String pair = currencyPair.base.toString() + currencyPair.counter.toString(); + String channelName = getChannelName("trades", pair); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - String pair = currencyPair.base.toString() + currencyPair.counter.toString(); - String channelName = getChannelName("ticker", pair); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + return service + .subscribeChannel(channelName) + .map(s -> mapper.readValue(s.toString(), HitbtcWebSocketTradesTransaction.class)) + .map(HitbtcWebSocketTradesTransaction::getParams) + .filter(Objects::nonNull) + .map(HitbtcWebSocketTradeParams::getData) + .filter(Objects::nonNull) + .map(Arrays::asList) + .flatMapIterable( + s -> { + Trades adaptedTrades = HitbtcAdapters.adaptTrades(s, currencyPair); + return adaptedTrades.getTrades(); + }); + } - return service.subscribeChannel(channelName) - .map(s -> mapper.readValue(s.toString(), HitbtcWebSocketTickerTransaction.class)) - .map(s -> HitbtcAdapters.adaptTicker(s.getParams(), currencyPair)); - } + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + String pair = currencyPair.base.toString() + currencyPair.counter.toString(); + String channelName = getChannelName("ticker", pair); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - private String getChannelName(String entityName, String pair) { - return entityName + "-" + pair; - } + return service + .subscribeChannel(channelName) + .map(s -> mapper.readValue(s.toString(), HitbtcWebSocketTickerTransaction.class)) + .map(s -> HitbtcAdapters.adaptTicker(s.getParams(), currencyPair)); + } + private String getChannelName(String entityName, String pair) { + return entityName + "-" + pair; + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingService.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingService.java index bb7f2dcf9..b20a55c33 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingService.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingService.java @@ -1,13 +1,16 @@ package info.bitrich.xchangestream.hitbtc; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.hitbtc.dto.HitbtcWebSocketBaseParams; import info.bitrich.xchangestream.hitbtc.dto.HitbtcWebSocketSubscriptionMessage; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -15,137 +18,132 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.Stream; - -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(HitbtcStreamingService.class); - - private static final String JSON_METHOD = "method"; - private static final String JSON_SYMBOL = "symbol"; - private static final String JSON_PARAMS = "params"; - private static final String JSON_RESULT = "result"; - private static final String JSON_ERROR = "error"; - private static final String JSON_ID = "id"; - - private static final String OP_SNAPSHOT = "snapshot"; - private static final String OP_UPDATE = "update"; - - /** - * Map request Id to Chanel Name and HitBTC method pair - */ - private final Map> requests = new HashMap<>(); - - - public HitbtcStreamingService(String apiUrl) { - super(apiUrl, Integer.MAX_VALUE); + private static final Logger LOG = LoggerFactory.getLogger(HitbtcStreamingService.class); + + private static final String JSON_METHOD = "method"; + private static final String JSON_SYMBOL = "symbol"; + private static final String JSON_PARAMS = "params"; + private static final String JSON_RESULT = "result"; + private static final String JSON_ERROR = "error"; + private static final String JSON_ID = "id"; + + private static final String OP_SNAPSHOT = "snapshot"; + private static final String OP_UPDATE = "update"; + + /** Map request Id to Chanel Name and HitBTC method pair */ + private final Map> requests = new HashMap<>(); + + public HitbtcStreamingService(String apiUrl) { + super(apiUrl, Integer.MAX_VALUE); + } + + @Override + protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { + return null; + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + + if (message.has(JSON_ID)) { + int requestId = message.get(JSON_ID).asInt(); + if (requests.containsKey(requestId)) { + return requests.get(requestId).getKey(); + } } - @Override - protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { - return null; + if (message.has(JSON_METHOD)) { + String method = message.get(JSON_METHOD).asText(); + if (message.has(JSON_PARAMS) && message.get(JSON_PARAMS).has(JSON_SYMBOL)) { + String symbol = message.get(JSON_PARAMS).get(JSON_SYMBOL).asText(); + + return Stream.of(OP_UPDATE, OP_SNAPSHOT) + .filter(method::startsWith) + .map(name -> method.substring(name.length()).toLowerCase() + "-" + symbol) + .findFirst() + .orElse(method.toLowerCase() + "-" + symbol); + } + return method; } - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - - if (message.has(JSON_ID)) { - int requestId = message.get(JSON_ID).asInt(); - if (requests.containsKey(requestId)) { - return requests.get(requestId).getKey(); - } + throw new IOException("Channel name can't be evaluated from message"); + } + + @Override + protected void handleMessage(JsonNode message) { + if (message.has(JSON_ID)) { + int requestId = message.get(JSON_ID).asInt(); + if (requests.containsKey(requestId)) { + + String subscriptionMethod = requests.get(requestId).getLeft(); + + if (message.has(JSON_ERROR)) { + try { + HitbtcException exception = objectMapper.treeToValue(message, HitbtcException.class); + super.handleError(message, exception); + } catch (JsonProcessingException e) { + super.handleError(message, e); + } + } else { + boolean result = message.get(JSON_RESULT).asBoolean(); + LOG.info("HitBTC returned {} as result of '{}' method", result, subscriptionMethod); } - if (message.has(JSON_METHOD)) { - String method = message.get(JSON_METHOD).asText(); - if (message.has(JSON_PARAMS) && message.get(JSON_PARAMS).has(JSON_SYMBOL)) { - String symbol = message.get(JSON_PARAMS).get(JSON_SYMBOL).asText(); - - return Stream.of(OP_UPDATE, OP_SNAPSHOT) - .filter(method::startsWith) - .map(name -> method.substring(name.length()).toLowerCase() + "-" + symbol) - .findFirst() - .orElse(method.toLowerCase() + "-" + symbol); - } - return method; - } + requests.remove(requestId); + return; - throw new IOException("Channel name can't be evaluated from message"); + } else { + LOG.error("Unknown request ID {}", requestId); + } } - @Override - protected void handleMessage(JsonNode message) { - if (message.has(JSON_ID)) { - int requestId = message.get(JSON_ID).asInt(); - if (requests.containsKey(requestId)) { - - String subscriptionMethod = requests.get(requestId).getLeft(); - - if (message.has(JSON_ERROR)) { - try { - HitbtcException exception = objectMapper.treeToValue(message, HitbtcException.class); - super.handleError(message, exception); - } catch (JsonProcessingException e) { - super.handleError(message, e); - } - } else { - boolean result = message.get(JSON_RESULT).asBoolean(); - LOG.info("HitBTC returned {} as result of '{}' method", result, subscriptionMethod); - } - - requests.remove(requestId); - return; - - } else { - LOG.error("Unknown request ID {}", requestId); - } - } - - String channel = getChannel(message); - if (!channels.containsKey(channel)) { - LOG.warn("The message has been received from disconnected channel '{}'. Skipped.", channel); - return; - } - - super.handleMessage(message); + String channel = getChannel(message); + if (!channels.containsKey(channel)) { + LOG.warn("The message has been received from disconnected channel '{}'. Skipped.", channel); + return; } + super.handleMessage(message); + } - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - HitbtcWebSocketSubscriptionMessage subscribeMessage = generateSubscribeMessage(channelName, "subscribe"); - requests.put(subscribeMessage.getId(), ImmutablePair.of(channelName, subscribeMessage.getMethod())); - - return objectMapper.writeValueAsString(subscribeMessage); - } + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + HitbtcWebSocketSubscriptionMessage subscribeMessage = + generateSubscribeMessage(channelName, "subscribe"); + requests.put( + subscribeMessage.getId(), ImmutablePair.of(channelName, subscribeMessage.getMethod())); - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { + return objectMapper.writeValueAsString(subscribeMessage); + } - HitbtcWebSocketSubscriptionMessage subscribeMessage = generateSubscribeMessage(channelName, "unsubscribe"); - requests.put(subscribeMessage.getId(), ImmutablePair.of(channelName, subscribeMessage.getMethod())); + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { - return objectMapper.writeValueAsString(subscribeMessage); + HitbtcWebSocketSubscriptionMessage subscribeMessage = + generateSubscribeMessage(channelName, "unsubscribe"); + requests.put( + subscribeMessage.getId(), ImmutablePair.of(channelName, subscribeMessage.getMethod())); - } + return objectMapper.writeValueAsString(subscribeMessage); + } - private HitbtcWebSocketSubscriptionMessage generateSubscribeMessage(String channelName, String methodType) throws IOException { + private HitbtcWebSocketSubscriptionMessage generateSubscribeMessage( + String channelName, String methodType) throws IOException { - String[] chanelInfo = channelName.split("-"); - if (chanelInfo.length < 2) { - throw new IOException(methodType + " message: channel name must has format - (e.g orderbook-ETHBTC)"); - } + String[] chanelInfo = channelName.split("-"); + if (chanelInfo.length < 2) { + throw new IOException( + methodType + + " message: channel name must has format - (e.g orderbook-ETHBTC)"); + } - String method = methodType + StringUtils.capitalize(chanelInfo[0]); - String symbol = chanelInfo[1]; - int requestId = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE); + String method = methodType + StringUtils.capitalize(chanelInfo[0]); + String symbol = chanelInfo[1]; + int requestId = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE); - return new HitbtcWebSocketSubscriptionMessage(requestId, method, new HitbtcWebSocketBaseParams(symbol)); - } + return new HitbtcWebSocketSubscriptionMessage( + requestId, method, new HitbtcWebSocketBaseParams(symbol)); + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseParams.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseParams.java index 57e910eda..17110f8e9 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseParams.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseParams.java @@ -2,19 +2,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketBaseParams { - protected final String symbol; + protected final String symbol; - public HitbtcWebSocketBaseParams(@JsonProperty("symbol") String symbol) { - this.symbol = symbol; - } - - public String getSymbol() { - return symbol; - } + public HitbtcWebSocketBaseParams(@JsonProperty("symbol") String symbol) { + this.symbol = symbol; + } + public String getSymbol() { + return symbol; + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseTransaction.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseTransaction.java index 8f077a1b4..25a2a757d 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseTransaction.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketBaseTransaction.java @@ -1,18 +1,15 @@ package info.bitrich.xchangestream.hitbtc.dto; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketBaseTransaction { - protected final String method; + protected final String method; - public HitbtcWebSocketBaseTransaction(String method) { - this.method = method; - } - - public String getMethod() { - return method; - } + public HitbtcWebSocketBaseTransaction(String method) { + this.method = method; + } + public String getMethod() { + return method; + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBook.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBook.java index dcc9450b0..61f9e55a4 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBook.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBook.java @@ -1,72 +1,68 @@ package info.bitrich.xchangestream.hitbtc.dto; -import org.knowm.xchange.hitbtc.v2.dto.HitbtcOrderBook; -import org.knowm.xchange.hitbtc.v2.dto.HitbtcOrderLimit; +import static java.util.Collections.reverseOrder; import java.math.BigDecimal; import java.util.Map; import java.util.TreeMap; +import org.knowm.xchange.hitbtc.v2.dto.HitbtcOrderBook; +import org.knowm.xchange.hitbtc.v2.dto.HitbtcOrderLimit; -import static java.util.Collections.reverseOrder; - -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketOrderBook { - private Map asks; - private Map bids; - private long sequence = 0; - - public HitbtcWebSocketOrderBook(HitbtcWebSocketOrderBookTransaction orderbookTransaction) { - createFromLevels(orderbookTransaction); - } + private Map asks; + private Map bids; + private long sequence = 0; - private void createFromLevels(HitbtcWebSocketOrderBookTransaction orderbookTransaction) { - this.asks = new TreeMap<>(BigDecimal::compareTo); - this.bids = new TreeMap<>(reverseOrder(BigDecimal::compareTo)); + public HitbtcWebSocketOrderBook(HitbtcWebSocketOrderBookTransaction orderbookTransaction) { + createFromLevels(orderbookTransaction); + } - for (HitbtcOrderLimit orderBookItem : orderbookTransaction.getParams().getAsk()) { - if (orderBookItem.getSize().signum() != 0) { - asks.put(orderBookItem.getPrice(), orderBookItem); - } - } + private void createFromLevels(HitbtcWebSocketOrderBookTransaction orderbookTransaction) { + this.asks = new TreeMap<>(BigDecimal::compareTo); + this.bids = new TreeMap<>(reverseOrder(BigDecimal::compareTo)); - for (HitbtcOrderLimit orderBookItem : orderbookTransaction.getParams().getBid()) { - if (orderBookItem.getSize().signum() != 0) { - bids.put(orderBookItem.getPrice(), orderBookItem); - } - } + for (HitbtcOrderLimit orderBookItem : orderbookTransaction.getParams().getAsk()) { + if (orderBookItem.getSize().signum() != 0) { + asks.put(orderBookItem.getPrice(), orderBookItem); + } + } - sequence = orderbookTransaction.getParams().getSequence(); + for (HitbtcOrderLimit orderBookItem : orderbookTransaction.getParams().getBid()) { + if (orderBookItem.getSize().signum() != 0) { + bids.put(orderBookItem.getPrice(), orderBookItem); + } } - public HitbtcOrderBook toHitbtcOrderBook() { - HitbtcOrderLimit[] askLimits = asks.entrySet().stream() - .map(Map.Entry::getValue) - .toArray(HitbtcOrderLimit[]::new); + sequence = orderbookTransaction.getParams().getSequence(); + } - HitbtcOrderLimit[] bidLimits = bids.entrySet().stream() - .map(Map.Entry::getValue) - .toArray(HitbtcOrderLimit[]::new); + public HitbtcOrderBook toHitbtcOrderBook() { + HitbtcOrderLimit[] askLimits = + asks.entrySet().stream().map(Map.Entry::getValue).toArray(HitbtcOrderLimit[]::new); - return new HitbtcOrderBook(askLimits, bidLimits); - } + HitbtcOrderLimit[] bidLimits = + bids.entrySet().stream().map(Map.Entry::getValue).toArray(HitbtcOrderLimit[]::new); + + return new HitbtcOrderBook(askLimits, bidLimits); + } - public void updateOrderBook(HitbtcWebSocketOrderBookTransaction orderBookTransaction) { - if (orderBookTransaction.getParams().getSequence() <= sequence) { - return; - } - updateOrderBookItems(orderBookTransaction.getParams().getAsk(), asks); - updateOrderBookItems(orderBookTransaction.getParams().getBid(), bids); - sequence = orderBookTransaction.getParams().getSequence(); + public void updateOrderBook(HitbtcWebSocketOrderBookTransaction orderBookTransaction) { + if (orderBookTransaction.getParams().getSequence() <= sequence) { + return; } + updateOrderBookItems(orderBookTransaction.getParams().getAsk(), asks); + updateOrderBookItems(orderBookTransaction.getParams().getBid(), bids); + sequence = orderBookTransaction.getParams().getSequence(); + } - private void updateOrderBookItems(HitbtcOrderLimit[] itemsToUpdate, Map localItems) { - for (HitbtcOrderLimit itemToUpdate : itemsToUpdate) { - localItems.remove(itemToUpdate.getPrice()); - if (itemToUpdate.getSize().signum() != 0) { - localItems.put(itemToUpdate.getPrice(), itemToUpdate); - } - } + private void updateOrderBookItems( + HitbtcOrderLimit[] itemsToUpdate, Map localItems) { + for (HitbtcOrderLimit itemToUpdate : itemsToUpdate) { + localItems.remove(itemToUpdate.getPrice()); + if (itemToUpdate.getSize().signum() != 0) { + localItems.put(itemToUpdate.getPrice(), itemToUpdate); + } } + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookParams.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookParams.java index 76f001341..45883a1f4 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookParams.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookParams.java @@ -1,36 +1,35 @@ package info.bitrich.xchangestream.hitbtc.dto; - import com.fasterxml.jackson.annotation.JsonProperty; import org.knowm.xchange.hitbtc.v2.dto.HitbtcOrderLimit; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketOrderBookParams extends HitbtcWebSocketBaseParams { - private final HitbtcOrderLimit[] ask; - private final HitbtcOrderLimit[] bid; - private final long sequence; - - public HitbtcWebSocketOrderBookParams(@JsonProperty("property") String symbol, @JsonProperty("sequence") long sequence, - @JsonProperty("ask") HitbtcOrderLimit[] ask, @JsonProperty("bid") HitbtcOrderLimit[] bid) { - super(symbol); - this.ask = ask; - this.bid = bid; - this.sequence = sequence; - } - - public HitbtcOrderLimit[] getAsk() { - return ask; - } - - public HitbtcOrderLimit[] getBid() { - return bid; - } - - public long getSequence() { - return sequence; - } - + private final HitbtcOrderLimit[] ask; + private final HitbtcOrderLimit[] bid; + private final long sequence; + + public HitbtcWebSocketOrderBookParams( + @JsonProperty("property") String symbol, + @JsonProperty("sequence") long sequence, + @JsonProperty("ask") HitbtcOrderLimit[] ask, + @JsonProperty("bid") HitbtcOrderLimit[] bid) { + super(symbol); + this.ask = ask; + this.bid = bid; + this.sequence = sequence; + } + + public HitbtcOrderLimit[] getAsk() { + return ask; + } + + public HitbtcOrderLimit[] getBid() { + return bid; + } + + public long getSequence() { + return sequence; + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookTransaction.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookTransaction.java index 08f698059..71e0f9d1c 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookTransaction.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketOrderBookTransaction.java @@ -2,28 +2,28 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketOrderBookTransaction extends HitbtcWebSocketBaseTransaction { - private static final String ORDERBOOK_METHOD_UPDATE = "updateOrderbook"; - private HitbtcWebSocketOrderBookParams params; + private static final String ORDERBOOK_METHOD_UPDATE = "updateOrderbook"; + private HitbtcWebSocketOrderBookParams params; - public HitbtcWebSocketOrderBookTransaction(@JsonProperty("method") String method, @JsonProperty("params") HitbtcWebSocketOrderBookParams params) { - super(method); - this.params = params; - } + public HitbtcWebSocketOrderBookTransaction( + @JsonProperty("method") String method, + @JsonProperty("params") HitbtcWebSocketOrderBookParams params) { + super(method); + this.params = params; + } - public HitbtcWebSocketOrderBookParams getParams() { - return params; - } + public HitbtcWebSocketOrderBookParams getParams() { + return params; + } - public HitbtcWebSocketOrderBook toHitbtcOrderBook(HitbtcWebSocketOrderBook orderbook) { - if (method.equals(ORDERBOOK_METHOD_UPDATE)) { - orderbook.updateOrderBook(this); - return orderbook; - } - return new HitbtcWebSocketOrderBook(this); + public HitbtcWebSocketOrderBook toHitbtcOrderBook(HitbtcWebSocketOrderBook orderbook) { + if (method.equals(ORDERBOOK_METHOD_UPDATE)) { + orderbook.updateOrderBook(this); + return orderbook; } + return new HitbtcWebSocketOrderBook(this); + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketSubscriptionMessage.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketSubscriptionMessage.java index 347c21006..fdcdf4bd9 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketSubscriptionMessage.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketSubscriptionMessage.java @@ -2,30 +2,31 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketSubscriptionMessage { - private final String method; - private final int id; - private final HitbtcWebSocketBaseParams params; + private final String method; + private final int id; + private final HitbtcWebSocketBaseParams params; - public HitbtcWebSocketSubscriptionMessage(@JsonProperty("id") int id, @JsonProperty("method") String method, @JsonProperty("params") HitbtcWebSocketBaseParams params) { - this.id = id; - this.method = method; - this.params = params; - } + public HitbtcWebSocketSubscriptionMessage( + @JsonProperty("id") int id, + @JsonProperty("method") String method, + @JsonProperty("params") HitbtcWebSocketBaseParams params) { + this.id = id; + this.method = method; + this.params = params; + } - public String getMethod() { - return method; - } + public String getMethod() { + return method; + } - public int getId() { - return id; - } + public int getId() { + return id; + } - public HitbtcWebSocketBaseParams getParams() { - return params; - } + public HitbtcWebSocketBaseParams getParams() { + return params; + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTickerTransaction.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTickerTransaction.java index 24562ee8b..5c19bb198 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTickerTransaction.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTickerTransaction.java @@ -3,26 +3,26 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.knowm.xchange.hitbtc.v2.dto.HitbtcTicker; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketTickerTransaction extends HitbtcWebSocketBaseTransaction { - private final Integer id; - private final HitbtcTicker params; + private final Integer id; + private final HitbtcTicker params; - public HitbtcWebSocketTickerTransaction(@JsonProperty("method") String method, @JsonProperty("id") Integer id, - @JsonProperty("params") HitbtcTicker params) { - super(method); - this.id = id; - this.params = params; - } + public HitbtcWebSocketTickerTransaction( + @JsonProperty("method") String method, + @JsonProperty("id") Integer id, + @JsonProperty("params") HitbtcTicker params) { + super(method); + this.id = id; + this.params = params; + } - public Integer getId() { - return id; - } + public Integer getId() { + return id; + } - public HitbtcTicker getParams() { - return params; - } + public HitbtcTicker getParams() { + return params; + } } diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradeParams.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradeParams.java index 154cee93c..989a8465d 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradeParams.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradeParams.java @@ -3,21 +3,18 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.knowm.xchange.hitbtc.v2.dto.HitbtcTrade; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketTradeParams extends HitbtcWebSocketBaseParams { - private final HitbtcTrade[] data; + private final HitbtcTrade[] data; - public HitbtcWebSocketTradeParams(@JsonProperty("symbol") String symbol, @JsonProperty("params") HitbtcTrade[] data) { - super(symbol); - this.data = data; - } - - public HitbtcTrade[] getData() { - return data; - } + public HitbtcWebSocketTradeParams( + @JsonProperty("symbol") String symbol, @JsonProperty("params") HitbtcTrade[] data) { + super(symbol); + this.data = data; + } + public HitbtcTrade[] getData() { + return data; + } } - diff --git a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradesTransaction.java b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradesTransaction.java index 6ef324ded..a528fae05 100644 --- a/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradesTransaction.java +++ b/xchange-stream-hitbtc/src/main/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcWebSocketTradesTransaction.java @@ -2,19 +2,18 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcWebSocketTradesTransaction extends HitbtcWebSocketBaseTransaction { - private final HitbtcWebSocketTradeParams params; + private final HitbtcWebSocketTradeParams params; - public HitbtcWebSocketTradesTransaction(@JsonProperty("method") String method, @JsonProperty("params") HitbtcWebSocketTradeParams params) { - super(method); - this.params = params; - } - - public HitbtcWebSocketTradeParams getParams() { - return params; - } + public HitbtcWebSocketTradesTransaction( + @JsonProperty("method") String method, + @JsonProperty("params") HitbtcWebSocketTradeParams params) { + super(method); + this.params = params; + } + public HitbtcWebSocketTradeParams getParams() { + return params; + } } diff --git a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcManualExample.java b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcManualExample.java index 171b0a1f7..e01370b60 100644 --- a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcManualExample.java +++ b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcManualExample.java @@ -7,41 +7,55 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcManualExample { - private static final Logger LOG = LoggerFactory.getLogger(HitbtcManualExample.class); - - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(HitbtcStreamingExchange.class - .getName()); - - exchange.connect().blockingAwait(); - Disposable orderBookObserver = exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); - - Disposable tradesObserver = exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD) - .subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting trade: ", throwable)); - - Disposable tickerObserver = exchange.getStreamingMarketDataService().getTicker(CurrencyPair.ETH_BTC).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - orderBookObserver.dispose(); - tradesObserver.dispose(); - tickerObserver.dispose(); - exchange.disconnect().subscribe(() -> LOG.info("Disconnected")); - + private static final Logger LOG = LoggerFactory.getLogger(HitbtcManualExample.class); + + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(HitbtcStreamingExchange.class.getName()); + + exchange.connect().blockingAwait(); + Disposable orderBookObserver = + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_USD) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); + + Disposable tradesObserver = + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting trade: ", throwable)); + + Disposable tickerObserver = + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.ETH_BTC) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + + orderBookObserver.dispose(); + tradesObserver.dispose(); + tickerObserver.dispose(); + exchange.disconnect().subscribe(() -> LOG.info("Disconnected")); + } } diff --git a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataServiceTest.java b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataServiceTest.java index afcefdd87..d4272c06b 100644 --- a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataServiceTest.java +++ b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/HitbtcStreamingMarketDataServiceTest.java @@ -1,8 +1,16 @@ package info.bitrich.xchangestream.hitbtc; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + import com.fasterxml.jackson.databind.ObjectMapper; import io.reactivex.Observable; import io.reactivex.observers.TestObserver; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; @@ -16,65 +24,98 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - @RunWith(MockitoJUnitRunner.class) public class HitbtcStreamingMarketDataServiceTest { - @Mock - private HitbtcStreamingService streamingService; - private HitbtcStreamingMarketDataService marketDataService; - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Before - public void setUp() { - marketDataService = new HitbtcStreamingMarketDataService(streamingService); - } - - @Test - public void testOrderbookCommon() throws Exception { - - // Read order book in JSON - String orderBook = IOUtils.toString(getClass().getResource("/example/notificationSnapshotOrderBook.json"), "UTF8"); - - when(streamingService.subscribeChannel(eq("orderbook-BTCEUR"))).thenReturn(Observable.just(objectMapper.readTree(orderBook))); - - List bids = new ArrayList<>(); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.500"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("0.054558"))); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.076"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("0.054557"))); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("7.725"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("0.054524"))); - - List asks = new ArrayList<>(); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.245"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("0.054588"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("2.784"), CurrencyPair.BTC_EUR, null, null, new BigDecimal("0.054591"))); - - // Call get order book observable - TestObserver test = marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test(); - - // We get order book object in correct order - test.assertValue(orderBook1 -> { - assertThat(orderBook1.getAsks()).as("Asks").isEqualTo(asks); - assertThat(orderBook1.getBids()).as("Bids").isEqualTo(bids); - return true; + @Mock private HitbtcStreamingService streamingService; + private HitbtcStreamingMarketDataService marketDataService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Before + public void setUp() { + marketDataService = new HitbtcStreamingMarketDataService(streamingService); + } + + @Test + public void testOrderbookCommon() throws Exception { + + // Read order book in JSON + String orderBook = + IOUtils.toString( + getClass().getResource("/example/notificationSnapshotOrderBook.json"), "UTF8"); + + when(streamingService.subscribeChannel(eq("orderbook-BTCEUR"))) + .thenReturn(Observable.just(objectMapper.readTree(orderBook))); + + List bids = new ArrayList<>(); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.500"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("0.054558"))); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.076"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("0.054557"))); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("7.725"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("0.054524"))); + + List asks = new ArrayList<>(); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.245"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("0.054588"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("2.784"), + CurrencyPair.BTC_EUR, + null, + null, + new BigDecimal("0.054591"))); + + // Call get order book observable + TestObserver test = marketDataService.getOrderBook(CurrencyPair.BTC_EUR).test(); + + // We get order book object in correct order + test.assertValue( + orderBook1 -> { + assertThat(orderBook1.getAsks()).as("Asks").isEqualTo(asks); + assertThat(orderBook1.getBids()).as("Bids").isEqualTo(bids); + return true; }); - } + } - @Test - public void testGetTrades() throws Exception { - // Read trades in JSON - String trades = IOUtils.toString(getClass().getResource("/example/notificationSnapshotTrades.json"), "UTF8"); + @Test + public void testGetTrades() throws Exception { + // Read trades in JSON + String trades = + IOUtils.toString( + getClass().getResource("/example/notificationSnapshotTrades.json"), "UTF8"); - when(streamingService.subscribeChannel(eq("trades-BTCUSD"))).thenReturn(Observable.just(objectMapper.readTree(trades))); + when(streamingService.subscribeChannel(eq("trades-BTCUSD"))) + .thenReturn(Observable.just(objectMapper.readTree(trades))); - Trade expected1 = new Trade.Builder() + Trade expected1 = + new Trade.Builder() .type(Order.OrderType.BID) .originalAmount(new BigDecimal("0.057")) .currencyPair(CurrencyPair.BTC_USD) @@ -83,7 +124,8 @@ public void testGetTrades() throws Exception { .id("54469456") .build(); - Trade expected2 = new Trade.Builder() + Trade expected2 = + new Trade.Builder() .type(Order.OrderType.BID) .originalAmount(new BigDecimal("0.092")) .currencyPair(CurrencyPair.BTC_USD) @@ -92,7 +134,8 @@ public void testGetTrades() throws Exception { .id("54469497") .build(); - Trade expected3 = new Trade.Builder() + Trade expected3 = + new Trade.Builder() .type(Order.OrderType.BID) .originalAmount(new BigDecimal("0.002")) .currencyPair(CurrencyPair.BTC_USD) @@ -101,53 +144,62 @@ public void testGetTrades() throws Exception { .id("54469697") .build(); - // Call get trades observable - TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_USD).test(); - - test.assertValues(expected1, expected2, expected3); - validateTrade(0, test, expected1); - validateTrade(1, test, expected2); - validateTrade(2, test, expected3); - } - - private void validateTrade(int index, TestObserver test, Trade expected) { - test.assertValueAt(index, trade -> { - assertThat(trade.getPrice()).isEqualTo(expected.getPrice()); - assertThat(trade.getType()).isEqualTo(expected.getType()); - assertThat(trade.getOriginalAmount()).isEqualTo(expected.getOriginalAmount()); - assertThat(trade.getCurrencyPair()).isEqualTo(expected.getCurrencyPair()); - assertThat(trade.getTimestamp()).isEqualTo(expected.getTimestamp()); - return true; + // Call get trades observable + TestObserver test = marketDataService.getTrades(CurrencyPair.BTC_USD).test(); + + test.assertValues(expected1, expected2, expected3); + validateTrade(0, test, expected1); + validateTrade(1, test, expected2); + validateTrade(2, test, expected3); + } + + private void validateTrade(int index, TestObserver test, Trade expected) { + test.assertValueAt( + index, + trade -> { + assertThat(trade.getPrice()).isEqualTo(expected.getPrice()); + assertThat(trade.getType()).isEqualTo(expected.getType()); + assertThat(trade.getOriginalAmount()).isEqualTo(expected.getOriginalAmount()); + assertThat(trade.getCurrencyPair()).isEqualTo(expected.getCurrencyPair()); + assertThat(trade.getTimestamp()).isEqualTo(expected.getTimestamp()); + return true; }); - } - - @Test - public void testGetTicker() throws Exception { - // Read ticker in JSON - String tickerString = IOUtils.toString(getClass().getResource("/example/notificationTicker.json"), "UTF8"); - - when(streamingService.subscribeChannel(eq("ticker-BTCUSD"))).thenReturn(Observable.just(objectMapper.readTree(tickerString))); - - Ticker expected = - new Ticker.Builder() - .currencyPair(CurrencyPair.BTC_USD).last(new BigDecimal("0.054463")) - .bid(new BigDecimal("0.054463")).ask(new BigDecimal("0.054464")) - .high(new BigDecimal("0.057559")).low(new BigDecimal("0.053615")) - .volume(new BigDecimal("33068.346")).timestamp(new Date(1508427944941L)) - .build(); - - // Call get ticker observable - TestObserver test = marketDataService.getTicker(CurrencyPair.BTC_USD).test(); - - test.assertValue(ticker -> { - assertThat(ticker.getAsk()).isEqualTo(expected.getAsk()); - assertThat(ticker.getBid()).isEqualTo(expected.getBid()); - assertThat(ticker.getHigh()).isEqualTo(expected.getHigh()); - assertThat(ticker.getLow()).isEqualTo(expected.getLow()); - assertThat(ticker.getLast()).isEqualTo(expected.getLast()); - assertThat(ticker.getVolume()).isEqualTo(expected.getVolume()); - assertThat(ticker.getTimestamp()).isEqualTo(expected.getTimestamp()); - return true; + } + + @Test + public void testGetTicker() throws Exception { + // Read ticker in JSON + String tickerString = + IOUtils.toString(getClass().getResource("/example/notificationTicker.json"), "UTF8"); + + when(streamingService.subscribeChannel(eq("ticker-BTCUSD"))) + .thenReturn(Observable.just(objectMapper.readTree(tickerString))); + + Ticker expected = + new Ticker.Builder() + .currencyPair(CurrencyPair.BTC_USD) + .last(new BigDecimal("0.054463")) + .bid(new BigDecimal("0.054463")) + .ask(new BigDecimal("0.054464")) + .high(new BigDecimal("0.057559")) + .low(new BigDecimal("0.053615")) + .volume(new BigDecimal("33068.346")) + .timestamp(new Date(1508427944941L)) + .build(); + + // Call get ticker observable + TestObserver test = marketDataService.getTicker(CurrencyPair.BTC_USD).test(); + + test.assertValue( + ticker -> { + assertThat(ticker.getAsk()).isEqualTo(expected.getAsk()); + assertThat(ticker.getBid()).isEqualTo(expected.getBid()); + assertThat(ticker.getHigh()).isEqualTo(expected.getHigh()); + assertThat(ticker.getLow()).isEqualTo(expected.getLow()); + assertThat(ticker.getLast()).isEqualTo(expected.getLast()); + assertThat(ticker.getVolume()).isEqualTo(expected.getVolume()); + assertThat(ticker.getTimestamp()).isEqualTo(expected.getTimestamp()); + return true; }); - } + } } diff --git a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcMessageTest.java b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcMessageTest.java index f59a8611a..2595852bf 100644 --- a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcMessageTest.java +++ b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcMessageTest.java @@ -1,21 +1,16 @@ package info.bitrich.xchangestream.hitbtc.dto; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.core.AllOf.allOf; + import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.DateSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import org.apache.commons.io.IOUtils; -import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; @@ -24,125 +19,119 @@ import java.util.Collection; import java.util.Date; import java.util.TimeZone; +import org.apache.commons.io.IOUtils; +import org.hamcrest.Matcher; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.core.AllOf.allOf; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Created by Pavel Chertalev on 19.03.2018. - */ +/** Created by Pavel Chertalev on 19.03.2018. */ @RunWith(Parameterized.class) public class HitbtcMessageTest { - private static final Logger LOG = LoggerFactory.getLogger(HitbtcMessageTest.class); - - private final Class clazz; - private final Matcher matcher; - private final String testResource; - private final ObjectMapper objectMapper; - - - public HitbtcMessageTest(Class clazz, Matcher matcher, String testResource) { - this.clazz = clazz; - this.matcher = matcher; - this.testResource = testResource; - this.objectMapper = new ObjectMapper(); - - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - SimpleModule module = new SimpleModule(); - module.addSerializer(BigDecimal.class, new ToStringSerializer()); - SimpleDateFormat customFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - customFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - module.addSerializer(Date.class, new DateSerializer(false, customFormat)); - objectMapper.registerModule(module); - objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - } - - @Test - public void test() throws IOException { - LOG.info("Testing {} message...", testResource); - - String message = IOUtils.toString(getClass().getResource(testResource).openStream(), StandardCharsets.UTF_8); - Object object = objectMapper.readValue(message, clazz); - - Assert.assertNotNull(object); - - message = objectMapper.writeValueAsString(object); - LOG.info(message); - - assertThat(message, matcher); - - } - - @Parameterized.Parameters - @SuppressWarnings("unchecked") - public static Collection data() { - return Arrays.asList(new Object[][]{ - { - HitbtcWebSocketSubscriptionMessage.class, - allOf( - hasJsonPath("$.method", equalTo("subscribeTicker")), - hasJsonPath("$.id", equalTo(123)), - hasJsonPath("$.params.symbol", equalTo("ETHBTC")) - ), - "/example/subscriptionMessage.json" - }, - { - HitbtcWebSocketTickerTransaction.class, - allOf( - hasJsonPath("$.method", equalTo("ticker")), - hasJsonPath("$.params.ask", equalTo("0.054464")), - hasJsonPath("$.params.bid", equalTo("0.054463")), - hasJsonPath("$.params.last", equalTo("0.054463")), - hasJsonPath("$.params.open", equalTo("0.057133")), - hasJsonPath("$.params.low", equalTo("0.053615")), - hasJsonPath("$.params.high", equalTo("0.057559")), - hasJsonPath("$.params.volume", equalTo("33068.346")), - hasJsonPath("$.params.volumeQuote", equalTo("1832.687530809")), - hasJsonPath("$.params.timestamp", equalTo("2017-10-19T15:45:44.941Z")), - hasJsonPath("$.params.symbol", equalTo("ETHBTC")) - ), - "/example/notificationTicker.json" - }, - { - HitbtcWebSocketTradesTransaction.class, - allOf( - hasJsonPath("$.method", equalTo("snapshotTrades")), - hasJsonPath("$.params.data[0].id", equalTo("54469456")), - hasJsonPath("$.params.data[0].price", equalTo("0.054656")), - hasJsonPath("$.params.data[0].quantity", equalTo("0.057")), - hasJsonPath("$.params.data[0].side", equalTo("buy")), - hasJsonPath("$.params.data[0].timestamp", equalTo("2017-10-19T16:33:42.821Z")), - hasJsonPath("$.params.data[2].id", equalTo("54469697")), - hasJsonPath("$.params.data[2].price", equalTo("0.054669")), - hasJsonPath("$.params.data[2].quantity", equalTo("0.002")), - hasJsonPath("$.params.data[2].side", equalTo("buy")), - hasJsonPath("$.params.data[2].timestamp", equalTo("2017-10-19T16:34:13.288Z")), - hasJsonPath("$.params.symbol", equalTo("ETHBTC")) - ), - "/example/notificationSnapshotTrades.json" - }, - { - HitbtcWebSocketOrderBookTransaction.class, - allOf( - hasJsonPath("$.method", equalTo("snapshotOrderbook")), - hasJsonPath("$.params.ask[0].price", equalTo("0.054588")), - hasJsonPath("$.params.ask[0].size", equalTo("0.245")), - hasJsonPath("$.params.ask[1].price", equalTo("0.054590")), - hasJsonPath("$.params.ask[1].size", equalTo("0.000")), - hasJsonPath("$.params.bid[0].price", equalTo("0.054558")), - hasJsonPath("$.params.bid[0].size", equalTo("0.500")), - hasJsonPath("$.params.bid[1].price", equalTo("0.054557")), - hasJsonPath("$.params.bid[1].size", equalTo("0.076")), - hasJsonPath("$.params.sequence", equalTo(8073827)), - hasJsonPath("$.params.symbol", equalTo("ETHBTC")) - ), - "/example/notificationSnapshotOrderBook.json" - } - + private static final Logger LOG = LoggerFactory.getLogger(HitbtcMessageTest.class); + + private final Class clazz; + private final Matcher matcher; + private final String testResource; + private final ObjectMapper objectMapper; + + public HitbtcMessageTest(Class clazz, Matcher matcher, String testResource) { + this.clazz = clazz; + this.matcher = matcher; + this.testResource = testResource; + this.objectMapper = new ObjectMapper(); + + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + SimpleModule module = new SimpleModule(); + module.addSerializer(BigDecimal.class, new ToStringSerializer()); + SimpleDateFormat customFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + customFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + module.addSerializer(Date.class, new DateSerializer(false, customFormat)); + objectMapper.registerModule(module); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + } + + @Test + public void test() throws IOException { + LOG.info("Testing {} message...", testResource); + + String message = + IOUtils.toString(getClass().getResource(testResource).openStream(), StandardCharsets.UTF_8); + Object object = objectMapper.readValue(message, clazz); + + Assert.assertNotNull(object); + + message = objectMapper.writeValueAsString(object); + LOG.info(message); + + assertThat(message, matcher); + } + + @Parameterized.Parameters + @SuppressWarnings("unchecked") + public static Collection data() { + return Arrays.asList( + new Object[][] { + { + HitbtcWebSocketSubscriptionMessage.class, + allOf( + hasJsonPath("$.method", equalTo("subscribeTicker")), + hasJsonPath("$.id", equalTo(123)), + hasJsonPath("$.params.symbol", equalTo("ETHBTC"))), + "/example/subscriptionMessage.json" + }, + { + HitbtcWebSocketTickerTransaction.class, + allOf( + hasJsonPath("$.method", equalTo("ticker")), + hasJsonPath("$.params.ask", equalTo("0.054464")), + hasJsonPath("$.params.bid", equalTo("0.054463")), + hasJsonPath("$.params.last", equalTo("0.054463")), + hasJsonPath("$.params.open", equalTo("0.057133")), + hasJsonPath("$.params.low", equalTo("0.053615")), + hasJsonPath("$.params.high", equalTo("0.057559")), + hasJsonPath("$.params.volume", equalTo("33068.346")), + hasJsonPath("$.params.volumeQuote", equalTo("1832.687530809")), + hasJsonPath("$.params.timestamp", equalTo("2017-10-19T15:45:44.941Z")), + hasJsonPath("$.params.symbol", equalTo("ETHBTC"))), + "/example/notificationTicker.json" + }, + { + HitbtcWebSocketTradesTransaction.class, + allOf( + hasJsonPath("$.method", equalTo("snapshotTrades")), + hasJsonPath("$.params.data[0].id", equalTo("54469456")), + hasJsonPath("$.params.data[0].price", equalTo("0.054656")), + hasJsonPath("$.params.data[0].quantity", equalTo("0.057")), + hasJsonPath("$.params.data[0].side", equalTo("buy")), + hasJsonPath("$.params.data[0].timestamp", equalTo("2017-10-19T16:33:42.821Z")), + hasJsonPath("$.params.data[2].id", equalTo("54469697")), + hasJsonPath("$.params.data[2].price", equalTo("0.054669")), + hasJsonPath("$.params.data[2].quantity", equalTo("0.002")), + hasJsonPath("$.params.data[2].side", equalTo("buy")), + hasJsonPath("$.params.data[2].timestamp", equalTo("2017-10-19T16:34:13.288Z")), + hasJsonPath("$.params.symbol", equalTo("ETHBTC"))), + "/example/notificationSnapshotTrades.json" + }, + { + HitbtcWebSocketOrderBookTransaction.class, + allOf( + hasJsonPath("$.method", equalTo("snapshotOrderbook")), + hasJsonPath("$.params.ask[0].price", equalTo("0.054588")), + hasJsonPath("$.params.ask[0].size", equalTo("0.245")), + hasJsonPath("$.params.ask[1].price", equalTo("0.054590")), + hasJsonPath("$.params.ask[1].size", equalTo("0.000")), + hasJsonPath("$.params.bid[0].price", equalTo("0.054558")), + hasJsonPath("$.params.bid[0].size", equalTo("0.500")), + hasJsonPath("$.params.bid[1].price", equalTo("0.054557")), + hasJsonPath("$.params.bid[1].size", equalTo("0.076")), + hasJsonPath("$.params.sequence", equalTo(8073827)), + hasJsonPath("$.params.symbol", equalTo("ETHBTC"))), + "/example/notificationSnapshotOrderBook.json" + } }); - } - - + } } diff --git a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcStreamingServiceTest.java b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcStreamingServiceTest.java index 8306e9c0c..35699f7be 100644 --- a/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcStreamingServiceTest.java +++ b/xchange-stream-hitbtc/src/test/java/info/bitrich/xchangestream/hitbtc/dto/HitbtcStreamingServiceTest.java @@ -3,54 +3,54 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.hitbtc.HitbtcStreamingService; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import org.apache.commons.lang3.reflect.MethodUtils; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * Created by Pavel Chertalev on 15.03.2018. - */ +/** Created by Pavel Chertalev on 15.03.2018. */ public class HitbtcStreamingServiceTest { - private final ObjectMapper objectMapper = new ObjectMapper(); - private final HitbtcStreamingService streamingService = new HitbtcStreamingService("testUrl"); - - @Rule - public ExpectedException thrown; - - public HitbtcStreamingServiceTest() { - thrown = ExpectedException.none(); - } + private final ObjectMapper objectMapper = new ObjectMapper(); + private final HitbtcStreamingService streamingService = new HitbtcStreamingService("testUrl"); - @Test - public void getChannelNameFromMessageTest() throws IOException, InvocationTargetException, IllegalAccessException { + @Rule public ExpectedException thrown; + public HitbtcStreamingServiceTest() { + thrown = ExpectedException.none(); + } - Method method = MethodUtils.getMatchingMethod(HitbtcStreamingService.class, "getChannelNameFromMessage", JsonNode.class); - method.setAccessible(true); + @Test + public void getChannelNameFromMessageTest() + throws IOException, InvocationTargetException, IllegalAccessException { - String json = "{\"method\":\"aaa\"}"; - Assert.assertEquals("aaa", method.invoke(streamingService, objectMapper.readTree(json))); + Method method = + MethodUtils.getMatchingMethod( + HitbtcStreamingService.class, "getChannelNameFromMessage", JsonNode.class); + method.setAccessible(true); - json = "{ \"method\": \"updateOrderbook\", \"params\": { \"symbol\": \"ETHBTC\" } }"; - Assert.assertEquals("orderbook-ETHBTC", method.invoke(streamingService, objectMapper.readTree(json))); + String json = "{\"method\":\"aaa\"}"; + Assert.assertEquals("aaa", method.invoke(streamingService, objectMapper.readTree(json))); - json = "{ \"method\": \"snapshotOrderbook\", \"params\": { \"symbol\": \"ETHBTC\" } }"; - Assert.assertEquals("orderbook-ETHBTC", method.invoke(streamingService, objectMapper.readTree(json))); + json = "{ \"method\": \"updateOrderbook\", \"params\": { \"symbol\": \"ETHBTC\" } }"; + Assert.assertEquals( + "orderbook-ETHBTC", method.invoke(streamingService, objectMapper.readTree(json))); - json = "{ \"method\": \"test\", \"params\": { \"symbol\": \"ETHBTC\" } }"; - Assert.assertEquals("test-ETHBTC", method.invoke(streamingService, objectMapper.readTree(json))); + json = "{ \"method\": \"snapshotOrderbook\", \"params\": { \"symbol\": \"ETHBTC\" } }"; + Assert.assertEquals( + "orderbook-ETHBTC", method.invoke(streamingService, objectMapper.readTree(json))); - json = "{ \"noMethod\": \"updateOrderbook\" } }"; + json = "{ \"method\": \"test\", \"params\": { \"symbol\": \"ETHBTC\" } }"; + Assert.assertEquals( + "test-ETHBTC", method.invoke(streamingService, objectMapper.readTree(json))); - thrown.expect(InvocationTargetException.class); - method.invoke(streamingService, objectMapper.readTree(json)); - } + json = "{ \"noMethod\": \"updateOrderbook\" } }"; + thrown.expect(InvocationTargetException.class); + method.invoke(streamingService, objectMapper.readTree(json)); + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenException.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenException.java index c90f54e36..f5150c35a 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenException.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenException.java @@ -1,11 +1,9 @@ package info.bitrich.xchangestream.kraken; -/** - * @author pchertalev - */ +/** @author pchertalev */ public class KrakenException extends Exception { - public KrakenException(String message) { - super(message); - } + public KrakenException(String message) { + super(message); + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookStorage.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookStorage.java index c5c73f318..badbed14e 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookStorage.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookStorage.java @@ -1,88 +1,89 @@ package info.bitrich.xchangestream.kraken; -import info.bitrich.xchangestream.kraken.dto.KrakenOrderBook; -import org.knowm.xchange.kraken.dto.marketdata.KrakenDepth; -import org.knowm.xchange.kraken.dto.marketdata.KrakenPublicOrder; +import static java.util.Collections.reverseOrder; +import info.bitrich.xchangestream.kraken.dto.KrakenOrderBook; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; +import org.knowm.xchange.kraken.dto.marketdata.KrakenDepth; +import org.knowm.xchange.kraken.dto.marketdata.KrakenPublicOrder; -import static java.util.Collections.reverseOrder; - -/** - * @author pchertalev - */ +/** @author pchertalev */ public class KrakenOrderBookStorage { - private TreeMap asks; - private TreeMap bids; + private TreeMap asks; + private TreeMap bids; - private final int maxDepth; - - /** - * Constructor is used for snapshots only - * @param orderBookUpdate order book update items - * @param maxDepth order book size can rise up, so depth value need for order book truncating - */ - public KrakenOrderBookStorage(KrakenOrderBook orderBookUpdate, int maxDepth) { - this.maxDepth = maxDepth; - createFromLevels(orderBookUpdate); - } + private final int maxDepth; - /** - * Create order book from snapshot - * @param orderBookUpdate order book snapshot - */ - private void createFromLevels(KrakenOrderBook orderBookUpdate) { - this.asks = new TreeMap<>(BigDecimal::compareTo); - this.bids = new TreeMap<>(reverseOrder(BigDecimal::compareTo)); + /** + * Constructor is used for snapshots only + * + * @param orderBookUpdate order book update items + * @param maxDepth order book size can rise up, so depth value need for order book truncating + */ + public KrakenOrderBookStorage(KrakenOrderBook orderBookUpdate, int maxDepth) { + this.maxDepth = maxDepth; + createFromLevels(orderBookUpdate); + } - for (KrakenPublicOrder orderBookItem : orderBookUpdate.getAsk()) { - asks.put(orderBookItem.getPrice(), orderBookItem); - } + /** + * Create order book from snapshot + * + * @param orderBookUpdate order book snapshot + */ + private void createFromLevels(KrakenOrderBook orderBookUpdate) { + this.asks = new TreeMap<>(BigDecimal::compareTo); + this.bids = new TreeMap<>(reverseOrder(BigDecimal::compareTo)); - for (KrakenPublicOrder orderBookItem : orderBookUpdate.getBid()) { - bids.put(orderBookItem.getPrice(), orderBookItem); - } + for (KrakenPublicOrder orderBookItem : orderBookUpdate.getAsk()) { + asks.put(orderBookItem.getPrice(), orderBookItem); } - /** - * Converting to Kraken XChange format - * @return - */ - public synchronized KrakenDepth toKrakenDepth() { - List askLimits = new ArrayList<>(asks.values()); - List bidLimits = new ArrayList<>(bids.values()); - return new KrakenDepth(askLimits, bidLimits); + for (KrakenPublicOrder orderBookItem : orderBookUpdate.getBid()) { + bids.put(orderBookItem.getPrice(), orderBookItem); } + } - /** - * Order book incremental update - * @param orderBookUpdate order book update - */ - public synchronized void updateOrderBook(KrakenOrderBook orderBookUpdate) { - updateOrderBookItems(orderBookUpdate.getAsk(), asks); - updateOrderBookItems(orderBookUpdate.getBid(), bids); - } + /** + * Converting to Kraken XChange format + * + * @return + */ + public synchronized KrakenDepth toKrakenDepth() { + List askLimits = new ArrayList<>(asks.values()); + List bidLimits = new ArrayList<>(bids.values()); + return new KrakenDepth(askLimits, bidLimits); + } - private void updateOrderBookItems(KrakenPublicOrder[] itemsToUpdate, Map localItems) { - for (KrakenPublicOrder askToUpdate : itemsToUpdate) { - localItems.remove(askToUpdate.getPrice()); - if (askToUpdate.getVolume().compareTo(BigDecimal.ZERO) != 0) { - localItems.put(askToUpdate.getPrice(), askToUpdate); - } - } - truncate(asks, maxDepth); - truncate(bids, maxDepth); - } + /** + * Order book incremental update + * + * @param orderBookUpdate order book update + */ + public synchronized void updateOrderBook(KrakenOrderBook orderBookUpdate) { + updateOrderBookItems(orderBookUpdate.getAsk(), asks); + updateOrderBookItems(orderBookUpdate.getBid(), bids); + } - private void truncate(TreeMap items, int maxSize) { - while (items.size() > maxSize) { - items.remove(items.lastKey()); - } + private void updateOrderBookItems( + KrakenPublicOrder[] itemsToUpdate, Map localItems) { + for (KrakenPublicOrder askToUpdate : itemsToUpdate) { + localItems.remove(askToUpdate.getPrice()); + if (askToUpdate.getVolume().compareTo(BigDecimal.ZERO) != 0) { + localItems.put(askToUpdate.getPrice(), askToUpdate); + } } + truncate(asks, maxDepth); + truncate(bids, maxDepth); + } + private void truncate(TreeMap items, int maxSize) { + while (items.size() > maxSize) { + items.remove(items.lastKey()); + } + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookUtils.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookUtils.java index 0d8013ee3..63897fb36 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookUtils.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenOrderBookUtils.java @@ -2,16 +2,15 @@ import info.bitrich.xchangestream.kraken.dto.KrakenOrderBook; import info.bitrich.xchangestream.kraken.dto.enums.KrakenOrderBookMessageType; -import org.apache.commons.lang3.StringUtils; -import org.knowm.xchange.kraken.dto.marketdata.KrakenPublicOrder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.math.BigDecimal; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.knowm.xchange.kraken.dto.marketdata.KrakenPublicOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Kraken order book utils @@ -20,75 +19,93 @@ */ public class KrakenOrderBookUtils { - private static final Logger LOG = LoggerFactory.getLogger(KrakenOrderBookUtils.class); + private static final Logger LOG = LoggerFactory.getLogger(KrakenOrderBookUtils.class); - private static final String ASK_SNAPSHOT = "as"; - private static final String ASK_UPDATE = "a"; + private static final String ASK_SNAPSHOT = "as"; + private static final String ASK_UPDATE = "a"; - private static final String BID_SNAPSHOT = "bs"; - private static final String BID_UPDATE = "b"; + private static final String BID_SNAPSHOT = "bs"; + private static final String BID_UPDATE = "b"; - private static final int EXPECTED_ORDER_BOOK_ARRAY_SIZE = 4; - private static final BigDecimal BIG_DECIMAL_1000 = new BigDecimal(1000); + private static final int EXPECTED_ORDER_BOOK_ARRAY_SIZE = 4; + private static final BigDecimal BIG_DECIMAL_1000 = new BigDecimal(1000); - @SuppressWarnings("unchecked") - public static KrakenOrderBook parse(List jsonParseResult) { - try { - Iterator iterator = jsonParseResult.iterator(); + @SuppressWarnings("unchecked") + public static KrakenOrderBook parse(List jsonParseResult) { + try { + Iterator iterator = jsonParseResult.iterator(); - Integer channelID = getTypedValue(iterator, Integer.class, "order book channel type"); - Map>> orderBookItems = getTypedValue(iterator, Map.class, "order book items"); - Map>> orderBookItemsMap = new HashMap(orderBookItems); - int index = 2; - if (jsonParseResult.size() > EXPECTED_ORDER_BOOK_ARRAY_SIZE) { - orderBookItems = getTypedValue(iterator, Map.class, "order book items"); - orderBookItemsMap.putAll(orderBookItems); - index = 3; - } - String channelName = (String) jsonParseResult.get(index++); - String pair = (String) jsonParseResult.get(index); - KrakenOrderBookMessageType orderBookType = orderBookItemsMap.keySet().stream() - .anyMatch(key -> StringUtils.equalsAny(key, ASK_SNAPSHOT, BID_SNAPSHOT)) ? KrakenOrderBookMessageType.SNAPSHOT : KrakenOrderBookMessageType.UPDATE; + Integer channelID = getTypedValue(iterator, Integer.class, "order book channel type"); + Map>> orderBookItems = + getTypedValue(iterator, Map.class, "order book items"); + Map>> orderBookItemsMap = new HashMap(orderBookItems); + int index = 2; + if (jsonParseResult.size() > EXPECTED_ORDER_BOOK_ARRAY_SIZE) { + orderBookItems = getTypedValue(iterator, Map.class, "order book items"); + orderBookItemsMap.putAll(orderBookItems); + index = 3; + } + String channelName = (String) jsonParseResult.get(index++); + String pair = (String) jsonParseResult.get(index); + KrakenOrderBookMessageType orderBookType = + orderBookItemsMap.keySet().stream() + .anyMatch(key -> StringUtils.equalsAny(key, ASK_SNAPSHOT, BID_SNAPSHOT)) + ? KrakenOrderBookMessageType.SNAPSHOT + : KrakenOrderBookMessageType.UPDATE; - List> asksValues; - List> bidsValues; - if (orderBookType == KrakenOrderBookMessageType.SNAPSHOT) { - asksValues = orderBookItemsMap.get(ASK_SNAPSHOT); - bidsValues = orderBookItemsMap.get(BID_SNAPSHOT); - } else { - asksValues = orderBookItemsMap.get(ASK_UPDATE); - bidsValues = orderBookItemsMap.get(BID_UPDATE); - } - return new KrakenOrderBook(channelID, channelName, pair, orderBookType, getItemsArray(asksValues), getItemsArray(bidsValues)); + List> asksValues; + List> bidsValues; + if (orderBookType == KrakenOrderBookMessageType.SNAPSHOT) { + asksValues = orderBookItemsMap.get(ASK_SNAPSHOT); + bidsValues = orderBookItemsMap.get(BID_SNAPSHOT); + } else { + asksValues = orderBookItemsMap.get(ASK_UPDATE); + bidsValues = orderBookItemsMap.get(BID_UPDATE); + } + return new KrakenOrderBook( + channelID, + channelName, + pair, + orderBookType, + getItemsArray(asksValues), + getItemsArray(bidsValues)); - } catch (KrakenException e) { - LOG.error("failed to parse order book tree {}", e.getMessage()); - return null; - } + } catch (KrakenException e) { + LOG.error("failed to parse order book tree {}", e.getMessage()); + return null; } + } - public static KrakenPublicOrder[] getItemsArray(List> values) { - return values == null ? new KrakenPublicOrder[0] : values.stream().map(KrakenOrderBookUtils::extractKrakenPublicOrder).toArray(KrakenPublicOrder[]::new); - } + public static KrakenPublicOrder[] getItemsArray(List> values) { + return values == null + ? new KrakenPublicOrder[0] + : values.stream() + .map(KrakenOrderBookUtils::extractKrakenPublicOrder) + .toArray(KrakenPublicOrder[]::new); + } - @SuppressWarnings("unchecked") - public static T getTypedValue(Iterator iterator, Class clazz, String fieldName) throws KrakenException { - if (!iterator.hasNext()) { - throw new KrakenException(String.format("Expected value of %s type for %s filed but there is no value", clazz, fieldName)); - } - Object object = iterator.next(); - if (!clazz.isAssignableFrom(object.getClass())) { - throw new KrakenException(String.format("Expected value of %s type for %s filed but there is invalid type %s", clazz, fieldName, object.getClass().getName())); - } - return (T) object; + @SuppressWarnings("unchecked") + public static T getTypedValue(Iterator iterator, Class clazz, String fieldName) + throws KrakenException { + if (!iterator.hasNext()) { + throw new KrakenException( + String.format( + "Expected value of %s type for %s filed but there is no value", clazz, fieldName)); } - - public static KrakenPublicOrder extractKrakenPublicOrder(List list) { - return new KrakenPublicOrder( - new BigDecimal(list.get(0)).stripTrailingZeros(), - new BigDecimal(list.get(1)).stripTrailingZeros(), - new BigDecimal(list.get(2)).multiply(BIG_DECIMAL_1000).longValue() - ); + Object object = iterator.next(); + if (!clazz.isAssignableFrom(object.getClass())) { + throw new KrakenException( + String.format( + "Expected value of %s type for %s filed but there is invalid type %s", + clazz, fieldName, object.getClass().getName())); } + return (T) object; + } + public static KrakenPublicOrder extractKrakenPublicOrder(List list) { + return new KrakenPublicOrder( + new BigDecimal(list.get(0)).stripTrailingZeros(), + new BigDecimal(list.get(1)).stripTrailingZeros(), + new BigDecimal(list.get(2)).multiply(BIG_DECIMAL_1000).longValue()); + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingExchange.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingExchange.java index 434fcbfb9..b67674bdb 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingExchange.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingExchange.java @@ -11,97 +11,98 @@ import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.kraken.KrakenExchange; import org.knowm.xchange.kraken.service.KrakenAccountServiceRaw; -import org.knowm.xchange.utils.nonce.CurrentNanosecondTimeIncrementalNonceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import si.mazi.rescu.SynchronizedValueFactory; -/** - * @author makarid - */ +/** @author makarid */ public class KrakenStreamingExchange extends KrakenExchange implements StreamingExchange { - private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingExchange.class); - private final static String USE_BETA = "Use_Beta"; - private static final String API_URI = "wss://ws.kraken.com"; - private static final String API_AUTH_URI = "wss://ws-auth.kraken.com"; - private static final String API_BETA_URI = "wss://beta-ws.kraken.com"; + private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingExchange.class); + private static final String USE_BETA = "Use_Beta"; + private static final String API_URI = "wss://ws.kraken.com"; + private static final String API_AUTH_URI = "wss://ws-auth.kraken.com"; + private static final String API_BETA_URI = "wss://beta-ws.kraken.com"; - private KrakenStreamingService streamingService, privateStreamingService; - private KrakenStreamingMarketDataService streamingMarketDataService; - private KrakenStreamingTradeService streamingTradeService; + private KrakenStreamingService streamingService, privateStreamingService; + private KrakenStreamingMarketDataService streamingMarketDataService; + private KrakenStreamingTradeService streamingTradeService; - public KrakenStreamingExchange() { - } - - private static String pickUri(boolean isPrivate, boolean useBeta) { - return useBeta ? API_BETA_URI : isPrivate ? API_AUTH_URI : API_URI; - } + public KrakenStreamingExchange() {} - @Override - protected void initServices() { - super.initServices(); - Boolean useBeta = MoreObjects.firstNonNull((Boolean)exchangeSpecification.getExchangeSpecificParametersItem(USE_BETA), Boolean.FALSE); + private static String pickUri(boolean isPrivate, boolean useBeta) { + return useBeta ? API_BETA_URI : isPrivate ? API_AUTH_URI : API_URI; + } - this.streamingService = new KrakenStreamingService(false, pickUri(false,useBeta)); - this.streamingMarketDataService = new KrakenStreamingMarketDataService(streamingService); + @Override + protected void initServices() { + super.initServices(); + Boolean useBeta = + MoreObjects.firstNonNull( + (Boolean) exchangeSpecification.getExchangeSpecificParametersItem(USE_BETA), + Boolean.FALSE); - if (StringUtils.isNotEmpty(exchangeSpecification.getApiKey())) { - this.privateStreamingService = new KrakenStreamingService(true, pickUri(true,useBeta)); - } + this.streamingService = new KrakenStreamingService(false, pickUri(false, useBeta)); + this.streamingMarketDataService = new KrakenStreamingMarketDataService(streamingService); - KrakenAccountServiceRaw rawKrakenAcctService = (KrakenAccountServiceRaw) getAccountService(); - - streamingTradeService = new KrakenStreamingTradeService(privateStreamingService, rawKrakenAcctService); + if (StringUtils.isNotEmpty(exchangeSpecification.getApiKey())) { + this.privateStreamingService = new KrakenStreamingService(true, pickUri(true, useBeta)); } - @Override - public Completable connect(ProductSubscription... args) { - if (privateStreamingService != null) - return privateStreamingService.connect().mergeWith(streamingService.connect()); - return streamingService.connect(); - } - - @Override - public Completable disconnect() { - if (privateStreamingService != null) - return privateStreamingService.disconnect().mergeWith(streamingService.disconnect()); - return streamingService.disconnect(); - } - - @Override - public boolean isAlive() { - return streamingService.isSocketOpen() && (privateStreamingService == null || privateStreamingService.isSocketOpen()); - } - - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } - - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } - - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); - return spec; - } - - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public StreamingTradeService getStreamingTradeService() { - return streamingTradeService; - } - @Override - public void useCompressedMessages(boolean compressedMessages) { - streamingService.useCompressedMessages(compressedMessages); - } + KrakenAccountServiceRaw rawKrakenAcctService = (KrakenAccountServiceRaw) getAccountService(); + + streamingTradeService = + new KrakenStreamingTradeService(privateStreamingService, rawKrakenAcctService); + } + + @Override + public Completable connect(ProductSubscription... args) { + if (privateStreamingService != null) + return privateStreamingService.connect().mergeWith(streamingService.connect()); + return streamingService.connect(); + } + + @Override + public Completable disconnect() { + if (privateStreamingService != null) + return privateStreamingService.disconnect().mergeWith(streamingService.disconnect()); + return streamingService.disconnect(); + } + + @Override + public boolean isAlive() { + return streamingService.isSocketOpen() + && (privateStreamingService == null || privateStreamingService.isSocketOpen()); + } + + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } + + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } + + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); + return spec; + } + + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public StreamingTradeService getStreamingTradeService() { + return streamingTradeService; + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingMarketDataService.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingMarketDataService.java index c83650eee..b0cd77f0e 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingMarketDataService.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingMarketDataService.java @@ -5,6 +5,13 @@ import info.bitrich.xchangestream.kraken.dto.enums.KrakenSubscriptionName; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.lang3.ArrayUtils; import org.knowm.xchange.currency.CurrencyPair; @@ -20,152 +27,167 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.math.BigDecimal; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -/** - * @author makarid, pchertalev - */ +/** @author makarid, pchertalev */ public class KrakenStreamingMarketDataService implements StreamingMarketDataService { - private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingMarketDataService.class); - - private static final int ORDER_BOOK_SIZE_DEFAULT = 25; - private static final int[] KRAKEN_VALID_ORDER_BOOK_SIZES = {10, 25, 100, 500, 1000}; - private static final int MIN_DATA_ARRAY_SIZE = 4; - - public static final String KRAKEN_CHANNEL_DELIMITER = "-"; - - private final KrakenStreamingService service; - private final Map orderBooks = new ConcurrentHashMap<>(); - - public KrakenStreamingMarketDataService(KrakenStreamingService service) { - this.service = service; - } - - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String channelName = getChannelName(KrakenSubscriptionName.book, currencyPair); - int depth = parseOrderBookSize(args); - return subscribe(channelName, MIN_DATA_ARRAY_SIZE, depth) - .map(KrakenOrderBookUtils::parse) - .map(ob -> { - KrakenOrderBookStorage orderBook = ob.toKrakenOrderBook(orderBooks.get(channelName), depth); - orderBooks.put(channelName, orderBook); - return KrakenAdapters.adaptOrderBook(orderBook.toKrakenDepth(), currencyPair); - }); - } - - @Override - @SuppressWarnings("unchecked") - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - String channelName = getChannelName(KrakenSubscriptionName.ticker, currencyPair); - return subscribe(channelName, MIN_DATA_ARRAY_SIZE, null) - .map(jsonParseResult -> { - Map> tickerItems; - if (Map.class.isAssignableFrom(jsonParseResult.get(1).getClass())) { - tickerItems = (Map>) jsonParseResult.get(1); - } else { - tickerItems = new HashMap<>(); - } - KrakenTicker krakenTicker = new KrakenTicker( - new KrakenPublicOrder(bd(tickerItems.get("a"), 0), bd(tickerItems.get("a"), 2), 0), - new KrakenPublicOrder(bd(tickerItems.get("b"), 0), bd(tickerItems.get("b"), 2), 0), - new KrakenPublicOrder(bd(tickerItems.get("c"), 0), bd(tickerItems.get("b"), 2), 0), - new BigDecimal[]{bd(tickerItems.get("v"), 0), bd(tickerItems.get("v"), 1)}, - new BigDecimal[]{bd(tickerItems.get("p"), 0), bd(tickerItems.get("p"), 1)}, - new BigDecimal[]{bd(tickerItems.get("t"), 0), bd(tickerItems.get("t"), 1)}, - new BigDecimal[]{bd(tickerItems.get("l"), 0), bd(tickerItems.get("l"), 1)}, - new BigDecimal[]{bd(tickerItems.get("h"), 0), bd(tickerItems.get("h"), 1)}, - bd(tickerItems.get("o"), 0) - ); - return KrakenAdapters.adaptTicker(krakenTicker, currencyPair); - }); - } - - @Override - @SuppressWarnings("unchecked") - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channelName = getChannelName(KrakenSubscriptionName.trade, currencyPair); - return subscribe(channelName, MIN_DATA_ARRAY_SIZE, null) - .filter(list -> List.class.isAssignableFrom(list.get(1).getClass())) - .map(list -> (List) list.get(1)) - .flatMap(list -> Observable.fromIterable( - list.stream().map( - tradeList -> { - String type = getValue(tradeList, 3, String.class); - String orderType = getValue(tradeList, 4, String.class); - return KrakenAdapters.adaptTrade( - new KrakenPublicTrade( - bd(tradeList, 0), - bd(tradeList, 1), - getValue(tradeList, 2, Double.class), - type == null ? null : KrakenType.fromString(type), - orderType == null ? null : KrakenOrderType.fromString(orderType), - getValue(tradeList, 5, String.class) - ), currencyPair - ); - } - ).collect(Collectors.toList()) - )); - } - - public Observable subscribe(String channelName, int maxItems, Integer depth) { - return service.subscribeChannel(channelName, depth) - .filter(JsonNode::isArray) - .filter(Objects::nonNull) - .map(jsonNode -> StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, List.class)) - .filter(list -> { - if (list.size() < maxItems) { - LOG.error("Invalid message in channel {}. It contains {} array items but expected at least {}", channelName, list.size(), maxItems); - return false; - } - return true; - }); + private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingMarketDataService.class); + + private static final int ORDER_BOOK_SIZE_DEFAULT = 25; + private static final int[] KRAKEN_VALID_ORDER_BOOK_SIZES = {10, 25, 100, 500, 1000}; + private static final int MIN_DATA_ARRAY_SIZE = 4; + + public static final String KRAKEN_CHANNEL_DELIMITER = "-"; + + private final KrakenStreamingService service; + private final Map orderBooks = new ConcurrentHashMap<>(); + + public KrakenStreamingMarketDataService(KrakenStreamingService service) { + this.service = service; + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + String channelName = getChannelName(KrakenSubscriptionName.book, currencyPair); + int depth = parseOrderBookSize(args); + return subscribe(channelName, MIN_DATA_ARRAY_SIZE, depth) + .map(KrakenOrderBookUtils::parse) + .map( + ob -> { + KrakenOrderBookStorage orderBook = + ob.toKrakenOrderBook(orderBooks.get(channelName), depth); + orderBooks.put(channelName, orderBook); + return KrakenAdapters.adaptOrderBook(orderBook.toKrakenDepth(), currencyPair); + }); + } + + @Override + @SuppressWarnings("unchecked") + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + String channelName = getChannelName(KrakenSubscriptionName.ticker, currencyPair); + return subscribe(channelName, MIN_DATA_ARRAY_SIZE, null) + .map( + jsonParseResult -> { + Map> tickerItems; + if (Map.class.isAssignableFrom(jsonParseResult.get(1).getClass())) { + tickerItems = (Map>) jsonParseResult.get(1); + } else { + tickerItems = new HashMap<>(); + } + KrakenTicker krakenTicker = + new KrakenTicker( + new KrakenPublicOrder( + bd(tickerItems.get("a"), 0), bd(tickerItems.get("a"), 2), 0), + new KrakenPublicOrder( + bd(tickerItems.get("b"), 0), bd(tickerItems.get("b"), 2), 0), + new KrakenPublicOrder( + bd(tickerItems.get("c"), 0), bd(tickerItems.get("b"), 2), 0), + new BigDecimal[] {bd(tickerItems.get("v"), 0), bd(tickerItems.get("v"), 1)}, + new BigDecimal[] {bd(tickerItems.get("p"), 0), bd(tickerItems.get("p"), 1)}, + new BigDecimal[] {bd(tickerItems.get("t"), 0), bd(tickerItems.get("t"), 1)}, + new BigDecimal[] {bd(tickerItems.get("l"), 0), bd(tickerItems.get("l"), 1)}, + new BigDecimal[] {bd(tickerItems.get("h"), 0), bd(tickerItems.get("h"), 1)}, + bd(tickerItems.get("o"), 0)); + return KrakenAdapters.adaptTicker(krakenTicker, currencyPair); + }); + } + + @Override + @SuppressWarnings("unchecked") + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channelName = getChannelName(KrakenSubscriptionName.trade, currencyPair); + return subscribe(channelName, MIN_DATA_ARRAY_SIZE, null) + .filter(list -> List.class.isAssignableFrom(list.get(1).getClass())) + .map(list -> (List) list.get(1)) + .flatMap( + list -> + Observable.fromIterable( + list.stream() + .map( + tradeList -> { + String type = getValue(tradeList, 3, String.class); + String orderType = getValue(tradeList, 4, String.class); + return KrakenAdapters.adaptTrade( + new KrakenPublicTrade( + bd(tradeList, 0), + bd(tradeList, 1), + getValue(tradeList, 2, Double.class), + type == null ? null : KrakenType.fromString(type), + orderType == null + ? null + : KrakenOrderType.fromString(orderType), + getValue(tradeList, 5, String.class)), + currencyPair); + }) + .collect(Collectors.toList()))); + } + + public Observable subscribe(String channelName, int maxItems, Integer depth) { + return service + .subscribeChannel(channelName, depth) + .filter(JsonNode::isArray) + .filter(Objects::nonNull) + .map( + jsonNode -> + StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, List.class)) + .filter( + list -> { + if (list.size() < maxItems) { + LOG.error( + "Invalid message in channel {}. It contains {} array items but expected at least {}", + channelName, + list.size(), + maxItems); + return false; + } + return true; + }); + } + + private String getChannelName( + KrakenSubscriptionName subscriptionName, CurrencyPair currencyPair) { + String pair = currencyPair.base.toString() + "/" + currencyPair.counter.toString(); + return subscriptionName + KRAKEN_CHANNEL_DELIMITER + pair; + } + + private BigDecimal bd(List list, int index) { + return getValue(list, index, BigDecimal.class); + } + + @SuppressWarnings("unchecked") + private T getValue(List list, int index, Class clazz) { + if (list == null || list.size() < index + 1) { + return null; } - - private String getChannelName(KrakenSubscriptionName subscriptionName, CurrencyPair currencyPair) { - String pair = currencyPair.base.toString() + "/" + currencyPair.counter.toString(); - return subscriptionName + KRAKEN_CHANNEL_DELIMITER + pair; - } - - private BigDecimal bd(List list, int index) { - return getValue(list, index, BigDecimal.class); - } - - @SuppressWarnings("unchecked") - private T getValue(List list, int index, Class clazz) { - if (list == null || list.size() < index + 1) { - return null; + return (T) ConvertUtils.convert(list.get(index), clazz); + } + + private int parseOrderBookSize(Object[] args) { + if (args != null && args.length > 0) { + Object obSizeParam = args[0]; + LOG.debug("Specified Kraken order book size: {}", obSizeParam); + if (Number.class.isAssignableFrom(obSizeParam.getClass())) { + int obSize = ((Number) obSizeParam).intValue(); + if (ArrayUtils.contains(KRAKEN_VALID_ORDER_BOOK_SIZES, obSize)) { + return obSize; } - return (T) ConvertUtils.convert(list.get(index), clazz); - } - - private int parseOrderBookSize(Object[] args) { - if (args != null && args.length > 0) { - Object obSizeParam = args[0]; - LOG.debug("Specified Kraken order book size: {}", obSizeParam); - if (Number.class.isAssignableFrom(obSizeParam.getClass())) { - int obSize = ((Number) obSizeParam).intValue(); - if (ArrayUtils.contains(KRAKEN_VALID_ORDER_BOOK_SIZES, obSize)) { - return obSize; - } - LOG.error("Invalid order book size {}. Valid values: {}. Default order book size has been used: {}", - obSize, ArrayUtils.toString(KRAKEN_VALID_ORDER_BOOK_SIZES), ORDER_BOOK_SIZE_DEFAULT); - return ORDER_BOOK_SIZE_DEFAULT; - } - LOG.error("Order book size param type {} is invalid. Expected: {}. Default order book size has been used {}", - obSizeParam.getClass().getName(), Number.class.getName(), ORDER_BOOK_SIZE_DEFAULT); - return ORDER_BOOK_SIZE_DEFAULT; - } - - LOG.debug("Order book size param has not been specified. Default order book size has been used: {}", ORDER_BOOK_SIZE_DEFAULT); + LOG.error( + "Invalid order book size {}. Valid values: {}. Default order book size has been used: {}", + obSize, + ArrayUtils.toString(KRAKEN_VALID_ORDER_BOOK_SIZES), + ORDER_BOOK_SIZE_DEFAULT); return ORDER_BOOK_SIZE_DEFAULT; + } + LOG.error( + "Order book size param type {} is invalid. Expected: {}. Default order book size has been used {}", + obSizeParam.getClass().getName(), + Number.class.getName(), + ORDER_BOOK_SIZE_DEFAULT); + return ORDER_BOOK_SIZE_DEFAULT; } + LOG.debug( + "Order book size param has not been specified. Default order book size has been used: {}", + ORDER_BOOK_SIZE_DEFAULT); + return ORDER_BOOK_SIZE_DEFAULT; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingService.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingService.java index 926d07920..31d0e8bad 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingService.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingService.java @@ -1,5 +1,7 @@ package info.bitrich.xchangestream.kraken; +import static info.bitrich.xchangestream.kraken.dto.enums.KrakenEventType.subscribe; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -16,220 +18,238 @@ import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.reactivex.Completable; import io.reactivex.Observable; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import static info.bitrich.xchangestream.kraken.dto.enums.KrakenEventType.subscribe; - -/** - * @author makarid, pchertalev - */ +/** @author makarid, pchertalev */ public class KrakenStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingService.class); - private static final String EVENT = "event"; - private final Map channels = new ConcurrentHashMap<>(); - private ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - private final boolean isPrivate; - - private final Map subscriptionRequestMap = new ConcurrentHashMap<>(); - - public KrakenStreamingService(boolean isPrivate, String uri) { - super(uri, Integer.MAX_VALUE); - this.isPrivate = isPrivate; - } - - - @Override - public boolean processArrayMassageSeparately() { - return false; - } - - @Override - protected void handleMessage(JsonNode message) { - String channelName = getChannel(message); - - try { - JsonNode event = message.get(EVENT); - KrakenEventType krakenEvent; - if (event != null && (krakenEvent = KrakenEventType.getEvent(event.textValue())) != null) { - switch (krakenEvent) { - case pong: - LOG.debug("Pong received"); - break; - case heartbeat: - LOG.debug("Heartbeat received"); - break; - case systemStatus: - KrakenSystemStatus systemStatus = mapper.treeToValue(message, KrakenSystemStatus.class); - LOG.info("System status: {}", systemStatus); - break; - case subscriptionStatus: - KrakenSubscriptionStatusMessage statusMessage = mapper.treeToValue(message, KrakenSubscriptionStatusMessage.class); - Integer reqid = statusMessage.getReqid(); - if (!isPrivate && reqid != null) - channelName = subscriptionRequestMap.remove(reqid); - - switch (statusMessage.getStatus()) { - case subscribed: - LOG.info("Channel {} has been subscribed", channelName); - - if (statusMessage.getChannelID() != null) - channels.put(statusMessage.getChannelID(), channelName); - - break; - case unsubscribed: - LOG.info("Channel {} has been unsubscribed", channelName); - channels.remove(statusMessage.getChannelID()); - break; - case error: - LOG.error("Channel {} has been failed: {}", channelName, statusMessage.getErrorMessage()); - } - break; - case error: - LOG.error("Error received: {}", message.has("errorMessage") ? message.get("errorMessage").asText() : message.toString()); - break; - default: - LOG.warn("Unexpected event type has been received: {}", krakenEvent); - } - return; - + private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingService.class); + private static final String EVENT = "event"; + private final Map channels = new ConcurrentHashMap<>(); + private ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + private final boolean isPrivate; + + private final Map subscriptionRequestMap = new ConcurrentHashMap<>(); + + public KrakenStreamingService(boolean isPrivate, String uri) { + super(uri, Integer.MAX_VALUE); + this.isPrivate = isPrivate; + } + + @Override + public boolean processArrayMassageSeparately() { + return false; + } + + @Override + protected void handleMessage(JsonNode message) { + String channelName = getChannel(message); + + try { + JsonNode event = message.get(EVENT); + KrakenEventType krakenEvent; + if (event != null && (krakenEvent = KrakenEventType.getEvent(event.textValue())) != null) { + switch (krakenEvent) { + case pong: + LOG.debug("Pong received"); + break; + case heartbeat: + LOG.debug("Heartbeat received"); + break; + case systemStatus: + KrakenSystemStatus systemStatus = mapper.treeToValue(message, KrakenSystemStatus.class); + LOG.info("System status: {}", systemStatus); + break; + case subscriptionStatus: + KrakenSubscriptionStatusMessage statusMessage = + mapper.treeToValue(message, KrakenSubscriptionStatusMessage.class); + Integer reqid = statusMessage.getReqid(); + if (!isPrivate && reqid != null) channelName = subscriptionRequestMap.remove(reqid); + + switch (statusMessage.getStatus()) { + case subscribed: + LOG.info("Channel {} has been subscribed", channelName); + + if (statusMessage.getChannelID() != null) + channels.put(statusMessage.getChannelID(), channelName); + + break; + case unsubscribed: + LOG.info("Channel {} has been unsubscribed", channelName); + channels.remove(statusMessage.getChannelID()); + break; + case error: + LOG.error( + "Channel {} has been failed: {}", channelName, statusMessage.getErrorMessage()); } - } catch (JsonProcessingException e) { - LOG.error("Error reading message: {}", e.getMessage(), e); - } - - if (!message.isArray() || channelName == null) { - LOG.error("Unknown message: {}", message.toString()); - return; + break; + case error: + LOG.error( + "Error received: {}", + message.has("errorMessage") + ? message.get("errorMessage").asText() + : message.toString()); + break; + default: + LOG.warn("Unexpected event type has been received: {}", krakenEvent); } - - super.handleMessage(message); + return; + } + } catch (JsonProcessingException e) { + LOG.error("Error reading message: {}", e.getMessage(), e); } - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - String channelName = null; - if (message.has("channelID")) { - channelName = channels.get(message.get("channelID").asInt()); - } - if (message.has("channelName")) { - channelName = message.get("channelName").asText(); - } - - if (message.isArray()) { - if (message.get(0).isInt()) { - channelName = channels.get(message.get(0).asInt()); - } - if (message.get(1).isTextual()) { - channelName = message.get(1).asText(); - } - } - - if (LOG.isDebugEnabled()) { - LOG.debug("ChannelName {}", StringUtils.isBlank(channelName) ? "not defined" : channelName); - } - return channelName; + if (!message.isArray() || channelName == null) { + LOG.error("Unknown message: {}", message.toString()); + return; } - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - int reqID = Math.abs(UUID.randomUUID().hashCode()); - String [] channelData = channelName.split(KrakenStreamingMarketDataService.KRAKEN_CHANNEL_DELIMITER); - KrakenSubscriptionName subscriptionName = KrakenSubscriptionName.valueOf(channelData[0]); - - if (isPrivate) { - String token = (String) args[0]; - - KrakenSubscriptionMessage subscriptionMessage = new KrakenSubscriptionMessage(reqID, subscribe, - null, new KrakenSubscriptionConfig(subscriptionName, null, token)); - - return objectMapper.writeValueAsString(subscriptionMessage); - } else { - String pair = channelData[1]; + super.handleMessage(message); + } - Integer depth = null; - if (args.length > 0 && args[0] != null) { - depth = (Integer) args[0]; - } - subscriptionRequestMap.put(reqID, channelName); + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + String channelName = null; + if (message.has("channelID")) { + channelName = channels.get(message.get("channelID").asInt()); + } + if (message.has("channelName")) { + channelName = message.get("channelName").asText(); + } - KrakenSubscriptionMessage subscriptionMessage = new KrakenSubscriptionMessage(reqID, subscribe, - Collections.singletonList(pair), new KrakenSubscriptionConfig(subscriptionName, depth, null)); - return objectMapper.writeValueAsString(subscriptionMessage); - } + if (message.isArray()) { + if (message.get(0).isInt()) { + channelName = channels.get(message.get(0).asInt()); + } + if (message.get(1).isTextual()) { + channelName = message.get(1).asText(); + } } - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - int reqID = Math.abs(UUID.randomUUID().hashCode()); - String [] channelData = channelName.split(KrakenStreamingMarketDataService.KRAKEN_CHANNEL_DELIMITER); - KrakenSubscriptionName subscriptionName = KrakenSubscriptionName.valueOf(channelData[0]); - - if (isPrivate) { - KrakenSubscriptionMessage subscriptionMessage = new KrakenSubscriptionMessage(reqID, KrakenEventType.unsubscribe, - null, new KrakenSubscriptionConfig(subscriptionName, null, null)); - return objectMapper.writeValueAsString(subscriptionMessage); - } else { - String pair = channelData[1]; - - subscriptionRequestMap.put(reqID, channelName); - KrakenSubscriptionMessage subscriptionMessage = new KrakenSubscriptionMessage(reqID, KrakenEventType.unsubscribe, - Collections.singletonList(pair), new KrakenSubscriptionConfig(subscriptionName)); - return objectMapper.writeValueAsString(subscriptionMessage); - } + if (LOG.isDebugEnabled()) { + LOG.debug("ChannelName {}", StringUtils.isBlank(channelName) ? "not defined" : channelName); } - @Override - protected WebSocketClientHandler getWebSocketClientHandler(WebSocketClientHandshaker handshaker, - WebSocketClientHandler.WebSocketMessageHandler handler) { - LOG.info("Registering KrakenWebSocketClientHandler"); - return new KrakenWebSocketClientHandler(handshaker, handler); + return channelName; + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + int reqID = Math.abs(UUID.randomUUID().hashCode()); + String[] channelData = + channelName.split(KrakenStreamingMarketDataService.KRAKEN_CHANNEL_DELIMITER); + KrakenSubscriptionName subscriptionName = KrakenSubscriptionName.valueOf(channelData[0]); + + if (isPrivate) { + String token = (String) args[0]; + + KrakenSubscriptionMessage subscriptionMessage = + new KrakenSubscriptionMessage( + reqID, subscribe, null, new KrakenSubscriptionConfig(subscriptionName, null, token)); + + return objectMapper.writeValueAsString(subscriptionMessage); + } else { + String pair = channelData[1]; + + Integer depth = null; + if (args.length > 0 && args[0] != null) { + depth = (Integer) args[0]; + } + subscriptionRequestMap.put(reqID, channelName); + + KrakenSubscriptionMessage subscriptionMessage = + new KrakenSubscriptionMessage( + reqID, + subscribe, + Collections.singletonList(pair), + new KrakenSubscriptionConfig(subscriptionName, depth, null)); + return objectMapper.writeValueAsString(subscriptionMessage); + } + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + int reqID = Math.abs(UUID.randomUUID().hashCode()); + String[] channelData = + channelName.split(KrakenStreamingMarketDataService.KRAKEN_CHANNEL_DELIMITER); + KrakenSubscriptionName subscriptionName = KrakenSubscriptionName.valueOf(channelData[0]); + + if (isPrivate) { + KrakenSubscriptionMessage subscriptionMessage = + new KrakenSubscriptionMessage( + reqID, + KrakenEventType.unsubscribe, + null, + new KrakenSubscriptionConfig(subscriptionName, null, null)); + return objectMapper.writeValueAsString(subscriptionMessage); + } else { + String pair = channelData[1]; + + subscriptionRequestMap.put(reqID, channelName); + KrakenSubscriptionMessage subscriptionMessage = + new KrakenSubscriptionMessage( + reqID, + KrakenEventType.unsubscribe, + Collections.singletonList(pair), + new KrakenSubscriptionConfig(subscriptionName)); + return objectMapper.writeValueAsString(subscriptionMessage); + } + } + + @Override + protected WebSocketClientHandler getWebSocketClientHandler( + WebSocketClientHandshaker handshaker, + WebSocketClientHandler.WebSocketMessageHandler handler) { + LOG.info("Registering KrakenWebSocketClientHandler"); + return new KrakenWebSocketClientHandler(handshaker, handler); + } + + @Override + protected Completable openConnection() { + + KrakenSubscriptionMessage ping = + new KrakenSubscriptionMessage(null, KrakenEventType.ping, null, null); + + subscribeConnectionSuccess() + .subscribe( + o -> + Observable.interval(30, TimeUnit.SECONDS) + .takeWhile(t -> isSocketOpen()) + .subscribe(t -> sendObjectMessage(ping))); + + return super.openConnection(); + } + + private WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler = null; + + /** + * Custom client handler in order to execute an external, user-provided handler on channel events. + * This is useful because it seems Kraken unexpectedly closes the web socket connection. + */ + class KrakenWebSocketClientHandler extends NettyWebSocketClientHandler { + + public KrakenWebSocketClientHandler( + WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { + super(handshaker, handler); } @Override - protected Completable openConnection() { - - KrakenSubscriptionMessage ping = new KrakenSubscriptionMessage(null,KrakenEventType.ping,null,null); - - subscribeConnectionSuccess().subscribe( o -> - Observable - .interval(30, TimeUnit.SECONDS) - .takeWhile( t -> isSocketOpen()) - .subscribe( t -> sendObjectMessage(ping))); - - return super.openConnection(); + public void channelActive(ChannelHandlerContext ctx) { + super.channelActive(ctx); } - private WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler = null; - - /** - * Custom client handler in order to execute an external, user-provided handler on channel events. - * This is useful because it seems Kraken unexpectedly closes the web socket connection. - */ - class KrakenWebSocketClientHandler extends NettyWebSocketClientHandler { - - public KrakenWebSocketClientHandler(WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { - super(handshaker, handler); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) { - super.channelActive(ctx); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - super.channelInactive(ctx); - if (channelInactiveHandler != null) { - channelInactiveHandler.onMessage("WebSocket Client disconnected!"); - } - } + @Override + public void channelInactive(ChannelHandlerContext ctx) { + super.channelInactive(ctx); + if (channelInactiveHandler != null) { + channelInactiveHandler.onMessage("WebSocket Client disconnected!"); + } } + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingTradeService.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingTradeService.java index d7ab43f01..a00e5a1fe 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingTradeService.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/KrakenStreamingTradeService.java @@ -7,6 +7,8 @@ import info.bitrich.xchangestream.kraken.dto.enums.KrakenSubscriptionName; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.io.IOException; +import java.util.*; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.LimitOrder; @@ -22,181 +24,202 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.*; - public class KrakenStreamingTradeService implements StreamingTradeService { - private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingTradeService.class); + private static final Logger LOG = LoggerFactory.getLogger(KrakenStreamingTradeService.class); - private KrakenAccountServiceRaw rawKrakenAcctService; - private KrakenStreamingService streamingService; - private KrakenWebsocketToken token; - private long tokenExpires = 0L; + private KrakenAccountServiceRaw rawKrakenAcctService; + private KrakenStreamingService streamingService; + private KrakenWebsocketToken token; + private long tokenExpires = 0L; - private volatile boolean ownTradesObservableSet, userTradeObservableSet; - private Observable ownTradesObservable; - private Observable userTradeObservable; + private volatile boolean ownTradesObservableSet, userTradeObservableSet; + private Observable ownTradesObservable; + private Observable userTradeObservable; - KrakenStreamingTradeService(KrakenStreamingService streamingService, KrakenAccountServiceRaw rawKrakenAcctService) { - this.streamingService = streamingService; - this.rawKrakenAcctService = rawKrakenAcctService; - } - private String getChannelName(KrakenSubscriptionName subscriptionName) { - return subscriptionName.toString(); - } + KrakenStreamingTradeService( + KrakenStreamingService streamingService, KrakenAccountServiceRaw rawKrakenAcctService) { + this.streamingService = streamingService; + this.rawKrakenAcctService = rawKrakenAcctService; + } - private static class KrakenDtoOrderHolder extends HashMap {} - private static class KrakenDtoUserTradeHolder extends HashMap {} + private String getChannelName(KrakenSubscriptionName subscriptionName) { + return subscriptionName.toString(); + } - private KrakenWebsocketToken renewToken() throws IOException { - if (System.currentTimeMillis() >= tokenExpires) { - token = rawKrakenAcctService.getKrakenWebsocketToken(); - tokenExpires = System.currentTimeMillis() + (token.getExpiresInSeconds() * 900L); // 900L instead of a 1000L for 90% - } - return token; - } + private static class KrakenDtoOrderHolder extends HashMap {} - @Override - public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { - try { - if (!ownTradesObservableSet) { - synchronized (this) { - if (!ownTradesObservableSet) { - String channelName = getChannelName(KrakenSubscriptionName.openOrders); - ownTradesObservable = streamingService.subscribeChannel(channelName, renewToken().getToken()) - .filter(JsonNode::isArray) - .filter(Objects::nonNull) - .map(jsonNode -> jsonNode.get(0)) - .map(jsonNode -> - StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, KrakenDtoOrderHolder[].class)) - .flatMapIterable(this::adaptKrakenOrders) - .share(); - - ownTradesObservableSet = true; - } - } - } - return Observable.create( emit -> ownTradesObservable - .filter(order -> currencyPair == null || order.getCurrencyPair() == null || order.getCurrencyPair().compareTo(currencyPair) == 0) - .subscribe(emit::onNext, emit::onError, emit::onComplete)); - - } catch (IOException e) { - return Observable.error(e); - } - } + private static class KrakenDtoUserTradeHolder extends HashMap {} - private Iterable adaptKrakenOrders(KrakenDtoOrderHolder[] dtoList) { - List result = new ArrayList<>(); - - for(KrakenDtoOrderHolder dtoHolder : dtoList) { - for (Map.Entry dtoOrderEntry : dtoHolder.entrySet()) { - String orderId = dtoOrderEntry.getKey(); - KrakenOpenOrder dto = dtoOrderEntry.getValue(); - KrakenOpenOrder.KrakenDtoDescr descr = dto.descr; - - CurrencyPair pair = descr == null ? null : new CurrencyPair(descr.pair); - Order.OrderType side = descr == null ? null : KrakenAdapters.adaptOrderType(KrakenType.fromString(descr.type)); - String orderType = (descr == null || descr.ordertype == null) ? null : descr.ordertype; - - Order.Builder builder; - if ("limit".equals(orderType)) - builder = new LimitOrder.Builder(side,pair) - .limitPrice(descr.price); - else if ("stop".equals(orderType)) - builder = new StopOrder.Builder(side,pair) - .limitPrice(descr.price) - .stopPrice(descr.price2); - - else if ("market".equals(orderType)) - builder = new MarketOrder.Builder(side, pair); - else // this is an order update (not the full order, it may only update one field) - builder = new MarketOrder.Builder(side, pair); - - result.add( - builder - .id(orderId) - .originalAmount(dto.vol) - .cumulativeAmount(dto.vol_exec) - .orderStatus(dto.status == null ? null : KrakenAdapters.adaptOrderStatus(KrakenOrderStatus.fromString(dto.status))) - .timestamp(dto.opentm == null ? null : new Date((long) (dto.opentm * 1000L))) - .fee(dto.fee) - .flags(adaptFlags(dto.oflags)) - .userReference(dto.userref == null ? null : Integer.toString(dto.userref)) - .build() - ); - } - } - return result; + private KrakenWebsocketToken renewToken() throws IOException { + if (System.currentTimeMillis() >= tokenExpires) { + token = rawKrakenAcctService.getKrakenWebsocketToken(); + tokenExpires = + System.currentTimeMillis() + + (token.getExpiresInSeconds() * 900L); // 900L instead of a 1000L for 90% } - - /** - * Comma delimited list of order flags (optional). viqc = volume in quote currency (not available for leveraged orders), - * fcib = prefer fee in base currency, fciq = prefer fee in quote currency, - * nompp = no market price protection, post = post only order (available when ordertype = limit) - */ - private Set adaptFlags(String oflags) { - if (oflags == null) - return new HashSet<>(0); - - String[] arr = oflags.split(","); - Set flags = new HashSet<>(arr.length); - - for (String flag : arr) { - flags.add(KrakenOrderFlags.fromString(flag)); + return token; + } + + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + try { + if (!ownTradesObservableSet) { + synchronized (this) { + if (!ownTradesObservableSet) { + String channelName = getChannelName(KrakenSubscriptionName.openOrders); + ownTradesObservable = + streamingService + .subscribeChannel(channelName, renewToken().getToken()) + .filter(JsonNode::isArray) + .filter(Objects::nonNull) + .map(jsonNode -> jsonNode.get(0)) + .map( + jsonNode -> + StreamingObjectMapperHelper.getObjectMapper() + .treeToValue(jsonNode, KrakenDtoOrderHolder[].class)) + .flatMapIterable(this::adaptKrakenOrders) + .share(); + + ownTradesObservableSet = true; + } } - return flags; + } + return Observable.create( + emit -> + ownTradesObservable + .filter( + order -> + currencyPair == null + || order.getCurrencyPair() == null + || order.getCurrencyPair().compareTo(currencyPair) == 0) + .subscribe(emit::onNext, emit::onError, emit::onComplete)); + + } catch (IOException e) { + return Observable.error(e); } - - @Override - public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { - try { - if (!userTradeObservableSet) { - synchronized (this) { - if (!userTradeObservableSet) { - String channelName = getChannelName(KrakenSubscriptionName.ownTrades); - userTradeObservable = streamingService.subscribeChannel(channelName, renewToken().getToken()) - .filter(JsonNode::isArray) - .filter(Objects::nonNull) - .map(jsonNode -> - jsonNode.get(0)) - .map(jsonNode -> - StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, KrakenDtoUserTradeHolder[].class)) - .flatMapIterable(this::adaptKrakenUserTrade) - .share(); - userTradeObservableSet = true; - } - } - } - return Observable.create( emit -> userTradeObservable - .filter(order -> currencyPair == null || order.getCurrencyPair() == null || order.getCurrencyPair().compareTo(currencyPair) == 0) - .subscribe(emit::onNext, emit::onError, emit::onComplete)); - - } catch (IOException e) { - return Observable.error(e); - } + } + + private Iterable adaptKrakenOrders(KrakenDtoOrderHolder[] dtoList) { + List result = new ArrayList<>(); + + for (KrakenDtoOrderHolder dtoHolder : dtoList) { + for (Map.Entry dtoOrderEntry : dtoHolder.entrySet()) { + String orderId = dtoOrderEntry.getKey(); + KrakenOpenOrder dto = dtoOrderEntry.getValue(); + KrakenOpenOrder.KrakenDtoDescr descr = dto.descr; + + CurrencyPair pair = descr == null ? null : new CurrencyPair(descr.pair); + Order.OrderType side = + descr == null ? null : KrakenAdapters.adaptOrderType(KrakenType.fromString(descr.type)); + String orderType = (descr == null || descr.ordertype == null) ? null : descr.ordertype; + + Order.Builder builder; + if ("limit".equals(orderType)) + builder = new LimitOrder.Builder(side, pair).limitPrice(descr.price); + else if ("stop".equals(orderType)) + builder = + new StopOrder.Builder(side, pair).limitPrice(descr.price).stopPrice(descr.price2); + else if ("market".equals(orderType)) builder = new MarketOrder.Builder(side, pair); + else // this is an order update (not the full order, it may only update one field) + builder = new MarketOrder.Builder(side, pair); + + result.add( + builder + .id(orderId) + .originalAmount(dto.vol) + .cumulativeAmount(dto.vol_exec) + .orderStatus( + dto.status == null + ? null + : KrakenAdapters.adaptOrderStatus(KrakenOrderStatus.fromString(dto.status))) + .timestamp(dto.opentm == null ? null : new Date((long) (dto.opentm * 1000L))) + .fee(dto.fee) + .flags(adaptFlags(dto.oflags)) + .userReference(dto.userref == null ? null : Integer.toString(dto.userref)) + .build()); + } } - private List adaptKrakenUserTrade(KrakenDtoUserTradeHolder[] ownTrades) { - List result = new ArrayList<>(); - - for(KrakenDtoUserTradeHolder holder : ownTrades) { - for (Map.Entry entry : holder.entrySet()) { - String tradeId = entry.getKey(); - KrakenOwnTrade dto = entry.getValue(); - - CurrencyPair currencyPair = new CurrencyPair(dto.pair); - result.add( new UserTrade.Builder() - .id(dto.postxid) - .orderId(dto.ordertxid) - .currencyPair(currencyPair) - .timestamp(dto.time == null ? null : new Date((long)(dto.time*1000L))) - .type(KrakenAdapters.adaptOrderType(KrakenType.fromString(dto.type))) - .price(dto.price) - .feeAmount(dto.fee) - .feeCurrency(currencyPair.base) - .originalAmount(dto.vol) - .build()); - } + return result; + } + + /** + * Comma delimited list of order flags (optional). viqc = volume in quote currency (not available + * for leveraged orders), fcib = prefer fee in base currency, fciq = prefer fee in quote currency, + * nompp = no market price protection, post = post only order (available when ordertype = limit) + */ + private Set adaptFlags(String oflags) { + if (oflags == null) return new HashSet<>(0); + + String[] arr = oflags.split(","); + Set flags = new HashSet<>(arr.length); + + for (String flag : arr) { + flags.add(KrakenOrderFlags.fromString(flag)); + } + return flags; + } + + @Override + public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + try { + if (!userTradeObservableSet) { + synchronized (this) { + if (!userTradeObservableSet) { + String channelName = getChannelName(KrakenSubscriptionName.ownTrades); + userTradeObservable = + streamingService + .subscribeChannel(channelName, renewToken().getToken()) + .filter(JsonNode::isArray) + .filter(Objects::nonNull) + .map(jsonNode -> jsonNode.get(0)) + .map( + jsonNode -> + StreamingObjectMapperHelper.getObjectMapper() + .treeToValue(jsonNode, KrakenDtoUserTradeHolder[].class)) + .flatMapIterable(this::adaptKrakenUserTrade) + .share(); + userTradeObservableSet = true; + } } - return result; + } + return Observable.create( + emit -> + userTradeObservable + .filter( + order -> + currencyPair == null + || order.getCurrencyPair() == null + || order.getCurrencyPair().compareTo(currencyPair) == 0) + .subscribe(emit::onNext, emit::onError, emit::onComplete)); + + } catch (IOException e) { + return Observable.error(e); + } + } + + private List adaptKrakenUserTrade(KrakenDtoUserTradeHolder[] ownTrades) { + List result = new ArrayList<>(); + + for (KrakenDtoUserTradeHolder holder : ownTrades) { + for (Map.Entry entry : holder.entrySet()) { + String tradeId = entry.getKey(); + KrakenOwnTrade dto = entry.getValue(); + + CurrencyPair currencyPair = new CurrencyPair(dto.pair); + result.add( + new UserTrade.Builder() + .id(dto.postxid) + .orderId(dto.ordertxid) + .currencyPair(currencyPair) + .timestamp(dto.time == null ? null : new Date((long) (dto.time * 1000L))) + .type(KrakenAdapters.adaptOrderType(KrakenType.fromString(dto.type))) + .price(dto.price) + .feeAmount(dto.fee) + .feeCurrency(currencyPair.base) + .originalAmount(dto.vol) + .build()); + } } + return result; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenEvent.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenEvent.java index c1495bca8..483f11acc 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenEvent.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenEvent.java @@ -3,27 +3,24 @@ import com.fasterxml.jackson.annotation.JsonProperty; import info.bitrich.xchangestream.kraken.dto.enums.KrakenEventType; -/** - * @author pchertalev - */ +/** @author pchertalev */ public class KrakenEvent { - @JsonProperty("event") - private final KrakenEventType event; + @JsonProperty("event") + private final KrakenEventType event; - @JsonProperty("error") - private String error; + @JsonProperty("error") + private String error; + public KrakenEvent(KrakenEventType event) { + this.event = event; + } - public KrakenEvent(KrakenEventType event) { - this.event = event; - } + public KrakenEventType getEvent() { + return event; + } - public KrakenEventType getEvent() { - return event; - } - - public String getError() { - return error; - } + public String getError() { + return error; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOpenOrder.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOpenOrder.java index c55ff4f8a..8ef9b9fc5 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOpenOrder.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOpenOrder.java @@ -2,105 +2,64 @@ import java.math.BigDecimal; -/** - * https://docs.kraken.com/websockets/#message-openOrders - */ +/** https://docs.kraken.com/websockets/#message-openOrders */ public class KrakenOpenOrder { - /** - * Referral order transaction id that created this order - */ - public String refid; - /** - * user reference id - */ - public Integer userref; - /** - * status of order: - */ - public String status; - /** - * unix timestamp of when order was placed - */ - public Double opentm; - /** - * unix timestamp of order start time (if set) - */ - public Double starttm; - /** - * unix timestamp of order end time (if set) - */ - public Double expiretm; + /** Referral order transaction id that created this order */ + public String refid; + /** user reference id */ + public Integer userref; + /** status of order: */ + public String status; + /** unix timestamp of when order was placed */ + public Double opentm; + /** unix timestamp of order start time (if set) */ + public Double starttm; + /** unix timestamp of order end time (if set) */ + public Double expiretm; - public KrakenDtoDescr descr; + public KrakenDtoDescr descr; - /** - * volume of order (base currency unless viqc set in oflags) - */ - public BigDecimal vol; - /** - * volume executed (base currency unless viqc set in oflags) - */ - public BigDecimal vol_exec; - /** - * total cost (quote currency unless unless viqc set in oflags) - */ - public BigDecimal cost; - /** - * total fee (quote currency) - */ - public BigDecimal fee; - /** - * average price (quote currency unless viqc set in oflags) - */ - public BigDecimal avg_price; - /** - * stop price (quote currency, for trailing stops) - */ - public BigDecimal stopprice; - /** - * triggered limit price (quote currency, when limit based order type triggered) - */ - public BigDecimal limitprice; - /** - * comma delimited list of miscellaneous info: stopped=triggered by stop price, touched=triggered by touch price, liquidation=liquidation, partial=partial fill - */ - public String misc; - /** - * Comma delimited list of order flags (optional). viqc = volume in quote currency (not available for leveraged orders), - * fcib = prefer fee in base currency, fciq = prefer fee in quote currency, nompp = no market price protection, - * post = post only order (available when ordertype = limit) - */ - public String oflags; + /** volume of order (base currency unless viqc set in oflags) */ + public BigDecimal vol; + /** volume executed (base currency unless viqc set in oflags) */ + public BigDecimal vol_exec; + /** total cost (quote currency unless unless viqc set in oflags) */ + public BigDecimal cost; + /** total fee (quote currency) */ + public BigDecimal fee; + /** average price (quote currency unless viqc set in oflags) */ + public BigDecimal avg_price; + /** stop price (quote currency, for trailing stops) */ + public BigDecimal stopprice; + /** triggered limit price (quote currency, when limit based order type triggered) */ + public BigDecimal limitprice; + /** + * comma delimited list of miscellaneous info: stopped=triggered by stop price, touched=triggered + * by touch price, liquidation=liquidation, partial=partial fill + */ + public String misc; + /** + * Comma delimited list of order flags (optional). viqc = volume in quote currency (not available + * for leveraged orders), fcib = prefer fee in base currency, fciq = prefer fee in quote currency, + * nompp = no market price protection, post = post only order (available when ordertype = limit) + */ + public String oflags; - public static class KrakenDtoDescr { - public String pair; - /** - * type of order (buy/sell) - */ - public String type; - /** - * order type - */ - public String ordertype; - /** - * primary price - */ - public BigDecimal price; - /** - * secondary price - */ - public BigDecimal price2; - /** - * amount of leverage - */ - public Double leverage; - /** - * order description - */ - public String order; - /** - * conditional close order description (if conditional close set) - */ - public String close; - } + public static class KrakenDtoDescr { + public String pair; + /** type of order (buy/sell) */ + public String type; + /** order type */ + public String ordertype; + /** primary price */ + public BigDecimal price; + /** secondary price */ + public BigDecimal price2; + /** amount of leverage */ + public Double leverage; + /** order description */ + public String order; + /** conditional close order description (if conditional close set) */ + public String close; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOrderBook.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOrderBook.java index 27ba899d9..796bdbc1a 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOrderBook.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOrderBook.java @@ -4,59 +4,61 @@ import info.bitrich.xchangestream.kraken.dto.enums.KrakenOrderBookMessageType; import org.knowm.xchange.kraken.dto.marketdata.KrakenPublicOrder; -/** - * @author pchertalev - */ +/** @author pchertalev */ public class KrakenOrderBook { - private Integer channelID; - private String channelName; - private String pair; - private KrakenOrderBookMessageType type; - - private KrakenPublicOrder[] ask; - private KrakenPublicOrder[] bid; - - public KrakenOrderBook(Integer channelID, String channelName, String pair, KrakenOrderBookMessageType type, KrakenPublicOrder[] ask, KrakenPublicOrder[] bid) { - this.channelID = channelID; - this.channelName = channelName; - this.pair = pair; - this.type = type; - this.ask = ask; - this.bid = bid; - } + private Integer channelID; + private String channelName; + private String pair; + private KrakenOrderBookMessageType type; - public Integer getChannelID() { - return channelID; - } + private KrakenPublicOrder[] ask; + private KrakenPublicOrder[] bid; - public String getChannelName() { - return channelName; - } + public KrakenOrderBook( + Integer channelID, + String channelName, + String pair, + KrakenOrderBookMessageType type, + KrakenPublicOrder[] ask, + KrakenPublicOrder[] bid) { + this.channelID = channelID; + this.channelName = channelName; + this.pair = pair; + this.type = type; + this.ask = ask; + this.bid = bid; + } - public String getPair() { - return pair; - } + public Integer getChannelID() { + return channelID; + } - public KrakenOrderBookMessageType getType() { - return type; - } + public String getChannelName() { + return channelName; + } - public KrakenPublicOrder[] getAsk() { - return ask; - } + public String getPair() { + return pair; + } - public KrakenPublicOrder[] getBid() { - return bid; - } + public KrakenOrderBookMessageType getType() { + return type; + } + public KrakenPublicOrder[] getAsk() { + return ask; + } - public KrakenOrderBookStorage toKrakenOrderBook(KrakenOrderBookStorage orderbook, int depth) { - if (type == KrakenOrderBookMessageType.UPDATE) { - orderbook.updateOrderBook(this); - return orderbook; - } - return new KrakenOrderBookStorage(this, depth); - } + public KrakenPublicOrder[] getBid() { + return bid; + } + public KrakenOrderBookStorage toKrakenOrderBook(KrakenOrderBookStorage orderbook, int depth) { + if (type == KrakenOrderBookMessageType.UPDATE) { + orderbook.updateOrderBook(this); + return orderbook; + } + return new KrakenOrderBookStorage(this, depth); + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOwnTrade.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOwnTrade.java index ea793f982..cd0386344 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOwnTrade.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenOwnTrade.java @@ -3,43 +3,26 @@ import java.math.BigDecimal; public class KrakenOwnTrade { - /** - * order responsible for execution of trade - */ - public String ordertxid; - /** - * Position trade id - */ - public String postxid; - public String pair; - /** - * unix timestamp of trade - */ - public Double time; - /** - * type of order (buy/sell) - */ - public String type; - public String ordertype; - /** - * average price order was executed at (quote currency) - */ - public BigDecimal price; - /** - * total cost of order (quote currency) - */ - public BigDecimal cost; - /** - * total fee (quote currency) - */ - public BigDecimal fee; - /** - * volume (base currency) - */ - public BigDecimal vol; - /** - * initial margin (quote currency) - */ - public BigDecimal margin; + /** order responsible for execution of trade */ + public String ordertxid; + /** Position trade id */ + public String postxid; + public String pair; + /** unix timestamp of trade */ + public Double time; + /** type of order (buy/sell) */ + public String type; + + public String ordertype; + /** average price order was executed at (quote currency) */ + public BigDecimal price; + /** total cost of order (quote currency) */ + public BigDecimal cost; + /** total fee (quote currency) */ + public BigDecimal fee; + /** volume (base currency) */ + public BigDecimal vol; + /** initial margin (quote currency) */ + public BigDecimal margin; } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionConfig.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionConfig.java index 1588ae1e6..1a66b572e 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionConfig.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionConfig.java @@ -4,72 +4,73 @@ import com.fasterxml.jackson.annotation.JsonProperty; import info.bitrich.xchangestream.kraken.dto.enums.KrakenSubscriptionName; -/** - * @author makarid, pchertalev - **/ +/** @author makarid, pchertalev */ public class KrakenSubscriptionConfig { - /** - * ticker|ohlc|trade|book|spread|ownTrades|openOrders|*, * for all available channels depending on the connected environment - * (ohlc interval value is 1 if all public channels subscribed) - */ - private KrakenSubscriptionName name; - - /** - * Optional - depth associated with book subscription in number of levels each side, default 10. Valid Options are: 10, 25, 100, 500, 1000 - */ - private Integer depth; - - /** - * Optional, base64-encoded authentication token for private-data endpoints. - */ - private String token; - - /** - * Optional - Time interval associated with ohlc subscription in minutes. Default 1. Valid Interval values: 1|5|15|30|60|240|1440|10080|21600 - */ - private Integer interval; - - public KrakenSubscriptionConfig(KrakenSubscriptionName name) { - this(name, null, null); - } - - @JsonCreator - public KrakenSubscriptionConfig(@JsonProperty("name") KrakenSubscriptionName name, @JsonProperty("depth") Integer depth, @JsonProperty("token") String token) { - this.name = name; - this.depth = depth; - this.token = token; - } - - public KrakenSubscriptionName getName() { - return name; - } - - public void setName(KrakenSubscriptionName name) { - this.name = name; - } - - public Integer getDepth() { - return depth; - } - - public void setDepth(Integer depth) { - this.depth = depth; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public Integer getInterval() { - return interval; - } - - public void setInterval(Integer interval) { - this.interval = interval; - } + /** + * ticker|ohlc|trade|book|spread|ownTrades|openOrders|*, * for all available channels depending on + * the connected environment (ohlc interval value is 1 if all public channels subscribed) + */ + private KrakenSubscriptionName name; + + /** + * Optional - depth associated with book subscription in number of levels each side, default 10. + * Valid Options are: 10, 25, 100, 500, 1000 + */ + private Integer depth; + + /** Optional, base64-encoded authentication token for private-data endpoints. */ + private String token; + + /** + * Optional - Time interval associated with ohlc subscription in minutes. Default 1. Valid + * Interval values: 1|5|15|30|60|240|1440|10080|21600 + */ + private Integer interval; + + public KrakenSubscriptionConfig(KrakenSubscriptionName name) { + this(name, null, null); + } + + @JsonCreator + public KrakenSubscriptionConfig( + @JsonProperty("name") KrakenSubscriptionName name, + @JsonProperty("depth") Integer depth, + @JsonProperty("token") String token) { + this.name = name; + this.depth = depth; + this.token = token; + } + + public KrakenSubscriptionName getName() { + return name; + } + + public void setName(KrakenSubscriptionName name) { + this.name = name; + } + + public Integer getDepth() { + return depth; + } + + public void setDepth(Integer depth) { + this.depth = depth; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Integer getInterval() { + return interval; + } + + public void setInterval(Integer interval) { + this.interval = interval; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionMessage.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionMessage.java index b775c189f..64ae79778 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionMessage.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionMessage.java @@ -3,48 +3,45 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import info.bitrich.xchangestream.kraken.dto.enums.KrakenEventType; -import org.knowm.xchange.kraken.dto.account.KrakenWebsocketToken; - import java.util.List; -/** - * @author pchertalev - */ +/** @author pchertalev */ public class KrakenSubscriptionMessage extends KrakenEvent { - /** - * Optional, client originated ID reflected in response message. - */ - @JsonProperty - private final Integer reqid; - - /** - * Optional - Array of currency pairs. Format of each pair is "A/B", where A and B are ISO 4217-A3 for standardized assets and popular unique symbol if not standardized. - */ - @JsonProperty(value = "pair", required = false) - private final List pairs; - - @JsonProperty("subscription") - private final KrakenSubscriptionConfig subscription; - - @JsonCreator - public KrakenSubscriptionMessage(@JsonProperty("reqid") Integer reqid, @JsonProperty("event") KrakenEventType event, - @JsonProperty("pair") List pairs, @JsonProperty("subscription") KrakenSubscriptionConfig subscription) { - super(event); - this.reqid = reqid; - this.pairs = pairs; - this.subscription = subscription; - } - - public List getPairs() { - return pairs; - } - - public KrakenSubscriptionConfig getSubscription() { - return subscription; - } - - public Integer getReqid() { - return reqid; - } + /** Optional, client originated ID reflected in response message. */ + @JsonProperty private final Integer reqid; + + /** + * Optional - Array of currency pairs. Format of each pair is "A/B", where A and B are ISO 4217-A3 + * for standardized assets and popular unique symbol if not standardized. + */ + @JsonProperty(value = "pair", required = false) + private final List pairs; + + @JsonProperty("subscription") + private final KrakenSubscriptionConfig subscription; + + @JsonCreator + public KrakenSubscriptionMessage( + @JsonProperty("reqid") Integer reqid, + @JsonProperty("event") KrakenEventType event, + @JsonProperty("pair") List pairs, + @JsonProperty("subscription") KrakenSubscriptionConfig subscription) { + super(event); + this.reqid = reqid; + this.pairs = pairs; + this.subscription = subscription; + } + + public List getPairs() { + return pairs; + } + + public KrakenSubscriptionConfig getSubscription() { + return subscription; + } + + public Integer getReqid() { + return reqid; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionStatusMessage.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionStatusMessage.java index 2d7b9cbaf..e8b2e59b3 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionStatusMessage.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSubscriptionStatusMessage.java @@ -5,53 +5,55 @@ import info.bitrich.xchangestream.kraken.dto.enums.KrakenEventType; import info.bitrich.xchangestream.kraken.dto.enums.KrakenSubscriptionStatus; -/** - * @author pchertalev - */ +/** @author pchertalev */ public class KrakenSubscriptionStatusMessage extends KrakenEvent { - private final Integer channelID; - private final Integer reqid; - private final KrakenSubscriptionStatus status; - private final String pair; - private final KrakenSubscriptionConfig krakenSubscriptionConfig; - private final String errorMessage; - - @JsonCreator - public KrakenSubscriptionStatusMessage(@JsonProperty("event") KrakenEventType event, @JsonProperty("channelID") Integer channelID, - @JsonProperty("reqid") Integer reqid, @JsonProperty("status") KrakenSubscriptionStatus status, - @JsonProperty("pair") String pair, @JsonProperty("subscription") KrakenSubscriptionConfig krakenSubscriptionConfig, - @JsonProperty("errorMessage") String errorMessage) { - super(event); - this.channelID = channelID; - this.reqid = reqid; - this.status = status; - this.pair = pair; - this.krakenSubscriptionConfig = krakenSubscriptionConfig; - this.errorMessage = errorMessage; - } - - public Integer getChannelID() { - return channelID; - } - - public KrakenSubscriptionStatus getStatus() { - return status; - } - - public String getPair() { - return pair; - } - - public KrakenSubscriptionConfig getKrakenSubscriptionConfig() { - return krakenSubscriptionConfig; - } - - public Integer getReqid() { - return reqid; - } - - public String getErrorMessage() { - return errorMessage; - } + private final Integer channelID; + private final Integer reqid; + private final KrakenSubscriptionStatus status; + private final String pair; + private final KrakenSubscriptionConfig krakenSubscriptionConfig; + private final String errorMessage; + + @JsonCreator + public KrakenSubscriptionStatusMessage( + @JsonProperty("event") KrakenEventType event, + @JsonProperty("channelID") Integer channelID, + @JsonProperty("reqid") Integer reqid, + @JsonProperty("status") KrakenSubscriptionStatus status, + @JsonProperty("pair") String pair, + @JsonProperty("subscription") KrakenSubscriptionConfig krakenSubscriptionConfig, + @JsonProperty("errorMessage") String errorMessage) { + super(event); + this.channelID = channelID; + this.reqid = reqid; + this.status = status; + this.pair = pair; + this.krakenSubscriptionConfig = krakenSubscriptionConfig; + this.errorMessage = errorMessage; + } + + public Integer getChannelID() { + return channelID; + } + + public KrakenSubscriptionStatus getStatus() { + return status; + } + + public String getPair() { + return pair; + } + + public KrakenSubscriptionConfig getKrakenSubscriptionConfig() { + return krakenSubscriptionConfig; + } + + public Integer getReqid() { + return reqid; + } + + public String getErrorMessage() { + return errorMessage; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSystemStatus.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSystemStatus.java index 62fa47bfe..f3b8ed68c 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSystemStatus.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/KrakenSystemStatus.java @@ -4,46 +4,54 @@ import com.fasterxml.jackson.annotation.JsonProperty; import info.bitrich.xchangestream.kraken.dto.enums.KrakenEventType; -/** - * @author pchertalev - */ +/** @author pchertalev */ public class KrakenSystemStatus extends KrakenEvent { - private final String connectionID; - /** - * online|maintenance|(custom status tbd) - */ - private final String status; - private final String version; - - @JsonCreator - public KrakenSystemStatus(@JsonProperty("event") KrakenEventType event, @JsonProperty("connectionID") String connectionID, - @JsonProperty("status") String status, @JsonProperty("version") String version) { - super(event); - this.connectionID = connectionID; - this.status = status; - this.version = version; - } - - public String getConnectionID() { - return connectionID; - } - - public String getStatus() { - return status; - } - - public String getVersion() { - return version; - } - - @Override - public String toString() { - return "KrakenSystemStatus{" + - "connectionID='" + connectionID + '\'' + - ", event='" + super.getEvent() + '\'' + - ", status='" + status + '\'' + - ", version='" + version + '\'' + - '}'; - } + private final String connectionID; + /** online|maintenance|(custom status tbd) */ + private final String status; + + private final String version; + + @JsonCreator + public KrakenSystemStatus( + @JsonProperty("event") KrakenEventType event, + @JsonProperty("connectionID") String connectionID, + @JsonProperty("status") String status, + @JsonProperty("version") String version) { + super(event); + this.connectionID = connectionID; + this.status = status; + this.version = version; + } + + public String getConnectionID() { + return connectionID; + } + + public String getStatus() { + return status; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return "KrakenSystemStatus{" + + "connectionID='" + + connectionID + + '\'' + + ", event='" + + super.getEvent() + + '\'' + + ", status='" + + status + + '\'' + + ", version='" + + version + + '\'' + + '}'; + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenEventType.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenEventType.java index eabb92256..3951fa254 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenEventType.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenEventType.java @@ -1,22 +1,22 @@ package info.bitrich.xchangestream.kraken.dto.enums; -import org.apache.commons.lang3.StringUtils; - import java.util.Arrays; +import org.apache.commons.lang3.StringUtils; public enum KrakenEventType { - heartbeat, - subscribe, - unsubscribe, - systemStatus, - subscriptionStatus, - ping, pong, - error; + heartbeat, + subscribe, + unsubscribe, + systemStatus, + subscriptionStatus, + ping, + pong, + error; - public static KrakenEventType getEvent(String event) { - return Arrays.stream(KrakenEventType.values()) - .filter(e -> StringUtils.equalsIgnoreCase(event, e.name())) - .findFirst() - .orElse(null); - } + public static KrakenEventType getEvent(String event) { + return Arrays.stream(KrakenEventType.values()) + .filter(e -> StringUtils.equalsIgnoreCase(event, e.name())) + .findFirst() + .orElse(null); + } } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenOrderBookMessageType.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenOrderBookMessageType.java index 23c34aaae..217058669 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenOrderBookMessageType.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenOrderBookMessageType.java @@ -1,6 +1,6 @@ package info.bitrich.xchangestream.kraken.dto.enums; public enum KrakenOrderBookMessageType { - SNAPSHOT, - UPDATE + SNAPSHOT, + UPDATE } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionName.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionName.java index a0460cb4f..0c604157f 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionName.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionName.java @@ -1,11 +1,11 @@ package info.bitrich.xchangestream.kraken.dto.enums; public enum KrakenSubscriptionName { - ticker, - ohlc, - trade, - book, - spread, - ownTrades, - openOrders + ticker, + ohlc, + trade, + book, + spread, + ownTrades, + openOrders } diff --git a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionStatus.java b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionStatus.java index 213db1926..01ccb0ad9 100644 --- a/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionStatus.java +++ b/xchange-stream-kraken/src/main/java/info/bitrich/xchangestream/kraken/dto/enums/KrakenSubscriptionStatus.java @@ -1,7 +1,7 @@ package info.bitrich.xchangestream.kraken.dto.enums; public enum KrakenSubscriptionStatus { - subscribed, - unsubscribed, - error + subscribed, + unsubscribed, + error } diff --git a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualExample.java b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualExample.java index ac5cefb76..c30058404 100644 --- a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualExample.java +++ b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualExample.java @@ -3,54 +3,91 @@ import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingExchangeFactory; import io.reactivex.disposables.Disposable; +import java.util.concurrent.TimeUnit; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.TimeUnit; - public class KrakenManualExample { - private static final Logger LOG = LoggerFactory.getLogger(KrakenManualExample.class); - - public static void main(String[] args) throws InterruptedException { - - ExchangeSpecification exchangeSpecification = new ExchangeSpecification(KrakenStreamingExchange.class); - - StreamingExchange krakenExchange = StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); - krakenExchange.connect().blockingAwait(); - - CurrencyPair bchUsdt = new CurrencyPair(Currency.BCH, Currency.getInstance("USD")); - Disposable btcEurOrderBookDis = krakenExchange.getStreamingMarketDataService().getOrderBook(bchUsdt, 100).subscribe(s -> { - LOG.info("Received order book {}({},{}) ask[0] = {} bid[0] = {}", bchUsdt, s.getAsks().size(), s.getBids().size(), s.getAsks().get(0), s.getBids().get(0)); - }, throwable -> { - LOG.error("Order book FAILED {}", throwable.getMessage(), throwable); - }); - Disposable btcUsdOrderBookDis = krakenExchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BCH_EUR, 10).subscribe(s -> { - LOG.info("Received order book {}({},{}) ask[0] = {} bid[0] = {}", CurrencyPair.BCH_EUR, s.getAsks().size(), s.getBids().size(), s.getAsks().get(0), s.getBids().get(0)); - }, throwable -> { - LOG.error("Order book FAILED {}", throwable.getMessage(), throwable); - }); - Disposable tickerDis = krakenExchange.getStreamingMarketDataService().getTicker(CurrencyPair.LTC_USD).subscribe(s -> { - LOG.info("Received {}", s); - }, throwable -> { - LOG.error("Fail to get ticker {}", throwable.getMessage(), throwable); - }); - - Disposable tradeDis = krakenExchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(s -> { - LOG.info("Received {}", s); - }, throwable -> { - LOG.error("Fail to get trade {}", throwable.getMessage(), throwable); - }); - TimeUnit.SECONDS.sleep(5); - - btcEurOrderBookDis.dispose(); - btcUsdOrderBookDis.dispose(); - tickerDis.dispose(); - tradeDis.dispose(); - - krakenExchange.disconnect().subscribe(() -> LOG.info("Disconnected")); - } + private static final Logger LOG = LoggerFactory.getLogger(KrakenManualExample.class); + + public static void main(String[] args) throws InterruptedException { + + ExchangeSpecification exchangeSpecification = + new ExchangeSpecification(KrakenStreamingExchange.class); + + StreamingExchange krakenExchange = + StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + krakenExchange.connect().blockingAwait(); + + CurrencyPair bchUsdt = new CurrencyPair(Currency.BCH, Currency.getInstance("USD")); + Disposable btcEurOrderBookDis = + krakenExchange + .getStreamingMarketDataService() + .getOrderBook(bchUsdt, 100) + .subscribe( + s -> { + LOG.info( + "Received order book {}({},{}) ask[0] = {} bid[0] = {}", + bchUsdt, + s.getAsks().size(), + s.getBids().size(), + s.getAsks().get(0), + s.getBids().get(0)); + }, + throwable -> { + LOG.error("Order book FAILED {}", throwable.getMessage(), throwable); + }); + Disposable btcUsdOrderBookDis = + krakenExchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BCH_EUR, 10) + .subscribe( + s -> { + LOG.info( + "Received order book {}({},{}) ask[0] = {} bid[0] = {}", + CurrencyPair.BCH_EUR, + s.getAsks().size(), + s.getBids().size(), + s.getAsks().get(0), + s.getBids().get(0)); + }, + throwable -> { + LOG.error("Order book FAILED {}", throwable.getMessage(), throwable); + }); + Disposable tickerDis = + krakenExchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.LTC_USD) + .subscribe( + s -> { + LOG.info("Received {}", s); + }, + throwable -> { + LOG.error("Fail to get ticker {}", throwable.getMessage(), throwable); + }); + + Disposable tradeDis = + krakenExchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + s -> { + LOG.info("Received {}", s); + }, + throwable -> { + LOG.error("Fail to get trade {}", throwable.getMessage(), throwable); + }); + TimeUnit.SECONDS.sleep(5); + + btcEurOrderBookDis.dispose(); + btcUsdOrderBookDis.dispose(); + tickerDis.dispose(); + tradeDis.dispose(); + + krakenExchange.disconnect().subscribe(() -> LOG.info("Disconnected")); + } } diff --git a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualPrivateTest.java b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualPrivateTest.java index 4ae95de42..bf0b3d82c 100644 --- a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualPrivateTest.java +++ b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenManualPrivateTest.java @@ -2,38 +2,48 @@ import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import java.util.concurrent.TimeUnit; import org.knowm.xchange.ExchangeSpecification; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.TimeUnit; - public class KrakenManualPrivateTest { - private static final Logger LOG = LoggerFactory.getLogger(KrakenManualExample.class); - - public static void main(String[] args) throws InterruptedException { - ExchangeSpecification exchangeSpecification = new ExchangeSpecification(KrakenStreamingExchange.class); - exchangeSpecification.setApiKey(args[0]); - exchangeSpecification.setSecretKey(args[1]); - exchangeSpecification.setUserName(args[2]); - - StreamingExchange krakenExchange = StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); - krakenExchange.connect().blockingAwait(); - - krakenExchange.getStreamingTradeService().getUserTrades(null).subscribe(b -> { - LOG.info("Received userTrade {}", b); - }, throwable -> { - LOG.error("UserTrades FAILED {}", throwable.getMessage(), throwable); - }); - krakenExchange.getStreamingTradeService().getOrderChanges(null).subscribe(b -> { - LOG.info("Received orderChange {}", b); - }, throwable -> { - LOG.error("OrderChange FAILED {}", throwable.getMessage(), throwable); - }); - - TimeUnit.SECONDS.sleep(120); - - krakenExchange.disconnect().subscribe(() -> LOG.info("Disconnected")); - } - + private static final Logger LOG = LoggerFactory.getLogger(KrakenManualExample.class); + + public static void main(String[] args) throws InterruptedException { + ExchangeSpecification exchangeSpecification = + new ExchangeSpecification(KrakenStreamingExchange.class); + exchangeSpecification.setApiKey(args[0]); + exchangeSpecification.setSecretKey(args[1]); + exchangeSpecification.setUserName(args[2]); + + StreamingExchange krakenExchange = + StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + krakenExchange.connect().blockingAwait(); + + krakenExchange + .getStreamingTradeService() + .getUserTrades(null) + .subscribe( + b -> { + LOG.info("Received userTrade {}", b); + }, + throwable -> { + LOG.error("UserTrades FAILED {}", throwable.getMessage(), throwable); + }); + krakenExchange + .getStreamingTradeService() + .getOrderChanges(null) + .subscribe( + b -> { + LOG.info("Received orderChange {}", b); + }, + throwable -> { + LOG.error("OrderChange FAILED {}", throwable.getMessage(), throwable); + }); + + TimeUnit.SECONDS.sleep(120); + + krakenExchange.disconnect().subscribe(() -> LOG.info("Disconnected")); + } } diff --git a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookParseTest.java b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookParseTest.java index a5133c925..442ea6422 100644 --- a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookParseTest.java +++ b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookParseTest.java @@ -4,55 +4,56 @@ import info.bitrich.xchangestream.kraken.dto.KrakenOrderBook; import info.bitrich.xchangestream.kraken.dto.enums.KrakenOrderBookMessageType; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; -import org.junit.Assert; -import org.junit.Test; -import org.knowm.xchange.kraken.dto.marketdata.KrakenPublicOrder; - import java.io.IOException; import java.math.BigDecimal; import java.util.List; +import org.junit.Assert; +import org.junit.Test; +import org.knowm.xchange.kraken.dto.marketdata.KrakenPublicOrder; public class KrakenOrderBookParseTest { - @Test - public void testOrderBookSnapshot() throws IOException { - JsonNode jsonNode = StreamingObjectMapperHelper.getObjectMapper().readTree( - this.getClass().getResource("/orderBookMessageSnapshot.json").openStream()); - Assert.assertNotNull(jsonNode); - List list = StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, List.class); - KrakenOrderBook krakenOrderBook = KrakenOrderBookUtils.parse(list); - Assert.assertNotNull(krakenOrderBook); - Assert.assertEquals(KrakenOrderBookMessageType.SNAPSHOT, krakenOrderBook.getType()); - Assert.assertNotNull(krakenOrderBook.getAsk()); - Assert.assertEquals(25, krakenOrderBook.getAsk().length); - Assert.assertNotNull(krakenOrderBook.getBid()); - Assert.assertEquals(25, krakenOrderBook.getBid().length); - KrakenPublicOrder firstAsk = krakenOrderBook.getAsk()[0]; - Assert.assertEquals(0, new BigDecimal("8692").compareTo(firstAsk.getPrice())); - Assert.assertEquals(0, new BigDecimal("2.01122372").compareTo(firstAsk.getVolume())); - Assert.assertEquals(1561120269939L, firstAsk.getTimestamp()); - KrakenPublicOrder firstBid = krakenOrderBook.getBid()[0]; - Assert.assertEquals(0, new BigDecimal("8691.9").compareTo(firstBid.getPrice())); - Assert.assertEquals(0, new BigDecimal("1.45612927").compareTo(firstBid.getVolume())); - Assert.assertEquals(1561120266647L, firstBid.getTimestamp()); - } + @Test + public void testOrderBookSnapshot() throws IOException { + JsonNode jsonNode = + StreamingObjectMapperHelper.getObjectMapper() + .readTree(this.getClass().getResource("/orderBookMessageSnapshot.json").openStream()); + Assert.assertNotNull(jsonNode); + List list = StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, List.class); + KrakenOrderBook krakenOrderBook = KrakenOrderBookUtils.parse(list); + Assert.assertNotNull(krakenOrderBook); + Assert.assertEquals(KrakenOrderBookMessageType.SNAPSHOT, krakenOrderBook.getType()); + Assert.assertNotNull(krakenOrderBook.getAsk()); + Assert.assertEquals(25, krakenOrderBook.getAsk().length); + Assert.assertNotNull(krakenOrderBook.getBid()); + Assert.assertEquals(25, krakenOrderBook.getBid().length); + KrakenPublicOrder firstAsk = krakenOrderBook.getAsk()[0]; + Assert.assertEquals(0, new BigDecimal("8692").compareTo(firstAsk.getPrice())); + Assert.assertEquals(0, new BigDecimal("2.01122372").compareTo(firstAsk.getVolume())); + Assert.assertEquals(1561120269939L, firstAsk.getTimestamp()); + KrakenPublicOrder firstBid = krakenOrderBook.getBid()[0]; + Assert.assertEquals(0, new BigDecimal("8691.9").compareTo(firstBid.getPrice())); + Assert.assertEquals(0, new BigDecimal("1.45612927").compareTo(firstBid.getVolume())); + Assert.assertEquals(1561120266647L, firstBid.getTimestamp()); + } - @Test - public void testOrderBookUpdate() throws IOException { - JsonNode jsonNode = StreamingObjectMapperHelper.getObjectMapper().readTree( - this.getClass().getResource("/orderBookMessageUpdate.json").openStream()); - Assert.assertNotNull(jsonNode); - List list = StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, List.class); - KrakenOrderBook krakenOrderBook = KrakenOrderBookUtils.parse(list); - Assert.assertNotNull(krakenOrderBook); - Assert.assertEquals(KrakenOrderBookMessageType.UPDATE, krakenOrderBook.getType()); - Assert.assertNotNull(krakenOrderBook.getAsk()); - Assert.assertEquals(2, krakenOrderBook.getAsk().length); - Assert.assertNotNull(krakenOrderBook.getBid()); - Assert.assertEquals(0, krakenOrderBook.getBid().length); - KrakenPublicOrder firstAsk = krakenOrderBook.getAsk()[0]; - Assert.assertEquals(0, new BigDecimal("9618.6").compareTo(firstAsk.getPrice())); - Assert.assertEquals(0, BigDecimal.ZERO.compareTo(firstAsk.getVolume())); - Assert.assertEquals(1561372908562L, firstAsk.getTimestamp()); - } + @Test + public void testOrderBookUpdate() throws IOException { + JsonNode jsonNode = + StreamingObjectMapperHelper.getObjectMapper() + .readTree(this.getClass().getResource("/orderBookMessageUpdate.json").openStream()); + Assert.assertNotNull(jsonNode); + List list = StreamingObjectMapperHelper.getObjectMapper().treeToValue(jsonNode, List.class); + KrakenOrderBook krakenOrderBook = KrakenOrderBookUtils.parse(list); + Assert.assertNotNull(krakenOrderBook); + Assert.assertEquals(KrakenOrderBookMessageType.UPDATE, krakenOrderBook.getType()); + Assert.assertNotNull(krakenOrderBook.getAsk()); + Assert.assertEquals(2, krakenOrderBook.getAsk().length); + Assert.assertNotNull(krakenOrderBook.getBid()); + Assert.assertEquals(0, krakenOrderBook.getBid().length); + KrakenPublicOrder firstAsk = krakenOrderBook.getAsk()[0]; + Assert.assertEquals(0, new BigDecimal("9618.6").compareTo(firstAsk.getPrice())); + Assert.assertEquals(0, BigDecimal.ZERO.compareTo(firstAsk.getVolume())); + Assert.assertEquals(1561372908562L, firstAsk.getTimestamp()); + } } diff --git a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookSizeTest.java b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookSizeTest.java index 7015f01d0..3cac39f92 100644 --- a/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookSizeTest.java +++ b/xchange-stream-kraken/src/test/java/info/bitrich/xchangestream/kraken/KrakenOrderBookSizeTest.java @@ -1,21 +1,43 @@ package info.bitrich.xchangestream.kraken; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.InvocationTargetException; import org.apache.commons.lang3.reflect.MethodUtils; import org.junit.Assert; import org.junit.Test; -import java.lang.reflect.InvocationTargetException; - -import static org.mockito.Mockito.mock; - public class KrakenOrderBookSizeTest { - @Test - public void test() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - KrakenStreamingMarketDataService krakenStreamingMarketDataService = new KrakenStreamingMarketDataService(mock(KrakenStreamingService.class)); - Assert.assertEquals(25, MethodUtils.invokeMethod(krakenStreamingMarketDataService, true, "parseOrderBookSize", new Object [] { new Object [] { "22" } })); - Assert.assertEquals(25, MethodUtils.invokeMethod(krakenStreamingMarketDataService, true, "parseOrderBookSize", new Object [] { null })); - Assert.assertEquals(25, MethodUtils.invokeMethod(krakenStreamingMarketDataService, true, "parseOrderBookSize", new Object [] { new Object [] { 22 } })); - Assert.assertEquals(100, MethodUtils.invokeMethod(krakenStreamingMarketDataService, true, "parseOrderBookSize", new Object [] { new Object [] { 100 } })); - } + @Test + public void test() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + KrakenStreamingMarketDataService krakenStreamingMarketDataService = + new KrakenStreamingMarketDataService(mock(KrakenStreamingService.class)); + Assert.assertEquals( + 25, + MethodUtils.invokeMethod( + krakenStreamingMarketDataService, + true, + "parseOrderBookSize", + new Object[] {new Object[] {"22"}})); + Assert.assertEquals( + 25, + MethodUtils.invokeMethod( + krakenStreamingMarketDataService, true, "parseOrderBookSize", new Object[] {null})); + Assert.assertEquals( + 25, + MethodUtils.invokeMethod( + krakenStreamingMarketDataService, + true, + "parseOrderBookSize", + new Object[] {new Object[] {22}})); + Assert.assertEquals( + 100, + MethodUtils.invokeMethod( + krakenStreamingMarketDataService, + true, + "parseOrderBookSize", + new Object[] {new Object[] {100}})); + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoAdapter.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoAdapter.java index 6259dd6a5..bff1bd7ea 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoAdapter.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoAdapter.java @@ -1,127 +1,143 @@ package info.bitrich.xchangestream.lgo; +import static java.util.stream.Collectors.*; + import info.bitrich.xchangestream.lgo.domain.*; import info.bitrich.xchangestream.lgo.dto.*; -import org.knowm.xchange.currency.Currency; +import java.math.BigDecimal; +import java.util.*; import org.knowm.xchange.currency.*; +import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.dto.trade.*; -import java.math.BigDecimal; -import java.util.*; - -import static java.util.stream.Collectors.*; - public class LgoAdapter { - static String channelName(String name, CurrencyPair currencyPair) { - return name + "-" + currencyPair.base.toString() + "-" + currencyPair.counter.toString(); - } + static String channelName(String name, CurrencyPair currencyPair) { + return name + "-" + currencyPair.base.toString() + "-" + currencyPair.counter.toString(); + } - public static Trade adaptTrade(CurrencyPair currencyPair, LgoTrade lgoTrade) { - return new Trade.Builder() - .type(parseTradeType(lgoTrade)) - .originalAmount(lgoTrade.getQuantity()) - .currencyPair(currencyPair) - .price(lgoTrade.getPrice()) - .timestamp(lgoTrade.getCreationTime()) - .id(lgoTrade.getId()) - .build(); - } + public static Trade adaptTrade(CurrencyPair currencyPair, LgoTrade lgoTrade) { + return new Trade.Builder() + .type(parseTradeType(lgoTrade)) + .originalAmount(lgoTrade.getQuantity()) + .currencyPair(currencyPair) + .price(lgoTrade.getPrice()) + .timestamp(lgoTrade.getCreationTime()) + .id(lgoTrade.getId()) + .build(); + } - static List adaptBalances(List> data) { - return data.stream().map(balance -> { - Currency currency = Currency.getInstance(balance.get(0)); - BigDecimal available = new BigDecimal(balance.get(1)); - BigDecimal escrow = new BigDecimal(balance.get(2)); - BigDecimal total = available.add(escrow); - return new Balance.Builder() - .currency(currency) - .total(total) - .available(available) - .frozen(escrow) - .build(); - }).collect(toList()); - } + static List adaptBalances(List> data) { + return data.stream() + .map( + balance -> { + Currency currency = Currency.getInstance(balance.get(0)); + BigDecimal available = new BigDecimal(balance.get(1)); + BigDecimal escrow = new BigDecimal(balance.get(2)); + BigDecimal total = available.add(escrow); + return new Balance.Builder() + .currency(currency) + .total(total) + .available(available) + .frozen(escrow) + .build(); + }) + .collect(toList()); + } - private static Order.OrderType parseTradeType(LgoTrade lgoTrade) { - //the XChange API requires the taker order type whereas the LGO API gives the maker order type - return lgoTrade.getSide().equals("B") ? Order.OrderType.ASK : Order.OrderType.BID; - } + private static Order.OrderType parseTradeType(LgoTrade lgoTrade) { + // the XChange API requires the taker order type whereas the LGO API gives the maker order type + return lgoTrade.getSide().equals("B") ? Order.OrderType.ASK : Order.OrderType.BID; + } - private static Order.OrderType parseOrderType(String side) { - return side.equals("B") ? Order.OrderType.BID : Order.OrderType.ASK; - } + private static Order.OrderType parseOrderType(String side) { + return side.equals("B") ? Order.OrderType.BID : Order.OrderType.ASK; + } - static Collection adaptOrdersSnapshot(List orderEvents, CurrencyPair currencyPair) { - return orderEvents.stream() - .map(orderEvent -> { - Order.OrderStatus status = orderEvent.getQuantity().equals(orderEvent.getRemainingQuantity()) ? Order.OrderStatus.NEW : Order.OrderStatus.PARTIALLY_FILLED; - return new LimitOrder.Builder(parseOrderType(orderEvent.getSide()), currencyPair) - .id(orderEvent.getOrderId()) - .userReference(null) - .originalAmount(orderEvent.getQuantity()) - .remainingAmount(orderEvent.getRemainingQuantity()) - .limitPrice(orderEvent.getPrice()) - .orderStatus(status) - .timestamp(orderEvent.getOrderCreationTime()) - .build(); - }).collect(toList()); - } + static Collection adaptOrdersSnapshot( + List orderEvents, CurrencyPair currencyPair) { + return orderEvents.stream() + .map( + orderEvent -> { + Order.OrderStatus status = + orderEvent.getQuantity().equals(orderEvent.getRemainingQuantity()) + ? Order.OrderStatus.NEW + : Order.OrderStatus.PARTIALLY_FILLED; + return new LimitOrder.Builder(parseOrderType(orderEvent.getSide()), currencyPair) + .id(orderEvent.getOrderId()) + .userReference(null) + .originalAmount(orderEvent.getQuantity()) + .remainingAmount(orderEvent.getRemainingQuantity()) + .limitPrice(orderEvent.getPrice()) + .orderStatus(status) + .timestamp(orderEvent.getOrderCreationTime()) + .build(); + }) + .collect(toList()); + } - public static Order adaptPendingOrder(LgoPendingOrderEvent orderEvent, CurrencyPair currencyPair) { - if (orderEvent.getOrderType().equals("L")) { - return adaptPendingLimitOrder(orderEvent, currencyPair); - } - return adaptPendingMarketOrder(orderEvent, currencyPair); + public static Order adaptPendingOrder( + LgoPendingOrderEvent orderEvent, CurrencyPair currencyPair) { + if (orderEvent.getOrderType().equals("L")) { + return adaptPendingLimitOrder(orderEvent, currencyPair); } + return adaptPendingMarketOrder(orderEvent, currencyPair); + } - private static LimitOrder adaptPendingLimitOrder(LgoPendingOrderEvent orderEvent, CurrencyPair currencyPair) { - return new LimitOrder.Builder(orderEvent.getSide(), currencyPair) - .id(orderEvent.getOrderId()) - .originalAmount(orderEvent.getInitialAmount()) - .remainingAmount(orderEvent.getInitialAmount()) - .limitPrice(orderEvent.getLimitPrice()) - .orderStatus(Order.OrderStatus.PENDING_NEW) - .timestamp(orderEvent.getTime()) - .build(); - } + private static LimitOrder adaptPendingLimitOrder( + LgoPendingOrderEvent orderEvent, CurrencyPair currencyPair) { + return new LimitOrder.Builder(orderEvent.getSide(), currencyPair) + .id(orderEvent.getOrderId()) + .originalAmount(orderEvent.getInitialAmount()) + .remainingAmount(orderEvent.getInitialAmount()) + .limitPrice(orderEvent.getLimitPrice()) + .orderStatus(Order.OrderStatus.PENDING_NEW) + .timestamp(orderEvent.getTime()) + .build(); + } - private static MarketOrder adaptPendingMarketOrder(LgoPendingOrderEvent orderEvent, CurrencyPair currencyPair) { - return new MarketOrder.Builder(orderEvent.getSide(), currencyPair) - .id(orderEvent.getOrderId()) - .originalAmount(orderEvent.getInitialAmount()) - .cumulativeAmount(BigDecimal.ZERO) - .orderStatus(Order.OrderStatus.PENDING_NEW) - .timestamp(orderEvent.getTime()) - .build(); - } + private static MarketOrder adaptPendingMarketOrder( + LgoPendingOrderEvent orderEvent, CurrencyPair currencyPair) { + return new MarketOrder.Builder(orderEvent.getSide(), currencyPair) + .id(orderEvent.getOrderId()) + .originalAmount(orderEvent.getInitialAmount()) + .cumulativeAmount(BigDecimal.ZERO) + .orderStatus(Order.OrderStatus.PENDING_NEW) + .timestamp(orderEvent.getTime()) + .build(); + } - static List adaptOrderEvent(List data, Long batchId, List openOrders) { - data.forEach(e -> { - e.setBatchId(batchId); - if ("match".equals(e.getType())) { - LgoMatchOrderEvent matchEvent = (LgoMatchOrderEvent) e; - Optional matchedOrder = openOrders.stream().filter(order -> order.getId().equals(e.getOrderId())).findFirst(); - matchEvent.setOrderType(matchedOrder.map(Order::getType).orElse(null)); - } + static List adaptOrderEvent( + List data, Long batchId, List openOrders) { + data.forEach( + e -> { + e.setBatchId(batchId); + if ("match".equals(e.getType())) { + LgoMatchOrderEvent matchEvent = (LgoMatchOrderEvent) e; + Optional matchedOrder = + openOrders.stream() + .filter(order -> order.getId().equals(e.getOrderId())) + .findFirst(); + matchEvent.setOrderType(matchedOrder.map(Order::getType).orElse(null)); + } }); - return data; - } + return data; + } - static UserTrade adaptUserTrade(CurrencyPair currencyPair, LgoMatchOrderEvent event) { - return new UserTrade.Builder() - .type(event.getOrderType()) - .originalAmount(event.getFilledQuantity()) - .currencyPair(currencyPair) - .price(event.getTradePrice()) - .timestamp(event.getTime()) - .id(event.getTradeId()) - .orderId(event.getOrderId()) - .feeAmount(event.getFees()) - .feeCurrency(currencyPair.counter) - .build(); - } + static UserTrade adaptUserTrade(CurrencyPair currencyPair, LgoMatchOrderEvent event) { + return new UserTrade.Builder() + .type(event.getOrderType()) + .originalAmount(event.getFilledQuantity()) + .currencyPair(currencyPair) + .price(event.getTradePrice()) + .timestamp(event.getTime()) + .id(event.getTradeId()) + .orderId(event.getOrderId()) + .feeAmount(event.getFees()) + .feeCurrency(currencyPair.counter) + .build(); + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoLevel2BatchSubscription.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoLevel2BatchSubscription.java index 31f1eeb85..f7447be6a 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoLevel2BatchSubscription.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoLevel2BatchSubscription.java @@ -5,65 +5,68 @@ import info.bitrich.xchangestream.lgo.dto.LgoLevel2Update; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.io.IOException; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; import org.slf4j.*; -import java.io.IOException; - class LgoLevel2BatchSubscription { - private final LgoStreamingService service; - private final Observable subscription; - private static final Logger LOGGER = LoggerFactory.getLogger(LgoLevel2BatchSubscription.class); - private CurrencyPair currencyPair; + private final LgoStreamingService service; + private final Observable subscription; + private static final Logger LOGGER = LoggerFactory.getLogger(LgoLevel2BatchSubscription.class); + private CurrencyPair currencyPair; - static LgoLevel2BatchSubscription create(LgoStreamingService service, CurrencyPair currencyPair) { - return new LgoLevel2BatchSubscription(service, currencyPair); - } + static LgoLevel2BatchSubscription create(LgoStreamingService service, CurrencyPair currencyPair) { + return new LgoLevel2BatchSubscription(service, currencyPair); + } - private LgoLevel2BatchSubscription(LgoStreamingService service, CurrencyPair currencyPair) { - this.service = service; - this.currencyPair = currencyPair; - subscription = createSubscription(); - } + private LgoLevel2BatchSubscription(LgoStreamingService service, CurrencyPair currencyPair) { + this.service = service; + this.currencyPair = currencyPair; + subscription = createSubscription(); + } - Observable getSubscription() { - return subscription; - } + Observable getSubscription() { + return subscription; + } - private Observable createSubscription() { - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - return service - .subscribeChannel(LgoAdapter.channelName("level2", currencyPair)) - .map(s -> mapper.readValue(s.toString(), LgoLevel2Update.class)) - .scan(new LgoGroupedLevel2Update(), (acc, s) -> { - if (s.getType().equals("snapshot")) { - acc.applySnapshot(s.getBatchId(), currencyPair, s.getData()); - return acc; - } - if (acc.getLastBatchId() + 1 != s.getBatchId()) { - LOGGER.warn("Wrong batch id. Expected {} got {}.", acc.getLastBatchId() + 1, s.getBatchId()); - acc.markDirty(); - resubscribe(); - return acc; - } - acc.applyUpdate(s.getBatchId(), currencyPair, s.getData()); - return acc; - }) - .filter(LgoGroupedLevel2Update::isValid) - .map(LgoGroupedLevel2Update::orderBook) - .share(); - } + private Observable createSubscription() { + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + return service + .subscribeChannel(LgoAdapter.channelName("level2", currencyPair)) + .map(s -> mapper.readValue(s.toString(), LgoLevel2Update.class)) + .scan( + new LgoGroupedLevel2Update(), + (acc, s) -> { + if (s.getType().equals("snapshot")) { + acc.applySnapshot(s.getBatchId(), currencyPair, s.getData()); + return acc; + } + if (acc.getLastBatchId() + 1 != s.getBatchId()) { + LOGGER.warn( + "Wrong batch id. Expected {} got {}.", + acc.getLastBatchId() + 1, + s.getBatchId()); + acc.markDirty(); + resubscribe(); + return acc; + } + acc.applyUpdate(s.getBatchId(), currencyPair, s.getData()); + return acc; + }) + .filter(LgoGroupedLevel2Update::isValid) + .map(LgoGroupedLevel2Update::orderBook) + .share(); + } - private void resubscribe() { - try { - String channelName = LgoAdapter.channelName("level2", currencyPair); - service.sendMessage(service.getUnsubscribeMessage(channelName)); - service.sendMessage(service.getSubscribeMessage(channelName)); - } catch (IOException e) { - LOGGER.warn("Error resubscribing", e); - } + private void resubscribe() { + try { + String channelName = LgoAdapter.channelName("level2", currencyPair); + service.sendMessage(service.getUnsubscribeMessage(channelName)); + service.sendMessage(service.getSubscribeMessage(channelName)); + } catch (IOException e) { + LOGGER.warn("Error resubscribing", e); } - + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountService.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountService.java index 9b345644f..dd33c0f5d 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountService.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountService.java @@ -6,56 +6,56 @@ import info.bitrich.xchangestream.lgo.dto.LgoBalanceUpdate; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.util.List; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.account.*; -import java.util.List; - public class LgoStreamingAccountService implements StreamingAccountService { - private static final String CHANNEL_NAME = "balance"; - private final LgoStreamingService service; - private volatile Observable subscription = null; + private static final String CHANNEL_NAME = "balance"; + private final LgoStreamingService service; + private volatile Observable subscription = null; - public LgoStreamingAccountService(LgoStreamingService lgoStreamingService) { - service = lgoStreamingService; - } + public LgoStreamingAccountService(LgoStreamingService lgoStreamingService) { + service = lgoStreamingService; + } - @Override - public Observable getBalanceChanges(Currency currency, Object... args) { - ensureSubscription(); - return subscription.map(u -> u.getWallet().get(currency)); - } + @Override + public Observable getBalanceChanges(Currency currency, Object... args) { + ensureSubscription(); + return subscription.map(u -> u.getWallet().get(currency)); + } - public Observable getWallet() { - ensureSubscription(); - return subscription - .map(u -> Wallet.Builder.from(u.getWallet().values()).build()); - } + public Observable getWallet() { + ensureSubscription(); + return subscription.map(u -> Wallet.Builder.from(u.getWallet().values()).build()); + } - private void ensureSubscription() { - if (subscription == null) { - createSubscription(); - } + private void ensureSubscription() { + if (subscription == null) { + createSubscription(); } + } - private synchronized void createSubscription() { - if (subscription != null) { - return; - } - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - subscription = service - .subscribeChannel(CHANNEL_NAME) - .map(s -> mapper.readValue(s.toString(), LgoBalanceUpdate.class)) - .scan(new LgoGroupedBalanceUpdate(), (acc, s) -> { - List updatedBalances = LgoAdapter.adaptBalances(s.getData()); - if (s.getType().equals("snapshot")) { - return acc.applySnapshot(s.getSeq(), updatedBalances); - } - return acc.applyUpdate(s.getSeq(), updatedBalances); - }) - .skip(1) // skips first element for it's just the empty initial accumulator - .share(); + private synchronized void createSubscription() { + if (subscription != null) { + return; } - + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + subscription = + service + .subscribeChannel(CHANNEL_NAME) + .map(s -> mapper.readValue(s.toString(), LgoBalanceUpdate.class)) + .scan( + new LgoGroupedBalanceUpdate(), + (acc, s) -> { + List updatedBalances = LgoAdapter.adaptBalances(s.getData()); + if (s.getType().equals("snapshot")) { + return acc.applySnapshot(s.getSeq(), updatedBalances); + } + return acc.applyUpdate(s.getSeq(), updatedBalances); + }) + .skip(1) // skips first element for it's just the empty initial accumulator + .share(); + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingExchange.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingExchange.java index bd88255d9..a7dc019ae 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingExchange.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingExchange.java @@ -14,63 +14,68 @@ public class LgoStreamingExchange extends LgoExchange implements StreamingExchange { - private LgoStreamingService streamingService; - private LgoStreamingMarketDataService marketDataService; - private LgoStreamingAccountService accountService; - private LgoStreamingTradeService tradeService; - private final SynchronizedValueFactory nonceFactory = new CurrentTimeNonceFactory(); + private LgoStreamingService streamingService; + private LgoStreamingMarketDataService marketDataService; + private LgoStreamingAccountService accountService; + private LgoStreamingTradeService tradeService; + private final SynchronizedValueFactory nonceFactory = new CurrentTimeNonceFactory(); - @Override - protected void initServices() { - super.initServices(); - streamingService = createStreamingService(); - marketDataService = new LgoStreamingMarketDataService(streamingService); - accountService = new LgoStreamingAccountService(streamingService); - tradeService = new LgoStreamingTradeService(streamingService, new LgoKeyService(getExchangeSpecification()), LgoSignatureService.createInstance(getExchangeSpecification()), nonceFactory); - } + @Override + protected void initServices() { + super.initServices(); + streamingService = createStreamingService(); + marketDataService = new LgoStreamingMarketDataService(streamingService); + accountService = new LgoStreamingAccountService(streamingService); + tradeService = + new LgoStreamingTradeService( + streamingService, + new LgoKeyService(getExchangeSpecification()), + LgoSignatureService.createInstance(getExchangeSpecification()), + nonceFactory); + } - private LgoStreamingService createStreamingService() { - String apiUrl = getExchangeSpecification().getExchangeSpecificParameters().get(LgoEnv.WS_URL).toString(); - return new LgoStreamingService(this.getSignatureService(), apiUrl); - } + private LgoStreamingService createStreamingService() { + String apiUrl = + getExchangeSpecification().getExchangeSpecificParameters().get(LgoEnv.WS_URL).toString(); + return new LgoStreamingService(this.getSignatureService(), apiUrl); + } - @Override - public void applySpecification(ExchangeSpecification exchangeSpecification) { - super.applySpecification(exchangeSpecification); - initServices(); - } + @Override + public void applySpecification(ExchangeSpecification exchangeSpecification) { + super.applySpecification(exchangeSpecification); + initServices(); + } - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return marketDataService; - } + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return marketDataService; + } - @Override - public LgoStreamingAccountService getStreamingAccountService() { - return accountService; - } + @Override + public LgoStreamingAccountService getStreamingAccountService() { + return accountService; + } - @Override - public LgoStreamingTradeService getStreamingTradeService() { - return tradeService; - } + @Override + public LgoStreamingTradeService getStreamingTradeService() { + return tradeService; + } - @Override - public void useCompressedMessages(boolean compressedMessages) { - } + @Override + public void useCompressedMessages(boolean compressedMessages) {} } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataService.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataService.java index b2747ef99..d7d1db0f3 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataService.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataService.java @@ -2,45 +2,48 @@ import info.bitrich.xchangestream.core.StreamingMarketDataService; import io.reactivex.Observable; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.*; import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - public class LgoStreamingMarketDataService implements StreamingMarketDataService { - private final LgoStreamingService service; - private final Map level2Subscriptions = new ConcurrentHashMap<>(); - private final Map tradeSubscriptions = new ConcurrentHashMap<>(); - - LgoStreamingMarketDataService(LgoStreamingService lgoStreamingService) { - service = lgoStreamingService; - } - - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - return level2Subscriptions.computeIfAbsent(currencyPair, this::createLevel2Subscription) - .getSubscription(); - } - - private LgoLevel2BatchSubscription createLevel2Subscription(CurrencyPair currencyPair) { - return LgoLevel2BatchSubscription.create(service, currencyPair); - } - - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - return tradeSubscriptions.computeIfAbsent(currencyPair, this::createTradeSubscription) - .getSubscription(); - } - - private LgoTradeBatchSubscription createTradeSubscription(CurrencyPair currencyPair) { - return LgoTradeBatchSubscription.create(service, currencyPair); - } - - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - throw new NotYetImplementedForExchangeException(); - } + private final LgoStreamingService service; + private final Map level2Subscriptions = + new ConcurrentHashMap<>(); + private final Map tradeSubscriptions = + new ConcurrentHashMap<>(); + + LgoStreamingMarketDataService(LgoStreamingService lgoStreamingService) { + service = lgoStreamingService; + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + return level2Subscriptions + .computeIfAbsent(currencyPair, this::createLevel2Subscription) + .getSubscription(); + } + + private LgoLevel2BatchSubscription createLevel2Subscription(CurrencyPair currencyPair) { + return LgoLevel2BatchSubscription.create(service, currencyPair); + } + + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + return tradeSubscriptions + .computeIfAbsent(currencyPair, this::createTradeSubscription) + .getSubscription(); + } + + private LgoTradeBatchSubscription createTradeSubscription(CurrencyPair currencyPair) { + return LgoTradeBatchSubscription.create(service, currencyPair); + } + + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + throw new NotYetImplementedForExchangeException(); + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingService.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingService.java index 03c3692f5..c542002e8 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingService.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingService.java @@ -1,60 +1,59 @@ package info.bitrich.xchangestream.lgo; +import static info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper.*; + import com.fasterxml.jackson.databind.JsonNode; import info.bitrich.xchangestream.lgo.dto.LgoSubscription; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; -import org.knowm.xchange.lgo.service.LgoSignatureService; - import java.io.IOException; import java.time.Duration; - -import static info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper.*; +import org.knowm.xchange.lgo.service.LgoSignatureService; public class LgoStreamingService extends JsonNettyStreamingService { - private final LgoSignatureService signatureService; - private final String apiUrl; - - LgoStreamingService(LgoSignatureService signatureService, String apiUrl) { - super(apiUrl, Integer.MAX_VALUE, Duration.ofSeconds(10), Duration.ofSeconds(15), 1); - this.apiUrl = apiUrl; - this.signatureService = signatureService; - } - - @Override - protected void handleIdle(ChannelHandlerContext ctx) { - ctx.writeAndFlush(new PingWebSocketFrame()); - } - - @Override - protected String getChannelNameFromMessage(JsonNode message) { - String channel = message.get("channel").asText(); - if (channel.equals("trades") || channel.equals("level2") || channel.equals("user")) { - return channel + "-" + message.get("product_id").textValue(); - } - return channel; - } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - return getObjectMapper().writeValueAsString(LgoSubscription.subscribe(channelName)); - } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - return getObjectMapper().writeValueAsString(LgoSubscription.unsubscribe(channelName)); - } - - @Override - protected DefaultHttpHeaders getCustomHeaders() { - DefaultHttpHeaders headers = super.getCustomHeaders(); - String timestamp = String.valueOf(System.currentTimeMillis()); - headers.add("X-LGO-DATE", timestamp); - String auth = signatureService.digestSignedUrlHeader(this.apiUrl, timestamp); - headers.add("Authorization", auth); - return headers; + private final LgoSignatureService signatureService; + private final String apiUrl; + + LgoStreamingService(LgoSignatureService signatureService, String apiUrl) { + super(apiUrl, Integer.MAX_VALUE, Duration.ofSeconds(10), Duration.ofSeconds(15), 1); + this.apiUrl = apiUrl; + this.signatureService = signatureService; + } + + @Override + protected void handleIdle(ChannelHandlerContext ctx) { + ctx.writeAndFlush(new PingWebSocketFrame()); + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) { + String channel = message.get("channel").asText(); + if (channel.equals("trades") || channel.equals("level2") || channel.equals("user")) { + return channel + "-" + message.get("product_id").textValue(); } + return channel; + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + return getObjectMapper().writeValueAsString(LgoSubscription.subscribe(channelName)); + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + return getObjectMapper().writeValueAsString(LgoSubscription.unsubscribe(channelName)); + } + + @Override + protected DefaultHttpHeaders getCustomHeaders() { + DefaultHttpHeaders headers = super.getCustomHeaders(); + String timestamp = String.valueOf(System.currentTimeMillis()); + headers.add("X-LGO-DATE", timestamp); + String auth = signatureService.digestSignedUrlHeader(this.apiUrl, timestamp); + headers.add("Authorization", auth); + return headers; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeService.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeService.java index c4705ef58..73305b072 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeService.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeService.java @@ -7,6 +7,10 @@ import info.bitrich.xchangestream.lgo.dto.*; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.*; @@ -16,164 +20,166 @@ import org.knowm.xchange.lgo.service.*; import si.mazi.rescu.SynchronizedValueFactory; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - public class LgoStreamingTradeService implements StreamingTradeService { - private final LgoStreamingService streamingService; - private final LgoKeyService keyService; - private final LgoSignatureService signatureService; - private final SynchronizedValueFactory nonceFactory; - private final Map batchSubscriptions = new ConcurrentHashMap<>(); - private Observable afrSubscription; - - LgoStreamingTradeService(LgoStreamingService streamingService, LgoKeyService keyService, LgoSignatureService signatureService, SynchronizedValueFactory nonceFactory) { - this.streamingService = streamingService; - this.keyService = keyService; - this.signatureService = signatureService; - this.nonceFactory = nonceFactory; - } - - /** - * {@inheritDoc} - * First sent orders will be current open orders. - */ - @Override - public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { - return getOrderBatchChanges(currencyPair) - .flatMap(Observable::fromIterable); - } - - /** - * Get an up-to-date view of all your open orders after each LGO batch execution. - * First sending will be the actual open orders list. - */ - public Observable getOpenOrders(CurrencyPair currencyPair) { - return getOrderUpdates(currencyPair) - .map(u -> u.getAllOpenOrders().values().stream() - .filter(order -> order instanceof LimitOrder) - .map(order -> (LimitOrder) order) - .collect(Collectors.toList())) - .map(OpenOrders::new); - } - - /** - * Receive all updated orders, for each LGO batches. - * First sending will be the actual open orders list. - */ - public Observable> getOrderBatchChanges(CurrencyPair currencyPair) { - return getOrderUpdates(currencyPair) - .map(LgoGroupedUserUpdate::getUpdatedOrders); - } - - private Observable getOrderUpdates(CurrencyPair currencyPair) { - return batchSubscriptions.computeIfAbsent(currencyPair, this::createBatchSubscription).getPublisher(); - } - - private LgoUserBatchSubscription createBatchSubscription(CurrencyPair currencyPair) { - return LgoUserBatchSubscription.create(streamingService, currencyPair); - } - - @Override - public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { - return getRawBatchOrderEvents(currencyPair) - .filter(lgoOrderEvent -> "match".equals(lgoOrderEvent.getType())) - .map(matchEvent -> LgoAdapter.adaptUserTrade(currencyPair, (LgoMatchOrderEvent) matchEvent)); - } - - /** - * Receive all events for the selected currency pairs. Merges batch order events and ack (AFR) - * events. - */ - public Observable getRawAllOrderEvents(Collection currencyPairs) { - Observable ackObservable = getRawReceivedOrderEvents(); - return currencyPairs.stream() - .map(this::getRawBatchOrderEvents) - .reduce(Observable::mergeWith) - .map(ackObservable::mergeWith) - .orElse(ackObservable); - } - - /** - * Get ack for your placed orders. "received" events indicate the orderId associated to your - * order, if you set a reference on order placement you will have it in the event. "failed" events - * indicate that the order could not be read or was invalid and not added to a batch. - */ - public Observable getRawReceivedOrderEvents() { - if (afrSubscription == null) { - createAfrSubscription(); - } - return afrSubscription; - } - - private void createAfrSubscription() { - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - afrSubscription = streamingService - .subscribeChannel("afr") - .map(s -> mapper.readValue(s.toString(), LgoAckUpdate.class)) - .map(LgoAckUpdate::getData) - .flatMap(Observable::fromIterable) - .share(); - } - - /** - * Get all events of your orders happened during batch execution, for a currency pair. "pending" - * events indicate that the order was added to a batch and received by the execution engine. - * "invalid" events indicate that the order was not suitable for execution. "match" events - * indicate that the order did match against another order. "open" events indicate that the order - * entered the order book. "done" events indicate that the order was filled, canceled or - * rejected. - */ - public Observable getRawBatchOrderEvents(CurrencyPair currencyPair) { - return getOrderUpdates(currencyPair) - .map(LgoGroupedUserUpdate::getEvents) - .flatMap(Observable::fromIterable); - } - - /** - * Place a market order - * - * @return the order reference - */ - public String placeMarketOrder(MarketOrder marketOrder) throws IOException { - Long ref = nonceFactory.createValue(); - LgoPlaceOrder lgoOrder = LgoAdapters.adaptEncryptedMarketOrder(marketOrder); - return placeOrder(ref, lgoOrder); - } - - /** - * Place a limit order - * - * @return the order reference - */ - public String placeLimitOrder(LimitOrder limitOrder) throws IOException { - Long ref = nonceFactory.createValue(); - LgoPlaceOrder lgoOrder = LgoAdapters.adaptLimitOrder(limitOrder); - return placeOrder(ref, lgoOrder); - } - - /** - * Place a cancel order - * - * @return true - */ - public boolean cancelOrder(String orderId) throws IOException { - Long ref = nonceFactory.createValue(); - LgoPlaceCancelOrder lgoOrder = new LgoPlaceCancelOrder(ref, orderId, new Date().toInstant()); - placeOrder(ref, lgoOrder); - return true; - } - - private String placeOrder(Long ref, LgoPlaceOrder lgoOrder) throws JsonProcessingException { - LgoKey lgoKey = keyService.selectKey(); - String encryptedOrder = CryptoUtils.encryptOrder(lgoKey, lgoOrder); - LgoOrderSignature signature = signatureService.signOrder(encryptedOrder); - LgoSocketPlaceOrder placeOrder = new LgoSocketPlaceOrder(new LgoEncryptedOrder(lgoKey.getId(), encryptedOrder, signature, ref)); - String payload = StreamingObjectMapperHelper.getObjectMapper().writeValueAsString(placeOrder); - streamingService.sendMessage(payload); - return ref.toString(); + private final LgoStreamingService streamingService; + private final LgoKeyService keyService; + private final LgoSignatureService signatureService; + private final SynchronizedValueFactory nonceFactory; + private final Map batchSubscriptions = + new ConcurrentHashMap<>(); + private Observable afrSubscription; + + LgoStreamingTradeService( + LgoStreamingService streamingService, + LgoKeyService keyService, + LgoSignatureService signatureService, + SynchronizedValueFactory nonceFactory) { + this.streamingService = streamingService; + this.keyService = keyService; + this.signatureService = signatureService; + this.nonceFactory = nonceFactory; + } + + /** {@inheritDoc} First sent orders will be current open orders. */ + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + return getOrderBatchChanges(currencyPair).flatMap(Observable::fromIterable); + } + + /** + * Get an up-to-date view of all your open orders after each LGO batch execution. First sending + * will be the actual open orders list. + */ + public Observable getOpenOrders(CurrencyPair currencyPair) { + return getOrderUpdates(currencyPair) + .map( + u -> + u.getAllOpenOrders().values().stream() + .filter(order -> order instanceof LimitOrder) + .map(order -> (LimitOrder) order) + .collect(Collectors.toList())) + .map(OpenOrders::new); + } + + /** + * Receive all updated orders, for each LGO batches. First sending will be the actual open orders + * list. + */ + public Observable> getOrderBatchChanges(CurrencyPair currencyPair) { + return getOrderUpdates(currencyPair).map(LgoGroupedUserUpdate::getUpdatedOrders); + } + + private Observable getOrderUpdates(CurrencyPair currencyPair) { + return batchSubscriptions + .computeIfAbsent(currencyPair, this::createBatchSubscription) + .getPublisher(); + } + + private LgoUserBatchSubscription createBatchSubscription(CurrencyPair currencyPair) { + return LgoUserBatchSubscription.create(streamingService, currencyPair); + } + + @Override + public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + return getRawBatchOrderEvents(currencyPair) + .filter(lgoOrderEvent -> "match".equals(lgoOrderEvent.getType())) + .map( + matchEvent -> LgoAdapter.adaptUserTrade(currencyPair, (LgoMatchOrderEvent) matchEvent)); + } + + /** + * Receive all events for the selected currency pairs. Merges batch order events and ack (AFR) + * events. + */ + public Observable getRawAllOrderEvents(Collection currencyPairs) { + Observable ackObservable = getRawReceivedOrderEvents(); + return currencyPairs.stream() + .map(this::getRawBatchOrderEvents) + .reduce(Observable::mergeWith) + .map(ackObservable::mergeWith) + .orElse(ackObservable); + } + + /** + * Get ack for your placed orders. "received" events indicate the orderId associated to your + * order, if you set a reference on order placement you will have it in the event. "failed" events + * indicate that the order could not be read or was invalid and not added to a batch. + */ + public Observable getRawReceivedOrderEvents() { + if (afrSubscription == null) { + createAfrSubscription(); } + return afrSubscription; + } + + private void createAfrSubscription() { + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + afrSubscription = + streamingService + .subscribeChannel("afr") + .map(s -> mapper.readValue(s.toString(), LgoAckUpdate.class)) + .map(LgoAckUpdate::getData) + .flatMap(Observable::fromIterable) + .share(); + } + + /** + * Get all events of your orders happened during batch execution, for a currency pair. "pending" + * events indicate that the order was added to a batch and received by the execution engine. + * "invalid" events indicate that the order was not suitable for execution. "match" events + * indicate that the order did match against another order. "open" events indicate that the order + * entered the order book. "done" events indicate that the order was filled, canceled or rejected. + */ + public Observable getRawBatchOrderEvents(CurrencyPair currencyPair) { + return getOrderUpdates(currencyPair) + .map(LgoGroupedUserUpdate::getEvents) + .flatMap(Observable::fromIterable); + } + + /** + * Place a market order + * + * @return the order reference + */ + public String placeMarketOrder(MarketOrder marketOrder) throws IOException { + Long ref = nonceFactory.createValue(); + LgoPlaceOrder lgoOrder = LgoAdapters.adaptEncryptedMarketOrder(marketOrder); + return placeOrder(ref, lgoOrder); + } + + /** + * Place a limit order + * + * @return the order reference + */ + public String placeLimitOrder(LimitOrder limitOrder) throws IOException { + Long ref = nonceFactory.createValue(); + LgoPlaceOrder lgoOrder = LgoAdapters.adaptLimitOrder(limitOrder); + return placeOrder(ref, lgoOrder); + } + + /** + * Place a cancel order + * + * @return true + */ + public boolean cancelOrder(String orderId) throws IOException { + Long ref = nonceFactory.createValue(); + LgoPlaceCancelOrder lgoOrder = new LgoPlaceCancelOrder(ref, orderId, new Date().toInstant()); + placeOrder(ref, lgoOrder); + return true; + } + + private String placeOrder(Long ref, LgoPlaceOrder lgoOrder) throws JsonProcessingException { + LgoKey lgoKey = keyService.selectKey(); + String encryptedOrder = CryptoUtils.encryptOrder(lgoKey, lgoOrder); + LgoOrderSignature signature = signatureService.signOrder(encryptedOrder); + LgoSocketPlaceOrder placeOrder = + new LgoSocketPlaceOrder( + new LgoEncryptedOrder(lgoKey.getId(), encryptedOrder, signature, ref)); + String payload = StreamingObjectMapperHelper.getObjectMapper().writeValueAsString(placeOrder); + streamingService.sendMessage(payload); + return ref.toString(); + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoTradeBatchSubscription.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoTradeBatchSubscription.java index db195aabf..47637c07b 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoTradeBatchSubscription.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoTradeBatchSubscription.java @@ -5,62 +5,67 @@ import info.bitrich.xchangestream.lgo.dto.LgoTradesUpdate; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.io.IOException; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Trade; import org.slf4j.*; -import java.io.IOException; - class LgoTradeBatchSubscription { - private final LgoStreamingService service; - private final CurrencyPair currencyPair; - private final Observable subscription; - private static final Logger LOGGER = LoggerFactory.getLogger(LgoTradeBatchSubscription.class); + private final LgoStreamingService service; + private final CurrencyPair currencyPair; + private final Observable subscription; + private static final Logger LOGGER = LoggerFactory.getLogger(LgoTradeBatchSubscription.class); - static LgoTradeBatchSubscription create(LgoStreamingService service, CurrencyPair currencyPair) { - return new LgoTradeBatchSubscription(service, currencyPair); - } + static LgoTradeBatchSubscription create(LgoStreamingService service, CurrencyPair currencyPair) { + return new LgoTradeBatchSubscription(service, currencyPair); + } - private LgoTradeBatchSubscription(LgoStreamingService service, CurrencyPair currencyPair) { - this.service = service; - this.currencyPair = currencyPair; - subscription = createTradeSubscription(); - } + private LgoTradeBatchSubscription(LgoStreamingService service, CurrencyPair currencyPair) { + this.service = service; + this.currencyPair = currencyPair; + subscription = createTradeSubscription(); + } - private Observable createTradeSubscription() { - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - Observable observable = service - .subscribeChannel(LgoAdapter.channelName("trades", currencyPair)) - .map(s -> mapper.readValue(s.toString(), LgoTradesUpdate.class)) - .scan(new LgoGroupedTradeUpdate(currencyPair), (acc, s) -> { - if ("snapshot".equals(s.getType())) { - acc.apply(s.getBatchId(), s.getTrades()); - return acc; - } - if (acc.getLastBatchId() + 1 != s.getBatchId()) { - LOGGER.warn("Wrong batchId. Expected {} got {}.", acc.getLastBatchId() + 1, s.getBatchId()); - resubscribe(); - } + private Observable createTradeSubscription() { + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + Observable observable = + service + .subscribeChannel(LgoAdapter.channelName("trades", currencyPair)) + .map(s -> mapper.readValue(s.toString(), LgoTradesUpdate.class)) + .scan( + new LgoGroupedTradeUpdate(currencyPair), + (acc, s) -> { + if ("snapshot".equals(s.getType())) { acc.apply(s.getBatchId(), s.getTrades()); return acc; + } + if (acc.getLastBatchId() + 1 != s.getBatchId()) { + LOGGER.warn( + "Wrong batchId. Expected {} got {}.", + acc.getLastBatchId() + 1, + s.getBatchId()); + resubscribe(); + } + acc.apply(s.getBatchId(), s.getTrades()); + return acc; }) - .skip(1) - .flatMap(acc -> Observable.fromIterable(acc.getTrades())); - return observable; - } + .skip(1) + .flatMap(acc -> Observable.fromIterable(acc.getTrades())); + return observable; + } - private void resubscribe() { - String channelName = LgoAdapter.channelName("trades", currencyPair); - try { - service.sendMessage(service.getUnsubscribeMessage(channelName)); - service.sendMessage(service.getSubscribeMessage(channelName)); - } catch (IOException e) { - LOGGER.error("Error resubscribing", e); - } + private void resubscribe() { + String channelName = LgoAdapter.channelName("trades", currencyPair); + try { + service.sendMessage(service.getUnsubscribeMessage(channelName)); + service.sendMessage(service.getSubscribeMessage(channelName)); + } catch (IOException e) { + LOGGER.error("Error resubscribing", e); } + } - Observable getSubscription() { - return subscription; - } + Observable getSubscription() { + return subscription; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoUserBatchSubscription.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoUserBatchSubscription.java index c4d6c0025..6e1ec75c6 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoUserBatchSubscription.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/LgoUserBatchSubscription.java @@ -1,83 +1,96 @@ package info.bitrich.xchangestream.lgo; +import static java.util.stream.Collectors.*; + import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.lgo.domain.*; import info.bitrich.xchangestream.lgo.dto.*; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.dto.trade.*; - import java.util.*; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; - -import static java.util.stream.Collectors.*; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.trade.*; class LgoUserBatchSubscription { - private final Observable downstream; - private final LgoStreamingService streamingService; - private final CurrencyPair currencyPair; - - - static LgoUserBatchSubscription create(LgoStreamingService streamingService, CurrencyPair currencyPair) { - return new LgoUserBatchSubscription(streamingService, currencyPair); - } - - private LgoUserBatchSubscription(LgoStreamingService streamingService, CurrencyPair currencyPair) { - this.streamingService = streamingService; - this.currencyPair = currencyPair; - downstream = createSubscription(); - } + private final Observable downstream; + private final LgoStreamingService streamingService; + private final CurrencyPair currencyPair; - Observable getPublisher() { - return downstream; - } + static LgoUserBatchSubscription create( + LgoStreamingService streamingService, CurrencyPair currencyPair) { + return new LgoUserBatchSubscription(streamingService, currencyPair); + } - private Observable createSubscription() { - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - return streamingService - .subscribeChannel(LgoAdapter.channelName("user", currencyPair)) - .map(s -> mapper.readValue(s.toString(), LgoUserMessage.class)) - .scan(new LgoGroupedUserUpdate(), (acc, s) -> { - List events = new ArrayList<>(); - if (s.getType().equals("update")) { - LgoUserUpdate userUpdate = (LgoUserUpdate) s; - List updates = updateAllOrders(currencyPair, userUpdate.getOrderEvents(), acc.getAllOpenOrders()); - events.addAll(LgoAdapter.adaptOrderEvent(userUpdate.getOrderEvents(), s.getBatchId(), updates)); - return new LgoGroupedUserUpdate(acc.getAllOpenOrders(), updates, events, s.getBatchId(), s.getType()); - } else { - Collection allOrders = handleUserSnapshot(currencyPair, (LgoUserSnapshot) s); - ConcurrentMap ordersById = allOrders.stream() - .collect(toConcurrentMap(LimitOrder::getId, this::copyOrder)); - return new LgoGroupedUserUpdate(ordersById, new ArrayList<>(allOrders), events, s.getBatchId(), s.getType()); - } + private LgoUserBatchSubscription( + LgoStreamingService streamingService, CurrencyPair currencyPair) { + this.streamingService = streamingService; + this.currencyPair = currencyPair; + downstream = createSubscription(); + } - }) - .skip(1) // skips the first element, for this is the empty accumulator - .share(); - } + Observable getPublisher() { + return downstream; + } - private List updateAllOrders(CurrencyPair currencyPair, List orderEvents, Map allOpenOrders) { - return orderEvents.stream() - .map(orderEvent -> orderEvent.applyOnOrders(currencyPair, allOpenOrders)) - .map(this::copyOrder) - .collect(Collectors.toList()); - } + private Observable createSubscription() { + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + return streamingService + .subscribeChannel(LgoAdapter.channelName("user", currencyPair)) + .map(s -> mapper.readValue(s.toString(), LgoUserMessage.class)) + .scan( + new LgoGroupedUserUpdate(), + (acc, s) -> { + List events = new ArrayList<>(); + if (s.getType().equals("update")) { + LgoUserUpdate userUpdate = (LgoUserUpdate) s; + List updates = + updateAllOrders( + currencyPair, userUpdate.getOrderEvents(), acc.getAllOpenOrders()); + events.addAll( + LgoAdapter.adaptOrderEvent( + userUpdate.getOrderEvents(), s.getBatchId(), updates)); + return new LgoGroupedUserUpdate( + acc.getAllOpenOrders(), updates, events, s.getBatchId(), s.getType()); + } else { + Collection allOrders = + handleUserSnapshot(currencyPair, (LgoUserSnapshot) s); + ConcurrentMap ordersById = + allOrders.stream().collect(toConcurrentMap(LimitOrder::getId, this::copyOrder)); + return new LgoGroupedUserUpdate( + ordersById, new ArrayList<>(allOrders), events, s.getBatchId(), s.getType()); + } + }) + .skip(1) // skips the first element, for this is the empty accumulator + .share(); + } - private Collection handleUserSnapshot(CurrencyPair currencyPair, LgoUserSnapshot s) { - return LgoAdapter.adaptOrdersSnapshot(s.getSnapshotData(), currencyPair); + private List updateAllOrders( + CurrencyPair currencyPair, + List orderEvents, + Map allOpenOrders) { + return orderEvents.stream() + .map(orderEvent -> orderEvent.applyOnOrders(currencyPair, allOpenOrders)) + .map(this::copyOrder) + .collect(Collectors.toList()); + } - } + private Collection handleUserSnapshot(CurrencyPair currencyPair, LgoUserSnapshot s) { + return LgoAdapter.adaptOrdersSnapshot(s.getSnapshotData(), currencyPair); + } - private Order copyOrder(Order order) { - Order copy = order instanceof LimitOrder ? LimitOrder.Builder.from(order).build() : MarketOrder.Builder.from(order).build(); - // because actual released version of xchange-core has buggy Builder.from methods - copy.setFee(order.getFee()); - copy.setCumulativeAmount(order.getCumulativeAmount()); - // https://github.com/knowm/XChange/pull/3163 - return copy; - } + private Order copyOrder(Order order) { + Order copy = + order instanceof LimitOrder + ? LimitOrder.Builder.from(order).build() + : MarketOrder.Builder.from(order).build(); + // because actual released version of xchange-core has buggy Builder.from methods + copy.setFee(order.getFee()); + copy.setCumulativeAmount(order.getCumulativeAmount()); + // https://github.com/knowm/XChange/pull/3163 + return copy; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoAckOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoAckOrderEvent.java index d49d46308..006e21e36 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoAckOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoAckOrderEvent.java @@ -4,8 +4,7 @@ public class LgoAckOrderEvent extends LgoOrderEvent { - protected LgoAckOrderEvent(String type, String orderId, Date time) { - super(type, orderId, time); - } - + protected LgoAckOrderEvent(String type, String orderId, Date time) { + super(type, orderId, time); + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoBatchOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoBatchOrderEvent.java index 1820c34b4..ff7f3af55 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoBatchOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoBatchOrderEvent.java @@ -1,35 +1,34 @@ package info.bitrich.xchangestream.lgo.domain; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; - import java.util.Date; import java.util.Map; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; public abstract class LgoBatchOrderEvent extends LgoOrderEvent { - /** - * Identifier of the batch where the event happened. - * Null in the case of a type=received event. Not null otherwise - */ - private Long batchId; + /** + * Identifier of the batch where the event happened. Null in the case of a type=received event. + * Not null otherwise + */ + private Long batchId; - protected LgoBatchOrderEvent(String type, String orderId, Date time) { - super(type, orderId, time); - } + protected LgoBatchOrderEvent(String type, String orderId, Date time) { + super(type, orderId, time); + } - public LgoBatchOrderEvent(Long batchId, String type, String orderId, Date time) { - super(type, orderId, time); - this.batchId = batchId; - } + public LgoBatchOrderEvent(Long batchId, String type, String orderId, Date time) { + super(type, orderId, time); + this.batchId = batchId; + } - public Long getBatchId() { - return batchId; - } + public Long getBatchId() { + return batchId; + } - public void setBatchId(long batchId) { - this.batchId = batchId; - } + public void setBatchId(long batchId) { + this.batchId = batchId; + } - public abstract Order applyOnOrders(CurrencyPair currencyPair, Map allOrders); + public abstract Order applyOnOrders(CurrencyPair currencyPair, Map allOrders); } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoDoneOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoDoneOrderEvent.java index f16f666bf..22d558aeb 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoDoneOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoDoneOrderEvent.java @@ -2,63 +2,62 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; - import java.math.BigDecimal; import java.util.Date; import java.util.Map; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; -/** - * Order left orderbook - */ +/** Order left orderbook */ public class LgoDoneOrderEvent extends LgoBatchOrderEvent { - /** - * Reason of done status (rejected, canceled, canceledBySelfTradePrevention, filled) - */ - private final String reason; - - /** - * Amount canceled (base currency) when reason=canceledBySelfTradePrevention - */ - private final BigDecimal canceled; - - public LgoDoneOrderEvent(Long batchId, String type, String orderId, Date time, String reason, BigDecimal canceled) { - super(batchId, type, orderId, time); - this.reason = reason; - this.canceled = canceled; - } - - public LgoDoneOrderEvent( - @JsonProperty("type") String type, - @JsonProperty("order_id") String orderId, - @JsonProperty("time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date time, - @JsonProperty("reason") String reason, - @JsonProperty("canceled") BigDecimal canceled) { - super(type, orderId, time); - this.reason = reason; - this.canceled = canceled; - } - - public String getReason() { - return reason; - } - - public BigDecimal getCanceled() { - return canceled; - } - - @Override - public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { - Order doneOrder = allOrders.remove(getOrderId()); - if ("canceledBySelfTradePrevention".equals(reason) || "canceled".equals(reason)) { - doneOrder.setOrderStatus(doneOrder.getStatus() == Order.OrderStatus.PARTIALLY_FILLED ? Order.OrderStatus.PARTIALLY_CANCELED : Order.OrderStatus.CANCELED); - } else if ("filled".equals(reason)) { - doneOrder.setOrderStatus(Order.OrderStatus.FILLED); - } else { - doneOrder.setOrderStatus(Order.OrderStatus.REJECTED); - } - return doneOrder; + /** Reason of done status (rejected, canceled, canceledBySelfTradePrevention, filled) */ + private final String reason; + + /** Amount canceled (base currency) when reason=canceledBySelfTradePrevention */ + private final BigDecimal canceled; + + public LgoDoneOrderEvent( + Long batchId, String type, String orderId, Date time, String reason, BigDecimal canceled) { + super(batchId, type, orderId, time); + this.reason = reason; + this.canceled = canceled; + } + + public LgoDoneOrderEvent( + @JsonProperty("type") String type, + @JsonProperty("order_id") String orderId, + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date time, + @JsonProperty("reason") String reason, + @JsonProperty("canceled") BigDecimal canceled) { + super(type, orderId, time); + this.reason = reason; + this.canceled = canceled; + } + + public String getReason() { + return reason; + } + + public BigDecimal getCanceled() { + return canceled; + } + + @Override + public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { + Order doneOrder = allOrders.remove(getOrderId()); + if ("canceledBySelfTradePrevention".equals(reason) || "canceled".equals(reason)) { + doneOrder.setOrderStatus( + doneOrder.getStatus() == Order.OrderStatus.PARTIALLY_FILLED + ? Order.OrderStatus.PARTIALLY_CANCELED + : Order.OrderStatus.CANCELED); + } else if ("filled".equals(reason)) { + doneOrder.setOrderStatus(Order.OrderStatus.FILLED); + } else { + doneOrder.setOrderStatus(Order.OrderStatus.REJECTED); } + return doneOrder; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoFailedOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoFailedOrderEvent.java index 4f023fb42..f6f3e9b43 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoFailedOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoFailedOrderEvent.java @@ -2,41 +2,38 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Date; -/** - * Order could not be added to a batch. - * No batchId for this event. - */ +/** Order could not be added to a batch. No batchId for this event. */ public class LgoFailedOrderEvent extends LgoAckOrderEvent { - /** - * Reference set by the trader - */ - private final String reference; - - /** - * Reason of failure (InvalidQuantity, InvalidPrice, InvalidAmount, InvalidPriceIncrement, InvalidProduct, InsufficientFunds) - */ - private final String reason; - - public LgoFailedOrderEvent( - @JsonProperty("order_id") String orderId, - @JsonProperty("reference") String reference, - @JsonProperty("type") String type, - @JsonProperty("time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date time, - @JsonProperty("reason") String reason) { - super(type, orderId, time); - this.reference = reference; - this.reason = reason; - } - - public String getReference() { - return reference; - } - - public String getReason() { - return reason; - } + /** Reference set by the trader */ + private final String reference; + + /** + * Reason of failure (InvalidQuantity, InvalidPrice, InvalidAmount, InvalidPriceIncrement, + * InvalidProduct, InsufficientFunds) + */ + private final String reason; + + public LgoFailedOrderEvent( + @JsonProperty("order_id") String orderId, + @JsonProperty("reference") String reference, + @JsonProperty("type") String type, + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date time, + @JsonProperty("reason") String reason) { + super(type, orderId, time); + this.reference = reference; + this.reason = reason; + } + + public String getReference() { + return reference; + } + + public String getReason() { + return reason; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedBalanceUpdate.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedBalanceUpdate.java index 0296f08b2..fd45dcf8f 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedBalanceUpdate.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedBalanceUpdate.java @@ -1,32 +1,31 @@ package info.bitrich.xchangestream.lgo.domain; -import org.knowm.xchange.currency.Currency; -import org.knowm.xchange.dto.account.Balance; - import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.dto.account.Balance; public class LgoGroupedBalanceUpdate { - private final Map wallet = new ConcurrentHashMap<>(); - private long seq; + private final Map wallet = new ConcurrentHashMap<>(); + private long seq; - public LgoGroupedBalanceUpdate applySnapshot(long seq, List updatedBalances) { - wallet.clear(); - return applyUpdate(seq, updatedBalances); - } + public LgoGroupedBalanceUpdate applySnapshot(long seq, List updatedBalances) { + wallet.clear(); + return applyUpdate(seq, updatedBalances); + } - public LgoGroupedBalanceUpdate applyUpdate(long seq, List updatedBalances) { - this.seq = seq; - updatedBalances.forEach(b -> wallet.put(b.getCurrency(), b)); - return this; - } + public LgoGroupedBalanceUpdate applyUpdate(long seq, List updatedBalances) { + this.seq = seq; + updatedBalances.forEach(b -> wallet.put(b.getCurrency(), b)); + return this; + } - public long getSeq() { - return seq; - } + public long getSeq() { + return seq; + } - public Map getWallet() { - return wallet; - } + public Map getWallet() { + return wallet; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedLevel2Update.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedLevel2Update.java index d01d68389..f2a5ddd99 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedLevel2Update.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedLevel2Update.java @@ -1,70 +1,69 @@ package info.bitrich.xchangestream.lgo.domain; import info.bitrich.xchangestream.lgo.dto.LgoLevel2Data; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.dto.marketdata.*; - import java.math.BigDecimal; import java.util.*; import java.util.stream.Stream; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.marketdata.*; public class LgoGroupedLevel2Update { - private final OrderBook orderBook = new OrderBook(null, new ArrayList<>(), new ArrayList<>()); - private long lastBatchId; - private boolean dirty = true; - - public void applySnapshot(long batchId, CurrencyPair currencyPair, LgoLevel2Data data) { - dirty = false; - applyUpdate(batchId, currencyPair, data); - } + private final OrderBook orderBook = new OrderBook(null, new ArrayList<>(), new ArrayList<>()); + private long lastBatchId; + private boolean dirty = true; - public void applyUpdate(long batchId, CurrencyPair currencyPair, LgoLevel2Data data) { - lastBatchId = batchId; - updateL2Book(currencyPair, data); - } + public void applySnapshot(long batchId, CurrencyPair currencyPair, LgoLevel2Data data) { + dirty = false; + applyUpdate(batchId, currencyPair, data); + } - private void updateL2Book(CurrencyPair currencyPair, LgoLevel2Data data) { - Stream asksUpdates = adaptLevel2Update(data, Order.OrderType.ASK, currencyPair); - Stream bidsUpdate = adaptLevel2Update(data, Order.OrderType.BID, currencyPair); - asksUpdates.forEach(orderBook::update); - bidsUpdate.forEach(orderBook::update); - } + public void applyUpdate(long batchId, CurrencyPair currencyPair, LgoLevel2Data data) { + lastBatchId = batchId; + updateL2Book(currencyPair, data); + } - private Stream adaptLevel2Update(LgoLevel2Data data, Order.OrderType type, CurrencyPair currencyPair) { - switch (type) { - case BID: - return data.getBids().stream() - .map(value -> toBookUpdate(type, currencyPair, value)); - case ASK: - return data.getAsks().stream() - .map(value -> toBookUpdate(type, currencyPair, value)); - default: - return Stream.empty(); - } - } + private void updateL2Book(CurrencyPair currencyPair, LgoLevel2Data data) { + Stream asksUpdates = + adaptLevel2Update(data, Order.OrderType.ASK, currencyPair); + Stream bidsUpdate = adaptLevel2Update(data, Order.OrderType.BID, currencyPair); + asksUpdates.forEach(orderBook::update); + bidsUpdate.forEach(orderBook::update); + } - private OrderBookUpdate toBookUpdate(Order.OrderType type, CurrencyPair currencyPair, List value) { - BigDecimal price = new BigDecimal(value.get(0)); - BigDecimal quantity = new BigDecimal(value.get(1)); - return new OrderBookUpdate(type, null, currencyPair, price, null, quantity); + private Stream adaptLevel2Update( + LgoLevel2Data data, Order.OrderType type, CurrencyPair currencyPair) { + switch (type) { + case BID: + return data.getBids().stream().map(value -> toBookUpdate(type, currencyPair, value)); + case ASK: + return data.getAsks().stream().map(value -> toBookUpdate(type, currencyPair, value)); + default: + return Stream.empty(); } + } - public void markDirty() { - dirty = true; - } + private OrderBookUpdate toBookUpdate( + Order.OrderType type, CurrencyPair currencyPair, List value) { + BigDecimal price = new BigDecimal(value.get(0)); + BigDecimal quantity = new BigDecimal(value.get(1)); + return new OrderBookUpdate(type, null, currencyPair, price, null, quantity); + } - public boolean isValid() { - return !dirty; - } + public void markDirty() { + dirty = true; + } - public long getLastBatchId() { - return lastBatchId; - } + public boolean isValid() { + return !dirty; + } - public OrderBook orderBook() { - return orderBook; - } + public long getLastBatchId() { + return lastBatchId; + } + public OrderBook orderBook() { + return orderBook; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedTradeUpdate.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedTradeUpdate.java index 93f3ff4eb..488b533de 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedTradeUpdate.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedTradeUpdate.java @@ -1,36 +1,36 @@ package info.bitrich.xchangestream.lgo.domain; +import static java.util.stream.Collectors.*; + import info.bitrich.xchangestream.lgo.LgoAdapter; import info.bitrich.xchangestream.lgo.dto.LgoTrade; +import java.util.List; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Trade; -import java.util.List; - -import static java.util.stream.Collectors.*; - public class LgoGroupedTradeUpdate { - private long lastBatchId; - private CurrencyPair currencyPair; - private List trades; - - public LgoGroupedTradeUpdate(CurrencyPair currencyPair) { - this.currencyPair = currencyPair; - } - - public void apply(long batchId, List trades) { - this.lastBatchId = batchId; - this.trades = trades.stream() - .map(lgoTrade -> LgoAdapter.adaptTrade(currencyPair, lgoTrade)) - .collect(toList()); - } - - public long getLastBatchId() { - return lastBatchId; - } - - public List getTrades() { - return trades; - } + private long lastBatchId; + private CurrencyPair currencyPair; + private List trades; + + public LgoGroupedTradeUpdate(CurrencyPair currencyPair) { + this.currencyPair = currencyPair; + } + + public void apply(long batchId, List trades) { + this.lastBatchId = batchId; + this.trades = + trades.stream() + .map(lgoTrade -> LgoAdapter.adaptTrade(currencyPair, lgoTrade)) + .collect(toList()); + } + + public long getLastBatchId() { + return lastBatchId; + } + + public List getTrades() { + return trades; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedUserUpdate.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedUserUpdate.java index c6d4c4707..2859f9bf4 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedUserUpdate.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoGroupedUserUpdate.java @@ -1,52 +1,55 @@ package info.bitrich.xchangestream.lgo.domain; -import org.knowm.xchange.dto.Order; - import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import org.knowm.xchange.dto.Order; public class LgoGroupedUserUpdate { - private final Map allOpenOrders; - private final List updatedOrders; - private final List events; - private final long batchId; - private final String type; - - public LgoGroupedUserUpdate() { - updatedOrders = Collections.emptyList(); - events = Collections.emptyList(); - batchId = 0; - type = ""; - allOpenOrders = new ConcurrentHashMap<>(); - } - - public LgoGroupedUserUpdate(Map allOpenOrders, List updatedOrders, List events, long batchId, String type) { - this.allOpenOrders = allOpenOrders; - this.updatedOrders = updatedOrders; - this.events = events; - this.batchId = batchId; - this.type = type; - } - - public List getUpdatedOrders() { - return updatedOrders; - } - - public Map getAllOpenOrders() { - return allOpenOrders; - } - - public List getEvents() { - return events; - } - - public long getBatchId() { - return batchId; - } - - public String getType() { - return type; - } - + private final Map allOpenOrders; + private final List updatedOrders; + private final List events; + private final long batchId; + private final String type; + + public LgoGroupedUserUpdate() { + updatedOrders = Collections.emptyList(); + events = Collections.emptyList(); + batchId = 0; + type = ""; + allOpenOrders = new ConcurrentHashMap<>(); + } + + public LgoGroupedUserUpdate( + Map allOpenOrders, + List updatedOrders, + List events, + long batchId, + String type) { + this.allOpenOrders = allOpenOrders; + this.updatedOrders = updatedOrders; + this.events = events; + this.batchId = batchId; + this.type = type; + } + + public List getUpdatedOrders() { + return updatedOrders; + } + + public Map getAllOpenOrders() { + return allOpenOrders; + } + + public List getEvents() { + return events; + } + + public long getBatchId() { + return batchId; + } + + public String getType() { + return type; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoInvalidOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoInvalidOrderEvent.java index 7c5c5c46b..fdf6488d5 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoInvalidOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoInvalidOrderEvent.java @@ -1,43 +1,43 @@ package info.bitrich.xchangestream.lgo.domain; import com.fasterxml.jackson.annotation.*; +import java.util.*; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; -import java.util.*; - -/** - * Order was invalid - */ +/** Order was invalid */ public class LgoInvalidOrderEvent extends LgoBatchOrderEvent { - /** - * Reason of invalidity (InvalidQuantity, InvalidPrice, InvalidAmount, InvalidPriceIncrement, InvalidProduct, InsufficientFunds) - */ - private final String reason; - - public LgoInvalidOrderEvent(Long batchId, String type, String orderId, Date time, String reason) { - super(batchId, type, orderId, time); - this.reason = reason; - } - - public LgoInvalidOrderEvent( - @JsonProperty("type") String type, - @JsonProperty("order_id") String orderId, - @JsonProperty("time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date time, - @JsonProperty("reason") String reason) { - super(type, orderId, time); - this.reason = reason; - } - - public String getReason() { - return reason; - } - - @Override - public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { - Order doneOrder = allOrders.remove(getOrderId()); - doneOrder.setOrderStatus(Order.OrderStatus.REJECTED); - return doneOrder; - } + /** + * Reason of invalidity (InvalidQuantity, InvalidPrice, InvalidAmount, InvalidPriceIncrement, + * InvalidProduct, InsufficientFunds) + */ + private final String reason; + + public LgoInvalidOrderEvent(Long batchId, String type, String orderId, Date time, String reason) { + super(batchId, type, orderId, time); + this.reason = reason; + } + + public LgoInvalidOrderEvent( + @JsonProperty("type") String type, + @JsonProperty("order_id") String orderId, + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date time, + @JsonProperty("reason") String reason) { + super(type, orderId, time); + this.reason = reason; + } + + public String getReason() { + return reason; + } + + @Override + public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { + Order doneOrder = allOrders.remove(getOrderId()); + doneOrder.setOrderStatus(Order.OrderStatus.REJECTED); + return doneOrder; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoMatchOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoMatchOrderEvent.java index 5cb55e67d..7a670bb36 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoMatchOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoMatchOrderEvent.java @@ -2,121 +2,117 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; - import java.math.BigDecimal; import java.util.Date; import java.util.Map; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; -/** - * Order matched against another order - */ +/** Order matched against another order */ public class LgoMatchOrderEvent extends LgoBatchOrderEvent { - /** - * Trade identifier - */ - private final String tradeId; - - /** - * Trade price (quote currency) - */ - private final BigDecimal tradePrice; - - /** - * Trade quantity (base currency) - */ - private final BigDecimal filledQuantity; - - /** - * Remaining amount (base currency) - */ - private final BigDecimal remainingQuantity; - - /** - * Fees (quote currency) - */ - private final BigDecimal fees; - - /** - * Trade liquidity (T for taker, M for maker) - */ - private final String liquidity; - - /** - * Trade type (BID/ASK): taker order type - */ - private Order.OrderType orderType; - - public LgoMatchOrderEvent( - @JsonProperty("type") String type, - @JsonProperty("order_id") String orderId, - @JsonProperty("time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date time, - @JsonProperty("trade_id") String tradeId, - @JsonProperty("price") BigDecimal price, - @JsonProperty("filled_quantity") BigDecimal filledQuantity, - @JsonProperty("remaining_quantity") BigDecimal remainingQuantity, - @JsonProperty("fees") BigDecimal fees, - @JsonProperty("liquidity") String liquidity) { - super(type, orderId, time); - this.tradeId = tradeId; - this.tradePrice = price; - this.filledQuantity = filledQuantity; - this.remainingQuantity = remainingQuantity; - this.fees = fees; - this.liquidity = liquidity; - } - - public LgoMatchOrderEvent(Long batchId, String type, String orderId, Date time, String tradeId, BigDecimal tradePrice, BigDecimal filledQuantity, BigDecimal remainingQuantity, BigDecimal fees, String liquidity, Order.OrderType orderType) { - super(batchId, type, orderId, time); - this.tradeId = tradeId; - this.tradePrice = tradePrice; - this.filledQuantity = filledQuantity; - this.remainingQuantity = remainingQuantity; - this.fees = fees; - this.liquidity = liquidity; - this.orderType = orderType; - } - - public String getTradeId() { - return tradeId; - } - - public BigDecimal getTradePrice() { - return tradePrice; - } - - public BigDecimal getFilledQuantity() { - return filledQuantity; - } - - public BigDecimal getRemainingQuantity() { - return remainingQuantity; - } - - public BigDecimal getFees() { - return fees; - } - - public String getLiquidity() { - return liquidity; - } - - public Order.OrderType getOrderType() { - return orderType; - } - - public void setOrderType(Order.OrderType orderType) { - this.orderType = orderType; - } - - @Override - public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { - Order matchedOrder = allOrders.get(getOrderId()); - matchedOrder.setOrderStatus(Order.OrderStatus.PARTIALLY_FILLED); - matchedOrder.setCumulativeAmount(matchedOrder.getOriginalAmount().subtract(remainingQuantity)); - BigDecimal fee = matchedOrder.getFee() == null ? fees : matchedOrder.getFee().add(fees); - matchedOrder.setFee(fee); - return matchedOrder; - } + /** Trade identifier */ + private final String tradeId; + + /** Trade price (quote currency) */ + private final BigDecimal tradePrice; + + /** Trade quantity (base currency) */ + private final BigDecimal filledQuantity; + + /** Remaining amount (base currency) */ + private final BigDecimal remainingQuantity; + + /** Fees (quote currency) */ + private final BigDecimal fees; + + /** Trade liquidity (T for taker, M for maker) */ + private final String liquidity; + + /** Trade type (BID/ASK): taker order type */ + private Order.OrderType orderType; + + public LgoMatchOrderEvent( + @JsonProperty("type") String type, + @JsonProperty("order_id") String orderId, + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date time, + @JsonProperty("trade_id") String tradeId, + @JsonProperty("price") BigDecimal price, + @JsonProperty("filled_quantity") BigDecimal filledQuantity, + @JsonProperty("remaining_quantity") BigDecimal remainingQuantity, + @JsonProperty("fees") BigDecimal fees, + @JsonProperty("liquidity") String liquidity) { + super(type, orderId, time); + this.tradeId = tradeId; + this.tradePrice = price; + this.filledQuantity = filledQuantity; + this.remainingQuantity = remainingQuantity; + this.fees = fees; + this.liquidity = liquidity; + } + + public LgoMatchOrderEvent( + Long batchId, + String type, + String orderId, + Date time, + String tradeId, + BigDecimal tradePrice, + BigDecimal filledQuantity, + BigDecimal remainingQuantity, + BigDecimal fees, + String liquidity, + Order.OrderType orderType) { + super(batchId, type, orderId, time); + this.tradeId = tradeId; + this.tradePrice = tradePrice; + this.filledQuantity = filledQuantity; + this.remainingQuantity = remainingQuantity; + this.fees = fees; + this.liquidity = liquidity; + this.orderType = orderType; + } + + public String getTradeId() { + return tradeId; + } + + public BigDecimal getTradePrice() { + return tradePrice; + } + + public BigDecimal getFilledQuantity() { + return filledQuantity; + } + + public BigDecimal getRemainingQuantity() { + return remainingQuantity; + } + + public BigDecimal getFees() { + return fees; + } + + public String getLiquidity() { + return liquidity; + } + + public Order.OrderType getOrderType() { + return orderType; + } + + public void setOrderType(Order.OrderType orderType) { + this.orderType = orderType; + } + + @Override + public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { + Order matchedOrder = allOrders.get(getOrderId()); + matchedOrder.setOrderStatus(Order.OrderStatus.PARTIALLY_FILLED); + matchedOrder.setCumulativeAmount(matchedOrder.getOriginalAmount().subtract(remainingQuantity)); + BigDecimal fee = matchedOrder.getFee() == null ? fees : matchedOrder.getFee().add(fees); + matchedOrder.setFee(fee); + return matchedOrder; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOpenOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOpenOrderEvent.java index 46c39c7bc..c033eae80 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOpenOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOpenOrderEvent.java @@ -2,33 +2,35 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; - import java.util.Date; import java.util.Map; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; -/** - * Order entered order book - */ +/** Order entered order book */ public class LgoOpenOrderEvent extends LgoBatchOrderEvent { - public LgoOpenOrderEvent(Long batchId, String type, String orderId, Date time) { - super(batchId, type, orderId, time); - } + public LgoOpenOrderEvent(Long batchId, String type, String orderId, Date time) { + super(batchId, type, orderId, time); + } - public LgoOpenOrderEvent( - @JsonProperty("type") String type, - @JsonProperty("order_id") String orderId, - @JsonProperty("time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date time) { - super(type, orderId, time); - } + public LgoOpenOrderEvent( + @JsonProperty("type") String type, + @JsonProperty("order_id") String orderId, + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date time) { + super(type, orderId, time); + } - @Override - public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { - Order pendingOrder = allOrders.get(getOrderId()); - Order.OrderStatus status = pendingOrder.getStatus().equals(Order.OrderStatus.PARTIALLY_FILLED) ? pendingOrder.getStatus() : Order.OrderStatus.NEW; - pendingOrder.setOrderStatus(status); - return pendingOrder; - } + @Override + public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { + Order pendingOrder = allOrders.get(getOrderId()); + Order.OrderStatus status = + pendingOrder.getStatus().equals(Order.OrderStatus.PARTIALLY_FILLED) + ? pendingOrder.getStatus() + : Order.OrderStatus.NEW; + pendingOrder.setOrderStatus(status); + return pendingOrder; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOrderEvent.java index 38f5b3d5a..abe7a9ef3 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoOrderEvent.java @@ -2,54 +2,44 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; - import java.util.Date; -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - property = "type", - visible = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true) @JsonSubTypes({ - @JsonSubTypes.Type(value = LgoPendingOrderEvent.class, name = "pending"), - @JsonSubTypes.Type(value = LgoOpenOrderEvent.class, name = "open"), - @JsonSubTypes.Type(value = LgoMatchOrderEvent.class, name = "match"), - @JsonSubTypes.Type(value = LgoInvalidOrderEvent.class, name = "invalid"), - @JsonSubTypes.Type(value = LgoDoneOrderEvent.class, name = "done"), - @JsonSubTypes.Type(value = LgoReceivedOrderEvent.class, name = "received"), - @JsonSubTypes.Type(value = LgoFailedOrderEvent.class, name = "failed") + @JsonSubTypes.Type(value = LgoPendingOrderEvent.class, name = "pending"), + @JsonSubTypes.Type(value = LgoOpenOrderEvent.class, name = "open"), + @JsonSubTypes.Type(value = LgoMatchOrderEvent.class, name = "match"), + @JsonSubTypes.Type(value = LgoInvalidOrderEvent.class, name = "invalid"), + @JsonSubTypes.Type(value = LgoDoneOrderEvent.class, name = "done"), + @JsonSubTypes.Type(value = LgoReceivedOrderEvent.class, name = "received"), + @JsonSubTypes.Type(value = LgoFailedOrderEvent.class, name = "failed") }) public abstract class LgoOrderEvent { - /** - * Type of the event (all events). - */ - private final String type; - - /** - * Identifier of the order concerned by the event (all events) - */ - private final String orderId; - - /** - * Date when the event happend, UTC (all events) - */ - private final Date time; - - protected LgoOrderEvent(String type, String orderId, Date time) { - this.type = type; - this.orderId = orderId; - this.time = time; - } - - public String getType() { - return type; - } - - public String getOrderId() { - return orderId; - } - - public Date getTime() { - return time; - } + /** Type of the event (all events). */ + private final String type; + + /** Identifier of the order concerned by the event (all events) */ + private final String orderId; + + /** Date when the event happend, UTC (all events) */ + private final Date time; + + protected LgoOrderEvent(String type, String orderId, Date time) { + this.type = type; + this.orderId = orderId; + this.time = time; + } + + public String getType() { + return type; + } + + public String getOrderId() { + return orderId; + } + + public Date getTime() { + return time; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoPendingOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoPendingOrderEvent.java index 32fe1af46..912fcbe12 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoPendingOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoPendingOrderEvent.java @@ -3,81 +3,80 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import info.bitrich.xchangestream.lgo.LgoAdapter; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.Order; - import java.math.BigDecimal; import java.util.Date; import java.util.Map; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; -/** - * Order was received by LGO execution engine in a batch - */ +/** Order was received by LGO execution engine in a batch */ public class LgoPendingOrderEvent extends LgoBatchOrderEvent { - /** - * Type of Order (L for LimitOrder, M for MarketOrder) when type=pending - */ - private final String orderType; + /** Type of Order (L for LimitOrder, M for MarketOrder) when type=pending */ + private final String orderType; - /** - * Limit price (quote currency) when type=pending and orderType=L - */ - private final BigDecimal limitPrice; + /** Limit price (quote currency) when type=pending and orderType=L */ + private final BigDecimal limitPrice; - /** - * Side of the order: BID/ASK when type=pending - */ - private final Order.OrderType side; + /** Side of the order: BID/ASK when type=pending */ + private final Order.OrderType side; - /** - * Initial amount (base currency) when type=pending - */ - private final BigDecimal initialAmount; + /** Initial amount (base currency) when type=pending */ + private final BigDecimal initialAmount; - public LgoPendingOrderEvent( - @JsonProperty("type") String type, - @JsonProperty("order_id") String orderId, - @JsonProperty("time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date time, - @JsonProperty("order_type") String orderType, - @JsonProperty("price") BigDecimal price, - @JsonProperty("side") String side, - @JsonProperty("quantity") BigDecimal quantity) { - super(type, orderId, time); - this.orderType = orderType; - this.limitPrice = price; - this.side = side.equals("B") ? Order.OrderType.BID : Order.OrderType.ASK; - this.initialAmount = quantity; - } + public LgoPendingOrderEvent( + @JsonProperty("type") String type, + @JsonProperty("order_id") String orderId, + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date time, + @JsonProperty("order_type") String orderType, + @JsonProperty("price") BigDecimal price, + @JsonProperty("side") String side, + @JsonProperty("quantity") BigDecimal quantity) { + super(type, orderId, time); + this.orderType = orderType; + this.limitPrice = price; + this.side = side.equals("B") ? Order.OrderType.BID : Order.OrderType.ASK; + this.initialAmount = quantity; + } - public LgoPendingOrderEvent(Long batchId, String type, String orderId, Date time, String orderType, BigDecimal limitPrice, Order.OrderType side, BigDecimal initialAmount) { - super(batchId, type, orderId, time); - this.orderType = orderType; - this.limitPrice = limitPrice; - this.side = side; - this.initialAmount = initialAmount; - } + public LgoPendingOrderEvent( + Long batchId, + String type, + String orderId, + Date time, + String orderType, + BigDecimal limitPrice, + Order.OrderType side, + BigDecimal initialAmount) { + super(batchId, type, orderId, time); + this.orderType = orderType; + this.limitPrice = limitPrice; + this.side = side; + this.initialAmount = initialAmount; + } - @Override - public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { - Order order = LgoAdapter.adaptPendingOrder(this, currencyPair); - allOrders.put(order.getId(), order); - return order; - } + @Override + public Order applyOnOrders(CurrencyPair currencyPair, Map allOrders) { + Order order = LgoAdapter.adaptPendingOrder(this, currencyPair); + allOrders.put(order.getId(), order); + return order; + } - public String getOrderType() { - return orderType; - } + public String getOrderType() { + return orderType; + } - public BigDecimal getLimitPrice() { - return limitPrice; - } + public BigDecimal getLimitPrice() { + return limitPrice; + } - public Order.OrderType getSide() { - return side; - } + public Order.OrderType getSide() { + return side; + } - public BigDecimal getInitialAmount() { - return initialAmount; - } + public BigDecimal getInitialAmount() { + return initialAmount; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoReceivedOrderEvent.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoReceivedOrderEvent.java index f90484a9b..b6a01c8fb 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoReceivedOrderEvent.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/domain/LgoReceivedOrderEvent.java @@ -2,27 +2,25 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Date; -/** - * Order was received by LGO platform. - * No batchId for this event. - */ +/** Order was received by LGO platform. No batchId for this event. */ public class LgoReceivedOrderEvent extends LgoAckOrderEvent { - private final String reference; + private final String reference; - public LgoReceivedOrderEvent( - @JsonProperty("order_id") String orderId, - @JsonProperty("reference") String reference, - @JsonProperty("type") String type, - @JsonProperty("time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date time) { - super(type, orderId, time); - this.reference = reference; - } + public LgoReceivedOrderEvent( + @JsonProperty("order_id") String orderId, + @JsonProperty("reference") String reference, + @JsonProperty("type") String type, + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date time) { + super(type, orderId, time); + this.reference = reference; + } - public String getReference() { - return reference; - } + public String getReference() { + return reference; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoAckUpdate.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoAckUpdate.java index 1fb440811..78facad8e 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoAckUpdate.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoAckUpdate.java @@ -2,33 +2,32 @@ import com.fasterxml.jackson.annotation.JsonProperty; import info.bitrich.xchangestream.lgo.domain.LgoAckOrderEvent; - import java.util.List; public class LgoAckUpdate { - private final String type; - private final String channel; - private final List data; - - public LgoAckUpdate( - @JsonProperty("type") String type, - @JsonProperty("channel") String channel, - @JsonProperty("payload") List data) { - this.type = type; - this.channel = channel; - this.data = data; - } - - public String getType() { - return type; - } - - public String getChannel() { - return channel; - } - - public List getData() { - return data; - } + private final String type; + private final String channel; + private final List data; + + public LgoAckUpdate( + @JsonProperty("type") String type, + @JsonProperty("channel") String channel, + @JsonProperty("payload") List data) { + this.type = type; + this.channel = channel; + this.data = data; + } + + public String getType() { + return type; + } + + public String getChannel() { + return channel; + } + + public List getData() { + return data; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoBalanceUpdate.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoBalanceUpdate.java index 42cb70281..0392180c9 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoBalanceUpdate.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoBalanceUpdate.java @@ -1,39 +1,38 @@ package info.bitrich.xchangestream.lgo.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.List; public class LgoBalanceUpdate { - private final long seq; - private final String type; - private final String channel; - private final List> data; - - public LgoBalanceUpdate( - @JsonProperty("seq") long seq, - @JsonProperty("type") String type, - @JsonProperty("channel") String channel, - @JsonProperty("payload") List> data) { - this.seq = seq; - this.type = type; - this.channel = channel; - this.data = data; - } - - public long getSeq() { - return seq; - } - - public String getType() { - return type; - } - - public String getChannel() { - return channel; - } - - public List> getData() { - return data; - } + private final long seq; + private final String type; + private final String channel; + private final List> data; + + public LgoBalanceUpdate( + @JsonProperty("seq") long seq, + @JsonProperty("type") String type, + @JsonProperty("channel") String channel, + @JsonProperty("payload") List> data) { + this.seq = seq; + this.type = type; + this.channel = channel; + this.data = data; + } + + public long getSeq() { + return seq; + } + + public String getType() { + return type; + } + + public String getChannel() { + return channel; + } + + public List> getData() { + return data; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Data.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Data.java index ad9be730c..283f5e5cc 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Data.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Data.java @@ -1,26 +1,25 @@ package info.bitrich.xchangestream.lgo.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.List; public class LgoLevel2Data { - private final List> bids; - private final List> asks; + private final List> bids; + private final List> asks; - public LgoLevel2Data( - @JsonProperty("bids") List> bids, - @JsonProperty("asks") List> asks) { - this.bids = bids; - this.asks = asks; - } + public LgoLevel2Data( + @JsonProperty("bids") List> bids, + @JsonProperty("asks") List> asks) { + this.bids = bids; + this.asks = asks; + } - public List> getBids() { - return bids; - } + public List> getBids() { + return bids; + } - public List> getAsks() { - return asks; - } + public List> getAsks() { + return asks; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Update.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Update.java index 7408a8ae0..6729a6a98 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Update.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoLevel2Update.java @@ -4,42 +4,42 @@ public class LgoLevel2Update { - private final long batchId; - private final String type; - private final String channel; - private final String productId; - private final LgoLevel2Data data; - - public LgoLevel2Update( - @JsonProperty("batch_id") long batchId, - @JsonProperty("type") String type, - @JsonProperty("channel") String channel, - @JsonProperty("product_id") String productId, - @JsonProperty("payload") LgoLevel2Data data) { - this.batchId = batchId; - this.type = type; - this.channel = channel; - this.productId = productId; - this.data = data; - } - - public long getBatchId() { - return batchId; - } - - public String getType() { - return type; - } - - public String getChannel() { - return channel; - } - - public String getProductId() { - return productId; - } - - public LgoLevel2Data getData() { - return data; - } + private final long batchId; + private final String type; + private final String channel; + private final String productId; + private final LgoLevel2Data data; + + public LgoLevel2Update( + @JsonProperty("batch_id") long batchId, + @JsonProperty("type") String type, + @JsonProperty("channel") String channel, + @JsonProperty("product_id") String productId, + @JsonProperty("payload") LgoLevel2Data data) { + this.batchId = batchId; + this.type = type; + this.channel = channel; + this.productId = productId; + this.data = data; + } + + public long getBatchId() { + return batchId; + } + + public String getType() { + return type; + } + + public String getChannel() { + return channel; + } + + public String getProductId() { + return productId; + } + + public LgoLevel2Data getData() { + return data; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSocketPlaceOrder.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSocketPlaceOrder.java index 62ddc834a..0eacce5ea 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSocketPlaceOrder.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSocketPlaceOrder.java @@ -4,19 +4,17 @@ public class LgoSocketPlaceOrder { - private final LgoEncryptedOrder order; + private final LgoEncryptedOrder order; - public LgoSocketPlaceOrder(LgoEncryptedOrder encryptedOrder) { - this.order = encryptedOrder; - } - - public String getType() { - return "placeorder"; - } - - public LgoEncryptedOrder getOrder() { - return order; - } + public LgoSocketPlaceOrder(LgoEncryptedOrder encryptedOrder) { + this.order = encryptedOrder; + } + public String getType() { + return "placeorder"; + } + public LgoEncryptedOrder getOrder() { + return order; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSubscription.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSubscription.java index 6fd01ff22..098887038 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSubscription.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoSubscription.java @@ -2,43 +2,42 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Collections; import java.util.List; public class LgoSubscription { - public final String type; - public final List channels; - - public LgoSubscription(String type, Channel channels) { - this.type = type; - this.channels = Collections.singletonList(channels); - } - - public static LgoSubscription subscribe(String channelName) { - return new LgoSubscription("subscribe", new LgoSubscription.Channel(channelName)); - } - - public static LgoSubscription unsubscribe(String channelName) { - return new LgoSubscription("unsubscribe", new LgoSubscription.Channel(channelName)); - } - - @JsonInclude(JsonInclude.Include.NON_NULL) - public static class Channel { - public final String name; - - @JsonProperty("product_id") - public final String productId; - - public Channel(String channelName) { - if (channelName.contains("-")) { - String[] strings = channelName.split("-"); - this.name = strings[0]; - this.productId = strings[1] + "-" + strings[2]; - } else { - this.name = channelName; - this.productId = null; - } - } + public final String type; + public final List channels; + + public LgoSubscription(String type, Channel channels) { + this.type = type; + this.channels = Collections.singletonList(channels); + } + + public static LgoSubscription subscribe(String channelName) { + return new LgoSubscription("subscribe", new LgoSubscription.Channel(channelName)); + } + + public static LgoSubscription unsubscribe(String channelName) { + return new LgoSubscription("unsubscribe", new LgoSubscription.Channel(channelName)); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Channel { + public final String name; + + @JsonProperty("product_id") + public final String productId; + + public Channel(String channelName) { + if (channelName.contains("-")) { + String[] strings = channelName.split("-"); + this.name = strings[0]; + this.productId = strings[1] + "-" + strings[2]; + } else { + this.name = channelName; + this.productId = null; + } } + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTrade.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTrade.java index f0082930c..4a0b850a9 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTrade.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTrade.java @@ -2,48 +2,49 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; import java.util.Date; public class LgoTrade { - private final String id; - private final String side; - private final BigDecimal price; - private final BigDecimal quantity; - private final Date creationTime; - - public LgoTrade( - @JsonProperty("trade_id") String id, - @JsonProperty("side") String side, - @JsonProperty("price") BigDecimal price, - @JsonProperty("quantity") BigDecimal quantity, - @JsonProperty("trade_creation_time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date creationTime) { - this.id = id; - this.side = side; - this.price = price; - this.quantity = quantity; - this.creationTime = creationTime; - } - - public String getId() { - return id; - } - - public String getSide() { - return side; - } - - public BigDecimal getPrice() { - return price; - } - - public BigDecimal getQuantity() { - return quantity; - } - - public Date getCreationTime() { - return creationTime; - } + private final String id; + private final String side; + private final BigDecimal price; + private final BigDecimal quantity; + private final Date creationTime; + + public LgoTrade( + @JsonProperty("trade_id") String id, + @JsonProperty("side") String side, + @JsonProperty("price") BigDecimal price, + @JsonProperty("quantity") BigDecimal quantity, + @JsonProperty("trade_creation_time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date creationTime) { + this.id = id; + this.side = side; + this.price = price; + this.quantity = quantity; + this.creationTime = creationTime; + } + + public String getId() { + return id; + } + + public String getSide() { + return side; + } + + public BigDecimal getPrice() { + return price; + } + + public BigDecimal getQuantity() { + return quantity; + } + + public Date getCreationTime() { + return creationTime; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTradesUpdate.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTradesUpdate.java index 5010c1399..396a72b04 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTradesUpdate.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoTradesUpdate.java @@ -1,47 +1,46 @@ package info.bitrich.xchangestream.lgo.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.List; public class LgoTradesUpdate { - private final long batchId; - private final String type; - private final String channel; - private final String productId; - private final List trades; - - public LgoTradesUpdate( - @JsonProperty("batch_id") long batchId, - @JsonProperty("type") String type, - @JsonProperty("channel") String channel, - @JsonProperty("product_id") String productId, - @JsonProperty("payload") List trades) { - this.batchId = batchId; - this.type = type; - this.channel = channel; - this.productId = productId; - this.trades = trades; - } - - public long getBatchId() { - return batchId; - } - - public String getType() { - return type; - } - - public String getChannel() { - return channel; - } - - public String getProductId() { - return productId; - } - - public List getTrades() { - return trades; - } + private final long batchId; + private final String type; + private final String channel; + private final String productId; + private final List trades; + + public LgoTradesUpdate( + @JsonProperty("batch_id") long batchId, + @JsonProperty("type") String type, + @JsonProperty("channel") String channel, + @JsonProperty("product_id") String productId, + @JsonProperty("payload") List trades) { + this.batchId = batchId; + this.type = type; + this.channel = channel; + this.productId = productId; + this.trades = trades; + } + + public long getBatchId() { + return batchId; + } + + public String getType() { + return type; + } + + public String getChannel() { + return channel; + } + + public String getProductId() { + return productId; + } + + public List getTrades() { + return trades; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserMessage.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserMessage.java index 73495bc6e..83ad016dc 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserMessage.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserMessage.java @@ -3,42 +3,38 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - property = "type", - visible = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true) @JsonSubTypes({ - @JsonSubTypes.Type(value = LgoUserUpdate.class, name = "update"), - @JsonSubTypes.Type(value = LgoUserSnapshot.class, name = "snapshot") + @JsonSubTypes.Type(value = LgoUserUpdate.class, name = "update"), + @JsonSubTypes.Type(value = LgoUserSnapshot.class, name = "snapshot") }) public class LgoUserMessage { - private final long batchId; - private final String type; - private final String channel; - private final String productId; - - public LgoUserMessage(long batchId, String type, String channel, String productId) { - this.batchId = batchId; - this.type = type; - this.channel = channel; - this.productId = productId; - } - - public long getBatchId() { - return batchId; - } - - public String getType() { - return type; - } - - public String getChannel() { - return channel; - } - - public String getProductId() { - return productId; - } - + private final long batchId; + private final String type; + private final String channel; + private final String productId; + + public LgoUserMessage(long batchId, String type, String channel, String productId) { + this.batchId = batchId; + this.type = type; + this.channel = channel; + this.productId = productId; + } + + public long getBatchId() { + return batchId; + } + + public String getType() { + return type; + } + + public String getChannel() { + return channel; + } + + public String getProductId() { + return productId; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshot.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshot.java index f223f7bb2..8ffb37129 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshot.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshot.java @@ -1,24 +1,23 @@ package info.bitrich.xchangestream.lgo.dto; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.List; public class LgoUserSnapshot extends LgoUserMessage { - private final List snapshotData; + private final List snapshotData; - public LgoUserSnapshot( - @JsonProperty("batch_id") long batchId, - @JsonProperty("type") String type, - @JsonProperty("channel") String channel, - @JsonProperty("product_id") String productId, - @JsonProperty("payload") List snapshotData) { - super(batchId, type, channel, productId); - this.snapshotData = snapshotData; - } + public LgoUserSnapshot( + @JsonProperty("batch_id") long batchId, + @JsonProperty("type") String type, + @JsonProperty("channel") String channel, + @JsonProperty("product_id") String productId, + @JsonProperty("payload") List snapshotData) { + super(batchId, type, channel, productId); + this.snapshotData = snapshotData; + } - public List getSnapshotData() { - return snapshotData; - } + public List getSnapshotData() { + return snapshotData; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshotData.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshotData.java index 83eae94ec..fa5fd7304 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshotData.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserSnapshotData.java @@ -2,62 +2,63 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; import java.util.Date; public class LgoUserSnapshotData { - private final String orderId; - private final String orderType; - private final BigDecimal price; - private final String side; - private final BigDecimal quantity; - private final BigDecimal remainingQuantity; - private final Date orderCreationTime; - - public LgoUserSnapshotData( - @JsonProperty("quantity") BigDecimal quantity, - @JsonProperty("price") BigDecimal price, - @JsonProperty("side") String side, - @JsonProperty("remaining_quantity") BigDecimal remainingQuantity, - @JsonProperty("order_id") String orderId, - @JsonProperty("order_type") String orderType, - @JsonProperty("order_creation_time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date orderCreationTime) { - this.orderId = orderId; - this.orderType = orderType; - this.price = price; - this.side = side; - this.quantity = quantity; - this.remainingQuantity = remainingQuantity; - this.orderCreationTime = orderCreationTime; - } - - public String getOrderId() { - return orderId; - } - - public String getOrderType() { - return orderType; - } - - public BigDecimal getPrice() { - return price; - } - - public String getSide() { - return side; - } - - public BigDecimal getQuantity() { - return quantity; - } - - public BigDecimal getRemainingQuantity() { - return remainingQuantity; - } - - public Date getOrderCreationTime() { - return orderCreationTime; - } + private final String orderId; + private final String orderType; + private final BigDecimal price; + private final String side; + private final BigDecimal quantity; + private final BigDecimal remainingQuantity; + private final Date orderCreationTime; + + public LgoUserSnapshotData( + @JsonProperty("quantity") BigDecimal quantity, + @JsonProperty("price") BigDecimal price, + @JsonProperty("side") String side, + @JsonProperty("remaining_quantity") BigDecimal remainingQuantity, + @JsonProperty("order_id") String orderId, + @JsonProperty("order_type") String orderType, + @JsonProperty("order_creation_time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + Date orderCreationTime) { + this.orderId = orderId; + this.orderType = orderType; + this.price = price; + this.side = side; + this.quantity = quantity; + this.remainingQuantity = remainingQuantity; + this.orderCreationTime = orderCreationTime; + } + + public String getOrderId() { + return orderId; + } + + public String getOrderType() { + return orderType; + } + + public BigDecimal getPrice() { + return price; + } + + public String getSide() { + return side; + } + + public BigDecimal getQuantity() { + return quantity; + } + + public BigDecimal getRemainingQuantity() { + return remainingQuantity; + } + + public Date getOrderCreationTime() { + return orderCreationTime; + } } diff --git a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserUpdate.java b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserUpdate.java index 8114e5415..ad6681623 100644 --- a/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserUpdate.java +++ b/xchange-stream-lgo/src/main/java/info/bitrich/xchangestream/lgo/dto/LgoUserUpdate.java @@ -2,24 +2,23 @@ import com.fasterxml.jackson.annotation.JsonProperty; import info.bitrich.xchangestream.lgo.domain.LgoBatchOrderEvent; - import java.util.List; public class LgoUserUpdate extends LgoUserMessage { - private final List orderEvents; + private final List orderEvents; - public LgoUserUpdate( - @JsonProperty("batch_id") long batchId, - @JsonProperty("type") String type, - @JsonProperty("channel") String channel, - @JsonProperty("product_id") String productId, - @JsonProperty("payload") List orderEvents) { - super(batchId, type, channel, productId); - this.orderEvents = orderEvents; - } + public LgoUserUpdate( + @JsonProperty("batch_id") long batchId, + @JsonProperty("type") String type, + @JsonProperty("channel") String channel, + @JsonProperty("product_id") String productId, + @JsonProperty("payload") List orderEvents) { + super(batchId, type, channel, productId); + this.orderEvents = orderEvents; + } - public List getOrderEvents() { - return orderEvents; - } + public List getOrderEvents() { + return orderEvents; + } } diff --git a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountServiceTest.java b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountServiceTest.java index 08e8980aa..fdb0b973a 100644 --- a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountServiceTest.java +++ b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingAccountServiceTest.java @@ -1,103 +1,107 @@ package info.bitrich.xchangestream.lgo; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + import com.fasterxml.jackson.databind.JsonNode; import io.reactivex.Observable; import io.reactivex.observers.TestObserver; -import org.junit.Test; -import org.knowm.xchange.currency.Currency; -import org.knowm.xchange.dto.account.*; - import java.io.IOException; import java.math.BigDecimal; import java.util.Arrays; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import org.junit.Test; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.dto.account.*; public class LgoStreamingAccountServiceTest { - @Test - public void it_gives_initial_wallet_snapshot() throws IOException { - JsonNode snapshot = TestUtils.getJsonContent("/account/balance-snapshot.json"); - LgoStreamingService streamingService = mock(LgoStreamingService.class); - LgoStreamingAccountService service = new LgoStreamingAccountService(streamingService); - Observable source = Observable.just(snapshot); - when(streamingService.subscribeChannel(anyString())).thenReturn(source); + @Test + public void it_gives_initial_wallet_snapshot() throws IOException { + JsonNode snapshot = TestUtils.getJsonContent("/account/balance-snapshot.json"); + LgoStreamingService streamingService = mock(LgoStreamingService.class); + LgoStreamingAccountService service = new LgoStreamingAccountService(streamingService); + Observable source = Observable.just(snapshot); + when(streamingService.subscribeChannel(anyString())).thenReturn(source); - TestObserver wallet = service.getWallet().test(); + TestObserver wallet = service.getWallet().test(); - verify(streamingService).subscribeChannel("balance"); - wallet.assertSubscribed(); - wallet.assertValueCount(1); - wallet.values().contains( - buildWallet( - new Balance( - Currency.BTC, - new BigDecimal("2301.01329566"), - new BigDecimal("2297.01329566"), - new BigDecimal("4.00000000")), - new Balance( - Currency.USD, - new BigDecimal("453616.3125"), - new BigDecimal("453616.3125"), - new BigDecimal("0.0000")))); - } + verify(streamingService).subscribeChannel("balance"); + wallet.assertSubscribed(); + wallet.assertValueCount(1); + wallet + .values() + .contains( + buildWallet( + new Balance( + Currency.BTC, + new BigDecimal("2301.01329566"), + new BigDecimal("2297.01329566"), + new BigDecimal("4.00000000")), + new Balance( + Currency.USD, + new BigDecimal("453616.3125"), + new BigDecimal("453616.3125"), + new BigDecimal("0.0000")))); + } - @Test - public void it_handles_update() throws IOException { - JsonNode snapshot = TestUtils.getJsonContent("/account/balance-snapshot.json"); - JsonNode update = TestUtils.getJsonContent("/account/balance-update.json"); - LgoStreamingService streamingService = mock(LgoStreamingService.class); - LgoStreamingAccountService service = new LgoStreamingAccountService(streamingService); - Observable source = Observable.just(snapshot, update); - when(streamingService.subscribeChannel(anyString())).thenReturn(source); + @Test + public void it_handles_update() throws IOException { + JsonNode snapshot = TestUtils.getJsonContent("/account/balance-snapshot.json"); + JsonNode update = TestUtils.getJsonContent("/account/balance-update.json"); + LgoStreamingService streamingService = mock(LgoStreamingService.class); + LgoStreamingAccountService service = new LgoStreamingAccountService(streamingService); + Observable source = Observable.just(snapshot, update); + when(streamingService.subscribeChannel(anyString())).thenReturn(source); - service.getWallet().subscribe(); + service.getWallet().subscribe(); - TestObserver wallet = service.getWallet().test(); - wallet.assertValueAt(1, - buildWallet( - new Balance( - Currency.BTC, - new BigDecimal("2299.01329566"), - new BigDecimal("2295.01329566"), - new BigDecimal("4.00000000")), - new Balance( - Currency.USD, - new BigDecimal("453616.3125"), - new BigDecimal("453616.3125"), - new BigDecimal("0.0000")))); - verify(streamingService).subscribeChannel("balance"); - } + TestObserver wallet = service.getWallet().test(); + wallet.assertValueAt( + 1, + buildWallet( + new Balance( + Currency.BTC, + new BigDecimal("2299.01329566"), + new BigDecimal("2295.01329566"), + new BigDecimal("4.00000000")), + new Balance( + Currency.USD, + new BigDecimal("453616.3125"), + new BigDecimal("453616.3125"), + new BigDecimal("0.0000")))); + verify(streamingService).subscribeChannel("balance"); + } - private Wallet buildWallet(Balance... balances) { - return Wallet.Builder.from(Arrays.asList(balances)).build(); - } + private Wallet buildWallet(Balance... balances) { + return Wallet.Builder.from(Arrays.asList(balances)).build(); + } - @Test - public void it_connects_one_time_and_filters_by_currency() throws IOException { - JsonNode snapshot = TestUtils.getJsonContent("/account/balance-snapshot.json"); - JsonNode update = TestUtils.getJsonContent("/account/balance-update.json"); - LgoStreamingService streamingService = mock(LgoStreamingService.class); - LgoStreamingAccountService service = new LgoStreamingAccountService(streamingService); - when(streamingService.subscribeChannel(anyString())) - .thenReturn(Observable.just(snapshot, update)); + @Test + public void it_connects_one_time_and_filters_by_currency() throws IOException { + JsonNode snapshot = TestUtils.getJsonContent("/account/balance-snapshot.json"); + JsonNode update = TestUtils.getJsonContent("/account/balance-update.json"); + LgoStreamingService streamingService = mock(LgoStreamingService.class); + LgoStreamingAccountService service = new LgoStreamingAccountService(streamingService); + when(streamingService.subscribeChannel(anyString())) + .thenReturn(Observable.just(snapshot, update)); - TestObserver btcChanges = service.getBalanceChanges(Currency.BTC).test(); - TestObserver usdChanges = service.getBalanceChanges(Currency.USD).test(); + TestObserver btcChanges = service.getBalanceChanges(Currency.BTC).test(); + TestObserver usdChanges = service.getBalanceChanges(Currency.USD).test(); - verify(streamingService, times(1)).subscribeChannel("balance"); - btcChanges.assertValueAt(1, - new Balance( - Currency.BTC, - new BigDecimal("2299.01329566"), - new BigDecimal("2295.01329566"), - new BigDecimal("4.00000000"))); - usdChanges.assertValueAt(1, - new Balance( - Currency.USD, - new BigDecimal("453616.3125"), - new BigDecimal("453616.3125"), - new BigDecimal("0.0000"))); - } + verify(streamingService, times(1)).subscribeChannel("balance"); + btcChanges.assertValueAt( + 1, + new Balance( + Currency.BTC, + new BigDecimal("2299.01329566"), + new BigDecimal("2295.01329566"), + new BigDecimal("4.00000000"))); + usdChanges.assertValueAt( + 1, + new Balance( + Currency.USD, + new BigDecimal("453616.3125"), + new BigDecimal("453616.3125"), + new BigDecimal("0.0000"))); + } } diff --git a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingExchangeExample.java b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingExchangeExample.java index fe52e21e8..3f9efdd6f 100644 --- a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingExchangeExample.java +++ b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingExchangeExample.java @@ -1,135 +1,144 @@ package info.bitrich.xchangestream.lgo; +import java.io.*; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.*; import org.apache.commons.io.IOUtils; import org.junit.*; import org.knowm.xchange.ExchangeSpecification; -import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.*; +import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.*; import org.knowm.xchange.lgo.LgoEnv; -import java.io.*; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.util.*; - @Ignore public class LgoStreamingExchangeExample { - private LgoStreamingExchange exchange; - - // api key and secret key are expected to be in test resources under - // example directory - // this directory is added to .gitignore to avoid committing a real usable key - @Before - public void setUp() throws Exception { - exchange = new LgoStreamingExchange(); - ExchangeSpecification spec = LgoEnv.sandbox(); - spec.setSecretKey(readResource("/example/private_key.pem")); - spec.setApiKey(readResource("/example/api_key.txt")); - spec.setShouldLoadRemoteMetaData(false); - exchange.applySpecification(spec); - exchange.connect().blockingAwait(); - } - - private String readResource(String path) throws IOException { - InputStream stream = LgoStreamingExchangeExample.class.getResourceAsStream(path); - return IOUtils.toString(stream, StandardCharsets.UTF_8); - } - - @Test - public void connectToMarketData() { - exchange - .getStreamingMarketDataService() - .getOrderBook(CurrencyPair.BTC_USD) - .blockingForEach(System.out::println); - } - - @Test - public void connectToTrades() { - exchange - .getStreamingMarketDataService() - .getTrades(CurrencyPair.BTC_USD) - .blockingForEach(System.out::println); - } - - @Test - public void connectToBalances() { - exchange - .getStreamingAccountService() - .getWallet() - .blockingForEach(System.out::println); - } - - @Test - public void connectToBalance() { - exchange - .getStreamingAccountService() - .getBalanceChanges(Currency.BTC) - .blockingForEach(System.out::println); - } - - @Test - public void connectToUserTrades() { - exchange - .getStreamingTradeService() - .getUserTrades(CurrencyPair.BTC_USD) - .blockingSubscribe(System.out::println); - } - - @Test - public void connectToOpenOrders() { - exchange - .getStreamingTradeService() - .getOpenOrders(CurrencyPair.BTC_USD) - .blockingForEach(System.out::println); - } - - @Test - public void connectToOrderChanges() { - exchange - .getStreamingTradeService() - .getOrderChanges(CurrencyPair.BTC_USD) - .blockingForEach(System.out::println); - } - - @Test - public void connectToOrderBatchChanges() { - exchange - .getStreamingTradeService() - .getOrderBatchChanges(CurrencyPair.BTC_USD) - .blockingForEach(System.out::println); - } - - @Test - public void connectToOrderEvents() { + private LgoStreamingExchange exchange; + + // api key and secret key are expected to be in test resources under + // example directory + // this directory is added to .gitignore to avoid committing a real usable key + @Before + public void setUp() throws Exception { + exchange = new LgoStreamingExchange(); + ExchangeSpecification spec = LgoEnv.sandbox(); + spec.setSecretKey(readResource("/example/private_key.pem")); + spec.setApiKey(readResource("/example/api_key.txt")); + spec.setShouldLoadRemoteMetaData(false); + exchange.applySpecification(spec); + exchange.connect().blockingAwait(); + } + + private String readResource(String path) throws IOException { + InputStream stream = LgoStreamingExchangeExample.class.getResourceAsStream(path); + return IOUtils.toString(stream, StandardCharsets.UTF_8); + } + + @Test + public void connectToMarketData() { + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_USD) + .blockingForEach(System.out::println); + } + + @Test + public void connectToTrades() { + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .blockingForEach(System.out::println); + } + + @Test + public void connectToBalances() { + exchange.getStreamingAccountService().getWallet().blockingForEach(System.out::println); + } + + @Test + public void connectToBalance() { + exchange + .getStreamingAccountService() + .getBalanceChanges(Currency.BTC) + .blockingForEach(System.out::println); + } + + @Test + public void connectToUserTrades() { + exchange + .getStreamingTradeService() + .getUserTrades(CurrencyPair.BTC_USD) + .blockingSubscribe(System.out::println); + } + + @Test + public void connectToOpenOrders() { + exchange + .getStreamingTradeService() + .getOpenOrders(CurrencyPair.BTC_USD) + .blockingForEach(System.out::println); + } + + @Test + public void connectToOrderChanges() { + exchange + .getStreamingTradeService() + .getOrderChanges(CurrencyPair.BTC_USD) + .blockingForEach(System.out::println); + } + + @Test + public void connectToOrderBatchChanges() { + exchange + .getStreamingTradeService() + .getOrderBatchChanges(CurrencyPair.BTC_USD) + .blockingForEach(System.out::println); + } + + @Test + public void connectToOrderEvents() { + exchange + .getStreamingTradeService() + .getRawAllOrderEvents(Collections.singletonList(CurrencyPair.BTC_USD)) + .blockingForEach(System.out::println); + } + + @Test + public void placeLimitOrder() throws IOException { + String ref = exchange - .getStreamingTradeService() - .getRawAllOrderEvents(Collections.singletonList(CurrencyPair.BTC_USD)) - .blockingForEach(System.out::println); - } - - @Test - public void placeLimitOrder() throws IOException { - String ref = exchange - .getStreamingTradeService() - .placeLimitOrder(new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.5"), CurrencyPair.BTC_USD, null, new Date(), new BigDecimal("12000"))); - System.out.println("Order was placed with reference: " + ref); - } - - @Test - public void placeMarketOrder() throws IOException { - String ref = exchange - .getStreamingTradeService() - .placeMarketOrder(new MarketOrder(Order.OrderType.ASK, new BigDecimal("0.5"), CurrencyPair.BTC_USD, null, new Date())); - System.out.println("Order was placed with reference: " + ref); - } - - @Test - public void cancelOrder() throws IOException { + .getStreamingTradeService() + .placeLimitOrder( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.5"), + CurrencyPair.BTC_USD, + null, + new Date(), + new BigDecimal("12000"))); + System.out.println("Order was placed with reference: " + ref); + } + + @Test + public void placeMarketOrder() throws IOException { + String ref = exchange - .getStreamingTradeService() - .cancelOrder("156406068135700001"); - } + .getStreamingTradeService() + .placeMarketOrder( + new MarketOrder( + Order.OrderType.ASK, + new BigDecimal("0.5"), + CurrencyPair.BTC_USD, + null, + new Date())); + System.out.println("Order was placed with reference: " + ref); + } + + @Test + public void cancelOrder() throws IOException { + exchange.getStreamingTradeService().cancelOrder("156406068135700001"); + } } diff --git a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataServiceTest.java b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataServiceTest.java index 24af35293..13e00e2f5 100644 --- a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataServiceTest.java +++ b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingMarketDataServiceTest.java @@ -1,7 +1,14 @@ package info.bitrich.xchangestream.lgo; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + import com.fasterxml.jackson.databind.JsonNode; import io.reactivex.Observable; +import java.io.IOException; +import java.math.BigDecimal; +import java.text.*; +import java.util.TimeZone; import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; import org.junit.*; import org.knowm.xchange.currency.CurrencyPair; @@ -9,105 +16,97 @@ import org.knowm.xchange.dto.marketdata.*; import org.knowm.xchange.dto.trade.LimitOrder; -import java.io.IOException; -import java.math.BigDecimal; -import java.text.*; -import java.util.TimeZone; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - public class LgoStreamingMarketDataServiceTest { - private final RecursiveComparisonConfiguration orderComparator = new RecursiveComparisonConfiguration(); - private SimpleDateFormat dateFormat; - - @Before - public void setUp() { - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - orderComparator.ignoreFields("userReference", "id"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - @Test - public void it_reads_trades() throws IOException, ParseException { - LgoStreamingService streamingService = mock(LgoStreamingService.class); - LgoStreamingMarketDataService service = new LgoStreamingMarketDataService(streamingService); - JsonNode message = TestUtils.getJsonContent("/marketdata/trades-update.json"); - when(streamingService.subscribeChannel(anyString())).thenReturn(Observable.just(message)); - - Observable trades = service.getTrades(CurrencyPair.BTC_USD); - - verify(streamingService).subscribeChannel("trades-BTC-USD"); - assertThat(trades.blockingFirst()) - .isEqualToComparingFieldByField( - new Trade.Builder() - .type(Order.OrderType.ASK) - .originalAmount(new BigDecimal("4.36920000")) - .currencyPair(CurrencyPair.BTC_USD) - .price(new BigDecimal("428.5000")) - .timestamp(dateFormat.parse("2019-07-19T12:25:01.596Z")) - .id("3128770") - .build()); - assertThat(trades.blockingLast()) - .isEqualToComparingFieldByField( - new Trade.Builder() - .type(Order.OrderType.BID) - .originalAmount(new BigDecimal("1.85390000")) - .currencyPair(CurrencyPair.BTC_USD) - .price(new BigDecimal("420.3000")) - .timestamp(dateFormat.parse("2019-07-19T12:25:05.860Z")) - .id("3128771") - .build()); - } - - @Test - public void it_reads_level2() throws IOException { - LgoStreamingService streamingService = mock(LgoStreamingService.class); - LgoStreamingMarketDataService service = new LgoStreamingMarketDataService(streamingService); - JsonNode snapshot = TestUtils.getJsonContent("/marketdata/level2-snapshot.json"); - JsonNode update = TestUtils.getJsonContent("/marketdata/level2-update.json"); - when(streamingService.subscribeChannel(anyString())) - .thenReturn(Observable.just(snapshot, update)); - - Observable orderBook = service.getOrderBook(CurrencyPair.BTC_USD); - - verify(streamingService).subscribeChannel("level2-BTC-USD"); - OrderBook firstBook = orderBook.blockingFirst(); - assertThat(firstBook.getAsks()) - .usingElementComparatorIgnoringFields("id", "userReference") - .contains(sellOrder("1111.1000", "9.39370000"), sellOrder("1115.9000", "0.88420000")); - assertThat(firstBook.getBids()) - .usingElementComparatorIgnoringFields("id", "userReference") - .contains(buyOrder("1089.1000", "0.10000000")); - OrderBook lastBook = orderBook.blockingLast(); - assertThat(lastBook.getBids()).isEmpty(); - assertThat(lastBook.getAsks()).usingElementComparatorIgnoringFields("id", "userReference") - .contains( - sellOrder("1111.1000", "0.10000000"), - sellOrder("1115.9000", "0.88420000"), - sellOrder("1116.9000", "0.20000000")); - - } - - private LimitOrder sellOrder(String price, String quantity) { - return new LimitOrder( - Order.OrderType.ASK, - new BigDecimal(quantity), - CurrencyPair.BTC_USD, - null, - null, - new BigDecimal(price)); - } - - private LimitOrder buyOrder(String price, String quantity) { - return new LimitOrder( - Order.OrderType.BID, - new BigDecimal(quantity), - CurrencyPair.BTC_USD, - "0", - null, - new BigDecimal(price)); - } - + private final RecursiveComparisonConfiguration orderComparator = + new RecursiveComparisonConfiguration(); + private SimpleDateFormat dateFormat; + + @Before + public void setUp() { + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + orderComparator.ignoreFields("userReference", "id"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + @Test + public void it_reads_trades() throws IOException, ParseException { + LgoStreamingService streamingService = mock(LgoStreamingService.class); + LgoStreamingMarketDataService service = new LgoStreamingMarketDataService(streamingService); + JsonNode message = TestUtils.getJsonContent("/marketdata/trades-update.json"); + when(streamingService.subscribeChannel(anyString())).thenReturn(Observable.just(message)); + + Observable trades = service.getTrades(CurrencyPair.BTC_USD); + + verify(streamingService).subscribeChannel("trades-BTC-USD"); + assertThat(trades.blockingFirst()) + .isEqualToComparingFieldByField( + new Trade.Builder() + .type(Order.OrderType.ASK) + .originalAmount(new BigDecimal("4.36920000")) + .currencyPair(CurrencyPair.BTC_USD) + .price(new BigDecimal("428.5000")) + .timestamp(dateFormat.parse("2019-07-19T12:25:01.596Z")) + .id("3128770") + .build()); + assertThat(trades.blockingLast()) + .isEqualToComparingFieldByField( + new Trade.Builder() + .type(Order.OrderType.BID) + .originalAmount(new BigDecimal("1.85390000")) + .currencyPair(CurrencyPair.BTC_USD) + .price(new BigDecimal("420.3000")) + .timestamp(dateFormat.parse("2019-07-19T12:25:05.860Z")) + .id("3128771") + .build()); + } + + @Test + public void it_reads_level2() throws IOException { + LgoStreamingService streamingService = mock(LgoStreamingService.class); + LgoStreamingMarketDataService service = new LgoStreamingMarketDataService(streamingService); + JsonNode snapshot = TestUtils.getJsonContent("/marketdata/level2-snapshot.json"); + JsonNode update = TestUtils.getJsonContent("/marketdata/level2-update.json"); + when(streamingService.subscribeChannel(anyString())) + .thenReturn(Observable.just(snapshot, update)); + + Observable orderBook = service.getOrderBook(CurrencyPair.BTC_USD); + + verify(streamingService).subscribeChannel("level2-BTC-USD"); + OrderBook firstBook = orderBook.blockingFirst(); + assertThat(firstBook.getAsks()) + .usingElementComparatorIgnoringFields("id", "userReference") + .contains(sellOrder("1111.1000", "9.39370000"), sellOrder("1115.9000", "0.88420000")); + assertThat(firstBook.getBids()) + .usingElementComparatorIgnoringFields("id", "userReference") + .contains(buyOrder("1089.1000", "0.10000000")); + OrderBook lastBook = orderBook.blockingLast(); + assertThat(lastBook.getBids()).isEmpty(); + assertThat(lastBook.getAsks()) + .usingElementComparatorIgnoringFields("id", "userReference") + .contains( + sellOrder("1111.1000", "0.10000000"), + sellOrder("1115.9000", "0.88420000"), + sellOrder("1116.9000", "0.20000000")); + } + + private LimitOrder sellOrder(String price, String quantity) { + return new LimitOrder( + Order.OrderType.ASK, + new BigDecimal(quantity), + CurrencyPair.BTC_USD, + null, + null, + new BigDecimal(price)); + } + + private LimitOrder buyOrder(String price, String quantity) { + return new LimitOrder( + Order.OrderType.BID, + new BigDecimal(quantity), + CurrencyPair.BTC_USD, + "0", + null, + new BigDecimal(price)); + } } diff --git a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingServiceTest.java b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingServiceTest.java index 4a9abb98b..c8c91bc27 100644 --- a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingServiceTest.java +++ b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingServiceTest.java @@ -1,88 +1,91 @@ package info.bitrich.xchangestream.lgo; +import static info.bitrich.xchangestream.lgo.TestUtils.*; +import static org.assertj.core.api.Assertions.*; + import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; import org.junit.*; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.lgo.LgoEnv; import org.knowm.xchange.lgo.LgoEnv.SignatureService; import org.knowm.xchange.lgo.service.LgoSignatureService; -import java.io.IOException; - -import static info.bitrich.xchangestream.lgo.TestUtils.*; -import static org.assertj.core.api.Assertions.*; - public class LgoStreamingServiceTest { - private LgoStreamingService service; + private LgoStreamingService service; - @Before - public void setUp() { - ExchangeSpecification env = LgoEnv.sandbox(); - env.getExchangeSpecificParameters().put(LgoEnv.SIGNATURE_SERVICE, SignatureService.PASSTHROUGHS); - service = new LgoStreamingService(LgoSignatureService.createInstance(env), "apiUrl"); - } + @Before + public void setUp() { + ExchangeSpecification env = LgoEnv.sandbox(); + env.getExchangeSpecificParameters() + .put(LgoEnv.SIGNATURE_SERVICE, SignatureService.PASSTHROUGHS); + service = new LgoStreamingService(LgoSignatureService.createInstance(env), "apiUrl"); + } - @Test - public void it_returns_the_trades_subscribe_message() throws IOException { - String channelName = "trades-BTC-USD"; + @Test + public void it_returns_the_trades_subscribe_message() throws IOException { + String channelName = "trades-BTC-USD"; - String subscribeMessage = service.getSubscribeMessage(channelName); + String subscribeMessage = service.getSubscribeMessage(channelName); - assertThat(asJsonNode(subscribeMessage)).isEqualTo(getJsonContent("/subscribe/trades-subscribe.json")); - } + assertThat(asJsonNode(subscribeMessage)) + .isEqualTo(getJsonContent("/subscribe/trades-subscribe.json")); + } - @Test - public void it_returns_the_level2_subscribe_message() throws IOException { - String channelName = "level2-BTC-USD"; + @Test + public void it_returns_the_level2_subscribe_message() throws IOException { + String channelName = "level2-BTC-USD"; - String subscribeMessage = service.getSubscribeMessage(channelName); + String subscribeMessage = service.getSubscribeMessage(channelName); - assertThat(asJsonNode(subscribeMessage)).isEqualTo(getJsonContent("/subscribe/level2-subscribe.json")); - } + assertThat(asJsonNode(subscribeMessage)) + .isEqualTo(getJsonContent("/subscribe/level2-subscribe.json")); + } - @Test - public void it_returns_the_balance_subscribe_message() throws IOException { - String channelName = "balance"; + @Test + public void it_returns_the_balance_subscribe_message() throws IOException { + String channelName = "balance"; - String subscribeMessage = service.getSubscribeMessage(channelName); + String subscribeMessage = service.getSubscribeMessage(channelName); - assertThat(asJsonNode(subscribeMessage)).isEqualTo(getJsonContent("/subscribe/balance-subscribe.json")); - } + assertThat(asJsonNode(subscribeMessage)) + .isEqualTo(getJsonContent("/subscribe/balance-subscribe.json")); + } - @Test - public void it_returns_the_right_channel_name_for_trades() throws IOException { - JsonNode message = getJsonContent("/marketdata/trades-update.json"); + @Test + public void it_returns_the_right_channel_name_for_trades() throws IOException { + JsonNode message = getJsonContent("/marketdata/trades-update.json"); - String name = service.getChannelNameFromMessage(message); + String name = service.getChannelNameFromMessage(message); - assertThat(name).isEqualTo("trades-BTC-USD"); - } + assertThat(name).isEqualTo("trades-BTC-USD"); + } - @Test - public void it_returns_the_right_channel_name_for_level2() throws IOException { - JsonNode message = getJsonContent("/marketdata/level2-snapshot.json"); + @Test + public void it_returns_the_right_channel_name_for_level2() throws IOException { + JsonNode message = getJsonContent("/marketdata/level2-snapshot.json"); - String name = service.getChannelNameFromMessage(message); + String name = service.getChannelNameFromMessage(message); - assertThat(name).isEqualTo("level2-BTC-USD"); - } + assertThat(name).isEqualTo("level2-BTC-USD"); + } - @Test - public void it_returns_the_right_channel_name_for_balances() throws IOException { - JsonNode message = getJsonContent("/account/balance-snapshot.json"); + @Test + public void it_returns_the_right_channel_name_for_balances() throws IOException { + JsonNode message = getJsonContent("/account/balance-snapshot.json"); - String name = service.getChannelNameFromMessage(message); + String name = service.getChannelNameFromMessage(message); - assertThat(name).isEqualTo("balance"); - } + assertThat(name).isEqualTo("balance"); + } - @Test - public void it_returns_the_right_channel_name_for_user() throws IOException { - JsonNode message = getJsonContent("/trade/orders-snapshot.json"); + @Test + public void it_returns_the_right_channel_name_for_user() throws IOException { + JsonNode message = getJsonContent("/trade/orders-snapshot.json"); - String name = service.getChannelNameFromMessage(message); + String name = service.getChannelNameFromMessage(message); - assertThat(name).isEqualTo("user-BTC-USD"); - } + assertThat(name).isEqualTo("user-BTC-USD"); + } } diff --git a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeServiceTest.java b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeServiceTest.java index ba939fea1..a1b386bcf 100644 --- a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeServiceTest.java +++ b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/LgoStreamingTradeServiceTest.java @@ -1,13 +1,23 @@ package info.bitrich.xchangestream.lgo; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + import com.fasterxml.jackson.databind.JsonNode; import info.bitrich.xchangestream.lgo.domain.*; import io.reactivex.Observable; +import java.io.*; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.text.*; +import java.time.temporal.ChronoUnit; +import java.util.*; import org.apache.commons.io.IOUtils; import org.assertj.core.util.Lists; import org.junit.*; -import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.*; +import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.*; import org.knowm.xchange.lgo.dto.key.LgoKey; @@ -16,220 +26,379 @@ import org.mockito.ArgumentCaptor; import si.mazi.rescu.SynchronizedValueFactory; -import java.io.*; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.text.*; -import java.time.temporal.ChronoUnit; -import java.util.*; +public class LgoStreamingTradeServiceTest { -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; + private SimpleDateFormat dateFormat; + private LgoKeyService keyService; + private LgoSignatureService signatureService; + private LgoStreamingTradeService service; + private LgoStreamingService streamingService; + private SynchronizedValueFactory nonceFactory; -public class LgoStreamingTradeServiceTest { + @Before + public void setUp() { + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + keyService = mock(LgoKeyService.class); + signatureService = mock(LgoSignatureService.class); + nonceFactory = mock(SynchronizedValueFactory.class); + streamingService = mock(LgoStreamingService.class); + service = + new LgoStreamingTradeService(streamingService, keyService, signatureService, nonceFactory); + } + + @Test + public void it_handles_open_orders_updates() throws IOException, ParseException { + JsonNode snapshot = TestUtils.getJsonContent("/trade/orders-snapshot.json"); + JsonNode update = TestUtils.getJsonContent("/trade/orders-update.json"); + when(streamingService.subscribeChannel(anyString())) + .thenReturn(Observable.just(snapshot, update)); + + Observable openOrders = service.getOpenOrders(CurrencyPair.BTC_USD); + + verify(streamingService).subscribeChannel("user-BTC-USD"); + Date date1 = dateFormat.parse("2019-07-23T15:36:14.304Z"); + Date date2 = dateFormat.parse("2019-07-18T12:24:21.891Z"); + Date date3 = dateFormat.parse("2019-07-24T08:37:19.116Z"); + LimitOrder order1 = + new LimitOrder( + Order.OrderType.ASK, + BigDecimal.ONE, + BigDecimal.ZERO, + CurrencyPair.BTC_USD, + "156389617430400001", + date1, + new BigDecimal(6000)); + order1.setOrderStatus(Order.OrderStatus.NEW); + LimitOrder order2 = + new LimitOrder( + Order.OrderType.ASK, + BigDecimal.ONE, + BigDecimal.ZERO, + CurrencyPair.BTC_USD, + "156345266189100001", + date2, + new BigDecimal(6000)); + order2.setOrderStatus(Order.OrderStatus.NEW); + LimitOrder order3 = + new LimitOrder( + Order.OrderType.BID, + new BigDecimal(2), + BigDecimal.ZERO, + CurrencyPair.BTC_USD, + "156395743911600001", + date3, + new BigDecimal(8000)); + order3.setOrderStatus(Order.OrderStatus.NEW); + assertThat(openOrders.blockingFirst()) + .usingRecursiveComparison() + .isEqualTo(new OpenOrders(Arrays.asList(order2, order1))); + assertThat(openOrders.blockingLast()) + .usingRecursiveComparison() + .isEqualTo(new OpenOrders(Arrays.asList(order3, order1))); + } + + @Test + public void it_handles_order_changes() throws IOException, ParseException { + JsonNode snapshot = TestUtils.getJsonContent("/trade/order-changes-snapshot.json"); + JsonNode update = TestUtils.getJsonContent("/trade/order-changes-update.json"); + when(streamingService.subscribeChannel(anyString())) + .thenReturn(Observable.just(snapshot, update)); + + Observable observable = service.getOrderChanges(CurrencyPair.BTC_USD); + + verify(streamingService).subscribeChannel("user-BTC-USD"); + Date date1 = dateFormat.parse("2019-07-23T15:36:14.304Z"); + Date date2 = dateFormat.parse("2019-07-18T12:24:21.891Z"); + Date date3 = dateFormat.parse("2019-07-24T08:37:19.116Z"); + LimitOrder order1 = + createOrder( + Order.OrderType.ASK, + new BigDecimal("1.00000000"), + new BigDecimal("0.00000000"), + CurrencyPair.BTC_USD, + "156389617430400001", + date1, + new BigDecimal("6000.0000"), + Order.OrderStatus.NEW); + LimitOrder order2 = + createOrder( + Order.OrderType.ASK, + new BigDecimal("1.00000000"), + new BigDecimal("0.00000000"), + CurrencyPair.BTC_USD, + "156345266189100001", + date2, + new BigDecimal("6000.0000"), + Order.OrderStatus.NEW); + LimitOrder order2Matched = + createOrder( + Order.OrderType.ASK, + new BigDecimal("1.00000000"), + new BigDecimal("0.40000000"), + CurrencyPair.BTC_USD, + "156345266189100001", + date2, + new BigDecimal("6000.0000"), + Order.OrderStatus.PARTIALLY_FILLED); + order2Matched.setFee(new BigDecimal("0.2388")); + LimitOrder order2Matched2 = + createOrder( + Order.OrderType.ASK, + new BigDecimal("1.00000000"), + new BigDecimal("1.00000000"), + CurrencyPair.BTC_USD, + "156345266189100001", + date2, + new BigDecimal("6000.0000"), + Order.OrderStatus.PARTIALLY_FILLED); + order2Matched2.setFee(new BigDecimal("0.6988")); + LimitOrder order2Filled = + createOrder( + Order.OrderType.ASK, + new BigDecimal("1.00000000"), + new BigDecimal("1.00000000"), + CurrencyPair.BTC_USD, + "156345266189100001", + date2, + new BigDecimal("6000.0000"), + Order.OrderStatus.FILLED); + order2Filled.setFee(new BigDecimal("0.6988")); + LimitOrder order3Pending = + createOrder( + Order.OrderType.BID, + new BigDecimal("2.00000000"), + new BigDecimal("0.00000000"), + CurrencyPair.BTC_USD, + "156395743911600001", + date3, + new BigDecimal("8000.0000"), + Order.OrderStatus.PENDING_NEW); + LimitOrder order3Open = + createOrder( + Order.OrderType.BID, + new BigDecimal("2.00000000"), + new BigDecimal("0.00000000"), + CurrencyPair.BTC_USD, + "156395743911600001", + date3, + new BigDecimal("8000.0000"), + Order.OrderStatus.NEW); + + ArrayList orderChanges = Lists.newArrayList(observable.blockingIterable()); + assertThat(orderChanges).hasSize(7); + assertThat(orderChanges.get(0)).usingRecursiveComparison().isEqualTo(order1); + assertThat(orderChanges.get(1)).usingRecursiveComparison().isEqualTo(order2); + assertThat(orderChanges.get(2)).usingRecursiveComparison().isEqualTo(order3Pending); + assertThat(orderChanges.get(3)).usingRecursiveComparison().isEqualTo(order3Open); + assertThat(orderChanges.get(4)).usingRecursiveComparison().isEqualTo(order2Matched); + assertThat(orderChanges.get(5)).usingRecursiveComparison().isEqualTo(order2Matched2); + assertThat(orderChanges.get(6)).usingRecursiveComparison().isEqualTo(order2Filled); + } + + private LimitOrder createOrder( + Order.OrderType type, + BigDecimal originalAmount, + BigDecimal cumulativeAmount, + CurrencyPair pair, + String id, + Date timestamp, + BigDecimal limitPrice, + Order.OrderStatus status) { + return new LimitOrder.Builder(type, pair) + .id(id) + .userReference(null) + .originalAmount(originalAmount) + .cumulativeAmount(cumulativeAmount) + .limitPrice(limitPrice) + .orderStatus(status) + .averagePrice(null) + .timestamp(timestamp) + .fee(null) + .build(); + } + + @Test + public void it_handles_trades() throws IOException, ParseException { + JsonNode snapshot = TestUtils.getJsonContent("/trade/user-trades-snapshot.json"); + JsonNode update = TestUtils.getJsonContent("/trade/user-trades-update.json"); + when(streamingService.subscribeChannel(anyString())) + .thenReturn(Observable.just(snapshot, update)); + + Observable userTrades = service.getUserTrades(CurrencyPair.BTC_USD); + + verify(streamingService).subscribeChannel("user-BTC-USD"); + Date date = dateFormat.parse("2019-08-06T10:00:05.658Z"); + ArrayList trades = Lists.newArrayList(userTrades.blockingIterable()); + assertThat(trades).hasSize(1); + assertThat(trades.get(0)) + .usingRecursiveComparison() + .isEqualTo( + new UserTrade.Builder() + .type(Order.OrderType.ASK) + .originalAmount(new BigDecimal("0.50000000")) + .currencyPair(CurrencyPair.BTC_USD) + .price(new BigDecimal("955.3000")) + .timestamp(date) + .id("4441691") + .orderId("156508560418400001") + .feeAmount(new BigDecimal("0.2388")) + .feeCurrency(Currency.USD) + .build()); + } + + @Test + public void it_handles_orders_events() throws IOException, ParseException { + JsonNode snapshot = TestUtils.getJsonContent("/trade/order-events-snapshot.json"); + JsonNode update = TestUtils.getJsonContent("/trade/order-events-update.json"); + when(streamingService.subscribeChannel(anyString())) + .thenReturn(Observable.just(snapshot, update)); + + Observable events = service.getRawBatchOrderEvents(CurrencyPair.BTC_USD); + + verify(streamingService).subscribeChannel("user-BTC-USD"); + Date date1 = dateFormat.parse("2019-07-24T08:37:19.116Z"); + Date date2 = dateFormat.parse("2019-07-24T08:37:19.849Z"); + Date date3 = dateFormat.parse("2019-07-24T08:37:19.922Z"); + ArrayList lgoOrderEvents = Lists.newArrayList(events.blockingIterable()); + assertThat(lgoOrderEvents.get(0)) + .isEqualToComparingFieldByField( + new LgoPendingOrderEvent( + 6317543L, + "pending", + "156395743911600001", + date1, + "L", + new BigDecimal("8000.0000"), + Order.OrderType.BID, + new BigDecimal("2.00000000"))); + assertThat(lgoOrderEvents.get(1)) + .isEqualToComparingFieldByField( + new LgoOpenOrderEvent(6317543L, "open", "156395743911600001", date2)); + assertThat(lgoOrderEvents.get(2)) + .isEqualToComparingFieldByField( + new LgoPendingOrderEvent( + 6317543L, + "pending", + "156395743912700001", + date2, + "L", + new BigDecimal("8000.0001"), + Order.OrderType.BID, + new BigDecimal("2.00000000"))); + assertThat(lgoOrderEvents.get(3)) + .isEqualToComparingFieldByField( + new LgoInvalidOrderEvent( + 6317543L, "invalid", "156395743912700001", date2, "InvalidAmount")); + assertThat(lgoOrderEvents.get(4)) + .isEqualToComparingFieldByField( + new LgoDoneOrderEvent(6317543L, "done", "156345266189100001", date3, "canceled", null)); + } + + @Test + public void it_handles_orders_ack() throws IOException, ParseException { + JsonNode snapshot = TestUtils.getJsonContent("/trade/afr-snapshot.json"); + JsonNode update1 = TestUtils.getJsonContent("/trade/afr-update1.json"); + JsonNode update2 = TestUtils.getJsonContent("/trade/afr-update2.json"); + when(streamingService.subscribeChannel(anyString())) + .thenReturn(Observable.just(snapshot, update1, update2)); + + Observable events = service.getRawReceivedOrderEvents(); + + verify(streamingService).subscribeChannel("afr"); + Date date1 = dateFormat.parse("2019-07-24T13:42:34.970Z"); + Date date2 = dateFormat.parse("2019-07-24T13:42:35.698Z"); + ArrayList lgoOrderEvents = Lists.newArrayList(events.blockingIterable()); + assertThat(lgoOrderEvents.get(0)) + .isEqualToComparingFieldByField( + new LgoReceivedOrderEvent("156397575497000001", "plop", "received", date1)); + assertThat(lgoOrderEvents.get(1)) + .isEqualToComparingFieldByField( + new LgoFailedOrderEvent( + "156397575497000001", "plop", "failed", date2, "INVALID_PAYLOAD")); + } + + @Test + public void it_handles_all_order_events() throws IOException, ParseException { + JsonNode snapshotOrders = TestUtils.getJsonContent("/trade/all-orders-snapshot.json"); + JsonNode snapshotAFR = TestUtils.getJsonContent("/trade/all-afr-snapshot.json"); + JsonNode update1 = TestUtils.getJsonContent("/trade/all-afr-update.json"); + JsonNode update2 = TestUtils.getJsonContent("/trade/all-orders-update.json"); + when(streamingService.subscribeChannel("afr")) + .thenReturn(Observable.just(snapshotAFR, update1)); + when(streamingService.subscribeChannel("user-BTC-USD")) + .thenReturn(Observable.just(snapshotOrders, update2)); + + Observable events = + service.getRawAllOrderEvents(Collections.singletonList(CurrencyPair.BTC_USD)); + + verify(streamingService).subscribeChannel("afr"); + verify(streamingService).subscribeChannel("user-BTC-USD"); + Date date1 = dateFormat.parse("2019-07-25T07:16:21.600Z"); + Date date2 = dateFormat.parse("2019-07-25T07:16:22.959Z"); + ArrayList lgoOrderEvents = Lists.newArrayList(events.blockingIterable()); + assertThat(lgoOrderEvents.get(0)) + .isEqualToComparingFieldByField( + new LgoReceivedOrderEvent("156403898160000001", "0", "received", date1)); + assertThat(lgoOrderEvents.get(1)) + .isEqualToComparingFieldByField( + new LgoPendingOrderEvent( + 6393996L, + "pending", + "156403898160000001", + date1, + "L", + new BigDecimal("7000.0000"), + Order.OrderType.ASK, + new BigDecimal("3.00000000"))); + assertThat(lgoOrderEvents.get(2)) + .isEqualToComparingFieldByField( + new LgoOpenOrderEvent(6393996L, "open", "156403898160000001", date2)); + } + + @Test + public void it_places_a_limit_order() throws IOException, ParseException { + Date date = dateFormat.parse("2019-07-25T07:16:21.600Z"); + LimitOrder limitOrder = + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.5"), + CurrencyPair.BTC_USD, + null, + date, + new BigDecimal("12000")); + when(nonceFactory.createValue()).thenReturn(22L); + LgoKey key = + new LgoKey( + "abcdefg", + new Date().toInstant().minus(1, ChronoUnit.HOURS), + new Date().toInstant().plus(1, ChronoUnit.HOURS)); + InputStream stream = LgoStreamingExchangeExample.class.getResourceAsStream("/public.pem"); + String utf8 = IOUtils.toString(stream, StandardCharsets.UTF_8); + key.setValue(parsePublicKey(utf8)); + when(keyService.selectKey()).thenReturn(key); + when(signatureService.signOrder(anyString())).thenReturn(new LgoOrderSignature("signed")); + doNothing().when(streamingService).sendMessage(anyString()); + + String ref = service.placeLimitOrder(limitOrder); + + verify(nonceFactory).createValue(); + verify(keyService).selectKey(); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(streamingService).sendMessage(captor.capture()); + assertThat(captor.getValue().contains("\"reference\":22")); + assertThat( + captor + .getValue() + .contains( + "\"signature\":{\"value\":\"signed\",\"source\":\"RSA\"},\"key_id\":\"abcdefg\"},\"type\":\"placeorder\"")); + assertThat(ref).isEqualTo("22"); + } - private SimpleDateFormat dateFormat; - private LgoKeyService keyService; - private LgoSignatureService signatureService; - private LgoStreamingTradeService service; - private LgoStreamingService streamingService; - private SynchronizedValueFactory nonceFactory; - - @Before - public void setUp() { - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - keyService = mock(LgoKeyService.class); - signatureService = mock(LgoSignatureService.class); - nonceFactory = mock(SynchronizedValueFactory.class); - streamingService = mock(LgoStreamingService.class); - service = new LgoStreamingTradeService(streamingService, keyService, signatureService, nonceFactory); - } - - @Test - public void it_handles_open_orders_updates() throws IOException, ParseException { - JsonNode snapshot = TestUtils.getJsonContent("/trade/orders-snapshot.json"); - JsonNode update = TestUtils.getJsonContent("/trade/orders-update.json"); - when(streamingService.subscribeChannel(anyString())).thenReturn(Observable.just(snapshot, update)); - - Observable openOrders = service.getOpenOrders(CurrencyPair.BTC_USD); - - verify(streamingService).subscribeChannel("user-BTC-USD"); - Date date1 = dateFormat.parse("2019-07-23T15:36:14.304Z"); - Date date2 = dateFormat.parse("2019-07-18T12:24:21.891Z"); - Date date3 = dateFormat.parse("2019-07-24T08:37:19.116Z"); - LimitOrder order1 = new LimitOrder(Order.OrderType.ASK, BigDecimal.ONE, BigDecimal.ZERO, CurrencyPair.BTC_USD, "156389617430400001", date1, new BigDecimal(6000)); - order1.setOrderStatus(Order.OrderStatus.NEW); - LimitOrder order2 = new LimitOrder(Order.OrderType.ASK, BigDecimal.ONE, BigDecimal.ZERO, CurrencyPair.BTC_USD, "156345266189100001", date2, new BigDecimal(6000)); - order2.setOrderStatus(Order.OrderStatus.NEW); - LimitOrder order3 = new LimitOrder(Order.OrderType.BID, new BigDecimal(2), BigDecimal.ZERO, CurrencyPair.BTC_USD, "156395743911600001", date3, new BigDecimal(8000)); - order3.setOrderStatus(Order.OrderStatus.NEW); - assertThat(openOrders.blockingFirst()).usingRecursiveComparison().isEqualTo(new OpenOrders(Arrays.asList(order2, order1))); - assertThat(openOrders.blockingLast()).usingRecursiveComparison().isEqualTo(new OpenOrders(Arrays.asList(order3, order1))); - } - - @Test - public void it_handles_order_changes() throws IOException, ParseException { - JsonNode snapshot = TestUtils.getJsonContent("/trade/order-changes-snapshot.json"); - JsonNode update = TestUtils.getJsonContent("/trade/order-changes-update.json"); - when(streamingService.subscribeChannel(anyString())).thenReturn(Observable.just(snapshot, update)); - - Observable observable = service.getOrderChanges(CurrencyPair.BTC_USD); - - verify(streamingService).subscribeChannel("user-BTC-USD"); - Date date1 = dateFormat.parse("2019-07-23T15:36:14.304Z"); - Date date2 = dateFormat.parse("2019-07-18T12:24:21.891Z"); - Date date3 = dateFormat.parse("2019-07-24T08:37:19.116Z"); - LimitOrder order1 = createOrder(Order.OrderType.ASK, new BigDecimal("1.00000000"), new BigDecimal("0.00000000"), CurrencyPair.BTC_USD, "156389617430400001", date1, new BigDecimal("6000.0000"), Order.OrderStatus.NEW); - LimitOrder order2 = createOrder(Order.OrderType.ASK, new BigDecimal("1.00000000"), new BigDecimal("0.00000000"), CurrencyPair.BTC_USD, "156345266189100001", date2, new BigDecimal("6000.0000"), Order.OrderStatus.NEW); - LimitOrder order2Matched = createOrder(Order.OrderType.ASK, new BigDecimal("1.00000000"), new BigDecimal("0.40000000"), CurrencyPair.BTC_USD, "156345266189100001", date2, new BigDecimal("6000.0000"), Order.OrderStatus.PARTIALLY_FILLED); - order2Matched.setFee(new BigDecimal("0.2388")); - LimitOrder order2Matched2 = createOrder(Order.OrderType.ASK, new BigDecimal("1.00000000"), new BigDecimal("1.00000000"), CurrencyPair.BTC_USD, "156345266189100001", date2, new BigDecimal("6000.0000"), Order.OrderStatus.PARTIALLY_FILLED); - order2Matched2.setFee(new BigDecimal("0.6988")); - LimitOrder order2Filled = createOrder(Order.OrderType.ASK, new BigDecimal("1.00000000"), new BigDecimal("1.00000000"), CurrencyPair.BTC_USD, "156345266189100001", date2, new BigDecimal("6000.0000"), Order.OrderStatus.FILLED); - order2Filled.setFee(new BigDecimal("0.6988")); - LimitOrder order3Pending = createOrder(Order.OrderType.BID, new BigDecimal("2.00000000"), new BigDecimal("0.00000000"), CurrencyPair.BTC_USD, "156395743911600001", date3, new BigDecimal("8000.0000"), Order.OrderStatus.PENDING_NEW); - LimitOrder order3Open = createOrder(Order.OrderType.BID, new BigDecimal("2.00000000"), new BigDecimal("0.00000000"), CurrencyPair.BTC_USD, "156395743911600001", date3, new BigDecimal("8000.0000"), Order.OrderStatus.NEW); - - ArrayList orderChanges = Lists.newArrayList(observable.blockingIterable()); - assertThat(orderChanges).hasSize(7); - assertThat(orderChanges.get(0)).usingRecursiveComparison().isEqualTo(order1); - assertThat(orderChanges.get(1)).usingRecursiveComparison().isEqualTo(order2); - assertThat(orderChanges.get(2)).usingRecursiveComparison().isEqualTo(order3Pending); - assertThat(orderChanges.get(3)).usingRecursiveComparison().isEqualTo(order3Open); - assertThat(orderChanges.get(4)).usingRecursiveComparison().isEqualTo(order2Matched); - assertThat(orderChanges.get(5)).usingRecursiveComparison().isEqualTo(order2Matched2); - assertThat(orderChanges.get(6)).usingRecursiveComparison().isEqualTo(order2Filled); - } - - private LimitOrder createOrder(Order.OrderType type, BigDecimal originalAmount, BigDecimal cumulativeAmount, CurrencyPair pair, String id, Date timestamp, BigDecimal limitPrice, Order.OrderStatus status) { - return new LimitOrder.Builder(type, pair) - .id(id) - .userReference(null) - .originalAmount(originalAmount) - .cumulativeAmount(cumulativeAmount) - .limitPrice(limitPrice) - .orderStatus(status) - .averagePrice(null) - .timestamp(timestamp) - .fee(null) - .build(); - } - - @Test - public void it_handles_trades() throws IOException, ParseException { - JsonNode snapshot = TestUtils.getJsonContent("/trade/user-trades-snapshot.json"); - JsonNode update = TestUtils.getJsonContent("/trade/user-trades-update.json"); - when(streamingService.subscribeChannel(anyString())).thenReturn(Observable.just(snapshot, update)); - - Observable userTrades = service.getUserTrades(CurrencyPair.BTC_USD); - - verify(streamingService).subscribeChannel("user-BTC-USD"); - Date date = dateFormat.parse("2019-08-06T10:00:05.658Z"); - ArrayList trades = Lists.newArrayList(userTrades.blockingIterable()); - assertThat(trades).hasSize(1); - assertThat(trades.get(0)) - .usingRecursiveComparison() - .isEqualTo(new UserTrade.Builder() - .type(Order.OrderType.ASK) - .originalAmount(new BigDecimal("0.50000000")) - .currencyPair( CurrencyPair.BTC_USD) - .price(new BigDecimal("955.3000")) - .timestamp(date) - .id("4441691") - .orderId("156508560418400001") - .feeAmount(new BigDecimal("0.2388")) - .feeCurrency(Currency.USD) - .build()); - } - - @Test - public void it_handles_orders_events() throws IOException, ParseException { - JsonNode snapshot = TestUtils.getJsonContent("/trade/order-events-snapshot.json"); - JsonNode update = TestUtils.getJsonContent("/trade/order-events-update.json"); - when(streamingService.subscribeChannel(anyString())).thenReturn(Observable.just(snapshot, update)); - - Observable events = service.getRawBatchOrderEvents(CurrencyPair.BTC_USD); - - verify(streamingService).subscribeChannel("user-BTC-USD"); - Date date1 = dateFormat.parse("2019-07-24T08:37:19.116Z"); - Date date2 = dateFormat.parse("2019-07-24T08:37:19.849Z"); - Date date3 = dateFormat.parse("2019-07-24T08:37:19.922Z"); - ArrayList lgoOrderEvents = Lists.newArrayList(events.blockingIterable()); - assertThat(lgoOrderEvents.get(0)).isEqualToComparingFieldByField(new LgoPendingOrderEvent(6317543L, "pending", "156395743911600001", date1, "L", new BigDecimal("8000.0000"), Order.OrderType.BID, new BigDecimal("2.00000000"))); - assertThat(lgoOrderEvents.get(1)).isEqualToComparingFieldByField(new LgoOpenOrderEvent(6317543L, "open", "156395743911600001", date2)); - assertThat(lgoOrderEvents.get(2)).isEqualToComparingFieldByField(new LgoPendingOrderEvent(6317543L, "pending", "156395743912700001", date2, "L", new BigDecimal("8000.0001"), Order.OrderType.BID, new BigDecimal("2.00000000"))); - assertThat(lgoOrderEvents.get(3)).isEqualToComparingFieldByField(new LgoInvalidOrderEvent(6317543L, "invalid", "156395743912700001", date2, "InvalidAmount")); - assertThat(lgoOrderEvents.get(4)).isEqualToComparingFieldByField(new LgoDoneOrderEvent(6317543L, "done", "156345266189100001", date3, "canceled", null)); - } - - @Test - public void it_handles_orders_ack() throws IOException, ParseException { - JsonNode snapshot = TestUtils.getJsonContent("/trade/afr-snapshot.json"); - JsonNode update1 = TestUtils.getJsonContent("/trade/afr-update1.json"); - JsonNode update2 = TestUtils.getJsonContent("/trade/afr-update2.json"); - when(streamingService.subscribeChannel(anyString())).thenReturn(Observable.just(snapshot, update1, update2)); - - Observable events = service.getRawReceivedOrderEvents(); - - verify(streamingService).subscribeChannel("afr"); - Date date1 = dateFormat.parse("2019-07-24T13:42:34.970Z"); - Date date2 = dateFormat.parse("2019-07-24T13:42:35.698Z"); - ArrayList lgoOrderEvents = Lists.newArrayList(events.blockingIterable()); - assertThat(lgoOrderEvents.get(0)).isEqualToComparingFieldByField(new LgoReceivedOrderEvent("156397575497000001", "plop", "received", date1)); - assertThat(lgoOrderEvents.get(1)).isEqualToComparingFieldByField(new LgoFailedOrderEvent("156397575497000001", "plop", "failed", date2, "INVALID_PAYLOAD")); - } - - @Test - public void it_handles_all_order_events() throws IOException, ParseException { - JsonNode snapshotOrders = TestUtils.getJsonContent("/trade/all-orders-snapshot.json"); - JsonNode snapshotAFR = TestUtils.getJsonContent("/trade/all-afr-snapshot.json"); - JsonNode update1 = TestUtils.getJsonContent("/trade/all-afr-update.json"); - JsonNode update2 = TestUtils.getJsonContent("/trade/all-orders-update.json"); - when(streamingService.subscribeChannel("afr")).thenReturn(Observable.just(snapshotAFR, update1)); - when(streamingService.subscribeChannel("user-BTC-USD")).thenReturn(Observable.just(snapshotOrders, update2)); - - Observable events = service.getRawAllOrderEvents(Collections.singletonList(CurrencyPair.BTC_USD)); - - verify(streamingService).subscribeChannel("afr"); - verify(streamingService).subscribeChannel("user-BTC-USD"); - Date date1 = dateFormat.parse("2019-07-25T07:16:21.600Z"); - Date date2 = dateFormat.parse("2019-07-25T07:16:22.959Z"); - ArrayList lgoOrderEvents = Lists.newArrayList(events.blockingIterable()); - assertThat(lgoOrderEvents.get(0)).isEqualToComparingFieldByField(new LgoReceivedOrderEvent("156403898160000001", "0", "received", date1)); - assertThat(lgoOrderEvents.get(1)).isEqualToComparingFieldByField(new LgoPendingOrderEvent(6393996L, "pending", "156403898160000001", date1, "L", new BigDecimal("7000.0000"), Order.OrderType.ASK, new BigDecimal("3.00000000"))); - assertThat(lgoOrderEvents.get(2)).isEqualToComparingFieldByField(new LgoOpenOrderEvent(6393996L, "open", "156403898160000001", date2)); - } - - @Test - public void it_places_a_limit_order() throws IOException, ParseException { - Date date = dateFormat.parse("2019-07-25T07:16:21.600Z"); - LimitOrder limitOrder = new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.5"), CurrencyPair.BTC_USD, null, date, new BigDecimal("12000")); - when(nonceFactory.createValue()).thenReturn(22L); - LgoKey key = new LgoKey("abcdefg", new Date().toInstant().minus(1, ChronoUnit.HOURS), new Date().toInstant().plus(1, ChronoUnit.HOURS)); - InputStream stream = LgoStreamingExchangeExample.class.getResourceAsStream("/public.pem"); - String utf8 = IOUtils.toString(stream, StandardCharsets.UTF_8); - key.setValue(parsePublicKey(utf8)); - when(keyService.selectKey()).thenReturn(key); - when(signatureService.signOrder(anyString())).thenReturn(new LgoOrderSignature("signed")); - doNothing().when(streamingService).sendMessage(anyString()); - - String ref = service.placeLimitOrder(limitOrder); - - verify(nonceFactory).createValue(); - verify(keyService).selectKey(); - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(streamingService).sendMessage(captor.capture()); - assertThat(captor.getValue().contains("\"reference\":22")); - assertThat(captor.getValue().contains("\"signature\":{\"value\":\"signed\",\"source\":\"RSA\"},\"key_id\":\"abcdefg\"},\"type\":\"placeorder\"")); - assertThat(ref).isEqualTo("22"); - } - - private static String parsePublicKey(String key) { - return key.replaceAll("-----END PUBLIC KEY-----", "") - .replaceAll("-----BEGIN PUBLIC KEY-----", "") - .replaceAll("\n", "") - .replaceAll("\r", ""); - } -} \ No newline at end of file + private static String parsePublicKey(String key) { + return key.replaceAll("-----END PUBLIC KEY-----", "") + .replaceAll("-----BEGIN PUBLIC KEY-----", "") + .replaceAll("\n", "") + .replaceAll("\r", ""); + } +} diff --git a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/TestUtils.java b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/TestUtils.java index 01808a049..e560be744 100644 --- a/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/TestUtils.java +++ b/xchange-stream-lgo/src/test/java/info/bitrich/xchangestream/lgo/TestUtils.java @@ -2,21 +2,19 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - import java.io.IOException; public class TestUtils { - private static final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = new ObjectMapper(); - private TestUtils() { - } + private TestUtils() {} - public static JsonNode getJsonContent(String path) throws IOException { - return mapper.readTree(LgoStreamingAccountServiceTest.class.getResourceAsStream(path)); - } + public static JsonNode getJsonContent(String path) throws IOException { + return mapper.readTree(LgoStreamingAccountServiceTest.class.getResourceAsStream(path)); + } - public static JsonNode asJsonNode(String jsonString) throws IOException { - return mapper.readTree(jsonString); - } + public static JsonNode asJsonNode(String jsonString) throws IOException { + return mapper.readTree(jsonString); + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingExchange.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingExchange.java index 75a980f6e..3fe318a82 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingExchange.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingExchange.java @@ -8,55 +8,57 @@ import org.knowm.xchange.okcoin.OkCoinExchange; public class OkCoinStreamingExchange extends OkCoinExchange implements StreamingExchange { - private static final String API_URI = "wss://real.okcoin.com:10440/websocket"; - - private final OkCoinStreamingService streamingService; - private OkCoinStreamingMarketDataService streamingMarketDataService; - - public OkCoinStreamingExchange() { - streamingService = new OkCoinStreamingService(API_URI); - } - - protected OkCoinStreamingExchange(OkCoinStreamingService streamingService) { - this.streamingService = streamingService; - } - - @Override - protected void initServices() { - super.initServices(); - streamingMarketDataService = new OkCoinStreamingMarketDataService(streamingService); - } - - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } - - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } - - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } - - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } - - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } - - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + private static final String API_URI = "wss://real.okcoin.com:10440/websocket"; + + private final OkCoinStreamingService streamingService; + private OkCoinStreamingMarketDataService streamingMarketDataService; + + public OkCoinStreamingExchange() { + streamingService = new OkCoinStreamingService(API_URI); + } + + protected OkCoinStreamingExchange(OkCoinStreamingService streamingService) { + this.streamingService = streamingService; + } + + @Override + protected void initServices() { + super.initServices(); + streamingMarketDataService = new OkCoinStreamingMarketDataService(streamingService); + } + + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } + + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } + + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } + + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } + + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } + + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataService.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataService.java index f7a29a186..afad735b0 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataService.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataService.java @@ -1,6 +1,5 @@ package info.bitrich.xchangestream.okcoin; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.core.StreamingMarketDataService; import info.bitrich.xchangestream.okcoin.dto.OkCoinOrderbook; @@ -8,6 +7,9 @@ import info.bitrich.xchangestream.okcoin.dto.marketdata.FutureTicker; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.marketdata.OrderBook; @@ -20,10 +22,6 @@ import org.knowm.xchange.okcoin.dto.marketdata.OkCoinTicker; import org.knowm.xchange.okcoin.dto.marketdata.OkCoinTickerResponse; -import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; - /** * #### spot #### * https://github.com/okcoin-okex/API-docs-OKEx.com/blob/master/API-For-Spot-CN/%E5%B8%81%E5%B8%81%E4%BA%A4%E6%98%93WebSocket%20API.md @@ -32,141 +30,175 @@ * https://github.com/okcoin-okex/API-docs-OKEx.com/blob/master/API-For-Futures-EN/WebSocket%20API%20for%20FUTURES.md */ public class OkCoinStreamingMarketDataService implements StreamingMarketDataService { - private final OkCoinStreamingService service; - - private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - private final Map orderbooks = new HashMap<>(); - - OkCoinStreamingMarketDataService(OkCoinStreamingService service) { - this.service = service; - } - - /** - * #### spot #### - * 2. ok_sub_spot_X_depth 订阅币币市场深度(200增量数据返回) - * 3. ok_sub_spot_X_depth_Y 订阅市场深度 - * #### future #### - * 3. ok_sub_futureusd_X_depth_Y 订阅合约市场深度(200增量数据返回) - * 3. ok_sub_futureusd_X_depth_Y Subscribe Contract Market Depth(Incremental) - * 4. ok_sub_futureusd_X_depth_Y_Z 订阅合约市场深度(全量返回) - * 4. ok_sub_futureusd_X_depth_Y_Z Subscribe Contract Market Depth(Full) - * - * @param currencyPair Currency pair of the order book - * @param args if the first arg is {@link FuturesContract} means future, the next arg is amount - * @return - */ - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String channel = String.format("ok_sub_spot_%s_%s_depth", currencyPair.base.toString().toLowerCase(), currencyPair.counter.toString().toLowerCase()); - - if (args.length > 0) { - if (args[0] instanceof FuturesContract) { - FuturesContract contract = (FuturesContract) args[0]; - channel = String.format("ok_sub_future%s_%s_depth_%s", currencyPair.counter.toString().toLowerCase(), currencyPair.base.toString().toLowerCase(), contract.getName()); - if (args.length > 1) { - channel = channel + "_" + args[1]; - } - } else { - channel = channel + "_" + args[1]; - } + private final OkCoinStreamingService service; + + private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + private final Map orderbooks = new HashMap<>(); + + OkCoinStreamingMarketDataService(OkCoinStreamingService service) { + this.service = service; + } + + /** + * #### spot #### 2. ok_sub_spot_X_depth 订阅币币市场深度(200增量数据返回) 3. ok_sub_spot_X_depth_Y 订阅市场深度 #### + * future #### 3. ok_sub_futureusd_X_depth_Y 订阅合约市场深度(200增量数据返回) 3. ok_sub_futureusd_X_depth_Y + * Subscribe Contract Market Depth(Incremental) 4. ok_sub_futureusd_X_depth_Y_Z 订阅合约市场深度(全量返回) 4. + * ok_sub_futureusd_X_depth_Y_Z Subscribe Contract Market Depth(Full) + * + * @param currencyPair Currency pair of the order book + * @param args if the first arg is {@link FuturesContract} means future, the next arg is amount + * @return + */ + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + String channel = + String.format( + "ok_sub_spot_%s_%s_depth", + currencyPair.base.toString().toLowerCase(), + currencyPair.counter.toString().toLowerCase()); + + if (args.length > 0) { + if (args[0] instanceof FuturesContract) { + FuturesContract contract = (FuturesContract) args[0]; + channel = + String.format( + "ok_sub_future%s_%s_depth_%s", + currencyPair.counter.toString().toLowerCase(), + currencyPair.base.toString().toLowerCase(), + contract.getName()); + if (args.length > 1) { + channel = channel + "_" + args[1]; } - final String key=channel; - - return service.subscribeChannel(channel) - .map(s -> { - OkCoinOrderbook okCoinOrderbook; - if (!orderbooks.containsKey(key)) { - OkCoinDepth okCoinDepth = mapper.treeToValue(s.get("data"), OkCoinDepth.class); - okCoinOrderbook = new OkCoinOrderbook(okCoinDepth); - orderbooks.put(key, okCoinOrderbook); - } else { - okCoinOrderbook = orderbooks.get(key); - if (s.get("data").has("asks")) { - if (s.get("data").get("asks").size() > 0) { - BigDecimal[][] askLevels = mapper.treeToValue(s.get("data").get("asks"), BigDecimal[][].class); - okCoinOrderbook.updateLevels(askLevels, Order.OrderType.ASK); - } - } - - if (s.get("data").has("bids")) { - if (s.get("data").get("bids").size() > 0) { - BigDecimal[][] bidLevels = mapper.treeToValue(s.get("data").get("bids"), BigDecimal[][].class); - okCoinOrderbook.updateLevels(bidLevels, Order.OrderType.BID); - } - } - } - - return OkCoinAdapters.adaptOrderBook(okCoinOrderbook.toOkCoinDepth(s.get("data").get("timestamp").asLong()), currencyPair); - }); - } - - /** - * #### spot #### - * 1. ok_sub_spot_X_ticker 订阅行情数据 - * - * @param currencyPair Currency pair of the ticker - * @param args - * @return - */ - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - String channel = String.format("ok_sub_spot_%s_%s_ticker", currencyPair.base.toString().toLowerCase(), currencyPair.counter.toString().toLowerCase()); - - return service.subscribeChannel(channel) - .map(s -> { - // TODO: fix parsing of BigDecimal attribute val that has format: 1,625.23 - OkCoinTicker okCoinTicker = mapper.treeToValue(s.get("data"), OkCoinTicker.class); - return OkCoinAdapters.adaptTicker(new OkCoinTickerResponse(okCoinTicker), currencyPair); - }); + } else { + channel = channel + "_" + args[1]; + } } + final String key = channel; + + return service + .subscribeChannel(channel) + .map( + s -> { + OkCoinOrderbook okCoinOrderbook; + if (!orderbooks.containsKey(key)) { + OkCoinDepth okCoinDepth = mapper.treeToValue(s.get("data"), OkCoinDepth.class); + okCoinOrderbook = new OkCoinOrderbook(okCoinDepth); + orderbooks.put(key, okCoinOrderbook); + } else { + okCoinOrderbook = orderbooks.get(key); + if (s.get("data").has("asks")) { + if (s.get("data").get("asks").size() > 0) { + BigDecimal[][] askLevels = + mapper.treeToValue(s.get("data").get("asks"), BigDecimal[][].class); + okCoinOrderbook.updateLevels(askLevels, Order.OrderType.ASK); + } + } - /** - * #### future #### - * 1. ok_sub_futureusd_X_ticker_Y 订阅合约行情 - * 1. ok_sub_futureusd_X_ticker_Y Subscribe Contract Market Price - * - * @param currencyPair Currency pair of the ticker - * @param contract {@link FuturesContract} - * @return - */ - public Observable getFutureTicker(CurrencyPair currencyPair, FuturesContract contract) { - String channel = String.format("ok_sub_future%s_%s_ticker_%s", currencyPair.counter.toString().toLowerCase(), currencyPair.base.toString().toLowerCase(), contract.getName()); - return service.subscribeChannel(channel).map(s -> mapper.treeToValue(s.get("data"), FutureTicker.class)); + if (s.get("data").has("bids")) { + if (s.get("data").get("bids").size() > 0) { + BigDecimal[][] bidLevels = + mapper.treeToValue(s.get("data").get("bids"), BigDecimal[][].class); + okCoinOrderbook.updateLevels(bidLevels, Order.OrderType.BID); + } + } + } + + return OkCoinAdapters.adaptOrderBook( + okCoinOrderbook.toOkCoinDepth(s.get("data").get("timestamp").asLong()), + currencyPair); + }); + } + + /** + * #### spot #### 1. ok_sub_spot_X_ticker 订阅行情数据 + * + * @param currencyPair Currency pair of the ticker + * @param args + * @return + */ + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + String channel = + String.format( + "ok_sub_spot_%s_%s_ticker", + currencyPair.base.toString().toLowerCase(), + currencyPair.counter.toString().toLowerCase()); + + return service + .subscribeChannel(channel) + .map( + s -> { + // TODO: fix parsing of BigDecimal attribute val that has format: 1,625.23 + OkCoinTicker okCoinTicker = mapper.treeToValue(s.get("data"), OkCoinTicker.class); + return OkCoinAdapters.adaptTicker( + new OkCoinTickerResponse(okCoinTicker), currencyPair); + }); + } + + /** + * #### future #### 1. ok_sub_futureusd_X_ticker_Y 订阅合约行情 1. ok_sub_futureusd_X_ticker_Y Subscribe + * Contract Market Price + * + * @param currencyPair Currency pair of the ticker + * @param contract {@link FuturesContract} + * @return + */ + public Observable getFutureTicker( + CurrencyPair currencyPair, FuturesContract contract) { + String channel = + String.format( + "ok_sub_future%s_%s_ticker_%s", + currencyPair.counter.toString().toLowerCase(), + currencyPair.base.toString().toLowerCase(), + contract.getName()); + return service + .subscribeChannel(channel) + .map(s -> mapper.treeToValue(s.get("data"), FutureTicker.class)); + } + + /** + * #### spot #### 4. ok_sub_spot_X_deals 订阅成交记录 + * + *

#### future #### 5. ok_sub_futureusd_X_trade_Y 订阅合约交易信息 5. ok_sub_futureusd_X_trade_Y + * Subscribe Contract Trade Record + * + * @param currencyPair Currency pair of the trades + * @param args the first arg {@link FuturesContract} + * @return + */ + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channel = + String.format( + "ok_sub_spot_%s_%s_deals", + currencyPair.base.toString().toLowerCase(), + currencyPair.counter.toString().toLowerCase()); + + if (args.length > 0) { + FuturesContract contract = (FuturesContract) args[0]; + channel = + String.format( + "ok_sub_future%s_%s_trade_%s", + currencyPair.counter.toString().toLowerCase(), + currencyPair.base.toString().toLowerCase(), + contract.getName()); } - /** - * #### spot #### - * 4. ok_sub_spot_X_deals 订阅成交记录 - *

- * #### future #### - * 5. ok_sub_futureusd_X_trade_Y 订阅合约交易信息 - * 5. ok_sub_futureusd_X_trade_Y Subscribe Contract Trade Record - * - * @param currencyPair Currency pair of the trades - * @param args the first arg {@link FuturesContract} - * @return - */ - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channel = String.format("ok_sub_spot_%s_%s_deals", currencyPair.base.toString().toLowerCase(), currencyPair.counter.toString().toLowerCase()); - - if (args.length > 0) { - FuturesContract contract = (FuturesContract) args[0]; - channel = String.format("ok_sub_future%s_%s_trade_%s", currencyPair.counter.toString().toLowerCase(), currencyPair.base.toString().toLowerCase(), contract.getName()); - } - - return service.subscribeChannel(channel) - .map(s -> { - String[][] trades = mapper.treeToValue(s.get("data"), String[][].class); - - // I don't know how to parse this array of arrays in Jacson. - OkCoinWebSocketTrade[] okCoinTrades = new OkCoinWebSocketTrade[trades.length]; - for (int i = 0; i < trades.length; ++i) { - OkCoinWebSocketTrade okCoinWebSocketTrade = new OkCoinWebSocketTrade(trades[i]); - okCoinTrades[i] = okCoinWebSocketTrade; - } - - return OkCoinAdapters.adaptTrades(okCoinTrades, currencyPair); - }).flatMapIterable(Trades::getTrades); - } + return service + .subscribeChannel(channel) + .map( + s -> { + String[][] trades = mapper.treeToValue(s.get("data"), String[][].class); + + // I don't know how to parse this array of arrays in Jacson. + OkCoinWebSocketTrade[] okCoinTrades = new OkCoinWebSocketTrade[trades.length]; + for (int i = 0; i < trades.length; ++i) { + OkCoinWebSocketTrade okCoinWebSocketTrade = new OkCoinWebSocketTrade(trades[i]); + okCoinTrades[i] = okCoinWebSocketTrade; + } + + return OkCoinAdapters.adaptTrades(okCoinTrades, currencyPair); + }) + .flatMapIterable(Trades::getTrades); + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingService.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingService.java index 83e3dace3..6d416dadf 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingService.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingService.java @@ -16,134 +16,143 @@ import io.reactivex.CompletableSource; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; -import org.knowm.xchange.exceptions.ExchangeException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.zip.Inflater; +import org.knowm.xchange.exceptions.ExchangeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class OkCoinStreamingService extends JsonNettyStreamingService { - private Observable pingPongSrc = Observable.interval(15, 15, TimeUnit.SECONDS); + private Observable pingPongSrc = Observable.interval(15, 15, TimeUnit.SECONDS); - private Disposable pingPongSubscription; + private Disposable pingPongSubscription; - public OkCoinStreamingService(String apiUrl) { - super(apiUrl); - } + public OkCoinStreamingService(String apiUrl) { + super(apiUrl); + } - @Override - public Completable connect() { - Completable conn = super.connect(); - return conn.andThen((CompletableSource)(completable) -> { - try { + @Override + public Completable connect() { + Completable conn = super.connect(); + return conn.andThen( + (CompletableSource) + (completable) -> { + try { if (pingPongSubscription != null && !pingPongSubscription.isDisposed()) { - pingPongSubscription.dispose(); + pingPongSubscription.dispose(); } - pingPongSubscription = pingPongSrc.subscribe(o -> { - this.sendMessage("{\"event\":\"ping\"}"); - }); + pingPongSubscription = + pingPongSrc.subscribe( + o -> { + this.sendMessage("{\"event\":\"ping\"}"); + }); completable.onComplete(); - } catch (Exception e) { + } catch (Exception e) { completable.onError(e); - } - }); + } + }); + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) throws IOException { + return message.get("channel").asText(); + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + WebSocketMessage webSocketMessage = new WebSocketMessage("addChannel", channelName); + + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + return objectMapper.writeValueAsString(webSocketMessage); + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + WebSocketMessage webSocketMessage = new WebSocketMessage("removeChannel", channelName); + + return objectMapper.writeValueAsString(webSocketMessage); + } + + @Override + protected void handleMessage(JsonNode message) { + if (message.get("event") != null && "pong".equals(message.get("event").asText())) { + // ignore pong message + return; } - - @Override - protected String getChannelNameFromMessage(JsonNode message) throws IOException { - return message.get("channel").asText(); + if (message.get("data") != null) { + if (message.get("data").has("result")) { + boolean success = message.get("data").get("result").asBoolean(); + if (!success) { + super.handleError( + message, + new ExchangeException( + "Error code: " + message.get("data").get("error_code").asText())); + } + return; + } } + super.handleMessage(message); + } - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - WebSocketMessage webSocketMessage = new WebSocketMessage("addChannel", channelName); + @Override + protected WebSocketClientHandler getWebSocketClientHandler( + WebSocketClientHandshaker handshaker, + WebSocketClientHandler.WebSocketMessageHandler handler) { + return new OkCoinNettyWebSocketClientHandler(handshaker, handler); + } - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - return objectMapper.writeValueAsString(webSocketMessage); - } + protected class OkCoinNettyWebSocketClientHandler extends NettyWebSocketClientHandler { - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - WebSocketMessage webSocketMessage = new WebSocketMessage("removeChannel", channelName); + private final Logger LOG = LoggerFactory.getLogger(OkCoinNettyWebSocketClientHandler.class); - return objectMapper.writeValueAsString(webSocketMessage); + protected OkCoinNettyWebSocketClientHandler( + WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { + super(handshaker, handler); } @Override - protected void handleMessage(JsonNode message) { - if (message.get("event") != null && "pong".equals(message.get("event").asText()) ) { - // ignore pong message - return; - } - if (message.get("data") != null) { - if (message.get("data").has("result")) { - boolean success = message.get("data").get("result").asBoolean(); - if (!success) { - super.handleError(message, new ExchangeException("Error code: " + message.get("data").get("error_code").asText())); - } - return; - } - } - super.handleMessage(message); + public void channelInactive(ChannelHandlerContext ctx) { + if (pingPongSubscription != null && !pingPongSubscription.isDisposed()) { + pingPongSubscription.dispose(); + } + super.channelInactive(ctx); } @Override - protected WebSocketClientHandler getWebSocketClientHandler(WebSocketClientHandshaker handshaker, WebSocketClientHandler.WebSocketMessageHandler handler) { - return new OkCoinNettyWebSocketClientHandler(handshaker, handler); - } - - protected class OkCoinNettyWebSocketClientHandler extends NettyWebSocketClientHandler { - - private final Logger LOG = LoggerFactory.getLogger(OkCoinNettyWebSocketClientHandler.class); - - protected OkCoinNettyWebSocketClientHandler(WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { - super(handshaker, handler); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - if (pingPongSubscription != null && !pingPongSubscription.isDisposed()) { - pingPongSubscription.dispose(); - } - super.channelInactive(ctx); - } - - @Override - public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { - - if (!handshaker.isHandshakeComplete()) { - super.channelRead0(ctx, msg); - return; - } - - super.channelRead0(ctx, msg); - - WebSocketFrame frame = (WebSocketFrame) msg; - if (frame instanceof BinaryWebSocketFrame) { - BinaryWebSocketFrame binaryFrame = (BinaryWebSocketFrame) frame; - ByteBuf byteBuf = binaryFrame.content(); - byte[] temp = new byte[byteBuf.readableBytes()]; - ByteBufInputStream bis = new ByteBufInputStream(byteBuf); - StringBuilder appender = new StringBuilder(); - try { - bis.read(temp); - bis.close(); - Inflater infl = new Inflater(true); - infl.setInput(temp, 0, temp.length); - byte[] result = new byte[1024]; - while (!infl.finished()) { - int length = infl.inflate(result); - appender.append(new String(result, 0, length, "UTF-8")); - } - infl.end(); - } catch (Exception e) { - LOG.trace("Error when inflate websocket binary message"); - } - handler.onMessage(appender.toString()); - } + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + + if (!handshaker.isHandshakeComplete()) { + super.channelRead0(ctx, msg); + return; + } + + super.channelRead0(ctx, msg); + + WebSocketFrame frame = (WebSocketFrame) msg; + if (frame instanceof BinaryWebSocketFrame) { + BinaryWebSocketFrame binaryFrame = (BinaryWebSocketFrame) frame; + ByteBuf byteBuf = binaryFrame.content(); + byte[] temp = new byte[byteBuf.readableBytes()]; + ByteBufInputStream bis = new ByteBufInputStream(byteBuf); + StringBuilder appender = new StringBuilder(); + try { + bis.read(temp); + bis.close(); + Inflater infl = new Inflater(true); + infl.setInput(temp, 0, temp.length); + byte[] result = new byte[1024]; + while (!infl.finished()) { + int length = infl.inflate(result); + appender.append(new String(result, 0, length, "UTF-8")); + } + infl.end(); + } catch (Exception e) { + LOG.trace("Error when inflate websocket binary message"); } + handler.onMessage(appender.toString()); + } } + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExFuturesStreamingExchange.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExFuturesStreamingExchange.java index e3a898438..fd9828dcc 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExFuturesStreamingExchange.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExFuturesStreamingExchange.java @@ -2,10 +2,9 @@ public class OkExFuturesStreamingExchange extends OkExStreamingExchange { - private static final String API_URI = "wss://real.okex.com:10440/websocket/okexapi?compress=true"; + private static final String API_URI = "wss://real.okex.com:10440/websocket/okexapi?compress=true"; - public OkExFuturesStreamingExchange() { - super(API_URI); - } + public OkExFuturesStreamingExchange() { + super(API_URI); + } } - diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExStreamingExchange.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExStreamingExchange.java index 4df8e4cf8..a1b121159 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExStreamingExchange.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/OkExStreamingExchange.java @@ -1,16 +1,14 @@ package info.bitrich.xchangestream.okcoin; -/** - * Created by Lukas Zaoralek on 17.11.17. - */ +/** Created by Lukas Zaoralek on 17.11.17. */ public class OkExStreamingExchange extends OkCoinStreamingExchange { - private static final String API_URI = "wss://real.okex.com:10441/websocket?compress=true"; + private static final String API_URI = "wss://real.okex.com:10441/websocket?compress=true"; - public OkExStreamingExchange() { - super(new OkCoinStreamingService(API_URI)); - } + public OkExStreamingExchange() { + super(new OkCoinStreamingService(API_URI)); + } - public OkExStreamingExchange(String apiUrl) { - super(new OkCoinStreamingService(apiUrl)); - } + public OkExStreamingExchange(String apiUrl) { + super(new OkCoinStreamingService(apiUrl)); + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinOrderbook.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinOrderbook.java index 4d83f2476..aa0dff65f 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinOrderbook.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinOrderbook.java @@ -1,80 +1,80 @@ package info.bitrich.xchangestream.okcoin.dto; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.okcoin.dto.marketdata.OkCoinDepth; - import java.math.BigDecimal; import java.util.Collection; import java.util.Date; import java.util.SortedMap; import java.util.TreeMap; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.okcoin.dto.marketdata.OkCoinDepth; -/** - * Created by Lukas Zaoralek on 16.11.17. - */ +/** Created by Lukas Zaoralek on 16.11.17. */ public class OkCoinOrderbook { - private final BigDecimal zero = new BigDecimal(0); + private final BigDecimal zero = new BigDecimal(0); - private final SortedMap asks; - private final SortedMap bids; + private final SortedMap asks; + private final SortedMap bids; - public OkCoinOrderbook() { - asks = new TreeMap<>(java.util.Collections.reverseOrder()); //Because okcoin adapter uses reverse sort for asks!!! - bids = new TreeMap<>(); - } + public OkCoinOrderbook() { + asks = + new TreeMap<>( + java.util.Collections + .reverseOrder()); // Because okcoin adapter uses reverse sort for asks!!! + bids = new TreeMap<>(); + } - public OkCoinOrderbook(OkCoinDepth depth) { - this(); - createFromDepth(depth); - } + public OkCoinOrderbook(OkCoinDepth depth) { + this(); + createFromDepth(depth); + } - public void createFromDepth(OkCoinDepth depth) { - BigDecimal[][] depthAsks = depth.getAsks(); - BigDecimal[][] depthBids = depth.getBids(); + public void createFromDepth(OkCoinDepth depth) { + BigDecimal[][] depthAsks = depth.getAsks(); + BigDecimal[][] depthBids = depth.getBids(); - createFromDepthLevels(depthAsks, Order.OrderType.ASK); - createFromDepthLevels(depthBids, Order.OrderType.BID); - } + createFromDepthLevels(depthAsks, Order.OrderType.ASK); + createFromDepthLevels(depthBids, Order.OrderType.BID); + } - public void createFromDepthLevels(BigDecimal[][] depthLevels, Order.OrderType side) { - SortedMap orderbookLevels = side == Order.OrderType.ASK ? asks : bids; - for (BigDecimal[] level : depthLevels) { - orderbookLevels.put(level[0], level); - } + public void createFromDepthLevels(BigDecimal[][] depthLevels, Order.OrderType side) { + SortedMap orderbookLevels = side == Order.OrderType.ASK ? asks : bids; + for (BigDecimal[] level : depthLevels) { + orderbookLevels.put(level[0], level); } + } - public void updateLevels(BigDecimal[][] depthLevels, Order.OrderType side) { - for (BigDecimal[] level : depthLevels) { - updateLevel(level, side); - } + public void updateLevels(BigDecimal[][] depthLevels, Order.OrderType side) { + for (BigDecimal[] level : depthLevels) { + updateLevel(level, side); } + } - public void updateLevel(BigDecimal[] level, Order.OrderType side) { - SortedMap orderBookSide = side == Order.OrderType.ASK ? asks : bids; - boolean shouldDelete = level[1].compareTo(zero) == 0; - BigDecimal price = level[0]; - orderBookSide.remove(price); - if (!shouldDelete) { - orderBookSide.put(price, level); - } + public void updateLevel(BigDecimal[] level, Order.OrderType side) { + SortedMap orderBookSide = side == Order.OrderType.ASK ? asks : bids; + boolean shouldDelete = level[1].compareTo(zero) == 0; + BigDecimal price = level[0]; + orderBookSide.remove(price); + if (!shouldDelete) { + orderBookSide.put(price, level); } + } - public BigDecimal[][] getSide(Order.OrderType side) { - SortedMap orderbookLevels = side == Order.OrderType.ASK ? asks : bids; - Collection levels = orderbookLevels.values(); - return levels.toArray(new BigDecimal[orderbookLevels.size()][]); - } + public BigDecimal[][] getSide(Order.OrderType side) { + SortedMap orderbookLevels = side == Order.OrderType.ASK ? asks : bids; + Collection levels = orderbookLevels.values(); + return levels.toArray(new BigDecimal[orderbookLevels.size()][]); + } - public BigDecimal[][] getAsks() { - return getSide(Order.OrderType.ASK); - } + public BigDecimal[][] getAsks() { + return getSide(Order.OrderType.ASK); + } - public BigDecimal[][] getBids() { - return getSide(Order.OrderType.BID); - } + public BigDecimal[][] getBids() { + return getSide(Order.OrderType.BID); + } - public OkCoinDepth toOkCoinDepth(long epoch) { - Date timestamp = new java.util.Date(epoch); - return new OkCoinDepth(getAsks(), getBids(), timestamp); - } + public OkCoinDepth toOkCoinDepth(long epoch) { + Date timestamp = new java.util.Date(epoch); + return new OkCoinDepth(getAsks(), getBids(), timestamp); + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinWebSocketTrade.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinWebSocketTrade.java index 83bdd5dfd..9962dab56 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinWebSocketTrade.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/OkCoinWebSocketTrade.java @@ -1,7 +1,5 @@ package info.bitrich.xchangestream.okcoin.dto; -import org.knowm.xchange.okcoin.dto.marketdata.OkCoinTrade; - import java.math.BigDecimal; import java.text.DateFormat; import java.text.ParseException; @@ -9,20 +7,26 @@ import java.util.Calendar; import java.util.Date; import java.util.TimeZone; +import org.knowm.xchange.okcoin.dto.marketdata.OkCoinTrade; public class OkCoinWebSocketTrade extends OkCoinTrade { - public OkCoinWebSocketTrade(String[] items) throws ParseException { - super(getDate(items[3]).getTime() / 1000, new BigDecimal(items[1]), new BigDecimal(items[2]), Long.valueOf(items[0]), items[4]); - } + public OkCoinWebSocketTrade(String[] items) throws ParseException { + super( + getDate(items[3]).getTime() / 1000, + new BigDecimal(items[1]), + new BigDecimal(items[2]), + Long.valueOf(items[0]), + items[4]); + } - private static Date getDate(String exchangeTime) throws ParseException { - DateFormat tdf = new SimpleDateFormat("yyyy-MM-dd"); - tdf.setTimeZone(TimeZone.getTimeZone("Hongkong")); - Date today = Calendar.getInstance(TimeZone.getDefault()).getTime(); - String exchangeToday = tdf.format(today); + private static Date getDate(String exchangeTime) throws ParseException { + DateFormat tdf = new SimpleDateFormat("yyyy-MM-dd"); + tdf.setTimeZone(TimeZone.getTimeZone("Hongkong")); + Date today = Calendar.getInstance(TimeZone.getDefault()).getTime(); + String exchangeToday = tdf.format(today); - SimpleDateFormat fdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss X"); - fdf.setTimeZone(TimeZone.getDefault()); - return fdf.parse(exchangeToday + " " + exchangeTime + " +0800"); - } + SimpleDateFormat fdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss X"); + fdf.setTimeZone(TimeZone.getDefault()); + return fdf.parse(exchangeToday + " " + exchangeTime + " +0800"); + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/WebSocketMessage.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/WebSocketMessage.java index 615cd906f..bed2f27c6 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/WebSocketMessage.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/WebSocketMessage.java @@ -3,19 +3,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class WebSocketMessage { - private final String event; - private final String channel; + private final String event; + private final String channel; - public WebSocketMessage(@JsonProperty("event") String event, @JsonProperty("channel") String channel) { - this.event = event; - this.channel = channel; - } + public WebSocketMessage( + @JsonProperty("event") String event, @JsonProperty("channel") String channel) { + this.event = event; + this.channel = channel; + } - public String getEvent() { - return event; - } + public String getEvent() { + return event; + } - public String getChannel() { - return channel; - } + public String getChannel() { + return channel; + } } diff --git a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/marketdata/FutureTicker.java b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/marketdata/FutureTicker.java index 341481651..fb52d6514 100644 --- a/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/marketdata/FutureTicker.java +++ b/xchange-stream-okcoin/src/main/java/info/bitrich/xchangestream/okcoin/dto/marketdata/FutureTicker.java @@ -1,92 +1,91 @@ package info.bitrich.xchangestream.okcoin.dto.marketdata; import com.fasterxml.jackson.annotation.JsonProperty; - import java.math.BigDecimal; public class FutureTicker { - private Long contractId; // 合约ID - private Integer unitAmount; // 合约价值 - private BigDecimal holdAmount; // 当前持仓量 - - private BigDecimal limitHigh; // 最高买入限制价格 - private BigDecimal limitLow; // 最低卖出限制价格 - - private BigDecimal buy; // 买一价格 - private BigDecimal sell; // 卖一价格 - private BigDecimal last; // 最新成交价 - - private BigDecimal high; // 24小时最高价格 - private BigDecimal low; // 24小时最低价格 - private BigDecimal vol; // 24小时成交量 - - public FutureTicker( - @JsonProperty("contractId") final Long contractId, - @JsonProperty("unitAmount") final Integer unitAmount, - @JsonProperty("hold_amount") final BigDecimal holdAmount, - @JsonProperty("limitHigh") final BigDecimal limitHigh, - @JsonProperty("limitLow") final BigDecimal limitLow, - @JsonProperty("buy") final BigDecimal buy, - @JsonProperty("sell") final BigDecimal sell, - @JsonProperty("last") final BigDecimal last, - @JsonProperty("high") final BigDecimal high, - @JsonProperty("low") final BigDecimal low, - @JsonProperty("vol") final BigDecimal vol) { - - this.contractId = contractId; - this.unitAmount = unitAmount; - this.holdAmount = holdAmount; - this.limitHigh = limitHigh; - this.limitLow = limitLow; - this.buy = buy; - this.sell = sell; - this.last = last; - this.high = high; - this.low = low; - this.vol = vol; - } - - public Long getContractId() { - return contractId; - } - - public Integer getUnitAmount() { - return unitAmount; - } - - public BigDecimal getHoldAmount() { - return holdAmount; - } - - public BigDecimal getLimitHigh() { - return limitHigh; - } - - public BigDecimal getLimitLow() { - return limitLow; - } - - public BigDecimal getBuy() { - return buy; - } - - public BigDecimal getSell() { - return sell; - } - - public BigDecimal getLast() { - return last; - } - - public BigDecimal getHigh() { - return high; - } - - public BigDecimal getLow() { - return low; - } - - public BigDecimal getVol() { - return vol; - } + private Long contractId; // 合约ID + private Integer unitAmount; // 合约价值 + private BigDecimal holdAmount; // 当前持仓量 + + private BigDecimal limitHigh; // 最高买入限制价格 + private BigDecimal limitLow; // 最低卖出限制价格 + + private BigDecimal buy; // 买一价格 + private BigDecimal sell; // 卖一价格 + private BigDecimal last; // 最新成交价 + + private BigDecimal high; // 24小时最高价格 + private BigDecimal low; // 24小时最低价格 + private BigDecimal vol; // 24小时成交量 + + public FutureTicker( + @JsonProperty("contractId") final Long contractId, + @JsonProperty("unitAmount") final Integer unitAmount, + @JsonProperty("hold_amount") final BigDecimal holdAmount, + @JsonProperty("limitHigh") final BigDecimal limitHigh, + @JsonProperty("limitLow") final BigDecimal limitLow, + @JsonProperty("buy") final BigDecimal buy, + @JsonProperty("sell") final BigDecimal sell, + @JsonProperty("last") final BigDecimal last, + @JsonProperty("high") final BigDecimal high, + @JsonProperty("low") final BigDecimal low, + @JsonProperty("vol") final BigDecimal vol) { + + this.contractId = contractId; + this.unitAmount = unitAmount; + this.holdAmount = holdAmount; + this.limitHigh = limitHigh; + this.limitLow = limitLow; + this.buy = buy; + this.sell = sell; + this.last = last; + this.high = high; + this.low = low; + this.vol = vol; + } + + public Long getContractId() { + return contractId; + } + + public Integer getUnitAmount() { + return unitAmount; + } + + public BigDecimal getHoldAmount() { + return holdAmount; + } + + public BigDecimal getLimitHigh() { + return limitHigh; + } + + public BigDecimal getLimitLow() { + return limitLow; + } + + public BigDecimal getBuy() { + return buy; + } + + public BigDecimal getSell() { + return sell; + } + + public BigDecimal getLast() { + return last; + } + + public BigDecimal getHigh() { + return high; + } + + public BigDecimal getLow() { + return low; + } + + public BigDecimal getVol() { + return vol; + } } diff --git a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinManualExample.java b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinManualExample.java index 56e074b3b..23715e41d 100644 --- a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinManualExample.java +++ b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinManualExample.java @@ -7,31 +7,47 @@ import org.slf4j.LoggerFactory; public class OkCoinManualExample { - private static final Logger LOG = LoggerFactory.getLogger(OkCoinManualExample.class); - - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(OkCoinStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); - - exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); - - exchange.getStreamingMarketDataService().getTicker(CurrencyPair.BTC_USD).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); - - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - exchange.disconnect().blockingAwait(); + private static final Logger LOG = LoggerFactory.getLogger(OkCoinManualExample.class); + + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(OkCoinStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); + + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_USD) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.BTC_USD) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting trades: ", throwable)); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + + exchange.disconnect().blockingAwait(); + } } diff --git a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataServiceTest.java b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataServiceTest.java index 30919ed2a..e33772d14 100644 --- a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataServiceTest.java +++ b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingMarketDataServiceTest.java @@ -1,9 +1,16 @@ package info.bitrich.xchangestream.okcoin; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.reactivex.Observable; import io.reactivex.observers.TestObserver; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import org.junit.Before; import org.junit.Test; import org.knowm.xchange.currency.CurrencyPair; @@ -13,51 +20,79 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - public class OkCoinStreamingMarketDataServiceTest { - @Mock - private OkCoinStreamingService okCoinStreamingService; - private OkCoinStreamingMarketDataService marketDataService; + @Mock private OkCoinStreamingService okCoinStreamingService; + private OkCoinStreamingMarketDataService marketDataService; - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - marketDataService = new OkCoinStreamingMarketDataService(okCoinStreamingService); - } + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + marketDataService = new OkCoinStreamingMarketDataService(okCoinStreamingService); + } - @Test - public void testGetOrderBook() throws Exception { - // Given order book in JSON - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode jsonNode = objectMapper.readTree(ClassLoader.getSystemClassLoader().getResourceAsStream("order-book.json")); + @Test + public void testGetOrderBook() throws Exception { + // Given order book in JSON + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = + objectMapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("order-book.json")); - when(okCoinStreamingService.subscribeChannel(any())).thenReturn(Observable.just(jsonNode)); + when(okCoinStreamingService.subscribeChannel(any())).thenReturn(Observable.just(jsonNode)); - Date timestamp = new Date(1484602135246L); + Date timestamp = new Date(1484602135246L); - List bids = new ArrayList<>(); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.922"), CurrencyPair.BTC_USD, null, timestamp, new BigDecimal("819.9"))); - bids.add(new LimitOrder(Order.OrderType.BID, new BigDecimal("0.085"), CurrencyPair.BTC_USD, null, timestamp, new BigDecimal("818.63"))); + List bids = new ArrayList<>(); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.922"), + CurrencyPair.BTC_USD, + null, + timestamp, + new BigDecimal("819.9"))); + bids.add( + new LimitOrder( + Order.OrderType.BID, + new BigDecimal("0.085"), + CurrencyPair.BTC_USD, + null, + timestamp, + new BigDecimal("818.63"))); - List asks = new ArrayList<>(); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("0.035"), CurrencyPair.BTC_USD, null, timestamp, new BigDecimal("821.6"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("5.18"), CurrencyPair.BTC_USD, null, timestamp, new BigDecimal("821.65"))); - asks.add(new LimitOrder(Order.OrderType.ASK, new BigDecimal("2.89"), CurrencyPair.BTC_USD, null, timestamp, new BigDecimal("821.7"))); + List asks = new ArrayList<>(); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("0.035"), + CurrencyPair.BTC_USD, + null, + timestamp, + new BigDecimal("821.6"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("5.18"), + CurrencyPair.BTC_USD, + null, + timestamp, + new BigDecimal("821.65"))); + asks.add( + new LimitOrder( + Order.OrderType.ASK, + new BigDecimal("2.89"), + CurrencyPair.BTC_USD, + null, + timestamp, + new BigDecimal("821.7"))); - OrderBook expected = new OrderBook(timestamp, asks, bids); + OrderBook expected = new OrderBook(timestamp, asks, bids); - // Call get order book observable - TestObserver test = marketDataService.getOrderBook(CurrencyPair.BTC_USD).test(); + // Call get order book observable + TestObserver test = marketDataService.getOrderBook(CurrencyPair.BTC_USD).test(); - // Get order book object in correct order - test.assertResult(expected); - } + // Get order book object in correct order + test.assertResult(expected); + } } diff --git a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingServiceTest.java b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingServiceTest.java index 3762d1dc3..0efe436b5 100644 --- a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingServiceTest.java +++ b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkCoinStreamingServiceTest.java @@ -1,44 +1,51 @@ package info.bitrich.xchangestream.okcoin; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Before; -import org.junit.Test; - import java.nio.file.Files; import java.nio.file.Paths; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Before; +import org.junit.Test; public class OkCoinStreamingServiceTest { - private OkCoinStreamingService streamingService; - - @Before - public void setUp() throws Exception { - streamingService = new OkCoinStreamingService("wss://example.com/websocket"); - } - - @Test - public void testGetSubscribeMessage() throws Exception { - String subscribeMessage = streamingService.getSubscribeMessage("ok_sub_spot_btc_usd_depth"); - String expected = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("subscribe.json").toURI()))); - assertThat(subscribeMessage).isEqualTo(expected); - } - - @Test - public void testGetUnsubscribeMessage() throws Exception { - String subscribeMessage = streamingService.getUnsubscribeMessage("orderbook"); - String expected = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("unsubscribe.json").toURI()))); - assertThat(subscribeMessage).isEqualTo(expected); - } - - @Test - public void testGetChannelFromMessage() throws Exception { - String expected = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("order-book.json").toURI()))); - JsonNode data = new ObjectMapper().readTree(expected); - String channel = streamingService.getChannelNameFromMessage(data); - - assertThat(channel).isEqualTo("ok_sub_spot_btc_usd_depth"); - } -} \ No newline at end of file + private OkCoinStreamingService streamingService; + + @Before + public void setUp() throws Exception { + streamingService = new OkCoinStreamingService("wss://example.com/websocket"); + } + + @Test + public void testGetSubscribeMessage() throws Exception { + String subscribeMessage = streamingService.getSubscribeMessage("ok_sub_spot_btc_usd_depth"); + String expected = + new String( + Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("subscribe.json").toURI()))); + assertThat(subscribeMessage).isEqualTo(expected); + } + + @Test + public void testGetUnsubscribeMessage() throws Exception { + String subscribeMessage = streamingService.getUnsubscribeMessage("orderbook"); + String expected = + new String( + Files.readAllBytes( + Paths.get(ClassLoader.getSystemResource("unsubscribe.json").toURI()))); + assertThat(subscribeMessage).isEqualTo(expected); + } + + @Test + public void testGetChannelFromMessage() throws Exception { + String expected = + new String( + Files.readAllBytes( + Paths.get(ClassLoader.getSystemResource("order-book.json").toURI()))); + JsonNode data = new ObjectMapper().readTree(expected); + String channel = streamingService.getChannelNameFromMessage(data); + + assertThat(channel).isEqualTo("ok_sub_spot_btc_usd_depth"); + } +} diff --git a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExFuturesManualExample.java b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExFuturesManualExample.java index cc2bcaef3..7cce834c3 100644 --- a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExFuturesManualExample.java +++ b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExFuturesManualExample.java @@ -8,30 +8,46 @@ import org.slf4j.LoggerFactory; public class OkExFuturesManualExample { - private static final Logger LOG = LoggerFactory.getLogger(OkExManualExample.class); - - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(OkExFuturesStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); - - exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD, FuturesContract.Quarter).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); - - exchange.getStreamingMarketDataService().getTicker(CurrencyPair.BTC_USD, FuturesContract.Quarter).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD, FuturesContract.Quarter).subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); - - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + private static final Logger LOG = LoggerFactory.getLogger(OkExManualExample.class); + + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange( + OkExFuturesStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); + + exchange + .getStreamingMarketDataService() + .getOrderBook(CurrencyPair.BTC_USD, FuturesContract.Quarter) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.BTC_USD, FuturesContract.Quarter) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USD, FuturesContract.Quarter) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting trades: ", throwable)); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + } } - diff --git a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExManualExample.java b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExManualExample.java index d84f457c2..ee30f3829 100644 --- a/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExManualExample.java +++ b/xchange-stream-okcoin/src/test/java/info/bitrich/xchangestream/okcoin/OkExManualExample.java @@ -7,34 +7,48 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 17.11.17. - */ +/** Created by Lukas Zaoralek on 17.11.17. */ public class OkExManualExample { - private static final Logger LOG = LoggerFactory.getLogger(OkExManualExample.class); + private static final Logger LOG = LoggerFactory.getLogger(OkExManualExample.class); - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(OkExStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(OkExStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); - CurrencyPair btcUsdt = new CurrencyPair(new Currency("BTC"), new Currency("USDT")); - exchange.getStreamingMarketDataService().getOrderBook(btcUsdt).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); + CurrencyPair btcUsdt = new CurrencyPair(new Currency("BTC"), new Currency("USDT")); + exchange + .getStreamingMarketDataService() + .getOrderBook(btcUsdt) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); - exchange.getStreamingMarketDataService().getTicker(btcUsdt).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + exchange + .getStreamingMarketDataService() + .getTicker(btcUsdt) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - exchange.getStreamingMarketDataService().getTrades(btcUsdt).subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); + exchange + .getStreamingMarketDataService() + .getTrades(btcUsdt) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting trades: ", throwable)); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + } } diff --git a/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingExchange.java b/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingExchange.java index b3c0ef7dc..14fe13e32 100644 --- a/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingExchange.java +++ b/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingExchange.java @@ -8,42 +8,44 @@ import org.knowm.xchange.poloniex.PoloniexExchange; public class PoloniexStreamingExchange extends PoloniexExchange implements StreamingExchange { - private static final String API_URI = "wss://api.poloniex.com"; - private static final String API_REALM = "realm1"; - - private final WampStreamingService streamingService; - private PoloniexStreamingMarketDataService streamingMarketDataService; - - public PoloniexStreamingExchange() { - streamingService = new WampStreamingService(API_URI, API_REALM); - } - - @Override - protected void initServices() { - super.initServices(); - streamingMarketDataService = new PoloniexStreamingMarketDataService(streamingService); - } - - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } - - @Override - public Completable disconnect() { - return null; - } - - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } + private static final String API_URI = "wss://api.poloniex.com"; + private static final String API_REALM = "realm1"; + + private final WampStreamingService streamingService; + private PoloniexStreamingMarketDataService streamingMarketDataService; + + public PoloniexStreamingExchange() { + streamingService = new WampStreamingService(API_URI, API_REALM); + } + + @Override + protected void initServices() { + super.initServices(); + streamingMarketDataService = new PoloniexStreamingMarketDataService(streamingService); + } + + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } + + @Override + public Completable disconnect() { + return null; + } + + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } } diff --git a/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingMarketDataService.java b/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingMarketDataService.java index 80c0a17c8..07f948ab3 100644 --- a/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingMarketDataService.java +++ b/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/PoloniexStreamingMarketDataService.java @@ -8,6 +8,8 @@ import info.bitrich.xchangestream.poloniex.utils.MinMaxPriorityQueueUtils; import info.bitrich.xchangestream.service.wamp.WampStreamingService; import io.reactivex.Observable; +import java.math.BigDecimal; +import java.util.*; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.marketdata.OrderBook; @@ -20,121 +22,156 @@ import org.knowm.xchange.poloniex.dto.marketdata.PoloniexPublicTrade; import org.knowm.xchange.poloniex.dto.marketdata.PoloniexTicker; -import java.math.BigDecimal; -import java.util.*; - public class PoloniexStreamingMarketDataService implements StreamingMarketDataService { - public static final String TICKER_CHANNEL_NAME = "ticker"; - private final WampStreamingService streamingService; - private final Supplier> streamingTickers; - - public PoloniexStreamingMarketDataService(WampStreamingService streamingService) { - this.streamingService = streamingService; - this.streamingTickers = Suppliers.memoize(() -> streamingService.subscribeChannel(TICKER_CHANNEL_NAME) - .map(pubSubData -> { - PoloniexMarketData marketData = new PoloniexMarketData(); - marketData.setLast(new BigDecimal(pubSubData.arguments().get(1).asText())); - marketData.setLowestAsk(new BigDecimal(pubSubData.arguments().get(2).asText())); - marketData.setHighestBid(new BigDecimal(pubSubData.arguments().get(3).asText())); - marketData.setPercentChange(new BigDecimal(pubSubData.arguments().get(4).asText())); - marketData.setBaseVolume(new BigDecimal(pubSubData.arguments().get(5).asText())); - marketData.setQuoteVolume(new BigDecimal(pubSubData.arguments().get(6).asText())); - marketData.setHigh24hr(new BigDecimal(pubSubData.arguments().get(8).asText())); - marketData.setLow24hr(new BigDecimal(pubSubData.arguments().get(9).asText())); - - PoloniexTicker ticker = new PoloniexTicker(marketData, PoloniexUtils.toCurrencyPair(pubSubData.arguments().get(0).asText())); - return PoloniexAdapters.adaptPoloniexTicker(ticker, ticker.getCurrencyPair()); - }).share()); - } - - private Map> orderBookBids = new HashMap<>(); - private Map> orderBookAsks = new HashMap<>(); - private static final int ORDER_BOOK_LEVELS = 100; - - Comparator asendingPriceComparator = Comparator.comparing(LimitOrder::getLimitPrice); - Comparator descendingPriceComparator = asendingPriceComparator.reversed(); - - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - MinMaxPriorityQueue bidQueue, askQueue; - - if (!orderBookBids.containsKey(currencyPair)) { - bidQueue = MinMaxPriorityQueue.orderedBy(descendingPriceComparator).expectedSize(ORDER_BOOK_LEVELS).maximumSize(ORDER_BOOK_LEVELS).create(); - orderBookBids.put(currencyPair, bidQueue); - } else { - bidQueue = orderBookAsks.get(currencyPair); - } - - if (!orderBookAsks.containsKey(currencyPair)) { - askQueue = MinMaxPriorityQueue.orderedBy(asendingPriceComparator).expectedSize(ORDER_BOOK_LEVELS).maximumSize(ORDER_BOOK_LEVELS).create(); - orderBookAsks.put(currencyPair, askQueue); - } else { - askQueue = orderBookAsks.get(currencyPair); - } - - String channel = PoloniexUtils.toPairString(currencyPair); - return streamingService.subscribeChannel(channel) - .map(pubSubData -> { - Date now = new Date(); - for (int i = 0; i < pubSubData.arguments().size(); i++) { - JsonNode item = pubSubData.arguments().get(i); - String type = item.get("type").asText(); - if ("orderBookRemove".equals(type) || "orderBookModify".equals(type)) { - - JsonNode data = item.get("data"); - BigDecimal rate = new BigDecimal(data.get("rate").asText()); - BigDecimal amount = data.has("amount") ? new BigDecimal(data.get("amount").asText()) : null; - String bookType = data.get("type").asText(); - if ("orderBookRemove".equals(type)) { - if ("ask".equals(bookType)) { - askQueue.removeIf(x -> rate.equals(x.getLimitPrice())); - } else if ("bid".equals(bookType)) { - bidQueue.removeIf(x -> rate.equals(x.getLimitPrice())); - } - - } else { - if ("ask".equals(bookType)) { - LimitOrder level = new LimitOrder(Order.OrderType.ASK, amount, currencyPair, null, now, rate); - askQueue.add(level); - } else if ("bid".equals(bookType)) { - LimitOrder level = new LimitOrder(Order.OrderType.BID, amount, currencyPair, null, now, rate); - bidQueue.add(level); - } - } - } - } - return new OrderBook(now, MinMaxPriorityQueueUtils.toList(askQueue, asendingPriceComparator), MinMaxPriorityQueueUtils.toList(bidQueue, descendingPriceComparator)); - }); + public static final String TICKER_CHANNEL_NAME = "ticker"; + private final WampStreamingService streamingService; + private final Supplier> streamingTickers; + + public PoloniexStreamingMarketDataService(WampStreamingService streamingService) { + this.streamingService = streamingService; + this.streamingTickers = + Suppliers.memoize( + () -> + streamingService + .subscribeChannel(TICKER_CHANNEL_NAME) + .map( + pubSubData -> { + PoloniexMarketData marketData = new PoloniexMarketData(); + marketData.setLast( + new BigDecimal(pubSubData.arguments().get(1).asText())); + marketData.setLowestAsk( + new BigDecimal(pubSubData.arguments().get(2).asText())); + marketData.setHighestBid( + new BigDecimal(pubSubData.arguments().get(3).asText())); + marketData.setPercentChange( + new BigDecimal(pubSubData.arguments().get(4).asText())); + marketData.setBaseVolume( + new BigDecimal(pubSubData.arguments().get(5).asText())); + marketData.setQuoteVolume( + new BigDecimal(pubSubData.arguments().get(6).asText())); + marketData.setHigh24hr( + new BigDecimal(pubSubData.arguments().get(8).asText())); + marketData.setLow24hr( + new BigDecimal(pubSubData.arguments().get(9).asText())); + + PoloniexTicker ticker = + new PoloniexTicker( + marketData, + PoloniexUtils.toCurrencyPair( + pubSubData.arguments().get(0).asText())); + return PoloniexAdapters.adaptPoloniexTicker( + ticker, ticker.getCurrencyPair()); + }) + .share()); + } + + private Map> orderBookBids = new HashMap<>(); + private Map> orderBookAsks = new HashMap<>(); + private static final int ORDER_BOOK_LEVELS = 100; + + Comparator asendingPriceComparator = Comparator.comparing(LimitOrder::getLimitPrice); + Comparator descendingPriceComparator = asendingPriceComparator.reversed(); + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + MinMaxPriorityQueue bidQueue, askQueue; + + if (!orderBookBids.containsKey(currencyPair)) { + bidQueue = + MinMaxPriorityQueue.orderedBy(descendingPriceComparator) + .expectedSize(ORDER_BOOK_LEVELS) + .maximumSize(ORDER_BOOK_LEVELS) + .create(); + orderBookBids.put(currencyPair, bidQueue); + } else { + bidQueue = orderBookAsks.get(currencyPair); } - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - return streamingTickers.get().filter(ticker -> ticker.getCurrencyPair().equals(currencyPair)); + if (!orderBookAsks.containsKey(currencyPair)) { + askQueue = + MinMaxPriorityQueue.orderedBy(asendingPriceComparator) + .expectedSize(ORDER_BOOK_LEVELS) + .maximumSize(ORDER_BOOK_LEVELS) + .create(); + orderBookAsks.put(currencyPair, askQueue); + } else { + askQueue = orderBookAsks.get(currencyPair); } - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String channel = PoloniexUtils.toPairString(currencyPair); - return streamingService.subscribeChannel(channel) - .flatMap(pubSubData -> { - List res = new ArrayList<>(); - for (int i = 0; i < pubSubData.arguments().size(); i++) { - JsonNode item = pubSubData.arguments().get(i); - if ("newTrade".equals(item.get("type").asText())) { - JsonNode data = item.get("data"); - PoloniexPublicTrade trade = new PoloniexPublicTrade(); - trade.setTradeID(data.get("tradeID").asText()); - trade.setAmount(new BigDecimal(data.get("amount").asText("0"))); - trade.setDate(data.get("date").asText()); - trade.setRate(new BigDecimal(data.get("rate").asText("0"))); - trade.setTotal(new BigDecimal(data.get("total").asText("0"))); - trade.setType(data.get("type").asText()); - - res.add(PoloniexAdapters.adaptPoloniexPublicTrade(trade, currencyPair)); - } + String channel = PoloniexUtils.toPairString(currencyPair); + return streamingService + .subscribeChannel(channel) + .map( + pubSubData -> { + Date now = new Date(); + for (int i = 0; i < pubSubData.arguments().size(); i++) { + JsonNode item = pubSubData.arguments().get(i); + String type = item.get("type").asText(); + if ("orderBookRemove".equals(type) || "orderBookModify".equals(type)) { + + JsonNode data = item.get("data"); + BigDecimal rate = new BigDecimal(data.get("rate").asText()); + BigDecimal amount = + data.has("amount") ? new BigDecimal(data.get("amount").asText()) : null; + String bookType = data.get("type").asText(); + if ("orderBookRemove".equals(type)) { + if ("ask".equals(bookType)) { + askQueue.removeIf(x -> rate.equals(x.getLimitPrice())); + } else if ("bid".equals(bookType)) { + bidQueue.removeIf(x -> rate.equals(x.getLimitPrice())); + } + } else { + if ("ask".equals(bookType)) { + LimitOrder level = + new LimitOrder( + Order.OrderType.ASK, amount, currencyPair, null, now, rate); + askQueue.add(level); + } else if ("bid".equals(bookType)) { + LimitOrder level = + new LimitOrder( + Order.OrderType.BID, amount, currencyPair, null, now, rate); + bidQueue.add(level); } - return Observable.fromIterable(res); - }); - } + } + } + } + return new OrderBook( + now, + MinMaxPriorityQueueUtils.toList(askQueue, asendingPriceComparator), + MinMaxPriorityQueueUtils.toList(bidQueue, descendingPriceComparator)); + }); + } + + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + return streamingTickers.get().filter(ticker -> ticker.getCurrencyPair().equals(currencyPair)); + } + + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + String channel = PoloniexUtils.toPairString(currencyPair); + return streamingService + .subscribeChannel(channel) + .flatMap( + pubSubData -> { + List res = new ArrayList<>(); + for (int i = 0; i < pubSubData.arguments().size(); i++) { + JsonNode item = pubSubData.arguments().get(i); + if ("newTrade".equals(item.get("type").asText())) { + JsonNode data = item.get("data"); + PoloniexPublicTrade trade = new PoloniexPublicTrade(); + trade.setTradeID(data.get("tradeID").asText()); + trade.setAmount(new BigDecimal(data.get("amount").asText("0"))); + trade.setDate(data.get("date").asText()); + trade.setRate(new BigDecimal(data.get("rate").asText("0"))); + trade.setTotal(new BigDecimal(data.get("total").asText("0"))); + trade.setType(data.get("type").asText()); + + res.add(PoloniexAdapters.adaptPoloniexPublicTrade(trade, currencyPair)); + } + } + return Observable.fromIterable(res); + }); + } } diff --git a/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/utils/MinMaxPriorityQueueUtils.java b/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/utils/MinMaxPriorityQueueUtils.java index 6d59b4912..8758319d6 100644 --- a/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/utils/MinMaxPriorityQueueUtils.java +++ b/xchange-stream-poloniex/src/main/java/info/bitrich/xchangestream/poloniex/utils/MinMaxPriorityQueueUtils.java @@ -1,20 +1,17 @@ package info.bitrich.xchangestream.poloniex.utils; - import com.google.common.collect.MinMaxPriorityQueue; - import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class MinMaxPriorityQueueUtils { - public static List toList(MinMaxPriorityQueue queue, Comparator comparator) { - List list = new ArrayList(queue.size()); - for (T e : queue) - list.add(e); - if (comparator != null) { - list.sort(comparator); - } - return list; + public static List toList(MinMaxPriorityQueue queue, Comparator comparator) { + List list = new ArrayList(queue.size()); + for (T e : queue) list.add(e); + if (comparator != null) { + list.sort(comparator); } + return list; + } } diff --git a/xchange-stream-poloniex/src/test/java/info/bitrich/xchangestream/poloniex/PoloniexManualExample.java b/xchange-stream-poloniex/src/test/java/info/bitrich/xchangestream/poloniex/PoloniexManualExample.java index c13863612..2f52d379c 100644 --- a/xchange-stream-poloniex/src/test/java/info/bitrich/xchangestream/poloniex/PoloniexManualExample.java +++ b/xchange-stream-poloniex/src/test/java/info/bitrich/xchangestream/poloniex/PoloniexManualExample.java @@ -7,29 +7,37 @@ import org.slf4j.LoggerFactory; public class PoloniexManualExample { - private static final Logger LOG = LoggerFactory.getLogger(PoloniexManualExample.class); + private static final Logger LOG = LoggerFactory.getLogger(PoloniexManualExample.class); - public static void main(String[] args) { - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(PoloniexStreamingExchange.class.getName()); - exchange.connect().blockingAwait(); + public static void main(String[] args) { + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(PoloniexStreamingExchange.class.getName()); + exchange.connect().blockingAwait(); -// exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD).subscribe(orderBook -> { -// LOG.info("First ask: {}", orderBook.getAsks().get(0)); -// LOG.info("First bid: {}", orderBook.getBids().get(0)); -// }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); + // + // exchange.getStreamingMarketDataService().getOrderBook(CurrencyPair.BTC_USD).subscribe(orderBook -> { + // LOG.info("First ask: {}", orderBook.getAsks().get(0)); + // LOG.info("First bid: {}", orderBook.getBids().get(0)); + // }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); - exchange.getStreamingMarketDataService().getTicker(CurrencyPair.LTC_BTC).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + exchange + .getStreamingMarketDataService() + .getTicker(CurrencyPair.LTC_BTC) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); -// exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(trade -> { -// LOG.info("TRADE: {}", trade); -// }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); + // + // exchange.getStreamingMarketDataService().getTrades(CurrencyPair.BTC_USD).subscribe(trade -> { + // LOG.info("TRADE: {}", trade); + // }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); } + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingExchange.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingExchange.java index e45e5c560..fc16af995 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingExchange.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingExchange.java @@ -8,103 +8,105 @@ import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Completable; import io.reactivex.Observable; -import org.knowm.xchange.ExchangeSpecification; -import org.knowm.xchange.currency.Currency; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.poloniex.PoloniexExchange; - import java.io.IOException; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.poloniex.PoloniexExchange; -/** - * Created by Lukas Zaoralek on 10.11.17. - */ +/** Created by Lukas Zaoralek on 10.11.17. */ public class PoloniexStreamingExchange extends PoloniexExchange implements StreamingExchange { - private static final String API_URI = "wss://api2.poloniex.com"; - private static final String TICKER_URL = "https://poloniex.com/public?command=returnTicker"; - - private final PoloniexStreamingService streamingService; - private PoloniexStreamingMarketDataService streamingMarketDataService; - - public PoloniexStreamingExchange() { - this.streamingService = new PoloniexStreamingService(API_URI); - } - - @Override - protected void initServices() { - applyStreamingSpecification(getExchangeSpecification(), streamingService); - super.initServices(); - Map currencyPairMap = getCurrencyPairMap(); - streamingMarketDataService = new PoloniexStreamingMarketDataService(streamingService, currencyPairMap); - } - - private Map getCurrencyPairMap() { - Map currencyPairMap = new HashMap<>(); - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - - try { - URL tickerUrl = new URL(TICKER_URL); - JsonNode jsonRootTickers = mapper.readTree(tickerUrl); - Iterator pairSymbols = jsonRootTickers.fieldNames(); - pairSymbols.forEachRemaining(pairSymbol ->{ - String id = jsonRootTickers.get(pairSymbol).get("id").toString(); - String[] currencies = pairSymbol.split("_"); - CurrencyPair currencyPair = new CurrencyPair(new Currency(currencies[1]), new Currency(currencies[0])); - currencyPairMap.put(Integer.valueOf(id), currencyPair); - - }); - } catch (IOException e) { - e.printStackTrace(); - } - - return currencyPairMap; + private static final String API_URI = "wss://api2.poloniex.com"; + private static final String TICKER_URL = "https://poloniex.com/public?command=returnTicker"; + + private final PoloniexStreamingService streamingService; + private PoloniexStreamingMarketDataService streamingMarketDataService; + + public PoloniexStreamingExchange() { + this.streamingService = new PoloniexStreamingService(API_URI); + } + + @Override + protected void initServices() { + applyStreamingSpecification(getExchangeSpecification(), streamingService); + super.initServices(); + Map currencyPairMap = getCurrencyPairMap(); + streamingMarketDataService = + new PoloniexStreamingMarketDataService(streamingService, currencyPairMap); + } + + private Map getCurrencyPairMap() { + Map currencyPairMap = new HashMap<>(); + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + try { + URL tickerUrl = new URL(TICKER_URL); + JsonNode jsonRootTickers = mapper.readTree(tickerUrl); + Iterator pairSymbols = jsonRootTickers.fieldNames(); + pairSymbols.forEachRemaining( + pairSymbol -> { + String id = jsonRootTickers.get(pairSymbol).get("id").toString(); + String[] currencies = pairSymbol.split("_"); + CurrencyPair currencyPair = + new CurrencyPair(new Currency(currencies[1]), new Currency(currencies[0])); + currencyPairMap.put(Integer.valueOf(id), currencyPair); + }); + } catch (IOException e) { + e.printStackTrace(); } - @Override - public Completable connect(ProductSubscription... args) { - return streamingService.connect(); - } - - @Override - public Completable disconnect() { - return streamingService.disconnect(); - } - - @Override - public Observable connectionIdle() { - return streamingService.subscribeIdle(); - } - - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification spec = super.getDefaultExchangeSpecification(); - spec.setShouldLoadRemoteMetaData(false); - - return spec; - } - - @Override - public StreamingMarketDataService getStreamingMarketDataService() { - return streamingMarketDataService; - } - - @Override - public boolean isAlive() { - return streamingService.isSocketOpen(); - } - - @Override - public void useCompressedMessages(boolean compressedMessages) { streamingService.useCompressedMessages(compressedMessages); } - - @Override - public Observable connectionSuccess() { - return streamingService.subscribeConnectionSuccess(); - } - @Override - public Observable reconnectFailure() { - return streamingService.subscribeReconnectFailure(); - } + return currencyPairMap; + } + + @Override + public Completable connect(ProductSubscription... args) { + return streamingService.connect(); + } + + @Override + public Completable disconnect() { + return streamingService.disconnect(); + } + + @Override + public Observable connectionIdle() { + return streamingService.subscribeIdle(); + } + + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification spec = super.getDefaultExchangeSpecification(); + spec.setShouldLoadRemoteMetaData(false); + + return spec; + } + + @Override + public StreamingMarketDataService getStreamingMarketDataService() { + return streamingMarketDataService; + } + + @Override + public boolean isAlive() { + return streamingService.isSocketOpen(); + } + + @Override + public void useCompressedMessages(boolean compressedMessages) { + streamingService.useCompressedMessages(compressedMessages); + } + + @Override + public Observable connectionSuccess() { + return streamingService.subscribeConnectionSuccess(); + } + + @Override + public Observable reconnectFailure() { + return streamingService.subscribeReconnectFailure(); + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingMarketDataService.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingMarketDataService.java index 343d436a4..c83ea8dbc 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingMarketDataService.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingMarketDataService.java @@ -1,5 +1,8 @@ package info.bitrich.xchangestream.poloniex2; +import static org.knowm.xchange.poloniex.PoloniexAdapters.adaptPoloniexDepth; +import static org.knowm.xchange.poloniex.PoloniexAdapters.adaptPoloniexTicker; + import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; @@ -7,6 +10,11 @@ import info.bitrich.xchangestream.poloniex2.dto.*; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.SortedMap; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; @@ -14,91 +22,99 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.SortedMap; - -import static org.knowm.xchange.poloniex.PoloniexAdapters.adaptPoloniexDepth; -import static org.knowm.xchange.poloniex.PoloniexAdapters.adaptPoloniexTicker; - -/** - * Created by Lukas Zaoralek on 10.11.17. - */ +/** Created by Lukas Zaoralek on 10.11.17. */ public class PoloniexStreamingMarketDataService implements StreamingMarketDataService { - private static final Logger LOG = LoggerFactory.getLogger(PoloniexStreamingMarketDataService.class); - private static final String TICKER_CHANNEL_ID = "1002"; + private static final Logger LOG = + LoggerFactory.getLogger(PoloniexStreamingMarketDataService.class); + private static final String TICKER_CHANNEL_ID = "1002"; - private final PoloniexStreamingService service; - private final Supplier> streamingTickers; + private final PoloniexStreamingService service; + private final Supplier> streamingTickers; - public PoloniexStreamingMarketDataService(PoloniexStreamingService service, Map currencyIdMap) { - this.service = service; - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + public PoloniexStreamingMarketDataService( + PoloniexStreamingService service, Map currencyIdMap) { + this.service = service; + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - streamingTickers = Suppliers.memoize(() -> service.subscribeChannel(TICKER_CHANNEL_ID) - .map(s -> { - PoloniexWebSocketTickerTransaction ticker = mapper.readValue(s.toString(), PoloniexWebSocketTickerTransaction.class); - CurrencyPair currencyPair = currencyIdMap.get(ticker.getPairId()); - return adaptPoloniexTicker(ticker.toPoloniexTicker(currencyPair), currencyPair); - }).share()); - } + streamingTickers = + Suppliers.memoize( + () -> + service + .subscribeChannel(TICKER_CHANNEL_ID) + .map( + s -> { + PoloniexWebSocketTickerTransaction ticker = + mapper.readValue( + s.toString(), PoloniexWebSocketTickerTransaction.class); + CurrencyPair currencyPair = currencyIdMap.get(ticker.getPairId()); + return adaptPoloniexTicker( + ticker.toPoloniexTicker(currencyPair), currencyPair); + }) + .share()); + } - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - Observable subscribedOrderbook = service.subscribeCurrencyPairChannel(currencyPair) - .scan( - Optional.empty(), - (Optional orderbook, List poloniexWebSocketEvents) -> - poloniexWebSocketEvents.stream() - .filter(s -> - s instanceof PoloniexWebSocketOrderbookInsertEvent - || s instanceof PoloniexWebSocketOrderbookModifiedEvent - ) - .reduce( - orderbook, - (poloniexOrderbook, s) -> getPoloniexOrderbook(orderbook, s), - (o1, o2) -> { - throw new UnsupportedOperationException("No parallel execution"); - } - ) - ) - .filter(Optional::isPresent) - .map(Optional::get); + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + Observable subscribedOrderbook = + service + .subscribeCurrencyPairChannel(currencyPair) + .scan( + Optional.empty(), + (Optional orderbook, + List poloniexWebSocketEvents) -> + poloniexWebSocketEvents.stream() + .filter( + s -> + s instanceof PoloniexWebSocketOrderbookInsertEvent + || s instanceof PoloniexWebSocketOrderbookModifiedEvent) + .reduce( + orderbook, + (poloniexOrderbook, s) -> getPoloniexOrderbook(orderbook, s), + (o1, o2) -> { + throw new UnsupportedOperationException("No parallel execution"); + })) + .filter(Optional::isPresent) + .map(Optional::get); - return subscribedOrderbook.map(s -> adaptPoloniexDepth(s.toPoloniexDepth(), currencyPair)); - } + return subscribedOrderbook.map(s -> adaptPoloniexDepth(s.toPoloniexDepth(), currencyPair)); + } - @Override - public Observable getTicker(CurrencyPair currencyPair, Object... args) { - return streamingTickers.get().filter(ticker -> ticker.getCurrencyPair().equals(currencyPair)); - } + @Override + public Observable getTicker(CurrencyPair currencyPair, Object... args) { + return streamingTickers.get().filter(ticker -> ticker.getCurrencyPair().equals(currencyPair)); + } - @Override - public Observable getTrades(CurrencyPair currencyPair, Object... args) { - Observable subscribedTrades = service.subscribeCurrencyPairChannel(currencyPair) - .flatMapIterable(poloniexWebSocketEvents -> poloniexWebSocketEvents) - .filter(PoloniexWebSocketTradeEvent.class::isInstance) - .map(PoloniexWebSocketTradeEvent.class::cast) - .share(); + @Override + public Observable getTrades(CurrencyPair currencyPair, Object... args) { + Observable subscribedTrades = + service + .subscribeCurrencyPairChannel(currencyPair) + .flatMapIterable(poloniexWebSocketEvents -> poloniexWebSocketEvents) + .filter(PoloniexWebSocketTradeEvent.class::isInstance) + .map(PoloniexWebSocketTradeEvent.class::cast) + .share(); - return subscribedTrades - .map(s -> PoloniexWebSocketAdapter.convertPoloniexWebSocketTradeEventToTrade(s, currencyPair)); - } + return subscribedTrades.map( + s -> PoloniexWebSocketAdapter.convertPoloniexWebSocketTradeEventToTrade(s, currencyPair)); + } - private Optional getPoloniexOrderbook(final Optional orderbook, - final PoloniexWebSocketEvent s) { - if (s.getEventType().equals("i")) { - OrderbookInsertEvent insertEvent = ((PoloniexWebSocketOrderbookInsertEvent) s).getInsert(); - SortedMap asks = insertEvent.toDepthLevels(OrderbookInsertEvent.ASK_SIDE); - SortedMap bids = insertEvent.toDepthLevels(OrderbookInsertEvent.BID_SIDE); - return Optional.of(new PoloniexOrderbook(asks, bids)); - } else { - OrderbookModifiedEvent modifiedEvent = ((PoloniexWebSocketOrderbookModifiedEvent) s).getModifiedEvent(); - orderbook.orElseThrow(() -> new IllegalStateException("Orderbook update received before initial snapshot")) - .modify(modifiedEvent); - return orderbook; - } + private Optional getPoloniexOrderbook( + final Optional orderbook, final PoloniexWebSocketEvent s) { + if (s.getEventType().equals("i")) { + OrderbookInsertEvent insertEvent = ((PoloniexWebSocketOrderbookInsertEvent) s).getInsert(); + SortedMap asks = + insertEvent.toDepthLevels(OrderbookInsertEvent.ASK_SIDE); + SortedMap bids = + insertEvent.toDepthLevels(OrderbookInsertEvent.BID_SIDE); + return Optional.of(new PoloniexOrderbook(asks, bids)); + } else { + OrderbookModifiedEvent modifiedEvent = + ((PoloniexWebSocketOrderbookModifiedEvent) s).getModifiedEvent(); + orderbook + .orElseThrow( + () -> new IllegalStateException("Orderbook update received before initial snapshot")) + .modify(modifiedEvent); + return orderbook; } + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingService.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingService.java index edfdc5b25..86870ab90 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingService.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/PoloniexStreamingService.java @@ -8,127 +8,126 @@ import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; import io.reactivex.Completable; import io.reactivex.Observable; -import org.knowm.xchange.currency.CurrencyPair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.knowm.xchange.currency.CurrencyPair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 10.11.17. - */ +/** Created by Lukas Zaoralek on 10.11.17. */ public class PoloniexStreamingService extends JsonNettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(PoloniexStreamingService.class); - - private static final String HEARTBEAT = "1010"; - - private final Map subscribedChannels = new HashMap<>(); - private final Map> subscriptions = new HashMap<>(); - - - public PoloniexStreamingService(String apiUrl) { - super(apiUrl, Integer.MAX_VALUE, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION, 2); - } - - @Override - protected void handleMessage(JsonNode message) { - - if (message.isArray()) { - if (message.size() < 3) { - if (message.get(0).asText().equals(HEARTBEAT)) return; - else if (message.get(0).asText().equals("1002")) return; + private static final Logger LOG = LoggerFactory.getLogger(PoloniexStreamingService.class); + + private static final String HEARTBEAT = "1010"; + + private final Map subscribedChannels = new HashMap<>(); + private final Map> subscriptions = new HashMap<>(); + + public PoloniexStreamingService(String apiUrl) { + super(apiUrl, Integer.MAX_VALUE, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION, 2); + } + + @Override + protected void handleMessage(JsonNode message) { + + if (message.isArray()) { + if (message.size() < 3) { + if (message.get(0).asText().equals(HEARTBEAT)) return; + else if (message.get(0).asText().equals("1002")) return; + } + int channelId = Integer.parseInt(message.get(0).toString()); + if (channelId > 0 && channelId < 1000) { + JsonNode events = message.get(2); + if (events != null && events.isArray()) { + JsonNode event = events.get(0); + if (event.get(0).toString().equals("\"i\"")) { + if (event.get(1).has("orderBook")) { + String currencyPair = event.get(1).get("currencyPair").asText(); + LOG.info("Register {} as {}", channelId, currencyPair); + subscribedChannels.put(String.valueOf(channelId), currencyPair); } - int channelId = Integer.parseInt(message.get(0).toString()); - if (channelId > 0 && channelId < 1000) { - JsonNode events = message.get(2); - if (events != null && events.isArray()) { - JsonNode event = events.get(0); - if (event.get(0).toString().equals("\"i\"")) { - if (event.get(1).has("orderBook")) { - String currencyPair = event.get(1).get("currencyPair").asText(); - LOG.info("Register {} as {}", channelId, currencyPair); - subscribedChannels.put(String.valueOf(channelId), currencyPair); - } - } - } - } - } - if (message.has("error")) { - LOG.error("Error with message: " + message.get("error").asText()); - return; + } } - super.handleMessage(message); + } } - - @Override - public boolean processArrayMassageSeparately() { - return false; - } - - @Override - public Observable subscribeChannel(String channelName, Object... args) { - if (!channels.containsKey(channelName)) { - Observable subscription = super.subscribeChannel(channelName, args); - subscriptions.put(channelName, subscription); - } - - return subscriptions.get(channelName); - } - - public Observable> subscribeCurrencyPairChannel(CurrencyPair currencyPair) { - String channelName = currencyPair.counter.toString() + "_" + currencyPair.base.toString(); - return subscribeChannel(channelName) - .map(jsonNode -> objectMapper.treeToValue(jsonNode, PoloniexWebSocketEventsTransaction.class)) - .scan((poloniexWebSocketEventsTransactionOld, poloniexWebSocketEventsTransactionNew) -> { - final boolean initialSnapshot = poloniexWebSocketEventsTransactionNew.getEvents() - .stream().anyMatch(PoloniexWebSocketOrderbookModifiedEvent.class::isInstance); - final boolean sequenceContinuous = poloniexWebSocketEventsTransactionOld.getSeqId() + 1 - == poloniexWebSocketEventsTransactionNew.getSeqId(); - if (!initialSnapshot || sequenceContinuous) { - return poloniexWebSocketEventsTransactionNew; - } else { - throw new RuntimeException( - String.format( - "Invalid sequencing, old: %s new: %s", - objectMapper.writeValueAsString(poloniexWebSocketEventsTransactionOld), - objectMapper.writeValueAsString(poloniexWebSocketEventsTransactionNew) - ) - ); - } - }) - .map(PoloniexWebSocketEventsTransaction::getEvents) - .share(); + if (message.has("error")) { + LOG.error("Error with message: " + message.get("error").asText()); + return; } - - @Override - protected String getChannelNameFromMessage(JsonNode message) { - String strChannelId = message.get(0).asText(); - int channelId = Integer.parseInt(strChannelId); - if (channelId >= 1000) return strChannelId; - else return subscribedChannels.get(message.get(0).asText()); - } - - @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { - PoloniexWebSocketSubscriptionMessage subscribeMessage = new PoloniexWebSocketSubscriptionMessage("subscribe", - channelName); - return objectMapper.writeValueAsString(subscribeMessage); - } - - @Override - public String getUnsubscribeMessage(String channelName) throws IOException { - PoloniexWebSocketSubscriptionMessage subscribeMessage = new PoloniexWebSocketSubscriptionMessage("unsubscribe", - channelName); - return objectMapper.writeValueAsString(subscribeMessage); - } - - @Override - public Completable disconnect() { - - return super.disconnect(); + super.handleMessage(message); + } + + @Override + public boolean processArrayMassageSeparately() { + return false; + } + + @Override + public Observable subscribeChannel(String channelName, Object... args) { + if (!channels.containsKey(channelName)) { + Observable subscription = super.subscribeChannel(channelName, args); + subscriptions.put(channelName, subscription); } + return subscriptions.get(channelName); + } + + public Observable> subscribeCurrencyPairChannel( + CurrencyPair currencyPair) { + String channelName = currencyPair.counter.toString() + "_" + currencyPair.base.toString(); + return subscribeChannel(channelName) + .map( + jsonNode -> + objectMapper.treeToValue(jsonNode, PoloniexWebSocketEventsTransaction.class)) + .scan( + (poloniexWebSocketEventsTransactionOld, poloniexWebSocketEventsTransactionNew) -> { + final boolean initialSnapshot = + poloniexWebSocketEventsTransactionNew.getEvents().stream() + .anyMatch(PoloniexWebSocketOrderbookModifiedEvent.class::isInstance); + final boolean sequenceContinuous = + poloniexWebSocketEventsTransactionOld.getSeqId() + 1 + == poloniexWebSocketEventsTransactionNew.getSeqId(); + if (!initialSnapshot || sequenceContinuous) { + return poloniexWebSocketEventsTransactionNew; + } else { + throw new RuntimeException( + String.format( + "Invalid sequencing, old: %s new: %s", + objectMapper.writeValueAsString(poloniexWebSocketEventsTransactionOld), + objectMapper.writeValueAsString(poloniexWebSocketEventsTransactionNew))); + } + }) + .map(PoloniexWebSocketEventsTransaction::getEvents) + .share(); + } + + @Override + protected String getChannelNameFromMessage(JsonNode message) { + String strChannelId = message.get(0).asText(); + int channelId = Integer.parseInt(strChannelId); + if (channelId >= 1000) return strChannelId; + else return subscribedChannels.get(message.get(0).asText()); + } + + @Override + public String getSubscribeMessage(String channelName, Object... args) throws IOException { + PoloniexWebSocketSubscriptionMessage subscribeMessage = + new PoloniexWebSocketSubscriptionMessage("subscribe", channelName); + return objectMapper.writeValueAsString(subscribeMessage); + } + + @Override + public String getUnsubscribeMessage(String channelName) throws IOException { + PoloniexWebSocketSubscriptionMessage subscribeMessage = + new PoloniexWebSocketSubscriptionMessage("unsubscribe", channelName); + return objectMapper.writeValueAsString(subscribeMessage); + } + + @Override + public Completable disconnect() { + + return super.disconnect(); + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookInsertEvent.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookInsertEvent.java index 9d244c30b..f7219ffcc 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookInsertEvent.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookInsertEvent.java @@ -2,52 +2,50 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; - import java.math.BigDecimal; import java.util.Iterator; import java.util.SortedMap; import java.util.TreeMap; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public class OrderbookInsertEvent { - public static final int ASK_SIDE = 0; - public static final int BID_SIDE = 1; - - private final String currencyPair; - private final JsonNode[] orderbookSides; - - - public OrderbookInsertEvent(@JsonProperty("currencyPair") String currencyPair, - @JsonProperty("orderBook") JsonNode[] orderbookSides) { - this.currencyPair = currencyPair; - this.orderbookSides = orderbookSides; - } - - public String getCurrencyPair() { - return currencyPair; + public static final int ASK_SIDE = 0; + public static final int BID_SIDE = 1; + + private final String currencyPair; + private final JsonNode[] orderbookSides; + + public OrderbookInsertEvent( + @JsonProperty("currencyPair") String currencyPair, + @JsonProperty("orderBook") JsonNode[] orderbookSides) { + this.currencyPair = currencyPair; + this.orderbookSides = orderbookSides; + } + + public String getCurrencyPair() { + return currencyPair; + } + + public JsonNode[] getOrderbookSides() { + return orderbookSides; + } + + public SortedMap toDepthLevels(int side) { + if (side == ASK_SIDE) return toDepthLevels(orderbookSides[ASK_SIDE], false); + else return toDepthLevels(orderbookSides[BID_SIDE], true); + } + + private SortedMap toDepthLevels(JsonNode side, boolean reverse) { + SortedMap levels = + new TreeMap<>(reverse ? java.util.Collections.reverseOrder() : null); + Iterator prices = side.fieldNames(); + while (prices.hasNext()) { + String strPrice = prices.next(); + BigDecimal price = new BigDecimal(strPrice); + BigDecimal volume = new BigDecimal(side.get(strPrice).asText()); + levels.put(price, volume); } - public JsonNode[] getOrderbookSides() { - return orderbookSides; - } - - public SortedMap toDepthLevels(int side) { - if (side == ASK_SIDE) return toDepthLevels(orderbookSides[ASK_SIDE], false); - else return toDepthLevels(orderbookSides[BID_SIDE], true); - } - - private SortedMap toDepthLevels(JsonNode side, boolean reverse) { - SortedMap levels = new TreeMap<>(reverse ? java.util.Collections.reverseOrder() : null); - Iterator prices = side.fieldNames(); - while (prices.hasNext()) { - String strPrice = prices.next(); - BigDecimal price = new BigDecimal(strPrice); - BigDecimal volume = new BigDecimal(side.get(strPrice).asText()); - levels.put(price, volume); - } - - return levels; - } + return levels; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookModifiedEvent.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookModifiedEvent.java index ec675f604..f30a43c31 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookModifiedEvent.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/OrderbookModifiedEvent.java @@ -2,29 +2,27 @@ import java.math.BigDecimal; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public class OrderbookModifiedEvent { - private String type; - private BigDecimal price; - private BigDecimal volume; + private String type; + private BigDecimal price; + private BigDecimal volume; - public OrderbookModifiedEvent(String type, BigDecimal price, BigDecimal volume) { - this.type = type; - this.price = price; - this.volume = volume; - } + public OrderbookModifiedEvent(String type, BigDecimal price, BigDecimal volume) { + this.type = type; + this.price = price; + this.volume = volume; + } - public String getType() { - return type; - } + public String getType() { + return type; + } - public BigDecimal getPrice() { - return price; - } + public BigDecimal getPrice() { + return price; + } - public BigDecimal getVolume() { - return volume; - } + public BigDecimal getVolume() { + return volume; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexOrderbook.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexOrderbook.java index 16c7af884..65a301554 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexOrderbook.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexOrderbook.java @@ -1,58 +1,56 @@ package info.bitrich.xchangestream.poloniex2.dto; -import org.knowm.xchange.poloniex.dto.marketdata.PoloniexDepth; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.SortedMap; +import org.knowm.xchange.poloniex.dto.marketdata.PoloniexDepth; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public class PoloniexOrderbook { - public static BigDecimal zero = new BigDecimal(0); + public static BigDecimal zero = new BigDecimal(0); - private SortedMap asks; - private SortedMap bids; + private SortedMap asks; + private SortedMap bids; - public PoloniexOrderbook(SortedMap asks, SortedMap bids) { - this.asks = asks; - this.bids = bids; - } + public PoloniexOrderbook( + SortedMap asks, SortedMap bids) { + this.asks = asks; + this.bids = bids; + } - public void modify(OrderbookModifiedEvent modifiedEvent) { - SortedMap side = modifiedEvent.getType().equals("0") ? asks : bids; - BigDecimal price = modifiedEvent.getPrice(); - BigDecimal volume = modifiedEvent.getVolume(); + public void modify(OrderbookModifiedEvent modifiedEvent) { + SortedMap side = modifiedEvent.getType().equals("0") ? asks : bids; + BigDecimal price = modifiedEvent.getPrice(); + BigDecimal volume = modifiedEvent.getVolume(); - side.remove(price); - if (volume.compareTo(zero) != 0) { - side.put(price, volume); - } + side.remove(price); + if (volume.compareTo(zero) != 0) { + side.put(price, volume); } - - private List> toPoloniexDepthLevels(SortedMap side) { - List> poloniexDepthSide = new ArrayList<>(side.size()); - for (Map.Entry level : side.entrySet()) { - List poloniexLevel = new ArrayList<>(2); - poloniexLevel.add(level.getKey()); - poloniexLevel.add(level.getValue()); - poloniexDepthSide.add(poloniexLevel); - } - - return poloniexDepthSide; + } + + private List> toPoloniexDepthLevels(SortedMap side) { + List> poloniexDepthSide = new ArrayList<>(side.size()); + for (Map.Entry level : side.entrySet()) { + List poloniexLevel = new ArrayList<>(2); + poloniexLevel.add(level.getKey()); + poloniexLevel.add(level.getValue()); + poloniexDepthSide.add(poloniexLevel); } - public PoloniexDepth toPoloniexDepth() { - PoloniexDepth orderbook = new PoloniexDepth(); + return poloniexDepthSide; + } - List> poloniexDepthAsk = toPoloniexDepthLevels(asks); - List> poloniexDepthBid = toPoloniexDepthLevels(bids); + public PoloniexDepth toPoloniexDepth() { + PoloniexDepth orderbook = new PoloniexDepth(); - orderbook.setAsks(poloniexDepthAsk); - orderbook.setBids(poloniexDepthBid); - return orderbook; - } + List> poloniexDepthAsk = toPoloniexDepthLevels(asks); + List> poloniexDepthBid = toPoloniexDepthLevels(bids); + + orderbook.setAsks(poloniexDepthAsk); + orderbook.setBids(poloniexDepthBid); + return orderbook; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketAdapter.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketAdapter.java index d88572927..83f52fa3e 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketAdapter.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketAdapter.java @@ -1,30 +1,26 @@ package info.bitrich.xchangestream.poloniex2.dto; +import java.util.Date; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Trade; -import java.util.Date; - -/** - * @author Nikita Belenkiy on 04/11/2019. - */ +/** @author Nikita Belenkiy on 04/11/2019. */ public class PoloniexWebSocketAdapter { - private PoloniexWebSocketAdapter() { - } + private PoloniexWebSocketAdapter() {} - public static Trade convertPoloniexWebSocketTradeEventToTrade( - PoloniexWebSocketTradeEvent poloniexTradeEvent, CurrencyPair currencyPair) { - TradeEvent tradeEvent = poloniexTradeEvent.getTradeEvent(); - Date timestamp = new Date(tradeEvent.getTimestampSeconds() * 1000L); - Trade trade = - new Trade.Builder() - .type(tradeEvent.getType()) - .price(tradeEvent.getPrice()) - .originalAmount(tradeEvent.getSize()) - .currencyPair(currencyPair) - .id(tradeEvent.getTradeId()) - .timestamp(timestamp) - .build(); - return trade; - } + public static Trade convertPoloniexWebSocketTradeEventToTrade( + PoloniexWebSocketTradeEvent poloniexTradeEvent, CurrencyPair currencyPair) { + TradeEvent tradeEvent = poloniexTradeEvent.getTradeEvent(); + Date timestamp = new Date(tradeEvent.getTimestampSeconds() * 1000L); + Trade trade = + new Trade.Builder() + .type(tradeEvent.getType()) + .price(tradeEvent.getPrice()) + .originalAmount(tradeEvent.getSize()) + .currencyPair(currencyPair) + .id(tradeEvent.getTradeId()) + .timestamp(timestamp) + .build(); + return trade; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEvent.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEvent.java index 924e76fc0..78694218a 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEvent.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEvent.java @@ -1,16 +1,14 @@ package info.bitrich.xchangestream.poloniex2.dto; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public abstract class PoloniexWebSocketEvent { - private String eventType; + private String eventType; - public PoloniexWebSocketEvent(String eventType) { - this.eventType = eventType; - } + public PoloniexWebSocketEvent(String eventType) { + this.eventType = eventType; + } - public String getEventType() { - return eventType; - } + public String getEventType() { + return eventType; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEventsTransaction.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEventsTransaction.java index 22c668bbb..4ba6ae500 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEventsTransaction.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketEventsTransaction.java @@ -6,95 +6,96 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; -import org.knowm.xchange.dto.Order; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import org.knowm.xchange.dto.Order; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class PoloniexWebSocketEventsTransaction { - private Long channelId; - private Long seqId; - private List events; + private Long channelId; + private Long seqId; + private List events; - @JsonCreator - public PoloniexWebSocketEventsTransaction( - @JsonProperty("channelId") final Long channelId, - @JsonProperty("seqId") final Long seqId, - @JsonProperty("jsonEvents") final List jsonEvents - ) { - this.channelId = channelId; - this.seqId = seqId; - this.events = createEvents(jsonEvents); - } + @JsonCreator + public PoloniexWebSocketEventsTransaction( + @JsonProperty("channelId") final Long channelId, + @JsonProperty("seqId") final Long seqId, + @JsonProperty("jsonEvents") final List jsonEvents) { + this.channelId = channelId; + this.seqId = seqId; + this.events = createEvents(jsonEvents); + } - public Long getChannelId() { - return channelId; - } + public Long getChannelId() { + return channelId; + } - public Long getSeqId() { - return seqId; - } - - public List getEvents() { - return events; - } + public Long getSeqId() { + return seqId; + } - private List createEvents(final List jsonEvents) { - final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + public List getEvents() { + return events; + } - List events = new ArrayList<>(jsonEvents.size()); - for (JsonNode jsonNode : jsonEvents) { - String eventType = jsonNode.get(0).asText(); - switch (eventType) { - case "i": { - OrderbookInsertEvent orderbookInsertEvent = mapper.convertValue(jsonNode.get(1), OrderbookInsertEvent.class); - PoloniexWebSocketOrderbookInsertEvent insertEvent = new PoloniexWebSocketOrderbookInsertEvent(orderbookInsertEvent); - events.add(insertEvent); - break; - } - case "o": { - OrderbookModifiedEvent orderbookModifiedEvent = new OrderbookModifiedEvent(jsonNode.get(1).asText(), - new BigDecimal(jsonNode.get(2).asText()), new BigDecimal(jsonNode.get(3).asText())); - PoloniexWebSocketOrderbookModifiedEvent insertEvent = new PoloniexWebSocketOrderbookModifiedEvent(orderbookModifiedEvent); - events.add(insertEvent); - break; - } - case "t": { - /* - * ["t", "", <1 for buy 0 for sell>, "", "", ] - */ - String tradeId = jsonNode.get(1).asText(); - Order.OrderType side = sideFromString(jsonNode.get(2).asText()); - BigDecimal price = new BigDecimal(jsonNode.get(3).asText()); - BigDecimal size = new BigDecimal(jsonNode.get(4).asText()); - int timestampSeconds = jsonNode.get(5).asInt(); - TradeEvent tradeEvent = new TradeEvent(tradeId, - side, - price, - size, - timestampSeconds); - PoloniexWebSocketTradeEvent insertEvent = new PoloniexWebSocketTradeEvent(tradeEvent); - events.add(insertEvent); - break; - } - default: //Ignore - } - } + private List createEvents(final List jsonEvents) { + final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); - return events; + List events = new ArrayList<>(jsonEvents.size()); + for (JsonNode jsonNode : jsonEvents) { + String eventType = jsonNode.get(0).asText(); + switch (eventType) { + case "i": + { + OrderbookInsertEvent orderbookInsertEvent = + mapper.convertValue(jsonNode.get(1), OrderbookInsertEvent.class); + PoloniexWebSocketOrderbookInsertEvent insertEvent = + new PoloniexWebSocketOrderbookInsertEvent(orderbookInsertEvent); + events.add(insertEvent); + break; + } + case "o": + { + OrderbookModifiedEvent orderbookModifiedEvent = + new OrderbookModifiedEvent( + jsonNode.get(1).asText(), + new BigDecimal(jsonNode.get(2).asText()), + new BigDecimal(jsonNode.get(3).asText())); + PoloniexWebSocketOrderbookModifiedEvent insertEvent = + new PoloniexWebSocketOrderbookModifiedEvent(orderbookModifiedEvent); + events.add(insertEvent); + break; + } + case "t": + { + /* + * ["t", "", <1 for buy 0 for sell>, "", "", ] + */ + String tradeId = jsonNode.get(1).asText(); + Order.OrderType side = sideFromString(jsonNode.get(2).asText()); + BigDecimal price = new BigDecimal(jsonNode.get(3).asText()); + BigDecimal size = new BigDecimal(jsonNode.get(4).asText()); + int timestampSeconds = jsonNode.get(5).asInt(); + TradeEvent tradeEvent = new TradeEvent(tradeId, side, price, size, timestampSeconds); + PoloniexWebSocketTradeEvent insertEvent = new PoloniexWebSocketTradeEvent(tradeEvent); + events.add(insertEvent); + break; + } + default: // Ignore + } } - private static Order.OrderType sideFromString(String side) { - if (side.equals("1")) { - return Order.OrderType.BID; - } else if (side.equals("0")) { - return Order.OrderType.ASK; - } - throw new IllegalArgumentException("Unknown side: " + side); + return events; + } + + private static Order.OrderType sideFromString(String side) { + if (side.equals("1")) { + return Order.OrderType.BID; + } else if (side.equals("0")) { + return Order.OrderType.ASK; } + throw new IllegalArgumentException("Unknown side: " + side); + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookInsertEvent.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookInsertEvent.java index eac42ea69..eb1d19125 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookInsertEvent.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookInsertEvent.java @@ -1,17 +1,15 @@ package info.bitrich.xchangestream.poloniex2.dto; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public class PoloniexWebSocketOrderbookInsertEvent extends PoloniexWebSocketEvent { - private final OrderbookInsertEvent insert; + private final OrderbookInsertEvent insert; - public PoloniexWebSocketOrderbookInsertEvent(OrderbookInsertEvent insert) { - super("i"); - this.insert = insert; - } + public PoloniexWebSocketOrderbookInsertEvent(OrderbookInsertEvent insert) { + super("i"); + this.insert = insert; + } - public OrderbookInsertEvent getInsert() { - return insert; - } + public OrderbookInsertEvent getInsert() { + return insert; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookModifiedEvent.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookModifiedEvent.java index 520be3c28..608339ab1 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookModifiedEvent.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketOrderbookModifiedEvent.java @@ -1,17 +1,15 @@ package info.bitrich.xchangestream.poloniex2.dto; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public class PoloniexWebSocketOrderbookModifiedEvent extends PoloniexWebSocketEvent { - private final OrderbookModifiedEvent modifiedEvent; + private final OrderbookModifiedEvent modifiedEvent; - public PoloniexWebSocketOrderbookModifiedEvent(OrderbookModifiedEvent modifiedEvent) { - super("o"); - this.modifiedEvent = modifiedEvent; - } + public PoloniexWebSocketOrderbookModifiedEvent(OrderbookModifiedEvent modifiedEvent) { + super("o"); + this.modifiedEvent = modifiedEvent; + } - public OrderbookModifiedEvent getModifiedEvent() { - return modifiedEvent; - } + public OrderbookModifiedEvent getModifiedEvent() { + return modifiedEvent; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketSubscriptionMessage.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketSubscriptionMessage.java index f03172cae..fd74302ac 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketSubscriptionMessage.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketSubscriptionMessage.java @@ -2,22 +2,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Created by Lukas Zaoralek on 10.11.17. - */ +/** Created by Lukas Zaoralek on 10.11.17. */ public class PoloniexWebSocketSubscriptionMessage { - private static final String COMMAND = "command"; - private static final String CHANNEL = "channel"; + private static final String COMMAND = "command"; + private static final String CHANNEL = "channel"; - @JsonProperty(COMMAND) - private String command; + @JsonProperty(COMMAND) + private String command; - @JsonProperty(CHANNEL) - private String channel; + @JsonProperty(CHANNEL) + private String channel; - public PoloniexWebSocketSubscriptionMessage(String command, String channel) { - this.command = command; - this.channel = channel; - } + public PoloniexWebSocketSubscriptionMessage(String command, String channel) { + this.command = command; + this.channel = channel; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTickerTransaction.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTickerTransaction.java index 8adc2a0e8..1b03c3439 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTickerTransaction.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTickerTransaction.java @@ -1,44 +1,41 @@ package info.bitrich.xchangestream.poloniex2.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import java.math.BigDecimal; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.poloniex.dto.marketdata.PoloniexMarketData; import org.knowm.xchange.poloniex.dto.marketdata.PoloniexTicker; -import java.math.BigDecimal; - -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ @JsonFormat(shape = JsonFormat.Shape.ARRAY) public class PoloniexWebSocketTickerTransaction { - public String channelId; - public String timestamp; - public String[] ticker; + public String channelId; + public String timestamp; + public String[] ticker; - public PoloniexTicker toPoloniexTicker(CurrencyPair currencyPair) { - PoloniexMarketData poloniexMarketData = new PoloniexMarketData(); - BigDecimal last = new BigDecimal(ticker[1]); - BigDecimal lowestAsk = new BigDecimal(ticker[2]); - BigDecimal highestBid = new BigDecimal(ticker[3]); - BigDecimal percentChange = new BigDecimal(ticker[4]); - BigDecimal baseVolume = new BigDecimal(ticker[5]); - BigDecimal quoteVolume = new BigDecimal(ticker[6]); - BigDecimal isFrozen = new BigDecimal(ticker[7]); - BigDecimal high24hr = new BigDecimal(ticker[8]); - BigDecimal low24hr = new BigDecimal(ticker[9]); - poloniexMarketData.setLast(last); - poloniexMarketData.setLowestAsk(lowestAsk); - poloniexMarketData.setHighestBid(highestBid); - poloniexMarketData.setPercentChange(percentChange); - poloniexMarketData.setBaseVolume(baseVolume); - poloniexMarketData.setQuoteVolume(quoteVolume); - poloniexMarketData.setHigh24hr(high24hr); - poloniexMarketData.setLow24hr(low24hr); - return new PoloniexTicker(poloniexMarketData, currencyPair); - } + public PoloniexTicker toPoloniexTicker(CurrencyPair currencyPair) { + PoloniexMarketData poloniexMarketData = new PoloniexMarketData(); + BigDecimal last = new BigDecimal(ticker[1]); + BigDecimal lowestAsk = new BigDecimal(ticker[2]); + BigDecimal highestBid = new BigDecimal(ticker[3]); + BigDecimal percentChange = new BigDecimal(ticker[4]); + BigDecimal baseVolume = new BigDecimal(ticker[5]); + BigDecimal quoteVolume = new BigDecimal(ticker[6]); + BigDecimal isFrozen = new BigDecimal(ticker[7]); + BigDecimal high24hr = new BigDecimal(ticker[8]); + BigDecimal low24hr = new BigDecimal(ticker[9]); + poloniexMarketData.setLast(last); + poloniexMarketData.setLowestAsk(lowestAsk); + poloniexMarketData.setHighestBid(highestBid); + poloniexMarketData.setPercentChange(percentChange); + poloniexMarketData.setBaseVolume(baseVolume); + poloniexMarketData.setQuoteVolume(quoteVolume); + poloniexMarketData.setHigh24hr(high24hr); + poloniexMarketData.setLow24hr(low24hr); + return new PoloniexTicker(poloniexMarketData, currencyPair); + } - public int getPairId() { - return Integer.parseInt(ticker[0]); - } + public int getPairId() { + return Integer.parseInt(ticker[0]); + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTradeEvent.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTradeEvent.java index 2e7f6377c..52d27a7a9 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTradeEvent.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/PoloniexWebSocketTradeEvent.java @@ -1,18 +1,15 @@ package info.bitrich.xchangestream.poloniex2.dto; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public class PoloniexWebSocketTradeEvent extends PoloniexWebSocketEvent { - private final TradeEvent tradeEvent; + private final TradeEvent tradeEvent; - public PoloniexWebSocketTradeEvent(TradeEvent tradeEvent) { - super("t"); - this.tradeEvent = tradeEvent; - } - - public TradeEvent getTradeEvent() { - return tradeEvent; - } + public PoloniexWebSocketTradeEvent(TradeEvent tradeEvent) { + super("t"); + this.tradeEvent = tradeEvent; + } + public TradeEvent getTradeEvent() { + return tradeEvent; + } } diff --git a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/TradeEvent.java b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/TradeEvent.java index 067b07b7b..0588bf78e 100644 --- a/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/TradeEvent.java +++ b/xchange-stream-poloniex2/src/main/java/info/bitrich/xchangestream/poloniex2/dto/TradeEvent.java @@ -1,44 +1,46 @@ package info.bitrich.xchangestream.poloniex2.dto; -import org.knowm.xchange.dto.Order; - import java.math.BigDecimal; +import org.knowm.xchange.dto.Order; -/** - * Created by Lukas Zaoralek on 11.11.17. - */ +/** Created by Lukas Zaoralek on 11.11.17. */ public class TradeEvent { - private String tradeId; - private Order.OrderType type; - private BigDecimal price; - private BigDecimal size; - private int timestampSeconds; - - public TradeEvent(String tradeId, Order.OrderType type, BigDecimal price, BigDecimal size, int timestampSeconds) { - this.tradeId = tradeId; - this.type = type; - this.price = price; - this.size = size; - this.timestampSeconds = timestampSeconds; - } - - public String getTradeId() { - return tradeId; - } - - public Order.OrderType getType() { - return type; - } - - public BigDecimal getPrice() { - return price; - } - - public BigDecimal getSize() { - return size; - } - - public int getTimestampSeconds() { - return timestampSeconds; - } + private String tradeId; + private Order.OrderType type; + private BigDecimal price; + private BigDecimal size; + private int timestampSeconds; + + public TradeEvent( + String tradeId, + Order.OrderType type, + BigDecimal price, + BigDecimal size, + int timestampSeconds) { + this.tradeId = tradeId; + this.type = type; + this.price = price; + this.size = size; + this.timestampSeconds = timestampSeconds; + } + + public String getTradeId() { + return tradeId; + } + + public Order.OrderType getType() { + return type; + } + + public BigDecimal getPrice() { + return price; + } + + public BigDecimal getSize() { + return size; + } + + public int getTimestampSeconds() { + return timestampSeconds; + } } diff --git a/xchange-stream-poloniex2/src/test/java/info/bitrich/xchangestream/poloniex2/PoloniexManualExample.java b/xchange-stream-poloniex2/src/test/java/info/bitrich/xchangestream/poloniex2/PoloniexManualExample.java index 1eae518fe..cf366e613 100644 --- a/xchange-stream-poloniex2/src/test/java/info/bitrich/xchangestream/poloniex2/PoloniexManualExample.java +++ b/xchange-stream-poloniex2/src/test/java/info/bitrich/xchangestream/poloniex2/PoloniexManualExample.java @@ -9,55 +9,77 @@ import org.slf4j.LoggerFactory; public class PoloniexManualExample { - private static final Logger LOG = LoggerFactory.getLogger(PoloniexManualExample.class); - - public static void main(String[] args) throws Exception { - CurrencyPair usdtBtc = new CurrencyPair(new Currency("BTC"), new Currency("USDT")); - CurrencyPair ltcBtc = new CurrencyPair(new Currency("LTC"), new Currency("BTC")); -// - // CertHelper.trustAllCerts(); - StreamingExchange exchange = StreamingExchangeFactory.INSTANCE.createExchange(PoloniexStreamingExchange.class.getName()); - ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); -// defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); - - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.USE_SANDBOX, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ACCEPT_ALL_CERITICATES, true); - defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.ENABLE_LOGGING_HANDLER, true); - -// defaultExchangeSpecification.setApiKey("API-KEY"); -// defaultExchangeSpecification.setSecretKey("SECRET-KEY"); - - - exchange.applySpecification(defaultExchangeSpecification); - exchange.connect().blockingAwait(); - - - exchange.getStreamingMarketDataService().getOrderBook(usdtBtc).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); - }, throwable -> LOG.error("ERROR in getting order book: ", throwable)); - - exchange.getStreamingMarketDataService().getTicker(usdtBtc).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - exchange.getStreamingMarketDataService().getTicker(ltcBtc).subscribe(ticker -> { - LOG.info("TICKER: {}", ticker); - }, throwable -> LOG.error("ERROR in getting ticker: ", throwable)); - - - exchange.getStreamingMarketDataService().getTrades(usdtBtc).subscribe(trade -> { - LOG.info("TRADE: {}", trade); - }, throwable -> LOG.error("ERROR in getting trades: ", throwable)); - - - try { - Thread.sleep(1000000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - exchange.disconnect().blockingAwait(); + private static final Logger LOG = LoggerFactory.getLogger(PoloniexManualExample.class); + + public static void main(String[] args) throws Exception { + CurrencyPair usdtBtc = new CurrencyPair(new Currency("BTC"), new Currency("USDT")); + CurrencyPair ltcBtc = new CurrencyPair(new Currency("LTC"), new Currency("BTC")); + // + // CertHelper.trustAllCerts(); + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(PoloniexStreamingExchange.class.getName()); + ExchangeSpecification defaultExchangeSpecification = exchange.getDefaultExchangeSpecification(); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_HOST, "localhost"); + // + // defaultExchangeSpecification.setExchangeSpecificParametersItem(StreamingExchange.SOCKS_PROXY_PORT, 8889); + + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.USE_SANDBOX, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ACCEPT_ALL_CERITICATES, true); + defaultExchangeSpecification.setExchangeSpecificParametersItem( + StreamingExchange.ENABLE_LOGGING_HANDLER, true); + + // defaultExchangeSpecification.setApiKey("API-KEY"); + // defaultExchangeSpecification.setSecretKey("SECRET-KEY"); + + exchange.applySpecification(defaultExchangeSpecification); + exchange.connect().blockingAwait(); + + exchange + .getStreamingMarketDataService() + .getOrderBook(usdtBtc) + .subscribe( + orderBook -> { + LOG.info("First ask: {}", orderBook.getAsks().get(0)); + LOG.info("First bid: {}", orderBook.getBids().get(0)); + }, + throwable -> LOG.error("ERROR in getting order book: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTicker(usdtBtc) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTicker(ltcBtc) + .subscribe( + ticker -> { + LOG.info("TICKER: {}", ticker); + }, + throwable -> LOG.error("ERROR in getting ticker: ", throwable)); + + exchange + .getStreamingMarketDataService() + .getTrades(usdtBtc) + .subscribe( + trade -> { + LOG.info("TRADE: {}", trade); + }, + throwable -> LOG.error("ERROR in getting trades: ", throwable)); + + try { + Thread.sleep(1000000); + } catch (InterruptedException e) { + e.printStackTrace(); } + + exchange.disconnect().blockingAwait(); + } } diff --git a/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/ConnectableService.java b/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/ConnectableService.java index a8e6188cd..ff2aa44b3 100644 --- a/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/ConnectableService.java +++ b/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/ConnectableService.java @@ -2,44 +2,37 @@ import io.reactivex.Completable; -/** - * Base class of streaming services, declares connect() method including before connection logic - */ +/** Base class of streaming services, declares connect() method including before connection logic */ public abstract class ConnectableService { - /** - * Exchange specific parameter is used for providing {@link Runnable} action which is caused before setup new connection. - * For example adding throttle control for limiting too often opening connections: - *
-     * {@code
-     * static final TimedSemaphore limiter = new TimedSemaphore(1, MINUTES, 15);
-     * ExchangeSpecification spec = exchange.getDefaultExchangeSpecification();
-     * spec.setExchangeSpecificParameters(ImmutableMap.of(
-     *   {@link ConnectableService#BEFORE_CONNECTION_HANDLER}, () -> limiter.acquire()
-     * ));
-     * }
-     * 
- */ - public static final String BEFORE_CONNECTION_HANDLER = "Before_Connection_Event_Handler"; - - /** - * {@link Runnable} handler is called before opening new socket connection. - */ - private Runnable beforeConnectionHandler = () -> { - }; - - public void setBeforeConnectionHandler(Runnable beforeConnectionHandler) { - if (beforeConnectionHandler != null) { - this.beforeConnectionHandler = beforeConnectionHandler; - } - } - - protected abstract Completable openConnection(); - - public Completable connect() { - beforeConnectionHandler.run(); - return openConnection(); + /** + * Exchange specific parameter is used for providing {@link Runnable} action which is caused + * before setup new connection. For example adding throttle control for limiting too often opening + * connections: + * + *
{@code
+   * static final TimedSemaphore limiter = new TimedSemaphore(1, MINUTES, 15);
+   * ExchangeSpecification spec = exchange.getDefaultExchangeSpecification();
+   * spec.setExchangeSpecificParameters(ImmutableMap.of(
+   *   {@link ConnectableService#BEFORE_CONNECTION_HANDLER}, () -> limiter.acquire()
+   * ));
+   * }
+ */ + public static final String BEFORE_CONNECTION_HANDLER = "Before_Connection_Event_Handler"; + + /** {@link Runnable} handler is called before opening new socket connection. */ + private Runnable beforeConnectionHandler = () -> {}; + + public void setBeforeConnectionHandler(Runnable beforeConnectionHandler) { + if (beforeConnectionHandler != null) { + this.beforeConnectionHandler = beforeConnectionHandler; } + } + protected abstract Completable openConnection(); + public Completable connect() { + beforeConnectionHandler.run(); + return openConnection(); + } } diff --git a/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/exception/NotConnectedException.java b/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/exception/NotConnectedException.java index c35393a70..6dde3dcee 100644 --- a/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/exception/NotConnectedException.java +++ b/xchange-stream-service-core/src/main/java/info/bitrich/xchangestream/service/exception/NotConnectedException.java @@ -1,10 +1,10 @@ package info.bitrich.xchangestream.service.exception; /** - * Exception indicating that call cannot be completed because not connected to the exchange's streaming API. + * Exception indicating that call cannot be completed because not connected to the exchange's + * streaming API. */ public class NotConnectedException extends RuntimeException { private static final long serialVersionUID = 486649649653058827L; - } diff --git a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/JsonNettyStreamingService.java b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/JsonNettyStreamingService.java index 95ecbc813..a6b4ef4e4 100644 --- a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/JsonNettyStreamingService.java +++ b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/JsonNettyStreamingService.java @@ -3,61 +3,64 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class JsonNettyStreamingService extends NettyStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(JsonNettyStreamingService.class); - protected final ObjectMapper objectMapper = StreamingObjectMapperHelper.getObjectMapper(); + private static final Logger LOG = LoggerFactory.getLogger(JsonNettyStreamingService.class); + protected final ObjectMapper objectMapper = StreamingObjectMapperHelper.getObjectMapper(); - public JsonNettyStreamingService(String apiUrl) { - super(apiUrl); - } + public JsonNettyStreamingService(String apiUrl) { + super(apiUrl); + } - public JsonNettyStreamingService(String apiUrl, int maxFramePayloadLength) { - super(apiUrl, maxFramePayloadLength); - } + public JsonNettyStreamingService(String apiUrl, int maxFramePayloadLength) { + super(apiUrl, maxFramePayloadLength); + } - public JsonNettyStreamingService(String apiUrl, int maxFramePayloadLength, Duration connectionTimeout, Duration retryDuration, int idleTimeoutSeconds) { - super(apiUrl, maxFramePayloadLength, connectionTimeout, retryDuration, idleTimeoutSeconds); - } + public JsonNettyStreamingService( + String apiUrl, + int maxFramePayloadLength, + Duration connectionTimeout, + Duration retryDuration, + int idleTimeoutSeconds) { + super(apiUrl, maxFramePayloadLength, connectionTimeout, retryDuration, idleTimeoutSeconds); + } + + public boolean processArrayMassageSeparately() { + return true; + } + + @Override + public void messageHandler(String message) { + LOG.debug("Received message: {}", message); + JsonNode jsonNode; - public boolean processArrayMassageSeparately() { - return true; + // Parse incoming message to JSON + try { + jsonNode = objectMapper.readTree(message); + } catch (IOException e) { + LOG.error("Error parsing incoming message to JSON: {}", message); + return; } - @Override - public void messageHandler(String message) { - LOG.debug("Received message: {}", message); - JsonNode jsonNode; - - // Parse incoming message to JSON - try { - jsonNode = objectMapper.readTree(message); - } catch (IOException e) { - LOG.error("Error parsing incoming message to JSON: {}", message); - return; - } - - if (processArrayMassageSeparately() && jsonNode.isArray()) { - // In case of array - handle every message separately. - for (JsonNode node : jsonNode) { - handleMessage(node); - } - } else { - handleMessage(jsonNode); - } + if (processArrayMassageSeparately() && jsonNode.isArray()) { + // In case of array - handle every message separately. + for (JsonNode node : jsonNode) { + handleMessage(node); + } + } else { + handleMessage(jsonNode); } + } - protected void sendObjectMessage(Object message) { - try { - sendMessage(objectMapper.writeValueAsString(message)); - } catch (JsonProcessingException e) { - LOG.error("Error creating json message: {}", e.getMessage()); - } + protected void sendObjectMessage(Object message) { + try { + sendMessage(objectMapper.writeValueAsString(message)); + } catch (JsonProcessingException e) { + LOG.error("Error creating json message: {}", e.getMessage()); } + } } diff --git a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/NettyStreamingService.java b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/NettyStreamingService.java index a401e3860..e6d80fd59 100644 --- a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/NettyStreamingService.java +++ b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/NettyStreamingService.java @@ -38,9 +38,6 @@ import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.subjects.PublishSubject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -52,426 +49,484 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class NettyStreamingService extends ConnectableService { - private final Logger LOG = LoggerFactory.getLogger(this.getClass()); - - protected static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(10); - protected static final Duration DEFAULT_RETRY_DURATION = Duration.ofSeconds(15); - protected static final int DEFAULT_IDLE_TIMEOUT = 0; - - private class Subscription { - final ObservableEmitter emitter; - final String channelName; - final Object[] args; - - public Subscription(ObservableEmitter emitter, String channelName, Object[] args) { - this.emitter = emitter; - this.channelName = channelName; - this.args = args; - } - } + private final Logger LOG = LoggerFactory.getLogger(this.getClass()); - private final int maxFramePayloadLength; - private final URI uri; - private final AtomicBoolean isManualDisconnect = new AtomicBoolean(); - private Channel webSocketChannel; - private final Duration retryDuration; - private final Duration connectionTimeout; - private final int idleTimeoutSeconds; - private volatile NioEventLoopGroup eventLoopGroup; - protected final Map channels = new ConcurrentHashMap<>(); - private boolean compressedMessages = false; - private final List> reconnFailEmitters = new LinkedList<>(); - private final List> connectionSuccessEmitters = new LinkedList<>(); - private final PublishSubject subjectIdle = PublishSubject.create(); - - //debugging - private boolean acceptAllCertificates = false; - private boolean enableLoggingHandler = false; - private LogLevel loggingHandlerLevel = LogLevel.DEBUG; - private String socksProxyHost; - private Integer socksProxyPort; - - public NettyStreamingService(String apiUrl) { - this(apiUrl, 65536); - } + protected static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(10); + protected static final Duration DEFAULT_RETRY_DURATION = Duration.ofSeconds(15); + protected static final int DEFAULT_IDLE_TIMEOUT = 0; - public NettyStreamingService(String apiUrl, int maxFramePayloadLength) { - this(apiUrl, maxFramePayloadLength, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION); - } + private class Subscription { + final ObservableEmitter emitter; + final String channelName; + final Object[] args; - public NettyStreamingService(String apiUrl, int maxFramePayloadLength, Duration connectionTimeout, Duration retryDuration) { - this(apiUrl, maxFramePayloadLength, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION, DEFAULT_IDLE_TIMEOUT); + public Subscription(ObservableEmitter emitter, String channelName, Object[] args) { + this.emitter = emitter; + this.channelName = channelName; + this.args = args; } - - public NettyStreamingService(String apiUrl, int maxFramePayloadLength, Duration connectionTimeout, Duration retryDuration, int idleTimeoutSeconds) { - this.maxFramePayloadLength = maxFramePayloadLength; - this.retryDuration = retryDuration; - this.connectionTimeout = connectionTimeout; - this.idleTimeoutSeconds = idleTimeoutSeconds; - try { - this.uri = new URI(apiUrl); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Error parsing URI " + apiUrl, e); - } + } + + private final int maxFramePayloadLength; + private final URI uri; + private final AtomicBoolean isManualDisconnect = new AtomicBoolean(); + private Channel webSocketChannel; + private final Duration retryDuration; + private final Duration connectionTimeout; + private final int idleTimeoutSeconds; + private volatile NioEventLoopGroup eventLoopGroup; + protected final Map channels = new ConcurrentHashMap<>(); + private boolean compressedMessages = false; + private final List> reconnFailEmitters = new LinkedList<>(); + private final List> connectionSuccessEmitters = new LinkedList<>(); + private final PublishSubject subjectIdle = PublishSubject.create(); + + // debugging + private boolean acceptAllCertificates = false; + private boolean enableLoggingHandler = false; + private LogLevel loggingHandlerLevel = LogLevel.DEBUG; + private String socksProxyHost; + private Integer socksProxyPort; + + public NettyStreamingService(String apiUrl) { + this(apiUrl, 65536); + } + + public NettyStreamingService(String apiUrl, int maxFramePayloadLength) { + this(apiUrl, maxFramePayloadLength, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_RETRY_DURATION); + } + + public NettyStreamingService( + String apiUrl, + int maxFramePayloadLength, + Duration connectionTimeout, + Duration retryDuration) { + this( + apiUrl, + maxFramePayloadLength, + DEFAULT_CONNECTION_TIMEOUT, + DEFAULT_RETRY_DURATION, + DEFAULT_IDLE_TIMEOUT); + } + + public NettyStreamingService( + String apiUrl, + int maxFramePayloadLength, + Duration connectionTimeout, + Duration retryDuration, + int idleTimeoutSeconds) { + this.maxFramePayloadLength = maxFramePayloadLength; + this.retryDuration = retryDuration; + this.connectionTimeout = connectionTimeout; + this.idleTimeoutSeconds = idleTimeoutSeconds; + try { + this.uri = new URI(apiUrl); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Error parsing URI " + apiUrl, e); } + } - @Override - protected Completable openConnection() { - return Completable.create(completable -> { - try { + @Override + protected Completable openConnection() { + return Completable.create( + completable -> { + try { LOG.info("Connecting to {}", uri.toString()); String scheme = uri.getScheme() == null ? "ws" : uri.getScheme(); String host = uri.getHost(); if (host == null) { - throw new IllegalArgumentException("Host cannot be null."); + throw new IllegalArgumentException("Host cannot be null."); } final int port; if (uri.getPort() == -1) { - if ("ws".equalsIgnoreCase(scheme)) { - port = 80; - } else if ("wss".equalsIgnoreCase(scheme)) { - port = 443; - } else { - port = -1; - } + if ("ws".equalsIgnoreCase(scheme)) { + port = 80; + } else if ("wss".equalsIgnoreCase(scheme)) { + port = 443; + } else { + port = -1; + } } else { - port = uri.getPort(); + port = uri.getPort(); } if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) { - throw new IllegalArgumentException("Only WS(S) is supported."); + throw new IllegalArgumentException("Only WS(S) is supported."); } final boolean ssl = "wss".equalsIgnoreCase(scheme); final SslContext sslCtx; if (ssl) { - SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); - if (acceptAllCertificates) { - sslContextBuilder = sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); - } - sslCtx = sslContextBuilder.build(); + SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); + if (acceptAllCertificates) { + sslContextBuilder = + sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); + } + sslCtx = sslContextBuilder.build(); } else { - sslCtx = null; + sslCtx = null; } - final WebSocketClientHandler handler = getWebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker( - uri, WebSocketVersion.V13, null, true, getCustomHeaders(), maxFramePayloadLength), + final WebSocketClientHandler handler = + getWebSocketClientHandler( + WebSocketClientHandshakerFactory.newHandshaker( + uri, + WebSocketVersion.V13, + null, + true, + getCustomHeaders(), + maxFramePayloadLength), this::messageHandler); if (eventLoopGroup == null || eventLoopGroup.isShutdown()) { - eventLoopGroup = new NioEventLoopGroup(2); + eventLoopGroup = new NioEventLoopGroup(2); } - new Bootstrap().group(eventLoopGroup) - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, java.lang.Math.toIntExact(connectionTimeout.toMillis())) - .option(ChannelOption.SO_KEEPALIVE, true) - .channel(NioSocketChannel.class) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) { - ChannelPipeline p = ch.pipeline(); - if (socksProxyHost != null) { - p.addLast(new Socks5ProxyHandler(SocketUtils.socketAddress(socksProxyHost, socksProxyPort))); - } - if (sslCtx != null) { - p.addLast(sslCtx.newHandler(ch.alloc(), host, port)); - } - p.addLast(new HttpClientCodec()); - if (enableLoggingHandler) p.addLast(new LoggingHandler(loggingHandlerLevel)); - if (compressedMessages) p.addLast(WebSocketClientCompressionHandler.INSTANCE); - p.addLast(new HttpObjectAggregator(8192)); - if (idleTimeoutSeconds > 0) p.addLast(new IdleStateHandler(idleTimeoutSeconds, 0, 0)); - WebSocketClientExtensionHandler clientExtensionHandler = getWebSocketClientExtensionHandler(); - if (clientExtensionHandler != null) { - p.addLast(clientExtensionHandler); - } - p.addLast(handler); + new Bootstrap() + .group(eventLoopGroup) + .option( + ChannelOption.CONNECT_TIMEOUT_MILLIS, + java.lang.Math.toIntExact(connectionTimeout.toMillis())) + .option(ChannelOption.SO_KEEPALIVE, true) + .channel(NioSocketChannel.class) + .handler( + new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline p = ch.pipeline(); + if (socksProxyHost != null) { + p.addLast( + new Socks5ProxyHandler( + SocketUtils.socketAddress(socksProxyHost, socksProxyPort))); } + if (sslCtx != null) { + p.addLast(sslCtx.newHandler(ch.alloc(), host, port)); + } + p.addLast(new HttpClientCodec()); + if (enableLoggingHandler) + p.addLast(new LoggingHandler(loggingHandlerLevel)); + if (compressedMessages) + p.addLast(WebSocketClientCompressionHandler.INSTANCE); + p.addLast(new HttpObjectAggregator(8192)); + if (idleTimeoutSeconds > 0) + p.addLast(new IdleStateHandler(idleTimeoutSeconds, 0, 0)); + WebSocketClientExtensionHandler clientExtensionHandler = + getWebSocketClientExtensionHandler(); + if (clientExtensionHandler != null) { + p.addLast(clientExtensionHandler); + } + p.addLast(handler); + } }) - .connect(uri.getHost(), port) - .addListener((ChannelFuture channelFuture) -> { - webSocketChannel = channelFuture.channel(); - if (channelFuture.isSuccess()) { - handler.handshakeFuture().addListener(handshakeFuture -> { - if (handshakeFuture.isSuccess()) { + .connect(uri.getHost(), port) + .addListener( + (ChannelFuture channelFuture) -> { + webSocketChannel = channelFuture.channel(); + if (channelFuture.isSuccess()) { + handler + .handshakeFuture() + .addListener( + handshakeFuture -> { + if (handshakeFuture.isSuccess()) { completable.onComplete(); - } else { - webSocketChannel.disconnect().addListener(x -> { - completable.onError(handshakeFuture.cause()); - }); - } - }); - } else { - scheduleReconnect(); - completable.onError(channelFuture.cause()); - } + } else { + webSocketChannel + .disconnect() + .addListener( + x -> { + completable.onError(handshakeFuture.cause()); + }); + } + }); + } else { + scheduleReconnect(); + completable.onError(channelFuture.cause()); + } }); - } catch (Exception throwable) { + } catch (Exception throwable) { scheduleReconnect(); completable.onError(throwable); - } - }) - .doOnError(t -> { - if (t instanceof WebSocketHandshakeException) { + } + }) + .doOnError( + t -> { + if (t instanceof WebSocketHandshakeException) { LOG.warn("Problem with connection: {} - {}", t.getClass(), t.getMessage()); - } else { + } else { LOG.warn("Problem with connection", t); - } - reconnFailEmitters.forEach(emitter -> emitter.onNext(t)); - }) - .doOnComplete(() -> { - LOG.warn("Resubscribing channels"); - resubscribeChannels(); - - connectionSuccessEmitters.forEach(emitter -> emitter.onNext(new Object())); + } + reconnFailEmitters.forEach(emitter -> emitter.onNext(t)); + }) + .doOnComplete( + () -> { + LOG.warn("Resubscribing channels"); + resubscribeChannels(); + + connectionSuccessEmitters.forEach(emitter -> emitter.onNext(new Object())); + }); + } + + private void scheduleReconnect() { + LOG.info("Scheduling reconnection"); + webSocketChannel + .eventLoop() + .schedule(() -> connect().subscribe(), retryDuration.toMillis(), TimeUnit.MILLISECONDS); + } + + protected DefaultHttpHeaders getCustomHeaders() { + return new DefaultHttpHeaders(); + } + + public Completable disconnect() { + isManualDisconnect.set(true); + return Completable.create( + completable -> { + if (webSocketChannel != null && webSocketChannel.isOpen()) { + CloseWebSocketFrame closeFrame = new CloseWebSocketFrame(); + webSocketChannel + .writeAndFlush(closeFrame) + .addListener( + future -> { + channels.clear(); + eventLoopGroup + .shutdownGracefully(2, 30, TimeUnit.SECONDS) + .addListener( + f -> { + LOG.info("Disconnected"); + completable.onComplete(); + }); + }); + } else { + LOG.warn("Disconnect called but already disconnected"); + completable.onComplete(); + } }); - } + } - private void scheduleReconnect() { - LOG.info("Scheduling reconnection"); - webSocketChannel.eventLoop().schedule( - () -> connect().subscribe(), - retryDuration.toMillis(), - TimeUnit.MILLISECONDS); - } + protected abstract String getChannelNameFromMessage(T message) throws IOException; - protected DefaultHttpHeaders getCustomHeaders() { - return new DefaultHttpHeaders(); - } + public abstract String getSubscribeMessage(String channelName, Object... args) throws IOException; - public Completable disconnect() { - isManualDisconnect.set(true); - return Completable.create(completable -> { - if (webSocketChannel != null && webSocketChannel.isOpen()) { - CloseWebSocketFrame closeFrame = new CloseWebSocketFrame(); - webSocketChannel.writeAndFlush(closeFrame).addListener(future -> { - channels.clear(); - eventLoopGroup.shutdownGracefully(2, 30, TimeUnit.SECONDS).addListener(f -> { - LOG.info("Disconnected"); - completable.onComplete(); - }); - }); - } else { - LOG.warn("Disconnect called but already disconnected"); - completable.onComplete(); - } - }); - } + public abstract String getUnsubscribeMessage(String channelName) throws IOException; - protected abstract String getChannelNameFromMessage(T message) throws IOException; + public String getSubscriptionUniqueId(String channelName, Object... args) { + return channelName; + } - public abstract String getSubscribeMessage(String channelName, Object... args) throws IOException; + /** + * Handler that receives incoming messages. + * + * @param message Content of the message from the server. + */ + public abstract void messageHandler(String message); - public abstract String getUnsubscribeMessage(String channelName) throws IOException; + public void sendMessage(String message) { + LOG.debug("Sending message: {}", message); - public String getSubscriptionUniqueId(String channelName, Object... args) { - return channelName; + if (webSocketChannel == null || !webSocketChannel.isOpen()) { + LOG.warn("WebSocket is not open! Call connect first."); + return; } - /** - * Handler that receives incoming messages. - * - * @param message Content of the message from the server. - */ - public abstract void messageHandler(String message); - - public void sendMessage(String message) { - LOG.debug("Sending message: {}", message); - - if (webSocketChannel == null || !webSocketChannel.isOpen()) { - LOG.warn("WebSocket is not open! Call connect first."); - return; - } - - if (!webSocketChannel.isWritable()) { - LOG.warn("Cannot send data to WebSocket as it is not writable."); - return; - } - - if (message != null) { - WebSocketFrame frame = new TextWebSocketFrame(message); - webSocketChannel.writeAndFlush(frame); - } + if (!webSocketChannel.isWritable()) { + LOG.warn("Cannot send data to WebSocket as it is not writable."); + return; } - public Observable subscribeReconnectFailure() { - return Observable.create(reconnFailEmitters::add); + if (message != null) { + WebSocketFrame frame = new TextWebSocketFrame(message); + webSocketChannel.writeAndFlush(frame); } + } - public Observable subscribeConnectionSuccess() { - return Observable.create(connectionSuccessEmitters::add); - } + public Observable subscribeReconnectFailure() { + return Observable.create(reconnFailEmitters::add); + } - public Observable subscribeChannel(String channelName, Object... args) { - final String channelId = getSubscriptionUniqueId(channelName, args); - LOG.info("Subscribing to channel {}", channelId); + public Observable subscribeConnectionSuccess() { + return Observable.create(connectionSuccessEmitters::add); + } - return Observable.create(e -> { - if (webSocketChannel == null || !webSocketChannel.isOpen()) { + public Observable subscribeChannel(String channelName, Object... args) { + final String channelId = getSubscriptionUniqueId(channelName, args); + LOG.info("Subscribing to channel {}", channelId); + + return Observable.create( + e -> { + if (webSocketChannel == null || !webSocketChannel.isOpen()) { e.onError(new NotConnectedException()); - } - channels.computeIfAbsent(channelId, cid -> { - Subscription newSubscription = new Subscription(e, channelName, args); - try { - sendMessage(getSubscribeMessage(channelName, args)); - } catch (IOException throwable) { - e.onError(throwable); - } - return newSubscription; - }); - }).doOnDispose(() -> { - if (channels.remove(channelId) != null) { + } + channels.computeIfAbsent( + channelId, + cid -> { + Subscription newSubscription = new Subscription(e, channelName, args); + try { + sendMessage(getSubscribeMessage(channelName, args)); + } catch (IOException throwable) { + e.onError(throwable); + } + return newSubscription; + }); + }) + .doOnDispose( + () -> { + if (channels.remove(channelId) != null) { sendMessage(getUnsubscribeMessage(channelId)); - } - }).share(); + } + }) + .share(); + } + + public void resubscribeChannels() { + for (Entry entry : channels.entrySet()) { + try { + Subscription subscription = entry.getValue(); + sendMessage(getSubscribeMessage(subscription.channelName, subscription.args)); + } catch (IOException e) { + LOG.error("Failed to reconnect channel: {}", entry.getKey()); + } } - - public void resubscribeChannels() { - for (Entry entry : channels.entrySet()) { - try { - Subscription subscription = entry.getValue(); - sendMessage(getSubscribeMessage(subscription.channelName, subscription.args)); - } catch (IOException e) { - LOG.error("Failed to reconnect channel: {}", entry.getKey()); - } - } + } + + protected String getChannel(T message) { + String channel; + try { + channel = getChannelNameFromMessage(message); + } catch (IOException e) { + LOG.error("Cannot parse channel from message: {}", message); + return ""; } - - protected String getChannel(T message) { - String channel; - try { - channel = getChannelNameFromMessage(message); - } catch (IOException e) { - LOG.error("Cannot parse channel from message: {}", message); - return ""; - } - return channel; + return channel; + } + + protected void handleMessage(T message) { + String channel = getChannel(message); + handleChannelMessage(channel, message); + } + + protected void handleError(T message, Throwable t) { + String channel = getChannel(message); + handleChannelError(channel, t); + } + + protected void handleIdle(ChannelHandlerContext ctx) { + // No-op + } + + private void onIdle(ChannelHandlerContext ctx) { + subjectIdle.onNext(1); + handleIdle(ctx); + } + + /** + * Observable which fires if the websocket is deemed idle, only fired if + * idleTimeoutSeconds != 0. + */ + public Observable subscribeIdle() { + return subjectIdle.share(); + } + + protected void handleChannelMessage(String channel, T message) { + NettyStreamingService.Subscription subscription = channels.get(channel); + if (subscription == null) { + LOG.debug("Channel has been closed {}.", channel); + return; } - - protected void handleMessage(T message) { - String channel = getChannel(message); - handleChannelMessage(channel, message); + ObservableEmitter emitter = subscription.emitter; + if (emitter == null) { + LOG.debug("No subscriber for channel {}.", channel); + return; } - protected void handleError(T message, Throwable t) { - String channel = getChannel(message); - handleChannelError(channel, t); - } + emitter.onNext(message); + } - protected void handleIdle(ChannelHandlerContext ctx) { - // No-op + protected void handleChannelError(String channel, Throwable t) { + NettyStreamingService.Subscription subscription = channels.get(channel); + if (subscription == null) { + LOG.debug("Channel {} has been closed.", channel); + return; } - - private void onIdle(ChannelHandlerContext ctx) { - subjectIdle.onNext(1); - handleIdle(ctx); + ObservableEmitter emitter = subscription.emitter; + if (emitter == null) { + LOG.debug("No subscriber for channel {}.", channel); + return; } - /** - * Observable which fires if the websocket is deemed idle, - * only fired if idleTimeoutSeconds != 0. - */ - public Observable subscribeIdle() { - return subjectIdle.share(); - } + emitter.onError(t); + } - protected void handleChannelMessage(String channel, T message) { - NettyStreamingService.Subscription subscription = channels.get(channel); - if (subscription == null) { - LOG.debug("Channel has been closed {}.", channel); - return; - } - ObservableEmitter emitter = subscription.emitter; - if (emitter == null) { - LOG.debug("No subscriber for channel {}.", channel); - return; - } - - emitter.onNext(message); - } + protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { + return WebSocketClientCompressionHandler.INSTANCE; + } - protected void handleChannelError(String channel, Throwable t) { - NettyStreamingService.Subscription subscription = channels.get(channel); - if (subscription == null) { - LOG.debug("Channel {} has been closed.", channel); - return; - } - ObservableEmitter emitter = subscription.emitter; - if (emitter == null) { - LOG.debug("No subscriber for channel {}.", channel); - return; - } - - emitter.onError(t); - } + protected WebSocketClientHandler getWebSocketClientHandler( + WebSocketClientHandshaker handshaker, + WebSocketClientHandler.WebSocketMessageHandler handler) { + return new NettyWebSocketClientHandler(handshaker, handler); + } - protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { - return WebSocketClientCompressionHandler.INSTANCE; + protected class NettyWebSocketClientHandler extends WebSocketClientHandler { + protected NettyWebSocketClientHandler( + WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { + super(handshaker, handler); } - protected WebSocketClientHandler getWebSocketClientHandler(WebSocketClientHandshaker handshaker, - WebSocketClientHandler.WebSocketMessageHandler handler) { - return new NettyWebSocketClientHandler(handshaker, handler); + @Override + public void channelInactive(ChannelHandlerContext ctx) { + if (isManualDisconnect.compareAndSet(true, false)) { + // Don't attempt to reconnect + } else { + super.channelInactive(ctx); + LOG.info("Reopening websocket because it was closed"); + scheduleReconnect(); + } } - protected class NettyWebSocketClientHandler extends WebSocketClientHandler { - protected NettyWebSocketClientHandler(WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { - super(handshaker, handler); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - if (isManualDisconnect.compareAndSet(true, false)) { - // Don't attempt to reconnect - } else { - super.channelInactive(ctx); - LOG.info("Reopening websocket because it was closed"); - scheduleReconnect(); - } - } - - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { - if (!(evt instanceof IdleStateEvent)) { - return; - } - IdleStateEvent e = (IdleStateEvent) evt; - if (e.state().equals(IdleState.READER_IDLE)) { - onIdle(ctx); - } - } + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (!(evt instanceof IdleStateEvent)) { + return; + } + IdleStateEvent e = (IdleStateEvent) evt; + if (e.state().equals(IdleState.READER_IDLE)) { + onIdle(ctx); + } } + } - public boolean isSocketOpen() { - return webSocketChannel != null && webSocketChannel.isOpen(); - } + public boolean isSocketOpen() { + return webSocketChannel != null && webSocketChannel.isOpen(); + } - public void useCompressedMessages(boolean compressedMessages) { this.compressedMessages = compressedMessages; } + public void useCompressedMessages(boolean compressedMessages) { + this.compressedMessages = compressedMessages; + } - public void setAcceptAllCertificates(boolean acceptAllCertificates) { - this.acceptAllCertificates = acceptAllCertificates; -} + public void setAcceptAllCertificates(boolean acceptAllCertificates) { + this.acceptAllCertificates = acceptAllCertificates; + } - public void setEnableLoggingHandler(boolean enableLoggingHandler) { - this.enableLoggingHandler = enableLoggingHandler; - } + public void setEnableLoggingHandler(boolean enableLoggingHandler) { + this.enableLoggingHandler = enableLoggingHandler; + } - public void setLoggingHandlerLevel(LogLevel loggingHandlerLevel) { - this.loggingHandlerLevel = loggingHandlerLevel; - } - - public void setSocksProxyHost(String socksProxyHost) { - this.socksProxyHost = socksProxyHost; - } - - public void setSocksProxyPort(Integer socksProxyPort) { - this.socksProxyPort = socksProxyPort; - } + public void setLoggingHandlerLevel(LogLevel loggingHandlerLevel) { + this.loggingHandlerLevel = loggingHandlerLevel; + } + public void setSocksProxyHost(String socksProxyHost) { + this.socksProxyHost = socksProxyHost; + } + public void setSocksProxyPort(Integer socksProxyPort) { + this.socksProxyPort = socksProxyPort; + } } diff --git a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/RetryWithDelay.java b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/RetryWithDelay.java index bb842dbf2..e8b967699 100644 --- a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/RetryWithDelay.java +++ b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/RetryWithDelay.java @@ -2,24 +2,24 @@ import io.reactivex.Flowable; import io.reactivex.functions.Function; -import org.reactivestreams.Publisher; - import java.util.concurrent.TimeUnit; +import org.reactivestreams.Publisher; public class RetryWithDelay implements Function, Publisher> { - private final long retryDelayMillis; + private final long retryDelayMillis; - public RetryWithDelay(final long retryDelayMillis) { - this.retryDelayMillis = retryDelayMillis; - } + public RetryWithDelay(final long retryDelayMillis) { + this.retryDelayMillis = retryDelayMillis; + } - @Override - public Publisher apply(Flowable flowable) throws Exception { - return flowable.flatMap(new Function>() { - @Override - public Publisher apply(Throwable throwable) throws Exception { - return Flowable.timer(retryDelayMillis, TimeUnit.MILLISECONDS); - } + @Override + public Publisher apply(Flowable flowable) throws Exception { + return flowable.flatMap( + new Function>() { + @Override + public Publisher apply(Throwable throwable) throws Exception { + return Flowable.timer(retryDelayMillis, TimeUnit.MILLISECONDS); + } }); - } + } } diff --git a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/StreamingObjectMapperHelper.java b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/StreamingObjectMapperHelper.java index 10ce1e5ac..af2941932 100644 --- a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/StreamingObjectMapperHelper.java +++ b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/StreamingObjectMapperHelper.java @@ -11,22 +11,19 @@ */ public class StreamingObjectMapperHelper { - private static final ObjectMapper objectMapper; + private static final ObjectMapper objectMapper; - static { - objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); - } + static { + objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); + } - private StreamingObjectMapperHelper() { - - } - - public static ObjectMapper getObjectMapper() { - return objectMapper; - } + private StreamingObjectMapperHelper() {} + public static ObjectMapper getObjectMapper() { + return objectMapper; + } } diff --git a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/WebSocketClientHandler.java b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/WebSocketClientHandler.java index 4397851f1..cdcf104f2 100644 --- a/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/WebSocketClientHandler.java +++ b/xchange-stream-service-netty/src/main/java/info/bitrich/xchangestream/service/netty/WebSocketClientHandler.java @@ -19,100 +19,108 @@ import org.slf4j.LoggerFactory; public class WebSocketClientHandler extends SimpleChannelInboundHandler { - private static final Logger LOG = LoggerFactory.getLogger(WebSocketClientHandler.class); - private final StringBuilder currentMessage = new StringBuilder(); + private static final Logger LOG = LoggerFactory.getLogger(WebSocketClientHandler.class); + private final StringBuilder currentMessage = new StringBuilder(); - public interface WebSocketMessageHandler { - public void onMessage(String message); - } + public interface WebSocketMessageHandler { + public void onMessage(String message); + } - protected final WebSocketClientHandshaker handshaker; - protected final WebSocketMessageHandler handler; - private ChannelPromise handshakeFuture; + protected final WebSocketClientHandshaker handshaker; + protected final WebSocketMessageHandler handler; + private ChannelPromise handshakeFuture; - public WebSocketClientHandler(WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { - this.handshaker = handshaker; - this.handler = handler; - } + public WebSocketClientHandler( + WebSocketClientHandshaker handshaker, WebSocketMessageHandler handler) { + this.handshaker = handshaker; + this.handler = handler; + } - public ChannelFuture handshakeFuture() { - return handshakeFuture; - } + public ChannelFuture handshakeFuture() { + return handshakeFuture; + } - @Override - public void handlerAdded(ChannelHandlerContext ctx) { - handshakeFuture = ctx.newPromise(); - } + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + handshakeFuture = ctx.newPromise(); + } - @Override - public void channelActive(ChannelHandlerContext ctx) { - handshaker.handshake(ctx.channel()); - } + @Override + public void channelActive(ChannelHandlerContext ctx) { + handshaker.handshake(ctx.channel()); + } - @Override - public void channelInactive(ChannelHandlerContext ctx) { - LOG.info("WebSocket Client disconnected! {}", ctx.channel()); - } + @Override + public void channelInactive(ChannelHandlerContext ctx) { + LOG.info("WebSocket Client disconnected! {}", ctx.channel()); + } - @Override - public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { - Channel ch = ctx.channel(); - if (!handshaker.isHandshakeComplete()) { - try { - handshaker.finishHandshake(ch, (FullHttpResponse)msg); - LOG.info("WebSocket Client connected! {}", ctx.channel()); - handshakeFuture.setSuccess(); - } - catch (WebSocketHandshakeException e) { - LOG.error("WebSocket Client failed to connect. {} {}", e.getMessage(), ctx.channel()); - handshakeFuture.setFailure(e); - } - return; - } + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + Channel ch = ctx.channel(); + if (!handshaker.isHandshakeComplete()) { + try { + handshaker.finishHandshake(ch, (FullHttpResponse) msg); + LOG.info("WebSocket Client connected! {}", ctx.channel()); + handshakeFuture.setSuccess(); + } catch (WebSocketHandshakeException e) { + LOG.error("WebSocket Client failed to connect. {} {}", e.getMessage(), ctx.channel()); + handshakeFuture.setFailure(e); + } + return; + } - if (msg instanceof FullHttpResponse) { - FullHttpResponse response = (FullHttpResponse)msg; - throw new IllegalStateException("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); - } + if (msg instanceof FullHttpResponse) { + FullHttpResponse response = (FullHttpResponse) msg; + throw new IllegalStateException( + "Unexpected FullHttpResponse (getStatus=" + + response.status() + + ", content=" + + response.content().toString(CharsetUtil.UTF_8) + + ')'); + } - WebSocketFrame frame = (WebSocketFrame)msg; - if (frame instanceof TextWebSocketFrame) { - dealWithTextFrame((TextWebSocketFrame) frame); - } else if (frame instanceof ContinuationWebSocketFrame) { - dealWithContinuation((ContinuationWebSocketFrame) frame); - } else if (frame instanceof PingWebSocketFrame) { - LOG.debug("WebSocket Client received ping"); - ch.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); - } else if (frame instanceof PongWebSocketFrame) { - LOG.debug("WebSocket Client received pong"); - } else if (frame instanceof CloseWebSocketFrame) { - LOG.info("WebSocket Client received closing"); - ch.close(); - } + WebSocketFrame frame = (WebSocketFrame) msg; + if (frame instanceof TextWebSocketFrame) { + dealWithTextFrame((TextWebSocketFrame) frame); + } else if (frame instanceof ContinuationWebSocketFrame) { + dealWithContinuation((ContinuationWebSocketFrame) frame); + } else if (frame instanceof PingWebSocketFrame) { + LOG.debug("WebSocket Client received ping"); + ch.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); + } else if (frame instanceof PongWebSocketFrame) { + LOG.debug("WebSocket Client received pong"); + } else if (frame instanceof CloseWebSocketFrame) { + LOG.info("WebSocket Client received closing"); + ch.close(); } + } - private void dealWithTextFrame(TextWebSocketFrame frame) { - if (frame.isFinalFragment()) { - handler.onMessage(frame.text()); - return; - } - currentMessage.append(frame.text()); + private void dealWithTextFrame(TextWebSocketFrame frame) { + if (frame.isFinalFragment()) { + handler.onMessage(frame.text()); + return; } + currentMessage.append(frame.text()); + } - private void dealWithContinuation(ContinuationWebSocketFrame frame) { - currentMessage.append(frame.text()); - if (frame.isFinalFragment()) { - handler.onMessage(currentMessage.toString()); - currentMessage.setLength(0); - } + private void dealWithContinuation(ContinuationWebSocketFrame frame) { + currentMessage.append(frame.text()); + if (frame.isFinalFragment()) { + handler.onMessage(currentMessage.toString()); + currentMessage.setLength(0); } + } - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - LOG.error("WebSocket client encountered exception ({} - {}). Closing", cause.getClass().getSimpleName(), cause.getMessage()); - if (!handshakeFuture.isDone()) { - handshakeFuture.setFailure(cause); - } - ctx.close(); + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + LOG.error( + "WebSocket client encountered exception ({} - {}). Closing", + cause.getClass().getSimpleName(), + cause.getMessage()); + if (!handshakeFuture.isDone()) { + handshakeFuture.setFailure(cause); } + ctx.close(); + } } diff --git a/xchange-stream-service-pubnub/src/main/java/info/bitrich/xchangestream/service/pubnub/PubnubStreamingService.java b/xchange-stream-service-pubnub/src/main/java/info/bitrich/xchangestream/service/pubnub/PubnubStreamingService.java index 1c83f700f..6880c4e60 100644 --- a/xchange-stream-service-pubnub/src/main/java/info/bitrich/xchangestream/service/pubnub/PubnubStreamingService.java +++ b/xchange-stream-service-pubnub/src/main/java/info/bitrich/xchangestream/service/pubnub/PubnubStreamingService.java @@ -1,13 +1,5 @@ package info.bitrich.xchangestream.service.pubnub; -import java.io.IOException; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -23,121 +15,137 @@ import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult; import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult; import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult; - import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.ObservableEmitter; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * Created by Lukas Zaoralek on 14.11.17. - */ +/** Created by Lukas Zaoralek on 14.11.17. */ public class PubnubStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(PubnubStreamingService.class); - - private final PubNub pubnub; - private PNStatusCategory pnStatusCategory; - private final Map> subscriptions = new ConcurrentHashMap<>(); - private final ObjectMapper mapper; - - public PubnubStreamingService(String publicKey) { - mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - PNConfiguration pnConfiguration = new PNConfiguration(); - pnConfiguration.setSubscribeKey(publicKey); - pubnub = new PubNub(pnConfiguration); - pnStatusCategory = PNStatusCategory.PNDisconnectedCategory; - } - - public Completable connect() { - return Completable.create(e -> { - pubnub.addListener(new SubscribeCallback() { + private static final Logger LOG = LoggerFactory.getLogger(PubnubStreamingService.class); + + private final PubNub pubnub; + private PNStatusCategory pnStatusCategory; + private final Map> subscriptions = new ConcurrentHashMap<>(); + private final ObjectMapper mapper; + + public PubnubStreamingService(String publicKey) { + mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey(publicKey); + pubnub = new PubNub(pnConfiguration); + pnStatusCategory = PNStatusCategory.PNDisconnectedCategory; + } + + public Completable connect() { + return Completable.create( + e -> { + pubnub.addListener( + new SubscribeCallback() { @Override public void status(PubNub pubNub, PNStatus pnStatus) { - pnStatusCategory = pnStatus.getCategory(); - LOG.debug("PubNub status: {} {}", pnStatusCategory.toString(), pnStatus.getStatusCode()); - if (pnStatusCategory == PNStatusCategory.PNConnectedCategory) { -// e.onComplete(); - } else if (pnStatus.isError()) { -// e.onError(pnStatus.getErrorData().getThrowable()); - } + pnStatusCategory = pnStatus.getCategory(); + LOG.debug( + "PubNub status: {} {}", + pnStatusCategory.toString(), + pnStatus.getStatusCode()); + if (pnStatusCategory == PNStatusCategory.PNConnectedCategory) { + // e.onComplete(); + } else if (pnStatus.isError()) { + // e.onError(pnStatus.getErrorData().getThrowable()); + } } @Override public void message(PubNub pubNub, PNMessageResult pnMessageResult) { - String channelName = pnMessageResult.getChannel(); - ObservableEmitter subscription = subscriptions.get(channelName); - LOG.debug("PubNub Message: {}", pnMessageResult.toString()); - if (subscription != null) { - JsonNode jsonMessage = null; - try { - jsonMessage = mapper.readTree(pnMessageResult.getMessage().toString()); - } catch (IOException ex) { - ex.printStackTrace(); - } - subscription.onNext(jsonMessage); - } else { - LOG.debug("No subscriber for channel {}.", channelName); + String channelName = pnMessageResult.getChannel(); + ObservableEmitter subscription = subscriptions.get(channelName); + LOG.debug("PubNub Message: {}", pnMessageResult.toString()); + if (subscription != null) { + JsonNode jsonMessage = null; + try { + jsonMessage = mapper.readTree(pnMessageResult.getMessage().toString()); + } catch (IOException ex) { + ex.printStackTrace(); } + subscription.onNext(jsonMessage); + } else { + LOG.debug("No subscriber for channel {}.", channelName); + } } @Override public void presence(PubNub pubNub, PNPresenceEventResult pnPresenceEventResult) { - LOG.debug("PubNub presence: {}", pnPresenceEventResult.toString()); + LOG.debug("PubNub presence: {}", pnPresenceEventResult.toString()); } @Override public void signal(PubNub pubnub, PNSignalResult pnSignalResult) { - LOG.debug("PubNub signal: {}", pnSignalResult.toString()); + LOG.debug("PubNub signal: {}", pnSignalResult.toString()); } @Override public void user(PubNub pubnub, PNUserResult pnUserResult) { - LOG.debug("PubNub user: {}", pnUserResult.toString()); + LOG.debug("PubNub user: {}", pnUserResult.toString()); } @Override public void space(PubNub pubnub, PNSpaceResult pnSpaceResult) { - LOG.debug("PubNub space: {}", pnSpaceResult.toString()); + LOG.debug("PubNub space: {}", pnSpaceResult.toString()); } @Override public void membership(PubNub pubnub, PNMembershipResult pnMembershipResult) { - LOG.debug("PubNub membership: {}", pnMembershipResult.toString()); + LOG.debug("PubNub membership: {}", pnMembershipResult.toString()); } @Override - public void messageAction(PubNub pubnub, PNMessageActionResult pnMessageActionResult) { - LOG.debug("PubNub messageAction: {}", pnMessageActionResult.toString()); + public void messageAction( + PubNub pubnub, PNMessageActionResult pnMessageActionResult) { + LOG.debug("PubNub messageAction: {}", pnMessageActionResult.toString()); } - }); - e.onComplete(); + }); + e.onComplete(); }); - } + } - public Observable subscribeChannel(String channelName) { - LOG.info("Subscribing to channel {}.", channelName); - return Observable.create(e -> { - if (!subscriptions.containsKey(channelName)) { + public Observable subscribeChannel(String channelName) { + LOG.info("Subscribing to channel {}.", channelName); + return Observable.create( + e -> { + if (!subscriptions.containsKey(channelName)) { subscriptions.put(channelName, e); pubnub.subscribe().channels(Collections.singletonList(channelName)).execute(); LOG.debug("Subscribe channel: {}", channelName); - } - }).doOnDispose(() -> { - LOG.debug("Unsubscribe channel: {}", channelName); - pubnub.unsubscribe().channels(Collections.singletonList(channelName)).execute(); - }).share(); - } - - public Completable disconnect() { - return Completable.create(completable -> { - pubnub.disconnect(); - completable.onComplete(); + } + }) + .doOnDispose( + () -> { + LOG.debug("Unsubscribe channel: {}", channelName); + pubnub.unsubscribe().channels(Collections.singletonList(channelName)).execute(); + }) + .share(); + } + + public Completable disconnect() { + return Completable.create( + completable -> { + pubnub.disconnect(); + completable.onComplete(); }); - } + } - public boolean isAlive() { - return (pnStatusCategory == PNStatusCategory.PNConnectedCategory); - } + public boolean isAlive() { + return (pnStatusCategory == PNStatusCategory.PNConnectedCategory); + } - public void useCompressedMessages(boolean compressedMessages) { throw new UnsupportedOperationException(); } + public void useCompressedMessages(boolean compressedMessages) { + throw new UnsupportedOperationException(); + } } diff --git a/xchange-stream-service-pusher/src/main/java/info/bitrich/xchangestream/service/pusher/PusherStreamingService.java b/xchange-stream-service-pusher/src/main/java/info/bitrich/xchangestream/service/pusher/PusherStreamingService.java index ae4eb66d1..c6a649170 100644 --- a/xchange-stream-service-pusher/src/main/java/info/bitrich/xchangestream/service/pusher/PusherStreamingService.java +++ b/xchange-stream-service-pusher/src/main/java/info/bitrich/xchangestream/service/pusher/PusherStreamingService.java @@ -1,6 +1,5 @@ package info.bitrich.xchangestream.service.pusher; -import com.pusher.client.Authorizer; import com.pusher.client.Pusher; import com.pusher.client.PusherOptions; import com.pusher.client.channel.*; @@ -11,132 +10,149 @@ import info.bitrich.xchangestream.service.exception.NotConnectedException; import io.reactivex.Completable; import io.reactivex.Observable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Collections; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class PusherStreamingService extends ConnectableService { - private static final Logger LOG = LoggerFactory.getLogger(PusherStreamingService.class); - - private final Pusher pusher; - - public PusherStreamingService(String apiKey) { - pusher = new Pusher(apiKey); - } - - public PusherStreamingService(String apiKey, String cluster) { - PusherOptions options = new PusherOptions(); - options.setCluster(cluster); - pusher = new Pusher(apiKey, options); - } - - public PusherStreamingService(String apiKey, PusherOptions options) { - this.pusher = new Pusher(apiKey, options); - } - - /** - * Testing constructor - */ - protected PusherStreamingService(Pusher pusher) { - this.pusher = pusher; - } - - @Override - protected Completable openConnection() { - return Completable.create(e -> pusher.connect(new ConnectionEventListener() { - @Override - public void onConnectionStateChange(ConnectionStateChange change) { - LOG.info("State changed to " + change.getCurrentState() + - " from " + change.getPreviousState()); - if (ConnectionState.CONNECTED.equals(change.getCurrentState())) { - e.onComplete(); - } - } - - @Override - public void onError(String message, String code, Exception throwable) { - if (throwable != null) { - e.onError(throwable); - } else { - e.onError(new RuntimeException("No exception found: [code: " + code + "], message: " + message)); - } - } - }, ConnectionState.ALL)); - } - - public Completable disconnect() { - return Completable.create(completable -> { - pusher.disconnect(); - completable.onComplete(); +public class PusherStreamingService extends ConnectableService { + private static final Logger LOG = LoggerFactory.getLogger(PusherStreamingService.class); + + private final Pusher pusher; + + public PusherStreamingService(String apiKey) { + pusher = new Pusher(apiKey); + } + + public PusherStreamingService(String apiKey, String cluster) { + PusherOptions options = new PusherOptions(); + options.setCluster(cluster); + pusher = new Pusher(apiKey, options); + } + + public PusherStreamingService(String apiKey, PusherOptions options) { + this.pusher = new Pusher(apiKey, options); + } + + /** Testing constructor */ + protected PusherStreamingService(Pusher pusher) { + this.pusher = pusher; + } + + @Override + protected Completable openConnection() { + return Completable.create( + e -> + pusher.connect( + new ConnectionEventListener() { + @Override + public void onConnectionStateChange(ConnectionStateChange change) { + LOG.info( + "State changed to " + + change.getCurrentState() + + " from " + + change.getPreviousState()); + if (ConnectionState.CONNECTED.equals(change.getCurrentState())) { + e.onComplete(); + } + } + + @Override + public void onError(String message, String code, Exception throwable) { + if (throwable != null) { + e.onError(throwable); + } else { + e.onError( + new RuntimeException( + "No exception found: [code: " + code + "], message: " + message)); + } + } + }, + ConnectionState.ALL)); + } + + public Completable disconnect() { + return Completable.create( + completable -> { + pusher.disconnect(); + completable.onComplete(); }); - } + } - public Observable subscribePrivateChannel(String channelName, String eventName) { - return this.subscribePrivateChannel(channelName, Collections.singletonList(eventName)); - } + public Observable subscribePrivateChannel(String channelName, String eventName) { + return this.subscribePrivateChannel(channelName, Collections.singletonList(eventName)); + } - public Observable subscribeChannel(String channelName, String eventName) { - return subscribeChannel(channelName, Collections.singletonList(eventName)); - } + public Observable subscribeChannel(String channelName, String eventName) { + return subscribeChannel(channelName, Collections.singletonList(eventName)); + } - public Observable subscribeChannel(String channelName, List eventsName) { - LOG.info("Subscribing to channel {}.", channelName); - return Observable.create(e -> { - if (!ConnectionState.CONNECTED.equals(pusher.getConnection().getState())) { + public Observable subscribeChannel(String channelName, List eventsName) { + LOG.info("Subscribing to channel {}.", channelName); + return Observable.create( + e -> { + if (!ConnectionState.CONNECTED.equals(pusher.getConnection().getState())) { e.onError(new NotConnectedException()); return; - } - Channel channel = pusher.subscribe(channelName); - - for (String event : eventsName) { - channel.bind(event, (pusherEvent) -> { - LOG.debug("Incoming data: {}", pusherEvent.getData()); - e.onNext(pusherEvent.getData()); - }); - } - }).doOnDispose(() -> pusher.unsubscribe(channelName)); - } - - public Observable subscribePrivateChannel(String channelName, List eventsName) { - LOG.info("Subscribing to channel {}.", channelName); - - return Observable.create(e -> { - if (!ConnectionState.CONNECTED.equals(pusher.getConnection().getState())) { + } + Channel channel = pusher.subscribe(channelName); + + for (String event : eventsName) { + channel.bind( + event, + (pusherEvent) -> { + LOG.debug("Incoming data: {}", pusherEvent.getData()); + e.onNext(pusherEvent.getData()); + }); + } + }) + .doOnDispose(() -> pusher.unsubscribe(channelName)); + } + + public Observable subscribePrivateChannel(String channelName, List eventsName) { + LOG.info("Subscribing to channel {}.", channelName); + + return Observable.create( + e -> { + if (!ConnectionState.CONNECTED.equals(pusher.getConnection().getState())) { e.onError(new NotConnectedException()); return; - } - - PrivateChannelEventListener listener = new PrivateChannelEventListener() { - public void onAuthenticationFailure(String s, Exception ex) { - LOG.error(ex.getMessage(), ex); - e.onError(ex); - } - - public void onSubscriptionSucceeded(String s) { - LOG.info("Subscription successful! :{} ", s); - } - - @Override - public void onEvent(PusherEvent pusherEvent) { - LOG.debug("Incoming data: {}", pusherEvent.getData()); - e.onNext(pusherEvent.getData()); - } - }; - Channel channel = pusher.subscribePrivate(channelName,listener); - for (String event : eventsName) { + } + + PrivateChannelEventListener listener = + new PrivateChannelEventListener() { + public void onAuthenticationFailure(String s, Exception ex) { + LOG.error(ex.getMessage(), ex); + e.onError(ex); + } + + public void onSubscriptionSucceeded(String s) { + LOG.info("Subscription successful! :{} ", s); + } + + @Override + public void onEvent(PusherEvent pusherEvent) { + LOG.debug("Incoming data: {}", pusherEvent.getData()); + e.onNext(pusherEvent.getData()); + } + }; + Channel channel = pusher.subscribePrivate(channelName, listener); + for (String event : eventsName) { channel.bind(event, listener); - } - }).doOnDispose(() -> { - LOG.info("Disposing " + channelName); - pusher.unsubscribe(channelName); - }); - } - - public boolean isSocketOpen() { - return pusher.getConnection().getState() == ConnectionState.CONNECTED; - } - - public void useCompressedMessages(boolean compressedMessages) { throw new UnsupportedOperationException(); } + } + }) + .doOnDispose( + () -> { + LOG.info("Disposing " + channelName); + pusher.unsubscribe(channelName); + }); + } + + public boolean isSocketOpen() { + return pusher.getConnection().getState() == ConnectionState.CONNECTED; + } + + public void useCompressedMessages(boolean compressedMessages) { + throw new UnsupportedOperationException(); + } } diff --git a/xchange-stream-service-pusher/src/test/java/info/bitrich/xchangestream/service/pusher/PusherStreamingServiceTest.java b/xchange-stream-service-pusher/src/test/java/info/bitrich/xchangestream/service/pusher/PusherStreamingServiceTest.java index 635f677be..a4152d404 100644 --- a/xchange-stream-service-pusher/src/test/java/info/bitrich/xchangestream/service/pusher/PusherStreamingServiceTest.java +++ b/xchange-stream-service-pusher/src/test/java/info/bitrich/xchangestream/service/pusher/PusherStreamingServiceTest.java @@ -1,5 +1,9 @@ package info.bitrich.xchangestream.service.pusher; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + import com.pusher.client.Pusher; import com.pusher.client.channel.Channel; import com.pusher.client.channel.PusherEvent; @@ -10,88 +14,87 @@ import com.pusher.client.connection.websocket.WebSocketConnection; import info.bitrich.xchangestream.service.exception.NotConnectedException; import io.reactivex.observers.TestObserver; +import java.util.HashMap; +import java.util.Map; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.HashMap; -import java.util.Map; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; - public class PusherStreamingServiceTest { - @Mock - private Pusher pusher; - - @Mock - private WebSocketConnection connection; - - @Mock - private Channel channel; - - private PusherStreamingService streamingService; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - streamingService = new PusherStreamingService(pusher); - } - - @Test - public void testConnect() throws Exception { - // Capture listener from connect method. - ArgumentCaptor listener = ArgumentCaptor.forClass(ConnectionEventListener.class); - TestObserver test = streamingService.connect().test(); - - // Verify the connect on pusher has been called. - verify(pusher).connect(listener.capture(), any()); - - // Mock connection state change. - listener.getValue().onConnectionStateChange(new ConnectionStateChange(ConnectionState.CONNECTING, ConnectionState.CONNECTED)); - - // Connect should complete. - test.assertComplete(); - } - - @Test - public void testSubscribeChannel() throws Exception { - when(pusher.getConnection()).thenReturn(connection); - when(connection.getState()).thenReturn(ConnectionState.CONNECTED); - when(pusher.subscribe(any())).thenReturn(channel); - - // Subscribe to the channel - TestObserver test = streamingService.subscribeChannel("channelName", "eventName").test(); - - // There are no errors and stream is not terminated. - test.assertNoErrors(); - test.assertNotTerminated(); - verify(pusher).subscribe("channelName"); - - ArgumentCaptor subscription = ArgumentCaptor.forClass(SubscriptionEventListener.class); - // Verify binding to the channel. - verify(channel).bind(anyString(), subscription.capture()); - - // Send data to the observable - Map pusherEventMap = new HashMap<>(); - pusherEventMap.put("data","dataObject"); - subscription.getValue().onEvent(new PusherEvent(pusherEventMap)); - test.assertValue("dataObject"); - } - - @Test - public void testSubscribeChannelNotConnected() throws Exception { - when(pusher.getConnection()).thenReturn(connection); - when(connection.getState()).thenReturn(ConnectionState.DISCONNECTED); - - // Subscribe to the channel - TestObserver test = streamingService.subscribeChannel("channelName", "eventName").test(); - - // There are no errors and stream is not terminated. - test.assertError(NotConnectedException.class); - verify(pusher, never()).subscribe("channelName"); - } -} \ No newline at end of file + @Mock private Pusher pusher; + + @Mock private WebSocketConnection connection; + + @Mock private Channel channel; + + private PusherStreamingService streamingService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + streamingService = new PusherStreamingService(pusher); + } + + @Test + public void testConnect() throws Exception { + // Capture listener from connect method. + ArgumentCaptor listener = + ArgumentCaptor.forClass(ConnectionEventListener.class); + TestObserver test = streamingService.connect().test(); + + // Verify the connect on pusher has been called. + verify(pusher).connect(listener.capture(), any()); + + // Mock connection state change. + listener + .getValue() + .onConnectionStateChange( + new ConnectionStateChange(ConnectionState.CONNECTING, ConnectionState.CONNECTED)); + + // Connect should complete. + test.assertComplete(); + } + + @Test + public void testSubscribeChannel() throws Exception { + when(pusher.getConnection()).thenReturn(connection); + when(connection.getState()).thenReturn(ConnectionState.CONNECTED); + when(pusher.subscribe(any())).thenReturn(channel); + + // Subscribe to the channel + TestObserver test = + streamingService.subscribeChannel("channelName", "eventName").test(); + + // There are no errors and stream is not terminated. + test.assertNoErrors(); + test.assertNotTerminated(); + verify(pusher).subscribe("channelName"); + + ArgumentCaptor subscription = + ArgumentCaptor.forClass(SubscriptionEventListener.class); + // Verify binding to the channel. + verify(channel).bind(anyString(), subscription.capture()); + + // Send data to the observable + Map pusherEventMap = new HashMap<>(); + pusherEventMap.put("data", "dataObject"); + subscription.getValue().onEvent(new PusherEvent(pusherEventMap)); + test.assertValue("dataObject"); + } + + @Test + public void testSubscribeChannelNotConnected() throws Exception { + when(pusher.getConnection()).thenReturn(connection); + when(connection.getState()).thenReturn(ConnectionState.DISCONNECTED); + + // Subscribe to the channel + TestObserver test = + streamingService.subscribeChannel("channelName", "eventName").test(); + + // There are no errors and stream is not terminated. + test.assertError(NotConnectedException.class); + verify(pusher, never()).subscribe("channelName"); + } +} diff --git a/xchange-stream-service-wamp/src/main/java/info/bitrich/xchangestream/service/wamp/WampStreamingService.java b/xchange-stream-service-wamp/src/main/java/info/bitrich/xchangestream/service/wamp/WampStreamingService.java index 99fb82d29..6a1434304 100644 --- a/xchange-stream-service-wamp/src/main/java/info/bitrich/xchangestream/service/wamp/WampStreamingService.java +++ b/xchange-stream-service-wamp/src/main/java/info/bitrich/xchangestream/service/wamp/WampStreamingService.java @@ -4,6 +4,7 @@ import info.bitrich.xchangestream.service.exception.NotConnectedException; import io.reactivex.Completable; import io.reactivex.Observable; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.subjects.BehaviorSubject; @@ -14,75 +15,84 @@ import ws.wamp.jawampa.connection.IWampConnectorProvider; import ws.wamp.jawampa.transport.netty.NettyWampClientConnectorProvider; -import java.util.concurrent.TimeUnit; - public class WampStreamingService { - private static final Logger LOG = LoggerFactory.getLogger(WampStreamingService.class); + private static final Logger LOG = LoggerFactory.getLogger(WampStreamingService.class); - private final String uri; - private final String realm; - private WampClient client; - private WampClient.State connectedState; + private final String uri; + private final String realm; + private WampClient client; + private WampClient.State connectedState; - public WampStreamingService(String uri, String realm) { - this.uri = uri; - this.realm = realm; - } + public WampStreamingService(String uri, String realm) { + this.uri = uri; + this.realm = realm; + } - public Completable connect() { - return Completable.create(completable -> { - IWampConnectorProvider connectorProvider = new NettyWampClientConnectorProvider(); + public Completable connect() { + return Completable.create( + completable -> { + IWampConnectorProvider connectorProvider = new NettyWampClientConnectorProvider(); - try { - // Create a builder and configure the client - WampClientBuilder builder = new WampClientBuilder(); - builder.withConnectorProvider(connectorProvider) - .withUri(uri) - .withRealm(realm) - .withInfiniteReconnects() - .withReconnectInterval(5, TimeUnit.SECONDS); - // Create a client through the builder. This will not immediatly start - // a connection attempt - client = builder.build(); - } catch (Exception e) { - // Catch exceptions that will be thrown in case of invalid configuration - completable.onError(e); - return; - } + try { + // Create a builder and configure the client + WampClientBuilder builder = new WampClientBuilder(); + builder + .withConnectorProvider(connectorProvider) + .withUri(uri) + .withRealm(realm) + .withInfiniteReconnects() + .withReconnectInterval(5, TimeUnit.SECONDS); + // Create a client through the builder. This will not immediatly start + // a connection attempt + client = builder.build(); + } catch (Exception e) { + // Catch exceptions that will be thrown in case of invalid configuration + completable.onError(e); + return; + } - client.statusChanged().subscribe(state -> { - LOG.debug("State changed: {}", state); - if (state instanceof WampClient.DisconnectedState && connectedState instanceof WampClient.ConnectingState) { - if (((WampClient.DisconnectedState) state).disconnectReason() != null) { - completable.onError(((WampClient.DisconnectedState) state).disconnectReason()); - } else { - completable.onError(new IllegalStateException("Cannot connect to the exchange.")); + client + .statusChanged() + .subscribe( + state -> { + LOG.debug("State changed: {}", state); + if (state instanceof WampClient.DisconnectedState + && connectedState instanceof WampClient.ConnectingState) { + if (((WampClient.DisconnectedState) state).disconnectReason() != null) { + completable.onError( + ((WampClient.DisconnectedState) state).disconnectReason()); + } else { + completable.onError( + new IllegalStateException("Cannot connect to the exchange.")); + } } - } - connectedState = state; + connectedState = state; - if (state instanceof WampClient.ConnectedState) { - completable.onComplete(); - } - }); + if (state instanceof WampClient.ConnectedState) { + completable.onComplete(); + } + }); - client.open(); + client.open(); }); - } - - public Observable subscribeChannel(String channel) { - if (!(connectedState instanceof WampClient.ConnectedState)) { - return Observable.error(new NotConnectedException()); - } + } - return RxJavaInterop.toV2Observable(client.makeSubscription(channel)); + public Observable subscribeChannel(String channel) { + if (!(connectedState instanceof WampClient.ConnectedState)) { + return Observable.error(new NotConnectedException()); } - public boolean isSocketOpen() { - // WampClient was initiated with infinite reconnects attempts, so we can just always return 'true' - return !((BehaviorSubject) client.statusChanged()).hasCompleted(); - } + return RxJavaInterop.toV2Observable(client.makeSubscription(channel)); + } + + public boolean isSocketOpen() { + // WampClient was initiated with infinite reconnects attempts, so we can just always return + // 'true' + return !((BehaviorSubject) client.statusChanged()).hasCompleted(); + } - public void useCompressedMessages(boolean compressedMessages) { throw new UnsupportedOperationException(); } + public void useCompressedMessages(boolean compressedMessages) { + throw new UnsupportedOperationException(); + } }