diff --git a/desktop/gold_order_app.py b/desktop/gold_order_app.py index 0df5031..9801093 100644 --- a/desktop/gold_order_app.py +++ b/desktop/gold_order_app.py @@ -118,15 +118,18 @@ def suggest_lot_by_balance(balance: float) -> float: 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: """北京时间盘段:亚盘/欧盘/美盘(可重叠显示);休市单独标注。""" if not market_open: return "休市" - dt = now or datetime.now(BJT) - if dt.tzinfo is None: - dt = dt.replace(tzinfo=BJT) - else: - dt = dt.astimezone(BJT) + dt = _as_bjt(now) h = dt.hour + dt.minute / 60.0 asia = 8.0 <= h < 16.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) -def format_open_elapsed(open_ts: float, market_open: bool) -> str: - """开盘后已过多久;休市时显示休市(仍可看下方 OHLC)。""" +# 黄金常见时段(一律按北京时间 UTC+8):周一 07:00 开 → 周六 05:00 收;日内约 05:00–07:00 +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: - 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)) days, rem = divmod(elapsed, 86400) hours, rem = divmod(rem, 3600) @@ -237,16 +305,20 @@ class Mt5Bridge: ask = float(tick.ask) high = max(high, 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 { "open": open_px, "high": high, "low": low, - "open_time": datetime.fromtimestamp(open_ts), + "open_time": open_dt, "open_ts": open_ts, "bar_day": bar_day, "market_open": 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: @@ -1460,8 +1532,12 @@ class OrderApp(tk.Tk): self.lbl_account.configure(text="余额 --") 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( - text=f"时段 {sess}", + text=sess_txt, bg="#3a2820" if not market_open else "#243044", 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}%)", COLOR_ACCENT, ) - ot = day["open_time"].strftime("%m-%d %H:%M") - tag = "交易中" if day["market_open"] else "休市 · 显示最近交易日" - self.lbl_day_ohlc.configure(text=f"{tag} · 日线开盘 {ot}") + ot = fmt_bjt(day["open_time"]) + if day["market_open"]: + 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: - 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("high", "--") self._set_metric("low", "--") @@ -1531,8 +1614,16 @@ class OrderApp(tk.Tk): f"{amp:.{digits}f} ({amp_pct:.2f}%)", COLOR_ACCENT, ) - self.lbl_session.configure(text="时段 休市", bg="#3a2820", fg=COLOR_WARN) - self.lbl_day_ohlc.configure(text="休市 · 显示最近交易日日线") + self.lbl_session.configure( + 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: pass except Exception: diff --git a/releases/黄金市价辅助.exe b/releases/黄金市价辅助.exe index f0c3bb3..3b92ea1 100644 Binary files a/releases/黄金市价辅助.exe and b/releases/黄金市价辅助.exe differ