Example #1
0
        public void Buy(BuyOptions options)
        {
            lock (SyncRoot)
            {
                IRule rule = signalsService.Rules.Entries.FirstOrDefault(r => r.Name == options.Metadata.SignalRule);

                ITradingPair swappedPair = Account.GetTradingPairs().OrderBy(p => p.CurrentMargin).FirstOrDefault(tradingPair =>
                {
                    IPairConfig pairConfig = GetPairConfig(tradingPair.Pair);
                    return(pairConfig.SellEnabled && pairConfig.SwapEnabled && pairConfig.SwapSignalRules != null && pairConfig.SwapSignalRules.Contains(options.Metadata.SignalRule) &&
                           pairConfig.SwapTimeout < (DateTimeOffset.Now - tradingPair.OrderDates.Max()).TotalSeconds);
                });

                if (swappedPair != null)
                {
                    Swap(new SwapOptions(swappedPair.Pair, options.Pair, options.Metadata));
                }
                else if (rule?.Action != Constants.SignalRuleActions.Swap)
                {
                    if (CanBuy(options, out string message))
                    {
                        tradingTimedTask.InitiateBuy(options);
                    }
                    else
                    {
                        loggingService.Debug(message);
                    }
                }
            }
        }
 private DCALevel GetCurrentDCALevel(ITradingPair tradingPair, List <DCALevel> dcaLevels)
 {
     if (tradingPair != null && tradingPair.DCALevel > 0 && dcaLevels.Count >= tradingPair.DCALevel)
     {
         return(dcaLevels[tradingPair.DCALevel - 1]);
     }
     else
     {
         return(null);
     }
 }
Example #3
0
        public void Buy(BuyOptions options)
        {
            lock (syncRoot)
            {
                PauseTasks();
                try
                {
                    IRule       rule       = signalsService.Rules.Entries.FirstOrDefault(r => r.Name == options.Metadata.SignalRule);
                    RuleAction  ruleAction = rule?.Action ?? RuleAction.Default;
                    IPairConfig pairConfig = GetPairConfig(options.Pair);

                    bool arbitragePair = pairConfig.ArbitrageEnabled && pairConfig.ArbitrageSignalRules.Contains(options.Metadata.SignalRule);
                    if (arbitragePair)
                    {
                        Arbitrage arbitrage = Exchange.GetArbitrage(options.Pair, Config.Market, pairConfig.ArbitrageMarkets, pairConfig.ArbitrageType);
                        if (arbitrage.IsAssigned)
                        {
                            Arbitrage(new ArbitrageOptions(options.Pair, arbitrage, options.Metadata));
                        }
                    }
                    else
                    {
                        ITradingPair swappedPair = Account.GetTradingPairs().OrderBy(p => p.CurrentMargin).FirstOrDefault(tradingPair =>
                        {
                            IPairConfig tradingPairConfig = GetPairConfig(tradingPair.Pair);
                            return(tradingPairConfig.SellEnabled && tradingPairConfig.SwapEnabled && tradingPairConfig.SwapSignalRules != null &&
                                   tradingPairConfig.SwapSignalRules.Contains(options.Metadata.SignalRule) &&
                                   tradingPairConfig.SwapTimeout < (DateTimeOffset.Now - tradingPair.OrderDates.DefaultIfEmpty().Max()).TotalSeconds);
                        });

                        if (swappedPair != null)
                        {
                            Swap(new SwapOptions(swappedPair.Pair, options.Pair, options.Metadata));
                        }
                        else if (ruleAction == RuleAction.Default)
                        {
                            if (CanBuy(options, out string message))
                            {
                                tradingTimedTask.InitiateBuy(options);
                            }
                            else
                            {
                                loggingService.Debug(message);
                            }
                        }
                    }
                }
                finally
                {
                    ContinueTasks();
                }
            }
        }
        private PairConfig CreatePairConfig(string pair, ITradingConfig modifiedTradingConfig, IPairConfig modifiedPairConfig, IEnumerable <IRule> appliedRules)
        {
            ITradingPair tradingPair     = tradingService.Account.GetTradingPair(pair);
            DCALevel     currentDCALevel = GetCurrentDCALevel(tradingPair, modifiedTradingConfig.DCALevels);
            DCALevel     nextDCALevel    = GetNextDCALevel(tradingPair, modifiedTradingConfig.DCALevels, modifiedTradingConfig.RepeatLastDCALevel);

            return(new PairConfig
            {
                Rules = appliedRules.Select(r => r.Name),

                MaxPairs = modifiedTradingConfig.MaxPairs,

                BuyEnabled = tradingPair == null ? modifiedTradingConfig.BuyEnabled : modifiedTradingConfig.BuyDCAEnabled,
                BuyType = modifiedTradingConfig.BuyType,
                BuyMaxCost = modifiedTradingConfig.BuyMaxCost,
                BuyMultiplier = tradingPair == null ? (modifiedTradingConfig.BuyMultiplier != 0 ? modifiedTradingConfig.BuyMultiplier : 1) : nextDCALevel?.BuyMultiplier ?? modifiedTradingConfig.BuyDCAMultiplier,
                BuyMinBalance = tradingPair == null ? modifiedTradingConfig.BuyMinBalance : modifiedTradingConfig.BuyDCAMinBalance,
                BuySamePairTimeout = (tradingPair == null ? modifiedTradingConfig.BuySamePairTimeout : nextDCALevel?.BuySamePairTimeout ?? modifiedTradingConfig.BuyDCASamePairTimeout) / Application.Speed,
                BuyTrailing = tradingPair == null ? modifiedTradingConfig.BuyTrailing : nextDCALevel?.BuyTrailing ?? modifiedTradingConfig.BuyDCATrailing,
                BuyTrailingStopMargin = tradingPair == null ? modifiedTradingConfig.BuyTrailingStopMargin : nextDCALevel?.BuyTrailingStopMargin ?? modifiedTradingConfig.BuyDCATrailingStopMargin,
                BuyTrailingStopAction = tradingPair == null ? modifiedTradingConfig.BuyTrailingStopAction : nextDCALevel?.BuyTrailingStopAction ?? modifiedTradingConfig.BuyDCATrailingStopAction,

                SellEnabled = modifiedTradingConfig.SellEnabled,
                SellType = modifiedTradingConfig.SellType,
                SellMargin = currentDCALevel == null ? modifiedTradingConfig.SellMargin : currentDCALevel?.SellMargin ?? modifiedTradingConfig.SellDCAMargin,
                SellTrailing = currentDCALevel == null ? modifiedTradingConfig.SellTrailing : currentDCALevel?.SellTrailing ?? modifiedTradingConfig.SellDCATrailing,
                SellTrailingStopMargin = currentDCALevel == null ? modifiedTradingConfig.SellTrailingStopMargin : currentDCALevel?.SellTrailingStopMargin ?? modifiedTradingConfig.SellDCATrailingStopMargin,
                SellTrailingStopAction = currentDCALevel == null ? modifiedTradingConfig.SellTrailingStopAction : currentDCALevel?.SellTrailingStopAction ?? modifiedTradingConfig.SellDCATrailingStopAction,
                SellStopLossEnabled = modifiedTradingConfig.SellStopLossEnabled,
                SellStopLossAfterDCA = modifiedTradingConfig.SellStopLossAfterDCA,
                SellStopLossMinAge = modifiedTradingConfig.SellStopLossMinAge / Application.Speed,
                SellStopLossMargin = modifiedTradingConfig.SellStopLossMargin,

                SwapEnabled = modifiedPairConfig.SwapEnabled,
                SwapSignalRules = modifiedPairConfig.SwapSignalRules,
                SwapTimeout = (int)Math.Round(modifiedPairConfig.SwapTimeout / Application.Speed),

                ArbitrageEnabled = modifiedPairConfig.ArbitrageEnabled,
                ArbitrageMarkets = modifiedPairConfig.ArbitrageMarkets,
                ArbitrageType = modifiedPairConfig.ArbitrageType,
                ArbitrageBuyMultiplier = modifiedPairConfig.ArbitrageBuyMultiplier,
                ArbitrageSellMultiplier = modifiedPairConfig.ArbitrageSellMultiplier,
                ArbitrageSignalRules = modifiedPairConfig.ArbitrageSignalRules,

                CurrentDCAMargin = currentDCALevel?.Margin,
                NextDCAMargin = nextDCALevel?.Margin
            });
        }
        public void InitiateBuy(BuyOptions options)
        {
            IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair);

            if (!options.ManualOrder && !options.Swap && pairConfig.BuyTrailing != 0)
            {
                if (!trailingBuys.ContainsKey(options.Pair))
                {
                    trailingSells.TryRemove(options.Pair, out SellTrailingInfo sellTrailingInfo);

                    ITradingPair tradingPair   = tradingService.Account.GetTradingPair(options.Pair);
                    decimal      currentPrice  = tradingService.GetCurrentPrice(options.Pair);
                    decimal      currentMargin = 0;

                    var trailingInfo = new BuyTrailingInfo
                    {
                        BuyOptions         = options,
                        Trailing           = pairConfig.BuyTrailing,
                        TrailingStopMargin = pairConfig.BuyTrailingStopMargin,
                        TrailingStopAction = pairConfig.BuyTrailingStopAction,
                        InitialPrice       = currentPrice,
                        LastTrailingMargin = currentMargin,
                        BestTrailingMargin = currentMargin
                    };

                    if (trailingBuys.TryAdd(options.Pair, trailingInfo))
                    {
                        if (LoggingEnabled)
                        {
                            loggingService.Info($"Start trailing buy {tradingPair?.FormattedName ?? options.Pair}. Price: {currentPrice:0.00000000}, Margin: {currentMargin:0.00}");
                        }
                    }
                }
                else
                {
                    if (LoggingEnabled)
                    {
                        //loggingService.Info($"Cancel trailing buy {tradingPair?.FormattedName ?? pair}. Reason: already trailing");
                    }
                }
            }
            else
            {
                PlaceBuyOrder(options);
            }
        }
        public void InitiateSell(SellOptions options)
        {
            IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair);

            if (!options.ManualOrder && !options.Swap && pairConfig.SellTrailing != 0)
            {
                if (!trailingSells.ContainsKey(options.Pair))
                {
                    trailingBuys.TryRemove(options.Pair, out BuyTrailingInfo buyTrailingInfo);

                    ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair);
                    tradingPair.SetCurrentValues(tradingService.GetCurrentPrice(options.Pair), tradingService.GetCurrentSpread(options.Pair));

                    var trailingInfo = new SellTrailingInfo
                    {
                        SellOptions        = options,
                        SellMargin         = pairConfig.SellMargin,
                        Trailing           = pairConfig.SellTrailing,
                        TrailingStopMargin = pairConfig.SellTrailingStopMargin,
                        TrailingStopAction = pairConfig.SellTrailingStopAction,
                        InitialPrice       = tradingPair.CurrentPrice,
                        LastTrailingMargin = tradingPair.CurrentMargin,
                        BestTrailingMargin = tradingPair.CurrentMargin
                    };

                    if (trailingSells.TryAdd(options.Pair, trailingInfo))
                    {
                        if (LoggingEnabled)
                        {
                            loggingService.Info($"Start trailing sell {tradingPair.FormattedName}. Price: {tradingPair.CurrentPrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}");
                        }
                    }
                }
                else
                {
                    if (LoggingEnabled)
                    {
                        //loggingService.Info($"Cancel trailing sell {tradingPair.FormattedName}. Reason: already trailing");
                    }
                }
            }
            else
            {
                PlaceSellOrder(options);
            }
        }
        public void InitiateSell(SellOptions options)
        {
            if (tradingService.Account.HasTradingPair(options.Pair))
            {
                IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair);
                if (!options.ManualOrder && pairConfig.SellTrailing != 0)
                {
                    if (!trailingSells.ContainsKey(options.Pair))
                    {
                        StopTrailingBuy(options.Pair);
                        ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair);
                        tradingPair.SetCurrentValues(tradingService.GetPrice(options.Pair), tradingService.Exchange.GetPriceSpread(options.Pair));

                        var trailingInfo = new SellTrailingInfo
                        {
                            SellOptions        = options,
                            SellMargin         = pairConfig.SellMargin,
                            Trailing           = pairConfig.SellTrailing,
                            TrailingStopMargin = pairConfig.SellTrailingStopMargin,
                            TrailingStopAction = pairConfig.SellTrailingStopAction,
                            InitialPrice       = tradingPair.CurrentPrice,
                            LastTrailingMargin = tradingPair.CurrentMargin,
                            BestTrailingMargin = tradingPair.CurrentMargin
                        };

                        if (trailingSells.TryAdd(options.Pair, trailingInfo))
                        {
                            if (LoggingEnabled)
                            {
                                loggingService.Info($"Start trailing sell {tradingPair.FormattedName}. " +
                                                    $"Price: {tradingPair.CurrentPrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}");
                            }
                        }
                    }
                }
                else
                {
                    orderingService.PlaceSellOrder(options);
                }
            }
            else
            {
                loggingService.Info($"Cancel initiate sell for {options.Pair}. Reason: pair does not exist");
            }
        }
        public void ProcessRule(IRule rule, Dictionary <string, ISignal> signals, string pair, List <string> excludedPairs, double?globalRating)
        {
            IEnumerable <IRuleCondition> conditions = rule.Trailing != null && rule.Trailing.Enabled ? rule.Trailing.StartConditions : rule.Conditions;
            ITradingPair tradingPair = tradingService.Account.GetTradingPair(pair, includeDust: true);
            List <SignalTrailingInfo> trailingInfoList;

            if (!excludedPairs.Contains(pair) && (!trailingSignals.TryGetValue(pair, out trailingInfoList) || !trailingInfoList.Any(t => t.Rule == rule)) &&
                (conditions == null || rulesService.CheckConditions(conditions, signals, globalRating, pair, tradingPair)))
            {
                IEnumerable <ISignal> ruleSignals = conditions != null?signals.Where(s => conditions.Any(c => c.Signal == s.Key)).Select(s => s.Value) : new List <ISignal>();

                if (rule.Trailing != null && rule.Trailing.Enabled)
                {
                    if (trailingInfoList == null)
                    {
                        trailingInfoList = new List <SignalTrailingInfo>();
                        trailingSignals.TryAdd(pair, trailingInfoList);
                    }

                    trailingInfoList.Add(new SignalTrailingInfo
                    {
                        Rule      = rule,
                        StartTime = DateTimeOffset.Now
                    });

                    if (LoggingEnabled)
                    {
                        loggingService.Info($"Start trailing signal for {pair}. Rule: {rule.Name}");
                    }
                }
                else
                {
                    InitiateBuy(pair, rule, ruleSignals);
                }

                if (signalsService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                {
                    excludedPairs.Add(pair);
                }
            }
        }
        public void InitiateBuy(BuyOptions options)
        {
            IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair);

            if (!options.ManualOrder && pairConfig.BuyTrailing != 0)
            {
                if (!trailingBuys.ContainsKey(options.Pair))
                {
                    StopTrailingSell(options.Pair);
                    decimal currentPrice  = tradingService.GetPrice(options.Pair);
                    decimal currentMargin = 0;

                    var trailingInfo = new BuyTrailingInfo
                    {
                        BuyOptions         = options,
                        Trailing           = pairConfig.BuyTrailing,
                        TrailingStopMargin = pairConfig.BuyTrailingStopMargin,
                        TrailingStopAction = pairConfig.BuyTrailingStopAction,
                        InitialPrice       = currentPrice,
                        LastTrailingMargin = currentMargin,
                        BestTrailingMargin = currentMargin
                    };

                    if (trailingBuys.TryAdd(options.Pair, trailingInfo))
                    {
                        if (LoggingEnabled)
                        {
                            ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair);
                            loggingService.Info($"Start trailing buy {tradingPair?.FormattedName ?? options.Pair}. " +
                                                $"Price: {currentPrice:0.00000000}, Margin: {currentMargin:0.00}");
                        }
                    }
                }
            }
            else
            {
                orderingService.PlaceBuyOrder(options);
            }
        }
 private DCALevel GetNextDCALevel(ITradingPair tradingPair, List <DCALevel> dcaLevels, bool repeatLastDCALevel)
 {
     if (tradingPair != null && dcaLevels.Count > 0)
     {
         if (dcaLevels.Count >= tradingPair.DCALevel + 1)
         {
             return(dcaLevels[tradingPair.DCALevel]);
         }
         else if (repeatLastDCALevel)
         {
             return(dcaLevels[dcaLevels.Count - 1]);
         }
         else
         {
             return(null);
         }
     }
     else
     {
         return(null);
     }
 }
