Use Beijing time for next-open countdown and day open labels.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-17 22:02:41 +08:00
parent 4aad197d46
commit bf5d29ba55
2 changed files with 108 additions and 17 deletions
+108 -17
View File
@@ -118,15 +118,18 @@ def suggest_lot_by_balance(balance: float) -> float:
return LOT_MID return LOT_MID
def _as_bjt(now: Optional[datetime] = None) -> datetime:
dt = now or datetime.now(BJT)
if dt.tzinfo is None:
return dt.replace(tzinfo=BJT)
return dt.astimezone(BJT)
def current_session_label(now: Optional[datetime] = None, market_open: bool = True) -> str: def current_session_label(now: Optional[datetime] = None, market_open: bool = True) -> str:
"""北京时间盘段:亚盘/欧盘/美盘(可重叠显示);休市单独标注。""" """北京时间盘段:亚盘/欧盘/美盘(可重叠显示);休市单独标注。"""
if not market_open: if not market_open:
return "休市" return "休市"
dt = now or datetime.now(BJT) dt = _as_bjt(now)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=BJT)
else:
dt = dt.astimezone(BJT)
h = dt.hour + dt.minute / 60.0 h = dt.hour + dt.minute / 60.0
asia = 8.0 <= h < 16.0 asia = 8.0 <= h < 16.0
euro = 15.0 <= h < 24.0 euro = 15.0 <= h < 24.0
@@ -143,10 +146,75 @@ def current_session_label(now: Optional[datetime] = None, market_open: bool = Tr
return "+".join(parts) return "+".join(parts)
def format_open_elapsed(open_ts: float, market_open: bool) -> str: # 黄金常见时段(一律按北京时间 UTC+8):周一 07:00 开 → 周六 05:00 收;日内约 05:00–07:00
"""开盘后已过多久;休市时显示休市(仍可看下方 OHLC)。""" GOLD_OPEN_HOUR = 7
GOLD_OPEN_MINUTE = 0
GOLD_CLOSE_HOUR = 5
GOLD_CLOSE_MINUTE = 0
def fmt_bjt(dt: datetime, fmt: str = "%m-%d %H:%M") -> str:
"""格式化为北京时间字符串(不依赖系统时区)。"""
return _as_bjt(dt).strftime(fmt)
def next_market_open(now: Optional[datetime] = None) -> datetime:
"""估算下一次开盘时刻,返回带 BJT 时区的 datetime。"""
dt = _as_bjt(now)
open_hm = (GOLD_OPEN_HOUR, GOLD_OPEN_MINUTE, 0)
close_hm = (GOLD_CLOSE_HOUR, GOLD_CLOSE_MINUTE, 0)
t = (dt.hour, dt.minute, dt.second)
wd = dt.weekday() # Mon=0 … Sun=6
# 周末休市:周六 05:00(北京)→ 下周一 07:00(北京)
if wd == 5 and t >= close_hm:
days = 2
elif wd == 6:
days = 1
elif wd == 0 and t < open_hm:
days = 0
# 工作日日内休市窗口 05:00–07:00(北京)
elif wd <= 4 and close_hm <= t < open_hm:
days = 0
else:
# 非预期休市(节假日等):推到下一个可交易日的北京时间 07:00
days = 1
while True:
cand_date = (dt + timedelta(days=days)).date()
cwd = datetime(cand_date.year, cand_date.month, cand_date.day, tzinfo=BJT).weekday()
if cwd <= 4:
break
days += 1
if days > 10:
break
target = (dt + timedelta(days=days)).date()
return datetime(
target.year, target.month, target.day,
GOLD_OPEN_HOUR, GOLD_OPEN_MINUTE, 0, tzinfo=BJT,
)
def format_countdown(seconds: int) -> str:
seconds = max(0, int(seconds))
days, rem = divmod(seconds, 86400)
hours, rem = divmod(rem, 3600)
mins, secs = divmod(rem, 60)
if days > 0:
return f"距开盘 {days}天{hours}小时{mins}分"
if hours > 0:
return f"距开盘 {hours}小时{mins}分"
if mins > 0:
return f"距开盘 {mins}分{secs}秒"
return f"距开盘 {secs}秒"
def format_open_elapsed(open_ts: float, market_open: bool, now: Optional[datetime] = None) -> str:
"""开盘后已过多久;休市时显示距下次开盘倒计时。"""
if not market_open: if not market_open:
return "休市" nxt = next_market_open(now)
left = max(0, int((nxt - _as_bjt(now)).total_seconds()))
return format_countdown(left)
elapsed = max(0, int(time.time() - open_ts)) elapsed = max(0, int(time.time() - open_ts))
days, rem = divmod(elapsed, 86400) days, rem = divmod(elapsed, 86400)
hours, rem = divmod(rem, 3600) hours, rem = divmod(rem, 3600)
@@ -237,16 +305,20 @@ class Mt5Bridge:
ask = float(tick.ask) ask = float(tick.ask)
high = max(high, bid, ask) high = max(high, bid, ask)
low = min(low, bid, ask) low = min(low, bid, ask)
bar_day = datetime.fromtimestamp(open_ts).strftime("%m-%d") open_dt = datetime.fromtimestamp(open_ts, BJT)
bar_day = open_dt.strftime("%m-%d")
nxt = None if market_open else next_market_open()
return { return {
"open": open_px, "open": open_px,
"high": high, "high": high,
"low": low, "low": low,
"open_time": datetime.fromtimestamp(open_ts), "open_time": open_dt,
"open_ts": open_ts, "open_ts": open_ts,
"bar_day": bar_day, "bar_day": bar_day,
"market_open": market_open, "market_open": market_open,
"elapsed_text": format_open_elapsed(float(open_ts), market_open), "elapsed_text": format_open_elapsed(float(open_ts), market_open),
"next_open": nxt,
"next_open_text": fmt_bjt(nxt) if nxt else "",
} }
def atr(self, symbol: str, period: int = ATR_PERIOD) -> float: def atr(self, symbol: str, period: int = ATR_PERIOD) -> float:
@@ -1460,8 +1532,12 @@ class OrderApp(tk.Tk):
self.lbl_account.configure(text="余额 --") self.lbl_account.configure(text="余额 --")
sess = current_session_label(market_open=market_open) sess = current_session_label(market_open=market_open)
if not market_open:
sess_txt = f"休市 · {format_open_elapsed(0, False)}"
else:
sess_txt = f"时段 {sess}"
self.lbl_session.configure( self.lbl_session.configure(
text=f"时段 {sess}", text=sess_txt,
bg="#3a2820" if not market_open else "#243044", bg="#3a2820" if not market_open else "#243044",
fg=COLOR_WARN if not market_open else COLOR_ACCENT, fg=COLOR_WARN if not market_open else COLOR_ACCENT,
) )
@@ -1493,11 +1569,18 @@ class OrderApp(tk.Tk):
f"{amp:.{digits}f} ({amp_pct:.2f}%)", f"{amp:.{digits}f} ({amp_pct:.2f}%)",
COLOR_ACCENT, COLOR_ACCENT,
) )
ot = day["open_time"].strftime("%m-%d %H:%M") ot = fmt_bjt(day["open_time"])
tag = "交易中" if day["market_open"] else "休市 · 显示最近交易日" if day["market_open"]:
self.lbl_day_ohlc.configure(text=f"{tag} · 日线开盘 {ot}") tag = "交易中"
else:
nxt_txt = day.get("next_open_text") or ""
tag = f"休市 · {day['elapsed_text']}"
if nxt_txt:
tag += f"(预计开盘 {nxt_txt} 北京时间)"
self.lbl_day_ohlc.configure(text=f"{tag} · 日线开盘 {ot} 北京时间")
except Exception: except Exception:
self._set_metric("elapsed", "休市" if not market_open else "--", COLOR_WARN) left = format_open_elapsed(0, False) if not market_open else "--"
self._set_metric("elapsed", left, COLOR_WARN)
self._set_metric("open", "--") self._set_metric("open", "--")
self._set_metric("high", "--") self._set_metric("high", "--")
self._set_metric("low", "--") self._set_metric("low", "--")
@@ -1531,8 +1614,16 @@ class OrderApp(tk.Tk):
f"{amp:.{digits}f} ({amp_pct:.2f}%)", f"{amp:.{digits}f} ({amp_pct:.2f}%)",
COLOR_ACCENT, COLOR_ACCENT,
) )
self.lbl_session.configure(text="时段 休市", bg="#3a2820", fg=COLOR_WARN) self.lbl_session.configure(
self.lbl_day_ohlc.configure(text="休市 · 显示最近交易日日线") text=f"休市 · {day['elapsed_text']}",
bg="#3a2820",
fg=COLOR_WARN,
)
nxt = day.get("next_open_text") or ""
tip = f"休市 · {day['elapsed_text']}"
if nxt:
tip += f"(预计开盘 {nxt} 北京时间)"
self.lbl_day_ohlc.configure(text=f"{tip} · 显示最近交易日日线")
except Exception: except Exception:
pass pass
except Exception: except Exception:
Binary file not shown.