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,
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
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
};
@@ -185,8 +186,7 @@ public class BacktestController : BaseController
request.StartDate,
request.EndDate,
user,
request.Save,
null);
request.Save);
break;
}

View File

@@ -8,68 +8,38 @@ namespace Managing.Application.Abstractions.Services
public interface IBacktester
{
/// <summary>
/// Runs a unified trading bot backtest with the specified configuration and date range.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType.
/// Runs a trading bot backtest with the specified configuration and date range.
/// Automatically handles different bot types based on config.BotType.
/// </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="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="initialCandles">Optional pre-loaded candles</param>
/// <returns>The backtest results</returns>
Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config,
DateTime startDate,
DateTime endDate,
User user = null,
bool save = false,
List<Candle>? initialCandles = null);
bool save = false);
/// <summary>
/// Runs a unified trading bot backtest with pre-loaded candles.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType.
/// Runs a trading bot backtest with pre-loaded candles.
/// Automatically handles different bot types based on config.BotType.
/// </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="user">The user running the backtest</param>
/// <param name="user">The user running the backtest (optional)</param>
/// <returns>The backtest results</returns>
Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config,
List<Candle> candles,
User user = null);
// Legacy methods - maintained for backward compatibility
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);
// Additional methods for backtest management
bool DeleteBacktest(string id);
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);
Backtest GetBacktestByIdForUser(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
};
var backtest = await _backtester.RunScalpingBotBacktest(
var backtest = await _backtester.RunTradingBotBacktest(
config,
DateTime.Now.AddDays(-7),
DateTime.Now,
null,
false,
null);
false);
return backtest.Signals;
}

View File

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

View File

@@ -56,47 +56,32 @@ namespace Managing.Application.Backtesting
}
/// <summary>
/// Runs a unified trading bot backtest with the specified configuration and date range.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType.
/// Runs a trading bot backtest with the specified configuration and date range.
/// Automatically handles different bot types based on config.BotType.
/// </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="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="initialCandles">Optional pre-loaded candles</param>
/// <returns>The backtest results</returns>
public async Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config,
DateTime startDate,
DateTime endDate,
User user = null,
bool save = false,
List<Candle>? initialCandles = null)
bool save = false)
{
var account = await GetAccountFromConfig(config);
var candles = GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
// Set FlipPosition based on BotType
config.FlipPosition = config.BotType == BotType.FlippingBot;
var result = await RunBacktestWithCandles(config, candles, user);
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
result.StartDate = startDate;
result.EndDate = endDate;
if (save)
if (save && user != null)
{
_backtestRepository.InsertBacktestForUser(user, result);
}
@@ -105,25 +90,48 @@ namespace Managing.Application.Backtesting
}
/// <summary>
/// Runs a unified trading bot backtest with pre-loaded candles.
/// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType.
/// Runs a trading bot backtest with pre-loaded candles.
/// Automatically handles different bot types based on config.BotType.
/// </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="user">The user running the backtest</param>
/// <param name="user">The user running the backtest (optional)</param>
/// <returns>The backtest results</returns>
public async Task<Backtest> RunTradingBotBacktest(
TradingBotConfig config,
List<Candle> candles,
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
config.FlipPosition = config.BotType == BotType.FlippingBot;
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
tradingBot.LoadScenario(config.ScenarioName);
// 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);
}
else
{
throw new ArgumentException("Either Scenario object or ScenarioName must be provided in TradingBotConfig");
}
tradingBot.User = user;
await tradingBot.LoadAccount();
@@ -137,73 +145,25 @@ namespace Managing.Application.Backtesting
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)
{
// Use the account service to get the actual account
var account = await _accountService.GetAccount(config.AccountName, false, false);
if (account != null)
{
return account;
}
// Fallback: create a basic account structure if not found
return new Account
{
Name = config.AccountName,
Exchange = TradingExchanges.GmxV2 // Default exchange, should be configurable
Exchange = TradingExchanges.GmxV2
};
}
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate)
{
List<Candle> candles;
// Use specific date range
candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
var candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
startDate, timeframe, endDate).Result;
if (candles == null || candles.Count == 0)
@@ -326,13 +286,10 @@ namespace Managing.Application.Backtesting
return strategiesValues;
}
public bool DeleteBacktest(string id)
{
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);
return true;
}
@@ -347,8 +304,6 @@ namespace Managing.Application.Backtesting
{
try
{
// Since we no longer have a general DeleteAllBacktests method in the repository,
// this should be implemented using DeleteAllBacktestsForUser with null
_backtestRepository.DeleteAllBacktestsForUser(null);
return true;
}
@@ -363,10 +318,8 @@ namespace Managing.Application.Backtesting
{
var backtests = _backtestRepository.GetBacktestsByUser(user).ToList();
// For each backtest, ensure candles are loaded
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)
{
try
@@ -386,7 +339,6 @@ namespace Managing.Application.Backtesting
catch (Exception ex)
{
_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)
{
// Get the backtest from the repository
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
if (backtest == 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)
{
try
{
// Get the account
var account = new Account
{ Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };
// Use the stored start and end dates to retrieve candles
var candles = _exchangeService.GetCandlesInflux(
account.Exchange,
backtest.Config.Ticker,
@@ -427,7 +375,6 @@ namespace Managing.Application.Backtesting
catch (Exception ex)
{
_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)
{
foreach (var strategy in strategies)

View File

@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using static Managing.Common.Enums;
namespace Managing.Domain.Bots;
@@ -9,7 +10,6 @@ public class TradingBotConfig
[Required] public string AccountName { get; set; }
[Required] public MoneyManagement MoneyManagement { get; set; }
[Required] public Ticker Ticker { get; set; }
[Required] public string ScenarioName { get; set; }
[Required] public Timeframe Timeframe { get; set; }
[Required] public bool IsForWatchingOnly { get; set; }
[Required] public decimal BotTradingBalance { get; set; }
@@ -20,6 +20,17 @@ public class TradingBotConfig
[Required] public bool FlipPosition { 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>
/// Maximum time in hours that a position can remain open before being automatically closed.
/// If null, time-based position closure is disabled.