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 CrazyBuyerTrap extends TraderBase { private double _operativeAmount; private double _minWallVolume; private double _maxWallVolume; private double _minDifference; private double _minPriceUpdate; // down price limit private double MIN_ORDER_AMOUNT = 0.5; private double NOT_SELL = 0.00001; private String _currencyCode; private String _gateway_address; private double _volumeWall; private int _counter; private long _sellOrderId = -1; private double _sellOrderAmount; private double _sellOrderPrice; private long _buyOrderId = -1; private double _buyOrderAmount; private double _buyOrderPrice; private double _executedSellPrice = -1.0; private double _xrpBalance; public CrazyBuyerTrap(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.DOWN) || (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 (_sellOrderId != -1) { Offer sellOrder = _rippleApi.getSynOrderInfo(_sellOrderId); if (sellOrder == null) { return; } if (!sellOrder.Closed) { if (Extensions.eq(sellOrder.getAmountXrp(), _sellOrderAmount)) { log("SELL order ID=%s untouched (amount=%s XRP, price=%s %s)", _sellOrderId, _sellOrderAmount, _sellOrderPrice, sellOrder.getCurrency()); double price = suggestSellPrice(market); double newAmount = _operativeAmount - _buyOrderAmount; if (newAmount > _sellOrderAmount || !Extensions.eq(_sellOrderPrice, price)) { this._sellOrderId = _rippleApi .updateSynXRPSellOrder(_sellOrderId, price, newAmount, _get); this._sellOrderAmount = newAmount; this._sellOrderPrice = price; log("Updated SELL order ID=%s; amount=%s XRP; price=%s %s", _sellOrderId, _sellOrderAmount, price, sellOrder.getCurrency()); } } else { this._executedSellPrice = sellOrder.getPrice(); this._sellOrderAmount = sellOrder.getAmountXrp(); log("SELL order ID=%s partially filled at price=%s %s. Remaining amount=%s XRP;", _sellOrderId, _executedSellPrice, sellOrder.getCurrency(), sellOrder.getAmountXrp()); // check update amount, drop the BUY 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); _executedSellPrice = _sellOrderPrice; _sellOrderId = -1; _sellOrderAmount = 0.0; } else { double price = suggestSellPrice(market); // The same price is totally unlikely, so we don't // check // it here 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, _sellOrderPrice, sellOrder.getCurrency()); } } } else { // offer close double balance = _rippleApi.getSynXrpBalance(); if (Extensions.eq(balance, _xrpBalance, 0.1d)) { 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 (_sellOrderId != -1) { log("Successfully created SELL order with ID=%s; amount=%s XRP; price=%s %s", _sellOrderId, _sellOrderAmount, _sellOrderPrice, _currencyCode); } } else { _executedSellPrice = _sellOrderPrice; log("SELL order ID=%s (amount=%s XRP) was closed at price=%s %s", _sellOrderId, _sellOrderAmount, _executedSellPrice, _currencyCode); _sellOrderId = -1; _sellOrderAmount = 0; } } } // create sell order else if (_operativeAmount - _buyOrderAmount > NOT_SELL) { _sellOrderPrice = suggestSellPrice(market); double amount = _operativeAmount - _buyOrderAmount; _sellOrderId = _rippleApi.placeSynXRPSellOrder(_sellOrderPrice, amount, _get); _sellOrderAmount = amount; log("Successfully created SELL order with ID=%s; amount=%s XRP; price=%s %s", _sellOrderId, _sellOrderAmount, _sellOrderPrice, _currencyCode); } // buy order if (_operativeAmount - _sellOrderAmount > NOT_SELL) { // BUY order already existed if (-1 != _buyOrderId) { Offer buyOrder = _rippleApi.getSynOrderInfo(_buyOrderId); if (buyOrder == null) { return; } if (!buyOrder.Closed) { log("BUY order ID=%s open (amount=%s XRP, price=%s %s)", _buyOrderId, buyOrder.getAmountXrp(), _buyOrderPrice, buyOrder.getCurrency()); 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(), buyOrder.getCurrency(), 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, buyOrder.getCurrency()); } else if (_operativeAmount - _sellOrderAmount > _buyOrderAmount) { double newAmount = _operativeAmount - _sellOrderAmount; log("SELL dumped some XRP. Increasing BUY amount to %s XRP", newAmount); _buyOrderId = _rippleApi.updateSynXRPBuyOrder( _buyOrderId, price, newAmount, _get); _buyOrderAmount = newAmount; _buyOrderPrice = price; log("Updated BUY order ID=%s; amount=%s XRP; price=%s %s", _buyOrderId, _buyOrderAmount, price, buyOrder.getCurrency()); } 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, buyOrder.getCurrency()); } } 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; } } } else { // No BUY order, create one _buyOrderPrice = suggestBuyPrice(market); _buyOrderAmount = _operativeAmount - _sellOrderAmount; _buyOrderId = _rippleApi.placeSynXRPBuyOrder( _buyOrderPrice, _buyOrderAmount, _get); log("Successfully created BUY order with ID=%s; amount=%s XRP; price=%s %s", _buyOrderId, _buyOrderAmount, _buyOrderPrice, _currencyCode); } } if (_cleanup && ++_counter == ZOMBIE_CHECK) { _counter = 0; cleanupZombies(_seed.getPublicKey(), _buyOrderId, _sellOrderId, null); } } _xrpBalance = _rippleApi.getSynXrpBalance(); log("### Balance= %s XRP", _xrpBalance); } private double suggestSellPrice(Market market) { final int decPlaces = 14; double increment = Math.pow(10.0, -1.0 * decPlaces); double sum = 0; double highestBid = market.Bids.get(0).getPrice(); for (Ask ask : market.Asks) { if (sum + _operativeAmount > _volumeWall && ask.getPrice() - _minDifference > highestBid) { double sellPrice = MathUtils.round(ask.getPrice() - increment, decPlaces); if (-1 != _sellOrderId && sellPrice > market.Asks.get(0).getPrice() && Math.abs(sellPrice - _sellOrderPrice) < _minPriceUpdate) { log("DEBUG: SELL price %s too similar, using previous", sellPrice); return _sellOrderPrice; } return sellPrice; } sum += ask.Amount; if (Extensions.eq(ask.getPrice(), _sellOrderPrice)) { sum -= _sellOrderAmount; } } double price = market.Asks.get(market.Asks.size() - 1).getPrice() - increment; if (_sellOrderId != -1 && Math.abs(price - _sellOrderPrice) < _minPriceUpdate) { return _sellOrderPrice; } return MathUtils.round(price, decPlaces); } private double suggestBuyPrice(Market market) { final int decPlaces = 14; double increment = Math.pow(10.0, -1.0 * decPlaces); 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; } if (bid.getPrice() < _executedSellPrice - _minDifference) { return Extensions.eq(bid.getPrice(), _buyOrderPrice, increment) ? _buyOrderPrice : MathUtils .round(bid.getPrice() + increment, decPlaces); } } return _executedSellPrice - _minDifference; } public String getCurrencyCode() { return _currencyCode; } public String getGatewayAddress() { return _gateway_address; } }