Reconcile stale open trades; report position ticket and history closes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+95
-10
@@ -504,21 +504,38 @@ class Mt5Bridge:
|
||||
result = self._send_deal(request)
|
||||
except TradeError as exc:
|
||||
raise TradeError(f"下单失败:{exc}") from exc
|
||||
# 持仓单号可能与 order 不同,优先用持仓 ticket,避免平仓对不上仍显示「持仓中」
|
||||
pos_ticket = int(result.order)
|
||||
open_px = float(result.price)
|
||||
open_ts = time.time()
|
||||
for _ in range(10):
|
||||
time.sleep(0.05)
|
||||
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)
|
||||
open_px = float(p.price_open)
|
||||
open_ts = float(p.time)
|
||||
sl = float(p.sl) if float(p.sl) > 0 else sl
|
||||
tp = float(p.tp) if float(p.tp) > 0 else tp
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
side = "买入" if order_type == mt5.ORDER_TYPE_BUY else "卖出"
|
||||
msg = f"{side}成功 手数={lots:.2f} 成交价={result.price} 单号={result.order}"
|
||||
msg = f"{side}成功 手数={lots:.2f} 成交价={open_px} 单号={pos_ticket}"
|
||||
report = {
|
||||
"ticket": str(result.order),
|
||||
"ticket": str(pos_ticket),
|
||||
"symbol": symbol,
|
||||
"side": "buy" if order_type == mt5.ORDER_TYPE_BUY else "sell",
|
||||
"volume": lots,
|
||||
"open_price": float(result.price),
|
||||
"open_price": open_px,
|
||||
"close_price": None,
|
||||
"open_time": time.time(),
|
||||
"open_time": open_ts,
|
||||
"close_time": None,
|
||||
"profit": 0.0,
|
||||
"sl": sl,
|
||||
"tp": tp,
|
||||
"snapshot": {"event": "open"},
|
||||
"snapshot": {"event": "open", "order": int(result.order)},
|
||||
}
|
||||
return msg, report
|
||||
|
||||
@@ -609,6 +626,63 @@ class Mt5Bridge:
|
||||
if kind == "profit":
|
||||
raise TradeError(f"当日盈利已达 {pct:.2f}%(上限 +{DAY_PROFIT_LOCK*100:.0f}%),禁止开仓")
|
||||
|
||||
def recent_close_reports(self, symbol: str, hours: float = 72) -> list:
|
||||
"""从 MT5 历史成交还原已平仓,用于补报/纠错仍显示「持仓中」的记录。"""
|
||||
self.ensure_symbol(symbol)
|
||||
now = datetime.now()
|
||||
deals = mt5.history_deals_get(now - timedelta(hours=hours), now + timedelta(hours=1))
|
||||
if not deals:
|
||||
return []
|
||||
out_entries = {
|
||||
mt5.DEAL_ENTRY_OUT,
|
||||
mt5.DEAL_ENTRY_OUT_BY,
|
||||
mt5.DEAL_ENTRY_INOUT,
|
||||
}
|
||||
by_pos: dict = {}
|
||||
for d in deals:
|
||||
if str(d.symbol) != symbol:
|
||||
continue
|
||||
if int(d.entry) not in out_entries:
|
||||
continue
|
||||
pid = int(d.position_id or 0)
|
||||
if pid <= 0:
|
||||
continue
|
||||
g = by_pos.get(pid)
|
||||
if g is None:
|
||||
# 平仓方向:卖出平仓 → 原多单
|
||||
side = "buy" if int(d.type) == mt5.DEAL_TYPE_SELL else "sell"
|
||||
g = {
|
||||
"ticket": str(pid),
|
||||
"symbol": symbol,
|
||||
"side": side,
|
||||
"volume": 0.0,
|
||||
"open_price": 0.0,
|
||||
"close_price": float(d.price),
|
||||
"open_time": None,
|
||||
"close_time": float(d.time),
|
||||
"profit": 0.0,
|
||||
"sl": None,
|
||||
"tp": None,
|
||||
"snapshot": {"event": "history_close"},
|
||||
}
|
||||
by_pos[pid] = g
|
||||
g["profit"] += float(d.profit) + float(d.swap) + float(d.commission)
|
||||
g["volume"] += float(d.volume)
|
||||
g["close_price"] = float(d.price)
|
||||
g["close_time"] = max(float(g["close_time"]), float(d.time))
|
||||
for d in deals:
|
||||
if str(d.symbol) != symbol:
|
||||
continue
|
||||
pid = int(d.position_id or 0)
|
||||
if pid not in by_pos:
|
||||
continue
|
||||
if int(d.entry) in (mt5.DEAL_ENTRY_IN, mt5.DEAL_ENTRY_INOUT):
|
||||
by_pos[pid]["open_price"] = float(d.price)
|
||||
prev = by_pos[pid].get("open_time")
|
||||
ts = float(d.time)
|
||||
by_pos[pid]["open_time"] = ts if prev is None else min(float(prev), ts)
|
||||
return list(by_pos.values())
|
||||
|
||||
def close_ticket(self, ticket: int) -> str:
|
||||
p = self.get_position(ticket)
|
||||
symbol = str(p.symbol)
|
||||
@@ -1522,7 +1596,7 @@ class OrderApp(tk.Tk):
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def _hub_sync_floating(self, symbol: str) -> None:
|
||||
"""定期把持仓浮盈 upsert 到中控,总览盈亏才有数。"""
|
||||
"""浮盈上报 + 历史平仓回填 + 对账清掉已不在仓的「假持仓中」。"""
|
||||
if hub_client is None or not is_activated():
|
||||
return
|
||||
now = time.time()
|
||||
@@ -1534,9 +1608,14 @@ class OrderApp(tk.Tk):
|
||||
poss = self.bridge.positions(symbol)
|
||||
except Exception:
|
||||
return
|
||||
if not poss:
|
||||
return
|
||||
reports = []
|
||||
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) 当前持仓浮盈
|
||||
for p in poss:
|
||||
reports.append({
|
||||
"ticket": str(p.ticket),
|
||||
@@ -1552,7 +1631,13 @@ class OrderApp(tk.Tk):
|
||||
"tp": float(p.tp),
|
||||
"snapshot": {"event": "float"},
|
||||
})
|
||||
self._hub_report(reports, with_bars=False)
|
||||
if reports:
|
||||
self._hub_report(reports, with_bars=False)
|
||||
# 3) 告诉服务器:不在 open_tickets 里的未平仓记录应结束「持仓中」
|
||||
try:
|
||||
hub_client.reconcile_trades(open_tickets, symbol)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _run_async(self, work) -> None:
|
||||
if self._busy:
|
||||
|
||||
Reference in New Issue
Block a user