internal override async Task <BinanceTradeRuleResult> CheckTradeRules(string symbol, decimal?quantity, decimal?price, decimal?stopPrice, OrderType type, CancellationToken ct)
        {
            var outputQuantity  = quantity;
            var outputPrice     = price;
            var outputStopPrice = stopPrice;

            if (BaseClient.TradeRulesBehaviour == TradeRulesBehaviour.None)
            {
                return(BinanceTradeRuleResult.CreatePassed(outputQuantity, outputPrice, outputStopPrice));
            }

            if (ExchangeInfo == null || LastExchangeInfoUpdate == null || (DateTime.UtcNow - LastExchangeInfoUpdate.Value).TotalMinutes > BaseClient.TradeRulesUpdateInterval.TotalMinutes)
            {
                await System.GetExchangeInfoAsync(ct).ConfigureAwait(false);
            }

            if (ExchangeInfo == null)
            {
                return(BinanceTradeRuleResult.CreateFailed("Unable to retrieve trading rules, validation failed"));
            }

            var symbolData = ExchangeInfo.Symbols.SingleOrDefault(s => string.Equals(s.Name, symbol, StringComparison.CurrentCultureIgnoreCase));

            if (symbolData == null)
            {
                return(BinanceTradeRuleResult.CreateFailed($"Trade rules check failed: Symbol {symbol} not found"));
            }

            if (!symbolData.OrderTypes.Contains(type))
            {
                return(BinanceTradeRuleResult.CreateFailed($"Trade rules check failed: {type} order type not allowed for {symbol}"));
            }

            if (symbolData.LotSizeFilter != null || (symbolData.MarketLotSizeFilter != null && type == OrderType.Market))
            {
                var minQty   = symbolData.LotSizeFilter?.MinQuantity;
                var maxQty   = symbolData.LotSizeFilter?.MaxQuantity;
                var stepSize = symbolData.LotSizeFilter?.StepSize;
                if (type == OrderType.Market && symbolData.MarketLotSizeFilter != null)
                {
                    minQty = symbolData.MarketLotSizeFilter.MinQuantity;
                    if (symbolData.MarketLotSizeFilter.MaxQuantity != 0)
                    {
                        maxQty = symbolData.MarketLotSizeFilter.MaxQuantity;
                    }

                    if (symbolData.MarketLotSizeFilter.StepSize != 0)
                    {
                        stepSize = symbolData.MarketLotSizeFilter.StepSize;
                    }
                }

                if (minQty.HasValue && quantity.HasValue)
                {
                    outputQuantity = BinanceHelpers.ClampQuantity(minQty.Value, maxQty !.Value, stepSize !.Value, quantity.Value);
                    if (outputQuantity != quantity.Value)
                    {
                        if (BaseClient.TradeRulesBehaviour == TradeRulesBehaviour.ThrowError)
                        {
                            return(BinanceTradeRuleResult.CreateFailed($"Trade rules check failed: LotSize filter failed. Original quantity: {quantity}, Closest allowed: {outputQuantity}"));
                        }

                        _log.Write(LogVerbosity.Info, $"Quantity clamped from {quantity} to {outputQuantity}");
                    }
                }
            }

            if (price == null)
            {
                return(BinanceTradeRuleResult.CreatePassed(outputQuantity, null, outputStopPrice));
            }

            if (symbolData.PriceFilter != null)
            {
                if (symbolData.PriceFilter.MaxPrice != 0 && symbolData.PriceFilter.MinPrice != 0)
                {
                    outputPrice = BinanceHelpers.ClampPrice(symbolData.PriceFilter.MinPrice, symbolData.PriceFilter.MaxPrice, price.Value);
                    if (outputPrice != price)
                    {
                        if (BaseClient.TradeRulesBehaviour == TradeRulesBehaviour.ThrowError)
                        {
                            return(BinanceTradeRuleResult.CreateFailed($"Trade rules check failed: Price filter max/min failed. Original price: {price}, Closest allowed: {outputPrice}"));
                        }

                        _log.Write(LogVerbosity.Info, $"price clamped from {price} to {outputPrice}");
                    }

                    if (stopPrice != null)
                    {
                        outputStopPrice = BinanceHelpers.ClampPrice(symbolData.PriceFilter.MinPrice,
                                                                    symbolData.PriceFilter.MaxPrice, stopPrice.Value);
                        if (outputStopPrice != stopPrice)
                        {
                            if (BaseClient.TradeRulesBehaviour == TradeRulesBehaviour.ThrowError)
                            {
                                return(BinanceTradeRuleResult.CreateFailed(
                                           $"Trade rules check failed: Stop price filter max/min failed. Original stop price: {stopPrice}, Closest allowed: {outputStopPrice}"));
                            }

                            _log.Write(LogVerbosity.Info,
                                       $"Stop price clamped from {stopPrice} to {outputStopPrice} based on price filter");
                        }
                    }
                }

                if (symbolData.PriceFilter.TickSize != 0)
                {
                    var beforePrice = outputPrice;
                    outputPrice = BinanceHelpers.FloorPrice(symbolData.PriceFilter.TickSize, price.Value);
                    if (outputPrice != beforePrice)
                    {
                        if (BaseClient.TradeRulesBehaviour == TradeRulesBehaviour.ThrowError)
                        {
                            return(BinanceTradeRuleResult.CreateFailed($"Trade rules check failed: Price filter tick failed. Original price: {price}, Closest allowed: {outputPrice}"));
                        }

                        _log.Write(LogVerbosity.Info, $"price rounded from {beforePrice} to {outputPrice}");
                    }

                    if (stopPrice != null)
                    {
                        var beforeStopPrice = outputStopPrice;
                        outputStopPrice = BinanceHelpers.FloorPrice(symbolData.PriceFilter.TickSize, stopPrice.Value);
                        if (outputStopPrice != beforeStopPrice)
                        {
                            if (BaseClient.TradeRulesBehaviour == TradeRulesBehaviour.ThrowError)
                            {
                                return(BinanceTradeRuleResult.CreateFailed(
                                           $"Trade rules check failed: Stop price filter tick failed. Original stop price: {stopPrice}, Closest allowed: {outputStopPrice}"));
                            }

                            _log.Write(LogVerbosity.Info,
                                       $"Stop price floored from {beforeStopPrice} to {outputStopPrice} based on price filter");
                        }
                    }
                }
            }

            return(BinanceTradeRuleResult.CreatePassed(outputQuantity, outputPrice, outputStopPrice));
        }
        public IRuleResult RuleExecuted(Solbot solbot)
        {
            var result  = false;
            var message = string.Empty;

            if (solbot.Communication.StopLoss.IsReady)
            {
                WebCallResult <BinancePlacedOrder> stopLossOrderResult = null;

                var quantity = BinanceHelpers.ClampQuantity(solbot.Communication.Symbol.MinQuantity, solbot.Communication.Symbol.MaxQuantity, solbot.Communication.Symbol.StepSize, solbot.Communication.AvailableAsset.Base);

                if (solbot.Strategy.AvailableStrategy.StopLossType == StopLossType.MARKETSELL)
                {
                    var minNotional = quantity * solbot.Communication.Price.Current;

                    if (minNotional > solbot.Communication.Symbol.MinNotional)
                    {
                        stopLossOrderResult = _binanceClient.PlaceOrder(
                            solbot.Strategy.AvailableStrategy.Symbol,
                            OrderSide.Sell,
                            OrderType.Market,
                            quantity: quantity);
                    }
                    else
                    {
                        message = "not enough";
                    }
                }
                else
                {
                    var stopLossPrice = BinanceHelpers.ClampPrice(solbot.Communication.Symbol.MinPrice, solbot.Communication.Symbol.MaxPrice, solbot.Communication.Price.Current);

                    var minNotional = quantity * stopLossPrice;

                    if (minNotional > solbot.Communication.Symbol.MinNotional)
                    {
                        stopLossOrderResult = _binanceClient.PlaceOrder(
                            solbot.Strategy.AvailableStrategy.Symbol,
                            OrderSide.Sell,
                            OrderType.StopLossLimit,
                            quantity: quantity,
                            stopPrice: BinanceHelpers.FloorPrice(solbot.Communication.Symbol.TickSize, stopLossPrice),
                            price: BinanceHelpers.FloorPrice(solbot.Communication.Symbol.TickSize, stopLossPrice),
                            timeInForce: TimeInForce.GoodTillCancel);
                    }
                    else
                    {
                        message = "not enough";
                    }
                }

                if (!(stopLossOrderResult is null))
                {
                    result = stopLossOrderResult.Success;

                    if (stopLossOrderResult.Success)
                    {
                        solbot.Actions.BoughtPrice     = 0;
                        solbot.Actions.StopLossReached = true;

                        Logger.Info(LogGenerator.TradeResultStart(stopLossOrderResult.Data.OrderId));

                        var prices      = new List <decimal>();
                        var quantityAll = new List <decimal>();
                        var commission  = new List <decimal>();

                        if (stopLossOrderResult.Data.Fills.AnyAndNotNull())
                        {
                            foreach (var item in stopLossOrderResult.Data.Fills)
                            {
                                Logger.Info(LogGenerator.TradeResult(MarketOrder, item));
                                prices.Add(item.Price);
                                quantityAll.Add(item.Quantity);
                                commission.Add(item.Commission);
                            }
                        }

                        Logger.Info(LogGenerator.TradeResultEnd(stopLossOrderResult.Data.OrderId, prices.Average(), quantityAll.Sum(), commission.Sum()));

                        _pushOverNotificationService.Send(
                            LogGenerator.NotificationTitle(WorkingType.PRODUCTION, MarketOrder, solbot.Strategy.AvailableStrategy.Symbol),
                            LogGenerator.NotificationMessage(
                                solbot.Communication.Average.Current,
                                solbot.Communication.Price.Current,
                                solbot.Communication.StopLoss.Change));
                    }
                    else
                    {
                        Logger.Warn(stopLossOrderResult.Error.Message);
                    }
                }
            }

            return(new MarketRuleResult()
            {
                Success = result,
                Message = result
                    ? LogGenerator.OrderMarketSuccess(MarketOrder)
                    : LogGenerator.OrderMarketError(MarketOrder, message)
            });
        }