diff --git a/desktop/gold_order_app.py b/desktop/gold_order_app.py index 5c03a08..d633fa9 100644 --- a/desktop/gold_order_app.py +++ b/desktop/gold_order_app.py @@ -9,10 +9,12 @@ import threading import time import webbrowser import tkinter as tk -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from tkinter import messagebox, simpledialog, ttk from typing import Any, Optional, Tuple, Union +BJT = timezone(timedelta(hours=8)) + # 顶层导入,避免打包 exe 漏依赖 import numpy # noqa: F401 import MetaTrader5 as mt5 @@ -99,6 +101,7 @@ def _init_mt5_constants() -> None: def suggest_lot_by_balance(balance: float) -> float: + """按余额档位手数:≥10000→0.10;1000~10000→0.05;<1000→0.01(点按钮即可)。""" if balance >= BALANCE_HIGH: return LOT_HIGH if balance < BALANCE_LOW: @@ -106,6 +109,29 @@ def suggest_lot_by_balance(balance: float) -> float: return LOT_MID +def current_session_label(now: Optional[datetime] = None) -> str: + """北京时间盘段:亚盘/欧盘/美盘(可重叠显示)。""" + dt = now or datetime.now(BJT) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=BJT) + else: + dt = dt.astimezone(BJT) + h = dt.hour + dt.minute / 60.0 + asia = 8.0 <= h < 16.0 + euro = 15.0 <= h < 24.0 + us = h >= 20.0 or h < 5.0 + parts = [] + if asia: + parts.append("亚盘") + if euro: + parts.append("欧盘") + if us: + parts.append("美盘") + if not parts: + return "过渡时段" + return "+".join(parts) + + class TradeError(Exception): pass @@ -153,6 +179,31 @@ class Mt5Bridge: raise TradeError(f"无法读取品种信息 {symbol}") return info + def day_ohlc(self, symbol: str) -> dict[str, Any]: + """今日(服务器日线)开高低;用最新 tick 实时修正最高/最低。""" + self.ensure_symbol(symbol) + rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 1) + if rates is None or len(rates) < 1: + raise TradeError("无法读取今日日线,请确认品种有 D1 数据") + bar = rates[0] + open_px = float(bar["open"]) + high = float(bar["high"]) + low = float(bar["low"]) + open_ts = int(bar["time"]) + tick = mt5.symbol_info_tick(symbol) + if tick is not None: + bid = float(tick.bid) + ask = float(tick.ask) + high = max(high, bid, ask) + low = min(low, bid, ask) + return { + "open": open_px, + "high": high, + "low": low, + "open_time": datetime.fromtimestamp(open_ts), + "open_ts": open_ts, + } + def atr(self, symbol: str, period: int = ATR_PERIOD) -> float: self.ensure_symbol(symbol) rates = mt5.copy_rates_from_pos(symbol, ATR_TIMEFRAME, 1, period + 1) @@ -605,8 +656,8 @@ class OrderApp(tk.Tk): super().__init__() _init_mt5_constants() self.title(APP_TITLE) - self.geometry("1080x900") - self.minsize(980, 820) + self.geometry("1080x940") + self.minsize(980, 860) self.configure(bg="#1e1e1e") try: self.tk.call("tk", "scaling", 1.45) @@ -645,10 +696,29 @@ class OrderApp(tk.Tk): self.lbl_account = ttk.Label(head, text="余额 --", font=("Microsoft YaHei UI", 13, "bold")) self.lbl_account.pack(side=tk.LEFT, padx=12) - self.lbl_quote = ttk.Label(root, text="Bid -- Ask --", font=("Microsoft YaHei UI", 14, "bold")) - self.lbl_quote.pack(anchor="w", pady=(10, 0)) + quote_row = ttk.Frame(root) + quote_row.pack(fill=tk.X, pady=(10, 0)) + self.lbl_quote = ttk.Label(quote_row, text="Bid -- Ask --", font=("Microsoft YaHei UI", 14, "bold")) + self.lbl_quote.pack(side=tk.LEFT) + self.lbl_session = tk.Label( + quote_row, + text="时段 --", + bg="#2a3a55", + fg="#7ec8ff", + font=("Microsoft YaHei UI", 12, "bold"), + padx=10, + pady=2, + ) + self.lbl_session.pack(side=tk.RIGHT) + self.lbl_info = ttk.Label(root, text="点差 -- ATR -- SL$ -- TP$ -- 持仓 --", font=("Microsoft YaHei UI", 12)) self.lbl_info.pack(anchor="w", pady=(4, 2)) + self.lbl_day_ohlc = ttk.Label( + root, + text="开盘 -- | 今开 -- | 最高 -- | 最低 --", + font=("Microsoft YaHei UI", 12), + ) + self.lbl_day_ohlc.pack(anchor="w", pady=(2, 2)) today_row = ttk.Frame(root) today_row.pack(fill=tk.X, pady=(0, 12)) self.lbl_today_base = tk.Label( @@ -717,7 +787,11 @@ class OrderApp(tk.Tk): self._mk_btn(opt, "按资金手数", "#3a5a80", self.on_auto_lot, h=34, w=110).pack(side=tk.LEFT, padx=10) ttk.Label( opt, - text=f"同向加仓上限 {MAX_SIDE_LOTS:.2f} 手 · 最多同时 {MAX_OPEN_POSITIONS} 笔", + text=( + f"点一下按余额改手数:≥{BALANCE_HIGH:.0f}→{LOT_HIGH:.2f}," + f"{BALANCE_LOW:.0f}~→{LOT_MID:.2f},<{BALANCE_LOW:.0f}→{LOT_LOW:.2f};" + f"同向≤{MAX_SIDE_LOTS:.2f} · 最多{MAX_OPEN_POSITIONS}笔" + ), style="Status.TLabel", ).pack(side=tk.LEFT) @@ -1160,12 +1234,19 @@ class OrderApp(tk.Tk): self.refresh_quotes() def on_auto_lot(self) -> None: + """任意资金都可点:小资金(<1000)会设为 0.01 手。""" try: self.bridge.connect() bal = self.bridge.account_balance() lot = suggest_lot_by_balance(bal) self.var_lot.set(f"{lot:.2f}") - self.set_status(f"按资金设置手数:余额 {bal:.2f} → {lot:.2f}", ok=True) + if bal < BALANCE_LOW: + tip = f"小资金档:余额 {bal:.2f} → 手数 {lot:.2f}" + elif bal >= BALANCE_HIGH: + tip = f"大资金档:余额 {bal:.2f} → 手数 {lot:.2f}" + else: + tip = f"中资金档:余额 {bal:.2f} → 手数 {lot:.2f}" + self.set_status(tip, ok=True) except TradeError as exc: self.set_status(str(exc), ok=False) @@ -1231,12 +1312,24 @@ class OrderApp(tk.Tk): self.lbl_account.configure(text="余额 --") self.lbl_quote.configure(text=f"Bid {bid:.{digits}f} Ask {ask:.{digits}f}") + self.lbl_session.configure(text=f"时段 {current_session_label()}") self.lbl_info.configure( text=( f"点差 {spread:.{digits}f} ATRM15 {atr_txt} " f"SL${sl_txt} TP${tp_txt} 持仓 {pos} 浮盈 {profit:.2f}" ) ) + try: + day = self.bridge.day_ohlc(symbol) + ot = day["open_time"].strftime("%H:%M") + self.lbl_day_ohlc.configure( + text=( + f"开盘 {ot} | 今开 {day['open']:.{digits}f} | " + f"最高 {day['high']:.{digits}f} | 最低 {day['low']:.{digits}f}" + ) + ) + except Exception: + self.lbl_day_ohlc.configure(text="开盘 -- | 今开 -- | 最高 -- | 最低 --") try: self._refresh_today_stats(symbol) except Exception: diff --git a/releases/黄金市价辅助.exe b/releases/黄金市价辅助.exe index 1480fa8..b552903 100644 Binary files a/releases/黄金市价辅助.exe and b/releases/黄金市价辅助.exe differ