using System; using System.Diagnostics; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace DesktopBot.Services { /// /// Misura la latenza (ping) verso l'endpoint di trading Alpaca. /// Usa una richiesta GET su /v2/clock che รจ leggera e non richiede /// autenticazione nella versione pubblica. /// public class AlpacaPingService : IDisposable { // endpoint pubblico leggero di Alpaca (non richiede auth, risponde 200) private const string PingUrlPaper = "https://paper-api.alpaca.markets/v2/clock"; private const string PingUrlLive = "https://api.alpaca.markets/v2/clock"; private readonly HttpClient _http; private CancellationTokenSource _cts; private bool _isPaper = true; public event EventHandler PingCompleted; public AlpacaPingService() { _http = new HttpClient { Timeout = TimeSpan.FromSeconds(8) }; } public void SetEnvironment(bool isPaper) => _isPaper = isPaper; public void Start(int intervalSeconds = 10) { _cts?.Cancel(); _cts = new CancellationTokenSource(); _ = PingLoopAsync(intervalSeconds, _cts.Token); } public void Stop() => _cts?.Cancel(); private async Task PingLoopAsync(int intervalSeconds, CancellationToken ct) { // Prima misura immediata await MeasureAsync(); while (!ct.IsCancellationRequested) { try { await Task.Delay(TimeSpan.FromSeconds(intervalSeconds), ct); } catch (OperationCanceledException) { return; } await MeasureAsync(); } } private async Task MeasureAsync() { string url = _isPaper ? PingUrlPaper : PingUrlLive; var sw = Stopwatch.StartNew(); try { using var req = new HttpRequestMessage(HttpMethod.Head, url); using var resp = await _http.SendAsync(req).ConfigureAwait(false); sw.Stop(); PingCompleted?.Invoke(this, new PingResult { LatencyMs = (int)sw.ElapsedMilliseconds, IsSuccess = resp.IsSuccessStatusCode || (int)resp.StatusCode == 403 // 403 = raggiungibile ma senza auth }); } catch { sw.Stop(); PingCompleted?.Invoke(this, new PingResult { LatencyMs = -1, IsSuccess = false }); } } public void Dispose() { _cts?.Cancel(); _http?.Dispose(); } } public class PingResult { /// Latenza in millisecondi. -1 se non raggiungibile. public int LatencyMs { get; set; } public bool IsSuccess { get; set; } public PingStatus Status => !IsSuccess ? PingStatus.Offline : LatencyMs < 80 ? PingStatus.Good : LatencyMs < 250 ? PingStatus.Fair : PingStatus.Poor; } public enum PingStatus { Good, Fair, Poor, Offline } }