Test strategy combo

This commit is contained in:
2025-06-15 18:46:41 +07:00
parent 3f34c56968
commit e4f4d078b2
8 changed files with 1024 additions and 157 deletions

View File

@@ -169,7 +169,8 @@ public class BacktestController : BaseController
MaxPositionTimeHours = request.Config.MaxPositionTimeHours, MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit, FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
FlipPosition = request.Config.FlipPosition, FlipPosition = request.Config.FlipPosition,
Name = request.Config.Name ?? $"Backtest-{request.Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}", Name = request.Config.Name ??
$"Backtest-{request.Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}",
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
}; };
@@ -185,8 +186,7 @@ public class BacktestController : BaseController
request.StartDate, request.StartDate,
request.EndDate, request.EndDate,
user, user,
request.Save, request.Save);
null);
break; break;
} }

View File

@@ -8,68 +8,38 @@ namespace Managing.Application.Abstractions.Services
public interface IBacktester public interface IBacktester
{ {
/// <summary> /// <summary>
/// Runs a unified trading bot backtest with the specified configuration and date range. /// Runs a trading bot backtest with the specified configuration and date range.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType. /// Automatically handles different bot types based on config.BotType.
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
/// <param name="startDate">The start date for the backtest</param> /// <param name="startDate">The start date for the backtest</param>
/// <param name="endDate">The end date for the backtest</param> /// <param name="endDate">The end date for the backtest</param>
/// <param name="user">The user running the backtest</param> /// <param name="user">The user running the backtest (optional)</param>
/// <param name="save">Whether to save the backtest results</param> /// <param name="save">Whether to save the backtest results</param>
/// <param name="initialCandles">Optional pre-loaded candles</param>
/// <returns>The backtest results</returns> /// <returns>The backtest results</returns>
Task<Backtest> RunTradingBotBacktest( Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config, TradingBotConfig config,
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
User user = null, User user = null,
bool save = false, bool save = false);
List<Candle>? initialCandles = null);
/// <summary> /// <summary>
/// Runs a unified trading bot backtest with pre-loaded candles. /// Runs a trading bot backtest with pre-loaded candles.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType. /// Automatically handles different bot types based on config.BotType.
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
/// <param name="candles">The candles to use for backtesting</param> /// <param name="candles">The candles to use for backtesting</param>
/// <param name="user">The user running the backtest</param> /// <param name="user">The user running the backtest (optional)</param>
/// <returns>The backtest results</returns> /// <returns>The backtest results</returns>
Task<Backtest> RunTradingBotBacktest( Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config, TradingBotConfig config,
List<Candle> candles, List<Candle> candles,
User user = null); User user = null);
// Legacy methods - maintained for backward compatibility // Additional methods for backtest management
Task<Backtest> RunScalpingBotBacktest(
TradingBotConfig config,
DateTime startDate,
DateTime endDate,
User user = null,
bool save = false,
List<Candle>? initialCandles = null);
Task<Backtest> RunFlippingBotBacktest(
TradingBotConfig config,
DateTime startDate,
DateTime endDate,
User user = null,
bool save = false,
List<Candle>? initialCandles = null);
bool DeleteBacktest(string id); bool DeleteBacktest(string id);
bool DeleteBacktests(); bool DeleteBacktests();
Task<Backtest> RunScalpingBotBacktest(
TradingBotConfig config,
List<Candle> candles,
User user = null);
Task<Backtest> RunFlippingBotBacktest(
TradingBotConfig config,
List<Candle> candles,
User user = null);
// User-specific operations
Task<IEnumerable<Backtest>> GetBacktestsByUser(User user); Task<IEnumerable<Backtest>> GetBacktestsByUser(User user);
Backtest GetBacktestByIdForUser(User user, string id); Backtest GetBacktestByIdForUser(User user, string id);
bool DeleteBacktestByUser(User user, string id); bool DeleteBacktestByUser(User user, string id);

File diff suppressed because it is too large Load Diff

View File

