From 04b3d105345e2727ce22f93c6376924ca0b4bf6c Mon Sep 17 00:00:00 2001 From: Foat Akhmadeev Date: Thu, 25 Oct 2018 21:40:32 +0100 Subject: [PATCH] Restore the Bitmex Execution reports code which was cut out of #191. This is has some issues with the latest codeset (in particular, it doesn't compile against XChange 4.3.11). It needs the attention of someone who actually uses BitMex. --- xchange-bitmex/bitmex.secret.keys.origin | 2 + xchange-bitmex/pom.xml | 46 ++- .../bitmex/BitmexStreamingExchange.java | 36 +- .../BitmexStreamingMarketDataService.java | 82 +++- .../bitmex/BitmexStreamingService.java | 134 ++++-- .../bitmex/BitmexTestStreamingExchange.java | 10 - .../bitmex/dto/BitmexExecution.java | 381 ++++++++++++++++++ .../BitmexWebSocketSubscriptionMessage.java | 4 +- .../dto/BitmexWebSocketTransaction.java | 10 +- .../bitmex/BitmexAuthenticatedExample.java | 67 +++ .../bitmex/BitmexDeadManSwitchTest.java | 91 +++++ .../bitmex/BitmexManualExample.java | 22 +- .../xchangestream/bitmex/BitmexOrderIT.java | 221 ++++++++++ .../bitmex/BitmexOrderReplaceTest.java | 115 ++++++ .../bitmex/BitmexStreamingTest.java | 30 ++ .../xchangestream/bitmex/BitmexTest.java | 102 +++++ .../bitmex/BitmexTestsCommons.java | 24 ++ .../bitmex/BitmexWithProxyIT.java | 67 +++ .../bitmex/dto/BitmexExecutionTest.java | 77 ++++ .../bitmex/dto/execution-spec.json | 72 ++++ .../xchangestream/bitmex/dto/execution.json | 50 +++ .../xchangestream/bitmex/dto/position.json | 20 + 22 files changed, 1591 insertions(+), 72 deletions(-) create mode 100644 xchange-bitmex/bitmex.secret.keys.origin delete mode 100644 xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexTestStreamingExchange.java create mode 100644 xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java create mode 100644 xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java create mode 100644 xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution-spec.json create mode 100644 xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution.json create mode 100644 xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/position.json diff --git a/xchange-bitmex/bitmex.secret.keys.origin b/xchange-bitmex/bitmex.secret.keys.origin new file mode 100644 index 000000000..87cee1347 --- /dev/null +++ b/xchange-bitmex/bitmex.secret.keys.origin @@ -0,0 +1,2 @@ +bitmex.api.key= +bitmex.secret.key= \ No newline at end of file diff --git a/xchange-bitmex/pom.xml b/xchange-bitmex/pom.xml index c3434c7de..fd4518545 100644 --- a/xchange-bitmex/pom.xml +++ b/xchange-bitmex/pom.xml @@ -1,5 +1,6 @@ - + xchange-stream-parent info.bitrich.xchange-stream @@ -9,6 +10,44 @@ xchange-bitmex + + + default + + true + + + + + maven-surefire-plugin + 2.18.1 + + + **/*IT.java + + + + + + + + integration-tests + + + + maven-surefire-plugin + 2.18.1 + + + **/*IT.java + + + + + + + + info.bitrich.xchange-stream @@ -25,5 +64,10 @@ xchange-bitmex ${xchange.version} + + org.apache.httpcomponents + fluent-hc + 4.5.5 + \ No newline at end of file diff --git a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java index c9cf0ab8b..d1abae223 100644 --- a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java +++ b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java @@ -4,30 +4,28 @@ import info.bitrich.xchangestream.core.StreamingExchange; import info.bitrich.xchangestream.core.StreamingMarketDataService; import io.reactivex.Completable; +import io.reactivex.Observable; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.bitmex.BitmexExchange; -import si.mazi.rescu.SynchronizedValueFactory; /** * 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 final BitmexStreamingService streamingService; + private BitmexStreamingService streamingService; private BitmexStreamingMarketDataService streamingMarketDataService; public BitmexStreamingExchange() { - this.streamingService = new BitmexStreamingService(API_URI); - } - - protected BitmexStreamingExchange(BitmexStreamingService streamingService) { - this.streamingService = streamingService; } @Override protected void initServices() { super.initServices(); + streamingService = createStreamingService(); + ExchangeSpecification exchangeSpecification = getExchangeSpecification(); streamingMarketDataService = new BitmexStreamingMarketDataService(streamingService); } @@ -36,14 +34,18 @@ public Completable connect(ProductSubscription... args) { return streamingService.connect(); } - @Override - public Completable disconnect() { - return streamingService.disconnect(); + 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 SynchronizedValueFactory getNonceFactory() { - return null; + public Completable disconnect() { + return streamingService.disconnect(); } @Override @@ -58,6 +60,16 @@ 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(); diff --git a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java index bf97ac90b..e4318fd9b 100644 --- a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java +++ b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingMarketDataService.java @@ -1,11 +1,14 @@ package info.bitrich.xchangestream.bitmex; -import info.bitrich.xchangestream.bitmex.dto.BitmexLimitOrder; -import info.bitrich.xchangestream.bitmex.dto.BitmexOrderbook; -import info.bitrich.xchangestream.bitmex.dto.BitmexTicker; -import info.bitrich.xchangestream.bitmex.dto.BitmexTrade; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import info.bitrich.xchangestream.bitmex.dto.*; import info.bitrich.xchangestream.core.StreamingMarketDataService; +import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; import io.reactivex.Observable; +import org.knowm.xchange.bitmex.BitmexContract; +import org.knowm.xchange.bitmex.BitmexPrompt; +import org.knowm.xchange.bitmex.BitmexUtils; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; @@ -13,10 +16,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; +import java.io.IOException; +import java.util.*; /** * Created by Lukas Zaoralek on 13.11.17. @@ -24,17 +25,34 @@ 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 SortedMap orderbooks = new TreeMap<>(); + private final SortedMap orderbooks = new TreeMap<>(); + public BitmexStreamingMarketDataService(BitmexStreamingService streamingService) { this.streamingService = streamingService; + this.streamingService.subscribeConnectionSuccess().subscribe(o -> { + LOG.info("Bitmex connection succeeded. Clearing orderbooks."); + orderbooks.clear(); + }); + } + + private String getBitmexSymbol(CurrencyPair currencyPair, Object... args) { + if (args.length > 0) { + BitmexPrompt prompt = (BitmexPrompt) args[0]; + BitmexContract contract = new BitmexContract(currencyPair, prompt); + return BitmexUtils.translateBitmexContract(contract); + } else { + return currencyPair.base.toString() + currencyPair.counter.toString(); + } } @Override public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { - String instrument = currencyPair.base.toString() + currencyPair.counter.toString(); + String instrument = getBitmexSymbol(currencyPair, args); String channelName = String.format("orderBookL2:%s", instrument); return streamingService.subscribeBitmexChannel(channelName).map(s -> { @@ -42,12 +60,12 @@ public Observable getOrderBook(CurrencyPair currencyPair, Object... a String action = s.getAction(); if (action.equals("partial")) { orderbook = s.toBitmexOrderbook(); - orderbooks.put(currencyPair, orderbook); + orderbooks.put(instrument, orderbook); } else { - orderbook = orderbooks.get(currencyPair); + orderbook = orderbooks.get(instrument); //ignore updates until first "partial" if (orderbook == null) { - return null; + return new OrderBook(null, Collections.emptyList(), Collections.emptyList()); } BitmexLimitOrder[] levels = s.toBitmexOrderbookLevels(); orderbook.updateLevels(levels, action); @@ -58,7 +76,7 @@ public Observable getOrderBook(CurrencyPair currencyPair, Object... a } public Observable getRawTicker(CurrencyPair currencyPair, Object... args) { - String instrument = currencyPair.base.toString() + currencyPair.counter.toString(); + String instrument = getBitmexSymbol(currencyPair, args); String channelName = String.format("quote:%s", instrument); return streamingService.subscribeBitmexChannel(channelName).map(s -> s.toBitmexTicker()); @@ -66,7 +84,7 @@ public Observable getRawTicker(CurrencyPair currencyPair, Object.. @Override public Observable getTicker(CurrencyPair currencyPair, Object... args) { - String instrument = currencyPair.base.toString() + currencyPair.counter.toString(); + String instrument = getBitmexSymbol(currencyPair, args); String channelName = String.format("quote:%s", instrument); return streamingService.subscribeBitmexChannel(channelName).map(s -> { @@ -77,7 +95,7 @@ public Observable getTicker(CurrencyPair currencyPair, Object... args) { @Override public Observable getTrades(CurrencyPair currencyPair, Object... args) { - String instrument = currencyPair.base.toString() + currencyPair.counter.toString(); + String instrument = getBitmexSymbol(currencyPair, args); String channelName = String.format("trade:%s", instrument); return streamingService.subscribeBitmexChannel(channelName).flatMapIterable(s -> { @@ -89,4 +107,36 @@ public Observable getTrades(CurrencyPair currencyPair, Object... args) { return trades; }); } + + + public Observable getExecutions(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(); + } } diff --git a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java index cafc16efa..7c7fcd388 100644 --- a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java +++ b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingService.java @@ -1,22 +1,25 @@ package info.bitrich.xchangestream.bitmex; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; -import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler; -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; - +import info.bitrich.xchangestream.bitmex.dto.BitmexMarketDataEvent; import info.bitrich.xchangestream.bitmex.dto.BitmexWebSocketSubscriptionMessage; import info.bitrich.xchangestream.bitmex.dto.BitmexWebSocketTransaction; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; import io.reactivex.Observable; +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.TimeZone; +import java.util.concurrent.TimeUnit; /** * Created by Lukas Zaoralek on 13.11.17. @@ -24,24 +27,63 @@ public class BitmexStreamingService extends JsonNettyStreamingService { private static final Logger LOG = LoggerFactory.getLogger(BitmexStreamingService.class); - public BitmexStreamingService(String apiUrl) { + + 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; } @Override protected void handleMessage(JsonNode message) { - 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("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 "); + } + return; + } + @Override protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { return null; @@ -55,9 +97,28 @@ public Observable subscribeBitmexChannel(String chan .share(); } + @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; + } + @Override protected String getChannelNameFromMessage(JsonNode message) throws IOException { - String instrument = message.get("data").get(0).get("symbol").asText(); + JsonNode data = message.get("data"); + String instrument = data.size() > 0 ? data.get(0).get("symbol").asText() : message.get("filter").get("symbol").asText(); String table = message.get("table").asText(); return String.format("%s:%s", table, instrument); } @@ -70,7 +131,34 @@ public String getSubscribeMessage(String channelName, Object... args) throws IOE @Override public String getUnsubscribeMessage(String channelName) throws IOException { - BitmexWebSocketSubscriptionMessage subscribeMessage = new BitmexWebSocketSubscriptionMessage("unsubscribe", new String[]{}); + 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[]{DMS_CANCEL_ALL_IN}); + String message = objectMapper.writeValueAsString(subscriptionMessage); + dmsDisposable = Schedulers.single().schedulePeriodicallyDirect(new Runnable() { + @Override + public void run() { + sendMessage(message); + } + }, 0, DMS_RESUBSCRIBE, 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; + } + } diff --git a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexTestStreamingExchange.java b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexTestStreamingExchange.java deleted file mode 100644 index dca595756..000000000 --- a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexTestStreamingExchange.java +++ /dev/null @@ -1,10 +0,0 @@ -package info.bitrich.xchangestream.bitmex; - -public class BitmexTestStreamingExchange extends BitmexStreamingExchange { - - private static final String API_URI = "wss://testnet.bitmex.com/realtime"; - - public BitmexTestStreamingExchange() { - super(new BitmexStreamingService(API_URI)); - } -} diff --git a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java new file mode 100644 index 000000000..79ff0c182 --- /dev/null +++ b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecution.java @@ -0,0 +1,381 @@ +package info.bitrich.xchangestream.bitmex.dto; + +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; + +/** + * @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 + + '}'; + } +} diff --git a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java index e19c5da60..6d82b4486 100644 --- a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java +++ b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketSubscriptionMessage.java @@ -13,9 +13,9 @@ public class BitmexWebSocketSubscriptionMessage { private String op; @JsonProperty(ARGS) - private String[] args; + private Object[] args; - public BitmexWebSocketSubscriptionMessage(String op, String[] args) { + public BitmexWebSocketSubscriptionMessage(String op, Object[] args) { this.op = op; this.args = args; } diff --git a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java index 210789609..20fbe0cca 100644 --- a/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java +++ b/xchange-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/dto/BitmexWebSocketTransaction.java @@ -4,6 +4,7 @@ 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; @@ -11,10 +12,10 @@ * 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; - private final ObjectMapper mapper = new ObjectMapper(); public BitmexWebSocketTransaction(@JsonProperty("table") String table, @JsonProperty("action") String action, @@ -22,7 +23,6 @@ public BitmexWebSocketTransaction(@JsonProperty("table") String table, this.table = table; this.action = action; this.data = data; - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } public BitmexLimitOrder[] toBitmexOrderbookLevels() { @@ -30,7 +30,7 @@ public BitmexLimitOrder[] toBitmexOrderbookLevels() { for (int i = 0; i < data.size(); i++) { JsonNode jsonLevel = data.get(i); try { - levels[i] = mapper.readValue(jsonLevel.toString(), BitmexLimitOrder.class); + levels[i] = mapper.treeToValue(jsonLevel, BitmexLimitOrder.class); } catch (IOException e) { e.printStackTrace(); } @@ -47,7 +47,7 @@ public BitmexOrderbook toBitmexOrderbook() { public BitmexTicker toBitmexTicker() { BitmexTicker bitmexTicker = null; try { - bitmexTicker = mapper.readValue(data.get(0).toString(), BitmexTicker.class); + bitmexTicker = mapper.treeToValue(data.get(0), BitmexTicker.class); } catch (IOException e) { e.printStackTrace(); } @@ -59,7 +59,7 @@ public BitmexTrade[] toBitmexTrades() { for (int i = 0; i < data.size(); i++) { JsonNode jsonTrade = data.get(i); try { - trades[i] = mapper.readValue(jsonTrade.toString(), BitmexTrade.class); + trades[i] = mapper.treeToValue(jsonTrade, BitmexTrade.class); } catch (IOException e) { e.printStackTrace(); } diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java new file mode 100644 index 000000000..608923ac4 --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexAuthenticatedExample.java @@ -0,0 +1,67 @@ +package info.bitrich.xchangestream.bitmex; + +import info.bitrich.xchangestream.bitmex.dto.BitmexExecution; +import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import io.reactivex.Observable; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.utils.CertHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by Lukas Zaoralek on 13.11.17. + */ +public class BitmexAuthenticatedExample { + + 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); + + 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(); + 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.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.getExecutions("XBTUSD").subscribe(bitmexExecution -> { + LOG.info("bitmexExecution = {}", bitmexExecution); + }); + try { + Thread.sleep(100_000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java new file mode 100644 index 000000000..74b5816b9 --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexDeadManSwitchTest.java @@ -0,0 +1,91 @@ +package info.bitrich.xchangestream.bitmex; + +import info.bitrich.xchangestream.core.StreamingExchange; +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.BitmexSide; +import org.knowm.xchange.bitmex.service.BitmexMarketDataService; +import org.knowm.xchange.bitmex.service.BitmexTradeService; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.utils.CertHelper; +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. + */ +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 = CurrencyPair.XBT_USD; + + streamingMarketDataService.getExecutions("XBTUSD").subscribe(bitmexExecution -> { + logger.info("!!!!EXECUTION!!!! = {}", bitmexExecution); + }); + + OrderBook orderBook = marketDataService.getOrderBook(CurrencyPair.XBT_USD, PERPETUAL); + // 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")); + BitmexPrivateOrder xbtusd = tradeService.placeLimitOrder("XBTUSD", originalOrderSize, price, BitmexSide.SELL, nosOrdId, null); + logger.info("!!!!!PRIVATE_ORDER!!!! {}",xbtusd); + Thread.sleep(100000); + System.out.println(); + System.out.println(); + + + exchange.disconnect(); + } +} diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java index 68b7fd702..530dd68f3 100644 --- a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexManualExample.java @@ -1,8 +1,8 @@ package info.bitrich.xchangestream.bitmex; -import info.bitrich.xchangestream.core.ProductSubscription; 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; @@ -22,8 +22,10 @@ public static void main(String[] args) { CurrencyPair xbtUsd = CurrencyPair.XBT_USD; streamingMarketDataService.getOrderBook(xbtUsd).subscribe(orderBook -> { - LOG.info("First ask: {}", orderBook.getAsks().get(0)); - LOG.info("First bid: {}", orderBook.getBids().get(0)); + 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 -> { @@ -38,6 +40,20 @@ public static void main(String[] args) { .subscribe(trade -> LOG.info("TRADE: {}", trade), throwable -> LOG.error("ERROR in getting trades: ", throwable)); + // Quarterly Contract + streamingMarketDataService.getOrderBook(xbtUsd, BitmexPrompt.QUARTERLY).subscribe(orderBook -> { + LOG.info("Quarterly Contract First ask: {}", orderBook.getAsks().get(0)); + LOG.info("Quarterly Contract First bid: {}", orderBook.getBids().get(0)); + }, throwable -> LOG.error("ERROR in getting Quarterly Contract order book: ", throwable)); + + streamingMarketDataService.getTicker(xbtUsd, BitmexPrompt.QUARTERLY).subscribe(ticker -> { + LOG.info("Quarterly Contract TICKER: {}", ticker); + }, throwable -> LOG.error("ERROR in getting Quarterly Contract ticker: ", throwable)); + + exchange.getStreamingMarketDataService().getTrades(xbtUsd, BitmexPrompt.QUARTERLY) + .subscribe(trade -> LOG.info("Quarterly Contract TRADE: {}", trade), + throwable -> LOG.error("ERROR in getting Quarterly Contract trades: ", throwable)); + try { Thread.sleep(100000); } catch (InterruptedException e) { diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java new file mode 100644 index 000000000..b76b6a73f --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderIT.java @@ -0,0 +1,221 @@ +package info.bitrich.xchangestream.bitmex; + +import info.bitrich.xchangestream.bitmex.dto.BitmexExecution; +import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import info.bitrich.xchangestream.util.LocalExchangeConfig; +import info.bitrich.xchangestream.util.PropsLoader; +import io.reactivex.Observable; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.knowm.xchange.bitmex.dto.marketdata.BitmexPrivateOrder; +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; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.trade.LimitOrder; +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. + */ +public class BitmexOrderIT { + private static final CurrencyPair xbtUsd = CurrencyPair.XBT_USD; + private static final Logger LOG = LoggerFactory.getLogger(BitmexTest.class); + + private static final BigDecimal priceShift = new BigDecimal("50"); + + private BigDecimal testAskPrice; + private BigDecimal testBidPrice; + + private BitmexTradeService tradeService; + private StreamingExchange exchange; + + @Before + public void setup() throws IOException { + LocalExchangeConfig localConfig = PropsLoader.loadKeys( + "bitmex.secret.keys", "bitmex.secret.keys.origin", "bitmex"); + exchange = StreamingExchangeFactory.INSTANCE.createExchange(BitmexStreamingExchange.class.getName()); + + exchange.applySpecification(BitmexTestsCommons.getExchangeSpecification(localConfig, + exchange.getDefaultExchangeSpecification())); + exchange.connect().blockingAwait(); + + BitmexMarketDataService marketDataService = + (BitmexMarketDataService) exchange.getMarketDataService(); + + OrderBook orderBook = marketDataService.getOrderBook(xbtUsd, PERPETUAL); + 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); + + if (topPriceAsk != null) { + testAskPrice = topPriceAsk.add(priceShift); + testBidPrice = topPriceAsk.subtract(priceShift); + } else { + testAskPrice = topPriceBid.add(priceShift); + testBidPrice = topPriceBid.subtract(priceShift); + } + + 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); + } + + @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"; + BitmexPrivateOrder bitmexPrivateOrder = + tradeService.replaceLimitOrder( + "XBTUSD", + new BigDecimal("5"), + null, + orderId, + replaceId, + clOrdId); + 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); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + }, 1, TimeUnit.SECONDS); + + Observable executionObservable = ((BitmexStreamingMarketDataService) + exchange.getStreamingMarketDataService()).getExecutions("XBTUSD"); + executionObservable.test() + .awaitCount(5) + .assertNever(execution -> Objects.equals(execution.getClOrdID(), clOrdId)) + .dispose(); + + scheduler.shutdown(); + } +} \ No newline at end of file diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java new file mode 100644 index 000000000..8d0e8367b --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexOrderReplaceTest.java @@ -0,0 +1,115 @@ +package info.bitrich.xchangestream.bitmex; + +import info.bitrich.xchangestream.core.StreamingExchange; +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.BitmexSide; +import org.knowm.xchange.bitmex.service.BitmexMarketDataService; +import org.knowm.xchange.bitmex.service.BitmexTradeService; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.utils.CertHelper; +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. + */ +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 = CurrencyPair.XBT_USD; + + streamingMarketDataService.getExecutions("XBTUSD").subscribe(bitmexExecution -> { + logger.info("!!!!EXECUTION!!!! = {}", bitmexExecution); + }); + OrderBook orderBook = marketDataService.getOrderBook(CurrencyPair.XBT_USD, PERPETUAL); + // 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")); + BitmexPrivateOrder xbtusd = tradeService.placeLimitOrder("XBTUSD", originalOrderSize, price, BitmexSide.SELL, nosOrdId, null); + logger.info("!!!!!PRIVATE_ORDER!!!! {}",xbtusd); + Thread.sleep(5000); + System.out.println(); + System.out.println(); + System.out.println(); + + + logger.info("Replacing"); + String replacedOrderId = nosOrdId + "replace"; + BitmexPrivateOrder replaceBPO = tradeService.replaceLimitOrder("XBTUSD", originalOrderSize.divide(BigDecimal.valueOf(2)), price, null, replacedOrderId, nosOrdId); + 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-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java new file mode 100644 index 000000000..e8ac86e8f --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexStreamingTest.java @@ -0,0 +1,30 @@ +package info.bitrich.xchangestream.bitmex; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +/** + * @author Foat Akhmadeev + * 13/06/2018 + */ +public class BitmexStreamingTest { + @Test + public void shouldGetCorrectSubscribeMessage() throws IOException { + BitmexStreamingService service = + new BitmexStreamingService("url", "api", "secret"); + + Assert.assertEquals("{\"op\":\"subscribe\",\"args\":[\"name\"]}", + service.getSubscribeMessage("name")); + } + + @Test + public void shouldGetCorrectUnsubscribeMessage() throws IOException { + BitmexStreamingService service = + new BitmexStreamingService("url", "api", "secret"); + + Assert.assertEquals("{\"op\":\"unsubscribe\",\"args\":[\"name\"]}", + service.getUnsubscribeMessage("name")); + } +} diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java new file mode 100644 index 000000000..ef8274ee5 --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTest.java @@ -0,0 +1,102 @@ +package info.bitrich.xchangestream.bitmex; + +import info.bitrich.xchangestream.bitmex.dto.BitmexTicker; +import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import info.bitrich.xchangestream.util.BookSanityChecker; +import io.reactivex.Completable; +import io.reactivex.Observable; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +/** + * @author Foat Akhmadeev + * 31/05/2018 + */ +public class BitmexTest { + 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 StreamingExchange exchange; + private BitmexStreamingMarketDataService streamingMarketDataService; + + @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()); + } + + 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(); + } + + @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 shouldReceiveTickers() { + Observable tickerObservable = streamingMarketDataService.getTicker(xbtUsd); + awaitDataCount(tickerObservable); + } + + @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(); + } +} diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java new file mode 100644 index 000000000..3bd1f0fd0 --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexTestsCommons.java @@ -0,0 +1,24 @@ +package info.bitrich.xchangestream.bitmex; + +import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.util.LocalExchangeConfig; +import org.knowm.xchange.ExchangeSpecification; + +/** + * @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; + } + +} diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java new file mode 100644 index 000000000..c0b75f727 --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/BitmexWithProxyIT.java @@ -0,0 +1,67 @@ +package info.bitrich.xchangestream.bitmex; + +import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import info.bitrich.xchangestream.util.LocalExchangeConfig; +import info.bitrich.xchangestream.util.PropsLoader; +import info.bitrich.xchangestream.util.ProxyUtil; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.knowm.xchange.ExchangeSpecification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Foat Akhmadeev + * 18/06/2018 + */ +public class BitmexWithProxyIT { + private static final Logger LOG = LoggerFactory.getLogger(BitmexWithProxyIT.class); + + 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()); + + 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(); + } + + @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); + }); + + proxyUtil.stopProxy(); + + Thread.sleep(5000); + Assert.assertFalse(exchange.isAlive()); + proxyUtil.startProxy(); + + Thread.sleep(15000); + Assert.assertTrue(exchange.isAlive()); + } +} diff --git a/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java new file mode 100644 index 000000000..248d7da27 --- /dev/null +++ b/xchange-bitmex/src/test/java/info/bitrich/xchangestream/bitmex/dto/BitmexExecutionTest.java @@ -0,0 +1,77 @@ +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 java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +import static org.junit.Assert.assertEquals; + +/** + * @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); + + 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-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution-spec.json b/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution-spec.json new file mode 100644 index 000000000..ec893d431 --- /dev/null +++ b/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution-spec.json @@ -0,0 +1,72 @@ +{ + "table": "execution", + "action": "partial", + "keys": [ + "execID" + ], + "types": { + "execID": "guid", + "orderID": "guid", + "clOrdID": "symbol", + "clOrdLinkID": "symbol", + "account": "long", + "symbol": "symbol", + "side": "symbol", + "lastQty": "long", + "lastPx": "float", + "underlyingLastPx": "float", + "lastMkt": "symbol", + "lastLiquidityInd": "symbol", + "simpleOrderQty": "float", + "orderQty": "long", + "price": "float", + "displayQty": "long", + "stopPx": "float", + "pegOffsetValue": "float", + "pegPriceType": "symbol", + "currency": "symbol", + "settlCurrency": "symbol", + "execType": "symbol", + "ordType": "symbol", + "timeInForce": "symbol", + "execInst": "symbol", + "contingencyType": "symbol", + "exDestination": "symbol", + "ordStatus": "symbol", + "triggered": "symbol", + "workingIndicator": "boolean", + "ordRejReason": "symbol", + "simpleLeavesQty": "float", + "leavesQty": "long", + "simpleCumQty": "float", + "cumQty": "long", + "avgPx": "float", + "commission": "float", + "tradePublishIndicator": "symbol", + "multiLegReportingType": "symbol", + "text": "symbol", + "trdMatchID": "guid", + "execCost": "long", + "execComm": "long", + "homeNotional": "float", + "foreignNotional": "float", + "transactTime": "timestamp", + "timestamp": "timestamp" + }, + "foreignKeys": { + "symbol": "instrument", + "side": "side", + "ordStatus": "ordStatus" + }, + "attributes": { + "execID": "grouped", + "account": "grouped", + "execType": "grouped", + "transactTime": "sorted" + }, + "filter": { + "account": 75430, + "symbol": "XBTUSD" + }, + "data": [] +} diff --git a/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution.json b/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution.json new file mode 100644 index 000000000..c9c571cdd --- /dev/null +++ b/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/execution.json @@ -0,0 +1,50 @@ +{ + "execID": "b47dfbd1-3b88-5678-f6d6-b9314a96c3b8", + "orderID": "5f6c16df-4706-4548-f47d-25f2f915f149", + "clOrdID": "1528259635504", + "clOrdLinkID": "clOrdLinkIDclOrdLinkID", + "account": 75430, + "symbol": "XBTUSD", + "side": "Sell", + "lastQty": 30, + "lastPx": 7622.5, + "underlyingLastPx": null, + "lastMkt": "XBME", + "lastLiquidityInd": "AddedLiquidity", + "simpleOrderQty": "3030", + "orderQty": 30, + "price": 7622.5, + "displayQty": 2, + "stopPx": 7622.1, + "pegOffsetValue": null, + "pegPriceType": "", + "currency": "USD", + "settlCurrency": "XBt", + "execType": "Trade", + "ordType": "Limit", + "timeInForce": "GoodTillCancel", + "execInst": "", + "contingencyType": "", + "exDestination": "XBME", + "ordStatus": "Filled", + "triggered": "", + "workingIndicator": false, + "ordRejReason": "", + "simpleLeavesQty": 10, + "leavesQty": 11, + "simpleCumQty": 0.0039357, + "cumQty": 30, + "avgPx": 7622.5, + "commission": -0.00025, + "tradePublishIndicator": "PublishTrade", + "multiLegReportingType": "SingleSecurity", + "text": "Submitted via API.", + "trdMatchID": "11bae57a-3a11-83bc-3b71-0e472b89156f", + "execCost": 393570, + "execComm": -98, + "homeNotional": -0.0039357, + "foreignNotional": 30, + "transactTime": "2018-06-06T04:35:04.763Z", + "timestamp": "2018-06-06T04:35:04.763Z" +} + diff --git a/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/position.json b/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/position.json new file mode 100644 index 000000000..428d3ed8f --- /dev/null +++ b/xchange-bitmex/src/test/resources/info/bitrich/xchangestream/bitmex/dto/position.json @@ -0,0 +1,20 @@ +{ + "account": 2, + "symbol": "XBTUSD", + "currency": "XBt", + "currentTimestamp": "2017-04-04T22:07:42.442Z", + "currentQty": 1, + "markPrice": 1136.88, + "markValue": -87960, + "riskValue": 87960, + "homeNotional": 0.0008796, + "posState": "Liquidation", + "maintMargin": 263, + "unrealisedGrossPnl": -677, + "unrealisedPnl": -677, + "unrealisedPnlPcnt": -0.0078, + "unrealisedRoePcnt": -0.7756, + "simpleQty": 0.001, + "liquidationPrice": 1140.1, + "timestamp": "2017-04-04T22:07:45.442Z" +}