Example #11
0
        public IOrderDetails PlaceBuyOrder(BuyOptions options)
        {
            OrderDetails orderDetails = new OrderDetails();

            tradingService.StopTrailingBuy(options.Pair);
            tradingService.StopTrailingSell(options.Pair);

            try
            {
                ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair, includeDust: true);
                options.Price  = tradingService.GetPrice(options.Pair, TradePriceType.Ask, normalize: false);
                options.Amount = options.Amount ?? (options.MaxCost.Value / (options.Pair.EndsWith(Constants.Markets.USDT) ? 1 : options.Price));
                options.Price  = tradingService.Exchange.ClampOrderPrice(options.Pair, options.Price.Value);
                options.Amount = tradingService.Exchange.ClampOrderAmount(options.Pair, options.Amount.Value);

                if (tradingService.CanBuy(options, out string message))
                {
                    IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair);
                    BuyOrder    buyOrder   = new BuyOrder
                    {
                        Type   = pairConfig.BuyType,
                        Date   = DateTimeOffset.Now,
                        Pair   = options.Pair,
                        Price  = options.Price.Value,
                        Amount = options.Amount.Value
                    };

                    lock (tradingService.Account.SyncRoot)
                    {
                        loggingService.Info($"Place buy order for {tradingPair?.FormattedName ?? options.Pair}. " +
                                            $"Price: {buyOrder.Price:0.00000000}, Amount: {buyOrder.Amount:0.########}, Signal Rule: " + (options.Metadata.SignalRule ?? "N/A"));

                        if (!tradingService.Config.VirtualTrading)
                        {
                            orderDetails = tradingService.Exchange.PlaceOrder(buyOrder) as OrderDetails;
                        }
                        else
                        {
                            string pairMarket = tradingService.Exchange.GetPairMarket(options.Pair);
                            orderDetails = new OrderDetails
                            {
                                OrderId      = DateTime.Now.ToFileTimeUtc().ToString(),
                                Side         = OrderSide.Buy,
                                Result       = OrderResult.Filled,
                                Date         = buyOrder.Date,
                                Pair         = buyOrder.Pair,
                                Amount       = buyOrder.Amount,
                                AmountFilled = buyOrder.Amount,
                                Price        = buyOrder.Price,
                                AveragePrice = buyOrder.Price,
                                Fees         = buyOrder.Amount * buyOrder.Price * tradingService.Config.VirtualTradingFees,
                                FeesCurrency = pairMarket
                            };
                        }

                        NormalizeOrder(orderDetails, TradePriceType.Ask);
                        options.Metadata.TradingRules  = pairConfig.Rules.ToList();
                        options.Metadata.LastBuyMargin = options.Metadata.LastBuyMargin ?? tradingPair?.CurrentMargin ?? null;
                        orderDetails.Metadata          = options.Metadata;
                        tradingService.Account.AddBuyOrder(orderDetails);
                        tradingService.Account.Save();
                        tradingService.LogOrder(orderDetails);

                        decimal fees = tradingService.CalculateOrderFees(orderDetails);
                        tradingPair = tradingService.Account.GetTradingPair(orderDetails.Pair, includeDust: true);
                        loggingService.Info("{@Trade}", orderDetails);
                        loggingService.Info($"Buy order result for {orderDetails.OriginalPair ?? tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). " +
                                            $"Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, " +
                                            $"Filled: {orderDetails.AmountFilled:0.########}, Cost: {orderDetails.Cost:0.00000000}, Fees: {fees:0.00000000}");
                        notificationService.Notify($"Bought {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, " +
                                                   $"Price: {orderDetails.AveragePrice:0.00000000}, Cost: {(orderDetails.Cost + fees):0.00000000}");
                    }

                    tradingService.ReapplyTradingRules();
                }
                else
                {
                    loggingService.Info(message);
                }
            }
            catch (Exception ex)
            {
                loggingService.Error($"Unable to place buy order for {options.Pair}", ex);
                notificationService.Notify($"Unable to buy {options.Pair}: {ex.Message}");
            }
            return(orderDetails);
        }