@@ -283,13 +283,12 @@ public class StatisticService : IStatisticService
CloseEarlyWhenProfitable = false CloseEarlyWhenProfitable = false
}; };
var backtest = await _backtester.RunScalpingBotBacktest( var backtest = await _backtester.RunTradingBotBacktest(
config, config,
DateTime.Now.AddDays(-7), DateTime.Now.AddDays(-7),
DateTime.Now, DateTime.Now,
null, null,
false, false);
null);
return backtest.Signals; return backtest.Signals;
} }

View File

@@ -34,6 +34,7 @@ namespace Managing.Application.Abstractions
decimal GetTotalFees(); decimal GetTotalFees();
void LoadStrategies(IEnumerable<IStrategy> strategies); void LoadStrategies(IEnumerable<IStrategy> strategies);
void LoadScenario(string scenarioName); void LoadScenario(string scenarioName);
void LoadScenario(Scenario scenario);
void UpdateStrategiesValues(); void UpdateStrategiesValues();
Task LoadAccount(); Task LoadAccount();
Task<Position> OpenPositionManually(TradeDirection direction); Task<Position> OpenPositionManually(TradeDirection direction);

View File

@@ -56,47 +56,32 @@ namespace Managing.Application.Backtesting
} }
/// <summary> /// <summary>
/// Runs a unified trading bot backtest with the specified configuration and date range. /// Runs a trading bot backtest with the specified configuration and date range.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType. /// Automatically handles different bot types based on config.BotType.
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
/// <param name="startDate">The start date for the backtest</param> /// <param name="startDate">The start date for the backtest</param>
/// <param name="endDate">The end date for the backtest</param> /// <param name="endDate">The end date for the backtest</param>
/// <param name="user">The user running the backtest</param> /// <param name="user">The user running the backtest (optional)</param>
/// <param name="save">Whether to save the backtest results</param> /// <param name="save">Whether to save the backtest results</param>
/// <param name="initialCandles">Optional pre-loaded candles</param>
/// <returns>The backtest results</returns> /// <returns>The backtest results</returns>
public async Task<Backtest> RunTradingBotBacktest( public async Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config, TradingBotConfig config,
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
User user = null, User user = null,
bool save = false, bool save = false)
List<Candle>? initialCandles = null)
{ {
var account = await GetAccountFromConfig(config); var account = await GetAccountFromConfig(config);
var candles = GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
// Set FlipPosition based on BotType var result = await RunBacktestWithCandles(config, candles, user);
config.FlipPosition = config.BotType == BotType.FlippingBot;
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
tradingBot.LoadScenario(config.ScenarioName);
tradingBot.User = user;
await tradingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
var result = GetBacktestingResult(config, tradingBot, candles);
if (user != null)
{
result.User = user;
}
// Set start and end dates // Set start and end dates
result.StartDate = startDate; result.StartDate = startDate;
result.EndDate = endDate; result.EndDate = endDate;
if (save) if (save && user != null)
{ {
_backtestRepository.InsertBacktestForUser(user, result); _backtestRepository.InsertBacktestForUser(user, result);
} }
@@ -105,25 +90,48 @@ namespace Managing.Application.Backtesting
} }
/// <summary> /// <summary>
/// Runs a unified trading bot backtest with pre-loaded candles. /// Runs a trading bot backtest with pre-loaded candles.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType. /// Automatically handles different bot types based on config.BotType.
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
/// <param name="candles">The candles to use for backtesting</param> /// <param name="candles">The candles to use for backtesting</param>
/// <param name="user">The user running the backtest</param> /// <param name="user">The user running the backtest (optional)</param>
/// <returns>The backtest results</returns> /// <returns>The backtest results</returns>
public async Task<Backtest> RunTradingBotBacktest( public async Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config, TradingBotConfig config,
List<Candle> candles, List<Candle> candles,
User user = null) User user = null)
{ {
var account = await GetAccountFromConfig(config); return await RunBacktestWithCandles(config, candles, user);
}
/// <summary>
/// Core backtesting logic - handles the actual backtest execution with pre-loaded candles
/// </summary>
private async Task<Backtest> RunBacktestWithCandles(
TradingBotConfig config,
List<Candle> candles,
User user = null)
{
// Set FlipPosition based on BotType // Set FlipPosition based on BotType
config.FlipPosition = config.BotType == BotType.FlippingBot; config.FlipPosition = config.BotType == BotType.FlippingBot;
var tradingBot = _botFactory.CreateBacktestTradingBot(config); var tradingBot = _botFactory.CreateBacktestTradingBot(config);
// Load scenario - prefer Scenario object over ScenarioName
if (config.Scenario != null)
{
tradingBot.LoadScenario(config.Scenario);
}
else if (!string.IsNullOrEmpty(config.ScenarioName))
{
tradingBot.LoadScenario(config.ScenarioName); tradingBot.LoadScenario(config.ScenarioName);
}
else
{
throw new ArgumentException("Either Scenario object or ScenarioName must be provided in TradingBotConfig");
}
tradingBot.User = user; tradingBot.User = user;
await tradingBot.LoadAccount(); await tradingBot.LoadAccount();
@@ -137,73 +145,25 @@ namespace Managing.Application.Backtesting
return result; return result;
} }
// Legacy methods - maintained for backward compatibility
public async Task<Backtest> RunScalpingBotBacktest(
TradingBotConfig config,
DateTime startDate,
DateTime endDate,
User user = null,
bool save = false,
List<Candle>? initialCandles = null)
{
config.BotType = BotType.ScalpingBot; // Ensure correct type
return await RunTradingBotBacktest(config, startDate, endDate, user, save, initialCandles);
}
public async Task<Backtest> RunFlippingBotBacktest(
TradingBotConfig config,
DateTime startDate,
DateTime endDate,
User user = null,
bool save = false,
List<Candle>? initialCandles = null)
{
config.BotType = BotType.FlippingBot; // Ensure correct type
return await RunTradingBotBacktest(config, startDate, endDate, user, save, initialCandles);
}
public async Task<Backtest> RunScalpingBotBacktest(
TradingBotConfig config,
List<Candle> candles,
User user = null)
{
config.BotType = BotType.ScalpingBot; // Ensure correct type
return await RunTradingBotBacktest(config, candles, user);
}
public async Task<Backtest> RunFlippingBotBacktest(
TradingBotConfig config,
List<Candle> candles,
User user = null)
{
config.BotType = BotType.FlippingBot; // Ensure correct type
return await RunTradingBotBacktest(config, candles, user);
}
private async Task<Account> GetAccountFromConfig(TradingBotConfig config) private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
{ {
// Use the account service to get the actual account
var account = await _accountService.GetAccount(config.AccountName, false, false); var account = await _accountService.GetAccount(config.AccountName, false, false);
if (account != null) if (account != null)
{ {
return account; return account;
} }
// Fallback: create a basic account structure if not found
return new Account return new Account
{ {
Name = config.AccountName, Name = config.AccountName,
Exchange = TradingExchanges.GmxV2 // Default exchange, should be configurable Exchange = TradingExchanges.GmxV2
}; };
} }
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe, private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate) DateTime startDate, DateTime endDate)
{ {
List<Candle> candles; var candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
// Use specific date range
candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
startDate, timeframe, endDate).Result; startDate, timeframe, endDate).Result;
if (candles == null || candles.Count == 0) if (candles == null || candles.Count == 0)
@@ -326,13 +286,10 @@ namespace Managing.Application.Backtesting
return strategiesValues; return strategiesValues;
} }
public bool DeleteBacktest(string id) public bool DeleteBacktest(string id)
{ {
try try
{ {
// Since we no longer have a general DeleteBacktestById method in the repository,
// this should be implemented using DeleteBacktestByIdForUser with null
_backtestRepository.DeleteBacktestByIdForUser(null, id); _backtestRepository.DeleteBacktestByIdForUser(null, id);
return true; return true;
} }
@@ -347,8 +304,6 @@ namespace Managing.Application.Backtesting
{ {
try try
{ {
// Since we no longer have a general DeleteAllBacktests method in the repository,
// this should be implemented using DeleteAllBacktestsForUser with null
_backtestRepository.DeleteAllBacktestsForUser(null); _backtestRepository.DeleteAllBacktestsForUser(null);
return true; return true;
} }
@@ -363,10 +318,8 @@ namespace Managing.Application.Backtesting
{ {
var backtests = _backtestRepository.GetBacktestsByUser(user).ToList(); var backtests = _backtestRepository.GetBacktestsByUser(user).ToList();
// For each backtest, ensure candles are loaded
foreach (var backtest in backtests) foreach (var backtest in backtests)
{ {
// If the backtest has no candles or only a few sample candles, retrieve them
if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10) if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10)
{ {
try try
@@ -386,7 +339,6 @@ namespace Managing.Application.Backtesting
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", backtest.Id); _logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", backtest.Id);
// Continue with the next backtest if there's an error
} }
} }
} }
@@ -396,22 +348,18 @@ namespace Managing.Application.Backtesting
public Backtest GetBacktestByIdForUser(User user, string id) public Backtest GetBacktestByIdForUser(User user, string id)
{ {
// Get the backtest from the repository
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id); var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
if (backtest == null) if (backtest == null)
return null; return null;
// If the backtest has no candles or only a few sample candles, retrieve them
if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10) if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10)
{ {
try try
{ {
// Get the account
var account = new Account var account = new Account
{ Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm }; { Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };
// Use the stored start and end dates to retrieve candles
var candles = _exchangeService.GetCandlesInflux( var candles = _exchangeService.GetCandlesInflux(
account.Exchange, account.Exchange,
backtest.Config.Ticker, backtest.Config.Ticker,
@@ -427,7 +375,6 @@ namespace Managing.Application.Backtesting
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", id); _logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", id);
// Return the backtest without candles if there's an error
} }
} }

View File

@@ -145,6 +145,20 @@ public class TradingBot : Bot, ITradingBot
} }
} }
public void LoadScenario(Scenario scenario)
{
if (scenario == null)
{
Logger.LogWarning("Null scenario provided");
Stop();
}
else
{
Scenario = scenario;
LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario));
}
}
public void LoadStrategies(IEnumerable<IStrategy> strategies) public void LoadStrategies(IEnumerable<IStrategy> strategies)
{ {
foreach (var strategy in strategies) foreach (var strategy in strategies)

View File

@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Domain.Bots; namespace Managing.Domain.Bots;
@@ -9,7 +10,6 @@ public class TradingBotConfig
[Required] public string AccountName { get; set; } [Required] public string AccountName { get; set; }
[Required] public MoneyManagement MoneyManagement { get; set; } [Required] public MoneyManagement MoneyManagement { get; set; }
[Required] public Ticker Ticker { get; set; } [Required] public Ticker Ticker { get; set; }
[Required] public string ScenarioName { get; set; }
[Required] public Timeframe Timeframe { get; set; } [Required] public Timeframe Timeframe { get; set; }
[Required] public bool IsForWatchingOnly { get; set; } [Required] public bool IsForWatchingOnly { get; set; }
[Required] public decimal BotTradingBalance { get; set; } [Required] public decimal BotTradingBalance { get; set; }
@@ -20,6 +20,17 @@ public class TradingBotConfig
[Required] public bool FlipPosition { get; set; } [Required] public bool FlipPosition { get; set; }
[Required] public string Name { get; set; } [Required] public string Name { get; set; }
/// <summary>
/// The scenario object containing all strategies. When provided, this takes precedence over ScenarioName.
/// This allows running backtests without requiring scenarios to be saved in the database.
/// </summary>
public Scenario Scenario { get; set; }
/// <summary>
/// The scenario name to load from database. Only used when Scenario object is not provided.
/// </summary>
public string ScenarioName { get; set; }
/// <summary> /// <summary>
/// Maximum time in hours that a position can remain open before being automatically closed. /// Maximum time in hours that a position can remain open before being automatically closed.
/// If null, time-based position closure is disabled. /// If null, time-based position closure is disabled.