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 CrazySellerTrap extends TraderBase {
// XRP amount to trade
private double _operativeAmount;
private double _minWallVolume;
private double _maxWallVolume;
// Volumen of XRP necessary to accept our offer
private double _volumeWall;
// Minimum difference between BUY price and subsequent SELL price (so we
// have at least some profit). Value from config.
private double _minDifference;
// Tolerance of BUY price. Usefull if possible price change is minor, to
// avoid frequent order updates. Value from config.
private double _minPriceUpdate; // fiat/XRP
private final double MIN_ORDER_AMOUNT = 0.5;
private String _currencyCode;
private String _gateway_address;
private int _counter;
private double MIN_WALL_VOLUME = 100.0;
// Active BUY order ID
private long _buyOrderId = -1;
// Active BUY order amount
private double _buyOrderAmount;
// Active BUY order price
private double _buyOrderPrice;
// Active SELL order ID
private long _sellOrderId = -1;
// Active SELL order amount
private double _sellOrderAmount;
// Active SELL order price
private double _sellOrderPrice;
// The price at which we bought from crazy buyer
private double _executedBuyPrice = -1.0;
private double _xrpBalance;
public CrazySellerTrap(RippleBackendsAPI api, RippleSeedAddress seed,
BOT_SET set, BotLog log) {
super(api, seed, set, log);
this._operativeAmount = set.operative_amount;
this._minWallVolume = set.min_volume;
this._maxWallVolume = set.max_volume;
this._minDifference = set.minDifference;
this._minPriceUpdate = set.minPriceUpdate;
this._currencyCode = set.currency_code;
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._pay = IssuedCurrency.BASE;
this._get = new Take(_currencyCode,set.gateway_address);
}
if (_log != null) {
log("Zombie cleanup: " + _cleanup);
}
}
@Override
protected void check() {
RippleBOTLoader.Trend trend = RippleBOTLoader
.getTrend(_currencyCode, 5);
if ((trend == null) || (trend == RippleBOTLoader.Trend.UP)
|| (trend == RippleBOTLoader.Trend.UNKOWN)) {
CandlesResponse candles = _rippleApi.getTradeStatistics(
LSystem.DAY * 2, _get);
Market market = _rippleApi.getSynMarketDepth(null, _pay,
_get, query_limit);
if (market == null) {
return;
}
float coef = getMadness(candles.results);
this._volumeWall = Extensions.suggestWallVolume(coef,
_minWallVolume, _maxWallVolume);
this._intervalMs = Extensions.suggestInterval(coef, _minInterval,
_maxInterval);
log("Madness=%s; Volume=%s XRP; Interval=%s ms", coef, _volumeWall,
_intervalMs);
if (-1 != _buyOrderId) {
Offer buyOrder = _rippleApi.getSynOrderInfo(_buyOrderId);
if (buyOrder == null) {
return;
}
if (!buyOrder.Closed) {
if (Extensions.eq(buyOrder.getAmountXrp(), _buyOrderAmount)) {
log("BUY order ID=%s untouched (amount=%s XRP, price=%s %s)",
_buyOrderId, _buyOrderAmount, _buyOrderPrice,
buyOrder.getCurrency());
double price = suggestBuyPrice(market);
double newAmount = _operativeAmount - _sellOrderAmount;
if (newAmount > _buyOrderAmount
|| !Extensions.eq(_buyOrderPrice, price)) {
_buyOrderAmount = newAmount;
_buyOrderId = _rippleApi.updateSynXRPBuyOrder(
_buyOrderId, price, newAmount, _get);
_buyOrderPrice = price;
log("Updated BUY order ID=%s; amount=%s XRP; price=%s %s",
_buyOrderId, _buyOrderAmount, price,
buyOrder.getCurrency());
}
} else
{
_executedBuyPrice = buyOrder.getPrice();
_buyOrderAmount = buyOrder.getAmountXrp();
log("BUY order ID=%s partially filled at price=%s %s. Remaining amount=%s XRP;",
_buyOrderId, _executedBuyPrice,
buyOrder.getCurrency(), buyOrder.getAmountXrp());
if (buyOrder.getAmountXrp() < MIN_ORDER_AMOUNT) {
log("The remaining BUY amount is too small, canceling the order ID=%s",
_buyOrderId);
_rippleApi.cancelSynOrder(_buyOrderId);
_executedBuyPrice = _buyOrderPrice;
_buyOrderId = -1;
_buyOrderAmount = 0.0;
} else {
double price = suggestBuyPrice(market);
_buyOrderId = _rippleApi.updateSynXRPBuyOrder(
_buyOrderId, price,
buyOrder.getAmountXrp(), _get);
_buyOrderPrice = price;
log("Updated BUY order ID=%s; amount=%s XRP; price=%s %s",
_buyOrderId, _buyOrderAmount,
_buyOrderPrice, buyOrder.getCurrency());
}
}
} else {
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 {
_executedBuyPrice = _buyOrderPrice;
log("BUY order ID=%s (amount=%s XRP) was closed at price=%s %s",
_buyOrderId, _buyOrderAmount,
_executedBuyPrice, _currencyCode);
_buyOrderId = -1;
_buyOrderAmount = 0;
}
}
} else if (_operativeAmount - _sellOrderAmount > 0.00001)
{
_buyOrderPrice = suggestBuyPrice(market);
_buyOrderAmount = _operativeAmount - _sellOrderAmount;
_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);
}
}
if (_operativeAmount - _buyOrderAmount > 0.00001) {
// SELL order already existed
if (-1 != _sellOrderId) {
Offer sellOrder = _rippleApi.getSynOrderInfo(_sellOrderId);
if (null == sellOrder)
return;
// The order is still open
if (!sellOrder.Closed) {
log("SELL order ID=%s open (amount=%s XRP, price=%s %s)",
_sellOrderId, sellOrder.getAmountXrp(),
_sellOrderPrice, sellOrder.getCurrency());
double price = suggestSellPrice(market);
// Partially filled
if (!Extensions.eq(sellOrder.getAmountXrp(),
_sellOrderAmount)) {
log("SELL order ID=%s partially filled at price=%s %s. Remaining amount=%s XRP;",
_sellOrderId, sellOrder.getPrice(),
sellOrder.getCurrency(),
sellOrder.getAmountXrp());
// Check remaining amount, drop the SELL if it's
// very tiny
if (sellOrder.getAmountXrp() < MIN_ORDER_AMOUNT) {
log("The remaining SELL amount is too small, canceling the order ID=%s",
_sellOrderId);
_rippleApi.cancelSynOrder(_sellOrderId);
_sellOrderId = -1;
_sellOrderAmount = 0.0;
} else {
double amount = sellOrder.getAmountXrp();
_sellOrderId = _rippleApi
.updateSynXRPSellOrder(_sellOrderId,
price, amount, _get);
_sellOrderAmount = amount;
_sellOrderPrice = price;
log("Updated SELL order ID=%s; amount=%s XRP; price=%s %s",
_sellOrderId, _sellOrderAmount, price,
sellOrder.getCurrency());
}
}
// If there were some money released by filling a BUY
// order, increase this SELL order
else if (_operativeAmount - _buyOrderAmount > _sellOrderAmount) {
double newAmount = _operativeAmount
- _buyOrderAmount;
_sellOrderId = _rippleApi.updateSynXRPSellOrder(
_sellOrderId, price, newAmount, _get);
_sellOrderAmount = newAmount;
_sellOrderPrice = price;
log("Updated SELL order ID=%s; amount=%s XRP; price=%s %s",
_sellOrderId, _sellOrderAmount, price,
sellOrder.getCurrency());
}
// Or if we simply need to change price.
else if (!Extensions.eq(_sellOrderPrice, price)) {
_sellOrderId = _rippleApi
.updateSynXRPSellOrder(_sellOrderId, price,
_sellOrderAmount, _get);
_sellOrderPrice = price;
log("Updated SELL order ID=%s; amount=%s XRP; price=%s %s",
_sellOrderId, _sellOrderAmount, price,
sellOrder.getCurrency());
}
} else // Closed or cancelled
{
// Check if cancelled by the network
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);
_sellOrderId = _rippleApi.placeSynXRPSellOrder(
_sellOrderPrice, _sellOrderAmount, _get);
if (-1 != _sellOrderId) {
log("Successfully created SELL order with ID=%s; amount=%s XRP; price=%s %s",
_sellOrderId, _sellOrderAmount,
_sellOrderPrice, _currencyCode);
}
} else {
log("SELL order ID=%s (amount=%s XRP) was closed at price=%s %s",
_sellOrderId, _sellOrderAmount,
_sellOrderPrice, _currencyCode);
_sellOrderAmount = 0;
_sellOrderId = -1;
}
}
} else // No SELL order, create one
{
_sellOrderPrice = suggestSellPrice(market);
double amount = _operativeAmount - _buyOrderAmount;
_sellOrderId = _rippleApi.placeSynXRPSellOrder(
_sellOrderPrice, amount, _get);
_sellOrderAmount = amount;
if (-1 != _sellOrderId) {
log("Successfully created SELL order with ID=%s; amount=%s XRP; price=%s %s",
_sellOrderId, _sellOrderAmount,
_sellOrderPrice, _currencyCode);
}
}
}
if (_cleanup && ++_counter == ZOMBIE_CHECK) {
_counter = 0;
cleanupZombies(_seed.getPublicKey(), _buyOrderId, _sellOrderId,
null);
}
}
_xrpBalance = _rippleApi.getSynXrpBalance();
log("### Balance= %s XRP", _xrpBalance);
}
private double suggestBuyPrice(Market market) {
final int decPlaces = 14;
double increment = Math.pow(10.0, -1.0 * decPlaces);
double sum = 0;
double lowestAsk = market.Asks.get(0).getPrice();
for (Bid bid : market.Bids) {
if (sum + _operativeAmount > _volumeWall
&& bid.getPrice() + 2.0 * _minDifference < lowestAsk) {
double buyPrice = MathUtils.round(bid.getPrice() + increment,
decPlaces);
if (-1 != _buyOrderId
&& buyPrice < market.Bids.get(0).getPrice()
&& Math.abs(buyPrice - _buyOrderPrice) < _minPriceUpdate) {
log("DEBUG: BUY price %s too similar, using previous",
buyPrice);
return _buyOrderPrice;
}
return buyPrice;
}
sum += bid.getAmount();
if (Extensions.eq(bid.getPrice(), _buyOrderPrice)) {
sum -= _buyOrderAmount;
}
}
double price = market.Bids.get(market.Bids.size() - 1).getPrice()
+ increment;
if (-1 != _buyOrderId
&& Math.abs(price - _buyOrderPrice) < _minPriceUpdate) {
return _buyOrderPrice;
}
return MathUtils.round(price, 7);
}
private double suggestSellPrice(Market market) {
final int decPlaces = 14;
double increment = Math.pow(10.0, -1.0 * decPlaces);
double sumVolume = 0.0;
for (Ask ask : market.Asks) {
if (Extensions.eq(ask.getPrice(), _sellOrderPrice)
&& Extensions.eq(ask.getAmount(), _sellOrderAmount)) {
continue;
}
sumVolume += ask.Amount;
if (sumVolume < MIN_WALL_VOLUME) {
continue;
}
if (ask.getPrice() > _executedBuyPrice + _minDifference) {
return Extensions.eq(ask.getPrice(), _sellOrderPrice) ? _sellOrderPrice
: MathUtils
.round(ask.getPrice() - increment, decPlaces);
}
}
return _executedBuyPrice + _minDifference;
}
public String getCurrencyCode() {
return _currencyCode;
}
public String getGatewayAddress() {
return _gateway_address;
}
}