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;
                }
            }
        }
        //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 ComputeRatiosAndTakePosition(object sender, BinancewholeMarketDataUpdateEventArgs args)
        {
            if (!IsBotRunning)
            {
                return;
            }


            try
            {
                MinutesSinceLastPosition++;
                //if (MinutesSinceLastPosition < _parameters.TimeWindowInMin)
                //{
                //    return;
                //}
                //else
                //{
                //    MinutesSinceLastPosition = 0;
                //}

                if (CurrentHoldBaseAssetPos.Any(x => x.Value.Side != 0) && MinutesSinceLastPosition <= _parameters.MaxWaitMinutes)
                {
                    return;
                }


                //Calculate ratios for each crypto
                if (CurrRatios.Count == 0)
                {
                    foreach (var symbol in ActualTradedSymbols)
                    {
                        //movingAverages.Add(symbol, null);
                        CurrRatios.Add(symbol, null);
                    }
                }

                //Calcul des indicateurs pour chaque asset
                List <string> symbolsKeys = CurrRatios.Keys.ToList();
                foreach (var symbol in symbolsKeys)
                {
                    List <Kline> lastSymbolKlines = _binanceWsClient.WholeMarketKlines[symbol].Select(x => x.Kline).ToList();
                    LastMinuteTimeMark = Math.Max(LastMinuteTimeMark, lastSymbolKlines.Last().KlineCloseTime);

                    decimal?newMovingAverage = (decimal?)lastSymbolKlines.TakeLast(_parameters.MovingAveragePeriods * _parameters.TimeWindowInMin).Average(k => k.ClosePrice);


                    decimal?recentMovingAverage = (decimal?)lastSymbolKlines.Last().ClosePrice;


                    if (!LastMovingAveragesRatios.ContainsKey(symbol))
                    {
                        decimal?prevMovingAverage = (decimal?)lastSymbolKlines.TakeLast((_parameters.MovingAveragePeriods + 1) * _parameters.TimeWindowInMin)
                                                    .SkipLast(_parameters.TimeWindowInMin).Average(k => k.ClosePrice);


                        decimal?mostPrevMovingAverage = (decimal?)lastSymbolKlines.TakeLast((_parameters.MovingAveragePeriods + 2) * _parameters.TimeWindowInMin)
                                                        .SkipLast(2 * _parameters.TimeWindowInMin).Average(k => k.ClosePrice);

                        decimal?prevRecentMovingAverage = (decimal?)lastSymbolKlines.SkipLast(_parameters.TimeWindowInMin).Last().ClosePrice;


                        decimal?mostPrevRecentMovingAverage = (decimal?)lastSymbolKlines.SkipLast(2 * _parameters.TimeWindowInMin).Last().ClosePrice;

                        LastMovingAveragesRatios.Add(symbol, new List <decimal?> {
                            mostPrevRecentMovingAverage / mostPrevMovingAverage != null ? (decimal)Math.Log((double)(mostPrevRecentMovingAverage / mostPrevMovingAverage)) : null,
                            prevRecentMovingAverage / prevMovingAverage != null ? (decimal)Math.Log((double)(prevRecentMovingAverage / prevMovingAverage)) : null,
                            recentMovingAverage / newMovingAverage != null ? (decimal)Math.Log((double)(recentMovingAverage / newMovingAverage)) : null
                        });
                    }
                    else
                    {
                        LastMovingAveragesRatios[symbol].Add(recentMovingAverage / newMovingAverage != null ? (decimal)Math.Log((double)(recentMovingAverage / newMovingAverage)) : null);
                        if (LastMovingAveragesRatios[symbol].Count > 3)
                        {
                            LastMovingAveragesRatios[symbol].RemoveAt(0);
                        }
                    }

                    if (LastMovingAveragesRatios[symbol].Any(x => x == null))
                    {
                        CurrRatios[symbol] = null;
                        continue;
                    }

                    IEnumerable <Kline> lastTimeWindowKlines = lastSymbolKlines.TakeLast(_parameters.TimeWindowInMin + 1);
                    decimal             lastVariation        = (decimal)Math.Log((double)(lastTimeWindowKlines.Last().ClosePrice / lastTimeWindowKlines.First().ClosePrice));

                    decimal a1 = _parameters.MovAvgRatioMixParam;
                    decimal a2 = _parameters.MovAvgSecondOrderDiffMixParam;
                    CurrRatios[symbol] = (1 - a1) * lastVariation + a1 * (1 - a2) * LastMovingAveragesRatios[symbol][2] - a2 * (LastMovingAveragesRatios[symbol][2] - 2 * LastMovingAveragesRatios[symbol][1] + LastMovingAveragesRatios[symbol][0]);
                }



                Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss")}] Ratios: {string.Join(" -- ", CurrRatios.OrderBy(kvp => kvp.Value ?? 0).Select(kvp => $"{kvp.Key}: {kvp.Value}"))}");


                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));
                List <string> newSelectedAssetNames = CurrRatios.Where(x => x.Value != null).OrderBy(x => x.Value).Select(x => x.Key)
                                                      .Skip(minTakenRank - 1).Take(maxTakenRank - minTakenRank + 1).ToList();

                Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss")}] New selected cryptos: " + string.Join(" / ", newSelectedAssetNames));

                List <string> currHoldAssetNames = CurrentHoldBaseAssetPos.Where(p => p.Value.Side != 0).Select(p => p.Key.ToLower()).ToList();
                List <string> soldAssetNames     = currHoldAssetNames.Except(newSelectedAssetNames).ToList();
                List <string> boughtAssetNames   = newSelectedAssetNames.Except(currHoldAssetNames).ToList();


                NbTradeCycles += 0.5m * (soldAssetNames.Count + boughtAssetNames.Count) / (maxTakenRank - minTakenRank + 1);

                await PlaceLimitOrdersTillFulfilled(soldAssetNames, boughtAssetNames);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[{DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss")}] Erreur dans StartBot: {ex.Message} -- {ex.InnerException} -- {ex.StackTrace}");
                //_binanceWsClient.SendFailedWSMail(ex.Message);
                //throw ex;
            }
        }