package org.knowm.xchange.bitfinex.v1; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.knowm.xchange.bitfinex.v1.dto.account.BitfinexBalancesResponse; import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexDepth; import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexLendLevel; import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexLevel; import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTicker; import org.knowm.xchange.bitfinex.v1.dto.marketdata.BitfinexTrade; import org.knowm.xchange.bitfinex.v1.dto.trade.BitfinexOrderStatusResponse; import org.knowm.xchange.bitfinex.v1.dto.trade.BitfinexTradeResponse; 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.OrderBook; 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.trade.FixedRateLoanOrder; import org.knowm.xchange.dto.trade.FloatingRateLoanOrder; import org.knowm.xchange.dto.trade.LimitOrder; 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; public final class BitfinexAdapters { public static final Logger log = LoggerFactory.getLogger(BitfinexAdapters.class); private BitfinexAdapters() { } public static List<CurrencyPair> adaptCurrencyPairs(Collection<String> bitfinexSymbol) { List<CurrencyPair> currencyPairs = new ArrayList<CurrencyPair>(); for (String symbol : bitfinexSymbol) { currencyPairs.add(adaptCurrencyPair(symbol)); } return currencyPairs; } public static CurrencyPair adaptCurrencyPair(String bitfinexSymbol) { String tradableIdentifier = bitfinexSymbol.substring(0, 3).toUpperCase(); String transactionCurrency = bitfinexSymbol.substring(3).toUpperCase(); return new CurrencyPair(tradableIdentifier, transactionCurrency); } public static String adaptCurrencyPair(CurrencyPair pair) { return (pair.base.getCurrencyCode() + pair.counter.getCurrencyCode()).toLowerCase(); } public static OrderBook adaptOrderBook(BitfinexDepth btceDepth, CurrencyPair currencyPair) { OrdersContainer asksOrdersContainer = adaptOrders(btceDepth.getAsks(), currencyPair, OrderType.ASK); OrdersContainer bidsOrdersContainer = adaptOrders(btceDepth.getBids(), currencyPair, OrderType.BID); return new OrderBook(new Date(Math.max(asksOrdersContainer.getTimestamp(), bidsOrdersContainer.getTimestamp())), asksOrdersContainer.getLimitOrders(), bidsOrdersContainer.getLimitOrders()); } public static OrdersContainer adaptOrders(BitfinexLevel[] bitfinexLevels, CurrencyPair currencyPair, OrderType orderType) { BigDecimal maxTimestamp = new BigDecimal(Long.MIN_VALUE); List<LimitOrder> limitOrders = new ArrayList<LimitOrder>(bitfinexLevels.length); for (BitfinexLevel bitfinexLevel : bitfinexLevels) { if (bitfinexLevel.getTimestamp().compareTo(maxTimestamp) > 0) { maxTimestamp = bitfinexLevel.getTimestamp(); } Date timestamp = convertBigDecimalTimestampToDate(bitfinexLevel.getTimestamp()); limitOrders.add(adaptOrder(bitfinexLevel.getAmount(), bitfinexLevel.getPrice(), currencyPair, orderType, timestamp)); } long maxTimestampInMillis = maxTimestamp.multiply(new BigDecimal(1000l)).longValue(); return new OrdersContainer(maxTimestampInMillis, limitOrders); } public static class OrdersContainer { private final long timestamp; private final List<LimitOrder> limitOrders; /** * Constructor * * @param timestamp * @param limitOrders */ public OrdersContainer(long timestamp, List<LimitOrder> limitOrders) { this.timestamp = timestamp; this.limitOrders = limitOrders; } public long getTimestamp() { return timestamp; } public List<LimitOrder> getLimitOrders() { return limitOrders; } } public static LimitOrder adaptOrder(BigDecimal amount, BigDecimal price, CurrencyPair currencyPair, OrderType orderType, Date timestamp) { return new LimitOrder(orderType, amount, currencyPair, "", timestamp, price); } public static List<FixedRateLoanOrder> adaptFixedRateLoanOrders(BitfinexLendLevel[] orders, String currency, String orderType, String id) { List<FixedRateLoanOrder> loanOrders = new ArrayList<FixedRateLoanOrder>(orders.length); for (BitfinexLendLevel order : orders) { if ("yes".equalsIgnoreCase(order.getFrr())) { continue; } // Bid orderbook is reversed order. Insert at reversed indices if (orderType.equalsIgnoreCase("loan")) { loanOrders.add(0, adaptFixedRateLoanOrder(currency, order.getAmount(), order.getPeriod(), orderType, id, order.getRate())); } else { loanOrders.add(adaptFixedRateLoanOrder(currency, order.getAmount(), order.getPeriod(), orderType, id, order.getRate())); } } return loanOrders; } public static FixedRateLoanOrder adaptFixedRateLoanOrder(String currency, BigDecimal amount, int dayPeriod, String direction, String id, BigDecimal rate) { OrderType orderType = direction.equalsIgnoreCase("loan") ? OrderType.BID : OrderType.ASK; return new FixedRateLoanOrder(orderType, currency, amount, dayPeriod, id, null, rate); } public static List<FloatingRateLoanOrder> adaptFloatingRateLoanOrders(BitfinexLendLevel[] orders, String currency, String orderType, String id) { List<FloatingRateLoanOrder> loanOrders = new ArrayList<FloatingRateLoanOrder>(orders.length); for (BitfinexLendLevel order : orders) { if ("no".equals(order.getFrr())) { continue; } // Bid orderbook is reversed order. Insert at reversed indices if (orderType.equalsIgnoreCase("loan")) { loanOrders.add(0, adaptFloatingRateLoanOrder(currency, order.getAmount(), order.getPeriod(), orderType, id, order.getRate())); } else { loanOrders.add(adaptFloatingRateLoanOrder(currency, order.getAmount(), order.getPeriod(), orderType, id, order.getRate())); } } return loanOrders; } public static FloatingRateLoanOrder adaptFloatingRateLoanOrder(String currency, BigDecimal amount, int dayPeriod, String direction, String id, BigDecimal rate) { OrderType orderType = direction.equalsIgnoreCase("loan") ? OrderType.BID : OrderType.ASK; return new FloatingRateLoanOrder(orderType, currency, amount, dayPeriod, id, null, rate); } public static Trade adaptTrade(BitfinexTrade trade, CurrencyPair currencyPair) { OrderType orderType = trade.getType().equals("buy") ? OrderType.BID : OrderType.ASK; BigDecimal amount = trade.getAmount(); BigDecimal price = trade.getPrice(); Date date = DateUtils.fromMillisUtc(trade.getTimestamp() * 1000L); // Bitfinex uses Unix timestamps final String tradeId = String.valueOf(trade.getTradeId()); return new Trade(orderType, amount, currencyPair, price, date, tradeId); } public static Trades adaptTrades(BitfinexTrade[] trades, CurrencyPair currencyPair) { List<Trade> tradesList = new ArrayList<Trade>(trades.length); long lastTradeId = 0; for (BitfinexTrade trade : trades) { long tradeId = trade.getTradeId(); if (tradeId > lastTradeId) { lastTradeId = tradeId; } tradesList.add(adaptTrade(trade, currencyPair)); } return new Trades(tradesList, lastTradeId, TradeSortType.SortByID); } public static Ticker adaptTicker(BitfinexTicker bitfinexTicker, CurrencyPair currencyPair) { BigDecimal last = bitfinexTicker.getLast_price(); BigDecimal bid = bitfinexTicker.getBid(); BigDecimal ask = bitfinexTicker.getAsk(); BigDecimal high = bitfinexTicker.getHigh(); BigDecimal low = bitfinexTicker.getLow(); BigDecimal volume = bitfinexTicker.getVolume(); Date timestamp = DateUtils.fromMillisUtc((long) (bitfinexTicker.getTimestamp() * 1000L)); return new Ticker.Builder().currencyPair(currencyPair).last(last).bid(bid).ask(ask).high(high).low(low).volume(volume).timestamp(timestamp) .build(); } public static Wallet adaptWallet(BitfinexBalancesResponse[] response) { Map<String, BigDecimal[]> balancesByCurrency = new HashMap<String, BigDecimal[]>(); // {total, available} // for each currency we have multiple balances types: exchange, trading, deposit. // each of those may be partially frozen/available for (BitfinexBalancesResponse balance : response) { String currencyName = balance.getCurrency().toUpperCase(); BigDecimal[] balanceDetail = balancesByCurrency.get(currencyName); if (balanceDetail == null) { balanceDetail = new BigDecimal[] { balance.getAmount(), balance.getAvailable() }; } else { balanceDetail[0] = balanceDetail[0].add(balance.getAmount()); balanceDetail[1] = balanceDetail[1].add(balance.getAvailable()); } balancesByCurrency.put(currencyName, balanceDetail); } List<Balance> balances = new ArrayList<Balance>(balancesByCurrency.size()); for (Entry<String, BigDecimal[]> entry : balancesByCurrency.entrySet()) { String currencyName = entry.getKey(); BigDecimal[] balanceDetail = entry.getValue(); BigDecimal balanceTotal = balanceDetail[0]; BigDecimal balanceAvailable = balanceDetail[1]; balances.add(new Balance(Currency.getInstance(currencyName), balanceTotal, balanceAvailable)); } return new Wallet(balances); } public static OpenOrders adaptOrders(BitfinexOrderStatusResponse[] activeOrders) { List<LimitOrder> limitOrders = new ArrayList<LimitOrder>(activeOrders.length); for (BitfinexOrderStatusResponse order : activeOrders) { OrderType orderType = order.getSide().equalsIgnoreCase("buy") ? OrderType.BID : OrderType.ASK; CurrencyPair currencyPair = adaptCurrencyPair(order.getSymbol()); Date timestamp = convertBigDecimalTimestampToDate(order.getTimestamp()); limitOrders .add(new LimitOrder(orderType, order.getRemainingAmount(), currencyPair, String.valueOf(order.getId()), timestamp, order.getPrice())); } return new OpenOrders(limitOrders); } public static UserTrades adaptTradeHistory(BitfinexTradeResponse[] trades, String symbol) { List<UserTrade> pastTrades = new ArrayList<UserTrade>(trades.length); CurrencyPair currencyPair = adaptCurrencyPair(symbol); for (BitfinexTradeResponse trade : trades) { OrderType orderType = trade.getType().equalsIgnoreCase("buy") ? OrderType.BID : OrderType.ASK; Date timestamp = convertBigDecimalTimestampToDate(trade.getTimestamp()); final BigDecimal fee = trade.getFeeAmount() == null ? null : trade.getFeeAmount().negate(); pastTrades.add(new UserTrade(orderType, trade.getAmount(), currencyPair, trade.getPrice(), timestamp, trade.getTradeId(), trade.getOrderId(), fee, Currency.getInstance(trade.getFeeCurrency()))); } return new UserTrades(pastTrades, TradeSortType.SortByTimestamp); } private static Date convertBigDecimalTimestampToDate(BigDecimal timestamp) { BigDecimal timestampInMillis = timestamp.multiply(new BigDecimal("1000")); return new Date(timestampInMillis.longValue()); } public static ExchangeMetaData adaptMetaData(List<CurrencyPair> currencyPairs, ExchangeMetaData metaData) { Map<CurrencyPair, CurrencyPairMetaData> pairsMap = metaData.getCurrencyPairs(); Map<Currency, CurrencyMetaData> currenciesMap = metaData.getCurrencies(); for (CurrencyPair c : currencyPairs) { if (!pairsMap.keySet().contains(c)) { pairsMap.put(c, null); } if (!currenciesMap.keySet().contains(c.base)) { currenciesMap.put(c.base, null); } if (!currenciesMap.keySet().contains(c.base)) { currenciesMap.put(c.counter, null); } } return metaData; } }