Reconcile stale open trades; report position ticket and history closes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
-1
@@ -683,12 +683,15 @@ def register_routes(app: Flask) -> None:
|
||||
|
||||
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
|
||||
ORDER BY CASE WHEN close_time IS NULL OR close_time='' THEN 0 ELSE 1 END,
|
||||
id DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(act["account_id"], ticket),
|
||||
).fetchone()
|
||||
@@ -765,6 +768,56 @@ def register_routes(app: Flask) -> None:
|
||||
tid = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
|
||||
return jsonify({"ok": True, "id": tid})
|
||||
|
||||
@app.post("/api/v1/trades/reconcile")
|
||||
def api_trades_reconcile():
|
||||
"""客户端上报当前仍持仓的 ticket;库中其它未平仓记录结束「持仓中」。"""
|
||||
token = request.headers.get("X-Device-Token") or ""
|
||||
data = request.get_json(silent=True) or {}
|
||||
open_tickets = {str(t).strip() for t in (data.get("open_tickets") or []) if str(t).strip()}
|
||||
symbol = (data.get("symbol") or "").strip().upper()
|
||||
with database.get_db() as conn:
|
||||
act = database.auth_token(conn, token)
|
||||
if not act:
|
||||
return jsonify({"ok": False, "error": "未激活"}), 401
|
||||
if not act.get("account_id"):
|
||||
return jsonify({"ok": False, "error": "激活码未绑定账户"}), 400
|
||||
sql = """
|
||||
SELECT id, ticket, profit, snapshot_json
|
||||
FROM trades
|
||||
WHERE account_id=?
|
||||
AND (close_time IS NULL OR close_time='')
|
||||
"""
|
||||
params: list = [act["account_id"]]
|
||||
if symbol:
|
||||
sql += " AND UPPER(symbol)=?"
|
||||
params.append(symbol)
|
||||
rows = conn.execute(sql, tuple(params)).fetchall()
|
||||
closed_n = 0
|
||||
now_ts = database.now()
|
||||
for r in rows:
|
||||
if str(r["ticket"] or "") in open_tickets:
|
||||
continue
|
||||
try:
|
||||
snap = json.loads(r["snapshot_json"] or "{}")
|
||||
except json.JSONDecodeError:
|
||||
snap = {}
|
||||
snap["event"] = "reconcile_closed"
|
||||
snap["reconciled_at"] = now_ts
|
||||
# 若无盈亏则置 0,避免一直「—」;有历史补报时会再 upsert 覆盖
|
||||
profit = r["profit"]
|
||||
if profit is None:
|
||||
profit = 0.0
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE trades
|
||||
SET close_time=?, profit=?, snapshot_json=?
|
||||
WHERE id=?
|
||||
""",
|
||||
(now_ts, profit, json.dumps(snap, ensure_ascii=False), r["id"]),
|
||||
)
|
||||
closed_n += 1
|
||||
return jsonify({"ok": True, "closed": closed_n})
|
||||
|
||||
@app.post("/api/v1/bars")
|
||||
def api_bars_upload():
|
||||
token = request.headers.get("X-Device-Token") or ""
|
||||
|
||||
Reference in New Issue
Block a user