diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAdapters.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAdapters.java index a1e10656f5a..e8609b7d73c 100644 --- a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAdapters.java +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAdapters.java @@ -1,13 +1,28 @@ package org.knowm.xchange.blockchain; import lombok.experimental.UtilityClass; -import org.knowm.xchange.blockchain.dto.account.*; +import org.knowm.xchange.blockchain.dto.account.BlockchainDeposit; +import org.knowm.xchange.blockchain.dto.account.BlockchainDeposits; +import org.knowm.xchange.blockchain.dto.account.BlockchainSymbol; +import org.knowm.xchange.blockchain.dto.account.BlockchainWithdrawal; +import org.knowm.xchange.blockchain.dto.trade.BlockchainOrder; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.account.AddressWithTag; import org.knowm.xchange.dto.account.FundingRecord; +import org.knowm.xchange.dto.marketdata.Trades; +import org.knowm.xchange.dto.meta.CurrencyMetaData; +import org.knowm.xchange.dto.meta.CurrencyPairMetaData; +import org.knowm.xchange.dto.meta.ExchangeMetaData; +import org.knowm.xchange.dto.meta.RateLimit; +import org.knowm.xchange.dto.trade.*; +import org.knowm.xchange.instrument.Instrument; import java.math.BigDecimal; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static org.knowm.xchange.blockchain.BlockchainConstants.*; @@ -18,6 +33,13 @@ public static String toSymbol(CurrencyPair currencyPair) { return String.format(CURRENCY_PAIR_SYMBOL_FORMAT, currencyPair.base.getCurrencyCode(), currencyPair.counter.getCurrencyCode()); } + public static CurrencyPair toCurrencyPair(Instrument instrument){ + if(instrument instanceof CurrencyPair) { + return (CurrencyPair) instrument; + } + throw new IllegalArgumentException(String.format("Unsupported instrument '%s'", instrument)); + } + public static AddressWithTag toAddressWithTag(BlockchainDeposit blockchainDeposit){ return new AddressWithTag(blockchainDeposit.getAddress(), null); } @@ -86,4 +108,148 @@ public static CurrencyPair toCurrencyPairBySymbol(BlockchainSymbol blockchainSym Currency counterSymbol = blockchainSymbol.getCounterCurrency(); return new CurrencyPair(baseSymbol, counterSymbol); } + + public static OpenOrders toOpenOrders(List blockchainOrders){ + List limitOrders = new ArrayList<>(); + List hiddenOrders = new ArrayList<>(); + + for(BlockchainOrder blockchainOrder : blockchainOrders) { + Order.Builder builder = blockchainOrder.getOrderBuilder(); + + Order order = builder.orderStatus(toOrderStatus(blockchainOrder.getOrdStatus())) + .originalAmount(blockchainOrder.getCumQty().add(blockchainOrder.getLeavesQty())) + .id(Long.toString(blockchainOrder.getExOrdId())) + .timestamp(blockchainOrder.getTimestamp()) + .averagePrice(blockchainOrder.getAvgPx()) + .build(); + + if (order instanceof LimitOrder) { + limitOrders.add((LimitOrder) order); + } else { + hiddenOrders.add(order); + } + } + + return new OpenOrders(limitOrders, hiddenOrders); + } + + public static Order toOpenOrdersById(BlockchainOrder blockchainOrder){ + Order.Builder builder = blockchainOrder.getOrderBuilder(); + + return builder.originalAmount(blockchainOrder.getCumQty().add(blockchainOrder.getLeavesQty())) + .id(Long.toString(blockchainOrder.getExOrdId())) + .timestamp(blockchainOrder.getTimestamp()) + .averagePrice(blockchainOrder.getAvgPx()) + .cumulativeAmount(blockchainOrder.getCumQty()) + .orderStatus(toOrderStatus(blockchainOrder.getOrdStatus())) + .userReference(blockchainOrder.getClOrdId()) + .build(); + } + + public static Order.OrderStatus toOrderStatus(String status) { + switch (status.toUpperCase()) { + case OPEN: + return Order.OrderStatus.OPEN; + case REJECTED: + return Order.OrderStatus.REJECTED; + case CANCELED: + return Order.OrderStatus.CANCELED; + case FILLED: + return Order.OrderStatus.FILLED; + case PART_FILLED: + return Order.OrderStatus.PARTIALLY_FILLED; + case EXPIRED: + return Order.OrderStatus.EXPIRED; + case PENDING: + return Order.OrderStatus.PENDING_NEW; + default: + return Order.OrderStatus.UNKNOWN; + } + } + + public static BlockchainOrder toBlockchainLimitOrder(LimitOrder limitOrder){ + return BlockchainOrder.builder() + .ordType(LIMIT) + .symbol(toCurrencyPair(limitOrder.getInstrument())) + .side(getOrderType(limitOrder.getType())) + .orderQty(limitOrder.getOriginalAmount()) + .price(limitOrder.getLimitPrice()) + .clOrdId(generateClOrdId()) + .build(); + } + + public static BlockchainOrder toBlockchainMarketOrder(MarketOrder marketOrder){ + return BlockchainOrder.builder() + .ordType(MARKET) + .symbol(toCurrencyPair(marketOrder.getInstrument())) + .side(getOrderType(marketOrder.getType())) + .orderQty(marketOrder.getOriginalAmount()) + .price(marketOrder.getCumulativeAmount()) + .clOrdId(generateClOrdId()) + .build(); + } + + public static BlockchainOrder toBlockchainStopOrder(StopOrder stopOrder){ + return BlockchainOrder.builder() + .ordType(STOP) + .symbol(toCurrencyPair(stopOrder.getInstrument())) + .side(getOrderType(stopOrder.getType())) + .orderQty(stopOrder.getOriginalAmount()) + .price(stopOrder.getLimitPrice()) + .stopPx(stopOrder.getStopPrice()) + .clOrdId(generateClOrdId()) + .build(); + } + + private static String generateClOrdId() { + String uuid = UUID.randomUUID().toString(); + uuid = uuid.substring(0, 16).replace("-", ""); + return uuid; + } + + public static UserTrades toUserTrades(List blockchainTrades) { + List trades = blockchainTrades.stream() + .map(blockchainTrade -> new UserTrade.Builder() + .type(blockchainTrade.getOrderType()) + .originalAmount(blockchainTrade.getCumQty()) + .currencyPair(blockchainTrade.getSymbol()) + .price(blockchainTrade.getPrice()) + .timestamp(blockchainTrade.getTimestamp()) + .id(Long.toString(blockchainTrade.getExOrdId())) + .orderId(blockchainTrade.getClOrdId()) + .build() + ).collect(Collectors.toList()); + Long lastId = blockchainTrades.stream().map(BlockchainOrder::getExOrdId).max(Long::compareTo).orElse(0L); + return new UserTrades(trades, lastId, Trades.TradeSortType.SortByTimestamp); + } + + public static ExchangeMetaData adaptMetaData(Map markets) { + Map currencyPairs = new HashMap<>(); + Map currency = new HashMap<>(); + + for (Map.Entry entry : markets.entrySet()) { + CurrencyPair pair = BlockchainAdapters.toCurrencyPairBySymbol(entry.getValue()); + BigDecimal minScale = BigDecimal.valueOf(Math.pow(10, (entry.getValue().getMinOrderSizeScale())*-1)); + BigDecimal minAmount = entry.getValue().getMinOrderSize().multiply(minScale); + BigDecimal maxScale = BigDecimal.valueOf(Math.pow(10, (entry.getValue().getMaxOrderSizeScale())*-1)); + BigDecimal maxAmount = entry.getValue().getMaxOrderSize().multiply(maxScale); + CurrencyPairMetaData currencyPairMetaData = + new CurrencyPairMetaData.Builder() + .baseScale(entry.getValue().getBaseCurrencyScale()) + .priceScale(entry.getValue().getCounterCurrencyScale()) + .minimumAmount(minAmount) + .maximumAmount(maxAmount) + .build(); + currencyPairs.put(pair, currencyPairMetaData); + currency.put(entry.getValue().getBaseCurrency(), new CurrencyMetaData(entry.getValue().getBaseCurrencyScale(),null)); + } + + RateLimit[] rateLimits = {new RateLimit(30, 1, TimeUnit.SECONDS)}; + + return new ExchangeMetaData(currencyPairs, currency, rateLimits, rateLimits, false); + } + + public static String getOrderType(Order.OrderType type){ + return Order.OrderType.BID.equals(type)? BUY.toUpperCase() : SELL.toUpperCase(); + } } diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAuthenticated.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAuthenticated.java index e16dcd8bd6a..301f3d7bd24 100644 --- a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAuthenticated.java +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainAuthenticated.java @@ -2,6 +2,7 @@ import org.knowm.xchange.blockchain.dto.BlockchainException; import org.knowm.xchange.blockchain.dto.account.*; +import org.knowm.xchange.blockchain.dto.trade.BlockchainOrder; import org.knowm.xchange.blockchain.params.BlockchainWithdrawalParams; import javax.ws.rs.*; @@ -85,4 +86,87 @@ List getWithdrawFunds(@QueryParam("from") Long startTime, @GET List depositHistory(@QueryParam("from") Long startTime, @QueryParam("to") Long endTime); + + /** + * Get a list orders + * + * @return live and historic orders, defaulting to live orders. Returns at most 100 results, use timestamp to + * paginate for further results + */ + @Path("/orders") + @GET + List getOrders(); + + /** + * Get a list orders by symbol + * + * @return live and historic orders, defaulting to live orders. Returns at most 100 results, use timestamp to + * paginate for further results + */ + @Path("/orders") + @GET + List getOrdersBySymbol(@QueryParam("symbol") String symbol) throws IOException, BlockchainException; + + /** + * Get a specific order + * + * @param orderId + * @return the order according to the orderId, 404 if not found + */ + @Path("/orders/{orderId}") + @GET + BlockchainOrder getOrder(@PathParam("orderId") String orderId) throws IOException, BlockchainException;; + + /** + * Add an order + * + * @param blockchainOrder + * @return a new order according to the provided parameters + */ + @Path("/orders") + @POST + @Consumes(MediaType.APPLICATION_JSON) + BlockchainOrder postOrder(BlockchainOrder blockchainOrder); + + /** + * Delete a specific order + * + * @param orderId + * @return status 200 if it was successfully removed or 400 if there was an error + * @throws IOException + * @throws BlockchainException + */ + @Path("/orders/{orderId}") + @DELETE + Void cancelOrder(@PathParam("orderId") String orderId) throws IOException, BlockchainException; + + /** + * Delete all open orders (of a symbol, if specified) + * + * @param symbol + * @return status 200 if it was successfully removed or 400 if there was an error + * @throws IOException + * @throws BlockchainException + */ + @Path("/orders") + @DELETE + Void cancelAllOrders(@QueryParam("symbol") String symbol) throws IOException, BlockchainException; + + /** + * Get a list of filled orders + * + * @param symbol + * @param startTime + * @param endTime + * @param limit + * @return filled orders, including partial fills. Returns at most 100 results, use timestamp to paginate for + * further results + */ + @Path("/trades") + @GET + List getTrades(@QueryParam("symbol") String symbol, + @QueryParam("from") Long startTime, + @QueryParam("to") Long endTime, + @QueryParam("limit") Integer limit); + } diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainConstants.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainConstants.java index 1c79fccdf8f..2d8cea04546 100644 --- a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainConstants.java +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainConstants.java @@ -11,18 +11,37 @@ public class BlockchainConstants { public static final String GET_FEES = "getFees"; public static final String GET_DEPOSIT_HISTORY = "depositHistory"; public static final String GET_WITHDRAWAL_HISTORY = "withdrawHistory"; + public static final String GET_ORDERS = "getOrders"; + public static final String GET_ORDER = "getOrder"; + public static final String POST_ORDER = "postOrder"; + public static final String CANCEL_ORDER = "cancelOrder"; + public static final String CANCEL_ALL_ORDERS = "cancelAllOrders"; public static final String GET_SYMBOLS = "getSymbols"; + public static final String GET_TRADES = "getTrades"; public static final String CURRENCY_PAIR_SYMBOL_FORMAT = "%s-%s"; public static final String X_API_TOKEN = "X-API-Token"; + public static final String XCHANGE = "XChange"; + public static final String X_API_INTEGRATION = "X_API_INTEGRATION"; public static final String WITHDRAWAL_EXCEPTION = "Invalid WithdrawFundsParams parameter. Only DefaultWithdrawFundsParams is supported."; public static final String EXCEPTION_MESSAGE = "Operation failed without any error message"; public static final String FUNDING_RECORD_TYPE_UNSUPPORTED = "Invalid FundingRecord parameter. Only DefaultWithdrawFundsParams is supported."; + public static final String CURRENCY_PAIR_EXCEPTION = "Invalid TradeHistoryParams type, it should be an instance of BlockchainTradeHistoryParams"; + public static final String OPEN = "OPEN"; public static final String REJECTED = "REJECTED"; public static final String REFUNDING = "REFUNDING"; public static final String PENDING = "PENDING"; public static final String FAILED = "FAILED"; public static final String COMPLETED = "COMPLETED"; public static final String UNCONFIRMED = "UNCONFIRMED"; + public static final String CANCELED = "CANCELED"; + public static final String FILLED = "FILLED"; + public static final String PART_FILLED = "PART_FILLED"; + public static final String EXPIRED = "EXPIRED"; public static final String STATUS_INVALID = "Unknown withdraw status: "; public static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; + public static final String MARKET = "MARKET"; + public static final String LIMIT = "LIMIT"; + public static final String STOP = "STOPLIMIT"; + public static final String BUY = "buy"; + public static final String SELL = "sell"; } diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainExchange.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainExchange.java index 12369f725ed..76198f88441 100644 --- a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainExchange.java +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/BlockchainExchange.java @@ -2,68 +2,79 @@ import org.knowm.xchange.BaseExchange; import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.blockchain.dto.account.BlockchainSymbol; import org.knowm.xchange.blockchain.service.BlockchainAccountService; +import org.knowm.xchange.blockchain.service.BlockchainAccountServiceRaw; +import org.knowm.xchange.blockchain.service.BlockchainTradeService; import org.knowm.xchange.client.ExchangeRestProxyBuilder; import org.knowm.xchange.client.ResilienceRegistries; +import org.knowm.xchange.exceptions.ExchangeException; import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import org.knowm.xchange.service.marketdata.MarketDataService; -import org.knowm.xchange.service.trade.TradeService; import si.mazi.rescu.SynchronizedValueFactory; import javax.ws.rs.HeaderParam; +import java.io.IOException; +import java.util.Map; import static org.knowm.xchange.blockchain.BlockchainConstants.*; -/** @author scuevas*/ +/** + * @author scuevas + */ public class BlockchainExchange extends BaseExchange { - protected static ResilienceRegistries RESILIENCE_REGISTRIES; + protected static ResilienceRegistries RESILIENCE_REGISTRIES; - protected BlockchainAuthenticated blockchain; + protected BlockchainAuthenticated blockchain; - @Override - protected void initServices() { - this.blockchain = ExchangeRestProxyBuilder - .forInterface(BlockchainAuthenticated.class, this.getExchangeSpecification()) - .clientConfigCustomizer(clientConfig -> clientConfig.addDefaultParam( - HeaderParam.class, X_API_TOKEN, - this.getExchangeSpecification().getSecretKey()) - ).build(); + @Override + protected void initServices() { + this.blockchain = ExchangeRestProxyBuilder + .forInterface(BlockchainAuthenticated.class, this.getExchangeSpecification()) + .clientConfigCustomizer(clientConfig -> { + clientConfig.addDefaultParam(HeaderParam.class, X_API_TOKEN, this.getExchangeSpecification().getSecretKey()); + clientConfig.addDefaultParam(HeaderParam.class, X_API_INTEGRATION, XCHANGE); + }).build(); - this.accountService = new BlockchainAccountService(this, this.blockchain, this.getResilienceRegistries()); - } + this.accountService = new BlockchainAccountService(this, this.blockchain, this.getResilienceRegistries()); + this.tradeService = new BlockchainTradeService(this, this.blockchain, this.getResilienceRegistries()); + } - @Override - public ExchangeSpecification getDefaultExchangeSpecification() { - ExchangeSpecification exchangeSpecification = new ExchangeSpecification(this.getClass()); - exchangeSpecification.setSslUri("https://api.blockchain.com"); - exchangeSpecification.setHost("www.blockchain.com"); - exchangeSpecification.setPort(80); - exchangeSpecification.setExchangeName("Blockchain Exchange"); - exchangeSpecification.setExchangeDescription("Blockchain Exchange"); - return exchangeSpecification; - } + @Override + public void remoteInit() throws IOException, ExchangeException { + BlockchainAccountServiceRaw dataService = + (BlockchainAccountServiceRaw) this.accountService; + Map markets = dataService.getSymbols(); + exchangeMetaData = BlockchainAdapters.adaptMetaData(markets); + } - @Override - public SynchronizedValueFactory getNonceFactory() { - throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET); - } + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification exchangeSpecification = new ExchangeSpecification(this.getClass()); + exchangeSpecification.setSslUri("https://api.blockchain.com"); + exchangeSpecification.setHost("www.blockchain.com"); + exchangeSpecification.setPort(80); + exchangeSpecification.setExchangeName("Blockchain Exchange"); + exchangeSpecification.setExchangeDescription("Blockchain Exchange"); + return exchangeSpecification; + } - @Override - public ResilienceRegistries getResilienceRegistries() { - if (RESILIENCE_REGISTRIES == null) { - RESILIENCE_REGISTRIES = BlockchainResilience.getResilienceRegistries(); + @Override + public SynchronizedValueFactory getNonceFactory() { + throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET); } - return RESILIENCE_REGISTRIES; - } - @Override - public MarketDataService getMarketDataService() { - throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET); - } + @Override + public ResilienceRegistries getResilienceRegistries() { + if (RESILIENCE_REGISTRIES == null) { + RESILIENCE_REGISTRIES = BlockchainResilience.getResilienceRegistries(); + } + return RESILIENCE_REGISTRIES; + } - @Override - public TradeService getTradeService() { - throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET); - } + @Override + public MarketDataService getMarketDataService() { + throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET); + } } diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/dto/account/BlockchainSymbol.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/dto/account/BlockchainSymbol.java index 8523b614c73..cc0e41b5b2f 100644 --- a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/dto/account/BlockchainSymbol.java +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/dto/account/BlockchainSymbol.java @@ -7,6 +7,8 @@ import lombok.extern.jackson.Jacksonized; import org.knowm.xchange.currency.Currency; +import java.math.BigDecimal; + @Data @Builder @Jacksonized @@ -26,11 +28,11 @@ public class BlockchainSymbol { @JsonProperty("min_price_increment_scale") private final Integer minPriceIncrementScale; @JsonProperty("min_order_size") - private final Long minOrderSize; + private final BigDecimal minOrderSize; @JsonProperty("min_order_size_scale") private final Integer minOrderSizeScale; @JsonProperty("max_order_size") - private final Integer maxOrderSize; + private final BigDecimal maxOrderSize; @JsonProperty("max_order_size_scale") private final Integer maxOrderSizeScale; @JsonProperty("lot_size") diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/dto/trade/BlockchainOrder.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/dto/trade/BlockchainOrder.java new file mode 100644 index 00000000000..91ee285612b --- /dev/null +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/dto/trade/BlockchainOrder.java @@ -0,0 +1,87 @@ +package org.knowm.xchange.blockchain.dto.trade; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.blockchain.serializer.BlockchainCurrencyPairSerializer; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.dto.trade.StopOrder; +import org.knowm.xchange.utils.jackson.CurrencyPairDeserializer; + +import java.math.BigDecimal; +import java.util.Date; + +import static org.knowm.xchange.blockchain.BlockchainConstants.*; +import static org.knowm.xchange.blockchain.BlockchainConstants.BUY; + +@Data +@Builder +@Jacksonized +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BlockchainOrder { + + @JsonSerialize(using = BlockchainCurrencyPairSerializer.class) + @JsonDeserialize(using = CurrencyPairDeserializer.class) + private final CurrencyPair symbol; + private final Long exOrdId; + private final String clOrdId; + private final String ordType; + private final String ordStatus; + private final BigDecimal orderQty; + private final String side; + private final String text; + private final BigDecimal price; + private final BigDecimal lastShares; + private final BigDecimal lastPx; + private final BigDecimal leavesQty; + private final BigDecimal cumQty; + private final BigDecimal avgPx; + private final BigDecimal stopPx; + private final Date timestamp; + + @JsonIgnore + public boolean isMarketOrder() { + return MARKET.equals(ordType); + } + + @JsonIgnore + public boolean isLimitOrder() { + return LIMIT.equals(ordType); + } + + @JsonIgnore + public boolean isBuyer() { + return BUY.equals(this.side.toUpperCase()); + } + + @JsonIgnore + public Order.OrderType getOrderType(){ + return isBuyer() ? Order.OrderType.BID : Order.OrderType.ASK; + } + + @JsonIgnore + public Order.Builder getOrderBuilder(){ + final CurrencyPair symbol = this.getSymbol(); + Order.Builder builder; + + if (this.isMarketOrder()) { + builder = new MarketOrder.Builder(getOrderType(), symbol); + } else if (this.isLimitOrder()){ + builder = new LimitOrder.Builder(getOrderType(), symbol).limitPrice(this.getPrice()); + } else { + builder = new StopOrder.Builder(getOrderType(), symbol).stopPrice(this.getPrice()); + } + + return builder; + } + +} diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/params/BlockchainTradeHistoryParams.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/params/BlockchainTradeHistoryParams.java new file mode 100644 index 00000000000..af07b9cad1a --- /dev/null +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/params/BlockchainTradeHistoryParams.java @@ -0,0 +1,26 @@ +package org.knowm.xchange.blockchain.params; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.service.trade.params.TradeHistoryParamCurrencyPair; +import org.knowm.xchange.service.trade.params.TradeHistoryParamLimit; +import org.knowm.xchange.service.trade.params.TradeHistoryParamsTimeSpan; +import java.util.Date; + +@Data +@Builder +@Jacksonized +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BlockchainTradeHistoryParams + implements TradeHistoryParamCurrencyPair, TradeHistoryParamLimit, TradeHistoryParamsTimeSpan { + + private CurrencyPair currencyPair; + private Integer limit; + private Date startTime; + private Date endTime; +} diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/serializer/BlockchainCurrencyPairSerializer.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/serializer/BlockchainCurrencyPairSerializer.java new file mode 100644 index 00000000000..81ae9a66c41 --- /dev/null +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/serializer/BlockchainCurrencyPairSerializer.java @@ -0,0 +1,17 @@ +package org.knowm.xchange.blockchain.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.knowm.xchange.blockchain.BlockchainAdapters; +import org.knowm.xchange.currency.CurrencyPair; + +import java.io.IOException; + +public class BlockchainCurrencyPairSerializer extends JsonSerializer { + + @Override + public void serialize(CurrencyPair currencyPair, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(BlockchainAdapters.toSymbol(currencyPair)); + } +} diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/service/BlockchainTradeService.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/service/BlockchainTradeService.java new file mode 100644 index 00000000000..2c3b8bb79ac --- /dev/null +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/service/BlockchainTradeService.java @@ -0,0 +1,195 @@ +package org.knowm.xchange.blockchain.service; + +import org.knowm.xchange.blockchain.BlockchainAdapters; +import org.knowm.xchange.blockchain.BlockchainAuthenticated; +import org.knowm.xchange.blockchain.BlockchainErrorAdapter; +import org.knowm.xchange.blockchain.BlockchainExchange; +import org.knowm.xchange.blockchain.dto.BlockchainException; +import org.knowm.xchange.blockchain.dto.trade.BlockchainOrder; +import org.knowm.xchange.blockchain.params.BlockchainTradeHistoryParams; +import org.knowm.xchange.client.ResilienceRegistries; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.account.OpenPositions; +import org.knowm.xchange.dto.trade.*; +import org.knowm.xchange.exceptions.ExchangeException; +import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; +import org.knowm.xchange.service.trade.TradeService; +import org.knowm.xchange.service.trade.params.*; +import org.knowm.xchange.service.trade.params.orders.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.knowm.xchange.blockchain.BlockchainConstants.*; + +public class BlockchainTradeService extends BlockchainTradeServiceRaw implements TradeService { + + public BlockchainTradeService(BlockchainExchange exchange, BlockchainAuthenticated blockchainApi, ResilienceRegistries resilienceRegistries) { + super(exchange, blockchainApi, resilienceRegistries); + } + + @Override + public OpenOrders getOpenOrders() throws IOException { + return getOpenOrders(createOpenOrdersParams()); + } + + @Override + public OpenOrders getOpenOrders(OpenOrdersParams params) throws IOException { + try { + CurrencyPair currencyPair = null; + if (params instanceof OpenOrdersParamCurrencyPair) { + currencyPair = ((OpenOrdersParamCurrencyPair) params).getCurrencyPair(); + } + + if(currencyPair != null) { + return BlockchainAdapters.toOpenOrders(this.getOrdersBySymbol(BlockchainAdapters.toSymbol(currencyPair))); + } + return BlockchainAdapters.toOpenOrders(this.getOrders()); + } catch (BlockchainException e) { + throw BlockchainErrorAdapter.adapt(e); + } + } + + @Override + public String placeLimitOrder(LimitOrder limitOrder) throws IOException { + try { + BlockchainOrder order = this.postOrder(BlockchainAdapters.toBlockchainLimitOrder(limitOrder)); + if (REJECTED.equals(order.getOrdStatus())) throw new ExchangeException(order.getText()); + return Long.toString(order.getExOrdId()); + } catch (BlockchainException e) { + throw BlockchainErrorAdapter.adapt(e); + } + } + + @Override + public String placeMarketOrder(MarketOrder marketOrder) throws IOException { + try { + BlockchainOrder order = this.postOrder(BlockchainAdapters.toBlockchainMarketOrder(marketOrder)); + if (REJECTED.equals(order.getOrdStatus())) throw new ExchangeException(order.getText()); + return Long.toString(order.getExOrdId()); + } catch (BlockchainException e) { + throw BlockchainErrorAdapter.adapt(e); + } + } + + @Override + public String placeStopOrder(StopOrder stopOrder) throws IOException { + try { + BlockchainOrder order = this.postOrder(BlockchainAdapters.toBlockchainStopOrder(stopOrder)); + if (REJECTED.equals(order.getOrdStatus())) throw new ExchangeException(order.getText()); + return Long.toString(order.getExOrdId()); + } catch (BlockchainException e) { + throw BlockchainErrorAdapter.adapt(e); + } + + } + + @Override + public OpenOrdersParams createOpenOrdersParams() { + return new DefaultOpenOrdersParamCurrencyPair(); + } + + @Override + public boolean cancelOrder(String orderId) throws IOException { + return cancelOrderById(orderId); + } + + @Override + public boolean cancelOrder(CancelOrderParams orderParams) throws IOException { + if (orderParams instanceof CancelOrderByCurrencyPair) { + return this.cancelAllOrdersBySymbol(BlockchainAdapters.toSymbol(((CancelOrderByCurrencyPair) orderParams).getCurrencyPair())); + } + + if (orderParams instanceof CancelOrderByIdParams) { + return cancelOrder(((CancelOrderByIdParams) orderParams).getOrderId()); + } + + return false; + } + + @Override + public Class[] getRequiredCancelOrderParamClasses() { + return new Class[]{CancelOrderByIdParams.class, CancelOrderByCurrencyPair.class}; + } + + @Override + public OpenPositions getOpenPositions() { + throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET); + } + + @Override + public UserTrades getTradeHistory(TradeHistoryParams params) throws IOException { + try { + Long startTime = null; + Long endTime = null; + Integer limit = null; + + if (!(params instanceof TradeHistoryParamCurrencyPair)) { + throw new ExchangeException(CURRENCY_PAIR_EXCEPTION); + } + + String symbol = BlockchainAdapters.toSymbol( + ((TradeHistoryParamCurrencyPair) params).getCurrencyPair()); + + if (params instanceof TradeHistoryParamsTimeSpan) { + if (((TradeHistoryParamsTimeSpan) params).getStartTime() != null) { + startTime = ((TradeHistoryParamsTimeSpan) params).getStartTime().getTime(); + } + if (((TradeHistoryParamsTimeSpan) params).getEndTime() != null) { + endTime = ((TradeHistoryParamsTimeSpan) params).getEndTime().getTime(); + } + } + + if (params instanceof TradeHistoryParamLimit) { + TradeHistoryParamLimit limitParams = (TradeHistoryParamLimit) params; + limit = limitParams.getLimit(); + } + + List tradesOrders = this.getTrades(symbol, startTime, endTime, limit); + return BlockchainAdapters.toUserTrades(tradesOrders); + } catch (BlockchainException e) { + throw BlockchainErrorAdapter.adapt(e); + } + } + + @Override + public BlockchainTradeHistoryParams createTradeHistoryParams() { + return BlockchainTradeHistoryParams.builder().build(); + } + + @Override + public Class getRequiredOrderQueryParamClass() { + return OrderQueryParamCurrencyPair.class; + } + + @Override + public Collection getOrder(String... orderIds) throws IOException { + List openOrders = new ArrayList<>(); + + for (String orderId : orderIds) { + if (orderId != null) { + openOrders.addAll(getOrder(new DefaultQueryOrderParam(orderId))); + } + } + return openOrders; + } + + @Override + public Collection getOrder(OrderQueryParams... params) throws IOException { + try { + Collection orders = new ArrayList<>(); + for (OrderQueryParams param : params) { + BlockchainOrder order = this.getOrder(param.getOrderId()); + orders.add(BlockchainAdapters.toOpenOrdersById(order)); + } + return orders; + } catch (BlockchainException e) { + throw BlockchainErrorAdapter.adapt(e); + } + } + + +} diff --git a/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/service/BlockchainTradeServiceRaw.java b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/service/BlockchainTradeServiceRaw.java new file mode 100644 index 00000000000..41177e66bf6 --- /dev/null +++ b/xchange-blockchain/src/main/java/org/knowm/xchange/blockchain/service/BlockchainTradeServiceRaw.java @@ -0,0 +1,85 @@ +package org.knowm.xchange.blockchain.service; + +import org.knowm.xchange.blockchain.BlockchainAuthenticated; +import org.knowm.xchange.blockchain.BlockchainErrorAdapter; +import org.knowm.xchange.blockchain.BlockchainExchange; +import org.knowm.xchange.blockchain.dto.BlockchainException; +import org.knowm.xchange.blockchain.dto.trade.BlockchainOrder; +import org.knowm.xchange.client.ResilienceRegistries; + +import java.io.IOException; +import java.util.List; + +import static org.knowm.xchange.blockchain.BlockchainConstants.*; + +public class BlockchainTradeServiceRaw extends BlockchainBaseService { + + protected BlockchainTradeServiceRaw(BlockchainExchange exchange, BlockchainAuthenticated blockchainApi, ResilienceRegistries resilienceRegistries) { + super(exchange, blockchainApi, resilienceRegistries); + } + + protected List getOrders() throws IOException, BlockchainException { + return decorateApiCall(this.blockchainApi::getOrders) + .withRetry(retry(GET_ORDERS)) + .withRateLimiter(rateLimiter(ENDPOINT_RATE_LIMIT)) + .call(); + } + + protected List getOrdersBySymbol(String symbol) throws IOException, BlockchainException { + return decorateApiCall(() -> this.blockchainApi.getOrdersBySymbol(symbol)) + .withRetry(retry(GET_ORDERS)) + .withRateLimiter(rateLimiter(ENDPOINT_RATE_LIMIT)) + .call(); + } + + protected BlockchainOrder getOrder(String orderId) throws IOException { + try { + return decorateApiCall(() -> this.blockchainApi.getOrder(orderId)) + .withRetry(retry(GET_ORDER)) + .withRateLimiter(rateLimiter(ENDPOINT_RATE_LIMIT)) + .call(); + }catch (BlockchainException e){ + throw BlockchainErrorAdapter.adapt(e); + } + + } + + protected BlockchainOrder postOrder(BlockchainOrder blockchainOrder) throws IOException{ + return decorateApiCall(() -> this.blockchainApi.postOrder(blockchainOrder)) + .withRetry(retry(POST_ORDER)) + .withRateLimiter(rateLimiter(ENDPOINT_RATE_LIMIT)) + .call(); + } + + protected Boolean cancelOrderById(String orderId) throws IOException, BlockchainException{ + try { + decorateApiCall(() -> this.blockchainApi.cancelOrder(orderId)) + .withRetry(retry(CANCEL_ORDER)) + .withRateLimiter(rateLimiter(ENDPOINT_RATE_LIMIT)) + .call(); + return true; + }catch (BlockchainException e){ + throw BlockchainErrorAdapter.adapt(e); + } + + } + + protected Boolean cancelAllOrdersBySymbol(String symbol) throws IOException, BlockchainException{ + try { + decorateApiCall(() -> this.blockchainApi.cancelAllOrders(symbol)) + .withRetry(retry(CANCEL_ALL_ORDERS)) + .withRateLimiter(rateLimiter(ENDPOINT_RATE_LIMIT)) + .call(); + return true; + }catch (BlockchainException e){ + throw BlockchainErrorAdapter.adapt(e); + } + } + + protected List getTrades(String symbol, Long startTime, Long endTime, Integer limit) throws IOException { + return decorateApiCall(() -> this.blockchainApi.getTrades(symbol, startTime, endTime, limit)) + .withRetry(retry(GET_TRADES)) + .withRateLimiter(rateLimiter(ENDPOINT_RATE_LIMIT)) + .call(); + } +} diff --git a/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/BlockchainBaseTest.java b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/BlockchainBaseTest.java index 87b4c263908..47de172b2c1 100644 --- a/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/BlockchainBaseTest.java +++ b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/BlockchainBaseTest.java @@ -6,6 +6,11 @@ import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.blockchain.BlockchainExchange; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static org.knowm.xchange.blockchain.service.utils.BlockchainConstants.APPLICATION; +import static org.knowm.xchange.blockchain.service.utils.BlockchainConstants.CONTENT_TYPE; + public class BlockchainBaseTest { @ClassRule @@ -23,4 +28,33 @@ protected static BlockchainExchange createExchange() { exchange.applySpecification(specification); return exchange; } + + protected void stubPost(String fileName, int statusCode, String url) { + stubFor( + post(urlPathEqualTo(url)) + .willReturn( + aResponse() + .withStatus(statusCode) + .withHeader(CONTENT_TYPE, APPLICATION) + .withBodyFile(fileName))); + } + + protected void stubGet(String fileName, int statusCode, String url) { + stubFor( + get(urlPathEqualTo(url)) + .willReturn( + aResponse() + .withStatus(statusCode) + .withHeader(CONTENT_TYPE, APPLICATION) + .withBodyFile(fileName))); + } + + protected void stubDelete(int statusCode, String url) { + stubFor( + delete(urlPathEqualTo(url)) + .willReturn( + aResponse() + .withStatus(statusCode) + .withHeader(CONTENT_TYPE, APPLICATION))); + } } diff --git a/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/account/AccountServiceTest.java b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/account/AccountServiceTest.java index 5d82ebc73e3..5c96171ef0d 100644 --- a/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/account/AccountServiceTest.java +++ b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/account/AccountServiceTest.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; -import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; import static org.knowm.xchange.blockchain.service.utils.BlockchainConstants.*; @@ -61,7 +60,7 @@ public void withdrawFailure() { @Test(timeout = 2000) public void withdrawException() { - Throwable exception = catchThrowable(() -> withdrawExcept()); + Throwable exception = catchThrowable(this::withdrawExcept); assertThat(exception) .isInstanceOf(NotYetImplementedForExchangeException.class) .hasMessage(NOT_IMPLEMENTED_YET); @@ -87,9 +86,7 @@ public void getWithdrawFundingHistorySuccess() throws Exception { Assert.assertNotNull(response); response.forEach( - record -> { - Assert.assertTrue( record.getAmount().compareTo(BigDecimal.ZERO) > 0); - }); + record -> Assert.assertTrue(record.getAmount().compareTo(BigDecimal.ZERO) > 0)); } @Test(timeout = 2000) @@ -98,9 +95,7 @@ public void getDepositFundingHistorySuccess() throws Exception { Assert.assertNotNull(response); response.forEach( - record -> { - Assert.assertTrue( record.getAmount().compareTo(BigDecimal.ZERO) > 0); - }); + record -> Assert.assertTrue(record.getAmount().compareTo(BigDecimal.ZERO) > 0)); } @Test(timeout = 2000) @@ -127,9 +122,9 @@ private String withdraw(String responseFileName, int statusCode) throws IOExcept return service.withdrawFunds(params); } - private String withdrawExcept() throws IOException { + private void withdrawExcept() throws IOException { stubPost(WITHDRAWAL_SUCCESS_JSON, 200, URL_WITHDRAWALS); - return service.withdrawFunds( + service.withdrawFunds( Currency.BTC, BigDecimal.valueOf(0.005), ADDRESS); } @@ -166,24 +161,4 @@ private Map tradingFees() throws IOException { return service.getDynamicTradingFees(); } - private void stubPost(String fileName, int statusCode, String url) { - stubFor( - post(urlPathEqualTo(url)) - .willReturn( - aResponse() - .withStatus(statusCode) - .withHeader("Content-Type", "application/json") - .withBodyFile(fileName))); - } - - private void stubGet(String fileName, int statusCode, String url) { - stubFor( - get(urlPathEqualTo(url)) - .willReturn( - aResponse() - .withStatus(statusCode) - .withHeader("Content-Type", "application/json") - .withBodyFile(fileName))); - } - } diff --git a/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/trade/TradeServiceTest.java b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/trade/TradeServiceTest.java new file mode 100644 index 00000000000..8b0b38224c9 --- /dev/null +++ b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/trade/TradeServiceTest.java @@ -0,0 +1,177 @@ +package org.knowm.xchange.blockchain.service.trade; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.knowm.xchange.blockchain.BlockchainExchange; +import org.knowm.xchange.blockchain.params.BlockchainTradeHistoryParams; +import org.knowm.xchange.blockchain.service.BlockchainBaseTest; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.trade.*; +import org.knowm.xchange.exceptions.RateLimitExceededException; +import org.knowm.xchange.service.trade.TradeService; +import org.knowm.xchange.service.trade.params.CancelOrderByCurrencyPair; +import org.knowm.xchange.service.trade.params.TradeHistoryParamsTimeSpan; +import si.mazi.rescu.HttpStatusIOException; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.knowm.xchange.blockchain.service.utils.BlockchainConstants.*; + +public class TradeServiceTest extends BlockchainBaseTest { + private TradeService service; + + @Before + public void init() { + BlockchainExchange exchange = createExchange(); + service = exchange.getTradeService(); + } + + @Test(timeout = 2000) + public void getOpenOrdersSuccess() throws Exception { + stubGet(ORDERS_JSON, 200, URL_ORDERS); + OpenOrders response = service.getOpenOrders(); + assertThat(response).isNotNull(); + List allOrders = response.getAllOpenOrders(); + assertThat(allOrders).isNotEmpty(); + Order order = allOrders.get(0); + assertThat(order).isNotNull(); + assertThat(order.getOriginalAmount()).isNotNull().isPositive(); + assertThat(order.getId()).isEqualTo(ORDER_ID); + } + + @Test(timeout = 2000) + public void placeLimitOrderSuccess() throws Exception { + String response = placeLimitOrder(); + assertThat(response).isEqualTo(ORDER_ID); + } + + @Test(timeout = 2000) + public void placeMarketOrderSuccess() throws Exception { + String response = placeMarketOrder(); + assertThat(response).isEqualTo(MARKET_ORDER_ID); + } + + @Test(timeout = 2000) + public void placeStopOrderSuccess() throws Exception { + String response = placeStopOrder(); + assertThat(response).isEqualTo(STOP_ORDER_ID); + } + + @Test(timeout = 2000) + public void cancelOrderSuccess() throws Exception { + Boolean response = cancelOrder(200); + assertThat(response).isEqualTo(true); + } + + @Test(timeout = 2000) + public void cancelOrderFailure() { + Throwable exception = catchThrowable(() -> cancelOrder(400)); + assertThat(exception) + .isInstanceOf(HttpStatusIOException.class) + .hasMessage(HTTP_CODE_400); + } + + @Test(timeout = 2000) + public void cancelOrderByCurrency() throws Exception { + CancelOrderByCurrencyPair cancelOrderByCurrencyPair = () -> new CurrencyPair("BTC/USD"); + Boolean response = cancelAllOrder(cancelOrderByCurrencyPair); + assertThat(response).isEqualTo(true); + } + + @Test(timeout = 2000) + public void getTrades() throws Exception { + BlockchainTradeHistoryParams params = (BlockchainTradeHistoryParams) service.createTradeHistoryParams(); + ((TradeHistoryParamsTimeSpan) params).setStartTime( + new Date(System.currentTimeMillis() - END_TIME)); + + params.setCurrencyPair(CurrencyPair.BTC_USDT); + + stubGet(ORDERS_JSON, 200, URL_TRADES); + UserTrades response = service.getTradeHistory(params); + assertThat(response).isNotNull(); + List userTrades = response.getUserTrades(); + assertThat(userTrades).isNotEmpty(); + UserTrade trade = userTrades.get(0); + assertThat(trade).isNotNull(); + assertThat(trade.getOriginalAmount()).isNotNull().isPositive(); + assertThat(trade.getPrice()).isNotNull().isPositive(); + } + + @Test(timeout = 2000) + public void getOrderSuccess() throws Exception { + stubGet(NEW_ORDER_LIMIT_JSON, 200, URL_ORDERS_BY_ID_1); + stubGet(NEW_ORDER_MARKET_JSON, 200, URL_ORDERS_BY_ID_2); + Collection response = service.getOrder("11111111", "22222222"); + assertThat(response).isNotNull(); + assertThat(response).isNotEmpty(); + response.forEach( + record -> Assert.assertTrue(record.getOriginalAmount().compareTo(BigDecimal.ZERO) > 0)); + + } + + @Test(timeout = 2000) + public void getOrderFailure() { + stubGet(ORDER_NOT_FOUND_JSON, 404, URL_ORDERS_BY_ID); + Throwable exception = catchThrowable(() -> service.getOrder("111111211")); + assertThat(exception) + .isInstanceOf(RateLimitExceededException.class) + .hasMessage(STATUS_CODE_404); + + } + + private String placeLimitOrder() throws IOException { + stubPost(NEW_ORDER_LIMIT_JSON, 200, URL_ORDERS); + + LimitOrder limitOrder = + new LimitOrder.Builder(Order.OrderType.BID, CurrencyPair.BTC_USDT) + .originalAmount(new BigDecimal("45.0")) + .limitPrice(new BigDecimal("0.23")) + .build(); + + return service.placeLimitOrder(limitOrder); + } + + private String placeMarketOrder() throws IOException { + stubPost(NEW_ORDER_MARKET_JSON, 200, URL_ORDERS); + + MarketOrder marketOrder = + new MarketOrder.Builder(Order.OrderType.BID, CurrencyPair.BTC_USDT) + .originalAmount(new BigDecimal("15.0")) + .cumulativeAmount(new BigDecimal("0.22")) + .build(); + + return service.placeMarketOrder(marketOrder); + } + + private String placeStopOrder() throws IOException { + stubPost(NEW_ORDER_STOP_JSON, 200, URL_ORDERS); + + StopOrder stopOrder = + new StopOrder.Builder(Order.OrderType.BID, CurrencyPair.BTC_USDT) + .originalAmount(new BigDecimal("67.0")) + .stopPrice(new BigDecimal("0.21")) + .build(); + + return service.placeStopOrder(stopOrder); + } + + private Boolean cancelOrder(int statusCode) throws IOException { + stubDelete(statusCode, URL_ORDERS_BY_ID_1); + + return service.cancelOrder(ORDER_ID); + } + + private Boolean cancelAllOrder(CancelOrderByCurrencyPair orderParams) throws IOException { + stubDelete(200, URL_ORDERS); + + return service.cancelOrder(orderParams); + } +} diff --git a/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/utils/BlockchainConstants.java b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/utils/BlockchainConstants.java index 7923975583b..690e56291e1 100644 --- a/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/utils/BlockchainConstants.java +++ b/xchange-blockchain/src/test/java/org/knowm/xchange/blockchain/service/utils/BlockchainConstants.java @@ -10,12 +10,24 @@ public class BlockchainConstants { public static final String URL_SYMBOLS = "/v3/exchange/symbols"; public static final String URL_DEPOSIT_BY_CURRENCY = "/v3/exchange/deposits/BTC"; public static final String URL_WITHDRAWALS = "/v3/exchange/withdrawals"; + public static final String URL_ORDERS = "/v3/exchange/orders"; + public static final String URL_ORDERS_BY_ID_1 = "/v3/exchange/orders/11111111"; + public static final String URL_ORDERS_BY_ID_2 = "/v3/exchange/orders/22222222"; + public static final String URL_ORDERS_BY_ID = "/v3/exchange/orders/111111211"; + public static final String URL_TRADES = "/v3/exchange/trades"; public static final String WITHDRAWAL_ID = "3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ"; public static final String STATUS_CODE_401 = "Unauthorized (HTTP status code: 401)"; public static final String STATUS_CODE_400 = "Bad Request (HTTP status code: 400)"; + public static final String STATUS_CODE_404 = " (HTTP status code: 404)"; + public static final String HTTP_CODE_400 = "HTTP status code was not OK: 400"; public static final String ADDRESS = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; public static final String ADDRESS_DEPOSIT = "3CrbF4Z45fnJs62jFs1p3LkR8KiZSGKJFL"; public static final String ACCOUNT_INFORMATION_JSON = "accountInformation.json"; + public static final String ORDERS_JSON = "orders.json"; + public static final String NEW_ORDER_MARKET_JSON = "new_order_market.json"; + public static final String NEW_ORDER_LIMIT_JSON = "new_order_limit.json"; + public static final String NEW_ORDER_STOP_JSON = "new_order_stop.json"; + public static final String ORDER_NOT_FOUND_JSON = "order_not_found.json"; public static final String DEPOSIT_SUCCESS_JSON = "deposit-success.json"; public static final String WITHDRAWAL_SUCCESS_JSON = "withdraw-success.json"; public static final String WITHDRAWAL_FAILURE_JSON = "withdraw-failure.json"; @@ -26,4 +38,10 @@ public class BlockchainConstants { public static final String DEPOSIT_FAILURE_JSON = "deposit-failure.json"; public static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; public static final String BENEFICIARY = "ea1f34b3-e77a-4646-9cfa-5d6d3518c6d3"; + public static final String ORDER_ID = "11111111"; + public static final String MARKET_ORDER_ID = "22222222"; + public static final String STOP_ORDER_ID = "33333333"; + public static final Long END_TIME = 12 * 30 * 24 * 60 * 60 * 1000L; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String APPLICATION = "application/json"; } diff --git a/xchange-blockchain/src/test/resources/__files/new_order_limit.json b/xchange-blockchain/src/test/resources/__files/new_order_limit.json new file mode 100644 index 00000000000..4d5b33a1fb9 --- /dev/null +++ b/xchange-blockchain/src/test/resources/__files/new_order_limit.json @@ -0,0 +1,16 @@ +{ + "exOrdId": 11111111, + "clOrdId": "ABC", + "ordType": "LIMIT", + "ordStatus": "FILLED", + "side": "BUY", + "price": 0.12345, + "text": "string", + "symbol": "BTC-USD", + "lastShares": 0.5678, + "lastPx": 3500.12, + "leavesQty": 10, + "cumQty": 0.123345, + "avgPx": 345.33, + "timestamp": 1592830770594 +} \ No newline at end of file diff --git a/xchange-blockchain/src/test/resources/__files/new_order_market.json b/xchange-blockchain/src/test/resources/__files/new_order_market.json new file mode 100644 index 00000000000..33677fae2f8 --- /dev/null +++ b/xchange-blockchain/src/test/resources/__files/new_order_market.json @@ -0,0 +1,16 @@ +{ + "exOrdId": 22222222, + "clOrdId": "ABC", + "ordType": "MARKET", + "ordStatus": "FILLED", + "side": "BUY", + "price": 0.12345, + "text": "string", + "symbol": "BTC-USD", + "lastShares": 0.5678, + "lastPx": 3500.12, + "leavesQty": 10, + "cumQty": 0.123345, + "avgPx": 345.33, + "timestamp": 1592830770594 +} \ No newline at end of file diff --git a/xchange-blockchain/src/test/resources/__files/new_order_stop.json b/xchange-blockchain/src/test/resources/__files/new_order_stop.json new file mode 100644 index 00000000000..911b6514878 --- /dev/null +++ b/xchange-blockchain/src/test/resources/__files/new_order_stop.json @@ -0,0 +1,16 @@ +{ + "exOrdId": 33333333, + "clOrdId": "ABC", + "ordType": "STOP", + "ordStatus": "FILLED", + "side": "BUY", + "price": 0.12345, + "text": "string", + "symbol": "BTC-USD", + "lastShares": 0.5678, + "lastPx": 3500.12, + "leavesQty": 10, + "cumQty": 0.123345, + "avgPx": 345.33, + "timestamp": 1592830770594 +} \ No newline at end of file diff --git a/xchange-blockchain/src/test/resources/__files/order_not_found.json b/xchange-blockchain/src/test/resources/__files/order_not_found.json new file mode 100644 index 00000000000..6f7472cac4c --- /dev/null +++ b/xchange-blockchain/src/test/resources/__files/order_not_found.json @@ -0,0 +1 @@ +{"timestamp":"2022-06-16T18:55:35.753+00:00","status":404,"error":"Not Found","message":"","path":"/orders/111111211"} \ No newline at end of file diff --git a/xchange-blockchain/src/test/resources/__files/orders.json b/xchange-blockchain/src/test/resources/__files/orders.json new file mode 100644 index 00000000000..e86d131e0d0 --- /dev/null +++ b/xchange-blockchain/src/test/resources/__files/orders.json @@ -0,0 +1,18 @@ +[ + { + "exOrdId": 11111111, + "clOrdId": "ABC", + "ordType": "MARKET", + "ordStatus": "FILLED", + "side": "BUY", + "price": 0.12345, + "text": "string", + "symbol": "BTC-USD", + "lastShares": 0.5678, + "lastPx": 3500.12, + "leavesQty": 10, + "cumQty": 0.123345, + "avgPx": 345.33, + "timestamp": 1592830770594 + } +] \ No newline at end of file diff --git a/xchange-examples/src/main/java/org/knowm/xchange/examples/blockchain/BlockchainDemoUtils.java b/xchange-examples/src/main/java/org/knowm/xchange/examples/blockchain/BlockchainDemoUtils.java index 975a8afcd0c..3860d2e76ab 100644 --- a/xchange-examples/src/main/java/org/knowm/xchange/examples/blockchain/BlockchainDemoUtils.java +++ b/xchange-examples/src/main/java/org/knowm/xchange/examples/blockchain/BlockchainDemoUtils.java @@ -5,9 +5,17 @@ import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.blockchain.BlockchainExchange; +import java.math.BigDecimal; + public class BlockchainDemoUtils { public static final Long END_TIME = 12 * 30 * 24 * 60 * 60 * 1000L; public static final String BENEFICIARY = "8a2e42ee-c94a-4641-9208-9501cbc0fed0"; + public static final String SYMBOL = "BTC/USDT"; + public static final BigDecimal AMOUNT = new BigDecimal("0.01"); + public static final BigDecimal AMOUNT_LIMIT = new BigDecimal("10"); + public static final BigDecimal STOP_PRICE = new BigDecimal("0.4"); + public static final BigDecimal STOP_LIMIT = new BigDecimal("2000"); + public static final BigDecimal STOP_LIMIT_PRICE = new BigDecimal("0.5"); public static Exchange createExchange() { Exchange bcd = ExchangeFactory.INSTANCE.createExchange(BlockchainExchange.class); diff --git a/xchange-examples/src/main/java/org/knowm/xchange/examples/blockchain/trade/BlockchainTradeDemo.java b/xchange-examples/src/main/java/org/knowm/xchange/examples/blockchain/trade/BlockchainTradeDemo.java new file mode 100644 index 00000000000..3c28c7cfb86 --- /dev/null +++ b/xchange-examples/src/main/java/org/knowm/xchange/examples/blockchain/trade/BlockchainTradeDemo.java @@ -0,0 +1,122 @@ +package org.knowm.xchange.examples.blockchain.trade; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.blockchain.params.BlockchainTradeHistoryParams; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.trade.*; +import org.knowm.xchange.examples.blockchain.BlockchainDemoUtils; +import org.knowm.xchange.service.trade.TradeService; +import org.knowm.xchange.service.trade.params.CancelOrderByCurrencyPair; +import org.knowm.xchange.service.trade.params.TradeHistoryParamsTimeSpan; +import org.knowm.xchange.service.trade.params.orders.OpenOrdersParamCurrencyPair; + +import java.io.IOException; +import java.util.Collection; +import java.util.Date; + +import static org.knowm.xchange.examples.blockchain.BlockchainDemoUtils.*; + +public class BlockchainTradeDemo { + private static final Exchange BLOCKCHAIN_EXCHANGE = BlockchainDemoUtils.createExchange(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + private static final CurrencyPair usdtUsd = new CurrencyPair(Currency.USDT, Currency.USD); + private static final TradeService tradeService = BLOCKCHAIN_EXCHANGE.getTradeService(); + + public static void main(String[] args) throws IOException, InterruptedException { + System.out.println("===== TRADE SERVICE ====="); + tradeServiceDemo(); + } + + private static void tradeServiceDemo() throws InterruptedException, IOException { + System.out.println("===== placeLimitOrder ====="); + executeOrder(limitOrder()); + + System.out.println("===== placeMarketOrder ====="); + executeOrder(marketOrder()); + + System.out.println("===== placeStopOrder ====="); + executeOrder(stopOrder()); + } + + public static void executeOrder(String orderId) throws InterruptedException, IOException{ + Thread.sleep(5000); + + System.out.println("===== getOpenOrders by symbol ====="); + final OpenOrdersParamCurrencyPair openOrdersParamsBtcUsd = + (OpenOrdersParamCurrencyPair) tradeService.createOpenOrdersParams(); + openOrdersParamsBtcUsd.setCurrencyPair(CurrencyPair.BTC_USDT); + OpenOrders openOrdersParams = tradeService.getOpenOrders(openOrdersParamsBtcUsd); + System.out.println(OBJECT_MAPPER.writeValueAsString(openOrdersParams)); + + System.out.println("===== getOpenOrders ====="); + OpenOrders openOrders = tradeService.getOpenOrders(); + System.out.println(OBJECT_MAPPER.writeValueAsString(openOrders)); + + System.out.println("===== getTradeHistory ====="); + BlockchainTradeHistoryParams params = (BlockchainTradeHistoryParams) tradeService.createTradeHistoryParams(); + ((TradeHistoryParamsTimeSpan) params).setStartTime( + new Date(System.currentTimeMillis() - END_TIME)); + + params.setCurrencyPair(usdtUsd); + + UserTrades tradeHistory = tradeService.getTradeHistory(params); + System.out.println(OBJECT_MAPPER.writeValueAsString(tradeHistory)); + + System.out.println("===== getOrder ====="); + Collection getOrder = tradeService.getOrder(orderId); + System.out.println(OBJECT_MAPPER.writeValueAsString(getOrder)); + + System.out.println("===== cancelOrder by id ====="); + System.out.println("Canceling returned " + tradeService.cancelOrder(orderId)); + + System.out.println("===== cancelOrder by symbol ====="); + CancelOrderByCurrencyPair cancelOrderByCurrencyPair = () -> new CurrencyPair(SYMBOL); + boolean cancelAllOrderByCurrency = tradeService.cancelOrder(cancelOrderByCurrencyPair); + System.out.println("Canceling returned " + cancelAllOrderByCurrency); + } + + public static String limitOrder() throws IOException { + LimitOrder limitOrder = + new LimitOrder.Builder(Order.OrderType.BID, CurrencyPair.BTC_USDT) + .originalAmount(AMOUNT) + .limitPrice(STOP_LIMIT) + .build(); + + tradeService.verifyOrder(limitOrder); + String tradeLimitOrder = tradeService.placeLimitOrder(limitOrder); + System.out.println(OBJECT_MAPPER.writeValueAsString(tradeLimitOrder)); + + return tradeLimitOrder; + } + + public static String marketOrder() throws IOException { + MarketOrder marketOrder = + new MarketOrder.Builder(Order.OrderType.BID, CurrencyPair.BTC_USDT) + .originalAmount(AMOUNT) + .build(); + + tradeService.verifyOrder(marketOrder); + String tradeMarketOrder = tradeService.placeMarketOrder(marketOrder); + System.out.println(OBJECT_MAPPER.writeValueAsString(tradeMarketOrder)); + + return tradeMarketOrder; + } + + public static String stopOrder() throws IOException { + StopOrder stopOrder = + new StopOrder.Builder(Order.OrderType.ASK, usdtUsd) + .originalAmount(AMOUNT_LIMIT) + .stopPrice(STOP_PRICE) + .limitPrice(STOP_LIMIT_PRICE) + .build(); + + String tradeStopOrder = tradeService.placeStopOrder(stopOrder); + System.out.println(OBJECT_MAPPER.writeValueAsString(tradeStopOrder)); + + return tradeStopOrder; + } +}