100 lines
2.7 KiB
C#
100 lines
2.7 KiB
C#
using Backend.Interface;
|
|
|
|
namespace Backend.ServiceHandler;
|
|
|
|
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.");
|
|
}
|
|
}
|
|
}
|