Revert "feat(options): push chain asks/bids via OKX WS + SSE"

This reverts commit 14a7adae1f.
This commit is contained in:
dekun
2026-08-11 12:22:41 +08:00
parent a488e2fabd
commit d592632834
7 changed files with 7 additions and 959 deletions
+6 -219
View File
@@ -7,10 +7,6 @@
root.setAttribute("data-options-booted", "1");
const panelCache = (window.__optionsPanelCache = window.__optionsPanelCache || {});
if (!panelCache.quoteWatcherId) {
panelCache.quoteWatcherId =
"w" + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
const state = {
underlying: root.dataset.defaultUnderly || "ETH",
@@ -44,17 +40,11 @@
let chainSoftTimer = null;
let lastChainSoftAt = 0;
let chainQuotedAt = 0;
let quoteLiveEs = null;
let quoteLiveReconnectTimer = null;
let quoteLiveOk = false;
let quoteLiveWsOk = false;
let lastOrderQuoteLiveAt = 0;
let pendingTtlSeconds = 600;
const POSITIONS_STALE_MS = 45000;
const PENDING_POLL_MS = 8000;
/** SSE/WS 断开时的 REST 兜底;连上后停用 */
const CHAIN_SOFT_POLL_MS = 30000;
const ORDER_QUOTE_LIVE_MIN_MS = 800;
/** 链卖一/买一静默刷新节流:无推送,靠拉;过密会撞 OKX 50011 */
const CHAIN_SOFT_POLL_MS = 15000;
const orderPanelHome = (function () {
const host = document.getElementById("opt-order-panel-host");
return host ? host.parentElement : null;
@@ -669,214 +659,16 @@
const line = document.getElementById("opt-index-line");
if (line) {
const liqHint = askLiqFilterOn() ? "仅显示卖一深度≥1张" : "显示全部卖一(含估算~)";
let liveHint = "";
if (quoteLiveOk && quoteLiveWsOk) {
liveHint = chainQuotedAt
? " · WS实时 " + fmtChainQuotedAt()
: " · WS实时";
} else if (quoteLiveOk) {
liveHint = " · 推送已连,等待 OKX WS…";
} else if (chainQuotedAt) {
liveHint = " · 链报价 " + fmtChainQuotedAt() + "REST兜底)";
}
const ageHint = chainQuotedAt ? " · 链报价 " + fmtChainQuotedAt() + "(约每15s静默刷新)" : "";
line.textContent =
"指数 " + state.underlying + " ≈ " + fmt(idx, 2) +
" · 默认最近一期 · " + liqHint + " · 实值含平值 · 虚值=价外" + liveHint;
" · 默认最近一期 · " + liqHint + " · 实值含平值 · 虚值=价外" + ageHint;
}
}
function findChainContract(instId) {
if (!state.chain || !instId) return null;
const exps = state.chain.expiries || [];
for (let i = 0; i < exps.length; i++) {
const contracts = exps[i].contracts || [];
for (let j = 0; j < contracts.length; j++) {
if (String(contracts[j].inst_id) === String(instId)) return contracts[j];
}
}
return null;
}
function currentExpiryContracts() {
if (!state.chain) return [];
const expMs = (document.getElementById("opt-exp-select") || {}).value;
const exp = (state.chain.expiries || []).find(function (e) {
return String(e.exp_time) === String(expMs);
});
return (exp && exp.contracts) || [];
}
async function watchCurrentExpiryQuotes() {
if (!state.chain) return;
const expMs = (document.getElementById("opt-exp-select") || {}).value;
const contracts = currentExpiryContracts().map(function (c) {
return {
inst_id: c.inst_id,
opt_type: c.opt_type,
strike: c.strike,
tick_sz: c.tick_sz,
};
});
if (!contracts.length) return;
try {
await apiJson("/api/options/quotes/watch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
underlying: state.underlying,
exp_time: expMs,
contracts: contracts,
index_inst_id: state.underlying + "-USD",
watcher_id: panelCache.quoteWatcherId,
}),
});
} catch (_) {
/* ignore watch errors; soft poll fallback remains */
}
}
function patchListRowDom(instId, c) {
const tr = document.querySelector('#opt-strike-tbody tr.opt-strike-row[data-inst="' + instId + '"]');
if (!tr || !c) return;
const indexPx = state.chain && state.chain.index_px;
const tds = tr.children;
if (tds.length < 8) return;
tds[3].textContent = "";
tds[3].className = "opt-px-sz";
tds[3].innerHTML = fmtPxSz(c.ask, c.ask_sz, c.ask_estimated);
tds[4].className = "opt-chain-lev";
tds[4].textContent = fmtChainLeverage(calcAskLeverage(indexPx, c.ask));
tds[5].className = "opt-px-sz";
tds[5].innerHTML = fmtPxSz(c.bid, c.bid_sz);
tds[6].textContent = c.expiry_be_px != null ? fmt(c.expiry_be_px, 0) : "—";
tds[7].className = distBeClass(c.dist_expiry_be);
tds[7].textContent = fmtDist(c.dist_expiry_be);
}
function patchTRowDom(instId, c) {
if (!c) return;
const callTr = document.querySelector(
'#opt-strike-tbody tr.opt-strike-row-t[data-call-inst="' + instId + '"]'
);
const putTr = document.querySelector(
'#opt-strike-tbody tr.opt-strike-row-t[data-put-inst="' + instId + '"]'
);
const tr = callTr || putTr;
if (!tr) return;
const callInst = tr.getAttribute("data-call-inst");
const putInst = tr.getAttribute("data-put-inst");
const call = callInst ? findChainContract(callInst) : null;
const put = putInst ? findChainContract(putInst) : null;
const callOk = call && (!askLiqFilterOn() || hasAskLiquidity(call)) ? call : null;
const putOk = put && (!askLiqFilterOn() || hasAskLiquidity(put)) ? put : null;
const tds = tr.children;
if (tds.length < 9) return;
tds[0].innerHTML = callOk ? fmtPxSz(callOk.ask, callOk.ask_sz, callOk.ask_estimated) : "—";
tds[7].innerHTML = putOk ? fmtPxSz(putOk.ask, putOk.ask_sz, putOk.ask_estimated) : "—";
const combined = straddleAskPerUnit(callOk && callOk.ask, putOk && putOk.ask);
tds[4].innerHTML = formatStraddlePremiumCell(callOk && callOk.ask, putOk && putOk.ask);
tds[5].innerHTML = formatStraddleBand(tr.getAttribute("data-strike"), combined);
}
function applyLiveQuotes(payload) {
if (!payload || !state.chain) return;
const uly = String(state.underlying || "").toUpperCase();
if (payload.indexes && payload.indexes[uly] != null && Number.isFinite(Number(payload.indexes[uly]))) {
state.chain.index_px = Number(payload.indexes[uly]);
} else if (payload.underlying && String(payload.underlying).toUpperCase() === uly) {
if (payload.index_px != null && Number.isFinite(Number(payload.index_px))) {
state.chain.index_px = Number(payload.index_px);
}
} else if (payload.underlying && String(payload.underlying).toUpperCase() !== uly) {
// 别的标的推送:仍可 patch 本页已有合约
}
const quotes = payload.quotes || [];
quotes.forEach(function (q) {
const instId = q && q.inst_id;
if (!instId) return;
if (q.underlying && String(q.underlying).toUpperCase() !== uly) return;
const c = findChainContract(instId);
if (!c) return;
if (q.ask !== undefined) c.ask = q.ask;
if (q.bid !== undefined) c.bid = q.bid;
if (q.ask_sz !== undefined) c.ask_sz = q.ask_sz;
if (q.bid_sz !== undefined) c.bid_sz = q.bid_sz;
if (q.mark_px !== undefined) c.mark_px = q.mark_px;
if (q.ask_estimated !== undefined) c.ask_estimated = !!q.ask_estimated;
if (q.expiry_be_px !== undefined) c.expiry_be_px = q.expiry_be_px;
if (q.dist_expiry_be !== undefined) c.dist_expiry_be = q.dist_expiry_be;
if (state.chainView === "t") patchTRowDom(instId, c);
else patchListRowDom(instId, c);
});
if (payload.ts) chainQuotedAt = Number(payload.ts) || Date.now();
else if (quotes.length || payload.index_px != null) chainQuotedAt = Date.now();
quoteLiveWsOk = payload.ws_ok !== false;
renderIndexLine();
if (state.selectedInst && quotes.some(function (q) { return q && q.inst_id === state.selectedInst; })) {
const now = Date.now();
if (now - lastOrderQuoteLiveAt >= ORDER_QUOTE_LIVE_MIN_MS) {
lastOrderQuoteLiveAt = now;
void selectContract(state.selectedInst, null, true);
}
}
}
function stopQuoteLiveStream() {
if (quoteLiveReconnectTimer) {
clearTimeout(quoteLiveReconnectTimer);
quoteLiveReconnectTimer = null;
}
if (quoteLiveEs) {
try { quoteLiveEs.close(); } catch (_) {}
quoteLiveEs = null;
}
quoteLiveOk = false;
quoteLiveWsOk = false;
}
function startQuoteLiveStream() {
if (quoteLiveEs) return;
if (typeof EventSource === "undefined") return;
try {
quoteLiveEs = new EventSource("/api/options/quotes/stream");
} catch (_) {
quoteLiveOk = false;
return;
}
quoteLiveEs.addEventListener("quotes", function (ev) {
try {
const data = JSON.parse(ev.data || "{}");
quoteLiveOk = true;
if (data.reason === "connect") {
quoteLiveWsOk = !!data.ws_ok;
renderIndexLine();
return;
}
applyLiveQuotes(data);
} catch (_) {}
});
quoteLiveEs.onopen = function () {
quoteLiveOk = true;
renderIndexLine();
void watchCurrentExpiryQuotes();
};
quoteLiveEs.onerror = function () {
quoteLiveOk = false;
quoteLiveWsOk = false;
renderIndexLine();
stopQuoteLiveStream();
quoteLiveReconnectTimer = setTimeout(function () {
quoteLiveReconnectTimer = null;
startQuoteLiveStream();
}, 8000);
};
}
function softRefreshChainThrottled(force) {
if (document.hidden) return;
if (!document.getElementById("options-root")) return;
// WS 推送正常时不靠 REST 刷卖一,避免 50011;仅结构兜底可 force
if (!force && quoteLiveOk && quoteLiveWsOk) return;
const now = Date.now();
if (!force && now - lastChainSoftAt < CHAIN_SOFT_POLL_MS) return;
lastChainSoftAt = now;
@@ -1536,8 +1328,6 @@
}
// soft 时保留 selectedInstrenderStrikes 会先 park 再按 prevSelected 静默重挂下单面板
renderStrikes();
void watchCurrentExpiryQuotes();
startQuoteLiveStream();
} catch (e) {
if (seq !== chainLoadSeq || soft) return;
setExpirySelectStatus("选择到期日");
@@ -2446,7 +2236,6 @@
const expandCb = document.getElementById("opt-strike-expand-all");
if (expandCb) expandCb.checked = false;
renderStrikes();
void watchCurrentExpiryQuotes();
}
function bootOptionsPanel() {
@@ -2458,7 +2247,6 @@
refreshPendingOrders();
startPendingOrdersPoll();
startChainSoftPoll();
startQuoteLiveStream();
const hasCache =
chainHasExpiries(panelCache.chain) &&
panelCache.underlying === state.underlying &&
@@ -2468,8 +2256,7 @@
renderExpiries();
renderStrikes();
refreshAllPositions();
void watchCurrentExpiryQuotes();
// 后台静默刷新结构;卖一优先走 WS
// 后台静默刷新,避免缓存过期后到期日变空 / 卖一过期
softRefreshChainThrottled(true);
return;
}
@@ -2594,7 +2381,7 @@
window.OptionsPanelLive = {
refreshSoft: function () {
refreshAllPositions();
// 有 WS 实时报价时不再 REST 刷链;断开时才兜底
// embed SSE 只通知「该拉了」,不推送链报价;这里节流拉新鲜卖一/买一
softRefreshChainThrottled(false);
},
refreshChain: loadChain,