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