Polish order UI: elapsed since open, show OHLC when closed, card layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-17 21:29:35 +08:00
parent 1a641f58bc
commit a70d8656e7
2 changed files with 258 additions and 121 deletions
+250 -113
View File
@@ -65,6 +65,12 @@ COLOR_BUY = "#108040"
COLOR_SELL = "#a02828" COLOR_SELL = "#a02828"
COLOR_BTN_OFF = "#555555" COLOR_BTN_OFF = "#555555"
COLOR_WARN = "#e6a23c" COLOR_WARN = "#e6a23c"
COLOR_BG = "#121218"
COLOR_CARD = "#1a1a22"
COLOR_CARD2 = "#22222e"
COLOR_MUTED = "#8a8a9c"
COLOR_TEXT = "#f0f0f5"
COLOR_ACCENT = "#4cc2ff"
BE_MIN_MOVE_USD = 3.0 # 价格向有利方向走出该美金距离才可保本(非账户浮盈) BE_MIN_MOVE_USD = 3.0 # 价格向有利方向走出该美金距离才可保本(非账户浮盈)
BE_OFFSET_USD = 1.0 # 保本止损 = 开仓价 ± 1$ BE_OFFSET_USD = 1.0 # 保本止损 = 开仓价 ± 1$
@@ -109,8 +115,10 @@ def suggest_lot_by_balance(balance: float) -> float:
return LOT_MID return LOT_MID
def current_session_label(now: Optional[datetime] = None) -> str: def current_session_label(now: Optional[datetime] = None, market_open: bool = True) -> str:
"""北京时间盘段:亚盘/欧盘/美盘(可重叠显示)。""" """北京时间盘段:亚盘/欧盘/美盘(可重叠显示);休市单独标注。"""
if not market_open:
return "休市"
dt = now or datetime.now(BJT) dt = now or datetime.now(BJT)
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=BJT) dt = dt.replace(tzinfo=BJT)
@@ -132,6 +140,21 @@ def current_session_label(now: Optional[datetime] = None) -> str:
return "+".join(parts) return "+".join(parts)
def format_open_elapsed(open_ts: float, market_open: bool) -> str:
"""开盘后已过多久;休市时显示休市(仍可看下方 OHLC)。"""
if not market_open:
return "休市"
elapsed = max(0, int(time.time() - open_ts))
days, rem = divmod(elapsed, 86400)
hours, rem = divmod(rem, 3600)
mins, _ = divmod(rem, 60)
if days > 0:
return f"已开盘 {days}天{hours}小时{mins}分"
if hours > 0:
return f"已开盘 {hours}小时{mins}分"
return f"已开盘 {mins}分"
class TradeError(Exception): class TradeError(Exception):
pass pass
@@ -179,29 +202,48 @@ class Mt5Bridge:
raise TradeError(f"无法读取品种信息 {symbol}") raise TradeError(f"无法读取品种信息 {symbol}")
return info return info
def day_ohlc(self, symbol: str) -> dict[str, Any]: def market_is_open(self, symbol: str) -> bool:
"""今日(服务器日线)开高低;用最新 tick 实时修正最高/最低。""" """根据最新 tick 是否新鲜判断是否在交易(休市也仍可读日线)。"""
self.ensure_symbol(symbol) self.ensure_symbol(symbol)
rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 1) info = mt5.symbol_info(symbol)
if info is not None and hasattr(info, "trade_mode"):
# 完全禁止交易
if int(info.trade_mode) == 0:
return False
tick = mt5.symbol_info_tick(symbol)
if tick is None:
return False
age = time.time() - float(tick.time)
return age < 180 # 3 分钟内有报价视为开市
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, 2)
if rates is None or len(rates) < 1: if rates is None or len(rates) < 1:
raise TradeError("无法读取今日日线,请确认品种有 D1 数据") raise TradeError("无法读取日线,请确认品种有 D1 数据")
bar = rates[0] bar = rates[0]
open_px = float(bar["open"]) open_px = float(bar["open"])
high = float(bar["high"]) high = float(bar["high"])
low = float(bar["low"]) low = float(bar["low"])
open_ts = int(bar["time"]) open_ts = int(bar["time"])
market_open = self.market_is_open(symbol)
tick = mt5.symbol_info_tick(symbol) tick = mt5.symbol_info_tick(symbol)
if tick is not None: if market_open and tick is not None:
bid = float(tick.bid) bid = float(tick.bid)
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")
return { return {
"open": open_px, "open": open_px,
"high": high, "high": high,
"low": low, "low": low,
"open_time": datetime.fromtimestamp(open_ts), "open_time": datetime.fromtimestamp(open_ts),
"open_ts": open_ts, "open_ts": open_ts,
"bar_day": bar_day,
"market_open": market_open,
"elapsed_text": format_open_elapsed(float(open_ts), market_open),
} }
def atr(self, symbol: str, period: int = ATR_PERIOD) -> float: def atr(self, symbol: str, period: int = ATR_PERIOD) -> float:
@@ -656,9 +698,9 @@ class OrderApp(tk.Tk):
super().__init__() super().__init__()
_init_mt5_constants() _init_mt5_constants()
self.title(APP_TITLE) self.title(APP_TITLE)
self.geometry("1080x940") self.geometry("1100x980")
self.minsize(980, 860) self.minsize(1000, 900)
self.configure(bg="#1e1e1e") self.configure(bg=COLOR_BG)
try: try:
self.tk.call("tk", "scaling", 1.45) self.tk.call("tk", "scaling", 1.45)
except tk.TclError: except tk.TclError:
@@ -670,113 +712,165 @@ class OrderApp(tk.Tk):
self._be_done_tickets: set = set() # 已拉过保本的单号,不再判断 self._be_done_tickets: set = set() # 已拉过保本的单号,不再判断
self._pos_sig = None self._pos_sig = None
self._pos_profit_labels: dict = {} self._pos_profit_labels: dict = {}
self._metric_values: dict = {}
self._build_ui() self._build_ui()
self.after(200, self._try_connect_and_refresh) self.after(200, self._try_connect_and_refresh)
self.after(3000, self._hub_heartbeat) self.after(3000, self._hub_heartbeat)
def _card(self, parent: tk.Misc, pad: int = 12) -> tk.Frame:
outer = tk.Frame(parent, bg=COLOR_BG)
card = tk.Frame(
outer,
bg=COLOR_CARD,
highlightthickness=1,
highlightbackground="#2a2a38",
highlightcolor="#2a2a38",
)
card.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
inner = tk.Frame(card, bg=COLOR_CARD)
inner.pack(fill=tk.BOTH, expand=True, padx=pad, pady=pad)
outer._inner = inner # type: ignore[attr-defined]
return outer
def _metric_box(self, parent: tk.Misc, key: str, title: str) -> None:
box = tk.Frame(parent, bg=COLOR_CARD2, highlightthickness=0)
box.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=4)
tk.Label(
box, text=title, bg=COLOR_CARD2, fg=COLOR_MUTED,
font=("Microsoft YaHei UI", 10),
).pack(anchor="w", padx=10, pady=(8, 0))
val = tk.Label(
box, text="--", bg=COLOR_CARD2, fg=COLOR_TEXT,
font=("Microsoft YaHei UI", 13, "bold"),
)
val.pack(anchor="w", padx=10, pady=(2, 8))
self._metric_values[key] = val
def _build_ui(self) -> None: def _build_ui(self) -> None:
style = ttk.Style(self) style = ttk.Style(self)
try: try:
style.theme_use("clam") style.theme_use("clam")
except tk.TclError: except tk.TclError:
pass pass
style.configure("TLabel", background="#1e1e1e", foreground="#f0f0f0", font=("Microsoft YaHei UI", 12)) style.configure("TLabel", background=COLOR_BG, foreground=COLOR_TEXT, font=("Microsoft YaHei UI", 12))
style.configure("Status.TLabel", background="#1e1e1e", foreground="#c0c0c0", font=("Microsoft YaHei UI", 11)) style.configure("Status.TLabel", background=COLOR_BG, foreground=COLOR_MUTED, font=("Microsoft YaHei UI", 11))
style.configure("TFrame", background="#1e1e1e") style.configure("TFrame", background=COLOR_BG)
style.configure("TEntry", fieldbackground="#2b2b2b", foreground="#ffffff", font=("Microsoft YaHei UI", 12)) style.configure("Card.TFrame", background=COLOR_CARD)
style.configure("TCheckbutton", background="#1e1e1e", foreground="#f0f0f0", font=("Microsoft YaHei UI", 11)) style.configure("TEntry", fieldbackground="#2a2a36", foreground="#ffffff", font=("Microsoft YaHei UI", 12))
root = ttk.Frame(self, padding=18) style.configure("TCheckbutton", background=COLOR_CARD, foreground=COLOR_TEXT, font=("Microsoft YaHei UI", 11))
root.pack(fill=tk.BOTH, expand=True) style.map("TCheckbutton", background=[("active", COLOR_CARD)])
head = ttk.Frame(root) root = tk.Frame(self, bg=COLOR_BG)
head.pack(fill=tk.X) root.pack(fill=tk.BOTH, expand=True, padx=16, pady=14)
ttk.Label(head, text="品种", font=("Microsoft YaHei UI", 13, "bold")).pack(side=tk.LEFT)
# —— 顶栏 ——
head_outer = self._card(root, pad=12)
head_outer.pack(fill=tk.X, pady=(0, 10))
head = head_outer._inner # type: ignore[attr-defined]
left = tk.Frame(head, bg=COLOR_CARD)
left.pack(side=tk.LEFT)
tk.Label(left, text=APP_TITLE, bg=COLOR_CARD, fg=COLOR_ACCENT,
font=("Microsoft YaHei UI", 14, "bold")).pack(side=tk.LEFT)
tk.Label(left, text=" 品种", bg=COLOR_CARD, fg=COLOR_MUTED,
font=("Microsoft YaHei UI", 11)).pack(side=tk.LEFT, padx=(16, 4))
self.var_symbol = tk.StringVar(value=DEFAULT_SYMBOL) self.var_symbol = tk.StringVar(value=DEFAULT_SYMBOL)
ttk.Entry(head, textvariable=self.var_symbol, width=16).pack(side=tk.LEFT, padx=8) ttk.Entry(left, textvariable=self.var_symbol, width=14).pack(side=tk.LEFT)
self.lbl_account = ttk.Label(head, text="余额 --", font=("Microsoft YaHei UI", 13, "bold")) self.lbl_account = tk.Label(
self.lbl_account.pack(side=tk.LEFT, padx=12) left, text="余额 --", bg=COLOR_CARD, fg=COLOR_TEXT,
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"), font=("Microsoft YaHei UI", 12, "bold"),
padx=10, )
pady=2, self.lbl_account.pack(side=tk.LEFT, padx=16)
self.lbl_session = tk.Label(
head, text="时段 --", bg="#243044", fg=COLOR_ACCENT,
font=("Microsoft YaHei UI", 11, "bold"), padx=12, pady=4,
) )
self.lbl_session.pack(side=tk.RIGHT) 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)) quote_outer = self._card(root, pad=12)
self.lbl_day_ohlc = ttk.Label( quote_outer.pack(fill=tk.X, pady=(0, 10))
root, q = quote_outer._inner # type: ignore[attr-defined]
text="开盘 -- | 今开 -- | 最高 -- | 最低 --", self.lbl_quote = tk.Label(
font=("Microsoft YaHei UI", 12), q, text="Bid -- Ask --", bg=COLOR_CARD, fg=COLOR_TEXT,
font=("Microsoft YaHei UI", 18, "bold"),
) )
self.lbl_day_ohlc.pack(anchor="w", pady=(2, 2)) self.lbl_quote.pack(anchor="w")
today_row = ttk.Frame(root) self.lbl_info = tk.Label(
today_row.pack(fill=tk.X, pady=(0, 12)) q, text="点差 -- ATR -- SL$ -- TP$ -- 持仓 --",
bg=COLOR_CARD, fg=COLOR_MUTED, font=("Microsoft YaHei UI", 11),
)
self.lbl_info.pack(anchor="w", pady=(4, 10))
metrics = tk.Frame(q, bg=COLOR_CARD)
metrics.pack(fill=tk.X)
self._metric_box(metrics, "elapsed", "开盘时长")
self._metric_box(metrics, "open", "开盘价")
self._metric_box(metrics, "high", "最高价")
self._metric_box(metrics, "low", "最低价")
self.lbl_day_ohlc = tk.Label(
q, text="", bg=COLOR_CARD, fg=COLOR_MUTED, font=("Microsoft YaHei UI", 10),
)
self.lbl_day_ohlc.pack(anchor="w", pady=(8, 0))
# —— 今日战绩 ——
today_outer = self._card(root, pad=10)
today_outer.pack(fill=tk.X, pady=(0, 10))
today_row = today_outer._inner # type: ignore[attr-defined]
self.lbl_today_base = tk.Label( self.lbl_today_base = tk.Label(
today_row, text="今日开单 -- 次 | ", bg="#1e1e1e", fg=COLOR_NEUTRAL, today_row, text="今日开单 -- 次", bg=COLOR_CARD, fg=COLOR_MUTED,
font=("Microsoft YaHei UI", 11), font=("Microsoft YaHei UI", 11),
) )
self.lbl_today_base.pack(side=tk.LEFT) self.lbl_today_base.pack(side=tk.LEFT)
self.lbl_today_closed = tk.Label( self.lbl_today_closed = tk.Label(
today_row, text="已平 --", bg="#1e1e1e", fg=COLOR_NEUTRAL, today_row, text=" 已平 --", bg=COLOR_CARD, fg=COLOR_NEUTRAL,
font=("Microsoft YaHei UI", 11, "bold"), font=("Microsoft YaHei UI", 11, "bold"),
) )
self.lbl_today_closed.pack(side=tk.LEFT) self.lbl_today_closed.pack(side=tk.LEFT)
self.lbl_today_mid = tk.Label( self.lbl_today_mid = tk.Label(
today_row, text=" | 浮盈 -- | 合计 ", bg="#1e1e1e", fg=COLOR_NEUTRAL, today_row, text=" 浮盈 -- 合计 ", bg=COLOR_CARD, fg=COLOR_MUTED,
font=("Microsoft YaHei UI", 11), font=("Microsoft YaHei UI", 11),
) )
self.lbl_today_mid.pack(side=tk.LEFT) self.lbl_today_mid.pack(side=tk.LEFT)
self.lbl_today_total = tk.Label( self.lbl_today_total = tk.Label(
today_row, text="--", bg="#1e1e1e", fg=COLOR_NEUTRAL, today_row, text="--", bg=COLOR_CARD, fg=COLOR_NEUTRAL,
font=("Microsoft YaHei UI", 11, "bold"), font=("Microsoft YaHei UI", 11, "bold"),
) )
self.lbl_today_total.pack(side=tk.LEFT) self.lbl_today_total.pack(side=tk.LEFT)
self.lbl_today_ratio = tk.Label( self.lbl_today_ratio = tk.Label(
today_row, text=" | 盈亏比 -- | 盈利率 --", bg="#1e1e1e", fg=COLOR_NEUTRAL, today_row, text=" 盈亏比 -- 盈利率 --", bg=COLOR_CARD, fg=COLOR_MUTED,
font=("Microsoft YaHei UI", 11, "bold"), font=("Microsoft YaHei UI", 11, "bold"),
) )
self.lbl_today_ratio.pack(side=tk.LEFT) self.lbl_today_ratio.pack(side=tk.LEFT)
self.lbl_day_lock = tk.Label( self.lbl_day_lock = tk.Label(
today_row, today_row, text="", bg=COLOR_CARD, fg=COLOR_WARN,
text="", font=("Microsoft YaHei UI", 11, "bold"), padx=8, pady=2,
bg="#1e1e1e",
fg=COLOR_WARN,
font=("Microsoft YaHei UI", 11, "bold"),
padx=8,
pady=2,
) )
self.lbl_day_lock.pack(side=tk.RIGHT) self.lbl_day_lock.pack(side=tk.RIGHT)
form = ttk.Frame(root) # —— 下单参数 ——
form_outer = self._card(root, pad=12)
form_outer.pack(fill=tk.X, pady=(0, 10))
form_wrap = form_outer._inner # type: ignore[attr-defined]
form = ttk.Frame(form_wrap)
form.pack(fill=tk.X) form.pack(fill=tk.X)
for i in range(3): for i in range(3):
form.columnconfigure(i, weight=1) form.columnconfigure(i, weight=1)
style.configure("Form.TLabel", background=COLOR_CARD, foreground=COLOR_MUTED, font=("Microsoft YaHei UI", 11))
ttk.Label(form, text="手数").grid(row=0, column=0, sticky="w") ttk.Label(form, text="手数", style="Form.TLabel").grid(row=0, column=0, sticky="w")
self.lbl_sl_title = ttk.Label(form, text="SL倍") self.lbl_sl_title = ttk.Label(form, text="SL倍", style="Form.TLabel")
self.lbl_sl_title.grid(row=0, column=1, sticky="w", padx=6) self.lbl_sl_title.grid(row=0, column=1, sticky="w", padx=6)
self.lbl_tp_title = ttk.Label(form, text="TP倍") self.lbl_tp_title = ttk.Label(form, text="TP倍", style="Form.TLabel")
self.lbl_tp_title.grid(row=0, column=2, sticky="w") self.lbl_tp_title.grid(row=0, column=2, sticky="w")
self.var_lot = tk.StringVar(value=f"{DEFAULT_LOT:.2f}") self.var_lot = tk.StringVar(value=f"{DEFAULT_LOT:.2f}")
self.var_atr_mult = tk.StringVar(value=f"{DEFAULT_ATR_MULT:.2f}") self.var_atr_mult = tk.StringVar(value=f"{DEFAULT_ATR_MULT:.2f}")
self.var_tp = tk.StringVar(value=f"{DEFAULT_TP_ATR_MULT:.2f}") self.var_tp = tk.StringVar(value=f"{DEFAULT_TP_ATR_MULT:.2f}")
ttk.Entry(form, textvariable=self.var_lot, width=12).grid(row=1, column=0, sticky="ew", pady=8, ipady=4) ttk.Entry(form, textvariable=self.var_lot, width=12).grid(row=1, column=0, sticky="ew", pady=6, ipady=5)
ttk.Entry(form, textvariable=self.var_atr_mult, width=12).grid(row=1, column=1, sticky="ew", padx=8, pady=8, ipady=4) ttk.Entry(form, textvariable=self.var_atr_mult, width=12).grid(row=1, column=1, sticky="ew", padx=8, pady=6, ipady=5)
ttk.Entry(form, textvariable=self.var_tp, width=12).grid(row=1, column=2, sticky="ew", pady=8, ipady=4) ttk.Entry(form, textvariable=self.var_tp, width=12).grid(row=1, column=2, sticky="ew", pady=6, ipady=5)
opt = ttk.Frame(root) opt = tk.Frame(form_wrap, bg=COLOR_CARD)
opt.pack(fill=tk.X, pady=(0, 10)) opt.pack(fill=tk.X, pady=(6, 0))
self.var_use_atr = tk.BooleanVar(value=True) self.var_use_atr = tk.BooleanVar(value=True)
ttk.Checkbutton( ttk.Checkbutton(
opt, opt,
@@ -784,37 +878,29 @@ class OrderApp(tk.Tk):
variable=self.var_use_atr, variable=self.var_use_atr,
command=self._sync_sl_label, command=self._sync_sl_label,
).pack(side=tk.LEFT) ).pack(side=tk.LEFT)
self._mk_btn(opt, "按资金手数", "#3a5a80", self.on_auto_lot, h=34, w=110).pack(side=tk.LEFT, padx=10) self._mk_btn(opt, "按资金手数", "#3a5a80", self.on_auto_lot, h=34, w=110).pack(side=tk.LEFT, padx=12)
ttk.Label( tk.Label(
opt, opt,
text=( text=f"小资金点一下→{LOT_LOW:.2f}手 · 同向≤{MAX_SIDE_LOTS:.2f} · 最多{MAX_OPEN_POSITIONS}笔",
f"点一下按余额改手数:≥{BALANCE_HIGH:.0f}→{LOT_HIGH:.2f}," bg=COLOR_CARD, fg=COLOR_MUTED, font=("Microsoft YaHei UI", 10),
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) ).pack(side=tk.LEFT)
self.lbl_open_block = tk.Label( self.lbl_open_block = tk.Label(
root, root, text="", bg=COLOR_BG, fg=COLOR_WARN,
text="", font=("Microsoft YaHei UI", 12, "bold"), anchor="center",
bg="#1e1e1e",
fg=COLOR_WARN,
font=("Microsoft YaHei UI", 12, "bold"),
anchor="center",
) )
self.lbl_open_block.pack(fill=tk.X, pady=(0, 4)) self.lbl_open_block.pack(fill=tk.X, pady=(0, 4))
btns = ttk.Frame(root) btns = tk.Frame(root, bg=COLOR_BG)
btns.pack(fill=tk.X, pady=6) btns.pack(fill=tk.X, pady=4)
btns.columnconfigure(0, weight=1) btns.columnconfigure(0, weight=1)
btns.columnconfigure(1, weight=1) btns.columnconfigure(1, weight=1)
self.btn_buy = self._mk_btn(btns, "买入", COLOR_BUY, self.on_buy, h=46) self.btn_buy = self._mk_btn(btns, "买入", COLOR_BUY, self.on_buy, h=48)
self.btn_buy.grid(row=0, column=0, sticky="ew", padx=(0, 6), ipady=8) self.btn_buy.grid(row=0, column=0, sticky="ew", padx=(0, 6), ipady=8)
self.btn_sell = self._mk_btn(btns, "卖出", COLOR_SELL, self.on_sell, h=46) self.btn_sell = self._mk_btn(btns, "卖出", COLOR_SELL, self.on_sell, h=48)
self.btn_sell.grid(row=0, column=1, sticky="ew", padx=(6, 0), ipady=8) self.btn_sell.grid(row=0, column=1, sticky="ew", padx=(6, 0), ipady=8)
close = ttk.Frame(root) close = tk.Frame(root, bg=COLOR_BG)
close.pack(fill=tk.X, pady=8) close.pack(fill=tk.X, pady=8)
for i in range(3): for i in range(3):
close.columnconfigure(i, weight=1) close.columnconfigure(i, weight=1)
@@ -822,12 +908,18 @@ class OrderApp(tk.Tk):
self._mk_btn(close, "平空", "#5a3232", self.on_close_sell, h=38).grid(row=0, column=1, sticky="ew", padx=4, ipady=4) self._mk_btn(close, "平空", "#5a3232", self.on_close_sell, h=38).grid(row=0, column=1, sticky="ew", padx=4, ipady=4)
self._mk_btn(close, "全平", "#464628", self.on_close_all, h=38).grid(row=0, column=2, sticky="ew", padx=(4, 0), ipady=4) self._mk_btn(close, "全平", "#464628", self.on_close_all, h=38).grid(row=0, column=2, sticky="ew", padx=(4, 0), ipady=4)
self.lbl_status = ttk.Label(root, text="就绪", style="Status.TLabel", wraplength=900) self.lbl_status = tk.Label(
self.lbl_status.pack(anchor="w", pady=(12, 6)) root, text="就绪", bg=COLOR_BG, fg=COLOR_MUTED,
font=("Microsoft YaHei UI", 11), anchor="w", wraplength=960,
)
self.lbl_status.pack(fill=tk.X, pady=(8, 4))
pos_bar = ttk.Frame(root) pos_bar = tk.Frame(root, bg=COLOR_BG)
pos_bar.pack(fill=tk.X, pady=(10, 6)) pos_bar.pack(fill=tk.X, pady=(8, 6))
ttk.Label(pos_bar, text="持仓记录", font=("Microsoft YaHei UI", 13, "bold")).pack(side=tk.LEFT) tk.Label(
pos_bar, text="持仓记录", bg=COLOR_BG, fg=COLOR_TEXT,
font=("Microsoft YaHei UI", 13, "bold"),
).pack(side=tk.LEFT)
self.btn_break_even = self._mk_btn(pos_bar, "保本", COLOR_BE, self.on_break_even, h=34, w=90) self.btn_break_even = self._mk_btn(pos_bar, "保本", COLOR_BE, self.on_break_even, h=34, w=90)
self.btn_break_even.pack(side=tk.RIGHT, padx=4) self.btn_break_even.pack(side=tk.RIGHT, padx=4)
self._mk_btn(pos_bar, "打开复盘", "#3a5a80", self.on_open_review, h=34, w=100).pack( self._mk_btn(pos_bar, "打开复盘", "#3a5a80", self.on_open_review, h=34, w=100).pack(
@@ -880,14 +972,18 @@ class OrderApp(tk.Tk):
self.pos_canvas.bind("<Enter>", lambda e: self.pos_canvas.bind_all("<MouseWheel>", self._on_pos_mousewheel)) self.pos_canvas.bind("<Enter>", lambda e: self.pos_canvas.bind_all("<MouseWheel>", self._on_pos_mousewheel))
self.pos_canvas.bind("<Leave>", lambda e: self.pos_canvas.unbind_all("<MouseWheel>")) self.pos_canvas.bind("<Leave>", lambda e: self.pos_canvas.unbind_all("<MouseWheel>"))
tip = ttk.Label( tip = tk.Label(
root, root,
text=( text=(
f"最多同时 {MAX_OPEN_POSITIONS} 笔持仓;同向≤{MAX_SIDE_LOTS:.2f} 手。" f"最多同时 {MAX_OPEN_POSITIONS} 笔 · 同向≤{MAX_SIDE_LOTS:.2f} 手 · "
"亏≥5%或盈≥10%达标后禁止开仓。保本:价格朝有利方向走出>3$ 后改止损到开仓价±1$。" "亏≥5%或盈≥10%禁止开仓 · 保本:有利方向>3$ 后止损到开仓价±1$"
), ),
style="Status.TLabel", bg=COLOR_BG,
wraplength=920, fg=COLOR_MUTED,
font=("Microsoft YaHei UI", 10),
wraplength=960,
justify=tk.LEFT,
anchor="w",
) )
tip.pack(anchor="w", pady=(8, 0)) tip.pack(anchor="w", pady=(8, 0))
@@ -938,7 +1034,7 @@ class OrderApp(tk.Tk):
return s or DEFAULT_SYMBOL return s or DEFAULT_SYMBOL
def set_status(self, text: str, ok: bool = True) -> None: def set_status(self, text: str, ok: bool = True) -> None:
self.lbl_status.configure(text=text, foreground="#7CFC00" if ok else "#FF7F50") self.lbl_status.configure(text=text, fg="#7CFC00" if ok else "#FF7F50")
def _parse_float(self, value: str, name: str) -> float: def _parse_float(self, value: str, name: str) -> float:
s = value.strip().replace(",", ".") s = value.strip().replace(",", ".")
@@ -1273,18 +1369,35 @@ class OrderApp(tk.Tk):
self.refresh_quotes() self.refresh_quotes()
self.after(REFRESH_MS, self._try_connect_and_refresh) self.after(REFRESH_MS, self._try_connect_and_refresh)
def _set_metric(self, key: str, text: str, fg: str = COLOR_TEXT) -> None:
lbl = self._metric_values.get(key)
if lbl is not None:
lbl.configure(text=text, fg=fg)
def refresh_quotes(self) -> None: def refresh_quotes(self) -> None:
symbol = self._symbol() symbol = self._symbol()
try: try:
if not self.bridge.connected: if not self.bridge.connected:
self.bridge.connect() self.bridge.connect()
t = self.bridge.tick(symbol)
info = self.bridge.symbol_info(symbol) info = self.bridge.symbol_info(symbol)
digits = int(info.digits) digits = int(info.digits)
market_open = False
bid = ask = 0.0
try:
t = self.bridge.tick(symbol)
bid = float(t.bid) bid = float(t.bid)
ask = float(t.ask) ask = float(t.ask)
spread = ask - bid market_open = self.bridge.market_is_open(symbol)
except TradeError:
bid = float(getattr(info, "bid", 0) or getattr(info, "last", 0) or 0)
ask = float(getattr(info, "ask", 0) or bid)
market_open = False
spread = ask - bid if ask and bid else 0.0
try:
buy, sell, profit = self.bridge.exposure(symbol) buy, sell, profit = self.bridge.exposure(symbol)
except TradeError:
buy = sell = profit = 0.0
net = buy - sell net = buy - sell
pos = f"{net:.2f}" pos = f"{net:.2f}"
if buy > 0 and sell > 0: if buy > 0 and sell > 0:
@@ -1311,37 +1424,61 @@ class OrderApp(tk.Tk):
except TradeError: except TradeError:
self.lbl_account.configure(text="余额 --") self.lbl_account.configure(text="余额 --")
sess = current_session_label(market_open=market_open)
self.lbl_session.configure(
text=f"时段 {sess}",
bg="#3a2820" if not market_open else "#243044",
fg=COLOR_WARN if not market_open else COLOR_ACCENT,
)
if bid > 0:
self.lbl_quote.configure(text=f"Bid {bid:.{digits}f} Ask {ask:.{digits}f}") self.lbl_quote.configure(text=f"Bid {bid:.{digits}f} Ask {ask:.{digits}f}")
self.lbl_session.configure(text=f"时段 {current_session_label()}") else:
self.lbl_quote.configure(text="Bid -- Ask --")
self.lbl_info.configure( self.lbl_info.configure(
text=( text=(
f"点差 {spread:.{digits}f} ATRM15 {atr_txt} " f"点差 {spread:.{digits}f} ATR {atr_txt} "
f"SL${sl_txt} TP${tp_txt} 持仓 {pos} 浮盈 {profit:.2f}" f"SL${sl_txt} TP${tp_txt} 持仓 {pos} 浮盈 {profit:.2f}"
) )
) )
try: try:
day = self.bridge.day_ohlc(symbol) day = self.bridge.day_ohlc(symbol)
ot = day["open_time"].strftime("%H:%M") elapsed_fg = COLOR_WARN if not day["market_open"] else COLOR_ACCENT
self.lbl_day_ohlc.configure( self._set_metric("elapsed", day["elapsed_text"], elapsed_fg)
text=( self._set_metric("open", f"{day['open']:.{digits}f}")
f"开盘 {ot} | 今开 {day['open']:.{digits}f} | " self._set_metric("high", f"{day['high']:.{digits}f}", COLOR_PROFIT)
f"最高 {day['high']:.{digits}f} | 最低 {day['low']:.{digits}f}" self._set_metric("low", f"{day['low']:.{digits}f}", COLOR_LOSS)
) ot = day["open_time"].strftime("%m-%d %H:%M")
) tag = "交易中" if day["market_open"] else "休市 · 显示最近交易日"
self.lbl_day_ohlc.configure(text=f"{tag} · 日线开盘 {ot}")
except Exception: except Exception:
self.lbl_day_ohlc.configure(text="开盘 -- | 今开 -- | 最高 -- | 最低 --") self._set_metric("elapsed", "休市" if not market_open else "--", COLOR_WARN)
self._set_metric("open", "--")
self._set_metric("high", "--")
self._set_metric("low", "--")
self.lbl_day_ohlc.configure(text="日线暂不可用(休市也会尽量显示最近交易日)")
try: try:
self._refresh_today_stats(symbol) self._refresh_today_stats(symbol)
except Exception: except Exception:
self.lbl_today_base.configure(text="今日开单 -- 次 | ") self.lbl_today_base.configure(text="今日开单 -- 次")
self.lbl_today_closed.configure(text=" 已平 --", fg=COLOR_NEUTRAL) self.lbl_today_closed.configure(text=" 已平 --", fg=COLOR_NEUTRAL)
self.lbl_today_mid.configure(text=" | 浮盈 -- | 合计 ") self.lbl_today_mid.configure(text=" 浮盈 -- 合计 ")
self.lbl_today_total.configure(text="--", fg=COLOR_NEUTRAL) self.lbl_today_total.configure(text="--", fg=COLOR_NEUTRAL)
self.lbl_today_ratio.configure(text=" | 盈亏比 -- | 盈利率 --", fg=COLOR_NEUTRAL) self.lbl_today_ratio.configure(text=" 盈亏比 -- 盈利率 --", fg=COLOR_NEUTRAL)
self._refresh_positions(symbol, digits) self._refresh_positions(symbol, digits)
self._sync_open_buttons(symbol) self._sync_open_buttons(symbol)
except TradeError: except TradeError:
# 断连时仍尝试刷新日线卡片
try:
day = self.bridge.day_ohlc(symbol)
digits = 3
self._set_metric("elapsed", day["elapsed_text"], COLOR_WARN)
self._set_metric("open", f"{day['open']:.{digits}f}")
self._set_metric("high", f"{day['high']:.{digits}f}", COLOR_PROFIT)
self._set_metric("low", f"{day['low']:.{digits}f}", COLOR_LOSS)
self.lbl_session.configure(text="时段 休市", bg="#3a2820", fg=COLOR_WARN)
self.lbl_day_ohlc.configure(text="休市 · 显示最近交易日日线")
except Exception:
pass pass
except Exception: except Exception:
pass pass
@@ -1360,9 +1497,9 @@ class OrderApp(tk.Tk):
floating = st["floating"] floating = st["floating"]
total = st["total"] total = st["total"]
rate = st["profit_rate"] * 100.0 rate = st["profit_rate"] * 100.0
self.lbl_today_base.configure(text=f"今日开单 {st['opens']} 次 | ") self.lbl_today_base.configure(text=f"今日开单 {st['opens']} 次")
self.lbl_today_closed.configure(text=f" 已平 {closed:+.2f}", fg=self._pnl_color(closed)) self.lbl_today_closed.configure(text=f" 已平 {closed:+.2f}", fg=self._pnl_color(closed))
self.lbl_today_mid.configure(text=f" | 浮盈 {floating:+.2f} | 合计 ") self.lbl_today_mid.configure(text=f" 浮盈 {floating:+.2f} 合计 ")
self.lbl_today_total.configure(text=f"{total:+.2f}", fg=self._pnl_color(total)) self.lbl_today_total.configure(text=f"{total:+.2f}", fg=self._pnl_color(total))
self.lbl_today_ratio.configure( self.lbl_today_ratio.configure(
text=f" | 盈亏比 {st['pl_ratio_txt']} | 盈利率 {rate:+.2f}%", text=f" | 盈亏比 {st['pl_ratio_txt']} | 盈利率 {rate:+.2f}%",
Binary file not shown.