Force-refresh header balances after transfers and balance SSE events.
Invalidate balance caches on mutation, push SSE balance tick, and bypass 60s cache when refreshing funds. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2526,6 +2526,10 @@ def friendly_okx_error(err, available_usdt=None):
|
|||||||
friendly_exchange_error = friendly_okx_error
|
friendly_exchange_error = friendly_okx_error
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_account_balance_cache() -> None:
|
||||||
|
ACCOUNT_BALANCE_CACHE["updated_at"] = 0
|
||||||
|
|
||||||
|
|
||||||
def get_exchange_capitals(force=False):
|
def get_exchange_capitals(force=False):
|
||||||
ok_live, _ = ensure_okx_live_ready()
|
ok_live, _ = ensure_okx_live_ready()
|
||||||
if not ok_live:
|
if not ok_live:
|
||||||
@@ -9286,6 +9290,13 @@ def manual_transfer():
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
if ok:
|
if ok:
|
||||||
|
invalidate_account_balance_cache()
|
||||||
|
try:
|
||||||
|
from lib.instance.instance_live_push_lib import notify_instance_balance_changed
|
||||||
|
|
||||||
|
notify_instance_balance_changed()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
||||||
else:
|
else:
|
||||||
flash(f"手动划转失败:{msg}")
|
flash(f"手动划转失败:{msg}")
|
||||||
|
|||||||
@@ -32,12 +32,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleRefresh() {
|
function scheduleRefresh(opts) {
|
||||||
if (refreshTimer) return;
|
if (refreshTimer) return;
|
||||||
|
const options = opts || {};
|
||||||
refreshTimer = setTimeout(function () {
|
refreshTimer = setTimeout(function () {
|
||||||
refreshTimer = null;
|
refreshTimer = null;
|
||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
refreshTabData(currentTab(), { silent: true });
|
refreshTabData(currentTab(), { silent: true, force: !!options.force });
|
||||||
}, 80);
|
}, 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
if (ver === localLiveVersion) return;
|
if (ver === localLiveVersion) return;
|
||||||
localLiveVersion = ver;
|
localLiveVersion = ver;
|
||||||
scheduleRefresh();
|
scheduleRefresh({ force: reason === "balance" });
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeLiveStream() {
|
function closeLiveStream() {
|
||||||
|
|||||||
@@ -9,6 +9,14 @@
|
|||||||
return r.json();
|
return r.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshFundsAfterMutation() {
|
||||||
|
if (typeof refreshAccountSnapshot !== "function") return;
|
||||||
|
refreshAccountSnapshot({ force: true });
|
||||||
|
setTimeout(function () {
|
||||||
|
refreshAccountSnapshot({ force: true, silent: true });
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
function setMsg(id, text, isErr) {
|
function setMsg(id, text, isErr) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -50,7 +58,7 @@
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setMsg("opt-set-swap-msg", d.ok ? "兑换单已提交" : (d.msg || "失败"), !d.ok);
|
setMsg("opt-set-swap-msg", d.ok ? "兑换单已提交" : (d.msg || "失败"), !d.ok);
|
||||||
if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
|
if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation();
|
||||||
}
|
}
|
||||||
|
|
||||||
const swapBtn = document.getElementById("opt-set-swap-btn");
|
const swapBtn = document.getElementById("opt-set-swap-btn");
|
||||||
@@ -96,7 +104,7 @@
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setMsg("opt-set-int-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
setMsg("opt-set-int-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
||||||
if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
|
if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation();
|
||||||
}
|
}
|
||||||
|
|
||||||
const intBtn = document.getElementById("opt-set-int-btn");
|
const intBtn = document.getElementById("opt-set-int-btn");
|
||||||
@@ -143,7 +151,7 @@
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setMsg("opt-set-cross-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
setMsg("opt-set-cross-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
||||||
if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
|
if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation();
|
||||||
}
|
}
|
||||||
|
|
||||||
const crossBtn = document.getElementById("opt-set-cross-btn");
|
const crossBtn = document.getElementById("opt-set-cross-btn");
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ _OKX_OPTION_ERR_ZH: dict[str, str] = {
|
|||||||
_OPTIONS_BALANCE_CACHE: dict[str, Any] = {"updated_at": 0.0, "data": None}
|
_OPTIONS_BALANCE_CACHE: dict[str, Any] = {"updated_at": 0.0, "data": None}
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_options_balance_cache() -> None:
|
||||||
|
_OPTIONS_BALANCE_CACHE["updated_at"] = 0.0
|
||||||
|
_OPTIONS_BALANCE_CACHE["data"] = None
|
||||||
|
|
||||||
|
|
||||||
def _okx_trade_error_message(exc: BaseException | None = None, resp: Any = None) -> str:
|
def _okx_trade_error_message(exc: BaseException | None = None, resp: Any = None) -> str:
|
||||||
row: dict[str, Any] | None = None
|
row: dict[str, Any] | None = None
|
||||||
if isinstance(resp, dict):
|
if isinstance(resp, dict):
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ class InstanceLivePush:
|
|||||||
instance_live_push = InstanceLivePush()
|
instance_live_push = InstanceLivePush()
|
||||||
|
|
||||||
|
|
||||||
|
def notify_instance_balance_changed() -> int:
|
||||||
|
"""划转/兑换后通知 embed 壳拉最新资金快照."""
|
||||||
|
return instance_live_push.tick("balance")
|
||||||
|
|
||||||
|
|
||||||
def register_instance_live_routes(app: Flask, login_required: Callable) -> None:
|
def register_instance_live_routes(app: Flask, login_required: Callable) -> None:
|
||||||
instance_live_push.start()
|
instance_live_push.start()
|
||||||
|
|
||||||
|
|||||||
@@ -1166,11 +1166,11 @@ function applyAccountSnapshot(data){
|
|||||||
}
|
}
|
||||||
if(data.options_funding_usdc != null || data.options_funding_usdt != null){
|
if(data.options_funding_usdc != null || data.options_funding_usdt != null){
|
||||||
const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt);
|
const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt);
|
||||||
if(optFunding !== "—") setFundsFieldText("options-funding-usdc", optFunding);
|
setFundsFieldText("options-funding-usdc", optFunding);
|
||||||
}
|
}
|
||||||
if(data.options_trading_usdc != null || data.options_trading_usdt != null){
|
if(data.options_trading_usdc != null || data.options_trading_usdt != null){
|
||||||
const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt);
|
const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt);
|
||||||
if(optTrading !== "—") setFundsFieldText("options-trading-usdc", optTrading);
|
setFundsFieldText("options-trading-usdc", optTrading);
|
||||||
}
|
}
|
||||||
if(typeof data.unrealized_pnl !== "undefined"){
|
if(typeof data.unrealized_pnl !== "undefined"){
|
||||||
updateRealtimePnl(data.unrealized_pnl);
|
updateRealtimePnl(data.unrealized_pnl);
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
|
|||||||
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/instance_settings_prefs.js?v=12"></script>
|
<script src="/static/instance_settings_prefs.js?v=12"></script>
|
||||||
<script src="/static/instance_live.js?v=4"></script>
|
<script src="/static/instance_live.js?v=5"></script>
|
||||||
<script src="/static/instance_embed.js?v=23"></script>
|
<script src="/static/instance_embed.js?v=23"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1811,11 +1811,11 @@ function applyAccountSnapshot(data){
|
|||||||
}
|
}
|
||||||
if(data.options_funding_usdc != null || data.options_funding_usdt != null){
|
if(data.options_funding_usdc != null || data.options_funding_usdt != null){
|
||||||
const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt);
|
const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt);
|
||||||
if(optFunding !== "—") setFundsFieldText("options-funding-usdc", optFunding);
|
setFundsFieldText("options-funding-usdc", optFunding);
|
||||||
}
|
}
|
||||||
if(data.options_trading_usdc != null || data.options_trading_usdt != null){
|
if(data.options_trading_usdc != null || data.options_trading_usdt != null){
|
||||||
const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt);
|
const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt);
|
||||||
if(optTrading !== "—") setFundsFieldText("options-trading-usdc", optTrading);
|
setFundsFieldText("options-trading-usdc", optTrading);
|
||||||
}
|
}
|
||||||
if(typeof data.unrealized_pnl !== "undefined"){
|
if(typeof data.unrealized_pnl !== "undefined"){
|
||||||
updateRealtimePnl(data.unrealized_pnl);
|
updateRealtimePnl(data.unrealized_pnl);
|
||||||
|
|||||||
@@ -123,9 +123,24 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
|||||||
"spot_market_swap_usdt_usdc": spot_market_swap_usdt_usdc,
|
"spot_market_swap_usdt_usdc": spot_market_swap_usdt_usdc,
|
||||||
"transfer_main_sub_account": transfer_main_sub_account,
|
"transfer_main_sub_account": transfer_main_sub_account,
|
||||||
"options_api_ready": options_api_ready,
|
"options_api_ready": options_api_ready,
|
||||||
|
"app_module": app_module,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _mark_balances_stale(cfg: dict[str, Any]) -> None:
|
||||||
|
from lib.exchange.okx_options_lib import invalidate_options_balance_cache
|
||||||
|
from lib.instance.instance_live_push_lib import notify_instance_balance_changed
|
||||||
|
|
||||||
|
invalidate_options_balance_cache()
|
||||||
|
app_mod = cfg.get("app_module")
|
||||||
|
if app_mod is not None and hasattr(app_mod, "invalidate_account_balance_cache"):
|
||||||
|
app_mod.invalidate_account_balance_cache()
|
||||||
|
try:
|
||||||
|
notify_instance_balance_changed()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _require_options_ex(cfg: dict[str, Any]):
|
def _require_options_ex(cfg: dict[str, Any]):
|
||||||
if not cfg.get("enabled"):
|
if not cfg.get("enabled"):
|
||||||
return None, "期权模块未启用,请在 .env 设置 OKX_OPTIONS_ENABLED=true 并重启 PM2"
|
return None, "期权模块未启用,请在 .env 设置 OKX_OPTIONS_ENABLED=true 并重启 PM2"
|
||||||
@@ -791,6 +806,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
_mark_balances_stale(cfg)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
@app.route("/api/options/spot/swap", methods=["POST"])
|
@app.route("/api/options/spot/swap", methods=["POST"])
|
||||||
@@ -805,7 +821,10 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
amount = float(data.get("amount"))
|
amount = float(data.get("amount"))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return jsonify({"ok": False, "msg": "数量无效"})
|
return jsonify({"ok": False, "msg": "数量无效"})
|
||||||
return jsonify(cfg["spot_market_swap_usdt_usdc"](ex, direction=direction, amount=amount))
|
result = cfg["spot_market_swap_usdt_usdc"](ex, direction=direction, amount=amount)
|
||||||
|
if result.get("ok"):
|
||||||
|
_mark_balances_stale(cfg)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
@app.route("/api/options/cross-transfer", methods=["POST"])
|
@app.route("/api/options/cross-transfer", methods=["POST"])
|
||||||
@lr
|
@lr
|
||||||
@@ -852,6 +871,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
_mark_balances_stale(cfg)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
@app.route("/api/options/history")
|
@app.route("/api/options/history")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{# 期权设置脚本挂载点(卡片在 settings_panel 中拆分) #}
|
{# 期权设置脚本挂载点(卡片在 settings_panel 中拆分) #}
|
||||||
<div id="options-settings-root" hidden
|
<div id="options-settings-root" hidden
|
||||||
data-sub-account="{{ instance_settings.options_sub_account | default('', true) }}"></div>
|
data-sub-account="{{ instance_settings.options_sub_account | default('', true) }}"></div>
|
||||||
<script src="/static/options_settings.js?v=6"></script>
|
<script src="/static/options_settings.js?v=7"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user