public bool CanBuy(BuyOptions options, out string message) { IPairConfig pairConfig = GetPairConfig(options.Pair); if (!options.ManualOrder && !options.Swap && IsTradingSuspended) { message = $"Cancel buy request for {options.Pair}. Reason: trading suspended"; return(false); } else if (!options.ManualOrder && !options.Swap && !pairConfig.BuyEnabled) { message = $"Cancel buy request for {options.Pair}. Reason: buying not enabled"; return(false); } else if (!options.ManualOrder && Config.ExcludedPairs.Contains(options.Pair)) { message = $"Cancel buy request for {options.Pair}. Reason: exluded pair"; return(false); } else if (!options.ManualOrder && !options.Arbitrage && !options.IgnoreExisting && Account.HasTradingPair(options.Pair)) { message = $"Cancel buy request for {options.Pair}. Reason: pair already exists"; return(false); } else if (!options.ManualOrder && !options.Swap && !options.Arbitrage && pairConfig.MaxPairs != 0 && Account.GetTradingPairs().Count() >= pairConfig.MaxPairs && !Account.HasTradingPair(options.Pair)) { message = $"Cancel buy request for {options.Pair}. Reason: maximum pairs reached"; return(false); } else if (!options.ManualOrder && !options.Swap && !options.IgnoreBalance && pairConfig.BuyMinBalance != 0 && (Account.GetBalance() - options.MaxCost) < pairConfig.BuyMinBalance && Exchange.GetPairMarket(options.Pair) == Config.Market) { message = $"Cancel buy request for {options.Pair}. Reason: minimum balance reached"; return(false); } else if (options.Price != null && options.Price <= 0) { message = $"Cancel buy request for {options.Pair}. Reason: invalid price"; return(false); } else if (options.Amount != null && options.Amount <= 0) { message = $"Cancel buy request for {options.Pair}. Reason: invalid amount"; return(false); } else if (!options.IgnoreBalance && Account.GetBalance() < options.MaxCost && Exchange.GetPairMarket(options.Pair) == Config.Market) { message = $"Cancel buy request for {options.Pair}. Reason: not enough balance"; return(false); } else if (options.Amount == null && options.MaxCost == null || options.Amount != null && options.MaxCost != null) { message = $"Cancel buy request for {options.Pair}. Reason: either max cost or amount needs to be specified (not both)"; } else if (!options.ManualOrder && !options.Swap && !options.Arbitrage && pairConfig.BuySamePairTimeout > 0 && OrderHistory.Any(h => h.Side == OrderSide.Buy && (h.Pair == options.Pair || h.Pair == h.OriginalPair)) && (DateTimeOffset.Now - OrderHistory.Where(h => (h.Pair == options.Pair || h.Pair == h.OriginalPair)).Max(h => h.Date)).TotalSeconds < pairConfig.BuySamePairTimeout) { var elapsedSeconds = (DateTimeOffset.Now - OrderHistory.Where(h => (h.Pair == options.Pair || h.Pair == h.OriginalPair)).Max(h => h.Date)).TotalSeconds; message = $"Cancel buy request for {options.Pair}. Reason: buy same pair timeout (elapsed: {elapsedSeconds:0.#}, timeout: {pairConfig.BuySamePairTimeout:0.#})"; return(false); } message = null; return(true); }