org.knowm.xchange.ripple.RippleAdapters Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xchange-ripple Show documentation
Show all versions of xchange-ripple Show documentation
XChange implementation for the Ripple Network
package org.knowm.xchange.ripple;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.AccountInfo;
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.Trades.TradeSortType;
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.ripple.dto.RippleAmount;
import org.knowm.xchange.ripple.dto.account.ITransferFeeSource;
import org.knowm.xchange.ripple.dto.account.RippleAccountBalances;
import org.knowm.xchange.ripple.dto.account.RippleBalance;
import org.knowm.xchange.ripple.dto.marketdata.RippleOrder;
import org.knowm.xchange.ripple.dto.marketdata.RippleOrderBook;
import org.knowm.xchange.ripple.dto.trade.IRippleTradeTransaction;
import org.knowm.xchange.ripple.dto.trade.RippleAccountOrders;
import org.knowm.xchange.ripple.dto.trade.RippleAccountOrdersBody;
import org.knowm.xchange.ripple.dto.trade.RippleLimitOrder;
import org.knowm.xchange.ripple.dto.trade.RippleUserTrade;
import org.knowm.xchange.ripple.service.RippleAccountService;
import org.knowm.xchange.ripple.service.RippleTradeServiceRaw;
import org.knowm.xchange.ripple.service.params.RippleMarketDataParams;
import org.knowm.xchange.ripple.service.params.RippleTradeHistoryPreferredCurrencies;
import org.knowm.xchange.service.trade.params.TradeHistoryParamCurrencyPair;
import org.knowm.xchange.service.trade.params.TradeHistoryParams;
import org.knowm.xchange.utils.jackson.CurrencyPairDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Various adapters for converting from Ripple DTOs to XChange DTOs */
public abstract class RippleAdapters {
private static final Set EMPTY_CURRENCY_SET = Collections.emptySet();
private static final Logger logger = LoggerFactory.getLogger(RippleAdapters.class);
/** private Constructor */
private RippleAdapters() {}
/** Adapts a Ripple Account to an XChange Wallet object. */
public static AccountInfo adaptAccountInfo(
final RippleAccountBalances account, final String username) {
// Adapt account balances to XChange balances
final Map> balances = new HashMap<>();
for (final RippleBalance balance : account.getBalances()) {
final String walletId;
if (balance.getCurrency().equals("XRP")) {
walletId = "main";
} else {
walletId = balance.getCounterparty();
}
if (!balances.containsKey(walletId)) {
balances.put(walletId, new LinkedList());
}
balances
.get(walletId)
.add(new Balance(Currency.getInstance(balance.getCurrency()), balance.getValue()));
}
final List accountInfo = new ArrayList<>(balances.size());
for (final Map.Entry> wallet : balances.entrySet()) {
accountInfo.add(Wallet.Builder.from(wallet.getValue()).id(wallet.getKey()).build());
}
return new AccountInfo(username, BigDecimal.ZERO, accountInfo);
}
/**
* Adapts a Ripple OrderBook to an XChange OrderBook object. Counterparties are not mapped since
* the application calling this should know and keep track of the counterparties it is using in
* the polling thread.
*/
public static OrderBook adaptOrderBook(
final RippleOrderBook rippleOrderBook,
final RippleMarketDataParams params,
final CurrencyPair currencyPair) {
final String orderBook =
rippleOrderBook.getOrderBook(); // e.g. XRP/BTC+rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B
final String[] splitPair = orderBook.split("/");
final String[] baseSplit = splitPair[0].split("\\+");
final String baseSymbol = baseSplit[0];
if (baseSymbol.equals(currencyPair.base.getCurrencyCode()) == false) {
throw new IllegalStateException(
String.format(
"base symbol in Ripple order book %s does not match requested base %s",
orderBook, currencyPair));
}
final String baseCounterparty;
if (baseSymbol.equals("XRP")) {
baseCounterparty = ""; // native currency
} else {
baseCounterparty = baseSplit[1];
}
if (baseCounterparty.equals(params.getBaseCounterparty()) == false) {
throw new IllegalStateException(
String.format(
"base counterparty in Ripple order book %s does not match requested counterparty %s",
orderBook, params.getBaseCounterparty()));
}
final String[] counterSplit = splitPair[1].split("\\+");
final String counterSymbol = counterSplit[0];
if (counterSymbol.equals(currencyPair.counter.getCurrencyCode()) == false) {
throw new IllegalStateException(
String.format(
"counter symbol in Ripple order book %s does not match requested base %s",
orderBook, currencyPair));
}
final String counterCounterparty;
if (counterSymbol.equals("XRP")) {
counterCounterparty = ""; // native currency
} else {
counterCounterparty = counterSplit[1];
}
if (counterCounterparty.equals(params.getCounterCounterparty()) == false) {
throw new IllegalStateException(
String.format(
"counter counterparty in Ripple order book %s does not match requested counterparty %s",
orderBook, params.getCounterCounterparty()));
}
final List bids =
createOrders(
currencyPair,
OrderType.BID,
rippleOrderBook.getBids(),
baseCounterparty,
counterCounterparty);
final List asks =
createOrders(
currencyPair,
OrderType.ASK,
rippleOrderBook.getAsks(),
baseCounterparty,
counterCounterparty);
return new OrderBook(null, asks, bids);
}
public static List createOrders(
final CurrencyPair currencyPair,
final OrderType orderType,
final List orders,
final String baseCounterparty,
final String counterCounterparty) {
final List limitOrders = new ArrayList<>();
for (final RippleOrder rippleOrder : orders) {
// Taker Pays = the amount the taker must pay to consume this order.
// Taker Gets = the amount the taker will get once the order is consumed.
//
// Funded vs Unfunded https://wiki.ripple.com/Unfunded_offers
// amount of base currency
final BigDecimal originalAmount;
if (orderType == OrderType.BID) {
originalAmount = rippleOrder.getTakerPaysFunded().getValue();
} else {
originalAmount = rippleOrder.getTakerGetsFunded().getValue();
}
// price in counter currency
final BigDecimal price = rippleOrder.getPrice().getValue();
final RippleLimitOrder order =
new RippleLimitOrder(
orderType,
originalAmount,
currencyPair,
Integer.toString(rippleOrder.getSequence()),
null,
price,
baseCounterparty,
counterCounterparty);
limitOrders.add(order);
}
return limitOrders;
}
/**
* Adapts a Ripple Account Orders object to an XChange OpenOrders object Counterparties set in
* additional data since there is no other way of the application receiving this information.
*/
public static OpenOrders adaptOpenOrders(
final RippleAccountOrders rippleOrders, final int scale) {
final List list = new ArrayList<>(rippleOrders.getOrders().size());
for (final RippleAccountOrdersBody order : rippleOrders.getOrders()) {
final OrderType orderType;
final RippleAmount baseAmount;
final RippleAmount counterAmount;
if (order.getType().equals("buy")) {
// buying: we receive base and pay with counter, taker receives counter and pays with base
counterAmount = order.getTakerGets();
baseAmount = order.getTakerPays();
orderType = OrderType.BID;
} else {
// selling: we receive counter and pay with base, taker receives base and pays with counter
baseAmount = order.getTakerGets();
counterAmount = order.getTakerPays();
orderType = OrderType.ASK;
}
final String baseSymbol = baseAmount.getCurrency();
final String counterSymbol = counterAmount.getCurrency();
// need to provide rounding scale to prevent ArithmeticException
final BigDecimal price =
counterAmount
.getValue()
.divide(baseAmount.getValue(), scale, RoundingMode.HALF_UP)
.stripTrailingZeros();
final CurrencyPair pair = new CurrencyPair(baseSymbol, counterSymbol);
final RippleLimitOrder xchangeOrder =
(RippleLimitOrder)
new RippleLimitOrder.Builder(orderType, pair)
.baseCounterparty(baseAmount.getCounterparty())
.counterCounterparty(counterAmount.getCounterparty())
.id(Long.toString(order.getSequence()))
.limitPrice(price)
.timestamp(null)
.originalAmount(baseAmount.getValue())
.build();
list.add(xchangeOrder);
}
return new OpenOrders(list);
}
public static UserTrade adaptTrade(
final IRippleTradeTransaction trade,
final TradeHistoryParams params,
final ITransferFeeSource transferFeeSource,
final int scale)
throws IOException {
// The order{} section of the body cannot be used to determine trade facts e.g. if the order was
// to sell BTC.Bitstamp and buy
// BTC.SnapSwap, and traded via XRP, and our trade was one of the XRP legs, all we'd see would
// be the taker getting and paying BTC.
//
// Details in the balance_changes{} and order_changes{} blocks are relative to the perspective
// account, i.e. the Ripple account address used in the URI.
final List balanceChanges = trade.getBalanceChanges();
final Iterator iterator = balanceChanges.iterator();
while (iterator.hasNext()) {
final RippleAmount amount = iterator.next();
if (amount.getCurrency().equals("XRP") && trade.getFee().equals(amount.getValue().negate())) {
// XRP balance change is just the fee - it should not be part of the currency pair
// considerations
iterator.remove();
}
}
if (balanceChanges.size() != 2) {
logger.warn(
"for hash[{}] of type[{}] balance changes section should contains 2 currency amounts but found {}",
trade.getHash(),
trade.getClass().getSimpleName(),
balanceChanges);
return null;
}
// There is no way of telling the original entered base or counter currency - Ripple just
// provides 2 currency adjustments.
// Check if TradeHistoryParams expressed a preference, otherwise arrange the currencies in the
// order they are supplied.
final Collection preferredBase, preferredCounter;
if (params instanceof RippleTradeHistoryPreferredCurrencies) {
final RippleTradeHistoryPreferredCurrencies rippleParams =
(RippleTradeHistoryPreferredCurrencies) params;
preferredBase = rippleParams.getPreferredBaseCurrency();
preferredCounter = rippleParams.getPreferredCounterCurrency();
} else {
preferredBase = preferredCounter = EMPTY_CURRENCY_SET;
}
final RippleAmount base, counter;
if (preferredBase.contains(Currency.getInstance(balanceChanges.get(0).getCurrency()))) {
base = balanceChanges.get(0);
counter = balanceChanges.get(1);
} else if (preferredBase.contains(Currency.getInstance(balanceChanges.get(1).getCurrency()))) {
base = balanceChanges.get(1);
counter = balanceChanges.get(0);
} else if (preferredCounter.contains(
Currency.getInstance(balanceChanges.get(0).getCurrency()))) {
counter = balanceChanges.get(0);
base = balanceChanges.get(1);
} else if (preferredCounter.contains(
Currency.getInstance(balanceChanges.get(1).getCurrency()))) {
counter = balanceChanges.get(1);
base = balanceChanges.get(0);
} else if ((params instanceof TradeHistoryParamCurrencyPair)
&& (((TradeHistoryParamCurrencyPair) params).getCurrencyPair() != null)) {
// Searching for a specific currency pair - use this direction
final CurrencyPair pair = ((TradeHistoryParamCurrencyPair) params).getCurrencyPair();
if (pair.base.getCurrencyCode().equals(balanceChanges.get(0).getCurrency())
&& pair.counter.getCurrencyCode().equals(balanceChanges.get(1).getCurrency())) {
base = balanceChanges.get(0);
counter = balanceChanges.get(1);
} else if (pair.base.getCurrencyCode().equals(balanceChanges.get(1).getCurrency())
&& pair.counter.getCurrencyCode().equals(balanceChanges.get(0).getCurrency())) {
base = balanceChanges.get(1);
counter = balanceChanges.get(0);
} else {
// Unexpected: this should have been filtered out in
// RippleTradeServiceRaw.getTradesForAccount(..) method.
throw new IllegalStateException(
String.format(
"trade history param currency filter specified %s but trade query returned %s and %s",
pair, balanceChanges.get(0).getCurrency(), balanceChanges.get(1).getCurrency()));
}
} else { // select the currency direction as return from the API
base = balanceChanges.get(0);
counter = balanceChanges.get(1);
}
final OrderType type;
if (base.getValue().signum() == 1) {
type = OrderType.BID;
} else {
type = OrderType.ASK;
}
final String currencyPairString = base.getCurrency() + "/" + counter.getCurrency();
final CurrencyPair currencyPair =
CurrencyPairDeserializer.getCurrencyPairFromString(currencyPairString);
// Ripple has 2 types of fee.
//
// (a) Transaction fee is a network charge levied in XRP.
// https://wiki.ripple.com/Transaction_Fee
//
// (b) Transfer fee charged by the issuer levied in the currency of traded instrument. Whoever
// sends an asset that has a transfer fee pays the fee, the receiver does not incur a charge.
// https://wiki.ripple.com/Transit_Fee
// https://ripple.com/knowledge_center/transfer-fees/
// Ripple supplies XRP with net quantity and price, must apply these to the
// trade as gross amounts to ensure the same as the other XChange connections.
final BigDecimal baseTransferFee =
RippleTradeServiceRaw.getExpectedTransferFee(
transferFeeSource, base.getCounterparty(), base.getCurrency(), base.getValue(), type);
final BigDecimal baseValue = base.getValue().abs().subtract(baseTransferFee);
final OrderType counterDirection;
if (type == OrderType.BID) {
counterDirection = OrderType.ASK;
} else {
counterDirection = OrderType.BID;
}
final BigDecimal counterTransferFee =
RippleTradeServiceRaw.getExpectedTransferFee(
transferFeeSource,
counter.getCounterparty(),
counter.getCurrency(),
counter.getValue(),
counterDirection);
final BigDecimal counterValue = counter.getValue().abs().subtract(counterTransferFee);
// Account for transaction fee in quantities.
final BigDecimal transactionFee = trade.getFee();
final BigDecimal quantity;
if (base.getCurrency().equals("XRP")) {
if (type == OrderType.BID) {
quantity = baseValue.add(transactionFee);
} else { // OrderType.ASK
quantity = baseValue.subtract(transactionFee);
}
} else {
quantity = baseValue;
}
final BigDecimal counterAmount;
if (counter.getCurrency().equals("XRP")) {
if (type == OrderType.ASK) {
counterAmount = counterValue.add(transactionFee);
} else { // OrderType.BID
counterAmount = counterValue.subtract(transactionFee);
}
} else {
counterAmount = counterValue;
}
// need to provide rounding scale to prevent ArithmeticException
final BigDecimal price = counterAmount.divide(quantity, scale, RoundingMode.HALF_UP);
final String orderId = Long.toString(trade.getOrderId());
final RippleUserTrade.Builder builder =
(RippleUserTrade.Builder)
new RippleUserTrade.Builder()
.currencyPair(currencyPair)
.feeAmount(transactionFee)
.feeCurrency(Currency.XRP)
.id(trade.getHash())
.orderId(orderId)
.price(price.stripTrailingZeros())
.timestamp(trade.getTimestamp())
.originalAmount(quantity.stripTrailingZeros())
.type(type);
builder.baseTransferFee(baseTransferFee.abs());
builder.counterTransferFee(counterTransferFee.abs());
if (base.getCounterparty().length() > 0) {
builder.baseCounterparty(base.getCounterparty());
}
if (counter.getCounterparty().length() > 0) {
builder.counterCounterparty(counter.getCounterparty());
}
return builder.build();
}
public static UserTrades adaptTrades(
final Collection tradesForAccount,
final TradeHistoryParams params,
final RippleAccountService accountService,
final int roundingScale)
throws IOException {
final List trades = new ArrayList<>();
for (final IRippleTradeTransaction orderDetails : tradesForAccount) {
final UserTrade trade = adaptTrade(orderDetails, params, accountService, roundingScale);
if (trade == null) {
// any issue should have been reported by adaptTrade
} else {
trades.add(trade);
}
}
return new UserTrades(trades, TradeSortType.SortByTimestamp);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy