449 lines
20 KiB
C#
449 lines
20 KiB
C#
using System.Collections;
|
|
using System.Diagnostics;
|
|
using Managing.Application.Abstractions;
|
|
using Managing.Application.Abstractions.Repositories;
|
|
using Managing.Application.Abstractions.Services;
|
|
using Managing.Application.Backtesting;
|
|
using Managing.Application.Bots.Base;
|
|
using Managing.Core;
|
|
using Managing.Domain.Candles;
|
|
using Managing.Domain.MoneyManagements;
|
|
using Managing.Domain.Scenarios;
|
|
using Moq;
|
|
using Newtonsoft.Json;
|
|
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<double> _elapsedTimes { get; set; }
|
|
|
|
public BotsTests() : base()
|
|
{
|
|
var backtestRepository = new Mock<IBacktestRepository>().Object;
|
|
var discordService = new Mock<IMessengerService>().Object;
|
|
var scenarioService = new Mock<IScenarioService>().Object;
|
|
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
|
|
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
|
|
var botService = new Mock<IBotService>().Object;
|
|
_botFactory = new BotFactory(
|
|
_exchangeService,
|
|
tradingBotLogger,
|
|
discordService,
|
|
_accountService.Object,
|
|
_tradingService.Object,
|
|
botService);
|
|
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
|
|
scenarioService);
|
|
_elapsedTimes = new List<double>();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -10)]
|
|
public async Task SwingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe,
|
|
int days)
|
|
{
|
|
// Arrange
|
|
var scenario = new Scenario("FlippingScenario");
|
|
var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, "RsiDiv", period: 14);
|
|
scenario.AddStrategy(strategy);
|
|
var localCandles =
|
|
FileHelpers.ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json");
|
|
|
|
// Act
|
|
var backtestResult = await _backtester.RunFlippingBotBacktest(_account, MoneyManagement, ticker, scenario,
|
|
timeframe, 1000, new DateTime().AddDays(-3), DateTime.UtcNow,
|
|
initialCandles: localCandles.TakeLast(500).ToList());
|
|
|
|
var json = JsonConvert.SerializeObject(backtestResult, Formatting.None);
|
|
File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json);
|
|
// 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 async Task 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, "RsiDiv", period: 5);
|
|
scenario.AddStrategy(strategy);
|
|
|
|
// Act
|
|
var backtestResult = await _backtester.RunScalpingBotBacktest(_account, MoneyManagement, ticker, scenario,
|
|
timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
|
|
//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 async Task 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, "RsiDiv", fastPeriods: 12,
|
|
slowPeriods: 26, signalPeriods: 9);
|
|
scenario.AddStrategy(strategy);
|
|
|
|
var moneyManagement = new MoneyManagement()
|
|
{
|
|
Leverage = 1,
|
|
Timeframe = timeframe,
|
|
StopLoss = 0.01m,
|
|
TakeProfit = 0.02m
|
|
};
|
|
|
|
// Act
|
|
var backtestResult = await _backtester.RunScalpingBotBacktest(_account, moneyManagement, ticker, scenario,
|
|
timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
|
|
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.Stc, 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<Tuple<string, int, decimal, decimal, decimal, decimal>>();
|
|
var errors = new List<string>();
|
|
var options = new ParallelOptions()
|
|
{
|
|
MaxDegreeOfParallelism = 4
|
|
};
|
|
|
|
var periodRange = new List<int>() { 2, 7 };
|
|
var stopLossRange = new List<decimal>() { 0.005m, 0.05m, 0.005m };
|
|
var takeProfitRange = new List<decimal>() { 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, true).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, "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()
|
|
{
|
|
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, null).Result,
|
|
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement,
|
|
scenario, timeframe, candles, 1000, null).Result,
|
|
_ => throw new NotImplementedException(),
|
|
};
|
|
timer.Stop();
|
|
|
|
if (backtestResult.FinalPnl > 0
|
|
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30
|
|
&& backtestResult.Statistics.MaxDrawdown < 3)
|
|
{
|
|
var currentResult = new Tuple<string, int, decimal, decimal, decimal, decimal>(
|
|
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<Tuple<string, decimal, decimal, decimal, decimal>>();
|
|
var errors = new List<string>();
|
|
var options = new ParallelOptions()
|
|
{
|
|
MaxDegreeOfParallelism = 4
|
|
};
|
|
|
|
var stopLossRange = new List<decimal>() { 0.005m, 0.05m, 0.005m };
|
|
var takeProfitRange = new List<decimal>() { 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, true).Result;
|
|
|
|
if (candles == null || candles.Count == 0)
|
|
return;
|
|
|
|
var scenario = new Scenario("ScalpingScenario");
|
|
var strategy = ScenarioHelpers.BuildStrategy(strategyType, "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()
|
|
{
|
|
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, null).Result,
|
|
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement,
|
|
scenario, timeframe, candles, 1000, null).Result,
|
|
_ => throw new NotImplementedException(),
|
|
};
|
|
|
|
if (backtestResult.FinalPnl > 0
|
|
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30
|
|
&& backtestResult.Statistics.MaxDrawdown < 3)
|
|
{
|
|
var currentResult = new Tuple<string, decimal, decimal, decimal, decimal>(
|
|
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<int> periodRange, List<decimal> stopLossRange,
|
|
List<decimal> 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<decimal> stopLossRange, List<decimal> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |