修复复盘列表先读缓存再后台同步,避免模拟盘握库卡死加载。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -168,10 +168,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reloadAll() {
|
function reloadAll() {
|
||||||
|
// 先无同步出列表(统计同理),再后台同步;避免模拟盘握库+拉行情把列表卡死在「加载中」
|
||||||
setSyncStatus("读取本地记录…");
|
setSyncStatus("读取本地记录…");
|
||||||
loadTrades({ sync: true });
|
loadTrades({ sync: false });
|
||||||
loadReviewed({ sync: false });
|
loadReviewed({ sync: false });
|
||||||
loadStats();
|
loadStats();
|
||||||
|
backgroundSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function backgroundSync() {
|
||||||
|
setSyncStatus("同步本地记录…");
|
||||||
|
fetch("/api/options/review/sync", { credentials: "same-origin", method: "POST" })
|
||||||
|
.then(function (r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function (data) {
|
||||||
|
if (data && data.ok === false) {
|
||||||
|
setSyncStatus("同步跳过,已显示本地缓存");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSyncStatus("本地记录已加载");
|
||||||
|
loadTrades({ sync: false, soft: true });
|
||||||
|
loadReviewed({ sync: false, soft: true });
|
||||||
|
loadStats();
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
setSyncStatus("同步跳过,已显示本地缓存");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHedgeSource(sourceType) {
|
function isHedgeSource(sourceType) {
|
||||||
@@ -451,6 +474,7 @@
|
|||||||
endListLoad(wrap);
|
endListLoad(wrap);
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
|
if (doSync) setSyncStatus("加载失败");
|
||||||
tbody.innerHTML = '<tr><td colspan="7" class="muted">加载失败</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="7" class="muted">加载失败</td></tr>';
|
||||||
endListLoad(wrap);
|
endListLoad(wrap);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -302,6 +302,11 @@ def sync_options_from_local_trades(
|
|||||||
LIMIT 500
|
LIMIT 500
|
||||||
"""
|
"""
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
# 读完先提交,后续拉指数不与写事务叠在一起
|
||||||
|
try:
|
||||||
|
conn.commit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def _index_px(underly: str) -> float | None:
|
def _index_px(underly: str) -> float | None:
|
||||||
u = (underly or "ETH").strip().upper() or "ETH"
|
u = (underly or "ETH").strip().upper() or "ETH"
|
||||||
@@ -356,6 +361,18 @@ def sync_options_from_local_trades(
|
|||||||
|
|
||||||
inserted = updated = skipped = 0
|
inserted = updated = skipped = 0
|
||||||
idx_cache: dict[str, float | None] = {}
|
idx_cache: dict[str, float | None] = {}
|
||||||
|
need_idx: set[str] = set()
|
||||||
|
for r in rows:
|
||||||
|
inst0 = str(r["inst_id"] or "")
|
||||||
|
underly0 = str(r["underlying"] or (inst0.split("-")[0] if inst0 else "ETH") or "ETH")
|
||||||
|
ccy0 = str(r["premium_ccy"] or "").strip().upper()
|
||||||
|
if not ccy0:
|
||||||
|
ccy0 = premium_ccy_for_mode(margin_mode_from_inst_id(inst0), underly0)
|
||||||
|
if ccy0 in ("ETH", "BTC"):
|
||||||
|
need_idx.add(underly0)
|
||||||
|
for underly0 in need_idx:
|
||||||
|
idx_cache[underly0] = _index_px(underly0)
|
||||||
|
|
||||||
for r in rows:
|
for r in rows:
|
||||||
trade_id = int(r["id"])
|
trade_id = int(r["id"])
|
||||||
history_key = f"local_opt:{trade_id}"
|
history_key = f"local_opt:{trade_id}"
|
||||||
@@ -364,8 +381,6 @@ def sync_options_from_local_trades(
|
|||||||
ccy = str(r["premium_ccy"] or "").strip().upper()
|
ccy = str(r["premium_ccy"] or "").strip().upper()
|
||||||
if not ccy:
|
if not ccy:
|
||||||
ccy = premium_ccy_for_mode(margin_mode_from_inst_id(inst), underly)
|
ccy = premium_ccy_for_mode(margin_mode_from_inst_id(inst), underly)
|
||||||
if underly not in idx_cache:
|
|
||||||
idx_cache[underly] = _index_px(underly)
|
|
||||||
idx = idx_cache.get(underly)
|
idx = idx_cache.get(underly)
|
||||||
pnl = _safe_float(r["realized_pnl"])
|
pnl = _safe_float(r["realized_pnl"])
|
||||||
if pnl is None:
|
if pnl is None:
|
||||||
@@ -644,6 +659,11 @@ def ensure_local_review_synced(
|
|||||||
note_swap = repair_swapped_closed_at_signal_note(conn)
|
note_swap = repair_swapped_closed_at_signal_note(conn)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
note_swap = {"ok": False, "msg": str(e)}
|
note_swap = {"ok": False, "msg": str(e)}
|
||||||
|
# 提交后再拉历史/行情,避免模拟盘 broker 二次开库与本连接互锁
|
||||||
|
try:
|
||||||
|
conn.commit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if backfill_exchange_pnl and ex is not None:
|
if backfill_exchange_pnl and ex is not None:
|
||||||
try:
|
try:
|
||||||
from lib.exchange.okx_options_lib import fetch_all_option_positions_history
|
from lib.exchange.okx_options_lib import fetch_all_option_positions_history
|
||||||
@@ -653,8 +673,12 @@ def ensure_local_review_synced(
|
|||||||
|
|
||||||
hist = fetch_all_option_positions_history(ex, limit=200)
|
hist = fetch_all_option_positions_history(ex, limit=200)
|
||||||
backfill_closed_options_realized_pnl_from_history(conn, hist)
|
backfill_closed_options_realized_pnl_from_history(conn, hist)
|
||||||
|
conn.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
try:
|
||||||
|
conn.rollback()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
out = sync_all_review_sources(conn, ex=ex, from_exchange=False)
|
out = sync_all_review_sources(conn, ex=ex, from_exchange=False)
|
||||||
except sqlite3.OperationalError as e:
|
except sqlite3.OperationalError as e:
|
||||||
@@ -662,6 +686,8 @@ def ensure_local_review_synced(
|
|||||||
if isinstance(out, dict):
|
if isinstance(out, dict):
|
||||||
out["ts_migrate"] = ts_migrate
|
out["ts_migrate"] = ts_migrate
|
||||||
out["note_swap"] = note_swap
|
out["note_swap"] = note_swap
|
||||||
|
if out.get("ok") is not False:
|
||||||
|
out["ok"] = True
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,16 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s
|
|||||||
ex, _err = _require_ex(cfg)
|
ex, _err = _require_ex(cfg)
|
||||||
result = ensure_local_review_synced(conn, ex=ex if ex is not None else None)
|
result = ensure_local_review_synced(conn, ex=ex if ex is not None else None)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return jsonify(result)
|
if isinstance(result, dict) and result.get("ok") is not False:
|
||||||
|
result["ok"] = True
|
||||||
|
return jsonify(result if isinstance(result, dict) else {"ok": True, "result": result})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[options_review_sync] {e}", flush=True)
|
||||||
|
try:
|
||||||
|
conn.rollback()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return jsonify({"ok": False, "msg": f"同步失败: {e}"}), 500
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -284,16 +293,21 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s
|
|||||||
def api_options_review_trades():
|
def api_options_review_trades():
|
||||||
conn = cfg["get_db"]()
|
conn = cfg["get_db"]()
|
||||||
try:
|
try:
|
||||||
# 翻页可跳过同步,仅刷新当前卡片列表
|
# 默认不同步:列表先读缓存;完整同步走 POST /api/options/review/sync
|
||||||
do_sync = (request.args.get("sync") or "1").strip().lower() not in (
|
do_sync = (request.args.get("sync") or "0").strip().lower() in (
|
||||||
"0",
|
"1",
|
||||||
"false",
|
"true",
|
||||||
"no",
|
"yes",
|
||||||
)
|
)
|
||||||
if do_sync:
|
if do_sync:
|
||||||
try:
|
try:
|
||||||
ex, _err = _require_ex(cfg)
|
ex, _err = _require_ex(cfg)
|
||||||
ensure_local_review_synced(conn, ex=ex if ex is not None else None)
|
# 列表路径不做历史回填,避免模拟盘握库二次开连接卡死
|
||||||
|
ensure_local_review_synced(
|
||||||
|
conn,
|
||||||
|
ex=ex if ex is not None else None,
|
||||||
|
backfill_exchange_pnl=False,
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except Exception as sync_err:
|
except Exception as sync_err:
|
||||||
# 同步撞锁时仍返回已有列表,避免整页 500
|
# 同步撞锁时仍返回已有列表,避免整页 500
|
||||||
@@ -303,6 +317,8 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
init_options_review_tables(conn)
|
init_options_review_tables(conn)
|
||||||
|
else:
|
||||||
|
init_options_review_tables(conn)
|
||||||
filt = dict(
|
filt = dict(
|
||||||
source_type=_review_source_for_mode(request.args.get("source_type")),
|
source_type=_review_source_for_mode(request.args.get("source_type")),
|
||||||
underlying=(request.args.get("underlying") or "").strip() or None,
|
underlying=(request.args.get("underlying") or "").strip() or None,
|
||||||
|
|||||||
@@ -456,4 +456,4 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/options_review.js?v=25"></script>
|
<script src="/static/options_review.js?v=26"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user