package org.ripple.power.hft.ripple;
import org.ripple.power.RippleSeedAddress;
import org.ripple.power.hft.BOT_SET;
import org.ripple.power.hft.BotLog;
import org.ripple.power.hft.TraderBase;
import org.ripple.power.txns.RippleBackendsAPI;
import org.ripple.power.txns.data.Market;
import org.ripple.power.txns.data.Offer;
import org.ripple.power.txns.data.Take;
public class ArbitrageTrader extends TraderBase {
private final String _baseCurrency;
private final String _arbCurrency;
private final String _baseGateway;
private final String _arbGateway;
private final double _parity;
private double _arbFactor = 1.007;
private final double MIN_TRADE_VOLUME = 1.0;
private final double MIN_ORDER_AMOUNT = 0.5;
private int _counter;
private double _lastValidXrpBalance = -1.0;
public ArbitrageTrader(RippleBackendsAPI api, RippleSeedAddress seed,
BOT_SET set, BotLog log) {
super(api, seed, set, log);
_baseCurrency = set.baseCurrency;
_baseGateway = set.baseGateway;
_arbCurrency = set.arbCurrency;
_arbGateway = set.arbGateway;
this._pay = new Take(set.baseCurrency,set.baseGateway);
this._get = new Take(set.arbCurrency,set.arbGateway);
this._parity = Double.parseDouble(set.parity);
this._arbFactor = Double.parseDouble(set.arbFactor);
}
@Override
protected void check() {
Market baseMarket = _rippleApi.getSynXRPMarketDepth(null,
_pay, query_limit);
if (null == baseMarket || null == baseMarket.Asks
|| null == baseMarket.Bids) {
return;
}
Market arbMarket = _rippleApi.getSynXRPMarketDepth(null,
_get, query_limit);
if (null == arbMarket || null == arbMarket.Asks
|| null == arbMarket.Bids) {
return;
}
double baseBalance = _rippleApi.getSynBalance(_pay);
double arbBalance = _rippleApi.getSynBalance(_get);
double xrpBalance = _rippleApi.getSynXrpBalance();
log("Balances: %s %s; %s %s; %s XRP", baseBalance, _baseCurrency,
arbBalance, _arbCurrency, xrpBalance);
double lowestBaseAskPrice = baseMarket.Asks.get(0).getPrice();
double highestArbBidPrice = arbMarket.Bids.get(0).getPrice();
double baseRatio = highestArbBidPrice / lowestBaseAskPrice;
double lowestArbAskPrice = arbMarket.Asks.get(0).getPrice();
double highestBaseBidPrice = baseMarket.Bids.get(0).getPrice();
double arbRatio = lowestArbAskPrice / highestBaseBidPrice;
log("BASIC ratio=%s; ARB ratio=%s", baseRatio, arbRatio);
if (Double.isNaN(baseRatio) || Double.isNaN(arbRatio)) {
return;
}
if (baseBalance >= 0.1) {
if (baseRatio > _parity * _arbFactor) {
if (baseRatio > _parity * 1.1 || baseRatio < _parity * 0.9) {
log("BASIC ratio has suspicious value %s. Let's leave it be",
baseRatio);
return;
}
log("Chance to buy cheap %s (BASIC ratio %s > %s)",
_arbCurrency, baseRatio, _parity * _arbFactor);
double baseVolume = baseMarket.Asks.get(0).getAmount();
double arbVolume = arbMarket.Bids.get(0).getAmount();
if (baseVolume < MIN_TRADE_VOLUME
|| arbVolume < MIN_TRADE_VOLUME) {
log("Insufficient volume: %s XRP for %s; %s XRP for %s",
baseVolume, _baseCurrency, arbVolume, _arbCurrency);
} else {
// Try to buy XRP for BASIC
double amount = Math.min(baseVolume, arbVolume);
long orderId = _rippleApi.placeSynXRPBuyOrder(
lowestBaseAskPrice + 0.00001, amount, _pay);
log("Tried to buy %s XRP for %s %s each. OrderID=%s",
amount, lowestBaseAskPrice, _baseCurrency, orderId);
Offer orderInfo = _rippleApi.getSynOrderInfo(orderId);
if (null != orderInfo && orderInfo.Closed) {
double newXrpBalance = _rippleApi.getSynXrpBalance();
amount = newXrpBalance - xrpBalance;
log("Buy XRP orderID=%s filled OK, bought %s XRP",
orderId, amount);
amount -= MIN_ORDER_AMOUNT;
long arbBuyOrderId = _rippleApi.placeSynXRPSellOrder(
highestArbBidPrice * 0.9, amount, _get);
log("Tried to sell %s XRP for %s %s each. OrderID=%s",
amount, highestArbBidPrice, _arbCurrency,
arbBuyOrderId);
Offer arbBuyOrderInfo = _rippleApi
.getSynOrderInfo(arbBuyOrderId);
if (null != arbBuyOrderInfo && arbBuyOrderInfo.Closed) {
log("Buy %s orderID=%s filled OK", _arbCurrency,
arbBuyOrderId);
log("%s -> %s ARBITRAGE SUCCEEDED!",
_baseCurrency, _arbCurrency);
} else {
log("OrderID=%s (sell %s XRP for %s %s each) remains dangling. Forgetting it...",
arbBuyOrderId,
arbBuyOrderInfo.getAmountXrp(),
arbBuyOrderInfo.getPrice(), _arbCurrency);
}
} else {
log("OrderID=%s (buy %s XRP for %s %s each) remains dangling. Trying to cancel...",
orderId, orderInfo.getAmountXrp(),
orderInfo.getPrice(), _baseCurrency);
if (_rippleApi.cancelSynOrder(orderId)) {
log("...success");
} else {
log("...failed");
}
}
}
}
}
if (arbBalance >= 0.1) {
if (arbRatio < _parity) {
if (arbRatio > _parity * 1.1 || arbRatio < _parity * 0.9) {
log("ARB ratio has suspicious value %s. Let's leave it be",
arbRatio);
return;
}
log("Chance to sell %s for %s (ARB ratio %s < {3:0.00000})",
_arbCurrency, _baseCurrency, arbRatio, _parity);
double arbVolume = arbMarket.Asks.get(0).getAmount();
double baseVolume = baseMarket.Bids.get(0).getAmount();
if (arbVolume < MIN_TRADE_VOLUME
|| baseVolume < MIN_TRADE_VOLUME) {
log("Insufficient volume: %s XRP for %s; %s XRP for %s",
arbVolume, _arbCurrency, baseVolume, _baseCurrency);
} else {
// Try to buy XRP for ARB
double amount = Math.min(baseVolume, arbVolume);
long orderId = _rippleApi.placeSynXRPBuyOrder(
lowestArbAskPrice + 0.00001, amount, _get);
log("Tried to buy %s XRP for %s %s each. OrderID=%s",
amount, lowestArbAskPrice, _arbCurrency, orderId);
Offer orderInfo = _rippleApi.getSynOrderInfo(orderId);
if (null != orderInfo && orderInfo.Closed) {
double newXrpBalance = _rippleApi.getSynXrpBalance();
amount = newXrpBalance - xrpBalance;
log("Buy XRP orderID=%s filled OK, bought %s XRP",
orderId, amount);
// Try to sell XRP for BASIC
long baseBuyOrderId = _rippleApi.placeSynXRPSellOrder(
highestBaseBidPrice * 0.9, amount, _pay);
log("Tried to sell %s XRP for %s %s each. OrderID=%s",
amount, highestBaseBidPrice, _baseCurrency,
baseBuyOrderId);
Offer baseBuyOrderInfo = _rippleApi
.getSynOrderInfo(baseBuyOrderId);
if (null != baseBuyOrderInfo && baseBuyOrderInfo.Closed) {
log("Buy %s orderID=%s filled OK", _baseCurrency,
baseBuyOrderId);
log("%s -> %s ARBITRAGE SUCCEEDED!",
_arbCurrency, _baseCurrency);
} else {
log("OrderID=%s (sell %s XRP for %s %s each) remains dangling. Forgetting it...",
baseBuyOrderId,
baseBuyOrderInfo.getAmountXrp(),
baseBuyOrderInfo.getPrice(), _baseCurrency);
}
} else {
log("OrderID=%s (buy %s XRP for %s %s each) remains dangling. Trying to cancel...",
orderId, orderInfo.getAmountXrp(),
orderInfo.getPrice(), _arbCurrency);
if (_rippleApi.cancelSynOrder(orderId)) {
log("...success");
} else {
log("...failed");
}
}
}
}
}
if (++_counter == ZOMBIE_CHECK) {
_counter = 0;
cleanupZombies(_baseGateway, -1, -1, null);
cleanupZombies(_arbGateway, -1, -1, null);
}
if (_lastValidXrpBalance > 0.0
&& xrpBalance - 2.0 > _lastValidXrpBalance) {
long orderId = -1;
double amount = xrpBalance - _lastValidXrpBalance;
log("Balance {0:0.000} XRP is too high. Must convert %s to fiat.",
xrpBalance, amount);
if (baseRatio > _parity * _arbFactor) {
log("Converting to %s", _arbCurrency);
orderId = _rippleApi.placeSynXRPSellOrder(
highestArbBidPrice * 0.9, amount, _get);
} else if (arbRatio < _parity) {
log("Converting to %s", _baseCurrency);
orderId = _rippleApi.placeSynXRPSellOrder(
highestBaseBidPrice * 0.9, amount, _pay);
} else {
double baseDiffFromSell = (_parity * _arbFactor) - baseRatio;
double arbDiffFromBuyback = arbRatio - _parity;
if (baseDiffFromSell < arbDiffFromBuyback) {
log("Better converting to %s", _arbCurrency);
orderId = _rippleApi.placeSynXRPSellOrder(
highestArbBidPrice * 0.9, amount, _get);
} else {
log("Bettger converting to %s", _baseCurrency);
orderId = _rippleApi.placeSynXRPSellOrder(
highestBaseBidPrice * 0.9, amount, _pay);
}
}
log("OrderId : %s", orderId);
} else if (xrpBalance > -1.0) {
_lastValidXrpBalance = xrpBalance;
}
}
}