using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Application.Backtesting; using Managing.Application.Bots.Base; using Managing.Domain.MoneyManagements; using Managing.Domain.Scenarios; using Moq; using System.Collections; using System.Diagnostics; using Xunit; using static Managing.Common.Enums; namespace Managing.Application.Tests { public class BotsTests : BaseTests { private readonly IBotFactory _botFactory; private readonly IBacktester _backtester; private readonly string _reportPath = "D:\\BacktestingReports\\backtesting.csv"; private string _analysePath = "D:\\BacktestingReports\\analyse"; private readonly string _errorsPath = "D:\\BacktestingReports\\errorsAnalyse.csv"; private readonly string _s = "|"; private List _elapsedTimes { get; set; } public BotsTests() : base () { var backtestRepository = new Mock().Object; var discordService = new Mock().Object; var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger(); var backtestLogger = TradingBaseTests.CreateBacktesterLogger(); _botFactory = new BotFactory( _exchangeService, tradingBotLogger, _moneyManagementService.Object, discordService, _accountService.Object, _tradingService.Object); _backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger); _elapsedTimes = new List(); } [Theory] [InlineData(Ticker.BTC, Timeframe.OneDay, -100)] public void SwingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days) { // Arrange var scenario = new Scenario("ScalpingScenario"); var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 14); scenario.AddStrategy(strategy); // Act var backtestResult = _backtester.RunFlippingBotBacktest(Account, MoneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days*2), 1000); WriteCsvReport(backtestResult.GetStringReport()); // Assert Assert.True(backtestResult.FinalPnl > 0); Assert.True(backtestResult.WinRate >= 30); Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage); } [Theory] //[InlineData(Enums.Exchanges.Binance, "ADAUSDT", Timeframe.ThirtyMinutes, -5)] //[InlineData(Enums.Exchanges.Binance, "ADAUSDT", Timeframe.FifteenMinutes, -5)] //[InlineData(Enums.Exchanges.Binance, "SOLUSDT", Timeframe.ThirtyMinutes, -4)] //[InlineData(Enums.Exchanges.Binance, "SOLUSDT", Timeframe.FifteenMinutes, -4)] //[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.ThirtyMinutes, -4)] //[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.FifteenMinutes, -4)] [InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -14)] public void ScalpingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days) { // Arrange var scenario = new Scenario("ScalpingScenario"); var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 5); scenario.AddStrategy(strategy); // Act var backtestResult = _backtester.RunScalpingBotBacktest(Account, MoneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days), 1000); //WriteCsvReport(backtestResult.GetStringReport()); // Assert Assert.True(backtestResult.FinalPnl > 0); Assert.True(backtestResult.WinRate >= 30); Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage); } [Theory] [InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -8)] public void MacdCross_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days) { // Arrange var scenario = new Scenario("ScalpingScenario"); var strategy = ScenarioHelpers.BuildStrategy(StrategyType.MacdCross, timeframe, "RsiDiv", fastPeriods: 12, slowPeriods: 26, signalPeriods: 9); scenario.AddStrategy(strategy); var moneyManagement = new MoneyManagement() { BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = timeframe, StopLoss = 0.01m, TakeProfit = 0.02m }; // Act var backtestResult = _backtester.RunScalpingBotBacktest(Account, moneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days), 1000); WriteCsvReport(backtestResult.GetStringReport()); // Assert Assert.True(backtestResult.FinalPnl > 0); Assert.True(backtestResult.WinRate >= 30); Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage); } [Theory] [InlineData(Timeframe.FifteenMinutes, -6, StrategyType.RsiDivergenceConfirm, BotType.ScalpingBot)] //[InlineData(Timeframe.FifteenMinutes, -6, Enums.StrategyType.RsiDivergenceConfirm, Enums.BotType.FlippingBot)] public void GetBestPeriodRsiForDivergenceFlippingBot(Timeframe timeframe, int days, StrategyType strategyType, BotType botType) { var result = new List>(); var errors = new List(); var options = new ParallelOptions() { MaxDegreeOfParallelism = 4 }; var periodRange = new List() { 2, 7}; var stopLossRange = new List() { 0.005m, 0.05m, 0.005m }; var takeProfitRange = new List() { 0.01m, 0.1m, 0.02m }; var fileIdentifier = $"{strategyType}-{timeframe}"; var completedTest = 0; var totalTests = GetTotalTrades(periodRange, stopLossRange, takeProfitRange) * Enum.GetNames(typeof(Ticker)).Length; CleanAnalyseFile(fileIdentifier); UpdateProgression(totalTests, completedTest); Parallel.ForEach((Ticker[])Enum.GetValues(typeof(Ticker)), options, ticker => { var candles = _exchangeService.GetCandles(Account, ticker, DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result; if (candles == null || candles.Count == 0) return; Parallel.For(periodRange[0], periodRange[1], options, i => { var scenario = new Scenario("ScalpingScenario"); var strategy = ScenarioHelpers.BuildStrategy(strategyType, timeframe, "RsiDiv", period: i); scenario.AddStrategy(strategy); // -0.5 to -5 for (decimal s = stopLossRange[0]; s < stopLossRange[1]; s += stopLossRange[2]) { // +1% to +10% in 1% for(decimal t = takeProfitRange[0]; t < takeProfitRange[1]; t += takeProfitRange[2]) { var moneyManagement = new MoneyManagement() { BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = timeframe, StopLoss = s, TakeProfit = t }; try { var timer = new Stopwatch(); timer.Start(); var backtestResult = botType switch { BotType.SimpleBot => throw new NotImplementedException(), BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), BotType.FlippingBot => _backtester.RunFlippingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), _ => throw new NotImplementedException(), }; timer.Stop(); if (backtestResult.FinalPnl > 0 && (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30 && backtestResult.Statistics.MaxDrawdown < 3) { var currentResult = new Tuple(ticker.ToString(), i, backtestResult.FinalPnl, s, t, backtestResult.GrowthPercentage - backtestResult.HodlPercentage); result.Add(currentResult); } completedTest++; UpdateProgression(totalTests, completedTest, timer.Elapsed.TotalSeconds); } catch (Exception ex) { completedTest++; errors.Add($"{ticker}{_s}{i}{_s}{s}{_s}{t}{_s}{ex.Message}"); } } } }); }); foreach (var r in result) { WriteCsvAnalyse($"{r.Item1}{_s}{r.Item2}{_s}{r.Item3.ToString("0.000")}{_s}{r.Item4 * 100}{_s}{r.Item5 * 100}{_s}{r.Item6}"); } foreach (var e in errors) { WriteCsvErrors(e); } var bestResult = result.OrderByDescending(b => b.Item3).FirstOrDefault(); WriteCsvAnalyse($"Best result : {bestResult.Item1} - Rsi Period : {bestResult.Item2} - {bestResult.Item3} - SL : {bestResult.Item4}% - TP : {bestResult.Item5}%"); Assert.True(result.Any()); } [Theory] [InlineData(Timeframe.OneHour, -30, StrategyType.MacdCross, BotType.FlippingBot)] [InlineData(Timeframe.OneHour, -30, StrategyType.MacdCross, BotType.ScalpingBot)] public void GetBestMMForMacdFlippingBot(Timeframe timeframe, int days, StrategyType strategyType, BotType botType) { var result = new List>(); var errors = new List(); var options = new ParallelOptions() { MaxDegreeOfParallelism = 4 }; var stopLossRange = new List() { 0.005m, 0.05m, 0.005m }; var takeProfitRange = new List() { 0.01m, 0.1m, 0.02m }; var fileIdentifier = $"{strategyType}-{timeframe}-{botType}"; var completedTest = 0; var totalTests = GetTotalTradeForStopLossTakeProfit(stopLossRange, takeProfitRange) * Enum.GetNames(typeof(Ticker)).Length; CleanAnalyseFile(fileIdentifier); UpdateProgression(totalTests, completedTest); Parallel.ForEach((Ticker[])Enum.GetValues(typeof(Ticker)), options, ticker => { var candles = _exchangeService.GetCandles(Account, ticker, DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result; if (candles == null || candles.Count == 0) return; var scenario = new Scenario("ScalpingScenario"); var strategy = ScenarioHelpers.BuildStrategy(strategyType, timeframe, "RsiDiv", fastPeriods: 12, slowPeriods: 26, signalPeriods: 9); scenario.AddStrategy(strategy); // -0.5 to -5 for (decimal s = stopLossRange[0]; s < stopLossRange[1]; s += stopLossRange[2]) { // +1% to +10% in 1% for (decimal t = takeProfitRange[0]; t < takeProfitRange[1]; t += takeProfitRange[2]) { var timer = new Stopwatch(); timer.Start(); try { var moneyManagement = new MoneyManagement() { BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = timeframe, StopLoss = s, TakeProfit = t }; var backtestResult = botType switch { BotType.SimpleBot => throw new NotImplementedException(), BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), BotType.FlippingBot => _backtester.RunFlippingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), _ => throw new NotImplementedException(), }; if (backtestResult.FinalPnl > 0 && (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30 && backtestResult.Statistics.MaxDrawdown < 3) { var currentResult = new Tuple(ticker.ToString(), backtestResult.FinalPnl, s, t, backtestResult.GrowthPercentage - backtestResult.HodlPercentage); result.Add(currentResult); } completedTest++; UpdateProgression(totalTests, completedTest, timer.Elapsed.TotalSeconds); } catch (Exception ex) { completedTest++; errors.Add($"{ticker}{_s}{s}{_s}{t}{_s}{ex.Message}"); } timer.Stop(); } } }); foreach (var r in result) { WriteCsvAnalyse($"{r.Item1}{_s}{r.Item2.ToString("0.000")}{_s}{r.Item3 * 100}{_s}{r.Item4 * 100}{_s}{r.Item5}"); } foreach (var e in errors) { WriteCsvErrors(e); } var bestResult = result.OrderByDescending(b => b.Item3).FirstOrDefault(); WriteCsvAnalyse($"Best result : {bestResult.Item1} - Rsi Period : {bestResult.Item2} - {bestResult.Item3} - SL : {bestResult.Item4}% - TP : {bestResult.Item5}%"); Assert.True(result.Any()); } private void WriteCsvReport(string line) { File.AppendAllLines(_reportPath, new[] { line }); } private void WriteCsvAnalyse(string line, string fileIdentifier = null) { if (!string.IsNullOrEmpty(fileIdentifier)) _analysePath += $"-{fileIdentifier}-{DateTime.Now.ToString("dd-MM-HH-mm")}.csv"; File.AppendAllLines(_analysePath , new[] { line }); } private void WriteCsvErrors(string line) { File.AppendAllLines(_errorsPath, new[] { line }); } private void CleanAnalyseFile(string fileIdentifier) { WriteCsvAnalyse("", fileIdentifier); } private decimal GetTotalTrades(List periodRange, List stopLossRange, List takeProfitRange) { var stopLossRangeTotalTest = stopLossRange[1] / stopLossRange[2]; var takeProfitRangeTotalTest = takeProfitRange[1] / takeProfitRange[2]; var totalTrades = GetTotalTradeForStopLossTakeProfit(stopLossRange, takeProfitRange) * stopLossRangeTotalTest * takeProfitRangeTotalTest; return totalTrades; } private decimal GetTotalTradeForStopLossTakeProfit(List stopLossRange, List takeProfitRange) { var stopLossRangeTotalTest = stopLossRange[1] / stopLossRange[2]; var takeProfitRangeTotalTest = takeProfitRange[1] / takeProfitRange[2]; var totalTrades = stopLossRangeTotalTest * takeProfitRangeTotalTest; return totalTrades; } private void UpdateProgression(decimal totalTest, int completedTest, double? elapsed = null) { var timeRemaining = ""; if (elapsed.HasValue && completedTest > 0) { //_elapsedTimes.Add((elapsed.Value / completedTest) * ((double)totalTest - completedTest)); _elapsedTimes.Add(elapsed.Value); //var t = (_elapsedTimes.Average() / completedTest) * ((double)totalTest - completedTest); var t = (_elapsedTimes.Average() * (double)(totalTest - completedTest)); timeRemaining = $" Remaining time: {t} seconds - Estimated end date: {DateTime.Now.AddSeconds(t)}"; } ModifyFirstRow(_analysePath, $"{completedTest}/{totalTest}{timeRemaining}"); } private void ModifyFirstRow(string filepath, string newValue) { ArrayList rows = new ArrayList(); using (StreamReader reader = new StreamReader(filepath)) { string row = null; while ((row = reader.ReadLine()) != null) { rows.Add(row); } } // Determ if the file even contains rows. if (rows.Count > 0) { // Replace the first row. rows[0] = newValue; } else { // Add the new value because there // where no rows found in the file. rows.Add(newValue); } // Write the modified content to the file. using (StreamWriter writer = new StreamWriter(filepath, false)) { foreach (String row in rows) { writer.WriteLine(row); } } } } }