//private decimal GetDrawdownThreshold(int minutesSinceLastPosition) //{ // decimal p = (decimal)Math.Min(1, Math.Pow((MinutesSinceLastPosition-1) / _parameters.AverageDrawbackRunLength, _parameters.DrawbackDecayRate)); // return (1 - p) * _parameters.StartDrawbackRate + p * _parameters.EndDrawbackRate; //} private async Task ReprocessWaitingOrder(BinanceBotOrder matchingOrder, BinanceBookTicker newTicker) { if (newTicker.BestBidPrice == null || newTicker.BestAskPrice == null) { return; } matchingOrder.IsBeingReplaced = true; string asset = newTicker.Symbol.ToLower(); bool isSold = matchingOrder.Side == -1; int symbolPricePrecision = ExchangeInfo.Symbols.FirstOrDefault(s => s.Symbol.ToLower() == asset).PricePrecision; //decimal midPrice = newTicker.BestAskPrice != null && newTicker.BestBidPrice != null // ? (decimal)(0.5m * newTicker.BestAskPrice + 0.5m * newTicker.BestBidPrice) // : newTicker.BestBidPrice == null ? (decimal)newTicker.BestAskPrice // : newTicker.BestAskPrice == null ? (decimal)newTicker.BestBidPrice : 0.0m; //decimal price = isSold // ? Math.Min(Math.Round(midPrice, symbolPricePrecision, MidpointRounding.ToZero) + (decimal)Math.Pow(0.1d, symbolPricePrecision), newTicker.BestAskPrice ?? 0.0m) // : Math.Max(Math.Round(midPrice, symbolPricePrecision, MidpointRounding.ToZero), newTicker.BestBidPrice ?? 0.0m); var lastTickers = _binanceWsClient.UpdatedBookTickersHistory[asset].TakeLast(_parameters.LastTickersNbForRepricing).ToList(); decimal expectedNextBestBidDelta = (decimal)Enumerable.Range(0, _parameters.LastTickersNbForRepricing - 2).Sum(i => (lastTickers[i + 1].BestBidPrice - lastTickers[i].BestBidPrice) * (decimal)Math.Pow((double)(i + 2 - _parameters.LastTickersNbForRepricing), (double)((-1) * _parameters.RepricingDecay))) * _parameters.RepricingCoefficient; decimal expectedNextBestAskDelta = (decimal)Enumerable.Range(0, _parameters.LastTickersNbForRepricing - 2).Sum(i => (lastTickers[i + 1].BestAskPrice - lastTickers[i].BestAskPrice) * (decimal)Math.Pow((double)(i + 2 - _parameters.LastTickersNbForRepricing), (double)((-1) * _parameters.RepricingDecay))) * _parameters.RepricingCoefficient; decimal exactPrice = (decimal)(isSold ? newTicker.BestAskPrice + expectedNextBestAskDelta : newTicker.BestBidPrice + expectedNextBestBidDelta); decimal price = Math.Round(exactPrice, symbolPricePrecision, MidpointRounding.ToZero); int minTakenRank = 1 + (int)Math.Floor(_parameters.MinTakenRank * (CurrRatios.Count(x => x.Value != null) - 1)); int maxTakenRank = 1 + (int)Math.Floor(_parameters.MaxTakenRank * (CurrRatios.Count(x => x.Value != null) - 1)); decimal qty = isSold ? matchingOrder.Qty : Math.Round((decimal)(CurrentInvestedAmount / (price * (maxTakenRank - minTakenRank + 1))), ExchangeInfo.Symbols.FirstOrDefault(s => asset == $"{s.BaseAsset.ToLower()}{s.QuoteAsset.ToLower()}")?.QuantityPrecision ?? 1, MidpointRounding.ToZero); matchingOrder.Price = price; matchingOrder.Qty = qty; //matchingOrder.LastModifed = DateTime.UtcNow; long previousOrderId = matchingOrder.OrderId; //matchingOrder.OrderId = Guid.NewGuid().ToString(); Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Début annulation de l'ordre {previousOrderId} pour {matchingOrder.Symbol}"); var cancelResponse = await _binanceWsClient.CancelOrder(matchingOrder.Symbol, previousOrderId); var filledOrder = await _binanceWsClient.QueryOrder(matchingOrder.Symbol.ToLower(), matchingOrder.OrderId); if (cancelResponse.IsSuccess && cancelResponse.Code == null) { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Annulation effectuée: {JsonConvert.SerializeObject(cancelResponse)}"); if (filledOrder != null && filledOrder.ExecutedQty > 0) //&& filledOrder.Status == "CANCELED" { matchingOrder.Qty -= filledOrder.ExecutedQty ?? 0.0m; } Debug.WriteLineIf(filledOrder == null, $"Ordre annulé mais introuvable (symbol: {matchingOrder.Symbol})"); Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Replacement ordre pour {matchingOrder.Symbol} @{matchingOrder.Price}usdt"); var response = await _binanceWsClient.PlaceOrder(new BinanceFuturesPlacedOrder { Symbol = matchingOrder.Symbol, Side = matchingOrder.Side == 1 ? "BUY" : "SELL", Price = matchingOrder.Price, Quantity = matchingOrder.Qty, //NewClientOrderId = matchingOrder.OrderId, TimeInForce = "GTX", Type = "LIMIT" }); if (response.IsSuccess) { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Replacement ordre OK"); matchingOrder.OrderId = response.OrderId; matchingOrder.LastModifed = DateTime.UtcNow; } else { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Replacement ordre ERREUR"); } } else { if (filledOrder != null && filledOrder.Status == "FILLED") { string filledAsset = filledOrder.Symbol.ToLower(); decimal filledQty = filledOrder.OrigQty ?? 0.0m; decimal filledPrice = filledOrder.Price ?? 0.0m; bool filledIsSold = filledOrder.Side == "SELL"; if (filledIsSold) { CurrentHoldBaseAssetPos[filledAsset] = new SingleAssetPosition { Qty = 0.0m, AvgEntryPrice = 0.0m, Side = 0, IsTotallyFilled = false, IsBeingLiquidated = false } } ; else { CurrentHoldBaseAssetPos[filledAsset] = new SingleAssetPosition { Qty = filledQty, AvgEntryPrice = filledPrice, Side = 1, IsTotallyFilled = true, LiquidationPrice = filledPrice * (1 - _parameters.RiskThreshold), IsBeingLiquidated = false } }; OrdersToProcess.Remove(matchingOrder); Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Ordre {matchingOrder.OrderId} complété --> retrait de la liste OrdersToProcess"); } else if (filledOrder != null && filledOrder.Status == "EXPIRED") { var response = await _binanceWsClient.PlaceOrder(new BinanceFuturesPlacedOrder { Symbol = matchingOrder.Symbol, Side = matchingOrder.Side == 1 ? "BUY" : "SELL", Price = matchingOrder.Price, Quantity = matchingOrder.Qty, //NewClientOrderId = matchingOrder.OrderId, TimeInForce = "GTX", Type = "LIMIT" }); if (response.IsSuccess) { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Replacement ordre GTX expiré OK (symbol: {matchingOrder.Symbol}, price: {matchingOrder.Price}usdt)"); matchingOrder.OrderId = response.OrderId; matchingOrder.LastModifed = DateTime.UtcNow; } else { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Replacement ordre GTX ERREUR"); } } } matchingOrder.IsBeingReplaced = false; }
private async Task PlaceLimitOrdersTillFulfilled(List <string> soldAssetNames, List <string> boughtAssetNames) { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss")}]Cryptos revendues: " + string.Join(" / ", soldAssetNames)); Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss")}]Cryptos achetées: " + string.Join(" / ", boughtAssetNames)); MinutesSinceLastPosition = 0; foreach (var asset in soldAssetNames.Concat(boughtAssetNames)) { if (OrdersToProcess.Any(o => o.Symbol.ToLower() == asset)) { continue; } bool isSold = soldAssetNames.Contains(asset); decimal price = 0.0m; if (!_parameters.DoPlaceMarketOrders) { price = isSold ? _binanceWsClient.UpdatedBookTickers[asset].BestAskPrice ?? 0.0m : _binanceWsClient.UpdatedBookTickers[asset].BestBidPrice ?? 0.0m; } decimal lastPrice = 0; lastPrice = (decimal)_binanceWsClient.WholeMarketKlines[asset].Last().Kline.ClosePrice; int minTakenRank = 1 + (int)Math.Floor(_parameters.MinTakenRank * (CurrRatios.Count(x => x.Value != null) - 1)); int maxTakenRank = 1 + (int)Math.Floor(_parameters.MaxTakenRank * (CurrRatios.Count(x => x.Value != null) - 1)); decimal qty = isSold ? CurrentHoldBaseAssetPos[asset].Qty : Math.Round(CurrentInvestedAmount / (lastPrice * (maxTakenRank - minTakenRank + 1)), ExchangeInfo.Symbols.FirstOrDefault(s => asset == $"{s.BaseAsset.ToLower()}{s.QuoteAsset.ToLower()}")?.QuantityPrecision ?? 1, MidpointRounding.ToZero); OrdersToProcess.Add(new BinanceBotOrder { //OrderId = Guid.NewGuid().ToString(), Price = price, Qty = qty, Side = (sbyte)(isSold ? -1 : 1), Symbol = asset, LastModifed = DateTime.UtcNow, IsClosingPosition = _parameters.DoPlaceMarketOrders && isSold }); if (isSold) { CurrentHoldBaseAssetPos[asset].IsBeingLiquidated = true; } if (_parameters.DoPlaceMarketOrders) { if (isSold) { CurrentHoldBaseAssetPos[asset] = new SingleAssetPosition { Qty = 0.0m, AvgEntryPrice = 0.0m, Side = 0, IsBeingLiquidated = true } } ; else { CurrentHoldBaseAssetPos[asset] = new SingleAssetPosition { Qty = qty, AvgEntryPrice = price, Side = 1, IsBeingLiquidated = false } }; } } Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Avant envoi ordre: " + string.Join(" / ", OrdersToProcess.Select(o => $"{o.Symbol}: qty = " + (o.Side == -1 ? "-" : "") + $"{o.Qty} "))); List <BinanceFuturesPlacedOrderResponse> responses = new List <BinanceFuturesPlacedOrderResponse>(); #region Process orders till all filled if (_parameters.DoPlaceMarketOrders) { List <string> liquidatedSymbols = CurrentHoldBaseAssetPos.Where(kvp => kvp.Value.IsBeingLiquidated).Select(kvp => kvp.Key.ToLower()).ToList(); foreach (var symbol in liquidatedSymbols) { var cancelResponse = await _binanceWsClient.CancelAllOpenOrders(symbol); Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd - MM - yyyy hh: mm:ss")}] Annulation de l'asset liquidé {symbol}: {JsonConvert.SerializeObject(cancelResponse, new JsonSerializerSettings { Formatting = Formatting.None })}"); //await Task.Delay(DELAY_BETWEEN_ORDERS_IN_MILLISECS); } var marketOrders = OrdersToProcess.Select(o => new BinanceFuturesPlacedOrder { Symbol = o.Symbol, Side = o.Side == 1 ? "BUY" : "SELL", Quantity = o.Qty, Type = "MARKET" }); responses = await _binanceWsClient.PlaceBatchOrder(marketOrders.ToList()); OrdersToProcess.Clear(); } else { responses = await _binanceWsClient.PlaceBatchOrder(OrdersToProcess.Select(o => new BinanceFuturesPlacedOrder { Symbol = o.Symbol, Side = o.Side == 1 ? "BUY" : "SELL", Price = o.Price, Quantity = o.Qty, //NewClientOrderId = o.OrderId, TimeInForce = "GTX", Type = "LIMIT" }) .ToList()); } #endregion foreach (var response in responses) { if (response.IsSuccess && response.Code == null) { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss")}]Vente/achat envoyé: {JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.None })}"); if (!_parameters.DoPlaceMarketOrders) { var order = OrdersToProcess.FirstOrDefault(o => o.Symbol.ToLower() == response.Symbol.ToLower()); order.OrderId = response.OrderId; order.LastModifed = DateTime.UtcNow; } } else { Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss")}]Vente/achat non-envoyé: {JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.None })}"); } } if (_parameters.DoPlaceMarketOrders) { foreach (var symbol in CurrentHoldBaseAssetPos.Keys) { CurrentHoldBaseAssetPos[symbol].IsBeingLiquidated = false; } } }