修复读取

This commit is contained in:
dekun
2026-05-17 17:12:02 +08:00
parent 56f58c2b52
commit a250c28ceb
2 changed files with 106 additions and 35 deletions
+78 -35
View File
@@ -4742,59 +4742,83 @@ def fetch_gate_positions_close_history():
return []
ensure_markets_loaded()
since_ms = exchange_position_sync_since_ms()
try:
rows = exchange.fetch_positions_history(
None,
since=int(since_ms),
limit=int(EXCHANGE_POSITION_HISTORY_LIMIT),
params={"settle": "usdt"},
)
except Exception:
try:
rows = exchange.fetch_positions_history(
None,
since=int(since_ms),
limit=int(EXCHANGE_POSITION_HISTORY_LIMIT),
params={},
)
except Exception:
return []
until_ms = int(time.time() * 1000)
out = []
for p in rows or []:
h = _normalize_gate_position_history_entry(p)
if h and h["close_ms"] and h["side"] in ("long", "short") and h["symbol_u"]:
out.append(h)
return out
offset = 0
page_limit = min(100, int(EXCHANGE_POSITION_HISTORY_LIMIT))
max_total = int(EXCHANGE_POSITION_HISTORY_LIMIT)
def _pull(params_extra):
nonlocal offset
offset = 0
while len(out) < max_total:
params = dict(params_extra)
params["offset"] = offset
params["until"] = until_ms
try:
rows = exchange.fetch_positions_history(
None,
since=int(since_ms),
limit=page_limit,
params=params,
)
except Exception:
return False
if not rows:
break
for p in rows:
h = _normalize_gate_position_history_entry(p)
if h and h["close_ms"] and h["side"] in ("long", "short") and h["symbol_u"]:
out.append(h)
offset += len(rows)
if len(rows) < page_limit:
break
return True
if not _pull({"settle": "usdt"}):
_pull({})
return out[:max_total]
def sync_trade_records_from_exchange(conn):
"""为未同步的 trade_records 回填 Gate 平仓历史中的已实现盈亏。"""
def sync_trade_records_from_exchange(conn, force=False):
"""为未同步的 trade_records 回填 Gate 平仓历史中的已实现盈亏。返回统计 dict。"""
global _LAST_EXCHANGE_PNL_SYNC_AT
stats = {"ok": False, "hist_count": 0, "matched": 0, "pending": 0, "skipped": False}
if not exchange_private_api_configured():
return
stats["reason"] = "未配置 GATE_API_KEY / GATE_API_SECRET"
return stats
now = time.time()
if now - _LAST_EXCHANGE_PNL_SYNC_AT < 25.0:
return
if not force and now - _LAST_EXCHANGE_PNL_SYNC_AT < 25.0:
stats["ok"] = True
stats["skipped"] = True
return stats
try:
hist = fetch_gate_positions_close_history()
except Exception:
return
except Exception as e:
stats["reason"] = str(e)
return stats
stats["hist_count"] = len(hist)
if not hist:
_LAST_EXCHANGE_PNL_SYNC_AT = now
return
stats["ok"] = True
stats["reason"] = "交易所平仓历史为空(请检查 API 权限或 EXCHANGE_POSITION_SYNC_FROM_BJ"
return stats
candidates = conn.execute(
"""
SELECT id, symbol, direction, closed_at, closed_at_ms, opened_at, opened_at_ms
FROM trade_records
WHERE (exchange_sync_key IS NULL OR TRIM(exchange_sync_key) = '')
OR exchange_realized_pnl IS NULL
ORDER BY id DESC
LIMIT 200
"""
).fetchall()
stats["pending"] = len(candidates)
if not candidates:
stats["ok"] = True
_LAST_EXCHANGE_PNL_SYNC_AT = now
return
return stats
used = set()
matched = 0
for tr in candidates:
close_ms_trade = _to_ms_with_fallback(
tr["closed_at_ms"] if "closed_at_ms" in tr.keys() else None, tr["closed_at"]
@@ -4848,11 +4872,15 @@ def sync_trade_records_from_exchange(conn):
(float(pnl_val), eo, ec, sk, int(tr["id"])),
)
used.add(sk)
matched += 1
stats["matched"] = matched
stats["ok"] = True
_LAST_EXCHANGE_PNL_SYNC_AT = now
try:
conn.commit()
except Exception:
pass
return stats
# ====================== 主页面 ======================
@@ -4874,11 +4902,12 @@ def render_main_page(page="trade"):
order_list = []
for o in raw_order_list:
order_list.append(enrich_order_item(row_to_dict(o), current_capital))
exchange_pnl_sync = {}
if exchange_private_api_configured():
try:
sync_trade_records_from_exchange(conn)
except Exception:
pass
exchange_pnl_sync = sync_trade_records_from_exchange(conn) or {}
except Exception as e:
exchange_pnl_sync = {"ok": False, "reason": str(e)}
raw_records = conn.execute("SELECT * FROM trade_records ORDER BY id DESC").fetchall()
records = [to_effective_trade_dict(r) for r in raw_records]
total = len(records)
@@ -4947,9 +4976,23 @@ def render_main_page(page="trade"):
key_auto_min_planned_rr=KEY_AUTO_MIN_PLANNED_RR,
key_gate_rule_text=key_gate_rule_text,
kline_timeframe=KLINE_TIMEFRAME,
exchange_pnl_sync=exchange_pnl_sync,
)
@app.route("/api/sync_exchange_pnl")
@login_required
def api_sync_exchange_pnl():
conn = get_db()
stats = sync_trade_records_from_exchange(conn, force=True)
try:
conn.commit()
except Exception:
pass
conn.close()
return jsonify(stats)
@app.route("/")
@login_required
def index():
+28
View File
@@ -493,6 +493,16 @@
{% if page == 'records' %}
<div class="card full records-card">
<h2>交易记录 & 错过机会</h2>
<div class="rule-tip" style="margin-bottom:8px;font-size:.78rem">
盈亏U:<span style="color:#6ab88a"></span>=交易所平仓历史,
<span style="color:#8892b0"></span>=本地估算。
{% if exchange_pnl_sync %}
{% if exchange_pnl_sync.skipped %}25秒内已同步,可点右侧按钮强制){% else %}
本轮:平仓历史 {{ exchange_pnl_sync.hist_count or 0 }} 条,对齐 {{ exchange_pnl_sync.matched or 0 }} 笔{% if exchange_pnl_sync.reason %} — {{ exchange_pnl_sync.reason }}{% endif %}
{% endif %}
{% endif %}
<button type="button" id="sync-exchange-pnl-btn" style="margin-left:8px;padding:4px 10px;background:#1f3a5a;color:#8fc8ff;border:none;border-radius:6px;cursor:pointer">立即同步</button>
</div>
<div class="form-row" style="margin-bottom:10px;gap:8px">
<label style="display:flex;align-items:center;gap:6px;font-size:.82rem;color:#cfd3ef">
<input id="review-mode-toggle" type="checkbox">
@@ -1704,6 +1714,24 @@ function refreshPriceSnapshotConditional(){
}).catch(()=>{});
}
setInterval(refreshPriceSnapshotConditional, {{ price_refresh_seconds * 1000 }});
const syncExchangePnlBtn = document.getElementById("sync-exchange-pnl-btn");
if(syncExchangePnlBtn){
syncExchangePnlBtn.addEventListener("click", ()=>{
syncExchangePnlBtn.disabled = true;
syncExchangePnlBtn.innerText = "同步中…";
fetch("/api/sync_exchange_pnl").then(r=>r.json()).then(data=>{
const msg = data.ok
? `平仓历史 ${data.hist_count||0} 条,对齐 ${data.matched||0}${data.reason?("\n"+data.reason):""}`
: (data.reason || "同步失败");
alert(msg);
if((data.matched||0) > 0) location.reload();
}).catch(()=>alert("同步请求失败")).finally(()=>{
syncExchangePnlBtn.disabled = false;
syncExchangePnlBtn.innerText = "立即同步";
});
});
}
</script>
</body>
</html>