diff --git a/src/KArtSell.Host/Features/ShadowRun/Endpoint.cs b/src/KArtSell.Host/Features/ShadowRun/Endpoint.cs index 85af9eea..4ceb6a3d 100644 --- a/src/KArtSell.Host/Features/ShadowRun/Endpoint.cs +++ b/src/KArtSell.Host/Features/ShadowRun/Endpoint.cs @@ -29,7 +29,7 @@ public sealed class InitiateShadowRunEndpoint : Endpoint(); } diff --git a/src/KArtSell.Host/Features/ShadowRun/GetShadowRunPollingEndpoint.cs b/src/KArtSell.Host/Features/ShadowRun/GetShadowRunPollingEndpoint.cs index 5c22fda0..edde31e2 100644 --- a/src/KArtSell.Host/Features/ShadowRun/GetShadowRunPollingEndpoint.cs +++ b/src/KArtSell.Host/Features/ShadowRun/GetShadowRunPollingEndpoint.cs @@ -28,7 +28,7 @@ public sealed class GetShadowRunPollingEndpoint : Endpoint LogPhase4Complete = + LoggerMessage.Define( + LogLevel.Information, + new EventId(7, nameof(LogPhase4Complete)), + "Shadow run {RunId} phase 4 (phase segmentation) complete"); + [Queue("q-research")] [DisableConcurrentExecution(timeoutInSeconds: 3600)] // Max 60 minutes [AutomaticRetry(Attempts = MaxAttempts, OnAttemptsExceeded = AttemptsExceededAction.Fail)] @@ -105,12 +111,17 @@ public sealed class ShadowRunJob( LogPhase3Complete(logger, command.RunId, null); - // Phase 4: Evaluate gates + // Phase 4: Phase segmentation + var phaseBreakdownDto = PhaseSegmentation.Segment( + replayResult.DailyReturns.ToList()); + var phaseBreakdown = new PhaseBreakdown( - BullMarket: new PhaseMetrics(0, 0, 0, 0, 0), // TODO: Phase segmentation - BearMarket: new PhaseMetrics(0, 0, 0, 0, 0), - Sideways: new PhaseMetrics(0, 0, 0, 0, 0), - HighVolatility: new PhaseMetrics(0, 0, 0, 0, 0)); + BullMarket: ConvertPhaseMetrics(phaseBreakdownDto.BullMarket), + BearMarket: ConvertPhaseMetrics(phaseBreakdownDto.BearMarket), + Sideways: ConvertPhaseMetrics(phaseBreakdownDto.Sideways), + HighVolatility: ConvertPhaseMetrics(phaseBreakdownDto.HighVolatility)); + + LogPhase4Complete(logger, command.RunId, null); var costAnalysis = new CostAnalysis( BaseScenarioReturn: metrics.TotalReturn, @@ -157,4 +168,12 @@ public sealed class ShadowRunJob( throw; // Hangfire will classify as transient/permanent based on exception type } } + + private static PhaseMetrics ConvertPhaseMetrics(PhaseMetricsDto dto) + => new PhaseMetrics( + TradingDays: dto.TradingDays, + Return: dto.Return, + Sharpe: dto.Sharpe, + WinRate: dto.WinRate, + MaxDrawdown: dto.MaxDrawdown); }