102 lines
3.2 KiB
C#
102 lines
3.2 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace DesktopBot.Services
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<PingResult> 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
|
|
{
|
|
/// <summary>Latenza in millisecondi. -1 se non raggiungibile.</summary>
|
|
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 }
|
|
}
|