using Managing.Application.Abstractions.Services; using Managing.Common; using Microsoft.Extensions.Diagnostics.HealthChecks; using static Managing.Common.Enums; namespace Managing.Api.HealthChecks { public class CandleDataDetailedHealthCheck : IHealthCheck { private readonly IExchangeService _exchangeService; private readonly TradingExchanges _exchangeToCheck = TradingExchanges.Evm; public CandleDataDetailedHealthCheck(IExchangeService exchangeService) { _exchangeService = exchangeService; } public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { try { var now = DateTime.UtcNow; // Define timeframes to check with their appropriate start dates and expected freshness var timeframeChecks = new[] { (timeframe: Timeframe.FiveMinutes, startDate: now.AddDays(-1), maxAgeMins: 7), (timeframe: Timeframe.FifteenMinutes, startDate: now.AddDays(-1), maxAgeMins: 17), (timeframe: Timeframe.OneHour, startDate: now.AddDays(-3), maxAgeMins: 62), (timeframe: Timeframe.FourHour, startDate: now.AddDays(-7), maxAgeMins: 242), (timeframe: Timeframe.OneDay, startDate: now.AddDays(-30), maxAgeMins: 1442) }; var supportedTickers = Constants.GMX.Config.SupportedTickers; var tickerResults = new Dictionary(); var isHealthy = true; var totalChecks = 0; var healthyChecks = 0; var degradedChecks = 0; foreach (var ticker in supportedTickers) { var degradedTimeframeResults = new List>(); foreach (var (timeframe, startDate, maxAgeMins) in timeframeChecks) { totalChecks++; // Fetch candles for this ticker and timeframe var candles = await _exchangeService.GetCandlesInflux( _exchangeToCheck, ticker, startDate, timeframe); if (candles == null || !candles.Any()) { var checkResult = new Dictionary { ["Timeframe"] = timeframe.ToString(), ["StartDate"] = startDate, ["Status"] = "Degraded", ["Message"] = $"No candle data found for {ticker} - {timeframe}" }; degradedTimeframeResults.Add(checkResult); isHealthy = false; degradedChecks++; } else { var latestCandle = candles.OrderByDescending(c => c.Date).FirstOrDefault(); var timeDiff = now - latestCandle.Date; if (timeDiff.TotalMinutes > maxAgeMins) { var checkResult = new Dictionary { ["Timeframe"] = timeframe.ToString(), ["StartDate"] = startDate, ["LatestCandleDate"] = latestCandle.Date, ["TimeDifference"] = $"{timeDiff.TotalMinutes:F2} minutes", ["CandleCount"] = candles.Count(), ["Status"] = "Degraded", ["Message"] = $"Data for {ticker} - {timeframe} is outdated. Latest candle is from {latestCandle.Date:yyyy-MM-dd HH:mm:ss} UTC" }; degradedTimeframeResults.Add(checkResult); isHealthy = false; degradedChecks++; } else { healthyChecks++; } } } // Only add ticker to results if it has degraded checks if (degradedTimeframeResults.Any()) { tickerResults[ticker.ToString()] = degradedTimeframeResults; } } // Build summary var summary = new Dictionary { ["TotalChecks"] = totalChecks, ["HealthyChecks"] = healthyChecks, ["DegradedChecks"] = degradedChecks, ["CheckedTickers"] = supportedTickers.Select(t => t.ToString()).ToArray(), ["CheckedTimeframes"] = timeframeChecks.Select(tc => tc.timeframe.ToString()).ToArray(), ["TickerResults"] = tickerResults }; if (isHealthy) { return HealthCheckResult.Healthy( $"All candle data is up-to-date for {supportedTickers.Length} tickers across {timeframeChecks.Length} timeframes", data: summary); } else { return HealthCheckResult.Degraded( $"{degradedChecks} out of {totalChecks} candle checks are outdated or missing", data: summary); } } catch (Exception ex) { return HealthCheckResult.Unhealthy( "Error checking detailed candle data health", ex, data: new Dictionary { ["ErrorMessage"] = ex.Message, ["ErrorType"] = ex.GetType().Name }); } } } }