package org.ripple.power.hft.ripple;
import org.ripple.power.RippleSeedAddress;
import org.ripple.power.config.LSystem;
import org.ripple.power.hft.BOT_SET;
import org.ripple.power.hft.BotLog;
import org.ripple.power.hft.Extensions;
import org.ripple.power.hft.TraderBase;
import org.ripple.power.txns.IssuedCurrency;
import org.ripple.power.txns.RippleBackendsAPI;
import org.ripple.power.txns.data.Ask;
import org.ripple.power.txns.data.Bid;
import org.ripple.power.txns.data.CandlesResponse;
import org.ripple.power.txns.data.Market;
import org.ripple.power.txns.data.Offer;
import org.ripple.power.txns.data.Take;
import org.ripple.power.utils.MathUtils;
public class WideSpreadSeller extends TraderBase {
final double _operativeAmount;
final double MIN_SPREAD = 0.0002;
final double MIN_DIFFERENCE = 0.000015;
final double MIN_PRICE_DELTA = 0.0000012;
private String _currencyCode;
private String _gateway_address;
private boolean _selling = true;
private long _sellOrderId = -1;
private double _sellOrderPrice;
private long _buyOrderId = -1;
private double _buyOrderAmount;
private double _buyOrderPrice;
private double _executedSellPrice = -1.0;
private double _executedSellAmount;
private double _xrpBalance;
public WideSpreadSeller(RippleBackendsAPI api, RippleSeedAddress seed,
BOT_SET set, BotLog log) {
super(api, seed, set, log);
this._operativeAmount = set.operative_amount;
if (set.arbitrage) {
if ((set.baseGateway == null) || (set.baseCurrency == null)
|| (set.arbCurrency == null) || (set.arbGateway == null)) {
throw new BOTException(
"Configuration key 'baseGateway' or 'arbGateway' missing");
}
this._gateway_address = set.arbGateway;
this._currencyCode = set.arbCurrency;
this._pay = new Take(set.baseCurrency,set.baseGateway);
this._get = new Take(set.arbCurrency,set.arbGateway);
} else {
if (set.gateway_address == null) {
throw new BOTException(
"Configuration key 'gateway_address' missing");
}
this._gateway_address = set.gateway_address;
this._currencyCode = set.currency_code;
this._pay = IssuedCurrency.BASE;
this._get = new Take( _currencyCode,set.gateway_address);
}
}
@Override
protected void check() {
CandlesResponse candles = _rippleApi.getTradeStatistics(
LSystem.DAY * 2, _get);
Market market = _rippleApi.getSynMarketDepth(null, _pay,
_get, query_limit);
if (market == null) {
return;
}
double spread = MathUtils.round(getLowestAsk(market)
- getHighestBid(market), 5);
float coef = getMadness(candles.results);
this._intervalMs = Extensions.suggestInterval(coef, _minInterval,
_maxInterval);
log("Madness=%s; spread={1:F5} XRP; Interval=%s ms", coef, spread, _intervalMs);
if (_selling)
{
//No active SELL order
if (-1 == _sellOrderId)
{
if (spread >= MIN_SPREAD)
{
double price = suggestSellPrice(market);
double amount = _operativeAmount;
_sellOrderId = _rippleApi.placeSynXRPSellOrder(price, amount,_get);
if (-1 != _sellOrderId)
{
log("Successfully created SELL order with ID=%s; amount=%s XRP; price=%s %s", _sellOrderId, amount, price,_currencyCode);
_sellOrderPrice = price;
}
}
else log("Spread too small for selling");
}
else //We have active SELL order
{
Offer sellOrder = _rippleApi.getSynOrderInfo(_sellOrderId);
if (null == sellOrder){
return;
}
if (!sellOrder.Closed)
{
//Untouched
if (Extensions.eq(sellOrder.getAmountXrp(),_operativeAmount))
{
log("SELL order ID=%s untouched (amount=%s XRP, price=%s %s)", _sellOrderId, _operativeAmount, _sellOrderPrice,_currencyCode);
if (spread < MIN_SPREAD)
{
log("Spread too small, canceling order ID=%s", _sellOrderId);
if (_rippleApi.cancelSynOrder(_sellOrderId))
{
_sellOrderId = -1;
_sellOrderPrice = -1;
}
}
else
{
double price = suggestSellPrice(market);
//Evaluate and update if needed
if (!Extensions.eq(_sellOrderPrice,price))
{
double amount = _operativeAmount;
_sellOrderId = _rippleApi.updateSynXRPSellOrder(_sellOrderId, price, amount,_get);
_sellOrderPrice = price;
log("Updated SELL order ID=%s; amount=%s XRP; price=%s %s", _sellOrderId, _operativeAmount, price,_currencyCode);
}
}
}
else //Partially filled
{
_executedSellPrice = sellOrder.getPrice();
_executedSellAmount = _operativeAmount - sellOrder.getAmountXrp();
log("SELL order ID=%s partially filled at price=%s %s. Filled amount=%s XRP;", _sellOrderId, _executedSellPrice,_currencyCode, _executedSellAmount);
//Cancel the rest of order
if (_rippleApi.cancelSynOrder(_sellOrderId))
{
log("Successfully cancelled SELL order ID=%s",_sellOrderId);
_sellOrderId = -1;
_selling = false;
}
}
}
else
{
//Check if cancelled by Ripple due to "lack of funds"
double balance = _rippleApi.getSynXrpBalance();
if (Extensions.eq(balance,_xrpBalance, 0.1))
{
log("SELL order ID=%s closed but asset validation failed (balance=%s XRP). Asuming was cancelled, trying to recreate",
_sellOrderId, balance);
_sellOrderPrice = suggestSellPrice(market);
double amount = _operativeAmount;
_sellOrderId = _rippleApi.placeSynXRPSellOrder(_sellOrderPrice, amount,_get);
if (-1 != _sellOrderId){
log("Successfully recreated SELL order with ID=%s; amount=%s XRP; price=%s %s", _sellOrderId, amount, _sellOrderPrice,_currencyCode);
}
}
else
{
_executedSellPrice = _sellOrderPrice;
_executedSellAmount = _operativeAmount;
log("SELL order ID=%s (amount=%s XRP) was closed at price=%s %s", _sellOrderId, _operativeAmount, _executedSellPrice,_currencyCode);
_sellOrderId = -1;
_selling = false;
}
}
}
}
else
{
if (-1 == _buyOrderId)
{
_buyOrderPrice = suggestBuyPrice(market);
_buyOrderAmount = _executedSellAmount;
_buyOrderId = _rippleApi.placeSynXRPBuyOrder(_buyOrderPrice, _buyOrderAmount,_get);
log("Successfully created BUY order with ID=%s; amount=%s XRP; price=%s %s", _buyOrderId, _buyOrderAmount, _buyOrderPrice,_currencyCode);
}
else
{
Offer buyOrder = _rippleApi.getSynOrderInfo(_buyOrderId);
if (null == buyOrder){
return;
}
if (!buyOrder.Closed)
{
log("BUY order ID=%s open (amount=%s XRP, price=%s %s)", _buyOrderId, buyOrder.getAmountXrp(), _buyOrderPrice,_currencyCode);
double price = suggestBuyPrice(market);
//Partially filled
if (!Extensions.eq(buyOrder.getAmountXrp(),_buyOrderAmount))
{
log("BUY order ID=%s partially filled at price=%s %s. Remaining amount=%s XRP;", _buyOrderId, buyOrder.getPrice(), _currencyCode,buyOrder.getAmountXrp());
_buyOrderId = _rippleApi.updateSynXRPBuyOrder(_buyOrderId, price, buyOrder.getAmountXrp(),_get);
_buyOrderAmount = buyOrder.getAmountXrp();
_buyOrderPrice = price;
log("Updated BUY order ID=%s; amount=%s XRP; price=%s %s", _buyOrderId, _buyOrderAmount, price,_currencyCode);
}
//We simply need to change price.
else if (!Extensions.eq(_buyOrderPrice,price))
{
_buyOrderId = _rippleApi.updateSynXRPBuyOrder(_buyOrderId, price, _buyOrderAmount,_get);
_buyOrderPrice = price;
log("Updated BUY order ID=%s; amount=%s XRP; price=%s %s", _buyOrderId, _buyOrderAmount, price,_currencyCode);
}
}
else
{
//Check if cancelled by Ripple due to "lack of funds"
double balance = _rippleApi.getSynXrpBalance();
if (Extensions.eq(balance,_xrpBalance, 0.1))
{
log("BUY order ID=%s closed but asset validation failed (balance=%s XRP). Asuming was cancelled, trying to recreate",
_buyOrderId, balance);
_buyOrderPrice = suggestBuyPrice(market);
_buyOrderId = _rippleApi.placeSynXRPBuyOrder(_buyOrderPrice, _buyOrderAmount,_get);
if (-1 != _buyOrderId)
log("Successfully created BUY order with ID=%s; amount=%s XRP; price=%s %s",_buyOrderId, _buyOrderAmount, _buyOrderPrice,_currencyCode);
}
else
{
log("BUY order ID=%s (amount=%s XRP) was closed at price=%s %s", _buyOrderId, _buyOrderAmount, _buyOrderPrice,_currencyCode);
_buyOrderAmount = 0;
_buyOrderId = -1;
_selling = true;
}
}
}
}
_xrpBalance = _rippleApi.getSynXrpBalance();
log("### Balance= %s XRP", _xrpBalance);
}
private double getLowestAsk(Market market) {
double lowestAsk = market.Asks.get(0).getPrice();
if (-1 != _sellOrderId) {
Ask ask = market.Asks.get(0);
if (Extensions.eq(ask.getAmount(), _operativeAmount)
&& Extensions.eq(ask.getPrice(), _sellOrderPrice)) {
lowestAsk = market.Asks.get(1).getPrice();
}
}
return lowestAsk;
}
private double getHighestBid(Market market) {
double bidVolume = 0.0;
for (Bid bid : market.Bids) {
bidVolume += bid.getAmount();
if (bidVolume > MIN_WALL_VOLUME){
return bid.getPrice();
}
}
return market.Bids.get(market.Bids.size() - 1).getPrice();
}
private double suggestSellPrice(Market market) {
double lowestAsk = getLowestAsk(market);
double highestBid = market.Bids.get(0).getPrice();
double spread = lowestAsk - highestBid;
double sellPrice = MathUtils.round(lowestAsk - (spread / 3.0), 7);
if (-1 != _sellOrderId
&& Math.abs(sellPrice - _sellOrderPrice) < MIN_PRICE_DELTA) {
log("DEBUG: SELL price %s too similar, using previous", sellPrice);
return _sellOrderPrice;
}
return sellPrice;
}
private double suggestBuyPrice(Market market) {
double maxPrice = _executedSellPrice - MIN_DIFFERENCE;
double highestBid = market.Bids.get(0).getPrice();
double sumVolume = 0.0;
for (Bid bid : market.Bids) {
if (Extensions.eq(bid.getPrice(), _buyOrderPrice)
&& Extensions.eq(bid.getAmount(), _buyOrderAmount)) {
continue;
}
sumVolume += bid.getAmount();
if (sumVolume < MIN_WALL_VOLUME) {
continue;
}
highestBid = bid.getPrice();
break;
}
if (highestBid > maxPrice) {
return maxPrice;
}
double buyPrice = maxPrice - ((maxPrice - highestBid) / 2.0);
return MathUtils.round(buyPrice, 7);
}
public String getCurrencyCode() {
return _currencyCode;
}
public String getGatewayAddress() {
return _gateway_address;
}
}