Example #12
0
        public IOrderDetails PlaceSellOrder(SellOptions options)
        {
            OrderDetails orderDetails = new OrderDetails();

            tradingService.StopTrailingSell(options.Pair);
            tradingService.StopTrailingBuy(options.Pair);

            try
            {
                string       normalizedPair = tradingService.NormalizePair(options.Pair);
                ITradingPair tradingPair    = tradingService.Account.GetTradingPair(normalizedPair, includeDust: true);
                options.Price  = tradingService.GetPrice(options.Pair, TradePriceType.Bid);
                options.Amount = options.Amount ?? tradingPair?.Amount ?? 0;
                options.Price  = options.Price != 1 ? tradingService.Exchange.ClampOrderPrice(options.Pair, options.Price.Value) : 1; // 1 = USDT price
                options.Amount = tradingService.Exchange.ClampOrderAmount(options.Pair, options.Amount.Value);

                if (tradingService.CanSell(options, out string message))
                {
                    IPairConfig pairConfig = tradingService.GetPairConfig(normalizedPair);
                    SellOrder   sellOrder  = new SellOrder
                    {
                        Type   = pairConfig.SellType,
                        Date   = DateTimeOffset.Now,
                        Pair   = options.Pair,
                        Price  = options.Price.Value,
                        Amount = options.Amount.Value
                    };

                    lock (tradingService.Account.SyncRoot)
                    {
                        tradingPair.SetCurrentValues(tradingService.GetPrice(normalizedPair), tradingService.Exchange.GetPriceSpread(normalizedPair));
                        string sellPairName = normalizedPair != options.Pair ? options.Pair : tradingPair.FormattedName;
                        loggingService.Info($"Place sell order for {sellPairName}. " +
                                            $"Price: {sellOrder.Price:0.00000000}, Amount: {sellOrder.Amount:0.########}, Margin: {tradingPair.CurrentMargin:0.00}");

                        if (!tradingService.Config.VirtualTrading)
                        {
                            orderDetails = tradingService.Exchange.PlaceOrder(sellOrder) as OrderDetails;
                        }
                        else
                        {
                            string pairMarket = tradingService.Exchange.GetPairMarket(options.Pair);
                            orderDetails = new OrderDetails
                            {
                                OrderId      = DateTime.Now.ToFileTimeUtc().ToString(),
                                Side         = OrderSide.Sell,
                                Result       = OrderResult.Filled,
                                Date         = sellOrder.Date,
                                Pair         = sellOrder.Pair,
                                Amount       = sellOrder.Amount,
                                AmountFilled = sellOrder.Amount,
                                Price        = sellOrder.Price,
                                AveragePrice = sellOrder.Price,
                                Fees         = sellOrder.Amount * sellOrder.Price * tradingService.Config.VirtualTradingFees,
                                FeesCurrency = pairMarket
                            };
                        }

                        NormalizeOrder(orderDetails, TradePriceType.Bid);
                        tradingPair.SetMetadata(tradingPair.Metadata.MergeWith(options.Metadata));
                        orderDetails.Metadata = tradingPair.Metadata;
                        var tradeResult = tradingService.Account.AddSellOrder(orderDetails) as TradeResult;
                        tradeResult.IsSwap      = options.Swap;
                        tradeResult.IsArbitrage = options.Arbitrage;
                        tradingService.Account.Save();
                        tradingService.LogOrder(orderDetails);

                        decimal fees      = tradingService.CalculateOrderFees(orderDetails);
                        decimal margin    = (tradeResult.Profit / (tradeResult.Cost + (tradeResult.Metadata.AdditionalCosts ?? 0)) * 100);
                        string  swapPair  = options.Metadata.SwapPair != null ? $", Swap Pair: {options.Metadata.SwapPair}" : "";
                        string  arbitrage = options.Metadata.Arbitrage != null ? $", Arbitrage: {options.Metadata.Arbitrage} ({options.Metadata.ArbitragePercentage:0.00})" : "";
                        loggingService.Info("{@Trade}", orderDetails);
                        loggingService.Info("{@Trade}", tradeResult);
                        loggingService.Info($"Sell order result for {orderDetails.OriginalPair ?? tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). " +
                                            $"Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Filled: {orderDetails.AmountFilled:0.########}, " +
                                            $"Cost: {orderDetails.Cost:0.00000000}, Fees: {fees:0.00000000}, Margin: {margin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}{arbitrage}");
                        notificationService.Notify($"Sold {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, " +
                                                   $"Price: {orderDetails.AveragePrice:0.00000000}, Margin: {margin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}{arbitrage}");
                    }

                    tradingService.ReapplyTradingRules();
                }
                else
                {
                    loggingService.Info(message);
                }
            }
            catch (Exception ex)
            {
                loggingService.Error($"Unable to place sell order for {options.Pair}", ex);
                notificationService.Notify($"Unable to sell {options.Pair}: {ex.Message}");
            }
            return(orderDetails);
        }
        public void ProcessAllRules()
        {
            IEnumerable <IRule> enabledRules = tradingService.Rules?.Entries?.Where(r => r.Enabled) ?? new List <IRule>();
            List <string>       allPairs     = tradingService.Exchange.GetMarketPairs(tradingService.Config.Market).ToList();
            double?globalRating = signalsService.GetGlobalRating();

            foreach (string pair in allPairs)
            {
                IEnumerable <ISignal> signalsByPair = signalsService.GetSignalsByPair(pair);
                if (signalsByPair != null)
                {
                    Dictionary <string, ISignal> signals = signalsByPair.ToDictionary(s => s.Name, s => s);
                    ITradingPair  tradingPair            = tradingService.Account.GetTradingPair(pair);
                    TradingConfig modifiedTradingConfig  = tradingService.Config.Clone() as TradingConfig;
                    PairConfig    modifiedPairConfig     = new PairConfig();
                    pairConfigs.TryGetValue(pair, out PairConfig oldPairConfig);
                    var appliedRules = new List <IRule>();

                    foreach (var rule in enabledRules)
                    {
                        if (rulesService.CheckConditions(rule.Conditions, signals, globalRating, pair, tradingPair))
                        {
                            var modifiers = rule.GetModifiers <TradingRuleModifiers>();
                            if (modifiers != null)
                            {
                                // Base Trading Config
                                modifiedTradingConfig.MaxPairs              = modifiers.MaxPairs ?? modifiedTradingConfig.MaxPairs;
                                modifiedTradingConfig.BuyEnabled            = modifiers.BuyEnabled ?? modifiedTradingConfig.BuyEnabled;
                                modifiedTradingConfig.BuyMaxCost            = modifiers.BuyMaxCost ?? modifiedTradingConfig.BuyMaxCost;
                                modifiedTradingConfig.BuyMultiplier         = modifiers.BuyMultiplier ?? modifiedTradingConfig.BuyMultiplier;
                                modifiedTradingConfig.BuyMinBalance         = modifiers.BuyMinBalance ?? modifiedTradingConfig.BuyMinBalance;
                                modifiedTradingConfig.BuySamePairTimeout    = modifiers.BuySamePairTimeout ?? modifiedTradingConfig.BuySamePairTimeout;
                                modifiedTradingConfig.BuyTrailing           = modifiers.BuyTrailing ?? modifiedTradingConfig.BuyTrailing;
                                modifiedTradingConfig.BuyTrailingStopMargin = modifiers.BuyTrailingStopMargin ?? modifiedTradingConfig.BuyTrailingStopMargin;
                                modifiedTradingConfig.BuyTrailingStopAction = modifiers.BuyTrailingStopAction ?? modifiedTradingConfig.BuyTrailingStopAction;

                                modifiedTradingConfig.BuyDCAEnabled            = modifiers.BuyDCAEnabled ?? modifiedTradingConfig.BuyDCAEnabled;
                                modifiedTradingConfig.BuyDCAMultiplier         = modifiers.BuyDCAMultiplier ?? modifiedTradingConfig.BuyDCAMultiplier;
                                modifiedTradingConfig.BuyDCAMinBalance         = modifiers.BuyDCAMinBalance ?? modifiedTradingConfig.BuyDCAMinBalance;
                                modifiedTradingConfig.BuyDCASamePairTimeout    = modifiers.BuyDCASamePairTimeout ?? modifiedTradingConfig.BuyDCASamePairTimeout;
                                modifiedTradingConfig.BuyDCATrailing           = modifiers.BuyDCATrailing ?? modifiedTradingConfig.BuyDCATrailing;
                                modifiedTradingConfig.BuyDCATrailingStopMargin = modifiers.BuyDCATrailingStopMargin ?? modifiedTradingConfig.BuyDCATrailingStopMargin;
                                modifiedTradingConfig.BuyDCATrailingStopAction = modifiers.BuyDCATrailingStopAction ?? modifiedTradingConfig.BuyDCATrailingStopAction;

                                modifiedTradingConfig.SellEnabled            = modifiers.SellEnabled ?? modifiedTradingConfig.SellEnabled;
                                modifiedTradingConfig.SellMargin             = modifiers.SellMargin ?? modifiedTradingConfig.SellMargin;
                                modifiedTradingConfig.SellTrailing           = modifiers.SellTrailing ?? modifiedTradingConfig.SellTrailing;
                                modifiedTradingConfig.SellTrailingStopMargin = modifiers.SellTrailingStopMargin ?? modifiedTradingConfig.SellTrailingStopMargin;
                                modifiedTradingConfig.SellTrailingStopAction = modifiers.SellTrailingStopAction ?? modifiedTradingConfig.SellTrailingStopAction;
                                modifiedTradingConfig.SellStopLossEnabled    = modifiers.SellStopLossEnabled ?? modifiedTradingConfig.SellStopLossEnabled;
                                modifiedTradingConfig.SellStopLossAfterDCA   = modifiers.SellStopLossAfterDCA ?? modifiedTradingConfig.SellStopLossAfterDCA;
                                modifiedTradingConfig.SellStopLossMinAge     = modifiers.SellStopLossMinAge ?? modifiedTradingConfig.SellStopLossMinAge;
                                modifiedTradingConfig.SellStopLossMargin     = modifiers.SellStopLossMargin ?? modifiedTradingConfig.SellStopLossMargin;

                                modifiedTradingConfig.SellDCAMargin             = modifiers.SellDCAMargin ?? modifiedTradingConfig.SellDCAMargin;
                                modifiedTradingConfig.SellDCATrailing           = modifiers.SellDCATrailing ?? modifiedTradingConfig.SellDCATrailing;
                                modifiedTradingConfig.SellDCATrailingStopMargin = modifiers.SellDCATrailingStopMargin ?? modifiedTradingConfig.SellDCATrailingStopMargin;
                                modifiedTradingConfig.SellDCATrailingStopAction = modifiers.SellDCATrailingStopAction ?? modifiedTradingConfig.SellDCATrailingStopAction;

                                modifiedTradingConfig.RepeatLastDCALevel = modifiers.RepeatLastDCALevel ?? modifiedTradingConfig.RepeatLastDCALevel;
                                modifiedTradingConfig.DCALevels          = modifiers.DCALevels ?? modifiedTradingConfig.DCALevels;

                                // Base Pair Config
                                modifiedPairConfig.SwapEnabled     = modifiers.SwapEnabled ?? modifiedPairConfig.SwapEnabled;
                                modifiedPairConfig.SwapSignalRules = modifiers.SwapSignalRules ?? modifiedPairConfig.SwapSignalRules;
                                modifiedPairConfig.SwapTimeout     = modifiers.SwapTimeout ?? modifiedPairConfig.SwapTimeout;

                                modifiedPairConfig.ArbitrageEnabled        = modifiers.ArbitrageEnabled ?? modifiedPairConfig.ArbitrageEnabled;
                                modifiedPairConfig.ArbitrageMarkets        = modifiers.ArbitrageMarkets ?? modifiedPairConfig.ArbitrageMarkets;
                                modifiedPairConfig.ArbitrageType           = modifiers.ArbitrageType ?? modifiedPairConfig.ArbitrageType;
                                modifiedPairConfig.ArbitrageBuyMultiplier  = modifiers.ArbitrageBuyMultiplier ?? modifiedPairConfig.ArbitrageBuyMultiplier;
                                modifiedPairConfig.ArbitrageSellMultiplier = modifiers.ArbitrageSellMultiplier ?? modifiedPairConfig.ArbitrageSellMultiplier;
                                modifiedPairConfig.ArbitrageSignalRules    = modifiers.ArbitrageSignalRules ?? modifiedPairConfig.ArbitrageSignalRules;

                                if (oldPairConfig != null && !oldPairConfig.ArbitrageEnabled && modifiedPairConfig.ArbitrageEnabled)
                                {
                                    signalsService.ProcessPair(pair, signals);
                                }
                            }

                            appliedRules.Add(rule);

                            if (tradingService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                            {
                                break;
                            }
                        }
                    }

                    pairConfigs[pair] = CreatePairConfig(pair, modifiedTradingConfig, modifiedPairConfig, appliedRules);
                }
            }

            healthCheckService.UpdateHealthCheck(Constants.HealthChecks.TradingRulesProcessed, $"Rules: {enabledRules.Count()}, Pairs: {allPairs.Count}");
        }
Example #14
0
        private void ArbitrageDirect(ArbitrageOptions options)
        {
            string       arbitragePair            = options.Pair;
            ITradingPair existingArbitragePair    = this.Account.GetTradingPair(arbitragePair);
            IPairConfig  pairConfig               = this.GetPairConfig(options.Pair);
            bool         useExistingArbitragePair = (existingArbitragePair != null && existingArbitragePair.CurrentCost > pairConfig.BuyMaxCost &&
                                                     existingArbitragePair.AveragePrice <= existingArbitragePair.CurrentPrice);

            var buyArbitragePairOptions = new BuyOptions(arbitragePair)
            {
                Arbitrage     = true,
                MaxCost       = pairConfig.BuyMaxCost,
                ManualOrder   = options.ManualOrder,
                IgnoreBalance = useExistingArbitragePair,
                Metadata      = options.Metadata
            };

            if (this.CanBuy(buyArbitragePairOptions, out string message))
            {
                IOrderDetails buyArbitragePairOrderDetails = null;
                if (useExistingArbitragePair)
                {
                    buyArbitragePairOrderDetails = this.Account.AddBlankOrder(buyArbitragePairOptions.Pair,
                                                                              buyArbitragePairOptions.MaxCost.Value / this.GetPrice(buyArbitragePairOptions.Pair, TradePriceType.Ask),
                                                                              includeFees: false);
                    loggingService.Info($"Use existing arbitrage pair for arbitrage: {arbitragePair}. " +
                                        $"Average price: {existingArbitragePair.AveragePrice}, Current price: {existingArbitragePair.CurrentPrice}");
                }
                else
                {
                    buyArbitragePairOrderDetails = orderingService.PlaceBuyOrder(buyArbitragePairOptions);
                }

                if (buyArbitragePairOrderDetails.Result == OrderResult.Filled)
                {
                    decimal buyArbitragePairFees     = this.CalculateOrderFees(buyArbitragePairOrderDetails);
                    string  flippedArbitragePair     = this.Exchange.ChangeMarket(arbitragePair, options.Arbitrage.Market.ToString());
                    var     sellArbitragePairOptions = new SellOptions(flippedArbitragePair)
                    {
                        Arbitrage   = true,
                        Amount      = buyArbitragePairOrderDetails.AmountFilled,
                        ManualOrder = options.ManualOrder,
                        Metadata    = options.Metadata.MergeWith(new OrderMetadata
                        {
                            IsTransitional = true
                        })
                    };

                    IOrderDetails sellArbitragePairOrderDetails = orderingService.PlaceSellOrder(sellArbitragePairOptions);
                    if (sellArbitragePairOrderDetails.Result == OrderResult.Filled)
                    {
                        decimal sellArbitragePairMultiplier = pairConfig.ArbitrageSellMultiplier ?? DEFAULT_ARBITRAGE_SELL_MULTIPLIER;
                        decimal sellArbitragePairFees       = this.CalculateOrderFees(sellArbitragePairOrderDetails);
                        options.Metadata.FeesNonDeductible = buyArbitragePairFees * sellArbitragePairMultiplier;
                        decimal sellMarketPairAmount = sellArbitragePairOrderDetails.AmountFilled * this.GetPrice(flippedArbitragePair, TradePriceType.Bid, normalize: false) * sellArbitragePairMultiplier;
                        string  marketPair           = this.Exchange.GetArbitrageMarketPair(options.Arbitrage.Market);

                        var sellMarketPairOptions = new SellOptions(marketPair)
                        {
                            Arbitrage   = true,
                            Amount      = sellMarketPairAmount,
                            ManualOrder = options.ManualOrder,
                            Metadata    = options.Metadata.MergeWith(new OrderMetadata
                            {
                                IsTransitional = false,
                                OriginalPair   = arbitragePair
                            })
                        };

                        existingArbitragePair = this.Account.GetTradingPair(marketPair);
                        existingArbitragePair.OverrideCost((buyArbitragePairOrderDetails.Cost + sellArbitragePairFees * 2) * sellArbitragePairMultiplier);
                        IOrderDetails sellMarketPairOrderDetails = orderingService.PlaceSellOrder(sellMarketPairOptions);
                        existingArbitragePair.OverrideCost(null);

                        if (sellMarketPairOrderDetails.Result == OrderResult.Filled)
                        {
                            loggingService.Info($"{pairConfig.ArbitrageType} arbitrage successful: {arbitragePair} -> {flippedArbitragePair} -> {marketPair}");
                        }
                        else
                        {
                            loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to sell market pair {arbitragePair}");
                            notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to sell market pair {arbitragePair}");
                        }
                    }
                    else
                    {
                        loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to sell arbitrage pair {flippedArbitragePair}");
                        notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to sell arbitrage pair {flippedArbitragePair}");
                    }
                }
                else
                {
                    loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to buy arbitrage pair {arbitragePair}");
                }
            }
            else
            {
                loggingService.Info($"Unable to arbitrage {options.Pair}: {message}");
            }
        }
        private void ProcessTradingPairs()
        {
            int traidingPairsCount = 0;

            foreach (var tradingPair in tradingService.Account.GetTradingPairs())
            {
                IPairConfig pairConfig = tradingService.GetPairConfig(tradingPair.Pair);
                tradingPair.SetCurrentValues(tradingService.GetCurrentPrice(tradingPair.Pair), tradingService.GetCurrentSpread(tradingPair.Pair));
                tradingPair.Metadata.TradingRules  = pairConfig.Rules.ToList();
                tradingPair.Metadata.CurrentRating = tradingPair.Metadata.Signals != null?signalsService.GetRating(tradingPair.Pair, tradingPair.Metadata.Signals) : null;

                tradingPair.Metadata.CurrentGlobalRating = signalsService.GetGlobalRating();

                if (trailingSells.TryGetValue(tradingPair.Pair, out SellTrailingInfo sellTrailingInfo))
                {
                    if (pairConfig.SellEnabled)
                    {
                        if (Math.Round(tradingPair.CurrentMargin, 1) != Math.Round(sellTrailingInfo.LastTrailingMargin, 1))
                        {
                            if (LoggingEnabled)
                            {
                                loggingService.Info($"Continue trailing sell {tradingPair.FormattedName}. Price: {tradingPair.CurrentPrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}");
                            }
                        }

                        if (tradingPair.CurrentMargin <= sellTrailingInfo.TrailingStopMargin || tradingPair.CurrentMargin < (sellTrailingInfo.BestTrailingMargin - sellTrailingInfo.Trailing))
                        {
                            trailingSells.TryRemove(tradingPair.Pair, out SellTrailingInfo p);

                            if (tradingPair.CurrentMargin > 0 || sellTrailingInfo.SellMargin < 0)
                            {
                                if (sellTrailingInfo.TrailingStopAction == SellTrailingStopAction.Sell || tradingPair.CurrentMargin > sellTrailingInfo.TrailingStopMargin)
                                {
                                    PlaceSellOrder(sellTrailingInfo.SellOptions);
                                }
                                else
                                {
                                    if (LoggingEnabled)
                                    {
                                        loggingService.Info($"Stop trailing sell {tradingPair.FormattedName}. Reason: stop margin reached");
                                    }
                                }
                            }
                            else
                            {
                                if (LoggingEnabled)
                                {
                                    loggingService.Info($"Stop trailing sell {tradingPair.FormattedName}. Reason: negative margin");
                                }
                            }
                        }
                        else
                        {
                            sellTrailingInfo.LastTrailingMargin = tradingPair.CurrentMargin;
                            if (tradingPair.CurrentMargin > sellTrailingInfo.BestTrailingMargin)
                            {
                                sellTrailingInfo.BestTrailingMargin = tradingPair.CurrentMargin;
                            }
                        }
                    }
                    else
                    {
                        trailingSells.TryRemove(tradingPair.Pair, out SellTrailingInfo p);
                    }
                }
                else
                {
                    if (pairConfig.SellEnabled && tradingPair.CurrentMargin >= pairConfig.SellMargin)
                    {
                        InitiateSell(new SellOptions(tradingPair.Pair));
                    }
                    else if (pairConfig.SellEnabled && pairConfig.SellStopLossEnabled && tradingPair.CurrentMargin <= pairConfig.SellStopLossMargin && tradingPair.CurrentAge >= pairConfig.SellStopLossMinAge &&
                             (pairConfig.NextDCAMargin == null || !pairConfig.SellStopLossAfterDCA))
                    {
                        if (LoggingEnabled)
                        {
                            loggingService.Info($"Stop loss triggered for {tradingPair.FormattedName}. Margin: {tradingPair.CurrentMargin:0.00}");
                        }
                        PlaceSellOrder(new SellOptions(tradingPair.Pair));
                    }
                    else if (pairConfig.NextDCAMargin != null && pairConfig.BuyEnabled && pairConfig.NextDCAMargin != null &&
                             !trailingBuys.ContainsKey(tradingPair.Pair) && !trailingSells.ContainsKey(tradingPair.Pair))
                    {
                        if (tradingPair.CurrentMargin <= pairConfig.NextDCAMargin)
                        {
                            var buyOptions = new BuyOptions(tradingPair.Pair)
                            {
                                MaxCost        = tradingPair.AverageCostPaid * pairConfig.BuyMultiplier,
                                IgnoreExisting = true
                            };

                            if (tradingService.CanBuy(buyOptions, message: out string message))
                            {
                                if (LoggingEnabled)
                                {
                                    loggingService.Info($"DCA triggered for {tradingPair.FormattedName}. Margin: {tradingPair.CurrentMargin:0.00}, Level: {pairConfig.NextDCAMargin:0.00}, Multiplier: {pairConfig.BuyMultiplier}");
                                }
                                InitiateBuy(buyOptions);
                            }
                        }
                    }
                }

                traidingPairsCount++;
            }

            foreach (var kvp in trailingBuys)
            {
                string          pair            = kvp.Key;
                BuyTrailingInfo buyTrailingInfo = kvp.Value;
                ITradingPair    tradingPair     = tradingService.Account.GetTradingPair(pair);
                IPairConfig     pairConfig      = tradingService.GetPairConfig(pair);
                decimal         currentPrice    = tradingService.GetCurrentPrice(pair);
                decimal         currentMargin   = Utils.CalculateMargin(buyTrailingInfo.InitialPrice, currentPrice);

                if (pairConfig.BuyEnabled)
                {
                    if (Math.Round(currentMargin, 1) != Math.Round(buyTrailingInfo.LastTrailingMargin, 1))
                    {
                        if (LoggingEnabled)
                        {
                            loggingService.Info($"Continue trailing buy {tradingPair?.FormattedName ?? pair}. Price: {currentPrice:0.00000000}, Margin: {currentMargin:0.00}");
                        }
                    }

                    if (currentMargin >= buyTrailingInfo.TrailingStopMargin || currentMargin > (buyTrailingInfo.BestTrailingMargin - buyTrailingInfo.Trailing))
                    {
                        trailingBuys.TryRemove(pair, out BuyTrailingInfo p);

                        if (buyTrailingInfo.TrailingStopAction == BuyTrailingStopAction.Buy || currentMargin < buyTrailingInfo.TrailingStopMargin)
                        {
                            PlaceBuyOrder(buyTrailingInfo.BuyOptions);
                        }
                        else
                        {
                            if (LoggingEnabled)
                            {
                                loggingService.Info($"Stop trailing buy {tradingPair?.FormattedName ?? pair}. Reason: stop margin reached");
                            }
                        }
                    }
                    else
                    {
                        buyTrailingInfo.LastTrailingMargin = currentMargin;
                        if (currentMargin < buyTrailingInfo.BestTrailingMargin)
                        {
                            buyTrailingInfo.BestTrailingMargin = currentMargin;
                        }
                    }
                }
                else
                {
                    trailingBuys.TryRemove(pair, out BuyTrailingInfo p);
                }
            }

            healthCheckService.UpdateHealthCheck(Constants.HealthChecks.TradingPairsProcessed, $"Pairs: {traidingPairsCount}, Trailing buys: {trailingBuys.Count}, Trailing sells: {trailingSells.Count}");
        }
Example #16
0
        public void Swap(SwapOptions options)
        {
            lock (syncRoot)
            {
                PauseTasks();
                try
                {
                    if (CanSwap(options, out string message))
                    {
                        ITradingPair oldTradingPair = Account.GetTradingPair(options.OldPair);
                        var          sellOptions    = new SellOptions(options.OldPair)
                        {
                            Swap        = true,
                            ManualOrder = options.ManualOrder,
                            Metadata    = new OrderMetadata {
                                SwapPair = options.NewPair
                            }
                        };

                        if (CanSell(sellOptions, out message))
                        {
                            decimal currentMargin       = oldTradingPair.CurrentMargin;
                            decimal additionalCosts     = oldTradingPair.Cost - oldTradingPair.CurrentCost + (oldTradingPair.Metadata.AdditionalCosts ?? 0);
                            int     additionalDCALevels = oldTradingPair.DCALevel;

                            IOrderDetails sellOrderDetails = orderingService.PlaceSellOrder(sellOptions);
                            if (!Account.HasTradingPair(options.OldPair))
                            {
                                var buyOptions = new BuyOptions(options.NewPair)
                                {
                                    Swap        = true,
                                    ManualOrder = options.ManualOrder,
                                    MaxCost     = sellOrderDetails.Cost,
                                    Metadata    = options.Metadata
                                };
                                buyOptions.Metadata.LastBuyMargin       = currentMargin;
                                buyOptions.Metadata.SwapPair            = options.OldPair;
                                buyOptions.Metadata.AdditionalDCALevels = additionalDCALevels;
                                buyOptions.Metadata.AdditionalCosts     = additionalCosts;
                                IOrderDetails buyOrderDetails = orderingService.PlaceBuyOrder(buyOptions);

                                var newTradingPair = Account.GetTradingPair(options.NewPair) as TradingPair;
                                if (newTradingPair != null)
                                {
                                    newTradingPair.Metadata.AdditionalCosts += CalculateOrderFees(sellOrderDetails);
                                    loggingService.Info($"Swap {oldTradingPair.FormattedName} for {newTradingPair.FormattedName}. " +
                                                        $"Old margin: {oldTradingPair.CurrentMargin:0.00}, new margin: {newTradingPair.CurrentMargin:0.00}");
                                }
                                else
                                {
                                    loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to buy {options.NewPair}");
                                    notificationService.Notify($"Unable to swap {options.OldPair} for {options.NewPair}: Failed to buy {options.NewPair}");
                                }
                            }
                            else
                            {
                                loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to sell {options.OldPair}");
                            }
                        }
                        else
                        {
                            loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}: {message}");
                        }
                    }
                    else
                    {
                        loggingService.Info(message);
                    }
                }
                finally
                {
                    ContinueTasks();
                }
            }
        }
