fix(okx): cache options balances and stop SSE from hammering exchange API
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+11
-17
@@ -6516,15 +6516,11 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
and embed_mode != "fragment"
|
and embed_mode != "fragment"
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
from lib.exchange.okx_options_lib import (
|
from lib.exchange.okx_options_lib import options_header_balances
|
||||||
fetch_options_funding_usdc,
|
|
||||||
fetch_options_funding_usdt,
|
|
||||||
fetch_options_trading_usdc,
|
|
||||||
)
|
|
||||||
|
|
||||||
options_trading_usdc = fetch_options_trading_usdc(exchange_options)
|
options_trading_usdc, options_funding_usdc, options_funding_usdt = options_header_balances(
|
||||||
options_funding_usdc = fetch_options_funding_usdc(exchange_options)
|
exchange_options
|
||||||
options_funding_usdt = fetch_options_funding_usdt(exchange_options)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
options_trading_usdc = None
|
options_trading_usdc = None
|
||||||
options_funding_usdc = None
|
options_funding_usdc = None
|
||||||
@@ -6838,7 +6834,8 @@ def api_account_snapshot():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
session_row = ensure_session(conn, trading_day)
|
session_row = ensure_session(conn, trading_day)
|
||||||
local_current_capital = float(session_row["current_capital"])
|
local_current_capital = float(session_row["current_capital"])
|
||||||
funding_capital, trading_capital = get_exchange_capitals(force=True)
|
force_refresh = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
|
||||||
|
funding_capital, trading_capital = get_exchange_capitals(force=force_refresh)
|
||||||
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
||||||
current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
|
current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
|
||||||
options_trading_usdc = None
|
options_trading_usdc = None
|
||||||
@@ -6846,15 +6843,12 @@ def api_account_snapshot():
|
|||||||
options_funding_usdt = None
|
options_funding_usdt = None
|
||||||
if OKX_OPTIONS_ENABLED and exchange_options.apiKey:
|
if OKX_OPTIONS_ENABLED and exchange_options.apiKey:
|
||||||
try:
|
try:
|
||||||
from lib.exchange.okx_options_lib import (
|
from lib.exchange.okx_options_lib import options_header_balances
|
||||||
fetch_options_funding_usdc,
|
|
||||||
fetch_options_funding_usdt,
|
|
||||||
fetch_options_trading_usdc,
|
|
||||||
)
|
|
||||||
|
|
||||||
options_trading_usdc = fetch_options_trading_usdc(exchange_options)
|
options_trading_usdc, options_funding_usdc, options_funding_usdt = options_header_balances(
|
||||||
options_funding_usdc = fetch_options_funding_usdc(exchange_options)
|
exchange_options,
|
||||||
options_funding_usdt = fetch_options_funding_usdt(exchange_options)
|
force=force_refresh,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
options_trading_usdc = None
|
options_trading_usdc = None
|
||||||
options_funding_usdc = None
|
options_funding_usdc = None
|
||||||
|
|||||||
@@ -206,11 +206,14 @@
|
|||||||
function preloadAllTabs() {
|
function preloadAllTabs() {
|
||||||
const tabs = Object.keys(TAB_PATH);
|
const tabs = Object.keys(TAB_PATH);
|
||||||
const current = getTab();
|
const current = getTab();
|
||||||
|
const heavyLast = new Set(["options", "records", "stats"]);
|
||||||
|
const ordered = tabs.filter((t) => t !== current && !heavyLast.has(t))
|
||||||
|
.concat(tabs.filter((t) => heavyLast.has(t) && t !== current));
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
function step() {
|
function step() {
|
||||||
if (idx >= tabs.length) return;
|
if (idx >= ordered.length) return;
|
||||||
const tab = tabs[idx++];
|
const tab = ordered[idx++];
|
||||||
if (tab === current || tabPanes.has(tab)) {
|
if (tabPanes.has(tab)) {
|
||||||
step();
|
step();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,11 +223,11 @@
|
|||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setTimeout(step, 120);
|
setTimeout(step, heavyLast.has(tab) ? 400 : 180);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const ric = global.requestIdleCallback || function (fn) {
|
const ric = global.requestIdleCallback || function (fn) {
|
||||||
setTimeout(fn, 1200);
|
setTimeout(fn, 2000);
|
||||||
};
|
};
|
||||||
ric(step);
|
ric(step);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
let liveReconnectTimer = null;
|
let liveReconnectTimer = null;
|
||||||
let localLiveVersion = -1;
|
let localLiveVersion = -1;
|
||||||
let sseConnected = false;
|
let sseConnected = false;
|
||||||
|
let refreshTimer = null;
|
||||||
|
|
||||||
function isEmbedShell() {
|
function isEmbedShell() {
|
||||||
return document.body && document.body.getAttribute("data-embed-shell") === "1";
|
return document.body && document.body.getAttribute("data-embed-shell") === "1";
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
function refreshTabData(tab, opts) {
|
function refreshTabData(tab, opts) {
|
||||||
const options = opts || {};
|
const options = opts || {};
|
||||||
if (typeof global.refreshAccountSnapshot === "function") {
|
if (typeof global.refreshAccountSnapshot === "function") {
|
||||||
global.refreshAccountSnapshot();
|
global.refreshAccountSnapshot(options);
|
||||||
}
|
}
|
||||||
if (typeof global.refreshPriceSnapshotConditional === "function") {
|
if (typeof global.refreshPriceSnapshotConditional === "function") {
|
||||||
global.refreshPriceSnapshotConditional();
|
global.refreshPriceSnapshotConditional();
|
||||||
@@ -31,6 +32,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleRefresh() {
|
||||||
|
if (refreshTimer) return;
|
||||||
|
refreshTimer = setTimeout(function () {
|
||||||
|
refreshTimer = null;
|
||||||
|
if (document.hidden) return;
|
||||||
|
refreshTabData(currentTab(), { silent: true });
|
||||||
|
}, 80);
|
||||||
|
}
|
||||||
|
|
||||||
function onLiveEvent(data) {
|
function onLiveEvent(data) {
|
||||||
const reason = data && data.reason;
|
const reason = data && data.reason;
|
||||||
const ver = Number(data && data.live_version) || 0;
|
const ver = Number(data && data.live_version) || 0;
|
||||||
@@ -41,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
if (ver === localLiveVersion) return;
|
if (ver === localLiveVersion) return;
|
||||||
localLiveVersion = ver;
|
localLiveVersion = ver;
|
||||||
refreshTabData(currentTab(), { silent: true });
|
scheduleRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeLiveStream() {
|
function closeLiveStream() {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ _OKX_OPTION_ERR_ZH: dict[str, str] = {
|
|||||||
"51019": "期权买入须使用逐仓模式(全仓模式下不能持有多头净头寸)",
|
"51019": "期权买入须使用逐仓模式(全仓模式下不能持有多头净头寸)",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_OPTIONS_BALANCE_CACHE: dict[str, Any] = {"updated_at": 0.0, "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
|
||||||
@@ -199,7 +201,15 @@ def fetch_account_balances_by_type(ex: ccxt.okx, account_type: str) -> dict[str,
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def fetch_options_balances(ex: ccxt.okx) -> dict[str, Any]:
|
def fetch_options_balances(ex: ccxt.okx, *, force: bool = False) -> dict[str, Any]:
|
||||||
|
import os
|
||||||
|
|
||||||
|
ttl = float(os.getenv("OKX_OPTIONS_BALANCE_REFRESH_SEC", "30"))
|
||||||
|
now = time.time()
|
||||||
|
cached = _OPTIONS_BALANCE_CACHE.get("data")
|
||||||
|
if not force and cached is not None and now - float(_OPTIONS_BALANCE_CACHE.get("updated_at") or 0) < ttl:
|
||||||
|
return dict(cached)
|
||||||
|
|
||||||
funding = fetch_account_balances_by_type(ex, "funding")
|
funding = fetch_account_balances_by_type(ex, "funding")
|
||||||
trading = fetch_account_balances_by_type(ex, "trading")
|
trading = fetch_account_balances_by_type(ex, "trading")
|
||||||
# 统一账户部分 USDC 可能在 swap 类型
|
# 统一账户部分 USDC 可能在 swap 类型
|
||||||
@@ -207,7 +217,7 @@ def fetch_options_balances(ex: ccxt.okx) -> dict[str, Any]:
|
|||||||
swap_bal = fetch_account_balances_by_type(ex, "swap")
|
swap_bal = fetch_account_balances_by_type(ex, "swap")
|
||||||
if swap_bal.get("USDC") is not None:
|
if swap_bal.get("USDC") is not None:
|
||||||
trading["USDC"] = swap_bal["USDC"]
|
trading["USDC"] = swap_bal["USDC"]
|
||||||
return {
|
result = {
|
||||||
"funding_usdt": funding.get("USDT"),
|
"funding_usdt": funding.get("USDT"),
|
||||||
"funding_usdc": funding.get("USDC"),
|
"funding_usdc": funding.get("USDC"),
|
||||||
"funding_usdg": funding.get("USDG"),
|
"funding_usdg": funding.get("USDG"),
|
||||||
@@ -215,6 +225,32 @@ def fetch_options_balances(ex: ccxt.okx) -> dict[str, Any]:
|
|||||||
"trading_usdc": trading.get("USDC"),
|
"trading_usdc": trading.get("USDC"),
|
||||||
"trading_usdg": trading.get("USDG"),
|
"trading_usdg": trading.get("USDG"),
|
||||||
}
|
}
|
||||||
|
_OPTIONS_BALANCE_CACHE["updated_at"] = now
|
||||||
|
_OPTIONS_BALANCE_CACHE["data"] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def options_header_balances(
|
||||||
|
ex: ccxt.okx,
|
||||||
|
*,
|
||||||
|
force: bool = False,
|
||||||
|
) -> tuple[float | None, float | None, float | None]:
|
||||||
|
"""顶栏三格:交易 USDC、资金 USDC、资金 USDT(单次拉取 + 缓存)。"""
|
||||||
|
bal = fetch_options_balances(ex, force=force)
|
||||||
|
|
||||||
|
def _round(v: Any) -> float | None:
|
||||||
|
if v is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return round(float(v), 2)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return (
|
||||||
|
_round(bal.get("trading_usdc")),
|
||||||
|
_round(bal.get("funding_usdc")),
|
||||||
|
_round(bal.get("funding_usdt")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def fetch_index_price(ex: ccxt.okx, uly: str) -> float | None:
|
def fetch_index_price(ex: ccxt.okx, uly: str) -> float | None:
|
||||||
@@ -525,24 +561,24 @@ def transfer_ccy(
|
|||||||
_OKX_ACCT_CODE = {"funding": "6", "trading": "18", "spot": "18"}
|
_OKX_ACCT_CODE = {"funding": "6", "trading": "18", "spot": "18"}
|
||||||
|
|
||||||
|
|
||||||
def fetch_options_trading_usdc(ex: ccxt.okx) -> float | None:
|
def fetch_options_trading_usdc(ex: ccxt.okx, *, force: bool = False) -> float | None:
|
||||||
bal = fetch_options_balances(ex)
|
bal = fetch_options_balances(ex, force=force)
|
||||||
v = bal.get("trading_usdc")
|
v = bal.get("trading_usdc")
|
||||||
if v is None:
|
if v is None:
|
||||||
return None
|
return None
|
||||||
return round(float(v), 2)
|
return round(float(v), 2)
|
||||||
|
|
||||||
|
|
||||||
def fetch_options_funding_usdc(ex: ccxt.okx) -> float | None:
|
def fetch_options_funding_usdc(ex: ccxt.okx, *, force: bool = False) -> float | None:
|
||||||
bal = fetch_options_balances(ex)
|
bal = fetch_options_balances(ex, force=force)
|
||||||
v = bal.get("funding_usdc")
|
v = bal.get("funding_usdc")
|
||||||
if v is None:
|
if v is None:
|
||||||
return None
|
return None
|
||||||
return round(float(v), 2)
|
return round(float(v), 2)
|
||||||
|
|
||||||
|
|
||||||
def fetch_options_funding_usdt(ex: ccxt.okx) -> float | None:
|
def fetch_options_funding_usdt(ex: ccxt.okx, *, force: bool = False) -> float | None:
|
||||||
bal = fetch_options_balances(ex)
|
bal = fetch_options_balances(ex, force=force)
|
||||||
v = bal.get("funding_usdt")
|
v = bal.get("funding_usdt")
|
||||||
if v is None:
|
if v is None:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1054,8 +1054,10 @@ function formatOptionsFundingLabel(usdc, usdt) {
|
|||||||
return parts.length ? parts.join(" · ") : "—";
|
return parts.length ? parts.join(" · ") : "—";
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshAccountSnapshot(){
|
function refreshAccountSnapshot(opts){
|
||||||
fetch("/api/account_snapshot").then(r=>r.json()).then(data=>{
|
const options = opts || {};
|
||||||
|
const qs = options.force ? "?force=1" : "";
|
||||||
|
fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{
|
||||||
if (typeof data.funding_usdt !== "undefined") {
|
if (typeof data.funding_usdt !== "undefined") {
|
||||||
const el = document.getElementById("total-capital");
|
const el = document.getElementById("total-capital");
|
||||||
if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
|
if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
|
||||||
@@ -1186,7 +1188,7 @@ if(window.ManualOrderRrPreview){
|
|||||||
|
|
||||||
refreshAccountSnapshot();
|
refreshAccountSnapshot();
|
||||||
const settingsRefreshFunds = document.getElementById("settings-refresh-funds");
|
const settingsRefreshFunds = document.getElementById("settings-refresh-funds");
|
||||||
if (settingsRefreshFunds) settingsRefreshFunds.addEventListener("click", refreshAccountSnapshot);
|
if (settingsRefreshFunds) settingsRefreshFunds.addEventListener("click", function(){ refreshAccountSnapshot({ force: true }); });
|
||||||
if (window.AccountRiskBadge) AccountRiskBadge.startTicker();
|
if (window.AccountRiskBadge) AccountRiskBadge.startTicker();
|
||||||
const _journalFormEl = document.getElementById("journal-form");
|
const _journalFormEl = document.getElementById("journal-form");
|
||||||
if(_journalFormEl){
|
if(_journalFormEl){
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
|
|||||||
<script src="/static/strategy_roll.js?v=6"></script>
|
<script src="/static/strategy_roll.js?v=6"></script>
|
||||||
<script src="/static/key_monitor_form.js?v=2"></script>
|
<script src="/static/key_monitor_form.js?v=2"></script>
|
||||||
{% include 'embed_boot_scripts.html' %}
|
{% include 'embed_boot_scripts.html' %}
|
||||||
<script src="/static/instance_live.js?v=2"></script>
|
<script src="/static/instance_live.js?v=3"></script>
|
||||||
<script src="/static/instance_embed.js?v=11"></script>
|
<script src="/static/instance_embed.js?v=12"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user