Files
managing-apps/src/Managing.Domain/Backtests/Backtest.cs
2025-07-11 17:56:03 +07:00

148 lines
6.3 KiB
C#

#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<Position> positions,
List<Signal> signals,
List<Candle> candles = null)
{
Config = config;
Positions = positions;
Signals = signals;
Candles = candles != null ? candles : new List<Candle>();
WalletBalances = new List<KeyValuePair<DateTime, decimal>>();
IndicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
// 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<Position> Positions { get; }
[Required] public List<Signal> Signals { get; }
[Required] public List<Candle> 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<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
[Required] public MoneyManagement OptimizedMoneyManagement { get; set; }
[Required] public User User { get; set; }
[Required] public Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
[Required] public double Score { get; set; }
public string RequestId { get; set; }
public object? Metadata { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <param name="accountName">The account name to use for the new bot (can override backtest account)</param>
/// <param name="botName">The name for the new bot</param>
/// <param name="initialTradingBalance">The initial trading balance for the new bot</param>
/// <param name="moneyManagement">Optional money management override (uses backtest's if not provided)</param>
/// <returns>A new TradingBotConfig ready for bot creation</returns>
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
};
}
/// <summary>
/// Creates a copy of the backtest's configuration for a new backtest.
/// Useful for running similar backtests with modified parameters.
/// </summary>
/// <param name="startDate">New start date for the backtest</param>
/// <param name="endDate">New end date for the backtest</param>
/// <param name="balance">New initial balance for the backtest</param>
/// <param name="moneyManagement">Optional money management override</param>
/// <returns>A new TradingBotConfig for backtesting</returns>
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;
}
}