/**
* OKX 期权复盘:待复盘交易(5行) → 点复盘出表单 → 复盘记录详情 → 统计.
*/
(function (global) {
"use strict";
var PAGE_SIZE = 5;
var TAB_LABELS = {
option_spot: "期权交易记录",
options_options: "期期对冲记录",
perp_options: "永期对冲记录",
};
var FORM_PRESETS = {
option_spot: {
strategy: ["顺势", "反转"],
direction: ["多", "空"],
entry: ["假突破", "结构突破"],
},
hedge: {
strategy: ["横盘", "趋势"],
direction: ["多", "空"],
entry: ["横盘博弈方向", "趋势对冲止损"],
},
};
var RESULT_OPTIONS = ["盈利", "亏损", "持平"];
var activeSource = "option_spot";
var currentTradeId = null;
var draftId = "";
var tradesCache = {};
var reviewedCache = {};
var tradesPage = 0;
var tradesPages = 1;
var reviewedPage = 0;
var reviewedPages = 1;
function $(id) {
return document.getElementById(id);
}
function escapeHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function fmtPnl(v) {
if (v == null || v === "") return "—";
var n = Number(v);
if (Number.isNaN(n)) return "—";
return (n >= 0 ? "+" : "") + n.toFixed(2);
}
function fmtHold(sec) {
if (sec == null) return "—";
var s = Math.max(0, Number(sec) || 0);
if (s < 3600) return Math.round(s / 60) + "m";
if (s < 86400) return (s / 3600).toFixed(1) + "h";
return (s / 86400).toFixed(1) + "d";
}
function toLocalInput(ts) {
if (!ts) return "";
var s = String(ts).trim().replace(" ", "T");
if (s.length >= 16) return s.slice(0, 16);
return s;
}
function tradeTitle(t) {
if (!t) return "—";
if (t.source_type === "option_spot") return t.inst_id || "—";
return (
(t.underlying || "") +
(t.direction ? " " + t.direction : "") +
(t.plan_close_reason ? " · " + t.plan_close_reason : "")
);
}
function pnlStyle(v) {
var n = Number(v);
if (n > 0) return "color:#3dd68c";
if (n < 0) return "color:#f07178";
return "";
}
function newDraftId() {
if (global.crypto && typeof global.crypto.randomUUID === "function") {
return global.crypto.randomUUID().replace(/-/g, "");
}
var s = "";
for (var i = 0; i < 32; i++) s += Math.floor(Math.random() * 16).toString(16);
return s;
}
function baseQs() {
var p = new URLSearchParams();
p.set("source_type", activeSource);
var uly = ($("or-filter-uly") || {}).value || "";
var opt = ($("or-filter-opt") || {}).value || "";
var strategy = (($("or-filter-strategy") || {}).value || "").trim();
var from = ($("or-filter-from") || {}).value || "";
var to = ($("or-filter-to") || {}).value || "";
if (uly) p.set("underlying", uly);
if (opt) p.set("opt_type", opt);
if (strategy) p.set("strategy_tag", strategy);
if (from) p.set("closed_from", from.replace("T", " ") + ":00");
if (to) p.set("closed_to", to.replace("T", " ") + ":00");
if (($("or-include-hedge-legs") || {}).checked) p.set("include_hedge_legs", "1");
return p;
}
function setSyncStatus(text) {
var el = $("or-sync-status");
if (el) el.textContent = text || "";
}
function reloadAll() {
setSyncStatus("读取本地记录…");
loadTrades({ sync: true });
loadReviewed({ sync: false });
loadStats();
}
function isHedgeSource(sourceType) {
return sourceType === "options_options" || sourceType === "perp_options";
}
function fillSelect(el, options, placeholder) {
if (!el) return;
var keep = el.value;
el.innerHTML = "";
var first = document.createElement("option");
first.value = "";
first.textContent = placeholder || "";
el.appendChild(first);
(options || []).forEach(function (v) {
var opt = document.createElement("option");
opt.value = v;
opt.textContent = v;
el.appendChild(opt);
});
if (keep) setSelectValue(el, keep);
}
function setSelectValue(el, value) {
if (!el) return;
var v = value == null ? "" : String(value);
if (!v) {
el.value = "";
return;
}
var found = false;
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].value === v) {
found = true;
break;
}
}
if (!found) {
var opt = document.createElement("option");
opt.value = v;
opt.textContent = v;
el.appendChild(opt);
}
el.value = v;
}
function applyFormPresets(sourceType) {
var preset = isHedgeSource(sourceType) ? FORM_PRESETS.hedge : FORM_PRESETS.option_spot;
fillSelect($("or-f-strategy"), preset.strategy, "策略标签");
fillSelect($("or-f-direction"), preset.direction, "方向判断");
fillSelect($("or-f-entry"), preset.entry, "入场逻辑");
fillSelect($("or-f-result"), RESULT_OPTIONS, "结果标签");
}
function autoDirection(t) {
if (!t) return "";
if (isHedgeSource(t.source_type)) {
var d = String(t.direction || "").trim().toLowerCase();
if (d === "long" || d === "buy" || d === "多") return "多";
if (d === "short" || d === "sell" || d === "空") return "空";
return "";
}
var ot = String(t.opt_type || "").trim().toUpperCase();
if (ot === "C" || ot === "CALL") return "多";
if (ot === "P" || ot === "PUT") return "空";
return "";
}
function autoResultTag(pnl) {
if (pnl == null || pnl === "") return "";
var n = Number(pnl);
if (Number.isNaN(n)) return "";
if (n > 0) return "盈利";
if (n < 0) return "亏损";
return "持平";
}
function setActiveTab(source) {
activeSource = source || "option_spot";
tradesPage = 0;
reviewedPage = 0;
document.querySelectorAll(".or-tab").forEach(function (btn) {
btn.classList.toggle("active", btn.getAttribute("data-source") === activeSource);
});
var title = $("or-list-title");
if (title) title.textContent = TAB_LABELS[activeSource] || "记录";
applyFormPresets(activeSource);
hideJournalForm();
hideDetail();
reloadAll();
}
function updateTradesPager() {
var label = $("or-trades-page-label");
var prev = $("or-trades-prev");
var next = $("or-trades-next");
if (label) {
label.textContent = "第 " + (tradesPage + 1) + " / " + tradesPages + " 页";
}
if (prev) prev.disabled = tradesPage <= 0;
if (next) next.disabled = tradesPage + 1 >= tradesPages;
}
function updateReviewedPager() {
var label = $("or-reviewed-page-label");
var prev = $("or-reviewed-prev");
var next = $("or-reviewed-next");
if (label) {
label.textContent = "第 " + (reviewedPage + 1) + " / " + reviewedPages + " 页";
}
if (prev) prev.disabled = reviewedPage <= 0;
if (next) next.disabled = reviewedPage + 1 >= reviewedPages;
}
function applyPagerMeta(data, kind) {
var pages = Number(data.pages || 1);
if (!pages || pages < 1) pages = 1;
var clamped = false;
if (kind === "trades") {
tradesPages = pages;
if (tradesPage >= tradesPages) {
tradesPage = Math.max(0, tradesPages - 1);
clamped = true;
}
updateTradesPager();
} else {
reviewedPages = pages;
if (reviewedPage >= reviewedPages) {
reviewedPage = Math.max(0, reviewedPages - 1);
clamped = true;
}
updateReviewedPager();
}
return clamped;
}
function beginListLoad(wrapId, soft) {
var wrap = $(wrapId);
if (!wrap) return null;
if (soft) {
if (!wrap.style.minHeight) {
wrap.style.minHeight = Math.max(wrap.offsetHeight, 1) + "px";
}
wrap.classList.add("or-list-loading");
} else {
wrap.classList.remove("or-list-loading");
wrap.style.minHeight = "";
}
return wrap;
}
function endListLoad(wrap) {
if (!wrap) return;
wrap.classList.remove("or-list-loading");
wrap.style.minHeight = "";
}
function loadTrades(opts) {
opts = opts || {};
var doSync = opts.sync !== false;
var soft = !!opts.soft;
var tbody = $("or-trades-tbody");
if (!tbody) return;
var wrap = beginListLoad("or-trades-wrap", soft);
if (!soft) {
tbody.innerHTML = '
| 加载中… |
';
}
var p = baseQs();
p.set("reviewed", "0");
p.set("limit", String(PAGE_SIZE));
p.set("offset", String(tradesPage * PAGE_SIZE));
if (!doSync) p.set("sync", "0");
fetch("/api/options/review/trades?" + p.toString(), { credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (data) {
if (doSync) setSyncStatus("本地记录已加载");
if (!data.ok) {
tbody.innerHTML = '| 加载失败 |
';
endListLoad(wrap);
return;
}
if (applyPagerMeta(data, "trades") && Number(data.total || 0) > 0) {
loadTrades(opts);
return;
}
var rows = data.trades || [];
tradesCache = {};
if (!rows.length) {
tbody.innerHTML =
'| 暂无待复盘记录 |
';
endListLoad(wrap);
return;
}
tbody.innerHTML = rows
.map(function (t) {
tradesCache[t.id] = t;
var active = currentTradeId === t.id ? " or-row-active" : "";
return (
'' +
"| " +
escapeHtml(t.source_label || t.source_type) +
" | " +
"" +
escapeHtml(tradeTitle(t)) +
" | " +
'' +
fmtPnl(t.realized_pnl_total) +
" | " +
'' +
escapeHtml(t.opened_at || "—") +
" " +
escapeHtml(t.closed_at || "—") +
" | " +
"" +
fmtHold(t.hold_seconds) +
" | " +
' ' +
' | ' +
"
"
);
})
.join("");
tbody.querySelectorAll(".or-review-btn").forEach(function (btn) {
btn.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
openJournalForm(Number(btn.getAttribute("data-id")));
});
});
tbody.querySelectorAll(".or-hide-btn").forEach(function (btn) {
btn.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
hideTrade(Number(btn.getAttribute("data-id")));
});
});
endListLoad(wrap);
})
.catch(function () {
tbody.innerHTML = '| 加载失败 |
';
endListLoad(wrap);
});
}
function loadReviewed(opts) {
opts = opts || {};
var doSync = opts.sync === true;
var soft = !!opts.soft;
var tbody = $("or-reviewed-tbody");
if (!tbody) return;
var wrap = beginListLoad("or-reviewed-wrap", soft);
if (!soft) {
tbody.innerHTML = '| 加载中… |
';
}
var p = baseQs();
p.set("reviewed", "1");
p.set("limit", String(PAGE_SIZE));
p.set("offset", String(reviewedPage * PAGE_SIZE));
if (!doSync) p.set("sync", "0");
fetch("/api/options/review/trades?" + p.toString(), { credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok) {
tbody.innerHTML = '| 加载失败 |
';
endListLoad(wrap);
return;
}
if (applyPagerMeta(data, "reviewed") && Number(data.total || 0) > 0) {
loadReviewed(opts);
return;
}
var rows = data.trades || [];
reviewedCache = {};
if (!rows.length) {
tbody.innerHTML = '| 暂无复盘记录 |
';
endListLoad(wrap);
return;
}
tbody.innerHTML = rows
.map(function (t) {
reviewedCache[t.id] = t;
return (
'' +
"| " +
escapeHtml(t.source_label || t.source_type) +
" | " +
"" +
escapeHtml(tradeTitle(t)) +
" | " +
'' +
fmtPnl(t.realized_pnl_total) +
" | " +
"" +
escapeHtml(t.strategy_tag || "—") +
" | " +
"" +
escapeHtml(t.result_tag || "—") +
" | " +
'' +
escapeHtml(t.reviewed_at || "—") +
" | " +
"
"
);
})
.join("");
tbody.querySelectorAll(".or-reviewed-row").forEach(function (tr) {
tr.addEventListener("click", function () {
openDetail(Number(tr.getAttribute("data-id")));
});
});
endListLoad(wrap);
})
.catch(function () {
tbody.innerHTML = '| 加载失败 |
';
endListLoad(wrap);
});
}
function hideDetail() {
var panel = $("or-detail-panel");
if (panel) panel.classList.add("hidden");
}
function openDetail(tradeId) {
var panel = $("or-detail-panel");
if (!panel) return;
panel.classList.remove("hidden");
($("or-detail-title") || {}).textContent = "加载中…";
($("or-detail-meta") || {}).innerHTML = "";
($("or-detail-text") || {}).innerHTML = "";
($("or-detail-images") || {}).innerHTML = "";
panel.scrollIntoView({ behavior: "smooth", block: "nearest" });
fetch("/api/options/review/trades/" + tradeId, { credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok || !data.trade) {
($("or-detail-title") || {}).textContent = "加载失败";
return;
}
renderDetail(data.trade);
})
.catch(function () {
($("or-detail-title") || {}).textContent = "加载失败";
});
}
function renderDetail(t) {
var e = t.entry || {};
reviewedCache[t.id] = t;
($("or-detail-title") || {}).textContent =
"复盘详情 · " + (t.source_label || "") + " · " + tradeTitle(t);
var editBtn = $("or-detail-edit-btn");
if (editBtn) editBtn.setAttribute("data-id", String(t.id));
var meta = $("or-detail-meta");
if (meta) {
var cells = [
["标的", t.underlying || "—"],
["合约/计划", tradeTitle(t)],
["盈亏", fmtPnl(t.realized_pnl_total)],
["持有", fmtHold(t.hold_seconds)],
["开仓", t.opened_at || "—"],
["平仓", t.closed_at || "—"],
["策略", e.strategy_tag || "—"],
["方向", e.direction_view || "—"],
["结果", e.result_tag || "—"],
["离场", e.exit_reason || "—"],
["按计划", e.followed_plan || "—"],
["入场逻辑", e.entry_logic || "—"],
];
if (t.is_hedge) {
cells.push(["永续盈亏", fmtPnl(t.realized_pnl_perp)]);
cells.push(["期权盈亏", fmtPnl(t.realized_pnl_options)]);
}
meta.innerHTML = cells
.map(function (pair) {
return (
"" +
escapeHtml(pair[0]) +
"
" +
escapeHtml(pair[1]) +
"
"
);
})
.join("");
}
var text = $("or-detail-text");
if (text) {
var lines = [];
if (e.mistake_tags) lines.push("心理标签:" + escapeHtml(e.mistake_tags) + "
");
if (e.note) lines.push("备注:" + escapeHtml(e.note).replace(/\n/g, "
") + "
");
if (t.legs && t.legs.length) {
lines.push(
"计划腿
| 腿 | 合约 | 盈亏 | 原因 |
" +
t.legs
.map(function (leg) {
return (
"| " +
escapeHtml(leg.leg_role || "") +
" | " +
escapeHtml(leg.inst_id || leg.symbol || "") +
" | " +
fmtPnl(leg.realized_pnl) +
" | " +
escapeHtml(leg.close_reason || "") +
" |
"
);
})
.join("") +
"
"
);
}
text.innerHTML = lines.join("") || '无额外备注
';
}
var imagesHost = $("or-detail-images");
if (imagesHost) {
var images = e.images || [];
if (!images.length) {
imagesHost.innerHTML = '无截图
';
} else {
imagesHost.innerHTML = images
.map(function (img) {
var file = String(img.file || "").trim();
if (!file) return "";
var src = "/static/images/options_journal/" + encodeURIComponent(file).replace(/%2F/g, "/");
var label = escapeHtml(img.tf || "截图");
return (
'' +
'
' +
label +
"" +
'

' +
"
"
);
})
.join("");
imagesHost.querySelectorAll("img").forEach(function (img) {
img.addEventListener("click", function () {
if (typeof global.showImage === "function") {
global.showImage(img.getAttribute("data-src"));
} else {
global.open(img.getAttribute("data-src"), "_blank");
}
});
});
}
}
}
function renderGroup(title, items) {
if (!items || !items.length) {
return (
''
);
}
var lines = items
.slice(0, 8)
.map(function (g) {
return (
'' +
"" +
escapeHtml(g.key) +
" · " +
g.count +
"笔" +
"" +
fmtPnl(g.pnl_sum) +
" / 胜" +
(g.win_rate || 0) +
"%" +
"
"
);
})
.join("");
return (
'' +
title +
"
" +
lines +
"
"
);
}
function loadStats() {
var kpi = $("or-kpi");
var groups = $("or-stats-groups");
if (!kpi || !groups) return;
fetch("/api/options/review/stats?" + baseQs().toString(), { credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok) return;
var k = data.kpi || {};
kpi.innerHTML = [
["笔数", k.total],
["已复盘率", (k.review_rate || 0) + "%"],
["胜率", (k.win_rate || 0) + "%"],
["累计盈亏", fmtPnl(k.pnl_sum)],
["平均盈亏", fmtPnl(k.avg_pnl)],
["平均持有", fmtHold(k.avg_hold_sec)],
]
.map(function (pair) {
return (
'' +
pair[0] +
'
' +
pair[1] +
"
"
);
})
.join("");
groups.innerHTML = [
renderGroup("按类型", data.by_source_type),
renderGroup("按标的", data.by_underlying),
renderGroup("按策略", data.by_strategy),
renderGroup("对冲结束原因", data.by_close_reason),
renderGroup("持有周期", data.by_hold_bucket),
renderGroup("Call/Put", data.by_opt_type),
].join("");
})
.catch(function () {});
}
function resetUploadSlots() {
draftId = newDraftId();
var draftEl = $("or-draft-id");
if (draftEl) draftEl.value = draftId;
document.querySelectorAll("#or-upload-slots .or-upload-hidden").forEach(function (el) {
el.value = "";
});
document.querySelectorAll("#or-upload-slots .or-upload-input").forEach(function (el) {
el.value = "";
});
document.querySelectorAll("#or-upload-slots .or-upload-status").forEach(function (el) {
el.textContent = "";
});
}
function bindUploadSlots() {
document.querySelectorAll("#or-upload-slots .or-upload-input").forEach(function (input) {
if (input.dataset.orBound === "1") return;
input.dataset.orBound = "1";
input.addEventListener("change", function () {
var file = input.files && input.files[0];
var row = input.closest(".journal-upload-row");
var status = row && row.querySelector(".or-upload-status");
var hidden = row && row.querySelector(".or-upload-hidden");
if (!file) {
if (hidden) hidden.value = "";
if (status) {
status.textContent = "";
status.className = "journal-upload-status or-upload-status";
}
return;
}
if (!draftId) draftId = newDraftId();
if (status) {
status.textContent = "上传中…";
status.className = "journal-upload-status or-upload-status journal-upload-status--pending";
}
var fd = new FormData();
fd.append("draft_id", draftId);
fd.append("tf", input.getAttribute("data-tf") || "");
fd.append("file", file);
fetch("/api/options/review/upload_slot", {
method: "POST",
body: fd,
credentials: "same-origin",
})
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok) throw new Error(data.error || "fail");
if (hidden) hidden.value = data.file;
if (status) {
status.textContent = "上传成功 " + data.file;
status.className = "journal-upload-status or-upload-status journal-upload-status--ok";
}
input.value = "";
})
.catch(function () {
if (hidden) hidden.value = "";
if (status) {
status.textContent = "上传失败";
status.className = "journal-upload-status or-upload-status journal-upload-status--err";
}
});
});
});
}
function setMoodTags(raw) {
var set = {};
String(raw || "")
.split(/[,,]/)
.map(function (x) {
return x.trim();
})
.filter(Boolean)
.forEach(function (x) {
set[x] = true;
});
document.querySelectorAll(".or-mood").forEach(function (cb) {
cb.checked = !!set[cb.value];
});
}
function collectMoodTags() {
var out = [];
document.querySelectorAll(".or-mood:checked").forEach(function (cb) {
out.push(cb.value);
});
return out.join(",");
}
function collectImages() {
var out = [];
document.querySelectorAll("#or-upload-slots .or-upload-hidden").forEach(function (el) {
var file = (el.value || "").trim();
if (file) out.push({ tf: el.getAttribute("data-tf") || "", file: file });
});
return out;
}
function hideJournalForm() {
currentTradeId = null;
var card = $("or-journal-card");
if (card) card.classList.add("hidden");
document.querySelectorAll(".or-trade-row").forEach(function (tr) {
tr.classList.remove("or-row-active");
});
($("or-trade-id") || {}).value = "";
($("or-f-open") || {}).value = "";
($("or-f-close") || {}).value = "";
($("or-f-coin") || {}).value = "";
($("or-f-inst") || {}).value = "";
($("or-f-pnl") || {}).value = "";
($("or-f-hold") || {}).value = "";
($("or-f-strategy") || {}).value = "";
($("or-f-direction") || {}).value = "";
($("or-f-exit") || {}).value = "";
($("or-f-followed") || {}).value = "";
($("or-f-result") || {}).value = "";
($("or-f-entry") || {}).value = "";
($("or-f-note") || {}).value = "";
setMoodTags("");
resetUploadSlots();
var summary = $("or-journal-summary");
if (summary) {
summary.textContent = "截图槽位与合约复盘相同(5m / 15m / 1h / 4h).";
}
var legsHost = $("or-legs-host");
if (legsHost) legsHost.innerHTML = "";
($("or-save-status") || {}).textContent = "";
}
function openJournalForm(tradeId) {
currentTradeId = tradeId;
var card = $("or-journal-card");
if (!card) return;
card.classList.remove("hidden");
card.scrollIntoView({ behavior: "smooth", block: "start" });
document.querySelectorAll(".or-trade-row").forEach(function (tr) {
tr.classList.toggle("or-row-active", Number(tr.getAttribute("data-id")) === tradeId);
});
resetUploadSlots();
bindUploadSlots();
($("or-save-status") || {}).textContent = "加载中…";
fetch("/api/options/review/trades/" + tradeId, { credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok || !data.trade) {
($("or-save-status") || {}).textContent = "加载失败";
return;
}
fillForm(data.trade);
($("or-save-status") || {}).textContent = "已选中 #" + tradeId;
})
.catch(function () {
($("or-save-status") || {}).textContent = "加载失败";
});
}
function fillForm(t) {
var e = t.entry || {};
applyFormPresets(t.source_type || activeSource);
($("or-trade-id") || {}).value = String(t.id || "");
($("or-f-open") || {}).value = toLocalInput(t.opened_at);
($("or-f-close") || {}).value = toLocalInput(t.closed_at);
($("or-f-coin") || {}).value = t.underlying || "";
($("or-f-inst") || {}).value =
t.source_type === "option_spot"
? t.inst_id || ""
: (t.source_label || "") + (t.plan_close_reason ? " · " + t.plan_close_reason : "");
($("or-f-pnl") || {}).value = fmtPnl(t.realized_pnl_total);
($("or-f-hold") || {}).value = fmtHold(t.hold_seconds);
setSelectValue($("or-f-strategy"), e.strategy_tag || "");
setSelectValue($("or-f-direction"), e.direction_view || autoDirection(t));
($("or-f-exit") || {}).value = e.exit_reason || t.plan_close_reason || "";
($("or-f-followed") || {}).value = e.followed_plan || "";
setSelectValue($("or-f-result"), e.result_tag || autoResultTag(t.realized_pnl_total));
setSelectValue($("or-f-entry"), e.entry_logic || "");
($("or-f-note") || {}).value = e.note || "";
setMoodTags(e.mistake_tags);
var summary = $("or-journal-summary");
if (summary) {
summary.textContent =
(t.source_label || "") +
" · " +
(t.inst_id || t.underlying || "#" + t.id) +
" · 盈亏 " +
fmtPnl(t.realized_pnl_total) +
(t.is_hedge
? " (永续 " + fmtPnl(t.realized_pnl_perp) + " / 期权 " + fmtPnl(t.realized_pnl_options) + ")"
: "");
}
(e.images || []).forEach(function (img) {
var hidden = document.querySelector(
'#or-upload-slots .or-upload-hidden[data-tf="' + img.tf + '"]'
);
var status = document.querySelector(
'#or-upload-slots .or-upload-status[data-tf="' + img.tf + '"]'
);
if (hidden && img.file) {
hidden.value = img.file;
if (status) status.textContent = "已有 " + img.file;
}
});
var legsHost = $("or-legs-host");
if (legsHost) {
if (t.legs && t.legs.length) {
legsHost.innerHTML =
"计划腿
| 腿 | 合约 | 盈亏 | 原因 |
" +
t.legs
.map(function (leg) {
return (
"| " +
escapeHtml(leg.leg_role || "") +
" | " +
escapeHtml(leg.inst_id || leg.symbol || "") +
" | " +
fmtPnl(leg.realized_pnl) +
" | " +
escapeHtml(leg.close_reason || "") +
" |
"
);
})
.join("") +
"
";
} else {
legsHost.innerHTML = "";
}
}
}
function hideTrade(tradeId) {
if (!tradeId) return;
if (!confirm("从待复盘列表删除并隐藏?刷新后也不会再出现.")) return;
fetch("/api/options/review/trades/" + tradeId, {
method: "DELETE",
credentials: "same-origin",
})
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok) {
alert(data.msg || "删除失败");
return;
}
if (currentTradeId === tradeId) hideJournalForm();
reloadAll();
})
.catch(function () {
alert("删除失败");
});
}
function saveEntry() {
var tradeId = Number(($("or-trade-id") || {}).value || 0);
if (!tradeId) {
alert("请先点击交易记录中的「复盘」");
return;
}
var strategy = (($("or-f-strategy") || {}).value || "").trim();
if (!strategy) {
alert("请选择策略标签");
return;
}
var payload = {
trade_id: tradeId,
strategy_tag: strategy,
direction_view: ($("or-f-direction") || {}).value || "",
exit_reason: ($("or-f-exit") || {}).value || "",
followed_plan: ($("or-f-followed") || {}).value || "",
result_tag: ($("or-f-result") || {}).value || "",
mistake_tags: collectMoodTags(),
entry_logic: ($("or-f-entry") || {}).value || "",
note: ($("or-f-note") || {}).value || "",
images: collectImages(),
};
($("or-save-status") || {}).textContent = "保存中…";
fetch("/api/options/review/entry", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
})
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok) {
($("or-save-status") || {}).textContent = data.msg || "保存失败";
return;
}
($("or-save-status") || {}).textContent = "已保存";
hideJournalForm();
reloadAll();
openDetail(tradeId);
})
.catch(function () {
($("or-save-status") || {}).textContent = "保存失败";
});
}
function deleteEntry() {
var tradeId = Number(($("or-trade-id") || {}).value || 0);
if (!tradeId) return;
if (!confirm("删除该条复盘内容与图片?交易记录会回到待复盘列表.")) return;
fetch("/api/options/review/entry/" + tradeId, {
method: "DELETE",
credentials: "same-origin",
})
.then(function (r) {
return r.json();
})
.then(function () {
hideJournalForm();
hideDetail();
reloadAll();
});
}
function init() {
if (!$("options-review-root")) return;
document.querySelectorAll(".or-tab").forEach(function (btn) {
btn.addEventListener("click", function () {
setActiveTab(btn.getAttribute("data-source"));
});
});
var reloadBtn = $("or-reload-btn");
var saveBtn = $("or-save-btn");
var clearBtn = $("or-clear-btn");
var delBtn = $("or-del-btn");
var prevBtn = $("or-trades-prev");
var nextBtn = $("or-trades-next");
var reviewedPrev = $("or-reviewed-prev");
var reviewedNext = $("or-reviewed-next");
var detailClose = $("or-detail-close-btn");
var detailEdit = $("or-detail-edit-btn");
if (reloadBtn) reloadBtn.addEventListener("click", reloadAll);
if (saveBtn) saveBtn.addEventListener("click", saveEntry);
if (clearBtn) clearBtn.addEventListener("click", hideJournalForm);
if (delBtn) delBtn.addEventListener("click", deleteEntry);
if (prevBtn) {
prevBtn.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
if (tradesPage <= 0) return;
tradesPage -= 1;
updateTradesPager();
loadTrades({ sync: false, soft: true });
});
}
if (nextBtn) {
nextBtn.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
if (tradesPage + 1 >= tradesPages) return;
tradesPage += 1;
updateTradesPager();
loadTrades({ sync: false, soft: true });
});
}
if (reviewedPrev) {
reviewedPrev.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
if (reviewedPage <= 0) return;
reviewedPage -= 1;
updateReviewedPager();
loadReviewed({ sync: false, soft: true });
});
}
if (reviewedNext) {
reviewedNext.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
if (reviewedPage + 1 >= reviewedPages) return;
reviewedPage += 1;
updateReviewedPager();
loadReviewed({ sync: false, soft: true });
});
}
if (detailClose) detailClose.addEventListener("click", hideDetail);
if (detailEdit) {
detailEdit.addEventListener("click", function () {
var id = Number(detailEdit.getAttribute("data-id") || 0);
if (id) openJournalForm(id);
});
}
["or-filter-uly", "or-filter-opt", "or-include-hedge-legs"].forEach(function (id) {
var el = $(id);
if (el) {
el.addEventListener("change", function () {
tradesPage = 0;
reviewedPage = 0;
reloadAll();
});
}
});
["or-filter-strategy", "or-filter-from", "or-filter-to"].forEach(function (id) {
var el = $(id);
if (el) {
el.addEventListener("change", function () {
tradesPage = 0;
reviewedPage = 0;
reloadAll();
});
}
});
bindUploadSlots();
hideJournalForm();
hideDetail();
setActiveTab("option_spot");
}
global.OptionsReview = {
init: init,
openJournalForm: openJournalForm,
hideJournalForm: hideJournalForm,
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})(typeof window !== "undefined" ? window : globalThis);