#nullable enable using System.ComponentModel.DataAnnotations; using Exilion.TradingAtomics; using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.MoneyManagements; using Managing.Domain.Strategies; using Managing.Domain.Strategies.Base; using Managing.Domain.Trades; using Managing.Domain.Users; using static Managing.Common.Enums; namespace Managing.Domain.Backtests; public class Backtest { public Backtest( TradingBotConfig config, List positions, List signals, List candles = null) { Config = config; Positions = positions; Signals = signals; Candles = candles != null ? candles : new List(); WalletBalances = new List>(); IndicatorsValues = new Dictionary(); // Initialize start and end dates if candles are provided if (candles != null && candles.Count > 0) { StartDate = candles.Min(c => c.Date); EndDate = candles.Max(c => c.Date); } else { StartDate = DateTime.UtcNow.AddDays(-30); EndDate = DateTime.UtcNow; } } [Required] public string Id { get; set; } [Required] public decimal FinalPnl { get; set; } [Required] public int WinRate { get; set; } [Required] public decimal GrowthPercentage { get; set; } [Required] public decimal HodlPercentage { get; set; } [Required] public TradingBotConfig Config { get; } [Required] public List Positions { get; } [Required] public List Signals { get; } [Required] public List Candles { get; set; } [Required] public DateTime StartDate { get; set; } [Required] public DateTime EndDate { get; set; } [Required] public PerformanceMetrics Statistics { get; set; } [Required] public decimal Fees { get; set; } [Required] public List> WalletBalances { get; set; } [Required] public MoneyManagement OptimizedMoneyManagement { get; set; } [Required] public User User { get; set; } [Required] public Dictionary IndicatorsValues { get; set; } [Required] public double Score { get; set; } public string RequestId { get; set; } public object? Metadata { get; set; } /// /// Creates a new TradingBotConfig based on this backtest's configuration for starting a live bot. /// This method ensures all properties are properly copied, including nullable values. /// /// The account name to use for the new bot (can override backtest account) /// The name for the new bot /// The initial trading balance for the new bot /// Optional money management override (uses backtest's if not provided) /// A new TradingBotConfig ready for bot creation public TradingBotConfig CreateLiveBotConfig( string accountName, string botName, decimal initialTradingBalance, MoneyManagement moneyManagement = null) { return new TradingBotConfig { AccountName = accountName, MoneyManagement = moneyManagement ?? Config.MoneyManagement, Ticker = Config.Ticker, ScenarioName = Config.ScenarioName, Timeframe = Config.Timeframe, IsForWatchingOnly = false, // Always start as active bot BotTradingBalance = initialTradingBalance, IsForBacktest = false, // Always false for live bots CooldownPeriod = Config.CooldownPeriod, MaxLossStreak = Config.MaxLossStreak, MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit, FlipPosition = Config.FlipPosition, Name = botName }; } /// /// Creates a copy of the backtest's configuration for a new backtest. /// Useful for running similar backtests with modified parameters. /// /// New start date for the backtest /// New end date for the backtest /// New initial balance for the backtest /// Optional money management override /// A new TradingBotConfig for backtesting public TradingBotConfig CreateBacktestConfig( DateTime startDate, DateTime endDate, decimal balance, MoneyManagement moneyManagement = null) { return new TradingBotConfig { AccountName = Config.AccountName, MoneyManagement = moneyManagement ?? Config.MoneyManagement, Ticker = Config.Ticker, ScenarioName = Config.ScenarioName, Timeframe = Config.Timeframe, IsForWatchingOnly = Config.IsForWatchingOnly, BotTradingBalance = balance, IsForBacktest = true, CooldownPeriod = Config.CooldownPeriod, MaxLossStreak = Config.MaxLossStreak, MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit, FlipPosition = Config.FlipPosition, Name = $"Backtest-{Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}" }; } public string GetStringReport() { var timeBasedInfo = Config.MaxPositionTimeHours.HasValue ? $" | MaxTime: {Config.MaxPositionTimeHours}h" : " | MaxTime: Disabled"; var flipInfo = Config.FlipPosition ? $" | Flip: {(Config.FlipOnlyWhenInProfit ? "Profit-Only" : "Always")}" : ""; return $"{Config.Ticker} | {Config.Timeframe} | Positions: {Positions.Count} | Winrate: {WinRate}% | " + $"Pnl: {FinalPnl:#.##}$ | %Pnl: {GrowthPercentage:#.##}% | %Hodl: {HodlPercentage:#.##}%" + $" | Cooldown: {Config.CooldownPeriod} | MaxLoss: {Config.MaxLossStreak}" + timeBasedInfo + flipInfo; } }