148 lines
6.3 KiB
C#
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;
|
|
}
|
|
} |