using System;
namespace DesktopBot.Services
{
///
/// Helper per determinare se un mercato è aperto in base all'asset class e all'orario locale.
///
/// Regole approssimative (senza chiamata API aggiuntiva):
/// - US Equity : Lun–Ven 09:30–16:00 ET (UTC-5 standard / UTC-4 daylight)
/// - Crypto : sempre aperto (24/7)
/// - Altro : assume sempre aperto
///
/// Per una verifica precisa (pre-market, post-market, festivi) usare
/// IAlpacaTradingClient.GetClockAsync() — disponibile ma costa una chiamata API.
///
public static class MarketHoursService
{
// ── Orari NYSE/NASDAQ in Eastern Time ────────────────────────────────
private static readonly TimeSpan MarketOpen = new TimeSpan(9, 30, 0);
private static readonly TimeSpan MarketClose = new TimeSpan(16, 0, 0);
// Fuso Eastern (tiene conto automaticamente del DST di .NET)
private static readonly TimeZoneInfo EasternTz =
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
///
/// Ritorna true se il mercato relativo all'asset class indicata è
/// presumibilmente aperto adesso.
///
public static bool IsMarketOpen(string assetClass)
{
if (string.IsNullOrEmpty(assetClass))
return true;
var cls = assetClass.ToLowerInvariant();
// Crypto: 24/7
if (cls.Contains("crypto"))
return true;
// US Equity: Lun–Ven, orario NYSE
var nowEt = TimeZoneInfo.ConvertTime(DateTime.UtcNow, EasternTz);
if (nowEt.DayOfWeek == DayOfWeek.Saturday || nowEt.DayOfWeek == DayOfWeek.Sunday)
return false;
var tod = nowEt.TimeOfDay;
return tod >= MarketOpen && tod < MarketClose;
}
///
/// Testo descrittivo dello stato mercato per la UI.
///
public static string GetMarketStatusLabel(string assetClass)
{
if (string.IsNullOrEmpty(assetClass))
return "";
var cls = assetClass.ToLowerInvariant();
if (cls.Contains("crypto"))
return "Mercato 24/7 — sempre aperto";
var nowEt = TimeZoneInfo.ConvertTime(DateTime.UtcNow, EasternTz);
if (nowEt.DayOfWeek == DayOfWeek.Saturday || nowEt.DayOfWeek == DayOfWeek.Sunday)
return $"Mercato chiuso (weekend) — riapertura lunedì {MarketOpen:hh\\:mm} ET";
var tod = nowEt.TimeOfDay;
if (tod < MarketOpen)
return $"Mercato chiuso — apertura alle {MarketOpen:hh\\:mm} ET";
if (tod >= MarketClose)
return $"Mercato chiuso — riapertura domani {MarketOpen:hh\\:mm} ET";
var remaining = MarketClose - tod;
return $"Mercato aperto — chiude tra {(int)remaining.TotalHours}h {remaining.Minutes}m ET";
}
///
/// Colore suggerito per il badge stato mercato.
///
public static string GetMarketStatusColor(string assetClass)
=> IsMarketOpen(assetClass) ? "#00E676" : "#FFC107";
///
/// Secondi da attendere prima della prossima apertura (utile per sleep nel loop).
/// Ritorna 0 se il mercato è già aperto.
///
public static double SecondsUntilOpen(string assetClass)
{
if (IsMarketOpen(assetClass)) return 0;
var cls = (assetClass ?? "").ToLowerInvariant();
if (cls.Contains("crypto")) return 0;
var nowEt = TimeZoneInfo.ConvertTime(DateTime.UtcNow, EasternTz);
DateTime nextOpen;
if (nowEt.TimeOfDay < MarketOpen)
{
nextOpen = nowEt.Date + MarketOpen;
}
else
{
// Dopo la chiusura → prossimo giorno lavorativo
var candidate = nowEt.Date.AddDays(1) + MarketOpen;
while (candidate.DayOfWeek == DayOfWeek.Saturday || candidate.DayOfWeek == DayOfWeek.Sunday)
candidate = candidate.AddDays(1);
nextOpen = candidate;
}
var diff = nextOpen - nowEt;
return diff.TotalSeconds > 0 ? diff.TotalSeconds : 0;
}
}
}