SoftTraderBackend/ServiceHandler/AlertEvaluationService.cs
2026-03-20 10:42:48 -04:00

100 lines
2.7 KiB
C#

using Backend.Interface;
namespace Backend.ServiceHander;
public sealed class AlertEvaluationService : BackgroundService
{
private static readonly TimeSpan PollInterval = TimeSpan.FromMinutes(1);
private static readonly TimeZoneInfo Pacific = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
private readonly IServiceProvider _services;
private readonly ILogger<AlertEvaluationService> _logger;
private readonly Dictionary<string, IAlertEvaluator> _evaluators;
public AlertEvaluationService(
IServiceProvider services,
ILogger<AlertEvaluationService> logger,
IEnumerable<IAlertEvaluator> evaluators)
{
_services = services;
_logger = logger;
_evaluators = evaluators.ToDictionary(e => e.AlertType);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (IsWithinActiveWindow())
{
await EvaluateAlertsAsync(stoppingToken);
}
else
{
_logger.LogDebug("Outside active window, skipping alert evaluation.");
}
await Task.Delay(PollInterval, stoppingToken);
}
}
private static bool IsWithinActiveWindow()
{
var pacificNow = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, Pacific);
if (pacificNow.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday)
return false;
var hour = pacificNow.Hour;
return hour >= 6 && hour < 14;
}
private async Task EvaluateAlertsAsync(CancellationToken cancellationToken)
{
try
{
var store = _services.GetRequiredService<IAlertStore>();
var marketData = _services.GetRequiredService<MarketDataService>();
var activeAlerts = await store.GetActiveAlertsAsync(cancellationToken);
_logger.LogInformation("Evaluating {Count} active alerts.", activeAlerts.Count);
foreach (var alert in activeAlerts)
{
try
{
if (!_evaluators.TryGetValue(alert.AlertType, out var evaluator))
{
_logger.LogWarning("No evaluator registered for alert type '{AlertType}'.", alert.AlertType);
continue;
}
var result = await evaluator.EvaluateAsync(alert, marketData);
if (result.UpdatedLastState is not null)
alert.LastState = result.UpdatedLastState;
if (result.Triggered)
{
alert.IsTriggered = true;
_logger.LogInformation(
"Alert {Id} triggered: {Type} for {Symbol} (user: {User})",
alert.Id, alert.AlertType, alert.Symbol, alert.Username);
}
await store.UpdateAlertAsync(alert, cancellationToken);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to evaluate alert {Id}.", alert.Id);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Alert evaluation cycle failed.");
}
}
}