Speed up UI: throttle history/stats, async hub sync, faster post-trade refresh.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+138
-32
@@ -44,7 +44,12 @@ MAGIC = 20261016
|
||||
COMMENT = "GoldPanel"
|
||||
DEVIATION = 100
|
||||
MUST_CONFIRM = False # 开仓/平仓默认不二次确认
|
||||
REFRESH_MS = 500
|
||||
REFRESH_MS = 400 # 报价/持仓轻量刷新
|
||||
TODAY_STATS_MS = 2500 # 今日战绩(含 history)节流
|
||||
ATR_CACHE_MS = 2000
|
||||
DAY_OHLC_CACHE_MS = 1500
|
||||
HUB_FLOAT_MS = 3000 # 浮盈上报间隔
|
||||
HUB_RECONCILE_MS = 45000 # 历史回填/对账间隔(重)
|
||||
|
||||
BALANCE_HIGH = 10000.0
|
||||
BALANCE_LOW = 1000.0
|
||||
@@ -509,8 +514,8 @@ class Mt5Bridge:
|
||||
pos_ticket = int(result.order)
|
||||
open_px = float(result.price)
|
||||
open_ts = time.time()
|
||||
for _ in range(10):
|
||||
time.sleep(0.05)
|
||||
for _ in range(6):
|
||||
time.sleep(0.02)
|
||||
for p in self.positions(symbol):
|
||||
if int(p.ticket) == int(result.order) or abs(float(p.price_open) - open_px) < 0.05:
|
||||
pos_ticket = int(p.ticket)
|
||||
@@ -965,6 +970,14 @@ class OrderApp(tk.Tk):
|
||||
self._pos_profit_labels: dict = {}
|
||||
self._metric_values: dict = {}
|
||||
self._last_spread: float = 0.0
|
||||
self._force_today_stats = True
|
||||
self._last_today_stats_ts = 0.0
|
||||
self._atr_cache: Tuple[float, float] = (0.0, 0.0) # (ts, value)
|
||||
self._day_ohlc_cache: Tuple[float, Any] = (0.0, None) # (ts, dict)
|
||||
self._last_float_sync = 0.0
|
||||
self._last_reconcile_sync = 0.0
|
||||
self._hub_sync_busy = False
|
||||
self._cached_today_stats: Optional[dict] = None
|
||||
self._build_ui()
|
||||
self.after(200, self._try_connect_and_refresh)
|
||||
self.after(3000, self._hub_heartbeat)
|
||||
@@ -1619,28 +1632,25 @@ class OrderApp(tk.Tk):
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def _hub_sync_floating(self, symbol: str) -> None:
|
||||
"""浮盈上报 + 历史平仓回填 + 对账清掉已不在仓的「假持仓中」。"""
|
||||
if hub_client is None or not is_activated():
|
||||
"""浮盈上报 +(低频)历史回填/对账。网络在后台,不堵界面。"""
|
||||
if hub_client is None or not is_activated() or self._hub_sync_busy:
|
||||
return
|
||||
now = time.time()
|
||||
last = float(getattr(self, "_last_float_sync", 0) or 0)
|
||||
if now - last < 2.0:
|
||||
if now - float(self._last_float_sync or 0) < HUB_FLOAT_MS / 1000.0:
|
||||
return
|
||||
self._last_float_sync = now
|
||||
do_reconcile = (now - float(self._last_reconcile_sync or 0)) >= HUB_RECONCILE_MS / 1000.0
|
||||
if do_reconcile:
|
||||
self._last_reconcile_sync = now
|
||||
# 持仓快照在 UI 线程取,避免与刷新抢 MT5
|
||||
try:
|
||||
poss = self.bridge.positions(symbol)
|
||||
except Exception:
|
||||
return
|
||||
open_tickets = [str(p.ticket) for p in poss]
|
||||
reports: list = []
|
||||
# 1) 用成交历史补平仓(修复旧数据 / 漏报)
|
||||
try:
|
||||
reports.extend(self.bridge.recent_close_reports(symbol, hours=72))
|
||||
except Exception:
|
||||
pass
|
||||
# 2) 当前持仓浮盈
|
||||
float_reports = []
|
||||
for p in poss:
|
||||
reports.append({
|
||||
float_reports.append({
|
||||
"ticket": str(p.ticket),
|
||||
"symbol": str(p.symbol),
|
||||
"side": "buy" if int(p.type) == mt5.POSITION_TYPE_BUY else "sell",
|
||||
@@ -1654,13 +1664,32 @@ class OrderApp(tk.Tk):
|
||||
"tp": float(p.tp),
|
||||
"snapshot": {"event": "float"},
|
||||
})
|
||||
if reports:
|
||||
self._hub_report(reports, with_bars=False)
|
||||
# 3) 告诉服务器:不在 open_tickets 里的未平仓记录应结束「持仓中」
|
||||
try:
|
||||
hub_client.reconcile_trades(open_tickets, symbol)
|
||||
except Exception:
|
||||
pass
|
||||
self._hub_sync_busy = True
|
||||
|
||||
def run() -> None:
|
||||
try:
|
||||
reports = list(float_reports)
|
||||
if do_reconcile:
|
||||
try:
|
||||
reports[0:0] = self.bridge.recent_close_reports(symbol, hours=48)
|
||||
except Exception:
|
||||
pass
|
||||
for item in reports:
|
||||
try:
|
||||
hub_client.report_trade(item)
|
||||
except Exception:
|
||||
pass
|
||||
if do_reconcile:
|
||||
try:
|
||||
hub_client.reconcile_trades(open_tickets, symbol)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self.after(0, lambda: setattr(self, "_hub_sync_busy", False))
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def _run_async(self, work) -> None:
|
||||
if self._busy:
|
||||
@@ -1682,7 +1711,10 @@ class OrderApp(tk.Tk):
|
||||
msg = result
|
||||
self.after(0, lambda m=msg: self.set_status(m, ok=True))
|
||||
if report is not None:
|
||||
self.after(0, lambda r=report: self._hub_report(r))
|
||||
# 成交后立刻刷新持仓;K 线上报延后,避免和 MT5 抢线程
|
||||
self.after(0, self._kick_after_trade)
|
||||
self.after(0, lambda r=report: self._hub_report(r, with_bars=False))
|
||||
self.after(800, lambda r=report: self._hub_upload_bars_for_report(r))
|
||||
except TradeError as exc:
|
||||
self.after(0, lambda: self.set_status(str(exc), ok=False))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
@@ -1692,8 +1724,33 @@ class OrderApp(tk.Tk):
|
||||
|
||||
threading.Thread(target=runner, daemon=True).start()
|
||||
|
||||
def _kick_after_trade(self) -> None:
|
||||
"""开平仓成功后立刻重绘持仓与今日战绩。"""
|
||||
self._force_today_stats = True
|
||||
self._pos_sig = None
|
||||
self._last_float_sync = 0.0
|
||||
|
||||
def _hub_upload_bars_for_report(self, payload: Union[dict, list, None]) -> None:
|
||||
if hub_client is None or not is_activated() or not payload:
|
||||
return
|
||||
|
||||
def run() -> None:
|
||||
try:
|
||||
items = payload if isinstance(payload, list) else [payload]
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
sym = str(item.get("symbol") or self._symbol())
|
||||
self._hub_upload_bars(sym)
|
||||
break # 一次成交只传一套 K 线即可
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def _done(self) -> None:
|
||||
self._busy = False
|
||||
self._kick_after_trade()
|
||||
self.refresh_quotes()
|
||||
|
||||
def on_auto_lot(self) -> None:
|
||||
@@ -1774,7 +1831,7 @@ class OrderApp(tk.Tk):
|
||||
atr_txt = sl_txt = tp_txt = "--"
|
||||
if self.var_use_atr.get():
|
||||
try:
|
||||
atr = self.bridge.atr(symbol)
|
||||
atr = self._cached_atr(symbol)
|
||||
base = self._atr_for_stops(atr)
|
||||
atr_txt = f"{atr:.2f}" + (f"→{base:.0f}" if base > atr + 1e-9 else "")
|
||||
sl_m = float(self.var_atr_mult.get().replace(",", ".") or "0")
|
||||
@@ -1817,7 +1874,7 @@ class OrderApp(tk.Tk):
|
||||
fg=COLOR_WARN if spread_wide else COLOR_MUTED,
|
||||
)
|
||||
try:
|
||||
day = self.bridge.day_ohlc(symbol)
|
||||
day = self._cached_day_ohlc(symbol, bid=bid, ask=ask, market_open=market_open)
|
||||
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}")
|
||||
@@ -1849,7 +1906,7 @@ class OrderApp(tk.Tk):
|
||||
self._set_metric("range", "--")
|
||||
self.lbl_day_ohlc.configure(text="日线暂不可用(休市也会尽量显示最近交易日)")
|
||||
try:
|
||||
self._refresh_today_stats(symbol)
|
||||
self._refresh_today_stats(symbol, floating=profit)
|
||||
except Exception:
|
||||
self.lbl_today_base.configure(text="今日开单 -- 次")
|
||||
self.lbl_today_closed.configure(text=" 已平 --", fg=COLOR_NEUTRAL)
|
||||
@@ -1900,15 +1957,64 @@ class OrderApp(tk.Tk):
|
||||
return COLOR_LOSS
|
||||
return COLOR_NEUTRAL
|
||||
|
||||
def _refresh_today_stats(self, symbol: str) -> None:
|
||||
st = self.bridge.today_stats(symbol)
|
||||
def _cached_atr(self, symbol: str) -> float:
|
||||
now = time.time()
|
||||
ts, val = self._atr_cache
|
||||
if val > 0 and (now - ts) * 1000.0 < ATR_CACHE_MS:
|
||||
return val
|
||||
atr = float(self.bridge.atr(symbol))
|
||||
self._atr_cache = (now, atr)
|
||||
return atr
|
||||
|
||||
def _cached_day_ohlc(
|
||||
self, symbol: str, bid: float = 0.0, ask: float = 0.0, market_open: bool = True
|
||||
) -> dict:
|
||||
now = time.time()
|
||||
ts, cached = self._day_ohlc_cache
|
||||
if cached is not None and (now - ts) * 1000.0 < DAY_OHLC_CACHE_MS:
|
||||
day = dict(cached)
|
||||
# 缓存期内仍用最新报价刷新高低价与开盘时长
|
||||
if market_open and bid > 0:
|
||||
day["high"] = max(float(day["high"]), bid, ask or bid)
|
||||
day["low"] = min(float(day["low"]), bid, ask or bid)
|
||||
day["market_open"] = market_open
|
||||
day["elapsed_text"] = format_open_elapsed(int(day.get("open_ts") or 0), market_open)
|
||||
return day
|
||||
day = self.bridge.day_ohlc(symbol)
|
||||
self._day_ohlc_cache = (now, dict(day))
|
||||
return day
|
||||
|
||||
def _refresh_today_stats(self, symbol: str, floating: Optional[float] = None) -> None:
|
||||
now = time.time()
|
||||
need = self._force_today_stats or (
|
||||
(now - self._last_today_stats_ts) * 1000.0 >= TODAY_STATS_MS
|
||||
)
|
||||
if need:
|
||||
st = self.bridge.today_stats(symbol)
|
||||
self._cached_today_stats = st
|
||||
self._last_today_stats_ts = now
|
||||
self._force_today_stats = False
|
||||
else:
|
||||
st = self._cached_today_stats
|
||||
if not st:
|
||||
st = self.bridge.today_stats(symbol)
|
||||
self._cached_today_stats = st
|
||||
self._last_today_stats_ts = now
|
||||
else:
|
||||
# 节流期内只刷新浮盈/合计,避免每拍扫成交历史
|
||||
st = dict(st)
|
||||
if floating is not None:
|
||||
st["floating"] = float(floating)
|
||||
st["total"] = float(st.get("closed_pnl") or 0) + float(floating)
|
||||
bal = float(st.get("balance") or 0)
|
||||
st["profit_rate"] = (st["total"] / bal) if bal > 1e-8 else 0.0
|
||||
closed = st["closed_pnl"]
|
||||
floating = st["floating"]
|
||||
total = st["total"]
|
||||
rate = st["profit_rate"] * 100.0
|
||||
floating_v = st["floating"] if floating is None else float(floating)
|
||||
total = float(st.get("closed_pnl") or 0) + floating_v
|
||||
rate = (total / float(st.get("balance") or 1)) * 100.0 if float(st.get("balance") or 0) > 1e-8 else 0.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_mid.configure(text=f" 浮盈 {floating_v:+.2f} 合计 ")
|
||||
self.lbl_today_total.configure(text=f"{total:+.2f}", fg=self._pnl_color(total))
|
||||
self.lbl_today_ratio.configure(
|
||||
text=(
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user