Skip to content
This repository has been archived by the owner on Mar 7, 2023. It is now read-only.

tradeservice implementation #2

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
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.trade.*;
import org.knowm.xchange.instrument.Instrument;
import org.knowm.xchange.service.trade.params.CancelOrderByCurrencyPair;
import org.knowm.xchange.service.trade.params.CancelOrderParams;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.knowm.xchange.blockchain.BlockchainConstants.*;

Expand All @@ -18,6 +32,22 @@ public static String toSymbol(CurrencyPair currencyPair) {
return String.format(CURRENCY_PAIR_SYMBOL_FORMAT, currencyPair.base.getCurrencyCode(), currencyPair.counter.getCurrencyCode());
}

public static String toSymbol(CancelOrderParams currencyPair) {
scuevas-bc marked this conversation as resolved.
Show resolved Hide resolved
if (currencyPair instanceof CancelOrderByCurrencyPair) {
return String.format(CURRENCY_PAIR_SYMBOL_FORMAT,
((CancelOrderByCurrencyPair) currencyPair).getCurrencyPair().base,
((CancelOrderByCurrencyPair) currencyPair).getCurrencyPair().counter);
}
throw new IllegalArgumentException(String.format("Unsupported currency pair '%s'", currencyPair));
}

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);
}
Expand Down Expand Up @@ -86,4 +116,170 @@ public static CurrencyPair toCurrencyPairBySymbol(BlockchainSymbol blockchainSym
Currency counterSymbol = blockchainSymbol.getCounterCurrency();
return new CurrencyPair(baseSymbol, counterSymbol);
}

