Sync OKX options closed trades into hub archive with a separate tab.

Mirror perpetual archive flow into archive_options_trade_cache for offline calendar and review.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-24 00:39:59 +08:00
parent 6f1ae14b3d
commit 54f1857fa2
8 changed files with 1058 additions and 27 deletions
+28
View File
@@ -8057,6 +8057,34 @@ body.funds-fullscreen-open {
gap: 12px;
align-items: stretch;
}
.archive-product-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0 0 10px;
}
.archive-product-tab {
border: 1px solid var(--border-soft);
background: transparent;
color: inherit;
padding: 8px 18px;
border-radius: 999px;
cursor: pointer;
font-family: var(--font);
font-size: 0.88rem;
font-weight: 600;
}
.archive-product-tab.is-active {
background: rgba(16, 185, 129, 0.2);
border-color: rgba(16, 185, 129, 0.55);
color: var(--text);
}
body.archive-product-options .archive-toolbar-desktop[data-archive-perp-only],
body.archive-product-options #archive-btn-chart-toggle,
body.archive-product-options #archive-filter-sick,
body.archive-product-options #archive-tab-viz {
display: none !important;
}
.archive-content-tabs {
display: flex;
flex-wrap: wrap;
+142 -5
View File
@@ -35,6 +35,7 @@
const elQuoteContent = document.getElementById("archive-quote-content");
const elQuoteSubmit = document.getElementById("archive-quote-submit");
const elContentTabs = document.getElementById("archive-content-tabs");
const elProductTabs = document.getElementById("archive-product-tabs");
const elPanelViz = document.getElementById("archive-panel-viz");
const elPanelCalendar = document.getElementById("archive-panel-calendar");
const elPanelTrades = document.getElementById("archive-panel-trades");
@@ -76,6 +77,7 @@
let selectedQuoteId = null;
let editingQuoteId = null;
let archiveContentTab = "trades";
let archiveProduct = "perp";
let quoteDayTrades = [];
let quoteDayTradesDay = "";
let quoteDayTradesReq = 0;
@@ -416,6 +418,44 @@
syncPeriodUI();
}
function isOptionsProduct() {
return archiveProduct === "options";
}
function syncProductUI() {
document.body.classList.toggle("archive-product-options", isOptionsProduct());
if (elProductTabs) {
elProductTabs.querySelectorAll(".archive-product-tab").forEach(function (btn) {
const on = btn.getAttribute("data-archive-product") === archiveProduct;
btn.classList.toggle("is-active", on);
btn.setAttribute("aria-selected", on ? "true" : "false");
});
}
if (isOptionsProduct()) {
setChartOpen(false);
if (archiveContentTab === "viz") setArchiveContentTab("trades");
}
}
function setArchiveProduct(product) {
const next = product === "options" ? "options" : "perp";
if (next === archiveProduct) return;
archiveProduct = next;
selected = null;
selectedTradeKey = null;
syncProductUI();
void loadDailyTrades();
void loadCalendar();
}
function dailyTradesApiPath() {
return isOptionsProduct() ? "/api/archive/options/daily-trades" : "/api/archive/daily-trades";
}
function calendarApiPath() {
return isOptionsProduct() ? "/api/archive/options/calendar" : "/api/archive/calendar";
}
function queryDailyParams() {
const q = new URLSearchParams();
q.set("period", periodMode);
@@ -430,7 +470,7 @@
if (ex) q.set("exchange_key", ex);
if (elFilterProfit && elFilterProfit.checked) q.set("filter_profit", "1");
if (elFilterLoss && elFilterLoss.checked) q.set("filter_loss", "1");
if (elFilterSick && elFilterSick.checked) q.set("filter_sick", "1");
if (!isOptionsProduct() && elFilterSick && elFilterSick.checked) q.set("filter_sick", "1");
if (elSearch && elSearch.value.trim()) q.set("search", elSearch.value.trim());
return q.toString();
}
@@ -554,7 +594,7 @@
return q;
},
fetchFn: async function (q) {
const r = await apiFetch("/api/archive/calendar?" + q.toString());
const r = await apiFetch(calendarApiPath() + "?" + q.toString());
return r.json();
},
parseResponse: function (data) {
@@ -1089,7 +1129,7 @@
elQuoteDayTradesBody.innerHTML = '<p class="archive-empty">加载当日已平仓…</p>';
if (elQuoteDayTradesMeta) elQuoteDayTradesMeta.textContent = day;
try {
const r = await apiFetch("/api/archive/daily-trades?" + q.toString());
const r = await apiFetch(dailyTradesApiPath() + "?" + q.toString());
const j = await r.json();
if (req !== quoteDayTradesReq) return;
if (!r.ok) {
@@ -1827,6 +1867,80 @@
return;
}
const pageRows = pagedDailyTrades();
if (isOptionsProduct()) {
elTrades.innerHTML =
'<table class="archive-trades-table"><thead><tr>' +
"<th>交易所</th><th>标的</th><th>合约/来源</th><th>开仓时间</th><th>平仓时间</th><th>持仓</th>" +
"<th>类型</th><th>策略</th><th>盈亏</th><th>权利金</th><th>复盘</th>" +
"</tr></thead><tbody>" +
pageRows
.map(function (t) {
const rowKey = tradeRowKey(t);
const active = rowKey && rowKey === selectedTradeKey ? " is-active" : "";
const holdMin =
t.hold_minutes != null
? t.hold_minutes
: t.hold_seconds != null
? Number(t.hold_seconds) / 60
: null;
const optLabel =
t.source_label ||
t.source_type ||
(t.opt_type === "C" || t.opt_type === "CALL"
? "Call"
: t.opt_type === "P" || t.opt_type === "PUT"
? "Put"
: "—");
const pnl = t.pnl_amount != null ? t.pnl_amount : t.realized_pnl_total;
return (
'<tr class="archive-trade-row' +
active +
'" data-key="' +
esc(rowKey) +
'">' +
"<td>" +
esc(tradeRowExchange(t)) +
"</td>" +
'<td class="archive-symbol">' +
esc(t.underlying || "—") +
"</td>" +
"<td>" +
esc(t.inst_id || t.source_label || "—") +
"</td>" +
'<td class="archive-dt">' +
fmtDt(t.opened_at) +
"</td>" +
'<td class="archive-dt">' +
fmtDt(t.closed_at) +
"</td>" +
'<td class="archive-hold">' +
fmtDurationMinutes(holdMin) +
"</td>" +
"<td>" +
esc(optLabel) +
"</td>" +
"<td>" +
esc(t.strategy_tag || "—") +
"</td>" +
'<td class="' +
pnlClass(pnl) +
'">' +
fmtPnl(pnl) +
"</td>" +
"<td>" +
fmtVolStat(t.premium_total != null ? t.premium_total : t.premium_paid) +
"</td>" +
"<td>" +
(t.reviewed ? "已复盘" : "—") +
"</td>" +
"</tr>"
);
})
.join("") +
"</tbody></table>";
updateTradesPager();
return;
}
elTrades.innerHTML =
'<table class="archive-trades-table"><thead><tr>' +
"<th>交易所</th><th>合约</th><th>开仓类型</th><th>开仓时间</th><th>平仓时间</th><th>持仓时长</th>" +
@@ -2051,7 +2165,7 @@
async function loadDailyTrades() {
setStatus("加载交易记录…");
const r = await apiFetch("/api/archive/daily-trades?" + queryDailyParams());
const r = await apiFetch(dailyTradesApiPath() + "?" + queryDailyParams());
const j = await r.json();
if (!r.ok) {
setStatus(j.detail || "加载失败");
@@ -2080,7 +2194,8 @@
void loadCalendar();
if (archiveContentTab === "quotes") void loadQuoteDayTrades();
setStatus(
(periodLabel || tradingDay || "当日") +
(isOptionsProduct() ? "期权 · " : "永续 · ") +
(periodLabel || tradingDay || "当日") +
" · 列表 " +
dailyTrades.length +
" 笔 · " +
@@ -2105,6 +2220,7 @@
function formatSyncSummary(j) {
const results = j.results || [];
const optResults = j.options_results || [];
const okN = results.filter(function (x) {
return x.ok !== false;
}).length;
@@ -2118,6 +2234,19 @@
parts.push(line);
}
});
optResults.forEach(function (row) {
const label = (row.exchange_key || row.name || "?") + "期权";
if (row.ok === false) parts.push(label + " 失败: " + (row.msg || "未知错误"));
else {
let line =
label +
" " +
(row.trade_count != null ? row.trade_count : row.trades_upserted || 0) +
" 笔";
if (row.trades_removed > 0) line += " 清" + row.trades_removed;
parts.push(line);
}
});
return parts.join(" · ");
}
@@ -2217,6 +2346,13 @@
setArchiveContentTab(btn.getAttribute("data-archive-tab") || "trades");
});
}
if (elProductTabs) {
elProductTabs.addEventListener("click", function (ev) {
const btn = ev.target.closest(".archive-product-tab");
if (!btn) return;
setArchiveProduct(btn.getAttribute("data-archive-product") || "perp");
});
}
if (elTfTabs) {
elTfTabs.addEventListener("click", function (ev) {
const btn = ev.target.closest(".archive-tf-btn");
@@ -2249,6 +2385,7 @@
syncPeriodUI();
syncTradesLayout();
bindEvents();
syncProductUI();
setArchiveContentTab("trades");
inited = true;
}
+7 -3
View File
@@ -16,7 +16,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
<link rel="stylesheet" href="/assets/app.css?v=20260724-display-hide" />
<link rel="stylesheet" href="/assets/app.css?v=20260724-opt-archive" />
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
<script src="/assets/account_risk_badge.js?v=4"></script>
@@ -451,7 +451,11 @@
<div id="page-archive" class="page hidden">
<div class="page-head">
<h1><span class="head-tag">IN</span> 内照明心</h1>
<p class="page-desc">交易记录 · 交易日历 · 图表概览 · 复盘语录</p>
<p class="page-desc">永续 / 期权交易记录 · 交易日历 · 图表概览 · 复盘语录</p>
</div>
<div class="archive-product-tabs" id="archive-product-tabs" role="tablist" aria-label="品种">
<button type="button" class="archive-product-tab is-active" role="tab" aria-selected="true" data-archive-product="perp">永续</button>
<button type="button" class="archive-product-tab" role="tab" aria-selected="false" data-archive-product="options">期权</button>
</div>
<div class="archive-toolbar toolbar">
<label class="chk-label archive-toolbar-desktop"><input type="checkbox" id="archive-filter-profit" /> 盈利单</label>
@@ -1673,7 +1677,7 @@
<script src="/assets/calculator.js?v=20260715-calc-tabs"></script>
<script src="/assets/compare.js?v=20260723-compare"></script>
<script src="/assets/trade_stats_calendar.js?v=3"></script>
<script src="/assets/archive.js?v=20260717-archive-cal-chart"></script>
<script src="/assets/archive.js?v=20260724-opt-archive"></script>
<script src="/assets/quotes.js?v=20260717-quotes-feed"></script>
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
<script src="/assets/dashboard.js?v=20260723-hide-pnl"></script>