/*abstract*/ public void runStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { // Run the strategy to try to have an order on at least one side of the book according to fixed price range // but never executing as a taker if (!_enabled) // strategy cannot run when disabled { LogStatus(LogStatusType.WARN, "Strategy is disabled and will not run"); return; } // get the BTC Price in dollar SecurityStatus btcusd_quote = _tradeclient.GetSecurityStatus("BITSTAMP", "BTCUSD"); if (btcusd_quote == null || btcusd_quote.LastPx == 0) { if (_priceType == PriceType.TRAILING_STOP || _priceType == PriceType.PEGGED) { LogStatus(LogStatusType.WARN, "BITSTAMP:BTCUSD not available"); return; } } // get the dollar price SecurityStatus usd_official_quote = _tradeclient.GetSecurityStatus("UOL", "USDBRL"); // use USDBRT for the turism quote if (usd_official_quote == null || usd_official_quote.BestAsk == 0 || usd_official_quote.BestBid == 0) { if (_priceType == PriceType.TRAILING_STOP || _priceType == PriceType.PEGGED) { LogStatus(LogStatusType.WARN, "UOL:USDBRL not available"); return; } } OrderBook orderBook = _tradeclient.GetOrderBook(symbol); if (orderBook == null) { LogStatus(LogStatusType.ERROR, "Order Book not available for " + symbol); return; } if (_priceType == PriceType.TRAILING_STOP) { if (_strategySide == OrderSide.SELL && _stop_price > 0) { if (btcusd_quote.LastPx <= _stop_price) { // trigger the stop when the price goes down ulong stop_price_floor = (ulong)(Math.Round(_stop_price / 1e8 * usd_official_quote.BestAsk / 1e8 * _stop_price_adjustment_factor, 2) * 1e8); ulong best_bid_price = orderBook.BestBid != null ? orderBook.BestBid.Price : 0; ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL, stop_price_floor, ulong.MaxValue); Console.WriteLine("DEBUG ** Triggered Trailing Stop and should execute ASAP ** [{0}],[{1}],[{2}],[{3}]", btcusd_quote.LastPx, best_bid_price, stop_price_floor, availableQty); if (best_bid_price >= stop_price_floor) { // force a minimal execution as maker to get e-mail notification when the trailing stop is triggered availableQty = availableQty > _minOrderSize ? availableQty - _minOrderSize : availableQty; sendOrder(webSocketConnection, symbol, OrderSide.SELL, availableQty, stop_price_floor, OrdType.LIMIT, 0, ExecInst.DEFAULT, TimeInForce.IMMEDIATE_OR_CANCEL); } // change the strategy so that the bot might negociate the leaves qty as a maker applying another limit factor as a sell floor _priceType = PriceType.PEGGED; _sell_floor_price = stop_price_floor; Console.WriteLine("DEBUG Changed Strategy to FLOAT with SELL_FLOOR_PRICE=[{0}]", _sell_floor_price); } else { // when the market goes up - adjust the stop in case of a new high price in BTCUSD if (btcusd_quote.LastPx > _trailing_stop_high_price) { _stop_price = (ulong)(Math.Round(btcusd_quote.LastPx / 1e8 * (1 - _stoppx_offset_percentage / 100), 3) * 1e8); _trailing_stop_high_price = btcusd_quote.LastPx; Console.WriteLine("DEBUG Trailing STOP [StopPx={0}] [HighPx=[{1}] [EntryPx={2}] [CapPx={3}]", _stop_price, _trailing_stop_high_price, _trailing_stop_entry_price, _trailing_stop_cap_price); } // and check we should make profit if (_trailing_stop_cap_price > 0 && btcusd_quote.LastPx > _trailing_stop_cap_price) { ulong best_bid_price = orderBook.BestBid != null ? orderBook.BestBid.Price : 0; double local_exchange_spread = (best_bid_price / 1e8) / (double)(btcusd_quote.LastPx / 1e8 * usd_official_quote.BestBid / 1e8); if (local_exchange_spread > 1) { _sell_floor_price = (ulong)(Math.Round(btcusd_quote.LastPx / 1e8 * usd_official_quote.BestBid / 1e8 * _stop_price_adjustment_factor, 2) * 1e8); ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL, _sell_floor_price, ulong.MaxValue); Console.WriteLine("DEBUG ** Reached Trailing Stop CAP and should execute ASAP ** [{0}],[{1}],[{2}],[{3}]", btcusd_quote.LastPx, best_bid_price, _sell_floor_price, availableQty); if (best_bid_price >= _sell_floor_price) { availableQty = availableQty > _minOrderSize ? availableQty - _minOrderSize : availableQty; // force minimal execution as maker sendOrder(webSocketConnection, symbol, OrderSide.SELL, availableQty, _sell_floor_price, OrdType.LIMIT, 0, ExecInst.DEFAULT, TimeInForce.IMMEDIATE_OR_CANCEL); } // change the strategy so that the bot might negociate the leaves qty as a maker _priceType = PriceType.PEGGED; Console.WriteLine("DEBUG Changed Strategy to FLOAT with SELL_FLOOR_PRICE=[{0}]", _sell_floor_price); } else { Console.WriteLine("DEBUG ** Reached Trailing Stop CAP [but without local exchange spread] ** [{0}],[{1}],[{2}],[{3}]", btcusd_quote.LastPx, best_bid_price, usd_official_quote.BestBid, local_exchange_spread); } } } } return; } // ** temporary workaround to support market pegged sell order strategy without plugins** if (_priceType == PriceType.PEGGED && _strategySide == OrderSide.SELL) { // make the price float according to the MID Price // gather the data to calculate the midprice // instead of bestAsk let's use the Price reached if one decides to buy X BTC ulong maxPriceToBuyXBTC = orderBook.MaxPriceForAmountWithoutSelfOrders( OrderBook.OrdSide.SELL, (ulong)(1 * 1e8), // TODO: make it a parameter _tradeclient.UserId); // let's use the VWAP as the market price (short period tick based i.e last 30 min.) ulong marketPrice = _tradeclient.CalculateVWAP(); // calculate the mid price ulong midprice = (ulong)((orderBook.BestBid.Price + maxPriceToBuyXBTC + marketPrice) / 3); _sellTargetPrice = midprice + _pegOffsetValue; // check the selling FLOOR if (_sellTargetPrice < _sell_floor_price) { _sellTargetPrice = _sell_floor_price; } } // another workaround for sending a single stop order and disable the strategy if (_priceType == PriceType.STOP) { if (_strategySide == OrderSide.BUY) { char ordType = (_buyTargetPrice == 0 ? OrdType.STOP_MARKET : OrdType.STOP_LIMIT); ulong ref_price = (_buyTargetPrice > _stop_price ? _buyTargetPrice : _stop_price); ulong qty = calculateOrderQty(symbol, _strategySide, ref_price); sendOrder(webSocketConnection, symbol, OrderSide.BUY, qty, _buyTargetPrice, ordType, _stop_price, default(char)); } if (_strategySide == OrderSide.SELL) { char ordType = (_sellTargetPrice == 0 ? OrdType.STOP_MARKET : OrdType.STOP_LIMIT); ulong qty = calculateOrderQty(symbol, OrderSide.SELL); sendOrder(webSocketConnection, symbol, OrderSide.SELL, qty, _sellTargetPrice, ordType, _stop_price, default(char)); } // disable strategy after sending the stop order... this._enabled = false; return; } // run the strategy that change orders in the book if (_maxOrderSize > 0) { webSocketConnection.EnableTestRequest = false; if (_strategySide == OrderSide.BUY || _strategySide == default(char)) // buy or both { runBuyStrategy(webSocketConnection, symbol); } if (_strategySide == OrderSide.SELL || _strategySide == default(char)) // sell or both { runSellStrategy(webSocketConnection, symbol); } webSocketConnection.EnableTestRequest = true; } }
private void runBuyStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { OrderBook.IOrder bestBid = _tradeclient.GetOrderBook(symbol).BestBid; OrderBook.IOrder bestOffer = _tradeclient.GetOrderBook(symbol).BestOffer; if (_priceType == PriceType.MARKET_AS_MAKER) { ulong buyPrice = 0; if (bestOffer != null) { buyPrice = bestOffer.Price - (ulong)(0.01 * 1e8); } else if (bestBid != null) { buyPrice = bestBid.Price; } Debug.Assert(_buy_cap_price > 0); if (_buy_cap_price == 0) { _buy_cap_price = ulong.MaxValue; } if (buyPrice > 0 && buyPrice <= _buy_cap_price) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, buyPrice); return; } if (_buy_cap_price == ulong.MaxValue) { _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategyBuyOrderClorid); return; } _buyTargetPrice = _buy_cap_price; // find the best position as maker at the buy cap price } if (_priceType == PriceType.EXPLORE_BOOK_DEPTH) { // set the price based on the depth (this is a lot inefficent but I don't care) OrderBook orderBook = _tradeclient.GetOrderBook(symbol); ulong max_price = orderBook.MaxPriceForAmountWithoutSelfOrders(OrderBook.OrdSide.BUY, _minBookDepth, _tradeclient.UserId); ulong min_price = orderBook.MaxPriceForAmountWithoutSelfOrders(OrderBook.OrdSide.BUY, _maxBookDepth, _tradeclient.UserId); max_price = max_price < ulong.MaxValue ? max_price : min_price; var myOrder = _tradeclient.miniOMS.GetOrderByClOrdID(this._strategyBuyOrderClorid); if (max_price < ulong.MaxValue) { min_price = min_price < ulong.MaxValue ? min_price : max_price; if (myOrder == null || myOrder.Price > max_price || myOrder.Price < min_price) { //LogStatus (LogStatusType.WARN, String.Format ("[DT] must change order price not in expected position {0} {1} {2}", myOrder != null ? myOrder.Price : 0, max_price, min_price)); if (min_price < max_price) { _buyTargetPrice = min_price + (ulong)(0.01 * 1e8); // 1 pip better than min_price } else { _buyTargetPrice = min_price - (ulong)(0.01 * 1e8); // 1 pip worse than min_price } } else { return; // don't change the order because it is still in an acceptable position } } else { // no reference found in the book SecurityStatus usd_official_quote = _tradeclient.GetSecurityStatus("UOL", "USDBRL"); // use USDBRT for the turism quote SecurityStatus btcusd_quote = _tradeclient.GetSecurityStatus("BITSTAMP", "BTCUSD"); if (usd_official_quote != null && usd_official_quote.BestBid > 0 && btcusd_quote != null && btcusd_quote.BestBid > 0) { ulong market_price = _tradeclient.CalculateVWAP(); ulong lastPrice = _tradeclient.GetLastPrice(); ulong off_sale_price = (ulong)(usd_official_quote.BestBid / 1e8 * btcusd_quote.BestBid / 1e8 * 0.5 * 1e8); _buyTargetPrice = Math.Min(Math.Min(market_price, lastPrice), off_sale_price); } else { return; } } } if (bestBid != null) { if (bestBid.UserId != _tradeclient.UserId) { // buy @ 1 cent above the best price (TODO: parameter for price increment) ulong buyPrice = bestBid.Price + (ulong)(0.01 * 1e8); if (buyPrice <= this._buyTargetPrice) { if (buyPrice < bestOffer.Price) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, buyPrice); } else { // avoid being a taker or receiving a reject when using ExecInst=6 but stay in the book with max price ulong max_buy_price = bestOffer.Price - (ulong)(0.01 * 1e8); var own_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategyBuyOrderClorid); ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, max_buy_price); if (own_order == null || own_order.Price != max_buy_price || availableQty > own_order.OrderQty) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, max_buy_price); } } } else { // cannot fight for the first position thus try to find a visible position in the book OrderBook orderBook = _tradeclient.GetOrderBook(symbol); List <OrderBook.Order> buyside = orderBook.GetBidOrders(); int i = buyside.BinarySearch( new OrderBook.Order(OrderBook.OrdSide.BUY, _buyTargetPrice - (ulong)(0.01 * 1e8)), new OrderBook.ReverseOrderPriceComparer() ); int position = (i < 0 ? ~i : i); Debug.Assert(position > 0); if (this._priceType != PriceType.EXPLORE_BOOK_DEPTH) { // verificar se a profundidade vale a pena: (TODO: parameters for max_pos_depth and max_amount_depth) if (position > 15 + 1 && orderBook.DoesAmountExceedsLimit( OrderBook.OrdSide.BUY, position - 1, (ulong)(20 * 1e8))) { _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategyBuyOrderClorid); return; } } var pivotOrder = buyside[position]; if (pivotOrder.UserId == _tradeclient.UserId) { // make sure the order is the same or from another client instance MiniOMS.IOrder own_buy_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategyBuyOrderClorid); if (buyside[position].OrderId == own_buy_order.OrderID) { // ordem ja e minha : pega + recursos disponiveis e cola no preco no vizinho se já nao estiver ulong price_delta = buyside.Count > position + 1 ? pivotOrder.Price - buyside[position + 1].Price : 0; ulong newBuyPrice = (price_delta > (ulong)(0.01 * 1e8) ? pivotOrder.Price - price_delta + (ulong)(0.01 * 1e8) : pivotOrder.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, newBuyPrice); if (newBuyPrice < pivotOrder.Price || availableQty > pivotOrder.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, newBuyPrice, availableQty); } } } else { // estabelece preco de venda 1 centavo maior do que nesta posicao ulong newbuyPrice = pivotOrder.Price + (ulong)(0.01 * 1e8); replaceOrder(webSocketConnection, symbol, OrderSide.BUY, newbuyPrice); } } } else { // make sure the order is the same or from another client instance // check and replace order to get closer to the order in the second position and gather more avaible funds List <OrderBook.Order> buyside = _tradeclient.GetOrderBook(symbol).GetBidOrders(); MiniOMS.IOrder own_buy_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategyBuyOrderClorid); if (buyside[0].OrderId == own_buy_order.OrderID) { ulong price_delta = buyside.Count > 1 ? buyside[0].Price - buyside[1].Price : 0; ulong newBuyPrice = (price_delta > (ulong)(0.01 * 1e8) ? bestBid.Price - price_delta + (ulong)(0.01 * 1e8) : bestBid.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, newBuyPrice); if (newBuyPrice < bestBid.Price || availableQty > bestBid.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, newBuyPrice, availableQty); } } } } else { // empty book scenario ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, _buyTargetPrice); Debug.Assert(_buyTargetPrice > 0); ulong buy_price = Math.Min(_buyTargetPrice, bestOffer != null ? bestOffer.Price - (ulong)(0.01 * 1e8) : ulong.MaxValue); sendOrder(webSocketConnection, symbol, OrderSide.BUY, availableQty, buy_price); } }
/*abstract*/ public void runStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { // Run the strategy to try to have an order on at least one side of the book according to fixed price range // but never executing as a taker if (!_enabled) // strategy cannot run when disabled { LogStatus(LogStatusType.WARN, "Strategy is disabled and will not run"); return; } // ** temporary workaround to support market pegged sell order strategy without plugins** if (_priceType == PriceType.PEGGED && _strategySide == OrderSide.SELL) { // make the price float according to the MID Price /* * // requires the Security List for the trading symbol * SecurityStatus status = _tradeclient.GetSecurityStatus ("BLINK", symbol); * if (status == null) * { * LogStatus( * LogStatusType.WARN, * String.Format( * "Waiting Security Status BLINK:{0} to run Pegged strategy", * symbol) * ); * return; * } */ // check the remaining qty that can still be sold ulong theSoldAmount = _tradeclient.GetSoldAmount(); if (theSoldAmount < _maxAmountToSell) { ulong uAllowedAmountToSell = _maxAmountToSell - theSoldAmount; _maxTradeSize = _maxTradeSize < uAllowedAmountToSell ? _maxTradeSize : uAllowedAmountToSell; _maxTradeSize = _maxTradeSize > _minTradeSize ? _maxTradeSize : _minTradeSize; } else { LogStatus(LogStatusType.WARN, String.Format("[runStrategy] Cannot exceed the allowed max amount to sell : {0} {1}", theSoldAmount, _maxAmountToSell)); _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategySellOrderClorid); return; } // gather the data to calculate the midprice OrderBook orderBook = _tradeclient.GetOrderBook(symbol); // instead of bestAsk let's use the Price reached if one decides to buy 1 BTC ulong maxPriceToBuy1BTC = orderBook.MaxPriceForAmountWithoutSelfOrders( OrderBook.OrdSide.SELL, (ulong)(1 * 1e8), // TODO: make it a parameter _tradeclient.UserId); // gather the magic element of the midprice (i.e. price to buy 10 BTC) ulong maxPriceToBuyXBTC = orderBook.MaxPriceForAmountWithoutSelfOrders( OrderBook.OrdSide.SELL, (ulong)(10 * 1e8), // TODO: make it a parameter _tradeclient.UserId); // instead of the last price let's use the VWAP (short period tick based i.e last 30 min.) ulong vwap = _tradeclient.CalculateVWAP(); ulong lastPx = _tradeclient.GetLastPrice(); ulong marketPrice = vwap > lastPx ? vwap : lastPx; // calculate the mid price //ulong midprice = (ulong)((status.BestAsk + status.BestBid + status.LastPx + maxPriceToBuyXBTC) / 4); ulong midprice = (ulong)((orderBook.BestBid.Price + maxPriceToBuy1BTC + maxPriceToBuyXBTC + marketPrice) / 4); Debug.Assert(_pegOffsetValue > 0); _sellTargetPrice = midprice + _pegOffsetValue; // get the dollar price SecurityStatus usd_official_quote = _tradeclient.GetSecurityStatus("UOL", "USDBRL"); // use USDBRT for the turism quote if (usd_official_quote == null || usd_official_quote.BestAsk == 0) { LogStatus(LogStatusType.WARN, "UOL:USDBRL not available"); } // get the BTC Price in dollar SecurityStatus bitfinex_btcusd_quote = _tradeclient.GetSecurityStatus("BITSTAMP", "BTCUSD"); if (bitfinex_btcusd_quote == null || bitfinex_btcusd_quote.BestAsk == 0) { LogStatus(LogStatusType.WARN, "BITSTAMP:BTCUSD not available"); } // calculate the selling floor must be at least the price of the BTC in USD //ulong floor = (ulong)(1.01 * bitfinex_btcusd_quote.BestAsk * (float)(usd_official_quote.BestAsk / 1e8)); //if (floor == 0) { ulong floor = (ulong)(8900 * 1e8); // TODO: make it an optional parameter or pegged to the dolar bitcoin //} //floor = (ulong)(5400 * 1e8); // check the selling FLOOR if (_sellTargetPrice < floor) { _sellTargetPrice = floor; } } // run the strategy if (_maxTradeSize > 0) { webSocketConnection.EnableTestRequest = false; if (_strategySide == OrderSide.BUY || _strategySide == default(char)) // buy or both { runBuyStrategy(webSocketConnection, symbol); } if (_strategySide == OrderSide.SELL || _strategySide == default(char)) // sell or both { runSellStrategy(webSocketConnection, symbol); } webSocketConnection.EnableTestRequest = true; } }