package org.knowm.xchange.btce.v3; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import org.knowm.xchange.btce.v3.dto.account.BTCEAccountInfo; import org.knowm.xchange.btce.v3.dto.marketdata.BTCEExchangeInfo; import org.knowm.xchange.btce.v3.dto.marketdata.BTCEPairInfo; import org.knowm.xchange.btce.v3.dto.marketdata.BTCETicker; import org.knowm.xchange.btce.v3.dto.marketdata.BTCETrade; import org.knowm.xchange.btce.v3.dto.meta.BTCEMetaData; import org.knowm.xchange.btce.v3.dto.trade.BTCEOrder; import org.knowm.xchange.btce.v3.dto.trade.BTCETradeHistoryResult; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.dto.account.Wallet; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.dto.marketdata.Trades; import org.knowm.xchange.dto.marketdata.Trades.TradeSortType; 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.LimitOrder; import org.knowm.xchange.dto.trade.MarketOrder; import org.knowm.xchange.dto.trade.OpenOrders; import org.knowm.xchange.dto.trade.UserTrade; import org.knowm.xchange.dto.trade.UserTrades; import org.knowm.xchange.utils.DateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Various adapters for converting from BTCE DTOs to XChange DTOs */ public final class BTCEAdapters { public static final Logger log = LoggerFactory.getLogger(BTCEAdapters.class); /** * private Constructor */ private BTCEAdapters() { } /** * Adapts a List of BTCEOrders to a List of LimitOrders * * @param bTCEOrders * @param currencyPair * @param orderTypeString * @param id * @return */ public static List<LimitOrder> adaptOrders(List<BigDecimal[]> bTCEOrders, CurrencyPair currencyPair, String orderTypeString, String id) { List<LimitOrder> limitOrders = new ArrayList<LimitOrder>(); OrderType orderType = orderTypeString.equalsIgnoreCase("bid") ? OrderType.BID : OrderType.ASK; for (BigDecimal[] btceOrder : bTCEOrders) { limitOrders.add(adaptOrder(btceOrder[1], btceOrder[0], currencyPair, orderType, id)); } return limitOrders; } /** * Adapts a BTCEOrder to a LimitOrder * * @param amount * @param price * @param currencyPair * @param orderType * @param id * @return */ public static LimitOrder adaptOrder(BigDecimal amount, BigDecimal price, CurrencyPair currencyPair, OrderType orderType, String id) { return new LimitOrder(orderType, amount, currencyPair, id, null, price); } /** * Adapts a BTCETradeV3 to a Trade Object * * @param bTCETrade BTCE trade object v.3 * @param currencyPair the currency pair * @return The XChange Trade */ public static Trade adaptTrade(BTCETrade bTCETrade, CurrencyPair currencyPair) { OrderType orderType = bTCETrade.getTradeType().equalsIgnoreCase("bid") ? OrderType.BID : OrderType.ASK; BigDecimal amount = bTCETrade.getAmount(); BigDecimal price = bTCETrade.getPrice(); Date date = DateUtils.fromMillisUtc(bTCETrade.getDate() * 1000L); final String tradeId = String.valueOf(bTCETrade.getTid()); return new Trade(orderType, amount, currencyPair, price, date, tradeId); } /** * Adapts a BTCETradeV3[] to a Trades Object * * @param bTCETrades The BTCE trade data returned by API v.3 * @param currencyPair the currency pair * @return The trades */ public static Trades adaptTrades(BTCETrade[] bTCETrades, CurrencyPair currencyPair) { List<Trade> tradesList = new ArrayList<Trade>(); long lastTradeId = 0; for (BTCETrade bTCETrade : bTCETrades) { // Date is reversed order. Insert at index 0 instead of appending long tradeId = bTCETrade.getTid(); if (tradeId > lastTradeId) { lastTradeId = tradeId; } tradesList.add(0, adaptTrade(bTCETrade, currencyPair)); } return new Trades(tradesList, lastTradeId, TradeSortType.SortByID); } /** * Adapts a BTCETicker to a Ticker Object * * @param bTCETicker * @return */ public static Ticker adaptTicker(BTCETicker bTCETicker, CurrencyPair currencyPair) { BigDecimal last = bTCETicker.getLast(); BigDecimal bid = bTCETicker.getSell(); BigDecimal ask = bTCETicker.getBuy(); BigDecimal high = bTCETicker.getHigh(); BigDecimal low = bTCETicker.getLow(); BigDecimal avg = bTCETicker.getAvg(); BigDecimal volume = bTCETicker.getVolCur(); Date timestamp = DateUtils.fromMillisUtc(bTCETicker.getUpdated() * 1000L); return new Ticker.Builder().currencyPair(currencyPair).last(last).bid(bid).ask(ask).high(high).low(low).vwap(avg).volume(volume) .timestamp(timestamp).build(); } public static Wallet adaptWallet(BTCEAccountInfo btceAccountInfo) { List<Balance> balances = new ArrayList<Balance>(); Map<String, BigDecimal> funds = btceAccountInfo.getFunds(); for (String lcCurrency : funds.keySet()) { /* BTC-E signals DASH as DSH. This is a different coin. Translate in correct DASH name */ BigDecimal fund = funds.get(lcCurrency); if (lcCurrency.equals("dsh")) { lcCurrency = "dash"; } Currency currency = Currency.getInstance(lcCurrency); balances.add(new Balance(currency, fund)); } return new Wallet(balances); } public static OpenOrders adaptOrders(Map<Long, BTCEOrder> btceOrderMap) { List<LimitOrder> limitOrders = new ArrayList<LimitOrder>(); for (Long id : btceOrderMap.keySet()) { BTCEOrder bTCEOrder = btceOrderMap.get(id); OrderType orderType = bTCEOrder.getType() == BTCEOrder.Type.buy ? OrderType.BID : OrderType.ASK; BigDecimal price = bTCEOrder.getRate(); Date timestamp = DateUtils.fromMillisUtc(bTCEOrder.getTimestampCreated() * 1000L); CurrencyPair currencyPair = adaptCurrencyPair(bTCEOrder.getPair()); limitOrders.add(new LimitOrder(orderType, bTCEOrder.getAmount(), currencyPair, Long.toString(id), timestamp, price)); } return new OpenOrders(limitOrders); } public static UserTrades adaptTradeHistory(Map<Long, BTCETradeHistoryResult> tradeHistory) { List<UserTrade> trades = new ArrayList<UserTrade>(tradeHistory.size()); for (Entry<Long, BTCETradeHistoryResult> entry : tradeHistory.entrySet()) { BTCETradeHistoryResult result = entry.getValue(); OrderType type = result.getType() == BTCETradeHistoryResult.Type.buy ? OrderType.BID : OrderType.ASK; BigDecimal price = result.getRate(); BigDecimal tradableAmount = result.getAmount(); Date timeStamp = DateUtils.fromMillisUtc(result.getTimestamp() * 1000L); String orderId = String.valueOf(result.getOrderId()); String tradeId = String.valueOf(entry.getKey()); CurrencyPair currencyPair = adaptCurrencyPair(result.getPair()); trades.add(new UserTrade(type, tradableAmount, currencyPair, price, timeStamp, tradeId, orderId, null, (Currency) null)); } return new UserTrades(trades, TradeSortType.SortByTimestamp); } public static CurrencyPair adaptCurrencyPair(String btceCurrencyPair) { String[] currencies = btceCurrencyPair.split("_"); /* BTC-E signals DASH as DSH. This is a different coin. Translate in correct DASH name */ if (currencies[0].equals("dsh")) { currencies[0] = "dash"; } if (currencies[1].equals("dsh")) { currencies[1] = "dash"; } return new CurrencyPair(currencies[0].toUpperCase(), currencies[1].toUpperCase()); } public static List<CurrencyPair> adaptCurrencyPairs(Iterable<String> btcePairs) { List<CurrencyPair> pairs = new ArrayList<CurrencyPair>(); for (String btcePair : btcePairs) { pairs.add(adaptCurrencyPair(btcePair)); } return pairs; } public static ExchangeMetaData toMetaData(BTCEExchangeInfo btceExchangeInfo, BTCEMetaData btceMetaData) { Map<CurrencyPair, CurrencyPairMetaData> currencyPairs = new HashMap<CurrencyPair, CurrencyPairMetaData>(); Map<Currency, CurrencyMetaData> currencies = new HashMap<Currency, CurrencyMetaData>(); if (btceExchangeInfo != null) { for (Entry<String, BTCEPairInfo> e : btceExchangeInfo.getPairs().entrySet()) { CurrencyPair pair = adaptCurrencyPair(e.getKey()); CurrencyPairMetaData marketMetaData = toMarketMetaData(e.getValue(), btceMetaData); currencyPairs.put(pair, marketMetaData); addCurrencyMetaData(pair.base, currencies, btceMetaData); addCurrencyMetaData(pair.counter, currencies, btceMetaData); } } RateLimit[] publicRateLimits = new RateLimit[] { new RateLimit(btceMetaData.publicInfoCacheSeconds, 1, TimeUnit.SECONDS) }; return new ExchangeMetaData(currencyPairs, currencies, publicRateLimits, null, false); } private static void addCurrencyMetaData(Currency symbol, Map<Currency, CurrencyMetaData> currencies, BTCEMetaData btceMetaData) { if (!currencies.containsKey(symbol)) { currencies.put(symbol, new CurrencyMetaData(btceMetaData.amountScale)); } } public static CurrencyPairMetaData toMarketMetaData(BTCEPairInfo info, BTCEMetaData btceMetaData) { int priceScale = info.getDecimals(); BigDecimal minimumAmount = withScale(info.getMinAmount(), btceMetaData.amountScale); BigDecimal feeFraction = info.getFee().movePointLeft(2); return new CurrencyPairMetaData(feeFraction, minimumAmount, null, priceScale); } private static BigDecimal withScale(BigDecimal value, int priceScale) { /* * Last time I checked BTC-e returned an erroneous JSON result, where the minimum price for LTC/EUR was .0001 and the price scale was 3 */ try { return value.setScale(priceScale, RoundingMode.UNNECESSARY); } catch (ArithmeticException e) { log.debug("Could not round {} to {} decimal places: {}", value, priceScale, e.getMessage()); return value.setScale(priceScale, RoundingMode.CEILING); } } public static String getPair(CurrencyPair currencyPair) { /* BTC-E signals DASH as DSH. This is a different coin. Translate in correct DASH name */ String base = currencyPair.base.getCurrencyCode(); String counter = currencyPair.counter.getCurrencyCode(); if (base.equals("DASH")) { base = "DSH"; } else if (counter.equals("DASH")) { counter = "DSH"; } return (base + "_" + counter).toLowerCase(); } public static LimitOrder createLimitOrder(MarketOrder marketOrder, BTCEExchangeInfo btceExchangeInfo) { BTCEPairInfo btcePairInfo = btceExchangeInfo.getPairs().get(getPair(marketOrder.getCurrencyPair())); BigDecimal limitPrice = marketOrder.getType() == OrderType.BID ? btcePairInfo.getMaxPrice() : btcePairInfo.getMinPrice(); return LimitOrder.Builder.from(marketOrder).limitPrice(limitPrice).build(); } }