From f5a76c9d9a0272647e35e5daa1383c209ad80fc5 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Mon, 2 May 2022 12:45:04 +0300 Subject: [PATCH 1/2] Binance: margin trading --- .../xchange/binance/BinanceAdapters.java | 7 +- .../xchange/binance/BinanceAuthenticated.java | 136 +++++++++++++- .../dto/trade/BinanceCancelledOrder.java | 2 +- .../binance/dto/trade/BinanceNewOrder.java | 2 +- .../binance/dto/trade/BinanceOrder.java | 2 +- .../margin/BinanceCancelledMarginOrder.java | 27 +++ .../dto/trade/margin/BinanceMarginOrder.java | 31 ++++ .../trade/margin/BinanceNewMarginOrder.java | 36 ++++ .../dto/trade/margin/MarginAccountType.java | 29 +++ .../trade/margin/MarginSideEffectType.java | 19 ++ .../service/BinanceCancelOrderParams.java | 10 + .../service/BinanceQueryOrderParams.java | 10 + .../binance/service/BinanceTradeService.java | 172 +++++++++++------- .../service/BinanceTradeServiceRaw.java | 107 ++++++++++- .../java/org/knowm/xchange/dto/Order.java | 10 +- 15 files changed, 507 insertions(+), 93 deletions(-) create mode 100644 xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceCancelledMarginOrder.java create mode 100644 xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceMarginOrder.java create mode 100644 xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceNewMarginOrder.java create mode 100644 xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginAccountType.java create mode 100644 xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginSideEffectType.java diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java index 626b455710d..76c0b793ed5 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java @@ -97,7 +97,12 @@ public static CurrencyPair convert(String symbol) { public static long id(String id) { try { - return Long.valueOf(id); + // hyphen could optionally be added to order id to specify margin account type + int hyphenIndex = id.indexOf('-'); + if (hyphenIndex > 0) + id = id.substring(0, hyphenIndex); + + return Long.parseLong(id); } catch (Throwable e) { throw new IllegalArgumentException("Binance id must be a valid long number.", e); } diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java index a2e1111053e..030f59a7dd5 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java @@ -25,15 +25,8 @@ import org.knowm.xchange.binance.dto.account.TransferHistory; import org.knowm.xchange.binance.dto.account.TransferSubUserHistory; import org.knowm.xchange.binance.dto.account.WithdrawResponse; -import org.knowm.xchange.binance.dto.trade.BinanceCancelledOrder; -import org.knowm.xchange.binance.dto.trade.BinanceDustLog; -import org.knowm.xchange.binance.dto.trade.BinanceListenKey; -import org.knowm.xchange.binance.dto.trade.BinanceNewOrder; -import org.knowm.xchange.binance.dto.trade.BinanceOrder; -import org.knowm.xchange.binance.dto.trade.BinanceTrade; -import org.knowm.xchange.binance.dto.trade.OrderSide; -import org.knowm.xchange.binance.dto.trade.OrderType; -import org.knowm.xchange.binance.dto.trade.TimeInForce; +import org.knowm.xchange.binance.dto.trade.*; +import org.knowm.xchange.binance.dto.trade.margin.*; import si.mazi.rescu.ParamsDigest; import si.mazi.rescu.SynchronizedValueFactory; @@ -543,4 +536,129 @@ BinanceListenKey startUserDataStream(@HeaderParam(X_MBX_APIKEY) String apiKey) Map closeUserDataStream( @HeaderParam(X_MBX_APIKEY) String apiKey, @PathParam("listenKey") String listenKey) throws IOException, BinanceException; + + /** + * Send in a new margin order + * + * @param symbol + * @param marginAccountType + * @param side + * @param type + * @param timeInForce + * @param quantity + * @param price optional, must be provided for limit orders only + * @param newClientOrderId optional, a unique id for the order. Automatically generated if not + * sent. + * @param stopPrice optional, used with stop orders + * @param icebergQty optional, used with iceberg orders + * @param newOrderRespType optional, MARKET and LIMIT order types default to FULL, all other + * orders default to ACK + * @param recvWindow optional + * @param timestamp + * @return + * @throws IOException + * @throws BinanceException + */ + @POST + @Path("sapi/v1/margin/order") + BinanceNewMarginOrder newMarginOrder( + @FormParam("symbol") String symbol, + @FormParam("isIsolated") MarginAccountType marginAccountType, + @FormParam("side") OrderSide side, + @FormParam("type") OrderType type, + @FormParam("timeInForce") TimeInForce timeInForce, + @FormParam("quantity") BigDecimal quantity, + @FormParam("price") BigDecimal price, + @FormParam("newClientOrderId") String newClientOrderId, + @FormParam("stopPrice") BigDecimal stopPrice, + @FormParam("icebergQty") BigDecimal icebergQty, + @FormParam("newOrderRespType") BinanceNewOrder.NewOrderResponseType newOrderRespType, + @FormParam("sideEffectType") MarginSideEffectType sideEffectType, + @FormParam("recvWindow") Long recvWindow, + @FormParam("timestamp") SynchronizedValueFactory timestamp, + @HeaderParam(X_MBX_APIKEY) String apiKey, + @QueryParam(SIGNATURE) ParamsDigest signature) + throws IOException, BinanceException; + + /** + * Check a margin order's status.
+ * Either orderId or origClientOrderId must be sent. + * + * @param symbol + * @param marginAccountType + * @param orderId optional + * @param origClientOrderId optional + * @param recvWindow optional + * @param timestamp + * @param apiKey + * @param signature + * @return + * @throws IOException + * @throws BinanceException + */ + @GET + @Path("sapi/v1/margin/order") + BinanceMarginOrder marginOrderStatus( + @QueryParam("symbol") String symbol, + @QueryParam("isIsolated") MarginAccountType marginAccountType, + @QueryParam("orderId") long orderId, + @QueryParam("origClientOrderId") String origClientOrderId, + @QueryParam("recvWindow") Long recvWindow, + @QueryParam("timestamp") SynchronizedValueFactory timestamp, + @HeaderParam(X_MBX_APIKEY) String apiKey, + @QueryParam(SIGNATURE) ParamsDigest signature) + throws IOException, BinanceException; + + /** + * Cancel an active margin order. + * + * @param symbol + * @param marginAccountType + * @param orderId optional + * @param origClientOrderId optional + * @param newClientOrderId optional, used to uniquely identify this cancel. Automatically + * generated by default. + * @param recvWindow optional + * @param timestamp + * @param apiKey + * @param signature + * @return + * @throws IOException + * @throws BinanceException + */ + @DELETE + @Path("sapi/v1/margin/order") + BinanceCancelledMarginOrder cancelMarginOrder( + @QueryParam("symbol") String symbol, + @QueryParam("isIsolated") MarginAccountType marginAccountType, + @QueryParam("orderId") long orderId, + @QueryParam("origClientOrderId") String origClientOrderId, + @QueryParam("newClientOrderId") String newClientOrderId, + @QueryParam("recvWindow") Long recvWindow, + @QueryParam("timestamp") SynchronizedValueFactory timestamp, + @HeaderParam(X_MBX_APIKEY) String apiKey, + @QueryParam(SIGNATURE) ParamsDigest signature) + throws IOException, BinanceException; + + /** + * Cancels all active margin orders on a symbol. This includes OCO orders. + * + * @param symbol + * @param marginAccountType + * @param recvWindow optional + * @param timestamp + * @return + * @throws IOException + * @throws BinanceException + */ + @DELETE + @Path("sapi/v1/margin/openOrders") + List cancelAllOpenMarginOrders( + @QueryParam("symbol") String symbol, + @QueryParam("isIsolated") MarginAccountType marginAccountType, + @QueryParam("recvWindow") Long recvWindow, + @QueryParam("timestamp") SynchronizedValueFactory timestamp, + @HeaderParam(X_MBX_APIKEY) String apiKey, + @QueryParam(SIGNATURE) ParamsDigest signature) + throws IOException, BinanceException; } diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceCancelledOrder.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceCancelledOrder.java index c16c27ec30a..e3ca3783c65 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceCancelledOrder.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceCancelledOrder.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public final class BinanceCancelledOrder { +public class BinanceCancelledOrder { public final String symbol; public final String origClientOrderId; diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceNewOrder.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceNewOrder.java index d24835e1b11..31396d95531 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceNewOrder.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceNewOrder.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; import java.util.List; -public final class BinanceNewOrder { +public class BinanceNewOrder { /** Desired response type for BinanceNewOrder. */ public enum NewOrderResponseType { diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceOrder.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceOrder.java index da09376d171..e8afadf48a6 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceOrder.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/BinanceOrder.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; import java.util.Date; -public final class BinanceOrder { +public class BinanceOrder { public final String symbol; public final long orderId; diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceCancelledMarginOrder.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceCancelledMarginOrder.java new file mode 100644 index 00000000000..3d2de4a5573 --- /dev/null +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceCancelledMarginOrder.java @@ -0,0 +1,27 @@ +package org.knowm.xchange.binance.dto.trade.margin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.knowm.xchange.binance.dto.trade.BinanceCancelledOrder; + +public class BinanceCancelledMarginOrder extends BinanceCancelledOrder { + public final MarginAccountType marginAccountType; + + public BinanceCancelledMarginOrder( + @JsonProperty("symbol") String symbol, + @JsonProperty("isIsolated") MarginAccountType marginAccountType, + @JsonProperty("origClientOrderId") String origClientOrderId, + @JsonProperty("orderId") long orderId, + @JsonProperty("clientOrderId") String clientOrderId, + @JsonProperty("price") String price, + @JsonProperty("origQty") String origQty, + @JsonProperty("executedQty") String executedQty, + @JsonProperty("cummulativeQuoteQty") String cummulativeQuoteQty, + @JsonProperty("status") String status, + @JsonProperty("timeInForce") String timeInForce, + @JsonProperty("type") String type, + @JsonProperty("side") String side) { + super(symbol, origClientOrderId, orderId, clientOrderId, price, origQty, executedQty, cummulativeQuoteQty, status, timeInForce, type, side); + + this.marginAccountType = marginAccountType; + } +} diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceMarginOrder.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceMarginOrder.java new file mode 100644 index 00000000000..06bf2b58a0b --- /dev/null +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceMarginOrder.java @@ -0,0 +1,31 @@ +package org.knowm.xchange.binance.dto.trade.margin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.knowm.xchange.binance.dto.trade.*; + +import java.math.BigDecimal; + +public class BinanceMarginOrder extends BinanceOrder { + public final MarginAccountType marginAccountType; + + public BinanceMarginOrder( + @JsonProperty("symbol") String symbol, + @JsonProperty("orderId") long orderId, + @JsonProperty("clientOrderId") String clientOrderId, + @JsonProperty("price") BigDecimal price, + @JsonProperty("origQty") BigDecimal origQty, + @JsonProperty("executedQty") BigDecimal executedQty, + @JsonProperty("cummulativeQuoteQty") BigDecimal cummulativeQuoteQty, + @JsonProperty("status") OrderStatus status, + @JsonProperty("timeInForce") TimeInForce timeInForce, + @JsonProperty("type") OrderType type, + @JsonProperty("side") OrderSide side, + @JsonProperty("stopPrice") BigDecimal stopPrice, + @JsonProperty("icebergQty") BigDecimal icebergQty, + @JsonProperty("isIsolated") MarginAccountType marginAccountType, + @JsonProperty("time") long time) { + super(symbol, orderId, clientOrderId, price, origQty, executedQty, cummulativeQuoteQty, status, timeInForce, type, side, stopPrice, icebergQty, time); + + this.marginAccountType = marginAccountType; + } +} diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceNewMarginOrder.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceNewMarginOrder.java new file mode 100644 index 00000000000..8914962fd9f --- /dev/null +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/BinanceNewMarginOrder.java @@ -0,0 +1,36 @@ +package org.knowm.xchange.binance.dto.trade.margin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.knowm.xchange.binance.dto.trade.*; + +import java.math.BigDecimal; +import java.util.List; + +public final class BinanceNewMarginOrder extends BinanceNewOrder { + + public final BigDecimal marginBuyBorrowAmount; + public final String marginBuyBorrowAsset; + public final MarginAccountType marginAccountType; + + public BinanceNewMarginOrder( + @JsonProperty("symbol") String symbol, + @JsonProperty("orderId") long orderId, + @JsonProperty("clientOrderId") String clientOrderId, + @JsonProperty("transactTime") long transactTime, + @JsonProperty("price") BigDecimal price, + @JsonProperty("origQty") BigDecimal origQty, + @JsonProperty("executedQty") BigDecimal executedQty, + @JsonProperty("status") OrderStatus status, + @JsonProperty("timeInForce") TimeInForce timeInForce, + @JsonProperty("type") OrderType type, + @JsonProperty("side") OrderSide side, + @JsonProperty("fills") List fills, + @JsonProperty("marginBuyBorrowAmount") BigDecimal marginBuyBorrowAmount, + @JsonProperty("marginBuyBorrowAsset") String marginBuyBorrowAsset, + @JsonProperty("isIsolated") MarginAccountType marginAccountType) { + super(symbol, orderId, clientOrderId, transactTime, price, origQty, executedQty, status, timeInForce, type, side, fills); + this.marginBuyBorrowAmount = marginBuyBorrowAmount; + this.marginBuyBorrowAsset = marginBuyBorrowAsset; + this.marginAccountType = marginAccountType; + } +} diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginAccountType.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginAccountType.java new file mode 100644 index 00000000000..fc29fb25b15 --- /dev/null +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginAccountType.java @@ -0,0 +1,29 @@ +package org.knowm.xchange.binance.dto.trade.margin; + +import com.fasterxml.jackson.annotation.JsonCreator; +import org.knowm.xchange.dto.Order; + +public enum MarginAccountType implements Order.IOrderFlags { + ISOLATED("TRUE"), + CROSS("FALSE"); + + private final String isIsolated; + + MarginAccountType(String isIsolated) { + this.isIsolated = isIsolated; + } + + @Override + public String toString() { + return isIsolated; + } + + @JsonCreator + public static MarginAccountType getAccountType(String s) { + try { + return Boolean.parseBoolean(s) ? ISOLATED : CROSS; + } catch (Exception e) { + throw new RuntimeException("Unknown account type " + s + "."); + } + } +} diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginSideEffectType.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginSideEffectType.java new file mode 100644 index 00000000000..d1836c1ef2f --- /dev/null +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/dto/trade/margin/MarginSideEffectType.java @@ -0,0 +1,19 @@ +package org.knowm.xchange.binance.dto.trade.margin; + +import com.fasterxml.jackson.annotation.JsonCreator; +import org.knowm.xchange.dto.Order; + +public enum MarginSideEffectType implements Order.IOrderFlags { + NO_SIDE_EFFECT, + MARGIN_BUY, + AUTO_REPAY; + + @JsonCreator + public static MarginSideEffectType getSideEffectType(String s) { + try { + return MarginSideEffectType.valueOf(s); + } catch (Exception e) { + throw new RuntimeException("Unknown side effect type " + s + "."); + } + } +} diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceCancelOrderParams.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceCancelOrderParams.java index 2d95ac5e43a..c14c9690fe9 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceCancelOrderParams.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceCancelOrderParams.java @@ -1,5 +1,6 @@ package org.knowm.xchange.binance.service; +import org.knowm.xchange.binance.dto.trade.margin.MarginAccountType; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.service.trade.params.CancelOrderByCurrencyPair; import org.knowm.xchange.service.trade.params.CancelOrderByIdParams; @@ -7,6 +8,7 @@ public class BinanceCancelOrderParams implements CancelOrderByIdParams, CancelOrderByCurrencyPair { private final String orderId; private final CurrencyPair pair; + private MarginAccountType marginAccountType; public BinanceCancelOrderParams(CurrencyPair pair, String orderId) { this.pair = pair; @@ -22,4 +24,12 @@ public CurrencyPair getCurrencyPair() { public String getOrderId() { return orderId; } + + public MarginAccountType getMarginAccountType() { + return marginAccountType; + } + + public void setMarginAccountType(MarginAccountType marginAccountType) { + this.marginAccountType = marginAccountType; + } } diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceQueryOrderParams.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceQueryOrderParams.java index b01d2149ac8..9d6adf08b2d 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceQueryOrderParams.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceQueryOrderParams.java @@ -1,11 +1,13 @@ package org.knowm.xchange.binance.service; +import org.knowm.xchange.binance.dto.trade.margin.MarginAccountType; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.service.trade.params.orders.OrderQueryParamCurrencyPair; public class BinanceQueryOrderParams implements OrderQueryParamCurrencyPair { private String orderId; private CurrencyPair pair; + private MarginAccountType marginAccountType; public BinanceQueryOrderParams() {} @@ -33,4 +35,12 @@ public String getOrderId() { public void setOrderId(String orderId) { this.orderId = orderId; } + + public MarginAccountType getMarginAccountType() { + return marginAccountType; + } + + public void setMarginAccountType(MarginAccountType marginAccountType) { + this.marginAccountType = marginAccountType; + } } diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java index 8987a74ac35..4d1effd8282 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java @@ -2,10 +2,7 @@ import java.io.IOException; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import lombok.Value; import org.knowm.xchange.binance.BinanceAdapters; @@ -13,49 +10,36 @@ import org.knowm.xchange.binance.BinanceErrorAdapter; import org.knowm.xchange.binance.BinanceExchange; import org.knowm.xchange.binance.dto.BinanceException; -import org.knowm.xchange.binance.dto.trade.BinanceNewOrder; -import org.knowm.xchange.binance.dto.trade.BinanceOrder; -import org.knowm.xchange.binance.dto.trade.BinanceTrade; -import org.knowm.xchange.binance.dto.trade.OrderType; -import org.knowm.xchange.binance.dto.trade.TimeInForce; +import org.knowm.xchange.binance.dto.trade.*; +import org.knowm.xchange.binance.dto.trade.margin.BinanceNewMarginOrder; +import org.knowm.xchange.binance.dto.trade.margin.MarginAccountType; +import org.knowm.xchange.binance.dto.trade.margin.MarginSideEffectType; import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.IOrderFlags; import org.knowm.xchange.dto.marketdata.Trades; -import org.knowm.xchange.dto.trade.LimitOrder; -import org.knowm.xchange.dto.trade.MarketOrder; -import org.knowm.xchange.dto.trade.OpenOrders; -import org.knowm.xchange.dto.trade.StopOrder; -import org.knowm.xchange.dto.trade.UserTrade; -import org.knowm.xchange.dto.trade.UserTrades; +import org.knowm.xchange.dto.trade.*; import org.knowm.xchange.exceptions.ExchangeException; import org.knowm.xchange.exceptions.NotAvailableFromExchangeException; import org.knowm.xchange.service.trade.TradeService; -import org.knowm.xchange.service.trade.params.CancelOrderByCurrencyPair; -import org.knowm.xchange.service.trade.params.CancelOrderByIdParams; -import org.knowm.xchange.service.trade.params.CancelOrderParams; -import org.knowm.xchange.service.trade.params.TradeHistoryParamCurrencyPair; -import org.knowm.xchange.service.trade.params.TradeHistoryParamLimit; -import org.knowm.xchange.service.trade.params.TradeHistoryParams; -import org.knowm.xchange.service.trade.params.TradeHistoryParamsIdSpan; -import org.knowm.xchange.service.trade.params.TradeHistoryParamsTimeSpan; -import org.knowm.xchange.service.trade.params.orders.DefaultOpenOrdersParam; -import org.knowm.xchange.service.trade.params.orders.DefaultOpenOrdersParamCurrencyPair; -import org.knowm.xchange.service.trade.params.orders.OpenOrdersParamCurrencyPair; -import org.knowm.xchange.service.trade.params.orders.OpenOrdersParams; -import org.knowm.xchange.service.trade.params.orders.OrderQueryParamCurrencyPair; -import org.knowm.xchange.service.trade.params.orders.OrderQueryParams; +import org.knowm.xchange.service.trade.params.*; +import org.knowm.xchange.service.trade.params.orders.*; import org.knowm.xchange.utils.Assert; public class BinanceTradeService extends BinanceTradeServiceRaw implements TradeService { + private final boolean includeMarginAccountTypeInOrderId; + public BinanceTradeService( BinanceExchange exchange, BinanceAuthenticated binance, ResilienceRegistries resilienceRegistries) { super(exchange, binance, resilienceRegistries); + + Boolean includeMarginAccountTypeInOrderId = (Boolean) exchange.getExchangeSpecification().getExchangeSpecificParametersItem("Include_Margin_Account_Type_In_OrderId"); + this.includeMarginAccountTypeInOrderId = includeMarginAccountTypeInOrderId != null && includeMarginAccountTypeInOrderId; } @Override @@ -98,7 +82,7 @@ public OpenOrders getOpenOrders(OpenOrdersParams params) throws IOException { @Override public String placeMarketOrder(MarketOrder mo) throws IOException { - return placeOrder(OrderType.MARKET, mo, null, null, null); + return placeOrder(OrderType.MARKET, mo, null, null, null, mo.getOrderFlags()); } @Override @@ -111,7 +95,7 @@ public String placeLimitOrder(LimitOrder limitOrder) throws IOException { } else { type = OrderType.LIMIT; } - return placeOrder(type, limitOrder, limitOrder.getLimitPrice(), null, tif); + return placeOrder(type, limitOrder, limitOrder.getLimitPrice(), null, tif, limitOrder.getOrderFlags()); } @Override @@ -126,7 +110,7 @@ public String placeStopOrder(StopOrder order) throws IOException { OrderType orderType = BinanceAdapters.adaptOrderType(order); - return placeOrder(orderType, order, order.getLimitPrice(), order.getStopPrice(), tif); + return placeOrder(orderType, order, order.getLimitPrice(), order.getStopPrice(), tif, order.getOrderFlags()); } private Optional timeInForceFromOrder(Order order) { @@ -137,25 +121,49 @@ private Optional timeInForceFromOrder(Order order) { } private String placeOrder( - OrderType type, Order order, BigDecimal limitPrice, BigDecimal stopPrice, TimeInForce tif) + OrderType type, Order order, BigDecimal limitPrice, BigDecimal stopPrice, TimeInForce tif, Set orderFlags) throws IOException { try { Long recvWindow = (Long) exchange.getExchangeSpecification().getExchangeSpecificParametersItem("recvWindow"); - BinanceNewOrder newOrder = - newOrder( - order.getCurrencyPair(), - BinanceAdapters.convert(order.getType()), - type, - tif, - order.getOriginalAmount(), - limitPrice, - getClientOrderId(order), - stopPrice, - null, - null); - return Long.toString(newOrder.orderId); + + MarginAccountType marginAccountType = IOrderFlags.getOrderFlagOfType(orderFlags, MarginAccountType.class); + + if (marginAccountType == null) { + BinanceNewOrder newOrder = + newOrder( + order.getCurrencyPair(), + BinanceAdapters.convert(order.getType()), + type, + tif, + order.getOriginalAmount(), + limitPrice, + getClientOrderId(order), + stopPrice, + null, + null); + return Long.toString(newOrder.orderId); + } else { + MarginSideEffectType marginSideEffect = IOrderFlags.getOrderFlagOfType(orderFlags, MarginSideEffectType.class); + + BinanceNewMarginOrder newOrder = + newMarginOrder( + order.getCurrencyPair(), + BinanceAdapters.convert(order.getType()), + marginAccountType, + type, + tif, + order.getOriginalAmount(), + limitPrice, + getClientOrderId(order), + stopPrice, + null, + null, + marginSideEffect); + + return newOrder.orderId + (includeMarginAccountTypeInOrderId ? "-" + marginAccountType.name() : ""); + } } catch (BinanceException e) { throw BinanceErrorAdapter.adapt(e); } @@ -184,17 +192,8 @@ public void placeTestOrder( } private String getClientOrderId(Order order) { - - String clientOrderId = null; - for (IOrderFlags flags : order.getOrderFlags()) { - if (flags instanceof BinanceOrderFlags) { - BinanceOrderFlags bof = (BinanceOrderFlags) flags; - if (clientOrderId == null) { - clientOrderId = bof.getClientId(); - } - } - } - return clientOrderId; + BinanceOrderFlags bof = IOrderFlags.getOrderFlagOfType(order.getOrderFlags(), BinanceOrderFlags.class); + return bof != null ? bof.getClientId() : null; } @Override @@ -206,23 +205,42 @@ public boolean cancelOrder(String orderId) { public boolean cancelOrder(CancelOrderParams params) throws IOException { try { if (!(params instanceof CancelOrderByCurrencyPair) - && !(params instanceof CancelOrderByIdParams)) { + || !(params instanceof CancelOrderByIdParams)) { throw new ExchangeException( "You need to provide the currency pair and the order id to cancel an order."); } CancelOrderByCurrencyPair paramCurrencyPair = (CancelOrderByCurrencyPair) params; CancelOrderByIdParams paramId = (CancelOrderByIdParams) params; - super.cancelOrder( - paramCurrencyPair.getCurrencyPair(), - BinanceAdapters.id(paramId.getOrderId()), - null, - null); + String orderId = paramId.getOrderId(); + MarginAccountType marginAccountType = + params instanceof BinanceCancelOrderParams ? ((BinanceCancelOrderParams) params).getMarginAccountType() + : includeMarginAccountTypeInOrderId ? getMarginAccountTypeFromOrderId(orderId) + : null; + if (marginAccountType == null) { + super.cancelOrder( + paramCurrencyPair.getCurrencyPair(), + BinanceAdapters.id(orderId), + null, + null); + } else { + super.cancelMarginOrder( + paramCurrencyPair.getCurrencyPair(), + marginAccountType, + BinanceAdapters.id(orderId), + null, + null); + } return true; } catch (BinanceException e) { throw BinanceErrorAdapter.adapt(e); } } + private MarginAccountType getMarginAccountTypeFromOrderId(String orderId) { + int hyphenIndex = orderId.indexOf('-'); + return hyphenIndex > 0 ? MarginAccountType.valueOf(orderId.substring(hyphenIndex + 1)) : null; + } + @Override public Class[] getRequiredCancelOrderParamClasses() { return new Class[] {CancelOrderByIdParams.class, CancelOrderByCurrencyPair.class}; @@ -334,13 +352,27 @@ public Collection getOrder(OrderQueryParams... params) throws IOException throw new ExchangeException( "You need to provide the currency pair and the order id to query an order."); } - - orders.add( - BinanceAdapters.adaptOrder( - super.orderStatus( - orderQueryParamCurrencyPair.getCurrencyPair(), - BinanceAdapters.id(orderQueryParamCurrencyPair.getOrderId()), - null))); + String orderId = orderQueryParamCurrencyPair.getOrderId(); + + MarginAccountType marginAccountType = + param instanceof BinanceQueryOrderParams ? ((BinanceQueryOrderParams) param).getMarginAccountType() + : includeMarginAccountTypeInOrderId ? getMarginAccountTypeFromOrderId(orderId) + : null; + + BinanceOrder binanceOrder; + if (marginAccountType == null) { + binanceOrder = super.orderStatus( + orderQueryParamCurrencyPair.getCurrencyPair(), + BinanceAdapters.id(orderId), + null); + } else { + binanceOrder = super.marginOrderStatus( + orderQueryParamCurrencyPair.getCurrencyPair(), + marginAccountType, + BinanceAdapters.id(orderId), + null); + } + orders.add(BinanceAdapters.adaptOrder(binanceOrder)); } return orders; } catch (BinanceException e) { diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java index e38292763a3..631b33bfcc4 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java @@ -13,15 +13,8 @@ import org.knowm.xchange.binance.BinanceAuthenticated; import org.knowm.xchange.binance.BinanceExchange; import org.knowm.xchange.binance.dto.BinanceException; -import org.knowm.xchange.binance.dto.trade.BinanceCancelledOrder; -import org.knowm.xchange.binance.dto.trade.BinanceDustLog; -import org.knowm.xchange.binance.dto.trade.BinanceListenKey; -import org.knowm.xchange.binance.dto.trade.BinanceNewOrder; -import org.knowm.xchange.binance.dto.trade.BinanceOrder; -import org.knowm.xchange.binance.dto.trade.BinanceTrade; -import org.knowm.xchange.binance.dto.trade.OrderSide; -import org.knowm.xchange.binance.dto.trade.OrderType; -import org.knowm.xchange.binance.dto.trade.TimeInForce; +import org.knowm.xchange.binance.dto.trade.*; +import org.knowm.xchange.binance.dto.trade.margin.*; import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.exceptions.ExchangeException; @@ -261,6 +254,102 @@ public void closeDataStream(String listenKey) throws IOException { .call(); } + public BinanceNewMarginOrder newMarginOrder( + CurrencyPair pair, + OrderSide side, + MarginAccountType marginAccountType, + OrderType type, + TimeInForce timeInForce, + BigDecimal quantity, + BigDecimal price, + String newClientOrderId, + BigDecimal stopPrice, + BigDecimal icebergQty, + BinanceNewOrder.NewOrderResponseType newOrderRespType, + MarginSideEffectType sideEffectType) + throws IOException, BinanceException { + return decorateApiCall( + () -> + binance.newMarginOrder( + BinanceAdapters.toSymbol(pair), + marginAccountType, + side, + type, + timeInForce, + quantity, + price, + newClientOrderId, + stopPrice, + icebergQty, + newOrderRespType, + sideEffectType, + getRecvWindow(), + getTimestampFactory(), + apiKey, + signatureCreator)) + .withRetry(retry("newMarginOrder", NON_IDEMPOTENT_CALLS_RETRY_CONFIG_NAME)) + .withRateLimiter(rateLimiter(ORDERS_PER_SECOND_RATE_LIMITER)) + .withRateLimiter(rateLimiter(ORDERS_PER_DAY_RATE_LIMITER)) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public BinanceMarginOrder marginOrderStatus( + CurrencyPair pair, MarginAccountType marginAccountType, Long orderId, String origClientOrderId) + throws IOException, BinanceException { + return decorateApiCall( + () -> + binance.marginOrderStatus( + BinanceAdapters.toSymbol(pair), + marginAccountType, + orderId, + origClientOrderId, + getRecvWindow(), + getTimestampFactory(), + super.apiKey, + super.signatureCreator)) + .withRetry(retry("marginOrderStatus")) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public BinanceCancelledMarginOrder cancelMarginOrder( + CurrencyPair pair, MarginAccountType marginAccountType, Long orderId, String origClientOrderId, String newClientOrderId) + throws IOException, BinanceException { + return decorateApiCall( + () -> + binance.cancelMarginOrder( + BinanceAdapters.toSymbol(pair), + marginAccountType, + orderId, + origClientOrderId, + newClientOrderId, + getRecvWindow(), + getTimestampFactory(), + super.apiKey, + super.signatureCreator)) + .withRetry(retry("cancelMarginOrder")) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public List cancelAllOpenMarginOrders( + CurrencyPair pair, MarginAccountType marginAccountType) + throws IOException, BinanceException { + return decorateApiCall( + () -> + binance.cancelAllOpenMarginOrders( + BinanceAdapters.toSymbol(pair), + marginAccountType, + getRecvWindow(), + getTimestampFactory(), + super.apiKey, + super.signatureCreator)) + .withRetry(retry("cancelAllOpenMarginOrders")) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + protected int openOrdersPermits(CurrencyPair pair) { return pair != null ? 1 : 40; } diff --git a/xchange-core/src/main/java/org/knowm/xchange/dto/Order.java b/xchange-core/src/main/java/org/knowm/xchange/dto/Order.java index b0cd25da0f3..59d2107cab4 100644 --- a/xchange-core/src/main/java/org/knowm/xchange/dto/Order.java +++ b/xchange-core/src/main/java/org/knowm/xchange/dto/Order.java @@ -473,7 +473,15 @@ public boolean isOpen() { } @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT) - public interface IOrderFlags {} + public interface IOrderFlags { + + static T getOrderFlagOfType(Set setOfFlags, Class type) { + return (T) setOfFlags.stream() + .filter(flag -> type.isAssignableFrom(flag.getClass())) + .findFirst() + .orElse(null); + } + } public abstract static class Builder { From 5b85f568490161c8bf827dca5b642e08e46c4354 Mon Sep 17 00:00:00 2001 From: Eugene Schava Date: Thu, 29 Sep 2022 11:29:38 +0200 Subject: [PATCH 2/2] Binance: margin trading streaming of margin trading and account data --- .../xchange/binance/BinanceAdapters.java | 14 +- .../xchange/binance/BinanceAuthenticated.java | 86 +++++++ .../xchange/binance/BinanceExchange.java | 2 + .../binance/service/BinanceTradeService.java | 23 +- .../service/BinanceTradeServiceRaw.java | 36 +++ .../xchange/binance/BinanceAdaptersTest.java | 2 +- .../BinanceIsolatedMarginUserDataChannel.java | 25 ++ .../binance/BinanceMarginUserDataChannel.java | 25 ++ .../binance/BinanceStreamingExchange.java | 216 +++++++++++++++--- .../binance/BinanceStreamingTradeService.java | 17 +- .../binance/BinanceUserDataChannel.java | 18 +- .../BinanceUserDataStreamingService.java | 14 +- .../CompositeStreamingAccountService.java | 26 +++ .../CompositeStreamingTradeService.java | 46 ++++ ...ExecutionReportBinanceUserTransaction.java | 9 +- .../binance/BinanceManualExample.java | 10 +- 16 files changed, 493 insertions(+), 76 deletions(-) create mode 100644 xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceIsolatedMarginUserDataChannel.java create mode 100644 xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceMarginUserDataChannel.java create mode 100644 xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingAccountService.java create mode 100644 xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingTradeService.java diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java index 76c0b793ed5..c0b78be0892 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAdapters.java @@ -16,6 +16,7 @@ import org.knowm.xchange.binance.dto.trade.BinanceOrder; import org.knowm.xchange.binance.dto.trade.OrderSide; import org.knowm.xchange.binance.dto.trade.OrderStatus; +import org.knowm.xchange.binance.dto.trade.margin.MarginAccountType; import org.knowm.xchange.binance.service.BinanceTradeService.BinanceOrderFlags; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; @@ -108,6 +109,15 @@ public static long id(String id) { } } + public static String placedOrderId(long orderId, MarginAccountType marginAccountType) { + return orderId + (marginAccountType != null ? "-" + marginAccountType.name() : ""); + } + + public static MarginAccountType getMarginAccountTypeFromOrderId(String orderId) { + int hyphenIndex = orderId.indexOf('-'); + return hyphenIndex > 0 ? MarginAccountType.valueOf(orderId.substring(hyphenIndex + 1)) : null; + } + public static Order.OrderStatus adaptOrderStatus(OrderStatus orderStatus) { switch (orderStatus) { case NEW: @@ -151,7 +161,7 @@ public static CurrencyPair adaptSymbol(String symbol) { } } - public static Order adaptOrder(BinanceOrder order) { + public static Order adaptOrder(BinanceOrder order, MarginAccountType marginAccountType) { OrderType type = convert(order.side); CurrencyPair currencyPair = adaptSymbol(order.symbol); Order.Builder builder; @@ -166,7 +176,7 @@ public static Order adaptOrder(BinanceOrder order) { builder .orderStatus(adaptOrderStatus(order.status)) .originalAmount(order.origQty) - .id(Long.toString(order.orderId)) + .id(BinanceAdapters.placedOrderId(order.orderId, marginAccountType)) .timestamp(order.getTime()) .cumulativeAmount(order.executedQty); if (order.executedQty.signum() != 0 && order.cummulativeQuoteQty.signum() != 0) { diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java index d0bc84a46af..d0a83212a73 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceAuthenticated.java @@ -675,4 +675,90 @@ List cancelAllOpenMarginOrders( @HeaderParam(X_MBX_APIKEY) String apiKey, @QueryParam(SIGNATURE) ParamsDigest signature) throws IOException, BinanceException; + + /** + * Returns a listen key for margin websocket login. + * + * @param apiKey the api key + * @return + * @throws BinanceException + * @throws IOException + */ + @POST + @Path("/sapi/v1/userDataStream") + BinanceListenKey startMarginUserDataStream(@HeaderParam(X_MBX_APIKEY) String apiKey) + throws IOException, BinanceException; + + /** + * Keeps the authenticated margin websocket session alive. + * + * @param apiKey the api key + * @param listenKey the api secret + * @return + * @throws BinanceException + * @throws IOException + */ + @PUT + @Path("/sapi/v1/userDataStream?listenKey={listenKey}") + Map keepAliveMarginUserDataStream( + @HeaderParam(X_MBX_APIKEY) String apiKey, @PathParam("listenKey") String listenKey) + throws IOException, BinanceException; + + /** + * Closes the authenticated margin connection. + * + * @param apiKey the api key + * @param listenKey the api secret + * @return + * @throws BinanceException + * @throws IOException + */ + @DELETE + @Path("/sapi/v1/userDataStream?listenKey={listenKey}") + Map closeMarginUserDataStream( + @HeaderParam(X_MBX_APIKEY) String apiKey, @PathParam("listenKey") String listenKey) + throws IOException, BinanceException; + + /** + * Returns a listen key for isolated margin websocket login. + * + * @param apiKey the api key + * @return + * @throws BinanceException + * @throws IOException + */ + @POST + @Path("/sapi/v1/userDataStream/isolated") + BinanceListenKey startIsolatedMarginUserDataStream(@HeaderParam(X_MBX_APIKEY) String apiKey) + throws IOException, BinanceException; + + /** + * Keeps the authenticated isolated margin websocket session alive. + * + * @param apiKey the api key + * @param listenKey the api secret + * @return + * @throws BinanceException + * @throws IOException + */ + @PUT + @Path("/sapi/v1/userDataStream/isolated?listenKey={listenKey}") + Map keepAliveIsolatedMarginUserDataStream( + @HeaderParam(X_MBX_APIKEY) String apiKey, @PathParam("listenKey") String listenKey) + throws IOException, BinanceException; + + /** + * Closes the authenticated isolated margin connection. + * + * @param apiKey the api key + * @param listenKey the api secret + * @return + * @throws BinanceException + * @throws IOException + */ + @DELETE + @Path("/sapi/v1/userDataStream/isolated?listenKey={listenKey}") + Map closeIsolatedMarginUserDataStream( + @HeaderParam(X_MBX_APIKEY) String apiKey, @PathParam("listenKey") String listenKey) + throws IOException, BinanceException; } diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceExchange.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceExchange.java index edf19624f64..8021bf3e197 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceExchange.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/BinanceExchange.java @@ -24,6 +24,8 @@ public class BinanceExchange extends BaseExchange { public static final String SPECIFIC_PARAM_USE_SANDBOX = "Use_Sandbox"; + public static final String SPECIFIC_PARAM_INCLUDE_MARGIN_ACCOUNT_TYPE_IN_ORDER_ID = + "Include_Margin_Account_Type_In_OrderId"; protected static ResilienceRegistries RESILIENCE_REGISTRIES; diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java index 8958db918ce..8a36c173f07 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeService.java @@ -38,8 +38,8 @@ public BinanceTradeService( ResilienceRegistries resilienceRegistries) { super(exchange, binance, resilienceRegistries); - Boolean includeMarginAccountTypeInOrderId = (Boolean) exchange.getExchangeSpecification().getExchangeSpecificParametersItem("Include_Margin_Account_Type_In_OrderId"); - this.includeMarginAccountTypeInOrderId = includeMarginAccountTypeInOrderId != null && includeMarginAccountTypeInOrderId; + this.includeMarginAccountTypeInOrderId = Boolean.TRUE.equals( + exchange.getExchangeSpecification().getExchangeSpecificParametersItem(BinanceExchange.SPECIFIC_PARAM_INCLUDE_MARGIN_ACCOUNT_TYPE_IN_ORDER_ID)); } @Override @@ -67,7 +67,7 @@ public OpenOrders getOpenOrders(OpenOrdersParams params) throws IOException { List otherOrders = new ArrayList<>(); binanceOpenOrders.forEach( binanceOrder -> { - Order order = BinanceAdapters.adaptOrder(binanceOrder); + Order order = BinanceAdapters.adaptOrder(binanceOrder, null); if (order instanceof LimitOrder) { limitOrders.add((LimitOrder) order); } else { @@ -153,7 +153,7 @@ private String placeOrder( trailingDelta, // TODO (Long)order.getExtraValue("trailingDelta") null, null); - return Long.toString(newOrder.orderId); + return BinanceAdapters.placedOrderId(newOrder.orderId, null); } else { MarginSideEffectType marginSideEffect = IOrderFlags.getOrderFlagOfType(orderFlags, MarginSideEffectType.class); @@ -172,7 +172,7 @@ private String placeOrder( null, marginSideEffect); - return newOrder.orderId + (includeMarginAccountTypeInOrderId ? "-" + marginAccountType.name() : ""); + return BinanceAdapters.placedOrderId(newOrder.orderId, includeMarginAccountTypeInOrderId ? marginAccountType : null); } } catch (BinanceException e) { throw BinanceErrorAdapter.adapt(e); @@ -237,8 +237,7 @@ public boolean cancelOrder(CancelOrderParams params) throws IOException { String orderId = paramId.getOrderId(); MarginAccountType marginAccountType = params instanceof BinanceCancelOrderParams ? ((BinanceCancelOrderParams) params).getMarginAccountType() - : includeMarginAccountTypeInOrderId ? getMarginAccountTypeFromOrderId(orderId) - : null; + : BinanceAdapters.getMarginAccountTypeFromOrderId(orderId); if (marginAccountType == null) { super.cancelOrder( paramCurrencyPair.getCurrencyPair(), @@ -259,11 +258,6 @@ public boolean cancelOrder(CancelOrderParams params) throws IOException { } } - private MarginAccountType getMarginAccountTypeFromOrderId(String orderId) { - int hyphenIndex = orderId.indexOf('-'); - return hyphenIndex > 0 ? MarginAccountType.valueOf(orderId.substring(hyphenIndex + 1)) : null; - } - @Override public Class[] getRequiredCancelOrderParamClasses() { return new Class[] {CancelOrderByIdParams.class, CancelOrderByCurrencyPair.class}; @@ -379,8 +373,7 @@ public Collection getOrder(OrderQueryParams... params) throws IOException MarginAccountType marginAccountType = param instanceof BinanceQueryOrderParams ? ((BinanceQueryOrderParams) param).getMarginAccountType() - : includeMarginAccountTypeInOrderId ? getMarginAccountTypeFromOrderId(orderId) - : null; + : BinanceAdapters.getMarginAccountTypeFromOrderId(orderId); BinanceOrder binanceOrder; if (marginAccountType == null) { @@ -395,7 +388,7 @@ public Collection getOrder(OrderQueryParams... params) throws IOException BinanceAdapters.id(orderId), null); } - orders.add(BinanceAdapters.adaptOrder(binanceOrder)); + orders.add(BinanceAdapters.adaptOrder(binanceOrder, includeMarginAccountTypeInOrderId ? marginAccountType : null)); } return orders; } catch (BinanceException e) { diff --git a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java index 04405206675..6b9d6bd5a3c 100644 --- a/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java +++ b/xchange-binance/src/main/java/org/knowm/xchange/binance/service/BinanceTradeServiceRaw.java @@ -358,6 +358,42 @@ public List cancelAllOpenMarginOrders( .call(); } + public BinanceListenKey startMarginUserDataStream() throws IOException { + return decorateApiCall(() -> binance.startMarginUserDataStream(apiKey)) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public void keepMarginAliveDataStream(String listenKey) throws IOException { + decorateApiCall(() -> binance.keepAliveMarginUserDataStream(apiKey, listenKey)) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public void closeMarginDataStream(String listenKey) throws IOException { + decorateApiCall(() -> binance.closeMarginUserDataStream(apiKey, listenKey)) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public BinanceListenKey startIsolatedMarginUserDataStream() throws IOException { + return decorateApiCall(() -> binance.startIsolatedMarginUserDataStream(apiKey)) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public void keepAliveIsolatedMarginDataStream(String listenKey) throws IOException { + decorateApiCall(() -> binance.keepAliveIsolatedMarginUserDataStream(apiKey, listenKey)) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + + public void closeIsolatedMarginDataStream(String listenKey) throws IOException { + decorateApiCall(() -> binance.closeIsolatedMarginUserDataStream(apiKey, listenKey)) + .withRateLimiter(rateLimiter(REQUEST_WEIGHT_RATE_LIMITER)) + .call(); + } + protected int openOrdersPermits(CurrencyPair pair) { return pair != null ? 1 : 40; } diff --git a/xchange-binance/src/test/java/org/knowm/xchange/binance/BinanceAdaptersTest.java b/xchange-binance/src/test/java/org/knowm/xchange/binance/BinanceAdaptersTest.java index 9adf1d63436..91a2145dac9 100644 --- a/xchange-binance/src/test/java/org/knowm/xchange/binance/BinanceAdaptersTest.java +++ b/xchange-binance/src/test/java/org/knowm/xchange/binance/BinanceAdaptersTest.java @@ -22,7 +22,7 @@ public void testFilledMarketOrder() throws IOException { BinanceAdaptersTest.class.getResource( "/org/knowm/xchange/binance/filled-market-order.json"), BinanceOrder.class); - Order order = BinanceAdapters.adaptOrder(binanceOrder); + Order order = BinanceAdapters.adaptOrder(binanceOrder, null); assertThat(order).isInstanceOf(MarketOrder.class); MarketOrder marketOrder = (MarketOrder) order; assertThat(marketOrder.getStatus()).isEqualByComparingTo(Order.OrderStatus.FILLED); diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceIsolatedMarginUserDataChannel.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceIsolatedMarginUserDataChannel.java new file mode 100644 index 00000000000..52e65e56981 --- /dev/null +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceIsolatedMarginUserDataChannel.java @@ -0,0 +1,25 @@ +package info.bitrich.xchangestream.binance; + +import org.knowm.xchange.binance.BinanceAuthenticated; + +import java.io.IOException; + +/** + * @author Eugene Schava + */ +class BinanceIsolatedMarginUserDataChannel extends BinanceUserDataChannel { + + BinanceIsolatedMarginUserDataChannel(BinanceAuthenticated binance, String apiKey, Runnable onApiCall) { + super(binance, apiKey, onApiCall); + } + + @Override + protected String startUserDataStream() throws IOException { + return binance.startIsolatedMarginUserDataStream(apiKey).getListenKey(); + } + + @Override + protected void keepAliveUserDataStream() throws IOException { + binance.keepAliveIsolatedMarginUserDataStream(apiKey, listenKey); + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceMarginUserDataChannel.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceMarginUserDataChannel.java new file mode 100644 index 00000000000..2f95b0685df --- /dev/null +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/BinanceMarginUserDataChannel.java @@ -0,0 +1,25 @@ +package info.bitrich.xchangestream.binance; + +import org.knowm.xchange.binance.BinanceAuthenticated; + +import java.io.IOException; + +/** + * @author Eugene Schava + */ +class BinanceMarginUserDataChannel extends BinanceUserDataChannel { + + BinanceMarginUserDataChannel(BinanceAuthenticated binance, String apiKey, Runnable onApiCall) { + super(binance, apiKey, onApiCall); + } + + @Override + protected String startUserDataStream() throws IOException { + return binance.startMarginUserDataStream(apiKey).getListenKey(); + } + + @Override + protected void keepAliveUserDataStream() throws IOException { + binance.keepAliveMarginUserDataStream(apiKey, listenKey); + } +} 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 411a9c3e037..0f6af7cc423 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 @@ -2,7 +2,9 @@ import info.bitrich.xchangestream.binance.BinanceUserDataChannel.NoActiveChannelException; import info.bitrich.xchangestream.core.ProductSubscription; +import info.bitrich.xchangestream.core.StreamingAccountService; import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.core.StreamingTradeService; import info.bitrich.xchangestream.service.netty.ConnectionStateModel.State; import info.bitrich.xchangestream.util.Events; import io.reactivex.Completable; @@ -13,6 +15,7 @@ import java.util.stream.Stream; import org.knowm.xchange.binance.BinanceAuthenticated; import org.knowm.xchange.binance.BinanceExchange; +import org.knowm.xchange.binance.dto.trade.margin.MarginAccountType; import org.knowm.xchange.binance.service.BinanceMarketDataService; import org.knowm.xchange.client.ExchangeRestProxyBuilder; import org.knowm.xchange.currency.CurrencyPair; @@ -28,14 +31,27 @@ public class BinanceStreamingExchange extends BinanceExchange implements Streami "Binance_Orderbook_Use_Higher_Frequency"; protected static final String USE_REALTIME_BOOK_TICKER = "Binance_Ticker_Use_Realtime"; protected static final String FETCH_ORDER_BOOK_LIMIT = "Binance_Fetch_Order_Book_Limit"; + protected static final String SUBSCRIBE_TO_MARGIN_TRADING = "Binance_Subscribe_To_Margin_Trading"; + protected static final String SUBSCRIBE_TO_ISOLATED_MARGIN_TRADING = "Binance_Subscribe_To_Isolated_Margin_Trading"; + private BinanceStreamingService streamingService; - private BinanceUserDataStreamingService userDataStreamingService; + private BinanceUserDataStreamingService spotUserDataStreamingService; + private BinanceUserDataStreamingService marginUserDataStreamingService; + private BinanceUserDataStreamingService isolatedMarginUserDataStreamingService; private BinanceStreamingMarketDataService streamingMarketDataService; - private BinanceStreamingAccountService streamingAccountService; - private BinanceStreamingTradeService streamingTradeService; + private StreamingAccountService streamingAccountService; + private BinanceStreamingAccountService streamingSpotAccountService; + private BinanceStreamingAccountService streamingMarginAccountService; + private BinanceStreamingAccountService streamingIsolatedMarginAccountService; + private StreamingTradeService streamingTradeService; + private BinanceStreamingTradeService streamingSpotTradeService; + private BinanceStreamingTradeService streamingMarginTradeService; + private BinanceStreamingTradeService streamingIsolatedMarginTradeService; - private BinanceUserDataChannel userDataChannel; + private BinanceUserDataChannel spotUserDataChannel; + private BinanceUserDataChannel marginUserDataChannel; + private BinanceUserDataChannel isolatedMarginUserDataChannel; private Runnable onApiCall; private String orderBookUpdateFrequencyParameter = ""; private int oderBookFetchLimitParameter = 1000; @@ -99,13 +115,49 @@ public Completable connect(ProductSubscription... args) { ExchangeRestProxyBuilder.forInterface( BinanceAuthenticated.class, getExchangeSpecification()) .build(); - userDataChannel = + spotUserDataChannel = new BinanceUserDataChannel(binance, exchangeSpecification.getApiKey(), onApiCall); try { - completables.add(createAndConnectUserDataService(userDataChannel.getListenKey())); + completables.add(createAndConnectSpotUserDataService(spotUserDataChannel.getListenKey())); } catch (NoActiveChannelException e) { throw new IllegalStateException("Failed to establish user data channel", e); } + + if (Boolean.TRUE.equals( + exchangeSpecification.getExchangeSpecificParametersItem(SUBSCRIBE_TO_MARGIN_TRADING))) { + LOG.info("Connecting to authenticated margin web socket"); + marginUserDataChannel = + new BinanceMarginUserDataChannel(binance, exchangeSpecification.getApiKey(), onApiCall); + try { + Completable marginUserDataServiceCompletable = createAndConnectMarginUserDataService(marginUserDataChannel.getListenKey()); + streamingMarginAccountService = new BinanceStreamingAccountService(marginUserDataStreamingService); + streamingMarginTradeService = new BinanceStreamingTradeService(this, marginUserDataStreamingService, MarginAccountType.CROSS); + completables.add(marginUserDataServiceCompletable + .doOnComplete(() -> streamingMarginAccountService.openSubscriptions()) + .doOnComplete(() -> streamingMarginTradeService.openSubscriptions()) + ); + } catch (NoActiveChannelException e) { + throw new IllegalStateException("Failed to establish margin user data channel", e); + } + } + + if (Boolean.TRUE.equals( + exchangeSpecification.getExchangeSpecificParametersItem(SUBSCRIBE_TO_ISOLATED_MARGIN_TRADING))) { + LOG.info("Connecting to authenticated isolated margin web socket"); + isolatedMarginUserDataChannel = + new BinanceIsolatedMarginUserDataChannel(binance, exchangeSpecification.getApiKey(), onApiCall); + try { + Completable isolatedMarginUserDataServiceCompletable = createAndConnectIsolatedMarginUserDataService(isolatedMarginUserDataChannel.getListenKey()); + streamingIsolatedMarginAccountService = new BinanceStreamingAccountService(isolatedMarginUserDataStreamingService); + streamingIsolatedMarginTradeService = new BinanceStreamingTradeService(this, isolatedMarginUserDataStreamingService, MarginAccountType.ISOLATED); + completables.add(isolatedMarginUserDataServiceCompletable + .doOnComplete(() -> streamingIsolatedMarginAccountService.openSubscriptions()) + .doOnComplete(() -> streamingIsolatedMarginTradeService.openSubscriptions()) + ); + } catch (NoActiveChannelException e) { + throw new IllegalStateException("Failed to establish isolated margin user data channel", e); + } + } } streamingMarketDataService = @@ -116,55 +168,135 @@ BinanceAuthenticated.class, getExchangeSpecification()) orderBookUpdateFrequencyParameter, realtimeOrderBookTicker, oderBookFetchLimitParameter); - streamingAccountService = new BinanceStreamingAccountService(userDataStreamingService); - streamingTradeService = new BinanceStreamingTradeService(userDataStreamingService); + streamingSpotAccountService = new BinanceStreamingAccountService(spotUserDataStreamingService); + streamingSpotTradeService = new BinanceStreamingTradeService(this, spotUserDataStreamingService, null); + + streamingAccountService = streamingMarginAccountService != null || streamingIsolatedMarginAccountService != null + ? new CompositeStreamingAccountService(streamingSpotAccountService, streamingMarginAccountService, streamingIsolatedMarginAccountService) + : streamingSpotAccountService; + + streamingTradeService = streamingMarginTradeService != null || streamingIsolatedMarginTradeService != null + ? new CompositeStreamingTradeService(streamingSpotTradeService, streamingMarginTradeService, streamingIsolatedMarginTradeService) + : streamingSpotTradeService; return Completable.concat(completables) - .doOnComplete(() -> streamingMarketDataService.openSubscriptions(subscriptions)) - .doOnComplete(() -> streamingAccountService.openSubscriptions()) - .doOnComplete(() -> streamingTradeService.openSubscriptions()); + .doOnComplete(() -> streamingMarketDataService.openSubscriptions(subscriptions)) + .doOnComplete(() -> streamingSpotAccountService.openSubscriptions()) + .doOnComplete(() -> streamingSpotTradeService.openSubscriptions()); } - private Completable createAndConnectUserDataService(String listenKey) { - userDataStreamingService = + private Completable createAndConnectSpotUserDataService(String listenKey) { + spotUserDataStreamingService = BinanceUserDataStreamingService.create(getStreamingBaseUri(), listenKey); - applyStreamingSpecification(getExchangeSpecification(), userDataStreamingService); - return userDataStreamingService + applyStreamingSpecification(getExchangeSpecification(), spotUserDataStreamingService); + return spotUserDataStreamingService .connect() .doOnComplete( () -> { LOG.info("Connected to authenticated web socket"); - userDataChannel.onChangeListenKey( + spotUserDataChannel.onChangeListenKey( newListenKey -> { - userDataStreamingService + spotUserDataStreamingService .disconnect() .doOnComplete( () -> { - createAndConnectUserDataService(newListenKey) + createAndConnectSpotUserDataService(newListenKey) .doOnComplete( () -> { - streamingAccountService.setUserDataStreamingService( - userDataStreamingService); - streamingTradeService.setUserDataStreamingService( - userDataStreamingService); + streamingSpotAccountService.setUserDataStreamingService( + spotUserDataStreamingService); + streamingSpotTradeService.setUserDataStreamingService( + spotUserDataStreamingService); }); }); }); }); } + private Completable createAndConnectMarginUserDataService(String listenKey) { + marginUserDataStreamingService = + BinanceUserDataStreamingService.create(getStreamingBaseUri(), listenKey); + applyStreamingSpecification(getExchangeSpecification(), marginUserDataStreamingService); + return marginUserDataStreamingService + .connect() + .doOnComplete( + () -> { + LOG.info("Connected to margin authenticated web socket"); + marginUserDataChannel.onChangeListenKey( + newListenKey -> { + marginUserDataStreamingService + .disconnect() + .doOnComplete( + () -> { + createAndConnectMarginUserDataService(newListenKey) + .doOnComplete( + () -> { + streamingMarginAccountService.setUserDataStreamingService( + marginUserDataStreamingService); + streamingMarginTradeService.setUserDataStreamingService( + marginUserDataStreamingService); + }); + }); + }); + }); + } + + private Completable createAndConnectIsolatedMarginUserDataService(String listenKey) { + isolatedMarginUserDataStreamingService = + BinanceUserDataStreamingService.create(getStreamingBaseUri(), listenKey); + applyStreamingSpecification(getExchangeSpecification(), isolatedMarginUserDataStreamingService); + return isolatedMarginUserDataStreamingService + .connect() + .doOnComplete( + () -> { + LOG.info("Connected to margin authenticated web socket"); + isolatedMarginUserDataChannel.onChangeListenKey( + newListenKey -> { + isolatedMarginUserDataStreamingService + .disconnect() + .doOnComplete( + () -> { + createAndConnectIsolatedMarginUserDataService(newListenKey) + .doOnComplete( + () -> { + streamingIsolatedMarginAccountService.setUserDataStreamingService( + isolatedMarginUserDataStreamingService); + streamingIsolatedMarginTradeService.setUserDataStreamingService( + isolatedMarginUserDataStreamingService); + }); + }); + }); + }); + } + @Override public Completable disconnect() { List completables = new ArrayList<>(); completables.add(streamingService.disconnect()); streamingService = null; - if (userDataStreamingService != null) { - completables.add(userDataStreamingService.disconnect()); - userDataStreamingService = null; + if (spotUserDataStreamingService != null) { + completables.add(spotUserDataStreamingService.disconnect()); + spotUserDataStreamingService = null; + } + if (marginUserDataStreamingService != null) { + completables.add(marginUserDataStreamingService.disconnect()); + marginUserDataStreamingService = null; } - if (userDataChannel != null) { - userDataChannel.close(); - userDataChannel = null; + if (isolatedMarginUserDataStreamingService != null) { + completables.add(isolatedMarginUserDataStreamingService.disconnect()); + isolatedMarginUserDataStreamingService = null; + } + if (spotUserDataChannel != null) { + spotUserDataChannel.close(); + spotUserDataChannel = null; + } + if (marginUserDataChannel != null) { + marginUserDataChannel.close(); + marginUserDataChannel = null; + } + if (isolatedMarginUserDataChannel != null) { + isolatedMarginUserDataChannel.close(); + isolatedMarginUserDataChannel = null; } streamingMarketDataService = null; return Completable.concat(completables); @@ -196,15 +328,39 @@ public BinanceStreamingMarketDataService getStreamingMarketDataService() { } @Override - public BinanceStreamingAccountService getStreamingAccountService() { + public StreamingAccountService getStreamingAccountService() { return streamingAccountService; } + public BinanceStreamingAccountService getStreamingSpotAccountService() { + return streamingSpotAccountService; + } + + public BinanceStreamingAccountService getStreamingMarginAccountService() { + return streamingMarginAccountService; + } + + public BinanceStreamingAccountService getStreamingIsolatedMarginAccountService() { + return streamingIsolatedMarginAccountService; + } + @Override - public BinanceStreamingTradeService getStreamingTradeService() { + public StreamingTradeService getStreamingTradeService() { return streamingTradeService; } + public BinanceStreamingTradeService getStreamingSpotTradeService() { + return streamingSpotTradeService; + } + + public BinanceStreamingTradeService getStreamingMarginTradeService() { + return streamingMarginTradeService; + } + + public BinanceStreamingTradeService getStreamingIsolatedMarginTradeService() { + return streamingIsolatedMarginTradeService; + } + protected BinanceStreamingService createStreamingService(ProductSubscription subscription) { String path = getStreamingBaseUri() + "stream?streams=" + buildSubscriptionStreams(subscription); 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 de2fa615bc3..92ee7f7f43a 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 @@ -12,6 +12,9 @@ import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.Subject; import java.io.IOException; + +import org.knowm.xchange.binance.BinanceExchange; +import org.knowm.xchange.binance.dto.trade.margin.MarginAccountType; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.UserTrade; @@ -25,12 +28,20 @@ public class BinanceStreamingTradeService implements StreamingTradeService { private volatile Disposable executionReports; private volatile BinanceUserDataStreamingService binanceUserDataStreamingService; + private final MarginAccountType marginAccountType; private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); public BinanceStreamingTradeService( - BinanceUserDataStreamingService binanceUserDataStreamingService) { + BinanceExchange exchange, + BinanceUserDataStreamingService binanceUserDataStreamingService, + MarginAccountType marginAccountType) { this.binanceUserDataStreamingService = binanceUserDataStreamingService; + + boolean includeMarginAccountTypeInOrderId = Boolean.TRUE.equals( + exchange.getExchangeSpecification().getExchangeSpecificParametersItem(BinanceExchange.SPECIFIC_PARAM_INCLUDE_MARGIN_ACCOUNT_TYPE_IN_ORDER_ID)); + // ignore margin account type if it doesn't have to be included in order ID + this.marginAccountType = includeMarginAccountTypeInOrderId ? marginAccountType : null; } public Observable getRawExecutionReports() { @@ -42,7 +53,7 @@ public Observable getRawExecutionReports( public Observable getOrderChanges() { return getRawExecutionReports() .filter(r -> !r.getExecutionType().equals(ExecutionType.REJECTED)) - .map(ExecutionReportBinanceUserTransaction::toOrder); + .map((ExecutionReportBinanceUserTransaction transaction) -> transaction.toOrder(marginAccountType)); } @Override @@ -53,7 +64,7 @@ public Observable getOrderChanges(CurrencyPair currencyPair, Object... ar public Observable getUserTrades() { return getRawExecutionReports() .filter(r -> r.getExecutionType().equals(ExecutionType.TRADE)) - .map(ExecutionReportBinanceUserTransaction::toUserTrade); + .map((ExecutionReportBinanceUserTransaction transaction) -> transaction.toUserTrade(marginAccountType)); } @Override 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 7d586661305..e92c211cbfb 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 @@ -26,12 +26,12 @@ class BinanceUserDataChannel implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(BinanceUserDataChannel.class); - private final BinanceAuthenticated binance; - private final String apiKey; + protected final BinanceAuthenticated binance; + protected final String apiKey; private final Runnable onApiCall; private final Disposable keepAlive; - private String listenKey; + protected String listenKey; private Consumer onChangeListenKey; /** @@ -65,7 +65,7 @@ private void keepAlive() { try { LOG.debug("Keeping user data channel alive"); onApiCall.run(); - binance.keepAliveUserDataStream(apiKey, listenKey); + keepAliveUserDataStream(); LOG.debug("User data channel keepalive sent successfully"); } catch (Exception e) { LOG.error("User data channel keepalive failed.", e); @@ -74,6 +74,10 @@ private void keepAlive() { } } + protected void keepAliveUserDataStream() throws IOException { + binance.keepAliveUserDataStream(apiKey, listenKey); + } + private void reconnect() { try { openChannel(); @@ -90,13 +94,17 @@ private void openChannel() { try { LOG.debug("Opening new user data channel"); onApiCall.run(); - this.listenKey = binance.startUserDataStream(apiKey).getListenKey(); + this.listenKey = startUserDataStream(); LOG.debug("Opened new user data channel"); } catch (IOException e) { throw new RuntimeException(e); } } + protected String startUserDataStream() throws IOException { + return binance.startUserDataStream(apiKey).getListenKey(); + } + /** * @return The current listen key. * @throws NoActiveChannelException If no listen key is currently available. 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 d897b32a6fb..31f36c00657 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 @@ -6,7 +6,6 @@ import info.bitrich.xchangestream.service.netty.WebSocketClientCompressionAllowClientNoContextAndServerNoContextHandler; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; import io.reactivex.Observable; -import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,35 +25,28 @@ public Observable subscribeChannel(BinanceWebSocketTypes eventType) { return super.subscribeChannel(eventType.getSerializedValue()); } - @Override - public void messageHandler(String message) { - LOG.debug("Received message: {}", message); - super.messageHandler(message); - } - @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 { + protected String getChannelNameFromMessage(JsonNode message) { return message.get("e").asText(); } @Override - public String getSubscribeMessage(String channelName, Object... args) throws IOException { + public String getSubscribeMessage(String channelName, Object... args) { // No op. Disconnecting from the web socket will cancel subscriptions. return null; } @Override - public String getUnsubscribeMessage(String channelName, Object... args) throws IOException { + public String getUnsubscribeMessage(String channelName, Object... args) { // No op. Disconnecting from the web socket will cancel subscriptions. return null; } diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingAccountService.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingAccountService.java new file mode 100644 index 00000000000..686f6694249 --- /dev/null +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingAccountService.java @@ -0,0 +1,26 @@ +package info.bitrich.xchangestream.binance; + +import info.bitrich.xchangestream.core.StreamingAccountService; +import io.reactivex.Observable; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.dto.account.Balance; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class CompositeStreamingAccountService implements StreamingAccountService { + private final List accountServices; + + public CompositeStreamingAccountService(StreamingAccountService... accountServices) { + this.accountServices = Stream.of(accountServices).filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public Observable getBalanceChanges(Currency currency, Object... args) { + return Observable.merge(accountServices.stream().map(s -> s.getBalanceChanges(currency, args)) + .collect(Collectors.toList())); + } +} diff --git a/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingTradeService.java b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingTradeService.java new file mode 100644 index 00000000000..c4f7fd02bd5 --- /dev/null +++ b/xchange-stream-binance/src/main/java/info/bitrich/xchangestream/binance/CompositeStreamingTradeService.java @@ -0,0 +1,46 @@ +package info.bitrich.xchangestream.binance; + +import info.bitrich.xchangestream.core.StreamingTradeService; +import io.reactivex.Observable; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.trade.UserTrade; +import org.knowm.xchange.instrument.Instrument; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class CompositeStreamingTradeService implements StreamingTradeService { + private final List tradeServices; + + public CompositeStreamingTradeService(StreamingTradeService... tradeServices) { + this.tradeServices = Stream.of(tradeServices).filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + return Observable.merge(tradeServices.stream().map(s -> s.getOrderChanges(currencyPair, args)) + .collect(Collectors.toList())); + } + + @Override + public Observable getOrderChanges(Instrument instrument, Object... args) { + return Observable.merge(tradeServices.stream().map(s -> s.getOrderChanges(instrument, args)) + .collect(Collectors.toList())); + } + + @Override + public Observable getUserTrades(CurrencyPair currencyPair, Object... args) { + return Observable.merge(tradeServices.stream().map(s -> s.getUserTrades(currencyPair, args)) + .collect(Collectors.toList())); + } + + @Override + public Observable getUserTrades(Instrument instrument, Object... args) { + return Observable.merge(tradeServices.stream().map(s -> s.getUserTrades(instrument, args)) + .collect(Collectors.toList())); + } +} 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 239708e1dfc..082425f9534 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 @@ -8,6 +8,7 @@ import org.knowm.xchange.binance.dto.trade.OrderStatus; import org.knowm.xchange.binance.dto.trade.OrderType; import org.knowm.xchange.binance.dto.trade.TimeInForce; +import org.knowm.xchange.binance.dto.trade.margin.MarginAccountType; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.UserTrade; @@ -185,7 +186,7 @@ public BigDecimal getCumulativeQuoteAssetTransactedQuantity() { return cumulativeQuoteAssetTransactedQuantity; } - public UserTrade toUserTrade() { + public UserTrade toUserTrade(MarginAccountType marginAccountType) { if (executionType != ExecutionType.TRADE) throw new IllegalStateException("Not a trade"); return new UserTrade.Builder() .type(BinanceAdapters.convert(side)) @@ -194,13 +195,13 @@ public UserTrade toUserTrade() { .price(lastExecutedPrice) .timestamp(getEventTime()) .id(Long.toString(tradeId)) - .orderId(Long.toString(orderId)) + .orderId(BinanceAdapters.placedOrderId(orderId, marginAccountType)) .feeAmount(commissionAmount) .feeCurrency(Currency.getInstance(commissionAsset)) .build(); } - public Order toOrder() { + public Order toOrder(MarginAccountType marginAccountType) { return BinanceAdapters.adaptOrder( new BinanceOrder( BinanceAdapters.toSymbol(getCurrencyPair()), @@ -216,7 +217,7 @@ public Order toOrder() { side, stopPrice, BigDecimal.ZERO, - timestamp)); + timestamp), marginAccountType); } @Override 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 2d534e69cf6..3d1416fc4f5 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 @@ -71,17 +71,17 @@ public static void main(String[] args) throws InterruptedException { // Level 1 (generic) APIs orderChanges = exchange - .getStreamingTradeService() + .getStreamingSpotTradeService() .getOrderChanges() .subscribe(oc -> LOG.info("Order change: {}", oc)); userTrades = exchange - .getStreamingTradeService() + .getStreamingSpotTradeService() .getUserTrades() .subscribe(trade -> LOG.info("User trade: {}", trade)); balances = exchange - .getStreamingAccountService() + .getStreamingSpotAccountService() .getBalanceChanges() .subscribe( trade -> LOG.info("Balance: {}", trade), @@ -90,12 +90,12 @@ public static void main(String[] args) throws InterruptedException { // Level 2 (exchange-specific) APIs executionReports = exchange - .getStreamingTradeService() + .getStreamingSpotTradeService() .getRawExecutionReports() .subscribe(report -> LOG.info("Subscriber got execution report: {}", report)); accountInfo = exchange - .getStreamingAccountService() + .getStreamingSpotAccountService() .getRawAccountInfo() .subscribe( accInfo ->