Polish order UI: elapsed since open, show OHLC when closed, card layout.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+258
-121
@@ -65,6 +65,12 @@ COLOR_BUY = "#108040"
|
||||
COLOR_SELL = "#a02828"
|
||||
COLOR_BTN_OFF = "#555555"
|
||||
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_OFFSET_USD = 1.0 # 保本止损 = 开仓价 ± 1$
|
||||
|
||||
@@ -109,8 +115,10 @@ def suggest_lot_by_balance(balance: float) -> float:
|
||||
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)
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=BJT)
|
||||
@@ -132,6 +140,21 @@ def current_session_label(now: Optional[datetime] = None) -> str:
|
||||
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):
|
||||
pass
|
||||
|
||||
@@ -179,29 +202,48 @@ class Mt5Bridge:
|
||||
raise TradeError(f"无法读取品种信息 {symbol}")
|
||||
return info
|
||||
|
||||
def day_ohlc(self, symbol: str) -> dict[str, Any]:
|
||||
"""今日(服务器日线)开高低;用最新 tick 实时修正最高/最低。"""
|
||||
def market_is_open(self, symbol: str) -> bool:
|
||||
"""根据最新 tick 是否新鲜判断是否在交易(休市也仍可读日线)。"""
|
||||
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:
|
||||
raise TradeError("无法读取今日日线,请确认品种有 D1 数据")
|
||||
raise TradeError("无法读取日线,请确认品种有 D1 数据")
|
||||
bar = rates[0]
|
||||
open_px = float(bar["open"])
|
||||
high = float(bar["high"])
|
||||
low = float(bar["low"])
|
||||
open_ts = int(bar["time"])
|
||||
market_open = self.market_is_open(symbol)
|
||||
tick = mt5.symbol_info_tick(symbol)
|
||||
if tick is not None:
|
||||
if market_open and tick is not None:
|
||||
bid = float(tick.bid)
|
||||
ask = float(tick.ask)
|
||||
high = max(high, bid, ask)
|
||||
low = min(low, bid, ask)
|
||||
bar_day = datetime.fromtimestamp(open_ts).strftime("%m-%d")
|
||||
return {
|
||||
"open": open_px,
|
||||
"high": high,
|
||||
"low": low,
|
||||
"open_time": datetime.fromtimestamp(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:
|
||||
@@ -656,9 +698,9 @@ class OrderApp(tk.Tk):
|
||||
super().__init__()
|
||||
_init_mt5_constants()
|
||||
self.title(APP_TITLE)
|
||||
self.geometry("1080x940")
|
||||
self.minsize(980, 860)
|
||||
self.configure(bg="#1e1e1e")
|
||||
self.geometry("1100x980")
|
||||
self.minsize(1000, 900)
|
||||
self.configure(bg=COLOR_BG)
|
||||
try:
|
||||
self.tk.call("tk", "scaling", 1.45)
|
||||
except tk.TclError:
|
||||
@@ -670,113 +712,165 @@ class OrderApp(tk.Tk):
|
||||
self._be_done_tickets: set = set() # 已拉过保本的单号,不再判断
|
||||
self._pos_sig = None
|
||||
self._pos_profit_labels: dict = {}
|
||||
self._metric_values: dict = {}
|
||||
self._build_ui()
|
||||
self.after(200, self._try_connect_and_refresh)
|
||||
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:
|
||||
style = ttk.Style(self)
|
||||
try:
|
||||
style.theme_use("clam")
|
||||
except tk.TclError:
|
||||
pass
|
||||
style.configure("TLabel", background="#1e1e1e", foreground="#f0f0f0", font=("Microsoft YaHei UI", 12))
|
||||
style.configure("Status.TLabel", background="#1e1e1e", foreground="#c0c0c0", font=("Microsoft YaHei UI", 11))
|
||||
style.configure("TFrame", background="#1e1e1e")
|
||||
style.configure("TEntry", fieldbackground="#2b2b2b", foreground="#ffffff", font=("Microsoft YaHei UI", 12))
|
||||
style.configure("TCheckbutton", background="#1e1e1e", foreground="#f0f0f0", font=("Microsoft YaHei UI", 11))
|
||||
root = ttk.Frame(self, padding=18)
|
||||
root.pack(fill=tk.BOTH, expand=True)
|
||||
style.configure("TLabel", background=COLOR_BG, foreground=COLOR_TEXT, font=("Microsoft YaHei UI", 12))
|
||||
style.configure("Status.TLabel", background=COLOR_BG, foreground=COLOR_MUTED, font=("Microsoft YaHei UI", 11))
|
||||
style.configure("TFrame", background=COLOR_BG)
|
||||
style.configure("Card.TFrame", background=COLOR_CARD)
|
||||
style.configure("TEntry", fieldbackground="#2a2a36", foreground="#ffffff", font=("Microsoft YaHei UI", 12))
|
||||
style.configure("TCheckbutton", background=COLOR_CARD, foreground=COLOR_TEXT, font=("Microsoft YaHei UI", 11))
|
||||
style.map("TCheckbutton", background=[("active", COLOR_CARD)])
|
||||
|
||||
head = ttk.Frame(root)
|
||||
head.pack(fill=tk.X)
|
||||
ttk.Label(head, text="品种", font=("Microsoft YaHei UI", 13, "bold")).pack(side=tk.LEFT)
|
||||
root = tk.Frame(self, bg=COLOR_BG)
|
||||
root.pack(fill=tk.BOTH, expand=True, padx=16, pady=14)
|
||||
|
||||
# —— 顶栏 ——
|
||||
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)
|
||||
ttk.Entry(head, textvariable=self.var_symbol, width=16).pack(side=tk.LEFT, padx=8)
|
||||
self.lbl_account = ttk.Label(head, text="余额 --", font=("Microsoft YaHei UI", 13, "bold"))
|
||||
self.lbl_account.pack(side=tk.LEFT, padx=12)
|
||||
|
||||
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",
|
||||
ttk.Entry(left, textvariable=self.var_symbol, width=14).pack(side=tk.LEFT)
|
||||
self.lbl_account = tk.Label(
|
||||
left, text="余额 --", bg=COLOR_CARD, fg=COLOR_TEXT,
|
||||
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_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),
|
||||
# —— 报价 + 日线指标 ——
|
||||
quote_outer = self._card(root, pad=12)
|
||||
quote_outer.pack(fill=tk.X, pady=(0, 10))
|
||||
q = quote_outer._inner # type: ignore[attr-defined]
|
||||
self.lbl_quote = tk.Label(
|
||||
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))
|
||||
today_row = ttk.Frame(root)
|
||||
today_row.pack(fill=tk.X, pady=(0, 12))
|
||||
self.lbl_quote.pack(anchor="w")
|
||||
self.lbl_info = tk.Label(
|
||||
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(
|
||||
today_row, text="今日开单 -- 次 | ", bg="#1e1e1e", fg=COLOR_NEUTRAL,
|
||||
today_row, text="今日开单 -- 次", bg=COLOR_CARD, fg=COLOR_MUTED,
|
||||
font=("Microsoft YaHei UI", 11),
|
||||
)
|
||||
self.lbl_today_base.pack(side=tk.LEFT)
|
||||
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"),
|
||||
)
|
||||
self.lbl_today_closed.pack(side=tk.LEFT)
|
||||
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),
|
||||
)
|
||||
self.lbl_today_mid.pack(side=tk.LEFT)
|
||||
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"),
|
||||
)
|
||||
self.lbl_today_total.pack(side=tk.LEFT)
|
||||
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"),
|
||||
)
|
||||
self.lbl_today_ratio.pack(side=tk.LEFT)
|
||||
self.lbl_day_lock = tk.Label(
|
||||
today_row,
|
||||
text="",
|
||||
bg="#1e1e1e",
|
||||
fg=COLOR_WARN,
|
||||
font=("Microsoft YaHei UI", 11, "bold"),
|
||||
padx=8,
|
||||
pady=2,
|
||||
today_row, text="", bg=COLOR_CARD, fg=COLOR_WARN,
|
||||
font=("Microsoft YaHei UI", 11, "bold"), padx=8, pady=2,
|
||||
)
|
||||
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)
|
||||
for i in range(3):
|
||||
form.columnconfigure(i, weight=1)
|
||||
|
||||
ttk.Label(form, text="手数").grid(row=0, column=0, sticky="w")
|
||||
self.lbl_sl_title = ttk.Label(form, text="SL倍")
|
||||
style.configure("Form.TLabel", background=COLOR_CARD, foreground=COLOR_MUTED, font=("Microsoft YaHei UI", 11))
|
||||
ttk.Label(form, text="手数", style="Form.TLabel").grid(row=0, column=0, sticky="w")
|
||||
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_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.var_lot = tk.StringVar(value=f"{DEFAULT_LOT:.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}")
|
||||
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_atr_mult, width=12).grid(row=1, column=1, sticky="ew", padx=8, pady=8, ipady=4)
|
||||
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_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=6, ipady=5)
|
||||
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.pack(fill=tk.X, pady=(0, 10))
|
||||
opt = tk.Frame(form_wrap, bg=COLOR_CARD)
|
||||
opt.pack(fill=tk.X, pady=(6, 0))
|
||||
self.var_use_atr = tk.BooleanVar(value=True)
|
||||
ttk.Checkbutton(
|
||||
opt,
|
||||
@@ -784,37 +878,29 @@ class OrderApp(tk.Tk):
|
||||
variable=self.var_use_atr,
|
||||
command=self._sync_sl_label,
|
||||
).pack(side=tk.LEFT)
|
||||
self._mk_btn(opt, "按资金手数", "#3a5a80", self.on_auto_lot, h=34, w=110).pack(side=tk.LEFT, padx=10)
|
||||
ttk.Label(
|
||||
self._mk_btn(opt, "按资金手数", "#3a5a80", self.on_auto_lot, h=34, w=110).pack(side=tk.LEFT, padx=12)
|
||||
tk.Label(
|
||||
opt,
|
||||
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",
|
||||
text=f"小资金点一下→{LOT_LOW:.2f}手 · 同向≤{MAX_SIDE_LOTS:.2f} · 最多{MAX_OPEN_POSITIONS}笔",
|
||||
bg=COLOR_CARD, fg=COLOR_MUTED, font=("Microsoft YaHei UI", 10),
|
||||
).pack(side=tk.LEFT)
|
||||
|
||||
self.lbl_open_block = tk.Label(
|
||||
root,
|
||||
text="",
|
||||
bg="#1e1e1e",
|
||||
fg=COLOR_WARN,
|
||||
font=("Microsoft YaHei UI", 12, "bold"),
|
||||
anchor="center",
|
||||
root, text="", bg=COLOR_BG, fg=COLOR_WARN,
|
||||
font=("Microsoft YaHei UI", 12, "bold"), anchor="center",
|
||||
)
|
||||
self.lbl_open_block.pack(fill=tk.X, pady=(0, 4))
|
||||
|
||||
btns = ttk.Frame(root)
|
||||
btns.pack(fill=tk.X, pady=6)
|
||||
btns = tk.Frame(root, bg=COLOR_BG)
|
||||
btns.pack(fill=tk.X, pady=4)
|
||||
btns.columnconfigure(0, 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_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)
|
||||
|
||||
close = ttk.Frame(root)
|
||||
close = tk.Frame(root, bg=COLOR_BG)
|
||||
close.pack(fill=tk.X, pady=8)
|
||||
for i in range(3):
|
||||
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, "全平", "#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.pack(anchor="w", pady=(12, 6))
|
||||
self.lbl_status = tk.Label(
|
||||
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.pack(fill=tk.X, pady=(10, 6))
|
||||
ttk.Label(pos_bar, text="持仓记录", font=("Microsoft YaHei UI", 13, "bold")).pack(side=tk.LEFT)
|
||||
pos_bar = tk.Frame(root, bg=COLOR_BG)
|
||||
pos_bar.pack(fill=tk.X, pady=(8, 6))
|
||||
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.pack(side=tk.RIGHT, padx=4)
|
||||
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("<Leave>", lambda e: self.pos_canvas.unbind_all("<MouseWheel>"))
|
||||
|
||||
tip = ttk.Label(
|
||||
tip = tk.Label(
|
||||
root,
|
||||
text=(
|
||||
f"最多同时 {MAX_OPEN_POSITIONS} 笔持仓;同向≤{MAX_SIDE_LOTS:.2f} 手。"
|
||||
"亏≥5%或盈≥10%达标后禁止开仓。保本:价格朝有利方向走出>3$ 后改止损到开仓价±1$。"
|
||||
f"最多同时 {MAX_OPEN_POSITIONS} 笔 · 同向≤{MAX_SIDE_LOTS:.2f} 手 · "
|
||||
"亏≥5%或盈≥10%禁止开仓 · 保本:有利方向>3$ 后止损到开仓价±1$"
|
||||
),
|
||||
style="Status.TLabel",
|
||||
wraplength=920,
|
||||
bg=COLOR_BG,
|
||||
fg=COLOR_MUTED,
|
||||
font=("Microsoft YaHei UI", 10),
|
||||
wraplength=960,
|
||||
justify=tk.LEFT,
|
||||
anchor="w",
|
||||
)
|
||||
tip.pack(anchor="w", pady=(8, 0))
|
||||
|
||||
@@ -938,7 +1034,7 @@ class OrderApp(tk.Tk):
|
||||
return s or DEFAULT_SYMBOL
|
||||
|
||||
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:
|
||||
s = value.strip().replace(",", ".")
|
||||
@@ -1273,18 +1369,35 @@ class OrderApp(tk.Tk):
|
||||
self.refresh_quotes()
|
||||
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:
|
||||
symbol = self._symbol()
|
||||
try:
|
||||
if not self.bridge.connected:
|
||||
self.bridge.connect()
|
||||
t = self.bridge.tick(symbol)
|
||||
info = self.bridge.symbol_info(symbol)
|
||||
digits = int(info.digits)
|
||||
bid = float(t.bid)
|
||||
ask = float(t.ask)
|
||||
spread = ask - bid
|
||||
buy, sell, profit = self.bridge.exposure(symbol)
|
||||
market_open = False
|
||||
bid = ask = 0.0
|
||||
try:
|
||||
t = self.bridge.tick(symbol)
|
||||
bid = float(t.bid)
|
||||
ask = float(t.ask)
|
||||
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)
|
||||
except TradeError:
|
||||
buy = sell = profit = 0.0
|
||||
net = buy - sell
|
||||
pos = f"{net:.2f}"
|
||||
if buy > 0 and sell > 0:
|
||||
@@ -1311,38 +1424,62 @@ class OrderApp(tk.Tk):
|
||||
except TradeError:
|
||||
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()}")
|
||||
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}")
|
||||
else:
|
||||
self.lbl_quote.configure(text="Bid -- Ask --")
|
||||
self.lbl_info.configure(
|
||||
text=(
|
||||
f"点差 {spread:.{digits}f} ATRM15 {atr_txt} "
|
||||
f"SL${sl_txt} TP${tp_txt} 持仓 {pos} 浮盈 {profit:.2f}"
|
||||
f"点差 {spread:.{digits}f} ATR {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}"
|
||||
)
|
||||
)
|
||||
elapsed_fg = COLOR_WARN if not day["market_open"] else COLOR_ACCENT
|
||||
self._set_metric("elapsed", day["elapsed_text"], elapsed_fg)
|
||||
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)
|
||||
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:
|
||||
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:
|
||||
self._refresh_today_stats(symbol)
|
||||
except Exception:
|
||||
self.lbl_today_base.configure(text="今日开单 -- 次 | ")
|
||||
self.lbl_today_closed.configure(text="已平 --", fg=COLOR_NEUTRAL)
|
||||
self.lbl_today_mid.configure(text=" | 浮盈 -- | 合计 ")
|
||||
self.lbl_today_base.configure(text="今日开单 -- 次")
|
||||
self.lbl_today_closed.configure(text=" 已平 --", fg=COLOR_NEUTRAL)
|
||||
self.lbl_today_mid.configure(text=" 浮盈 -- 合计 ")
|
||||
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._sync_open_buttons(symbol)
|
||||
except TradeError:
|
||||
pass
|
||||
# 断连时仍尝试刷新日线卡片
|
||||
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
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -1360,9 +1497,9 @@ class OrderApp(tk.Tk):
|
||||
floating = st["floating"]
|
||||
total = st["total"]
|
||||
rate = st["profit_rate"] * 100.0
|
||||
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_mid.configure(text=f" | 浮盈 {floating:+.2f} | 合计 ")
|
||||
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_mid.configure(text=f" 浮盈 {floating:+.2f} 合计 ")
|
||||
self.lbl_today_total.configure(text=f"{total:+.2f}", fg=self._pnl_color(total))
|
||||
self.lbl_today_ratio.configure(
|
||||
text=f" | 盈亏比 {st['pl_ratio_txt']} | 盈利率 {rate:+.2f}%",
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user