using System.Text.Json; using Backend.DatabaseHandler; using Backend.Interface; namespace Backend.ServiceHandler; public sealed class PriceSpikeEvaluator : IAlertEvaluator { public string AlertType => "price_spike"; public Task EvaluateAsync(AlertRecord alert, MarketDataService marketData) { var parameters = JsonSerializer.Deserialize(alert.Parameters) ?? throw new InvalidOperationException($"Invalid parameters for alert {alert.Id}"); var snapshot = marketData.GetLiveQuote(alert.Symbol); if (!snapshot.LastPrice.HasValue) { return Task.FromResult(new AlertEvaluation { Triggered = false }); } double price = snapshot.LastPrice.Value; var now = DateTime.UtcNow; var state = JsonSerializer.Deserialize(alert.LastState) ?? new PriceSpikeState(); state.Prices ??= new List(); state.Prices.Add(new PricePoint { Price = price, Timestamp = now }); var cutoff = now.AddMinutes(-parameters.WindowMinutes); state.Prices.RemoveAll(p => p.Timestamp < cutoff); bool triggered = false; if (state.Prices.Count >= 2) { double oldest = state.Prices[0].Price; if (oldest != 0) { double change = Math.Abs((price - oldest) / oldest * 100.0); triggered = change >= parameters.PercentChange; } } string updatedState = JsonSerializer.Serialize(state); return Task.FromResult(new AlertEvaluation { Triggered = triggered, UpdatedLastState = updatedState }); } private sealed class PriceSpikeParameters { public double PercentChange { get; set; } public int WindowMinutes { get; set; } = 15; } private sealed class PriceSpikeState { public List? Prices { get; set; } } private sealed class PricePoint { public double Price { get; set; } public DateTime Timestamp { get; set; } } }