diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index 83a32364..e11d45d1 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -538,7 +538,7 @@ public class BacktestController : BaseController if (request.Config.Scenario != null) { // Convert ScenarioRequest to Scenario domain object - scenario = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod) + scenario = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LookbackPeriod) { User = user }; diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index 607ea2c9..444bcee3 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -1,4 +1,4 @@ -using Managing.Api.Models.Requests; +using Managing.Api.Models.Requests; using Managing.Api.Models.Responses; using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; @@ -430,7 +430,7 @@ public class BotController : BaseController /// Filter by ticker (partial match, case-insensitive). If null, no ticker filtering is applied. /// Filter by agent name (partial match, case-insensitive). If null, no agent name filtering is applied. /// Sort field. Valid values: "Name", "Ticker", "Status", "CreateDate", "StartupTime", "Pnl", "WinRate", "AgentName". Default is "CreateDate". - /// Sort direction. Default is "Desc". + /// Sort direction. Default is Desc. /// A paginated response containing trading bots [HttpGet] [Route("Paginated")] @@ -442,7 +442,7 @@ public class BotController : BaseController string? ticker = null, string? agentName = null, BotSortableColumn sortBy = BotSortableColumn.CreateDate, - string sortDirection = "Desc") + SortDirection sortDirection = SortDirection.Desc) { try { @@ -770,7 +770,7 @@ public class BotController : BaseController if (request.Config.Scenario != null) { // Convert ScenarioRequest to Scenario domain object - scenarioForUpdate = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod) + scenarioForUpdate = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LookbackPeriod) { User = user }; @@ -931,7 +931,7 @@ public class BotController : BaseController if (request.Config.Scenario != null) { // Convert ScenarioRequest to Scenario domain object - scenario = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod) + scenario = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LookbackPeriod) { User = user }; diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index 4da9429f..8b59bfe7 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -1,4 +1,4 @@ -using Managing.Api.Models.Requests; +using Managing.Api.Models.Requests; using Managing.Api.Models.Responses; using Managing.Application.Abstractions; using Managing.Application.Abstractions.Grains; @@ -561,7 +561,7 @@ public class DataController : ControllerBase // Use caching for position data in UI context (not critical trading operations) var cacheKey = $"positions_{strategy.Identifier}"; var cachedPositions = _cacheService.GetValue>(cacheKey); - + List positions; if (cachedPositions != null) { @@ -571,7 +571,7 @@ public class DataController : ControllerBase { // Fetch positions associated with this bot using the provided trading service positions = (await tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier)).ToList(); - + // Cache positions for 2 minutes for UI display purposes _cacheService.SaveValue(cacheKey, positions, TimeSpan.FromMinutes(2)); } @@ -619,9 +619,9 @@ public class DataController : ControllerBase NetPnL = strategy.NetPnL, ROIPercentage = strategy.Roi, Runtime = strategy.Status == BotStatus.Running ? strategy.LastStartTime : null, - LastStartTime = strategy.LastStartTime, - LastStopTime = strategy.LastStopTime, - AccumulatedRunTimeSeconds = strategy.AccumulatedRunTimeSeconds, + LastStartTime = strategy.LastStartTime, + LastStopTime = strategy.LastStopTime, + AccumulatedRunTimeSeconds = strategy.AccumulatedRunTimeSeconds, TotalRuntimeSeconds = strategy.GetTotalRuntimeSeconds(), WinRate = winRate, TotalVolumeTraded = totalVolume, @@ -715,7 +715,9 @@ public class DataController : ControllerBase var showOnlyProfitable = _configuration.GetValue("showOnlyProfitable", false); // Get paginated results from database - var command = new GetPaginatedAgentSummariesCommand(page, pageSize, sortBy, sortOrder, agentNamesList, showOnlyProfitable); + var command = + new GetPaginatedAgentSummariesCommand(page, pageSize, sortBy, sortOrder, agentNamesList, + showOnlyProfitable); var result = await _mediator.Send(command); var agentSummaries = result.Results; var totalCount = result.TotalCount; @@ -819,7 +821,7 @@ public class DataController : ControllerBase /// A domain Scenario object. private Scenario MapScenarioRequestToScenario(ScenarioRequest scenarioRequest) { - var scenario = new Scenario(scenarioRequest.Name, scenarioRequest.LoopbackPeriod); + var scenario = new Scenario(scenarioRequest.Name, scenarioRequest.LookbackPeriod); foreach (var indicatorRequest in scenarioRequest.Indicators) { @@ -907,7 +909,7 @@ public class DataController : ControllerBase /// Filter by ticker (partial match, case-insensitive) /// Filter by agent name (partial match, case-insensitive) /// Sort field (defaults to CreateDate) - /// Sort direction - "Asc" or "Desc" (defaults to "Desc") + /// Sort direction - Asc or Desc (defaults to Desc) /// A paginated list of strategies excluding Saved status bots [HttpGet("GetStrategiesPaginated")] public async Task>> GetStrategiesPaginated( @@ -917,7 +919,7 @@ public class DataController : ControllerBase string? ticker = null, string? agentName = null, BotSortableColumn sortBy = BotSortableColumn.CreateDate, - string sortDirection = "Desc") + SortDirection sortDirection = SortDirection.Desc) { // Validate pagination parameters if (pageNumber < 1) @@ -930,11 +932,6 @@ public class DataController : ControllerBase return BadRequest("Page size must be between 1 and 100"); } - // Validate sort direction - if (sortDirection != "Asc" && sortDirection != "Desc") - { - return BadRequest("Sort direction must be 'Asc' or 'Desc'"); - } try { diff --git a/src/Managing.Api/Controllers/ScenarioController.cs b/src/Managing.Api/Controllers/ScenarioController.cs index 7d26529c..b0a968b1 100644 --- a/src/Managing.Api/Controllers/ScenarioController.cs +++ b/src/Managing.Api/Controllers/ScenarioController.cs @@ -191,7 +191,7 @@ public class ScenarioController : BaseController return new ScenarioViewModel { Name = scenario.Name, - LoopbackPeriod = scenario.LoopbackPeriod, + LoopbackPeriod = scenario.LookbackPeriod, UserName = scenario.User?.Name, Indicators = scenario.Indicators?.Select(MapToIndicatorViewModel).ToList() ?? new List() }; diff --git a/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs b/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs index 05c83de3..5ecebbe6 100644 --- a/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs +++ b/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs @@ -49,7 +49,7 @@ public class GetBotsPaginatedRequest public BotSortableColumn SortBy { get; set; } = BotSortableColumn.CreateDate; /// - /// Sort direction. Default is "Desc" (descending). + /// Sort direction. Default is Desc (descending). /// - public string SortDirection { get; set; } = "Desc"; + public SortDirection SortDirection { get; set; } = SortDirection.Desc; } \ No newline at end of file diff --git a/src/Managing.Api/Models/Responses/UserStrategyDetailsViewModel.cs b/src/Managing.Api/Models/Responses/UserStrategyDetailsViewModel.cs index 3a1ca986..37a7c669 100644 --- a/src/Managing.Api/Models/Responses/UserStrategyDetailsViewModel.cs +++ b/src/Managing.Api/Models/Responses/UserStrategyDetailsViewModel.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using Managing.Common; namespace Managing.Api.Models.Responses @@ -10,41 +11,49 @@ namespace Managing.Api.Models.Responses /// /// Name of the deployed strategy /// + [Required] public string Name { get; set; } /// /// Current state of the strategy (RUNNING, STOPPED, UNUSED) /// + [Required] public Enums.BotStatus State { get; set; } /// /// Total profit or loss generated by the strategy in USD (gross, before fees) /// + [Required] public decimal PnL { get; set; } /// /// Net profit or loss generated by the strategy in USD (after fees) /// + [Required] public decimal NetPnL { get; set; } /// /// Return on investment percentage /// + [Required] public decimal ROIPercentage { get; set; } /// /// Date and time when the strategy was started (only present when running, for live ticker) /// + [Required] public DateTime? Runtime { get; set; } /// /// Total accumulated runtime in seconds (including current session if running) /// + [Required] public long TotalRuntimeSeconds { get; set; } /// /// Time when the current or last session started /// + [Required] public DateTime? LastStartTime { get; set; } /// @@ -55,42 +64,49 @@ namespace Managing.Api.Models.Responses /// /// Total accumulated runtime across all past sessions (seconds) /// + [Required] public long AccumulatedRunTimeSeconds { get; set; } /// /// Average percentage of successful trades /// + [Required] public int WinRate { get; set; } /// /// Total trading volume for all trades /// + [Required] public decimal TotalVolumeTraded { get; set; } /// /// Trading volume in the last 24 hours /// + [Required] public decimal VolumeLast24H { get; set; } /// /// Number of winning trades /// + [Required] public int Wins { get; set; } /// /// Number of losing trades /// + [Required] public int Losses { get; set; } /// /// Dictionary of all positions executed by this strategy, keyed by position identifier /// + [Required] public List Positions { get; set; } = new List(); - public Guid Identifier { get; set; } + [Required] public Guid Identifier { get; set; } public Dictionary WalletBalances { get; set; } = new Dictionary(); - public Enums.Ticker Ticker { get; set; } + [Required] public Enums.Ticker Ticker { get; set; } /// /// The agent name of the master bot's owner (for copy trading bots) diff --git a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs index 54c31134..100c7ab6 100644 --- a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs @@ -37,7 +37,7 @@ public interface IBotRepository string? ticker = null, string? agentName = null, BotSortableColumn sortBy = BotSortableColumn.CreateDate, - string sortDirection = "Desc", + SortDirection sortDirection = SortDirection.Desc, bool showOnlyProfitable = false); /// diff --git a/src/Managing.Application.Tests/BacktestTests.cs b/src/Managing.Application.Tests/BacktestTests.cs index 49806621..4055e1cd 100644 --- a/src/Managing.Application.Tests/BacktestTests.cs +++ b/src/Managing.Application.Tests/BacktestTests.cs @@ -158,7 +158,7 @@ public class BacktestTests : BaseTests var scenario = new Scenario("ETH_BacktestScenario"); var rsiDivIndicator = ScenarioHelpers.BuildIndicator(IndicatorType.RsiDivergence, "RsiDiv", period: 14); scenario.Indicators = new List { (IndicatorBase)rsiDivIndicator }; - scenario.LoopbackPeriod = 15; + scenario.LookbackPeriod = 15; var config = new TradingBotConfig { diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index 92999b2f..f0d211a2 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -47,7 +47,7 @@ public interface IBotService /// Filter by ticker (partial match, case-insensitive) /// Filter by agent name (partial match, case-insensitive) /// Sort field - /// Sort direction ("Asc" or "Desc") + /// Sort direction /// Whether to show only profitable bots (ROI > 0) /// Tuple containing the bots for the current page and total count Task<(IEnumerable Bots, int TotalCount)> GetBotsPaginatedAsync( @@ -58,7 +58,7 @@ public interface IBotService string? ticker = null, string? agentName = null, BotSortableColumn sortBy = BotSortableColumn.CreateDate, - string sortDirection = "Desc", + SortDirection sortDirection = SortDirection.Desc, bool showOnlyProfitable = false); /// diff --git a/src/Managing.Application/Abstractions/IScenarioService.cs b/src/Managing.Application/Abstractions/IScenarioService.cs index f4a78792..e4027162 100644 --- a/src/Managing.Application/Abstractions/IScenarioService.cs +++ b/src/Managing.Application/Abstractions/IScenarioService.cs @@ -7,7 +7,7 @@ namespace Managing.Application.Abstractions { public interface IScenarioService { - Task CreateScenario(string name, List strategies, int? loopbackPeriod = 1); + Task CreateScenario(string name, List strategies, int loopbackPeriod = 1); Task> GetIndicatorsAsync(); Task UpdateScenario(string name, List strategies, int? loopbackPeriod); diff --git a/src/Managing.Application/Backtests/BacktestJobService.cs b/src/Managing.Application/Backtests/BacktestJobService.cs index bf40f15e..40e33070 100644 --- a/src/Managing.Application/Backtests/BacktestJobService.cs +++ b/src/Managing.Application/Backtests/BacktestJobService.cs @@ -151,7 +151,7 @@ public class JobService if (backtestRequest.Config.Scenario != null) { var sReq = backtestRequest.Config.Scenario; - scenario = new LightScenario(sReq.Name, sReq.LoopbackPeriod) + scenario = new LightScenario(sReq.Name, sReq.LookbackPeriod) { Indicators = sReq.Indicators?.Select(ind => new LightIndicator(ind.Name, ind.Type) { diff --git a/src/Managing.Application/Bots/BacktestFuturesBot.cs b/src/Managing.Application/Bots/BacktestFuturesBot.cs index f6a6d5cb..c8ffc767 100644 --- a/src/Managing.Application/Bots/BacktestFuturesBot.cs +++ b/src/Managing.Application/Bots/BacktestFuturesBot.cs @@ -176,7 +176,7 @@ public class BacktestFuturesBot : TradingBotBase, ITradingBot throw new ArgumentNullException(nameof(Config.Scenario), "Config.Scenario cannot be null"); // Use TradingBox.GetSignal for backtest with pre-calculated indicators - var backtestSignal = TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LoopbackPeriod, + var backtestSignal = TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LookbackPeriod, preCalculatedIndicatorValues); if (backtestSignal == null) return; diff --git a/src/Managing.Application/Bots/BacktestSpotBot.cs b/src/Managing.Application/Bots/BacktestSpotBot.cs index b08163f9..b8263f83 100644 --- a/src/Managing.Application/Bots/BacktestSpotBot.cs +++ b/src/Managing.Application/Bots/BacktestSpotBot.cs @@ -180,7 +180,7 @@ public class BacktestSpotBot : TradingBotBase, ITradingBot throw new ArgumentNullException(nameof(Config.Scenario), "Config.Scenario cannot be null"); // Use TradingBox.GetSignal for backtest with pre-calculated indicators - var backtestSignal = TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LoopbackPeriod, + var backtestSignal = TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LookbackPeriod, preCalculatedIndicatorValues); if (backtestSignal == null) return; diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 4f1ddab8..82fa965f 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -487,7 +487,7 @@ namespace Managing.Application.ManageBot string? ticker = null, string? agentName = null, BotSortableColumn sortBy = BotSortableColumn.CreateDate, - string sortDirection = "Desc", + SortDirection sortDirection = SortDirection.Desc, bool showOnlyProfitable = false) { return await ServiceScopeHelpers.WithScopedService Bots, int TotalCount)>( diff --git a/src/Managing.Application/Scenarios/ScenarioRunnerGrain.cs b/src/Managing.Application/Scenarios/ScenarioRunnerGrain.cs index b872b5ca..b92723a9 100644 --- a/src/Managing.Application/Scenarios/ScenarioRunnerGrain.cs +++ b/src/Managing.Application/Scenarios/ScenarioRunnerGrain.cs @@ -68,7 +68,7 @@ public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain // Get candles as ordered List (already ordered by date from CandleStoreGrain) var candlesList = await GetCandlesAsync(tradingExchanges, config); - + if (candlesList.Count == 0) { _logger.LogWarning($"No candles available for {config.Ticker} for {config.Name}"); @@ -88,7 +88,7 @@ public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain candlesList, config.Scenario, previousSignals, - config.Scenario?.LoopbackPeriod ?? 1); + config.Scenario?.LookbackPeriod ?? 1); if (signal != null && signal.Date > candle.Date) { diff --git a/src/Managing.Application/Scenarios/ScenarioService.cs b/src/Managing.Application/Scenarios/ScenarioService.cs index d9582957..9cf469cb 100644 --- a/src/Managing.Application/Scenarios/ScenarioService.cs +++ b/src/Managing.Application/Scenarios/ScenarioService.cs @@ -20,7 +20,7 @@ namespace Managing.Application.Scenarios _tradingService = tradingService; } - public async Task CreateScenario(string name, List strategies, int? loopbackPeriod = 1) + public async Task CreateScenario(string name, List strategies, int loopbackPeriod = 1) { var scenario = new Scenario(name, loopbackPeriod); @@ -77,7 +77,7 @@ namespace Managing.Application.Scenarios scenario.AddIndicator(await _tradingService.GetIndicatorByNameAsync(strategy)); } - scenario.LoopbackPeriod = loopbackPeriod ?? 1; + scenario.LookbackPeriod = loopbackPeriod ?? 1; await _tradingService.UpdateScenarioAsync(scenario); return true; } @@ -262,7 +262,7 @@ namespace Managing.Application.Scenarios } scenario.Indicators.Clear(); - scenario.LoopbackPeriod = loopbackPeriod ?? 1; + scenario.LookbackPeriod = loopbackPeriod ?? 1; foreach (var strategyName in strategies) { diff --git a/src/Managing.Application/Workers/BundleBacktestWorker.cs b/src/Managing.Application/Workers/BundleBacktestWorker.cs index d469afbc..63e50413 100644 --- a/src/Managing.Application/Workers/BundleBacktestWorker.cs +++ b/src/Managing.Application/Workers/BundleBacktestWorker.cs @@ -225,7 +225,7 @@ public class BundleBacktestWorker : BaseWorker if (runBacktestRequest.Config.Scenario != null) { var sReq = runBacktestRequest.Config.Scenario; - scenario = new LightScenario(sReq.Name, sReq.LoopbackPeriod) + scenario = new LightScenario(sReq.Name, sReq.LookbackPeriod) { Indicators = sReq.Indicators?.Select(i => new LightIndicator(i.Name, i.Type) { diff --git a/src/Managing.Common/Enums.cs b/src/Managing.Common/Enums.cs index e5a0fb76..a8d0c4ef 100644 --- a/src/Managing.Common/Enums.cs +++ b/src/Managing.Common/Enums.cs @@ -1,4 +1,4 @@ -namespace Managing.Common; +namespace Managing.Common; public static class Enums { @@ -101,6 +101,15 @@ public static class Enums AgentName } + /// + /// Sort direction for pagination endpoints + /// + public enum SortDirection + { + Asc, + Desc + } + public enum SignalStatus { WaitingForPosition, diff --git a/src/Managing.Domain/Backtests/ScenarioRequest.cs b/src/Managing.Domain/Backtests/ScenarioRequest.cs index 547d8fe0..57e54878 100644 --- a/src/Managing.Domain/Backtests/ScenarioRequest.cs +++ b/src/Managing.Domain/Backtests/ScenarioRequest.cs @@ -22,5 +22,5 @@ public class ScenarioRequest /// /// The loopback period for the scenario /// - public int? LoopbackPeriod { get; set; } -} \ No newline at end of file + public int LookbackPeriod { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Bots/TradingBotConfig.cs b/src/Managing.Domain/Bots/TradingBotConfig.cs index c5cc0c3f..68e3c62d 100644 --- a/src/Managing.Domain/Bots/TradingBotConfig.cs +++ b/src/Managing.Domain/Bots/TradingBotConfig.cs @@ -45,6 +45,7 @@ public class TradingBotConfig /// Orleans-friendly version without FixedSizeQueue and User properties. /// [Id(12)] + [Required] public LightScenario Scenario { get; set; } /// @@ -104,6 +105,7 @@ public class TradingBotConfig /// [Id(20)] public bool UseForDynamicStopLoss { get; set; } = true; + /// /// Parameter to indicate if the bot is for copy trading /// diff --git a/src/Managing.Domain/Indicators/LightIndicator.cs b/src/Managing.Domain/Indicators/LightIndicator.cs index 1e8a35a9..1f75d210 100644 --- a/src/Managing.Domain/Indicators/LightIndicator.cs +++ b/src/Managing.Domain/Indicators/LightIndicator.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using Managing.Domain.Scenarios; using Orleans; using static Managing.Common.Enums; @@ -18,11 +19,11 @@ public class LightIndicator SignalType = ScenarioHelpers.GetSignalType(type); } - [Id(0)] public string Name { get; set; } + [Id(0)] [Required] public string Name { get; set; } - [Id(1)] public IndicatorType Type { get; set; } + [Id(1)] [Required] public IndicatorType Type { get; set; } - [Id(2)] public SignalType SignalType { get; set; } + [Id(2)] [Required] public SignalType SignalType { get; set; } [Id(3)] public int MinimumHistory { get; set; } diff --git a/src/Managing.Domain/Scenarios/LightScenario.cs b/src/Managing.Domain/Scenarios/LightScenario.cs index 692526cc..31625a60 100644 --- a/src/Managing.Domain/Scenarios/LightScenario.cs +++ b/src/Managing.Domain/Scenarios/LightScenario.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using Managing.Domain.Strategies; using Orleans; @@ -10,25 +11,25 @@ namespace Managing.Domain.Scenarios; [GenerateSerializer] public class LightScenario { - public LightScenario(string name, int? loopbackPeriod = 1) + public LightScenario(string name, int lookbackPeriod = 1) { Name = name; Indicators = new List(); - LoopbackPeriod = loopbackPeriod; + LookbackPeriod = lookbackPeriod; } [Id(0)] public string Name { get; set; } [Id(1)] public List Indicators { get; set; } - [Id(2)] public int? LoopbackPeriod { get; set; } + [Id(2)] [Required] public int LookbackPeriod { get; set; } /// /// Converts a full Scenario to a LightScenario /// public static LightScenario FromScenario(Scenario scenario) { - var lightScenario = new LightScenario(scenario.Name, scenario.LoopbackPeriod) + var lightScenario = new LightScenario(scenario.Name, scenario.LookbackPeriod) { Indicators = scenario.Indicators?.Select(ScenarioHelpers.BaseToLight).ToList() ?? new List() @@ -41,7 +42,7 @@ public class LightScenario /// public Scenario ToScenario() { - var scenario = new Scenario(Name, LoopbackPeriod) + var scenario = new Scenario(Name, LookbackPeriod) { Indicators = Indicators?.Select(li => li.LightToBase()).ToList() }; diff --git a/src/Managing.Domain/Scenarios/Scenario.cs b/src/Managing.Domain/Scenarios/Scenario.cs index 58571371..88558b11 100644 --- a/src/Managing.Domain/Scenarios/Scenario.cs +++ b/src/Managing.Domain/Scenarios/Scenario.cs @@ -7,18 +7,18 @@ namespace Managing.Domain.Scenarios [GenerateSerializer] public class Scenario { - public Scenario(string name, int? loopbackPeriod = 1) + public Scenario(string name, int lookbackPeriod = 1) { Name = name; Indicators = new List(); - LoopbackPeriod = loopbackPeriod; + LookbackPeriod = lookbackPeriod; } [Id(0)] public string Name { get; set; } [Id(1)] public List Indicators { get; set; } - [Id(2)] public int? LoopbackPeriod { get; set; } + [Id(2)] public int LookbackPeriod { get; set; } [Id(3)] public User User { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs index 627923ae..5e33894a 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs @@ -206,7 +206,7 @@ public class PostgreSqlBotRepository : IBotRepository string? ticker = null, string? agentName = null, BotSortableColumn sortBy = BotSortableColumn.CreateDate, - string sortDirection = "Desc", + SortDirection sortDirection = SortDirection.Desc, bool showOnlyProfitable = false) { // Build the query with filters @@ -249,33 +249,33 @@ public class PostgreSqlBotRepository : IBotRepository // Apply sorting query = sortBy switch { - BotSortableColumn.Name => sortDirection.ToLower() == "asc" + BotSortableColumn.Name => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.Name) : query.OrderByDescending(b => b.Name), - BotSortableColumn.Ticker => sortDirection.ToLower() == "asc" + BotSortableColumn.Ticker => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.Ticker) : query.OrderByDescending(b => b.Ticker), - BotSortableColumn.Status => sortDirection.ToLower() == "asc" + BotSortableColumn.Status => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.Status) : query.OrderByDescending(b => b.Status), - BotSortableColumn.StartupTime => sortDirection.ToLower() == "asc" + BotSortableColumn.StartupTime => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.StartupTime) : query.OrderByDescending(b => b.StartupTime), - BotSortableColumn.Roi => sortDirection.ToLower() == "asc" + BotSortableColumn.Roi => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.Roi) : query.OrderByDescending(b => b.Roi), - BotSortableColumn.Pnl => sortDirection.ToLower() == "asc" + BotSortableColumn.Pnl => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.Pnl) : query.OrderByDescending(b => b.Pnl), - BotSortableColumn.WinRate => sortDirection.ToLower() == "asc" + BotSortableColumn.WinRate => sortDirection == SortDirection.Asc ? query.OrderBy(b => (b.TradeWins + b.TradeLosses) > 0 ? (double)b.TradeWins / (b.TradeWins + b.TradeLosses) : 0) : query.OrderByDescending(b => (b.TradeWins + b.TradeLosses) > 0 ? (double)b.TradeWins / (b.TradeWins + b.TradeLosses) : 0), - BotSortableColumn.AgentName => sortDirection.ToLower() == "asc" + BotSortableColumn.AgentName => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.User.AgentName) : query.OrderByDescending(b => b.User.AgentName), - _ => sortDirection.ToLower() == "asc" + _ => sortDirection == SortDirection.Asc ? query.OrderBy(b => b.CreateDate) : query.OrderByDescending(b => b.CreateDate) }; diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs index 019f5ad8..1cf2fe12 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs @@ -360,7 +360,9 @@ public static class PostgreSqlMappers Duration = backtest.EndDate - backtest.StartDate, MoneyManagementJson = JsonConvert.SerializeObject(backtest.Config?.MoneyManagement, jsonSettings), UserId = backtest.User?.Id ?? 0, - StatisticsJson = backtest.Statistics != null ? JsonConvert.SerializeObject(backtest.Statistics, jsonSettings) : null, + StatisticsJson = backtest.Statistics != null + ? JsonConvert.SerializeObject(backtest.Statistics, jsonSettings) + : null, SharpeRatio = backtest.Statistics?.SharpeRatio ?? 0m, MaxDrawdown = backtest.Statistics?.MaxDrawdown ?? 0m, MaxDrawdownRecoveryTime = backtest.Statistics?.MaxDrawdownRecoveryTime ?? TimeSpan.Zero, @@ -503,7 +505,7 @@ public static class PostgreSqlMappers return new ScenarioEntity { Name = scenario.Name, - LoopbackPeriod = scenario.LoopbackPeriod ?? 1, + LoopbackPeriod = scenario.LookbackPeriod, UserId = scenario.User?.Id ?? 0 }; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs index f4d70f8b..dd7340c6 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs @@ -13,7 +13,8 @@ namespace Managing.Infrastructure.Databases.PostgreSql; public class PostgreSqlTradingRepository : BaseRepositoryWithLogging, ITradingRepository { - public PostgreSqlTradingRepository(ManagingDbContext context, ILogger logger, SentrySqlMonitoringService sentryMonitoringService) + public PostgreSqlTradingRepository(ManagingDbContext context, ILogger logger, + SentrySqlMonitoringService sentryMonitoringService) : base(context, logger, sentryMonitoringService) { } @@ -154,7 +155,7 @@ public class PostgreSqlTradingRepository : BaseRepositoryWithLogging, ITradingRe if (entity != null) { - entity.LoopbackPeriod = scenario.LoopbackPeriod ?? 1; + entity.LoopbackPeriod = scenario.LookbackPeriod; entity.UserId = scenario.User?.Id ?? 0; entity.UpdatedAt = DateTime.UtcNow; @@ -408,62 +409,63 @@ public class PostgreSqlTradingRepository : BaseRepositoryWithLogging, ITradingRe public async Task UpdatePositionAsync(Position position) { await ExecuteWithLoggingAsync(async () => - { - try { - await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context); - - var entity = _context.Positions - .AsTracking() - .Include(p => p.OpenTrade) - .Include(p => p.StopLossTrade) - .Include(p => p.TakeProfit1Trade) - .Include(p => p.TakeProfit2Trade) - .FirstOrDefault(p => p.Identifier == position.Identifier); - - if (entity != null) + try { - entity.ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0; - entity.NetPnL = position.ProfitAndLoss?.Net ?? 0; - entity.UiFees = position.UiFees; - // entity.OriginDirection = position.OriginDirection; - entity.GasFees = position.GasFees; - entity.Status = position.Status; - entity.MoneyManagementJson = position.MoneyManagement != null - ? JsonConvert.SerializeObject(position.MoneyManagement) - : null; - entity.UpdatedAt = DateTime.UtcNow; + await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context); - // Update related trades directly through the position's trade references - // This ensures we're updating the correct trade records for this specific position - if (position.Open != null && entity.OpenTrade != null) + var entity = _context.Positions + .AsTracking() + .Include(p => p.OpenTrade) + .Include(p => p.StopLossTrade) + .Include(p => p.TakeProfit1Trade) + .Include(p => p.TakeProfit2Trade) + .FirstOrDefault(p => p.Identifier == position.Identifier); + + if (entity != null) { - UpdateTradeEntity(entity.OpenTrade, position.Open); - } + entity.ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0; + entity.NetPnL = position.ProfitAndLoss?.Net ?? 0; + entity.UiFees = position.UiFees; + // entity.OriginDirection = position.OriginDirection; + entity.GasFees = position.GasFees; + entity.Status = position.Status; + entity.MoneyManagementJson = position.MoneyManagement != null + ? JsonConvert.SerializeObject(position.MoneyManagement) + : null; + entity.UpdatedAt = DateTime.UtcNow; - if (position.StopLoss != null && entity.StopLossTrade != null) - { - UpdateTradeEntity(entity.StopLossTrade, position.StopLoss); - } + // Update related trades directly through the position's trade references + // This ensures we're updating the correct trade records for this specific position + if (position.Open != null && entity.OpenTrade != null) + { + UpdateTradeEntity(entity.OpenTrade, position.Open); + } - if (position.TakeProfit1 != null && entity.TakeProfit1Trade != null) - { - UpdateTradeEntity(entity.TakeProfit1Trade, position.TakeProfit1); - } + if (position.StopLoss != null && entity.StopLossTrade != null) + { + UpdateTradeEntity(entity.StopLossTrade, position.StopLoss); + } - if (position.TakeProfit2 != null && entity.TakeProfit2Trade != null) - { - UpdateTradeEntity(entity.TakeProfit2Trade, position.TakeProfit2); - } + if (position.TakeProfit1 != null && entity.TakeProfit1Trade != null) + { + UpdateTradeEntity(entity.TakeProfit1Trade, position.TakeProfit1); + } - await _context.SaveChangesAsync(); + if (position.TakeProfit2 != null && entity.TakeProfit2Trade != null) + { + UpdateTradeEntity(entity.TakeProfit2Trade, position.TakeProfit2); + } + + await _context.SaveChangesAsync(); + } } - } - finally - { - await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context); - } - }, nameof(UpdatePositionAsync), ("positionIdentifier", position.Identifier), ("positionStatus", position.Status)); + finally + { + await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context); + } + }, nameof(UpdatePositionAsync), ("positionIdentifier", position.Identifier), + ("positionStatus", position.Status)); } /// @@ -474,7 +476,7 @@ public class PostgreSqlTradingRepository : BaseRepositoryWithLogging, ITradingRe { // Only update the date if the trade status is changing from Requested to Filled // This prevents overwriting dates for trades that are already filled - if (entity.Status != trade.Status) + if (entity.Status != trade.Status) { entity.Date = trade.Date; } diff --git a/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts b/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts index c6c2deb6..6985348b 100644 --- a/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts +++ b/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts @@ -355,7 +355,7 @@ export interface TradingBotConfig { flipPosition: boolean; name: string; riskManagement?: RiskManagement | null; - scenario?: LightScenario | null; + scenario: LightScenario; scenarioName?: string | null; maxPositionTimeHours?: number | null; closeEarlyWhenProfitable?: boolean; @@ -420,13 +420,13 @@ export enum RiskToleranceLevel { export interface LightScenario { name?: string | null; indicators?: LightIndicator[] | null; - loopbackPeriod?: number | null; + lookbackPeriod: number; } export interface LightIndicator { - name?: string | null; - type?: IndicatorType; - signalType?: SignalType; + name: string; + type: IndicatorType; + signalType: SignalType; minimumHistory?: number; period?: number | null; fastPeriods?: number | null; @@ -728,7 +728,7 @@ export interface TradingBotConfigRequest { export interface ScenarioRequest { name: string; indicators: IndicatorRequest[]; - loopbackPeriod?: number | null; + lookbackPeriod?: number; } export interface IndicatorRequest { @@ -1014,6 +1014,11 @@ export enum BotSortableColumn { AgentName = "AgentName", } +export enum SortDirection { + Asc = "Asc", + Desc = "Desc", +} + export interface CreateManualSignalRequest { identifier?: string; direction?: TradeDirection; @@ -1052,7 +1057,7 @@ export interface Spotlight { export interface Scenario { name?: string | null; indicators?: IndicatorBase[] | null; - loopbackPeriod?: number | null; + lookbackPeriod?: number; user?: User | null; } @@ -1231,25 +1236,25 @@ export interface StrategyRoiPerformance { } export interface UserStrategyDetailsViewModel { - name?: string | null; - state?: BotStatus; - pnL?: number; - netPnL?: number; - roiPercentage?: number; - runtime?: Date | null; - totalRuntimeSeconds?: number; - lastStartTime?: Date | null; + name: string; + state: BotStatus; + pnL: number; + netPnL: number; + roiPercentage: number; + runtime: Date; + totalRuntimeSeconds: number; + lastStartTime: Date; lastStopTime?: Date | null; - accumulatedRunTimeSeconds?: number; - winRate?: number; - totalVolumeTraded?: number; - volumeLast24H?: number; - wins?: number; - losses?: number; - positions?: PositionViewModel[] | null; - identifier?: string; + accumulatedRunTimeSeconds: number; + winRate: number; + totalVolumeTraded: number; + volumeLast24H: number; + wins: number; + losses: number; + positions: PositionViewModel[]; + identifier: string; walletBalances?: { [key: string]: number; } | null; - ticker?: Ticker; + ticker: Ticker; masterAgentName?: string | null; } diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index a8a6d37f..0db7d052 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -1917,7 +1917,7 @@ export class BotClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - bot_GetBotsPaginated(pageNumber: number | undefined, pageSize: number | undefined, status: BotStatus | null | undefined, name: string | null | undefined, ticker: string | null | undefined, agentName: string | null | undefined, sortBy: BotSortableColumn | undefined, sortDirection: string | null | undefined): Promise { + bot_GetBotsPaginated(pageNumber: number | undefined, pageSize: number | undefined, status: BotStatus | null | undefined, name: string | null | undefined, ticker: string | null | undefined, agentName: string | null | undefined, sortBy: BotSortableColumn | undefined, sortDirection: SortDirection | undefined): Promise { let url_ = this.baseUrl + "/Bot/Paginated?"; if (pageNumber === null) throw new Error("The parameter 'pageNumber' cannot be null."); @@ -1939,7 +1939,9 @@ export class BotClient extends AuthorizedApiBase { throw new Error("The parameter 'sortBy' cannot be null."); else if (sortBy !== undefined) url_ += "sortBy=" + encodeURIComponent("" + sortBy) + "&"; - if (sortDirection !== undefined && sortDirection !== null) + if (sortDirection === null) + throw new Error("The parameter 'sortDirection' cannot be null."); + else if (sortDirection !== undefined) url_ += "sortDirection=" + encodeURIComponent("" + sortDirection) + "&"; url_ = url_.replace(/[?&]$/, ""); @@ -2599,7 +2601,7 @@ export class DataClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - data_GetStrategiesPaginated(pageNumber: number | undefined, pageSize: number | undefined, name: string | null | undefined, ticker: string | null | undefined, agentName: string | null | undefined, sortBy: BotSortableColumn | undefined, sortDirection: string | null | undefined): Promise { + data_GetStrategiesPaginated(pageNumber: number | undefined, pageSize: number | undefined, name: string | null | undefined, ticker: string | null | undefined, agentName: string | null | undefined, sortBy: BotSortableColumn | undefined, sortDirection: SortDirection | undefined): Promise { let url_ = this.baseUrl + "/Data/GetStrategiesPaginated?"; if (pageNumber === null) throw new Error("The parameter 'pageNumber' cannot be null."); @@ -2619,7 +2621,9 @@ export class DataClient extends AuthorizedApiBase { throw new Error("The parameter 'sortBy' cannot be null."); else if (sortBy !== undefined) url_ += "sortBy=" + encodeURIComponent("" + sortBy) + "&"; - if (sortDirection !== undefined && sortDirection !== null) + if (sortDirection === null) + throw new Error("The parameter 'sortDirection' cannot be null."); + else if (sortDirection !== undefined) url_ += "sortDirection=" + encodeURIComponent("" + sortDirection) + "&"; url_ = url_.replace(/[?&]$/, ""); @@ -4854,7 +4858,7 @@ export interface TradingBotConfig { flipPosition: boolean; name: string; riskManagement?: RiskManagement | null; - scenario?: LightScenario | null; + scenario: LightScenario; scenarioName?: string | null; maxPositionTimeHours?: number | null; closeEarlyWhenProfitable?: boolean; @@ -4919,13 +4923,13 @@ export enum RiskToleranceLevel { export interface LightScenario { name?: string | null; indicators?: LightIndicator[] | null; - loopbackPeriod?: number | null; + lookbackPeriod: number; } export interface LightIndicator { - name?: string | null; - type?: IndicatorType; - signalType?: SignalType; + name: string; + type: IndicatorType; + signalType: SignalType; minimumHistory?: number; period?: number | null; fastPeriods?: number | null; @@ -5227,7 +5231,7 @@ export interface TradingBotConfigRequest { export interface ScenarioRequest { name: string; indicators: IndicatorRequest[]; - loopbackPeriod?: number | null; + lookbackPeriod?: number; } export interface IndicatorRequest { @@ -5513,6 +5517,11 @@ export enum BotSortableColumn { AgentName = "AgentName", } +export enum SortDirection { + Asc = "Asc", + Desc = "Desc", +} + export interface CreateManualSignalRequest { identifier?: string; direction?: TradeDirection; @@ -5551,7 +5560,7 @@ export interface Spotlight { export interface Scenario { name?: string | null; indicators?: IndicatorBase[] | null; - loopbackPeriod?: number | null; + lookbackPeriod?: number; user?: User | null; } @@ -5730,25 +5739,25 @@ export interface StrategyRoiPerformance { } export interface UserStrategyDetailsViewModel { - name?: string | null; - state?: BotStatus; - pnL?: number; - netPnL?: number; - roiPercentage?: number; - runtime?: Date | null; - totalRuntimeSeconds?: number; - lastStartTime?: Date | null; + name: string; + state: BotStatus; + pnL: number; + netPnL: number; + roiPercentage: number; + runtime: Date; + totalRuntimeSeconds: number; + lastStartTime: Date; lastStopTime?: Date | null; - accumulatedRunTimeSeconds?: number; - winRate?: number; - totalVolumeTraded?: number; - volumeLast24H?: number; - wins?: number; - losses?: number; - positions?: PositionViewModel[] | null; - identifier?: string; + accumulatedRunTimeSeconds: number; + winRate: number; + totalVolumeTraded: number; + volumeLast24H: number; + wins: number; + losses: number; + positions: PositionViewModel[]; + identifier: string; walletBalances?: { [key: string]: number; } | null; - ticker?: Ticker; + ticker: Ticker; masterAgentName?: string | null; } diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index c6c2deb6..6985348b 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -355,7 +355,7 @@ export interface TradingBotConfig { flipPosition: boolean; name: string; riskManagement?: RiskManagement | null; - scenario?: LightScenario | null; + scenario: LightScenario; scenarioName?: string | null; maxPositionTimeHours?: number | null; closeEarlyWhenProfitable?: boolean; @@ -420,13 +420,13 @@ export enum RiskToleranceLevel { export interface LightScenario { name?: string | null; indicators?: LightIndicator[] | null; - loopbackPeriod?: number | null; + lookbackPeriod: number; } export interface LightIndicator { - name?: string | null; - type?: IndicatorType; - signalType?: SignalType; + name: string; + type: IndicatorType; + signalType: SignalType; minimumHistory?: number; period?: number | null; fastPeriods?: number | null; @@ -728,7 +728,7 @@ export interface TradingBotConfigRequest { export interface ScenarioRequest { name: string; indicators: IndicatorRequest[]; - loopbackPeriod?: number | null; + lookbackPeriod?: number; } export interface IndicatorRequest { @@ -1014,6 +1014,11 @@ export enum BotSortableColumn { AgentName = "AgentName", } +export enum SortDirection { + Asc = "Asc", + Desc = "Desc", +} + export interface CreateManualSignalRequest { identifier?: string; direction?: TradeDirection; @@ -1052,7 +1057,7 @@ export interface Spotlight { export interface Scenario { name?: string | null; indicators?: IndicatorBase[] | null; - loopbackPeriod?: number | null; + lookbackPeriod?: number; user?: User | null; } @@ -1231,25 +1236,25 @@ export interface StrategyRoiPerformance { } export interface UserStrategyDetailsViewModel { - name?: string | null; - state?: BotStatus; - pnL?: number; - netPnL?: number; - roiPercentage?: number; - runtime?: Date | null; - totalRuntimeSeconds?: number; - lastStartTime?: Date | null; + name: string; + state: BotStatus; + pnL: number; + netPnL: number; + roiPercentage: number; + runtime: Date; + totalRuntimeSeconds: number; + lastStartTime: Date; lastStopTime?: Date | null; - accumulatedRunTimeSeconds?: number; - winRate?: number; - totalVolumeTraded?: number; - volumeLast24H?: number; - wins?: number; - losses?: number; - positions?: PositionViewModel[] | null; - identifier?: string; + accumulatedRunTimeSeconds: number; + winRate: number; + totalVolumeTraded: number; + volumeLast24H: number; + wins: number; + losses: number; + positions: PositionViewModel[]; + identifier: string; walletBalances?: { [key: string]: number; } | null; - ticker?: Ticker; + ticker: Ticker; masterAgentName?: string | null; } diff --git a/src/Managing.Workers.Tests/BacktestExecutorTests.cs b/src/Managing.Workers.Tests/BacktestExecutorTests.cs index a1fae7f8..94b9ae9e 100644 --- a/src/Managing.Workers.Tests/BacktestExecutorTests.cs +++ b/src/Managing.Workers.Tests/BacktestExecutorTests.cs @@ -112,7 +112,7 @@ public class BacktestExecutorTests : BaseTests, IDisposable var scenario = new Scenario("ETH_BacktestScenario"); var rsiDivIndicator = ScenarioHelpers.BuildIndicator(IndicatorType.RsiDivergence, "RsiDiv", period: 14); scenario.Indicators = new List { (IndicatorBase)rsiDivIndicator }; - scenario.LoopbackPeriod = 15; + scenario.LookbackPeriod = 15; var config = new TradingBotConfig { @@ -207,7 +207,7 @@ public class BacktestExecutorTests : BaseTests, IDisposable var scenario = new Scenario("ETH_BacktestScenario"); var rsiDivIndicator = ScenarioHelpers.BuildIndicator(IndicatorType.RsiDivergence, "RsiDiv", period: 14); scenario.Indicators = new List { (IndicatorBase)rsiDivIndicator }; - scenario.LoopbackPeriod = 15; + scenario.LookbackPeriod = 15; var config = new TradingBotConfig { @@ -297,7 +297,7 @@ public class BacktestExecutorTests : BaseTests, IDisposable var scenario = new Scenario("ETH_BacktestScenario"); var rsiDivIndicator = ScenarioHelpers.BuildIndicator(IndicatorType.RsiDivergence, "RsiDiv", period: 14); scenario.Indicators = new List { (IndicatorBase)rsiDivIndicator }; - scenario.LoopbackPeriod = 15; + scenario.LookbackPeriod = 15; var config = new TradingBotConfig { @@ -387,7 +387,7 @@ public class BacktestExecutorTests : BaseTests, IDisposable var emaCrossIndicator = ScenarioHelpers.BuildIndicator(IndicatorType.EmaCross, "EmaCross", period: 21); scenario.Indicators = new List { (IndicatorBase)rsiDivIndicator, (IndicatorBase)emaCrossIndicator }; - scenario.LoopbackPeriod = 15; // 15 minutes loopback period as requested + scenario.LookbackPeriod = 15; // 15 minutes loopback period as requested var config = new TradingBotConfig { @@ -488,7 +488,7 @@ public class BacktestExecutorTests : BaseTests, IDisposable Console.WriteLine($"📈 Win Rate: {result.WinRate}% (Expected: {expectedWinRatePercent}%)"); Console.WriteLine($"📈 Growth: {result.GrowthPercentage:F2}% (Expected: {expectedGrowthPercentage:F2}%)"); Console.WriteLine( - $"🎭 Scenario: {scenario.Name} ({scenario.Indicators.Count} indicators, LoopbackPeriod: {scenario.LoopbackPeriod})"); + $"🎭 Scenario: {scenario.Name} ({scenario.Indicators.Count} indicators, LoopbackPeriod: {scenario.LookbackPeriod})"); // Performance assertion - should be reasonably fast even with 2 indicators Assert.True(candlesPerSecond > 200, @@ -510,7 +510,7 @@ public class BacktestExecutorTests : BaseTests, IDisposable var scenario = new Scenario("ETH_Spot_BacktestScenario"); var rsiDivIndicator = ScenarioHelpers.BuildIndicator(IndicatorType.RsiDivergence, "RsiDiv", period: 14); scenario.Indicators = new List { (IndicatorBase)rsiDivIndicator }; - scenario.LoopbackPeriod = 15; + scenario.LookbackPeriod = 15; var config = new TradingBotConfig { @@ -570,10 +570,10 @@ public class BacktestExecutorTests : BaseTests, IDisposable // Assert - Validate specific backtest results Assert.NotNull(result); Assert.IsType(result); - + // Verify TradingType is BacktestSpot Assert.Equal(TradingType.BacktestSpot, result.Config.TradingType); - + // Validate key metrics - Updated with actual backtest results Assert.Equal(1000.0m, result.InitialBalance); Assert.Equal(-71.63m, Math.Round(result.FinalPnl, 2)); @@ -586,7 +586,7 @@ public class BacktestExecutorTests : BaseTests, IDisposable Assert.Equal(-0.107, Math.Round((double)(result.SharpeRatio ?? 0), 3)); Assert.True(Math.Abs(result.Score - 0.0) < 0.001, $"Score {result.Score} should be within 0.001 of expected value 0.0"); - + // Validate dates Assert.Equal(new DateTime(2025, 10, 14, 12, 0, 0), result.StartDate); Assert.Equal(new DateTime(2025, 10, 24, 11, 45, 0), result.EndDate);