diff --git a/app/routes.py b/app/routes.py index 6998416..131e00a 100644 --- a/app/routes.py +++ b/app/routes.py @@ -339,6 +339,7 @@ def register_routes(app: Flask) -> None: "side": t.get("side"), "volume": t.get("volume"), "profit": t.get("profit"), + "close_time": t.get("close_time"), "account_name": t.get("account_name"), "machine_name": t.get("machine_name"), "ticket": t.get("ticket"), @@ -658,35 +659,110 @@ def register_routes(app: Flask) -> None: if not act.get("account_id"): return jsonify({"ok": False, "error": "激活码未绑定账户"}), 400 snapshot = data.get("snapshot") or {} - conn.execute( - """ - INSERT INTO trades( - ticket, symbol, side, volume, open_price, close_price, - open_time, close_time, profit, sl, tp, - account_id, machine_id, entry_style, review_note, snapshot_json, created_at - ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) - """, - ( - str(data.get("ticket") or ""), - (data.get("symbol") or "XAUUSD").upper(), - (data.get("side") or "").lower(), - float(data.get("volume") or 0), - float(data.get("open_price") or 0), - float(data.get("close_price") or 0) if data.get("close_price") is not None else None, - data.get("open_time"), - data.get("close_time"), - float(data.get("profit") or 0) if data.get("profit") is not None else None, - data.get("sl"), - data.get("tp"), - act["account_id"], - act["machine_id"], - (data.get("entry_style") or "").strip(), - (data.get("review_note") or "").strip(), - json.dumps(snapshot, ensure_ascii=False), - database.now(), - ), + ticket = str(data.get("ticket") or "").strip() + symbol = (data.get("symbol") or "XAUUSD").upper() + side = (data.get("side") or "").lower() + volume = float(data.get("volume") or 0) + open_price = float(data.get("open_price") or 0) + close_price = ( + float(data.get("close_price") or 0) + if data.get("close_price") is not None + else None ) - tid = conn.execute("SELECT last_insert_rowid()").fetchone()[0] + open_time = data.get("open_time") + close_time = data.get("close_time") + if data.get("profit") is None: + profit = None + else: + profit = float(data.get("profit") or 0) + sl = data.get("sl") + tp = data.get("tp") + entry_style = (data.get("entry_style") or "").strip() + review_note = (data.get("review_note") or "").strip() + snap_json = json.dumps(snapshot, ensure_ascii=False) + + existing = None + if ticket: + existing = conn.execute( + """ + SELECT id, entry_style, review_note, open_time, close_time, close_price + FROM trades + WHERE account_id=? AND ticket=? + ORDER BY id DESC LIMIT 1 + """, + (act["account_id"], ticket), + ).fetchone() + + if existing: + # 浮盈更新不覆盖已有平仓;空备注不覆盖已标注 + keep_close = existing["close_time"] not in (None, "") and close_time in (None, "") + new_close_time = existing["close_time"] if keep_close else close_time + new_close_price = existing["close_price"] if keep_close else close_price + new_entry = entry_style or (existing["entry_style"] or "") + new_note = review_note or (existing["review_note"] or "") + new_open_time = open_time if open_time is not None else existing["open_time"] + if keep_close: + # 已平仓单不再被浮盈推送改写盈亏 + profit = conn.execute( + "SELECT profit FROM trades WHERE id=?", (existing["id"],) + ).fetchone()["profit"] + conn.execute( + """ + UPDATE trades SET + symbol=?, side=?, volume=?, open_price=?, close_price=?, + open_time=?, close_time=?, profit=?, sl=?, tp=?, + machine_id=?, entry_style=?, review_note=?, snapshot_json=? + WHERE id=? + """, + ( + symbol, + side, + volume, + open_price, + new_close_price, + new_open_time, + new_close_time, + profit, + sl, + tp, + act["machine_id"], + new_entry, + new_note, + snap_json, + existing["id"], + ), + ) + tid = int(existing["id"]) + else: + conn.execute( + """ + INSERT INTO trades( + ticket, symbol, side, volume, open_price, close_price, + open_time, close_time, profit, sl, tp, + account_id, machine_id, entry_style, review_note, snapshot_json, created_at + ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + """, + ( + ticket, + symbol, + side, + volume, + open_price, + close_price, + open_time, + close_time, + profit, + sl, + tp, + act["account_id"], + act["machine_id"], + entry_style, + review_note, + snap_json, + database.now(), + ), + ) + tid = conn.execute("SELECT last_insert_rowid()").fetchone()[0] return jsonify({"ok": True, "id": tid}) @app.post("/api/v1/bars") diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index ccee60d..3116c1c 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -60,8 +60,8 @@ {{ t.symbol }} {{ t.side }} {{ t.volume if t.volume is not none else '—' }} - - {{ t.profit if t.profit is not none else '—' }} + + {% if t.profit is none %}—{% else %}{{ '%.2f'|format(t.profit) }}{% if not t.close_time %} 浮{% endif %}{% endif %} {{ t.account_name or '—' }} {{ t.ticket or '—' }} @@ -149,7 +149,11 @@ '' + esc(t.symbol) + '' + '' + esc(t.side) + '' + '' + esc(t.volume != null ? t.volume : '—') + '' + - '' + esc(t.profit != null ? t.profit : '—') + '' + + '' + + (t.profit == null || t.profit === '' + ? '—' + : (Number(t.profit).toFixed(2) + (t.close_time ? '' : ' 浮'))) + + '' + '' + esc(t.account_name || '—') + '' + '' + esc(t.ticket || '—') + '' + '复盘' + diff --git a/desktop/gold_order_app.py b/desktop/gold_order_app.py index 999a7de..b47ada9 100644 --- a/desktop/gold_order_app.py +++ b/desktop/gold_order_app.py @@ -288,12 +288,13 @@ class Mt5Bridge: return age < 180 # 3 分钟内有报价视为开市 def day_ohlc(self, symbol: str) -> dict[str, Any]: - """最近交易日日线开高低(休市仍显示);开市时用 tick 修正最高/最低。""" + """当前/最近交易日日线开高低(休市仍显示);开市时用 tick 修正最高/最低。""" self.ensure_symbol(symbol) - rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 2) + rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 3) if rates is None or len(rates) < 1: raise TradeError("无法读取日线,请确认品种有 D1 数据") - bar = rates[0] + # MT5 返回数组从旧到新:[-1] 才是当前日线 + bar = rates[-1] open_px = float(bar["open"]) high = float(bar["high"]) low = float(bar["low"]) @@ -514,7 +515,7 @@ class Mt5Bridge: "close_price": None, "open_time": time.time(), "close_time": None, - "profit": None, + "profit": 0.0, "sl": sl, "tp": tp, "snapshot": {"event": "open"}, @@ -1383,7 +1384,7 @@ class OrderApp(tk.Tk): ] hub_client.report_bars(symbol, name, bars) - def _hub_report(self, payload: Union[dict, list, None]) -> None: + def _hub_report(self, payload: Union[dict, list, None], with_bars: bool = True) -> None: if hub_client is None or not is_activated() or not payload: return @@ -1394,13 +1395,47 @@ class OrderApp(tk.Tk): if not isinstance(item, dict): continue hub_client.report_trade(item) - sym = str(item.get("symbol") or self._symbol()) - self._hub_upload_bars(sym) + if with_bars: + sym = str(item.get("symbol") or self._symbol()) + self._hub_upload_bars(sym) except Exception: pass 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() + last = float(getattr(self, "_last_float_sync", 0) or 0) + if now - last < 2.0: + return + self._last_float_sync = now + try: + poss = self.bridge.positions(symbol) + except Exception: + return + if not poss: + return + reports = [] + for p in poss: + reports.append({ + "ticket": str(p.ticket), + "symbol": str(p.symbol), + "side": "buy" if int(p.type) == mt5.POSITION_TYPE_BUY else "sell", + "volume": float(p.volume), + "open_price": float(p.price_open), + "close_price": None, + "open_time": float(p.time), + "close_time": None, + "profit": float(p.profit) + float(p.swap), + "sl": float(p.sl), + "tp": float(p.tp), + "snapshot": {"event": "float"}, + }) + self._hub_report(reports, with_bars=False) + def _run_async(self, work) -> None: if self._busy: self.set_status("上一次操作还未完成", ok=False) @@ -1597,6 +1632,7 @@ class OrderApp(tk.Tk): self._refresh_positions(symbol, digits) self._sync_open_buttons(symbol) + self._hub_sync_floating(symbol) except TradeError: # 断连时仍尝试刷新日线卡片 try: diff --git a/releases/黄金市价辅助.exe b/releases/黄金市价辅助.exe index 9c6d3e2..211b82b 100644 Binary files a/releases/黄金市价辅助.exe and b/releases/黄金市价辅助.exe differ