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 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; } }