Add auto break-even toggle and WeCom hub notifications.
Desktop can auto-pull stop to open+/-1 after a 3$ favorable move; hub pushes open/close events and a 23:00 BJT daily summary via webhook. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,12 +22,19 @@ import MetaTrader5 as mt5
|
||||
try:
|
||||
import hub_client
|
||||
from activation_dialog import ensure_activated
|
||||
from device_store import is_activated, load_device
|
||||
from device_store import (
|
||||
get_auto_break_even,
|
||||
is_activated,
|
||||
load_device,
|
||||
set_auto_break_even,
|
||||
)
|
||||
except ImportError: # 允许单独拷贝本文件调试
|
||||
hub_client = None # type: ignore
|
||||
ensure_activated = None # type: ignore
|
||||
is_activated = lambda: False # type: ignore
|
||||
load_device = lambda: {} # type: ignore
|
||||
get_auto_break_even = lambda: False # type: ignore
|
||||
set_auto_break_even = lambda _enabled: None # type: ignore
|
||||
|
||||
|
||||
APP_TITLE = "黄金市价辅助"
|
||||
@@ -1096,6 +1103,7 @@ class OrderApp(tk.Tk):
|
||||
self._lot_auto_done = False
|
||||
self._selected_tickets_set: set = set()
|
||||
self._be_done_tickets: set = set() # 已拉过保本的单号,不再判断
|
||||
self._auto_be_busy = False # 自动保本进行中,防并发改单
|
||||
self._pos_sig = None
|
||||
self._pos_profit_labels: dict = {}
|
||||
self._metric_values: dict = {}
|
||||
@@ -1320,6 +1328,13 @@ class OrderApp(tk.Tk):
|
||||
self._be_ready = False
|
||||
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.var_auto_be = tk.BooleanVar(value=bool(get_auto_break_even()))
|
||||
ttk.Checkbutton(
|
||||
pos_bar,
|
||||
text="自动保本",
|
||||
variable=self.var_auto_be,
|
||||
command=self._on_auto_be_toggle,
|
||||
).pack(side=tk.RIGHT, padx=(4, 8))
|
||||
self._mk_btn(pos_bar, "打开复盘", "#3a5a80", self.on_open_review, h=34, w=100).pack(
|
||||
side=tk.RIGHT, padx=4
|
||||
)
|
||||
@@ -1384,7 +1399,8 @@ class OrderApp(tk.Tk):
|
||||
text=(
|
||||
f"最多同时 {MAX_OPEN_POSITIONS} 笔 · 同向≤{MAX_SIDE_LOTS:.2f} 手 · "
|
||||
f"点差>{SPREAD_WARN_USD:.2f} 买卖提示 · "
|
||||
"亏≥5%或盈≥10%禁止开仓 · 持仓可单独「改止损/改止盈」 · 保本:有利≥3$ 后止损到开仓价±1$"
|
||||
"亏≥5%或盈≥10%禁止开仓 · 持仓可单独「改止损/改止盈」 · "
|
||||
"保本:有利≥3$ 后止损到开仓价±1$(可勾选自动保本)"
|
||||
),
|
||||
bg=COLOR_BG,
|
||||
fg=COLOR_MUTED,
|
||||
@@ -1743,6 +1759,72 @@ class OrderApp(tk.Tk):
|
||||
except Exception:
|
||||
ok = False
|
||||
self._set_be_button_enabled(ok)
|
||||
if ok and getattr(self, "var_auto_be", None) is not None and self.var_auto_be.get():
|
||||
self._maybe_auto_break_even()
|
||||
|
||||
def _on_auto_be_toggle(self) -> None:
|
||||
enabled = bool(self.var_auto_be.get())
|
||||
try:
|
||||
set_auto_break_even(enabled)
|
||||
except Exception:
|
||||
pass
|
||||
self.set_status(
|
||||
"已开启自动保本:有利≥3$ 时自动拉止损到开仓价±1$"
|
||||
if enabled
|
||||
else "已关闭自动保本(仍可手动点「保本」)",
|
||||
ok=True,
|
||||
)
|
||||
if enabled:
|
||||
try:
|
||||
positions = self.bridge.positions(self._symbol())
|
||||
except Exception:
|
||||
positions = []
|
||||
self._maybe_auto_break_even(positions)
|
||||
|
||||
def _maybe_auto_break_even(self, positions=None) -> None:
|
||||
"""开启自动保本且存在候选单时,后台改止损(不抢手动操作的 _busy)。"""
|
||||
if self._busy or self._auto_be_busy:
|
||||
return
|
||||
if not getattr(self, "var_auto_be", None) or not self.var_auto_be.get():
|
||||
return
|
||||
symbol = self._symbol()
|
||||
|
||||
def work():
|
||||
self.bridge.connect()
|
||||
pos = positions
|
||||
if pos is None:
|
||||
pos = self.bridge.positions(symbol)
|
||||
tick = self.bridge.tick(symbol)
|
||||
tickets = self._be_candidate_tickets(pos, tick)
|
||||
if not tickets:
|
||||
return None
|
||||
msg, done = self.bridge.break_even_tickets(tickets, BE_OFFSET_USD)
|
||||
self._be_done_tickets.update(done)
|
||||
return f"自动保本:{msg}"
|
||||
|
||||
def after_ok(msg: str) -> None:
|
||||
self.set_status(msg, ok=True)
|
||||
self._pos_sig = None
|
||||
try:
|
||||
digits = int(self.bridge.symbol_info(symbol).digits)
|
||||
except Exception:
|
||||
digits = 3
|
||||
self._refresh_positions(symbol, digits)
|
||||
|
||||
def runner():
|
||||
try:
|
||||
msg = work()
|
||||
if msg:
|
||||
self.after(0, lambda m=msg: after_ok(m))
|
||||
except TradeError as exc:
|
||||
self.after(0, lambda: self.set_status(f"自动保本:{exc}", ok=False))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self.after(0, lambda: self.set_status(f"自动保本异常:{exc}", ok=False))
|
||||
finally:
|
||||
self._auto_be_busy = False
|
||||
|
||||
self._auto_be_busy = True
|
||||
threading.Thread(target=runner, daemon=True).start()
|
||||
|
||||
def _hub_upload_bars(self, symbol: str) -> None:
|
||||
if hub_client is None or not is_activated():
|
||||
|
||||
Reference in New Issue
Block a user