Files
Encelado/DesktopBot/Services/MarketHoursService.cs
T
2026-06-09 18:29:41 +02:00

114 lines
4.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
namespace DesktopBot.Services
{
/// <summary>
/// Helper per determinare se un mercato è aperto in base all'asset class e all'orario locale.
///
/// Regole approssimative (senza chiamata API aggiuntiva):
/// - US Equity : LunVen 09:3016: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.
/// </summary>
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");
/// <summary>
/// Ritorna true se il mercato relativo all'asset class indicata è
/// presumibilmente aperto adesso.
/// </summary>
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: LunVen, 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;
}
/// <summary>
/// Testo descrittivo dello stato mercato per la UI.
/// </summary>
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";
}
/// <summary>
/// Colore suggerito per il badge stato mercato.
/// </summary>
public static string GetMarketStatusColor(string assetClass)
=> IsMarketOpen(assetClass) ? "#00E676" : "#FFC107";
/// <summary>
/// Secondi da attendere prima della prossima apertura (utile per sleep nel loop).
/// Ritorna 0 se il mercato è già aperto.
/// </summary>
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;
}
}
}