Example #17
0
        private void ArbitrageReverse(ArbitrageOptions options)
        {
            string       marketPair            = Exchange.GetArbitrageMarketPair(options.Arbitrage.Market);
            ITradingPair existingMarketPair    = Account.GetTradingPair(marketPair);
            IPairConfig  pairConfig            = GetPairConfig(options.Pair);
            bool         useExistingMarketPair = (existingMarketPair != null && existingMarketPair.CurrentCost > pairConfig.BuyMaxCost &&
                                                  existingMarketPair.AveragePrice <= existingMarketPair.CurrentPrice);

            var buyMarketPairOptions = new BuyOptions(marketPair)
            {
                Arbitrage     = true,
                MaxCost       = pairConfig.BuyMaxCost,
                ManualOrder   = options.ManualOrder,
                IgnoreBalance = useExistingMarketPair,
                Metadata      = options.Metadata
            };

            if (CanBuy(buyMarketPairOptions, out string message))
            {
                IOrderDetails buyMarketPairOrderDetails = null;
                if (useExistingMarketPair)
                {
                    buyMarketPairOrderDetails = Account.AddBlankOrder(buyMarketPairOptions.Pair,
                                                                      buyMarketPairOptions.MaxCost.Value / GetPrice(buyMarketPairOptions.Pair, TradePriceType.Ask),
                                                                      includeFees: false);
                    loggingService.Info($"Use existing market pair for arbitrage: {marketPair}. " +
                                        $"Average price: {existingMarketPair.AveragePrice}, Current price: {existingMarketPair.CurrentPrice}");
                }
                else
                {
                    buyMarketPairOrderDetails = orderingService.PlaceBuyOrder(buyMarketPairOptions);
                }

                if (buyMarketPairOrderDetails.Result == OrderResult.Filled)
                {
                    decimal buyArbitragePairMultiplier = pairConfig.ArbitrageBuyMultiplier ?? DEFAULT_ARBITRAGE_BUY_MULTIPLIER;
                    decimal buyMarketPairFees          = CalculateOrderFees(buyMarketPairOrderDetails);
                    string  arbitragePair          = Exchange.ChangeMarket(options.Pair, options.Arbitrage.Market.ToString());
                    decimal buyArbitragePairAmount = options.Arbitrage.Market == ArbitrageMarket.USDT ?
                                                     buyMarketPairOrderDetails.AmountFilled * GetPrice(buyMarketPairOrderDetails.Pair, TradePriceType.Ask, normalize: false) / GetPrice(arbitragePair, TradePriceType.Ask) :
                                                     buyMarketPairOrderDetails.AmountFilled / GetPrice(arbitragePair, TradePriceType.Ask);

                    var buyArbitragePairOptions = new BuyOptions(arbitragePair)
                    {
                        Arbitrage   = true,
                        ManualOrder = options.ManualOrder,
                        Amount      = buyArbitragePairAmount * buyArbitragePairMultiplier,
                        Metadata    = options.Metadata
                    };

                    IOrderDetails buyArbitragePairOrderDetails = orderingService.PlaceBuyOrder(buyArbitragePairOptions);
                    if (buyArbitragePairOrderDetails.Result == OrderResult.Filled)
                    {
                        decimal buyArbitragePairFees = CalculateOrderFees(buyArbitragePairOrderDetails);
                        options.Metadata.FeesNonDeductible = buyMarketPairFees * buyArbitragePairMultiplier;
                        var sellArbitragePairOptions = new SellOptions(buyArbitragePairOrderDetails.Pair)
                        {
                            Arbitrage   = true,
                            Amount      = buyArbitragePairOrderDetails.AmountFilled,
                            ManualOrder = options.ManualOrder,
                            Metadata    = options.Metadata
                        };

                        TradingPair existingArbitragePair = Account.GetTradingPair(buyArbitragePairOrderDetails.Pair) as TradingPair;
                        existingArbitragePair.OverrideCost(buyArbitragePairOrderDetails.Cost + buyArbitragePairFees * 2);
                        IOrderDetails sellArbitragePairOrderDetails = orderingService.PlaceSellOrder(sellArbitragePairOptions);
                        existingArbitragePair.OverrideCost(null);

                        if (sellArbitragePairOrderDetails.Result == OrderResult.Filled)
                        {
                            loggingService.Info($"{pairConfig.ArbitrageType} arbitrage successful: {marketPair} -> {arbitragePair} -> {existingArbitragePair.Pair}");
                        }
                        else
                        {
                            loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to sell arbitrage pair {arbitragePair}");
                            notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to sell arbitrage pair {arbitragePair}");
                        }
                    }
                    else
                    {
                        loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to buy arbitrage pair {arbitragePair}");
                        notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to buy arbitrage pair {arbitragePair}");
                    }
                }
                else
                {
                    loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to buy market pair {marketPair}");
                }
            }
            else
            {
                loggingService.Info($"Unable to arbitrage {options.Pair}: {message}");
            }
        }
        private void ProcessRules()
        {
            if (tradingService.Config.BuyEnabled)
            {
                var allSignals = signalsService.GetAllSignals();
                if (allSignals != null)
                {
                    var enabledRules = signalsService.Rules.Entries.Where(r => r.Enabled);
                    if (enabledRules.Any())
                    {
                        var    groupedSignals = allSignals.Where(s => tradingService.GetPairConfig(s.Pair).BuyEnabled).GroupBy(s => s.Pair).ToDictionary(g => g.Key, g => g.ToDictionary(s => s.Name, s => s));
                        double?globalRating   = signalsService.GetGlobalRating();

                        var excludedPairs = tradingService.Config.ExcludedPairs
                                            .Concat(tradingService.Account.GetTradingPairs().Select(p => p.Pair))
                                            .Concat(tradingService.GetTrailingBuys()).ToList();

                        if (signalsService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                        {
                            excludedPairs.AddRange(trailingSignals.Keys);
                        }

                        foreach (var rule in enabledRules)
                        {
                            foreach (var group in groupedSignals)
                            {
                                string pair = group.Key;
                                Dictionary <string, ISignal> signals    = group.Value;
                                ITradingPair tradingPair                = tradingService.Account.GetTradingPair(pair);
                                IEnumerable <IRuleCondition> conditions = rule.Trailing != null && rule.Trailing.Enabled ? rule.Trailing.StartConditions : rule.Conditions;
                                List <SignalTrailingInfo>    trailingInfoList;

                                if (!excludedPairs.Contains(pair) && (!trailingSignals.TryGetValue(pair, out trailingInfoList) || !trailingInfoList.Any(t => t.Rule == rule)) &&
                                    rulesService.CheckConditions(conditions, signals, globalRating, pair, tradingPair))
                                {
                                    IEnumerable <ISignal> ruleSignals = signals.Where(s => conditions.Any(c => c.Signal == s.Key)).Select(s => s.Value);
                                    if (rule.Trailing != null && rule.Trailing.Enabled)
                                    {
                                        if (trailingInfoList == null)
                                        {
                                            trailingInfoList = new List <SignalTrailingInfo>();
                                            trailingSignals.TryAdd(pair, trailingInfoList);
                                        }

                                        trailingInfoList.Add(new SignalTrailingInfo
                                        {
                                            Rule      = rule,
                                            StartTime = DateTimeOffset.Now
                                        });

                                        string ruleSignalsList = String.Join(", ", ruleSignals.Select(s => s.Name));
                                        if (LoggingEnabled)
                                        {
                                            loggingService.Info($"Start trailing signal for {pair}. Rule: {rule.Name}, Signals: {ruleSignalsList}");
                                        }
                                    }
                                    else
                                    {
                                        InitiateBuy(pair, rule, ruleSignals);
                                    }

                                    if (signalsService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                                    {
                                        excludedPairs.Add(pair);
                                    }
                                }
                            }
                        }
                    }

                    healthCheckService.UpdateHealthCheck(Constants.HealthChecks.SignalRulesProcessed, $"Rules: {enabledRules.Count()}, Trailing signals: {trailingSignals.Count}");
                }
            }
        }
Example #19
0
        public bool CheckConditions(IEnumerable <IRuleCondition> conditions, Dictionary <string, ISignal> signals, double?globalRating, string pair, ITradingPair tradingPair)
        {
            if (conditions != null)
            {
                foreach (var condition in conditions)
                {
                    ISignal signal = null;
                    if (condition.Signal != null && signals.TryGetValue(condition.Signal, out ISignal s))
                    {
                        signal = s;
                    }

                    if (condition.MinPrice != null && (tradingService.GetPrice(pair) < condition.MinPrice) ||
                        condition.MaxPrice != null && (tradingService.GetPrice(pair) > condition.MaxPrice) ||
                        condition.MinSpread != null && (tradingService.Exchange.GetPriceSpread(pair) < condition.MinSpread) ||
                        condition.MaxSpread != null && (tradingService.Exchange.GetPriceSpread(pair) > condition.MaxSpread) ||
                        condition.MinArbitrage != null && tradingService.Exchange.GetArbitrage(pair, tradingService.Config.Market,
                                                                                               condition.ArbitrageMarket != null ? new List <ArbitrageMarket> {
                        condition.ArbitrageMarket.Value
                    } : null, condition.ArbitrageType).Percentage < condition.MinArbitrage ||
                        condition.MaxArbitrage != null && tradingService.Exchange.GetArbitrage(pair, tradingService.Config.Market,
                                                                                               condition.ArbitrageMarket != null ? new List <ArbitrageMarket> {
                        condition.ArbitrageMarket.Value
                    } : null, condition.ArbitrageType).Percentage > condition.MaxArbitrage ||

                        condition.MinVolume != null && (signal == null || signal.Volume == null || signal.Volume < condition.MinVolume) ||
                        condition.MaxVolume != null && (signal == null || signal.Volume == null || signal.Volume > condition.MaxVolume) ||
                        condition.MinVolumeChange != null && (signal == null || signal.VolumeChange == null || signal.VolumeChange < condition.MinVolumeChange) ||
                        condition.MaxVolumeChange != null && (signal == null || signal.VolumeChange == null || signal.VolumeChange > condition.MaxVolumeChange) ||
                        condition.MinPriceChange != null && (signal == null || signal.PriceChange == null || signal.PriceChange < condition.MinPriceChange) ||
                        condition.MaxPriceChange != null && (signal == null || signal.PriceChange == null || signal.PriceChange > condition.MaxPriceChange) ||
                        condition.MinRating != null && (signal == null || signal.Rating == null || signal.Rating < condition.MinRating) ||
                        condition.MaxRating != null && (signal == null || signal.Rating == null || signal.Rating > condition.MaxRating) ||
                        condition.MinRatingChange != null && (signal == null || signal.RatingChange == null || signal.RatingChange < condition.MinRatingChange) ||
                        condition.MaxRatingChange != null && (signal == null || signal.RatingChange == null || signal.RatingChange > condition.MaxRatingChange) ||
                        condition.MinVolatility != null && (signal == null || signal.Volatility == null || signal.Volatility < condition.MinVolatility) ||
                        condition.MaxVolatility != null && (signal == null || signal.Volatility == null || signal.Volatility > condition.MaxVolatility) ||
                        condition.MinGlobalRating != null && (globalRating == null || globalRating < condition.MinGlobalRating) ||
                        condition.MaxGlobalRating != null && (globalRating == null || globalRating > condition.MaxGlobalRating) ||
                        condition.Pairs != null && (pair == null || !condition.Pairs.Contains(pair)) ||
                        condition.NotPairs != null && (pair == null || condition.NotPairs.Contains(pair)) ||

                        condition.MinAge != null && (tradingPair == null || tradingPair.CurrentAge < condition.MinAge / Application.Speed) ||
                        condition.MaxAge != null && (tradingPair == null || tradingPair.CurrentAge > condition.MaxAge / Application.Speed) ||
                        condition.MinLastBuyAge != null && (tradingPair == null || tradingPair.LastBuyAge < condition.MinLastBuyAge / Application.Speed) ||
                        condition.MaxLastBuyAge != null && (tradingPair == null || tradingPair.LastBuyAge > condition.MaxLastBuyAge / Application.Speed) ||
                        condition.MinMargin != null && (tradingPair == null || tradingPair.CurrentMargin < condition.MinMargin) ||
                        condition.MaxMargin != null && (tradingPair == null || tradingPair.CurrentMargin > condition.MaxMargin) ||
                        condition.MinMarginChange != null && (tradingPair == null || tradingPair.Metadata.LastBuyMargin == null || (tradingPair.CurrentMargin - tradingPair.Metadata.LastBuyMargin) < condition.MinMarginChange) ||
                        condition.MaxMarginChange != null && (tradingPair == null || tradingPair.Metadata.LastBuyMargin == null || (tradingPair.CurrentMargin - tradingPair.Metadata.LastBuyMargin) > condition.MaxMarginChange) ||
                        condition.MinAmount != null && (tradingPair == null || tradingPair.Amount < condition.MinAmount) ||
                        condition.MaxAmount != null && (tradingPair == null || tradingPair.Amount > condition.MaxAmount) ||
                        condition.MinCost != null && (tradingPair == null || tradingPair.CurrentCost < condition.MinCost) ||
                        condition.MaxCost != null && (tradingPair == null || tradingPair.CurrentCost > condition.MaxCost) ||
                        condition.MinDCALevel != null && (tradingPair == null || tradingPair.DCALevel < condition.MinDCALevel) ||
                        condition.MaxDCALevel != null && (tradingPair == null || tradingPair.DCALevel > condition.MaxDCALevel) ||
                        condition.SignalRules != null && (tradingPair == null || tradingPair.Metadata.SignalRule == null || !condition.SignalRules.Contains(tradingPair.Metadata.SignalRule)) ||
                        condition.NotSignalRules != null && (tradingPair == null || tradingPair.Metadata.SignalRule == null || condition.NotSignalRules.Contains(tradingPair.Metadata.SignalRule)))
                    {
                        return(false);
                    }
                }
            }
            return(true);
        }
Example #20
0
        public void Swap(SwapOptions options)
        {
            lock (SyncRoot)
            {
                if (CanSwap(options, out string message))
                {
                    ITradingPair oldTradingPair = Account.GetTradingPair(options.OldPair);

                    var sellOptions = new SellOptions(options.OldPair)
                    {
                        Swap        = true,
                        SwapPair    = options.NewPair,
                        ManualOrder = options.ManualOrder
                    };

                    if (CanSell(sellOptions, out message))
                    {
                        decimal currentMargin       = oldTradingPair.CurrentMargin;
                        decimal additionalCosts     = oldTradingPair.AverageCostPaid - oldTradingPair.CurrentCost + (oldTradingPair.Metadata.AdditionalCosts ?? 0);
                        int     additionalDCALevels = oldTradingPair.DCALevel;

                        IOrderDetails sellOrderDetails = tradingTimedTask.PlaceSellOrder(sellOptions);
                        if (!Account.HasTradingPair(options.OldPair))
                        {
                            var buyOptions = new BuyOptions(options.NewPair)
                            {
                                Swap        = true,
                                ManualOrder = options.ManualOrder,
                                MaxCost     = sellOrderDetails.AverageCost,
                                Metadata    = options.Metadata
                            };
                            buyOptions.Metadata.LastBuyMargin       = currentMargin;
                            buyOptions.Metadata.SwapPair            = options.OldPair;
                            buyOptions.Metadata.AdditionalDCALevels = additionalDCALevels;
                            buyOptions.Metadata.AdditionalCosts     = additionalCosts;
                            IOrderDetails buyOrderDetails = tradingTimedTask.PlaceBuyOrder(buyOptions);

                            var newTradingPair = Account.GetTradingPair(options.NewPair) as TradingPair;
                            if (newTradingPair != null)
                            {
                                if (sellOrderDetails.Fees != 0 && sellOrderDetails.FeesCurrency != null)
                                {
                                    if (sellOrderDetails.FeesCurrency == Config.Market)
                                    {
                                        newTradingPair.Metadata.AdditionalCosts += sellOrderDetails.Fees;
                                    }
                                    else
                                    {
                                        string feesPair = sellOrderDetails.FeesCurrency + Config.Market;
                                        newTradingPair.Metadata.AdditionalCosts += GetCurrentPrice(feesPair) * sellOrderDetails.Fees;
                                    }
                                }
                                loggingService.Info($"Swap {oldTradingPair.FormattedName} for {newTradingPair.FormattedName}. Old margin: {oldTradingPair.CurrentMargin:0.00}, new margin: {newTradingPair.CurrentMargin:0.00}");
                            }
                            else
                            {
                                loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to buy {options.NewPair}");
                                notificationService.Notify($"Unable to swap {options.OldPair} for {options.NewPair}: Failed to buy {options.NewPair}");
                            }
                        }
                        else
                        {
                            loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to sell {options.OldPair}");
                        }
                    }
                    else
                    {
                        loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}: {message}");
                    }
                }
                else
                {
                    loggingService.Info(message);
                }
            }
        }
        public IOrderDetails PlaceSellOrder(SellOptions options)
        {
            IOrderDetails orderDetails = null;

            trailingSells.TryRemove(options.Pair, out SellTrailingInfo sellTrailingInfo);
            trailingBuys.TryRemove(options.Pair, out BuyTrailingInfo buyTrailingInfo);

            if (tradingService.CanSell(options, out string message))
            {
                IPairConfig  pairConfig  = tradingService.GetPairConfig(options.Pair);
                ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair);
                tradingPair.SetCurrentValues(tradingService.GetCurrentPrice(options.Pair), tradingService.GetCurrentSpread(options.Pair));

                SellOrder sellOrder = new SellOrder
                {
                    Type   = pairConfig.SellType,
                    Date   = DateTimeOffset.Now,
                    Pair   = options.Pair,
                    Amount = options.Amount ?? tradingPair.TotalAmount,
                    Price  = tradingPair.CurrentPrice
                };

                if (!tradingService.Config.VirtualTrading)
                {
                    loggingService.Info($"Place sell order for {tradingPair.FormattedName}. Price: {sellOrder.Price:0.00000000}, Amount: {sellOrder.Amount:0.########}, Margin: {tradingPair.CurrentMargin:0.00}");

                    try
                    {
                        lock (tradingService.Account.SyncRoot)
                        {
                            orderDetails = tradingService.PlaceOrder(sellOrder);
                            tradingPair.Metadata.SwapPair = options.SwapPair;
                            orderDetails.SetMetadata(tradingPair.Metadata);
                            ITradeResult tradeResult = tradingService.Account.AddSellOrder(orderDetails);
                            tradeResult.SetSwap(options.Swap);
                            tradingService.Account.Save();
                            tradingService.LogOrder(orderDetails);

                            decimal soldMargin = (tradeResult.Profit / (tradeResult.AverageCost + (tradeResult.Metadata.AdditionalCosts ?? 0)) * 100);
                            string  swapPair   = options.SwapPair != null ? $", Swap Pair: {options.SwapPair}" : "";
                            loggingService.Info("{@Trade}", orderDetails);
                            loggingService.Info("{@Trade}", tradeResult);
                            loggingService.Info($"Sell order result for {tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Filled: {orderDetails.AmountFilled:0.########}, Margin: {soldMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}");
                            notificationService.Notify($"Sold {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Margin: {soldMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}");
                        }
                    }
                    catch (Exception ex)
                    {
                        loggingService.Error($"Unable to place sell order for {options.Pair}", ex);
                        notificationService.Notify($"Unable to sell {options.Pair}: {ex.Message}");
                    }
                }
                else
                {
                    loggingService.Info($"Place virtual sell order for {tradingPair.FormattedName}. Price: {sellOrder.Price:0.00000000}, Amount: {sellOrder.Amount:0.########}");

                    lock (tradingService.Account.SyncRoot)
                    {
                        orderDetails = new OrderDetails
                        {
                            Metadata     = tradingPair.Metadata,
                            OrderId      = DateTime.Now.ToFileTimeUtc().ToString(),
                            Side         = OrderSide.Sell,
                            Result       = OrderResult.Filled,
                            Date         = sellOrder.Date,
                            Pair         = sellOrder.Pair,
                            Amount       = sellOrder.Amount,
                            AmountFilled = sellOrder.Amount,
                            Price        = sellOrder.Price,
                            AveragePrice = sellOrder.Price
                        };
                        tradingPair.Metadata.SwapPair = options.SwapPair;
                        ITradeResult tradeResult = tradingService.Account.AddSellOrder(orderDetails);
                        tradeResult.SetSwap(options.Swap);
                        tradingService.Account.Save();
                        tradingService.LogOrder(orderDetails);

                        decimal soldMargin = (tradeResult.Profit / (tradeResult.AverageCost + (tradeResult.Metadata.AdditionalCosts ?? 0)) * 100);
                        string  swapPair   = options.SwapPair != null ? $", Swap Pair: {options.SwapPair}" : "";
                        loggingService.Info("{@Trade}", orderDetails);
                        loggingService.Info("{@Trade}", tradeResult);
                        loggingService.Info($"Virtual sell order result for {tradingPair.FormattedName}. Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Margin: {tradingPair.CurrentMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}");
                        notificationService.Notify($"Sold {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}");
                    }
                }

                tradingService.ReapplyTradingRules();
            }
            else
            {
                loggingService.Info(message);
            }
            return(orderDetails);
        }
        public IOrderDetails PlaceBuyOrder(BuyOptions options)
        {
            IOrderDetails orderDetails = null;

            trailingBuys.TryRemove(options.Pair, out BuyTrailingInfo buyTrailingInfo);
            trailingSells.TryRemove(options.Pair, out SellTrailingInfo sellTrailingInfo);

            if (tradingService.CanBuy(options, out string message))
            {
                IPairConfig  pairConfig   = tradingService.GetPairConfig(options.Pair);
                ITradingPair tradingPair  = tradingService.Account.GetTradingPair(options.Pair);
                decimal      currentPrice = tradingService.GetCurrentPrice(options.Pair);
                options.Metadata.TradingRules = pairConfig.Rules.ToList();
                if (options.Metadata.LastBuyMargin == null)
                {
                    options.Metadata.LastBuyMargin = tradingPair?.CurrentMargin ?? 0;
                }
                string signalRule = options.Metadata.SignalRule ?? "N/A";

                BuyOrder buyOrder = new BuyOrder
                {
                    Type   = pairConfig.BuyType,
                    Date   = DateTimeOffset.Now,
                    Pair   = options.Pair,
                    Amount = options.Amount ?? (options.MaxCost.Value / currentPrice),
                    Price  = currentPrice
                };

                if (!tradingService.Config.VirtualTrading)
                {
                    loggingService.Info($"Place buy order for {tradingPair?.FormattedName ?? options.Pair}. Price: {buyOrder.Price:0.00000000}, Amount: {buyOrder.Amount:0.########}, Signal Rule: {signalRule}");

                    try
                    {
                        lock (tradingService.Account.SyncRoot)
                        {
                            orderDetails = tradingService.PlaceOrder(buyOrder);
                            orderDetails.SetMetadata(options.Metadata);
                            tradingService.Account.AddBuyOrder(orderDetails);
                            tradingService.Account.Save();
                            tradingService.LogOrder(orderDetails);

                            tradingPair = tradingService.Account.GetTradingPair(options.Pair);
                            loggingService.Info("{@Trade}", orderDetails);
                            loggingService.Info($"Buy order result for {tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Filled: {orderDetails.AmountFilled:0.########}, Cost: {orderDetails.AverageCost:0.00000000}");
                            notificationService.Notify($"Bought {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Cost: {orderDetails.AverageCost:0.00000000}");
                        }
                    }
                    catch (Exception ex)
                    {
                        loggingService.Error($"Unable to place buy order for {options.Pair}", ex);
                        notificationService.Notify($"Unable to buy {options.Pair}: {ex.Message}");
                    }
                }
                else
                {
                    loggingService.Info($"Place virtual buy order for {tradingPair?.FormattedName ?? options.Pair}. Price: {buyOrder.Price:0.00000000}, Amount: {buyOrder.Amount:0.########}, Signal Rule: {signalRule}");

                    lock (tradingService.Account.SyncRoot)
                    {
                        decimal roundedAmount = Math.Round(buyOrder.Amount, 4);
                        orderDetails = new OrderDetails
                        {
                            Metadata     = options.Metadata,
                            OrderId      = DateTime.Now.ToFileTimeUtc().ToString(),
                            Side         = OrderSide.Buy,
                            Result       = OrderResult.Filled,
                            Date         = buyOrder.Date,
                            Pair         = buyOrder.Pair,
                            Amount       = roundedAmount,
                            AmountFilled = roundedAmount,
                            Price        = buyOrder.Price,
                            AveragePrice = buyOrder.Price
                        };
                        tradingService.Account.AddBuyOrder(orderDetails);
                        tradingService.Account.Save();
                        tradingService.LogOrder(orderDetails);

                        tradingPair = tradingService.Account.GetTradingPair(options.Pair);
                        loggingService.Info("{@Trade}", orderDetails);
                        loggingService.Info($"Virtual buy order result for {tradingPair.FormattedName}. Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Cost: {orderDetails.AverageCost:0.00000000}");
                        notificationService.Notify($"Bought {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Cost: {orderDetails.AverageCost:0.00000000}");
                    }
                }

                tradingService.ReapplyTradingRules();
            }
            else
            {
                loggingService.Info(message);
            }
            return(orderDetails);
        }