public static OpenOrders toOpenOrders(List<BlockchainOrder> blockchainOrders){
List<LimitOrder> limitOrders = new ArrayList<>(Collections.emptyList());
scuevas-bc marked this conversation as resolved.
Show resolved Hide resolved
List<Order> hiddenOrders = new ArrayList<>(Collections.emptyList());

for(BlockchainOrder blockchainOrder : blockchainOrders) {
final Order.OrderType orderType = blockchainOrder.isBuyer() ? Order.OrderType.BID : Order.OrderType.ASK;
scuevas-bc marked this conversation as resolved.
Show resolved Hide resolved
final CurrencyPair symbol = blockchainOrder.getSymbol();
Order.Builder builder;

if (blockchainOrder.isMarketOrder()) {
scuevas-bc marked this conversation as resolved.
Show resolved Hide resolved
builder = new MarketOrder.Builder(orderType, symbol);
} else if (blockchainOrder.isLimitOrder()){
builder = new LimitOrder.Builder(orderType, symbol).limitPrice(blockchainOrder.getPrice());
} else {
builder = new StopOrder.Builder(orderType, symbol).stopPrice(blockchainOrder.getPrice());
}
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.OrderType orderType = blockchainOrder.isBuyer() ? Order.OrderType.BID : Order.OrderType.ASK;
CurrencyPair symbol = blockchainOrder.getSymbol();
Order.Builder builder;

if (blockchainOrder.isMarketOrder()) {
builder = new MarketOrder.Builder(orderType, symbol);
} else if (blockchainOrder.isLimitOrder()){
builder = new LimitOrder.Builder(orderType, symbol).limitPrice(blockchainOrder.getPrice());
} else {
builder = new StopOrder.Builder(orderType, symbol).stopPrice(blockchainOrder.getPrice());
}

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(limitOrder.getType().toString())
.symbol(toCurrencyPair(limitOrder.getInstrument()))
.side(Order.OrderType.BID.equals(limitOrder.getType())? BUY.toUpperCase() : SELL.toUpperCase())
.orderQty(limitOrder.getOriginalAmount())
.price(limitOrder.getLimitPrice())
.clOrdId(generateClOrdId())
.build();
}

public static BlockchainOrder toBlockchainMarketOrder(MarketOrder marketOrder){
return BlockchainOrder.builder()
.ordType(marketOrder.getType().toString())
.symbol(toCurrencyPair(marketOrder.getInstrument()))
.side(Order.OrderType.BID.equals(marketOrder.getType())? BUY.toUpperCase() : SELL.toUpperCase())
scuevas-bc marked this conversation as resolved.
Show resolved Hide resolved
.orderQty(marketOrder.getOriginalAmount())
.price(marketOrder.getCumulativeAmount())
.clOrdId(generateClOrdId())
.build();
}

public static BlockchainOrder toBlockchainStopOrder(StopOrder stopOrder){
return BlockchainOrder.builder()
.ordType(stopOrder.getType().toString())
.symbol(toCurrencyPair(stopOrder.getInstrument()))
.side(Order.OrderType.BID.equals(stopOrder.getType())? BUY.toUpperCase() : SELL.toUpperCase())
.orderQty(stopOrder.getOriginalAmount())
.price(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<BlockchainOrder> blockchainTrades) {
List<UserTrade> trades = blockchainTrades.stream()
.map(blockchainTrade -> new UserTrade.Builder()
.type(blockchainTrade.isBuyer()? Order.OrderType.BID : Order.OrderType.ASK)
.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(
scuevas-bc marked this conversation as resolved.
Show resolved Hide resolved
List<CurrencyPair> currencyPairs, ExchangeMetaData metaData) {

Map<CurrencyPair, CurrencyPairMetaData> pairsMap = metaData.getCurrencyPairs();
Map<Currency, CurrencyMetaData> currenciesMap = metaData.getCurrencies();

pairsMap.keySet().retainAll(currencyPairs);

Set<Currency> currencies =
currencyPairs.stream()
.flatMap(pair -> Stream.of(pair.base, pair.counter))
.collect(Collectors.toSet());
currenciesMap.keySet().retainAll(currencies);

for (CurrencyPair c : currencyPairs) {
if (!pairsMap.containsKey(c)) {
pairsMap.put(c, null);
}

if (!currenciesMap.containsKey(c.base)) {
currenciesMap.put(
c.base,
new CurrencyMetaData(
2,
null));
}
if (!currenciesMap.containsKey(c.counter)) {
currenciesMap.put(c.counter, new CurrencyMetaData(2, null));
}
}

return metaData;
}*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -85,4 +86,77 @@ List<BlockchainWithdrawal> getWithdrawFunds(@QueryParam("from") Long startTime,
@GET
List<BlockchainDeposits> 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<BlockchainOrder> getOrders();

/**
* 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);

/**
* 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<BlockchainOrder> getTrades(@QueryParam("symbol") String symbol,
@QueryParam("from") Long startTime,
@QueryParam("to") Long endTime,
@QueryParam("limit") Integer limit);

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,35 @@ 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 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 = "You need to provide the currency pair.";
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_ORDER = "STOP_ORDER";
public static final String BUY = "buy";
public static final String SELL = "sell";
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.knowm.xchange.BaseExchange;
import org.knowm.xchange.ExchangeSpecification;
import org.knowm.xchange.blockchain.service.BlockchainAccountService;
import org.knowm.xchange.blockchain.service.BlockchainTradeService;
import org.knowm.xchange.client.ExchangeRestProxyBuilder;
import org.knowm.xchange.client.ResilienceRegistries;
import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException;
Expand Down Expand Up @@ -31,6 +32,7 @@ protected void initServices() {
).build();

this.accountService = new BlockchainAccountService(this, this.blockchain, this.getResilienceRegistries());
this.tradeService = new BlockchainTradeService(this, this.blockchain, this.getResilienceRegistries());
}

@Override
Expand Down Expand Up @@ -61,9 +63,4 @@ public ResilienceRegistries getResilienceRegistries() {
public MarketDataService getMarketDataService() {
throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET);
}

@Override
public TradeService getTradeService() {
throw new NotYetImplementedForExchangeException(NOT_IMPLEMENTED_YET);
}
}
Loading