mirror of
https://github.com/carey314/mio-plugin-fund.git
synced 2026-08-10 05:44:31 +00:00
88 lines
3.5 KiB
Swift
88 lines
3.5 KiB
Swift
|
|
//
|
|||
|
|
// RefreshScheduler.swift
|
|||
|
|
// 盯基金 plugin
|
|||
|
|
//
|
|||
|
|
// Drives the refresh cadence based on what's tradeable right now.
|
|||
|
|
// Different things have different refresh budgets:
|
|||
|
|
//
|
|||
|
|
// Tradeable now Cadence
|
|||
|
|
// ─────────────────────────────────
|
|||
|
|
// A-share funds (intraday) 60 s (estimate API is ~1 min granularity)
|
|||
|
|
// A-share funds (closed) 30 min (just to pick up newly published NAV)
|
|||
|
|
// Foreign gold (24/5) 30 s (Sina updates GC/XAU continuously)
|
|||
|
|
// Domestic gold 30 s while SHFE open
|
|||
|
|
//
|
|||
|
|
// Trade hours (Asia/Shanghai):
|
|||
|
|
// A-share: 09:30–11:30, 13:00–15:00, Mon–Fri
|
|||
|
|
// SHFE day: 09:00–10:15, 10:30–11:30, 13:30–15:00
|
|||
|
|
// SHFE night: 21:00–02:30
|
|||
|
|
// COMEX gold: 06:00–05:00 next day (essentially 23h, 5 days/week)
|
|||
|
|
// LBMA London: rolls with COMEX in practice
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
import Foundation
|
|||
|
|
|
|||
|
|
@MainActor
|
|||
|
|
final class RefreshScheduler {
|
|||
|
|
|
|||
|
|
enum Tier {
|
|||
|
|
/// Funds intraday cadence.
|
|||
|
|
case fundsActive
|
|||
|
|
/// Funds idle cadence — only listening for end-of-day NAV publish.
|
|||
|
|
case fundsIdle
|
|||
|
|
/// Realtime gold tier (always on, foreign markets virtually 24/7).
|
|||
|
|
case gold
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Returns refresh interval (seconds) for a tier at the given moment.
|
|||
|
|
func interval(for tier: Tier, at now: Date = Date()) -> TimeInterval {
|
|||
|
|
switch tier {
|
|||
|
|
case .fundsActive:
|
|||
|
|
return isAShareTradeHour(now) ? 60 : 30 * 60
|
|||
|
|
case .fundsIdle:
|
|||
|
|
return 30 * 60
|
|||
|
|
case .gold:
|
|||
|
|
// Always 30s — foreign gold trades virtually 24/7. SHFE has
|
|||
|
|
// closed windows but we don't bother backing off; the request
|
|||
|
|
// is cheap and Sina caches the latest tick anyway.
|
|||
|
|
return 30
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// True iff `now` falls inside an A-share trading session
|
|||
|
|
/// (09:30–11:30, 13:00–15:00 China time, Mon–Fri, no holiday awareness yet).
|
|||
|
|
func isAShareTradeHour(_ now: Date = Date()) -> Bool {
|
|||
|
|
var cal = Calendar(identifier: .gregorian)
|
|||
|
|
cal.timeZone = TimeZone(identifier: "Asia/Shanghai")!
|
|||
|
|
let weekday = cal.component(.weekday, from: now) // 1=Sun … 7=Sat
|
|||
|
|
guard (2...6).contains(weekday) else { return false }
|
|||
|
|
let hm = cal.component(.hour, from: now) * 60 + cal.component(.minute, from: now)
|
|||
|
|
// 09:30–11:30
|
|||
|
|
if hm >= 9 * 60 + 30 && hm < 11 * 60 + 30 { return true }
|
|||
|
|
// 13:00–15:00
|
|||
|
|
if hm >= 13 * 60 && hm < 15 * 60 { return true }
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// True iff SHFE gold is in a trading window (loose — no holiday).
|
|||
|
|
func isSHFETradeHour(_ now: Date = Date()) -> Bool {
|
|||
|
|
var cal = Calendar(identifier: .gregorian)
|
|||
|
|
cal.timeZone = TimeZone(identifier: "Asia/Shanghai")!
|
|||
|
|
let weekday = cal.component(.weekday, from: now)
|
|||
|
|
// Day session also runs Mon-Fri; night session technically can
|
|||
|
|
// span into Sat early morning but we keep it simple.
|
|||
|
|
guard (2...6).contains(weekday) else { return false }
|
|||
|
|
let hm = cal.component(.hour, from: now) * 60 + cal.component(.minute, from: now)
|
|||
|
|
// 09:00-10:15
|
|||
|
|
if hm >= 9 * 60 && hm < 10 * 60 + 15 { return true }
|
|||
|
|
// 10:30-11:30
|
|||
|
|
if hm >= 10 * 60 + 30 && hm < 11 * 60 + 30 { return true }
|
|||
|
|
// 13:30-15:00
|
|||
|
|
if hm >= 13 * 60 + 30 && hm < 15 * 60 { return true }
|
|||
|
|
// 21:00-23:59 + 00:00-02:30
|
|||
|
|
if hm >= 21 * 60 { return true }
|
|||
|
|
if hm < 2 * 60 + 30 { return true }
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|