using System.Text.Json; using Backend.DatabaseHandler; using Backend.Interface; namespace Backend.ServiceHander; public sealed class TrailingPriceEvaluator : IAlertEvaluator { public string AlertType => "trailing_price"; 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 state = JsonSerializer.Deserialize(alert.LastState) ?? new TrailingPriceState(); bool triggered; if (parameters.Direction.Equals("bullish", StringComparison.OrdinalIgnoreCase)) { if (!state.PeakPrice.HasValue || price > state.PeakPrice.Value) state.PeakPrice = price; double dropPercent = (state.PeakPrice.Value - price) / state.PeakPrice.Value * 100.0; triggered = dropPercent >= parameters.TrailPercent; } else { if (!state.TroughPrice.HasValue || price < state.TroughPrice.Value) state.TroughPrice = price; double risePercent = state.TroughPrice.Value == 0 ? 0 : (price - state.TroughPrice.Value) / state.TroughPrice.Value * 100.0; triggered = risePercent >= parameters.TrailPercent; } string updatedState = JsonSerializer.Serialize(new { peakPrice = state.PeakPrice, troughPrice = state.TroughPrice, lastPrice = price }); return Task.FromResult(new AlertEvaluation { Triggered = triggered, UpdatedLastState = updatedState }); } private sealed class TrailingPriceParameters { public string Direction { get; set; } = string.Empty; public double TrailPercent { get; set; } } private sealed class TrailingPriceState { public double? PeakPrice { get; set; } public double? TroughPrice { get; set; } } }