mirror of
https://github.com/VGEAREN/Stockbar.git
synced 2026-10-06 14:43:34 +08:00
v1.2.0: Add US overnight session real-time prices via Pyth/TradingView API
- Add PythService.swift: fetch overnight prices from us-overnight-stock-api.vercel.app - Restore "夜盘" session in usMarketSession() (ET 20:00-04:00) - Parallel fetch Sina + Pyth during overnight, merge once to avoid UI flicker - Clear stale after-hours extendedPrice before applying overnight data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
72103bd69f
commit
94b14693d5
@@ -0,0 +1,40 @@
|
||||
import Foundation
|
||||
|
||||
/// 美股夜盘 (ET 20:00–04:00) 实时报价
|
||||
/// 数据源:Pyth Network / TradingView BOATS(via Vercel API)
|
||||
final class PythService {
|
||||
|
||||
private static let baseURL = "https://us-overnight-stock-api.vercel.app/api"
|
||||
|
||||
/// 批量获取美股夜盘价格,返回 [stockCode: overnightPrice]
|
||||
/// stockCodes 格式如 ["usr_aapl", "usr_tsla"]
|
||||
static func fetchOvernightPrices(codes: [String]) async -> [String: Double] {
|
||||
var result: [String: Double] = [:]
|
||||
|
||||
await withTaskGroup(of: (String, Double?).self) { group in
|
||||
for code in codes {
|
||||
group.addTask {
|
||||
let symbol = code.replacingOccurrences(of: "usr_", with: "").uppercased()
|
||||
let price = await fetchPrice(symbol: symbol)
|
||||
return (code, price)
|
||||
}
|
||||
}
|
||||
for await (code, price) in group {
|
||||
if let p = price {
|
||||
result[code] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// 获取单只股票夜盘价格
|
||||
private static func fetchPrice(symbol: String) async -> Double? {
|
||||
guard let url = URL(string: "\(baseURL)?symbol=\(symbol)&session=overnight") else { return nil }
|
||||
guard let (data, _) = try? await URLSession.shared.data(from: url),
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let price = json["price"] as? Double,
|
||||
price > 0 else { return nil }
|
||||
return price
|
||||
}
|
||||
}
|
||||
@@ -161,12 +161,29 @@ final class AppState: ObservableObject {
|
||||
let all = stocks.map(\.id)
|
||||
let sinaCodes = all.filter { !$0.hasPrefix("hk") }
|
||||
let hkCodes = all.filter { $0.hasPrefix("hk") }
|
||||
async let sinaResult = DataService.fetchSinaQuotes(codes: sinaCodes)
|
||||
async let hkResult = DataService.fetchTencentHKQuotes(codes: hkCodes)
|
||||
async let ratesResult = CurrencyService.fetchRates()
|
||||
|
||||
let isOvernight = Self.usMarketSession() == "夜盘"
|
||||
let usCodes = isOvernight ? all.filter { $0.hasPrefix("usr_") } : []
|
||||
|
||||
// 所有数据源并行请求
|
||||
async let sinaResult = DataService.fetchSinaQuotes(codes: sinaCodes)
|
||||
async let hkResult = DataService.fetchTencentHKQuotes(codes: hkCodes)
|
||||
async let ratesResult = CurrencyService.fetchRates()
|
||||
async let overnightResult = PythService.fetchOvernightPrices(codes: usCodes)
|
||||
|
||||
let (s, h) = try await (sinaResult, hkResult)
|
||||
let rates = await ratesResult
|
||||
quotes.merge(s) { $1 }
|
||||
let rates = await ratesResult
|
||||
let overnight = await overnightResult
|
||||
|
||||
// 夜盘时段:清除新浪的盘后旧价格,用夜盘实时价替换
|
||||
var merged = s
|
||||
if isOvernight {
|
||||
for key in merged.keys where key.hasPrefix("usr_") {
|
||||
merged[key]?.extendedPrice = overnight[key]
|
||||
}
|
||||
}
|
||||
|
||||
quotes.merge(merged) { $1 }
|
||||
quotes.merge(h) { $1 }
|
||||
exchangeRates = rates
|
||||
lastUpdateTime = Date()
|
||||
@@ -261,12 +278,24 @@ final class AppState: ObservableObject {
|
||||
let comps = cal.dateComponents([.weekday, .hour, .minute], from: now)
|
||||
let weekday = comps.weekday ?? 1
|
||||
let minutes = (comps.hour ?? 0) * 60 + (comps.minute ?? 0)
|
||||
guard weekday >= 2, weekday <= 6 else { return nil }
|
||||
switch minutes {
|
||||
case 240..<570: return "盘前" // ET 04:00–09:30
|
||||
case 570..<960: return "盘中" // ET 09:30–16:00
|
||||
case 960..<1200: return "盘后" // ET 16:00–20:00
|
||||
default: return nil // 夜盘无数据源,不显示
|
||||
case 0..<240:
|
||||
guard weekday >= 3, weekday <= 7 else { return nil }
|
||||
return "夜盘" // ET 00:00–04:00(跨日,周二夜~周六凌晨)
|
||||
case 240..<570:
|
||||
guard weekday >= 2, weekday <= 6 else { return nil }
|
||||
return "盘前" // ET 04:00–09:30
|
||||
case 570..<960:
|
||||
guard weekday >= 2, weekday <= 6 else { return nil }
|
||||
return "盘中" // ET 09:30–16:00
|
||||
case 960..<1200:
|
||||
guard weekday >= 2, weekday <= 6 else { return nil }
|
||||
return "盘后" // ET 16:00–20:00
|
||||
case 1200..<1440:
|
||||
guard weekday >= 2, weekday <= 6 else { return nil }
|
||||
return "夜盘" // ET 20:00–24:00
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user