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