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 }
}