Restructure options review into pending trades, form, and reviewed detail.
Show five pending trades per page, reveal the upload form only after clicking 复盘, and list saved reviews with image detail above stats. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* OKX 期权复盘:三类 Tab + 列表点选 + 下方多周期(5m/15m/1h/4h)复盘表单.
|
* OKX 期权复盘:待复盘交易(5行) → 点复盘出表单 → 复盘记录详情 → 统计.
|
||||||
*/
|
*/
|
||||||
(function (global) {
|
(function (global) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var SLOT_TFS = ["5m", "15m", "1h", "4h"];
|
var PAGE_SIZE = 5;
|
||||||
var TAB_LABELS = {
|
var TAB_LABELS = {
|
||||||
option_spot: "期权交易记录",
|
option_spot: "期权交易记录",
|
||||||
options_options: "期期对冲记录",
|
options_options: "期期对冲记录",
|
||||||
@@ -27,11 +27,22 @@
|
|||||||
var currentTradeId = null;
|
var currentTradeId = null;
|
||||||
var draftId = "";
|
var draftId = "";
|
||||||
var tradesCache = {};
|
var tradesCache = {};
|
||||||
|
var reviewedCache = {};
|
||||||
|
var tradesPage = 0;
|
||||||
|
var tradesHasMore = false;
|
||||||
|
|
||||||
function $(id) {
|
function $(id) {
|
||||||
return document.getElementById(id);
|
return document.getElementById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(s) {
|
||||||
|
return String(s == null ? "" : s)
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
function fmtPnl(v) {
|
function fmtPnl(v) {
|
||||||
if (v == null || v === "") return "—";
|
if (v == null || v === "") return "—";
|
||||||
var n = Number(v);
|
var n = Number(v);
|
||||||
@@ -54,6 +65,23 @@
|
|||||||
return s;
|
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() {
|
function newDraftId() {
|
||||||
if (global.crypto && typeof global.crypto.randomUUID === "function") {
|
if (global.crypto && typeof global.crypto.randomUUID === "function") {
|
||||||
return global.crypto.randomUUID().replace(/-/g, "");
|
return global.crypto.randomUUID().replace(/-/g, "");
|
||||||
@@ -63,23 +91,21 @@
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function qs() {
|
function baseQs() {
|
||||||
var p = new URLSearchParams();
|
var p = new URLSearchParams();
|
||||||
p.set("source_type", activeSource);
|
p.set("source_type", activeSource);
|
||||||
var uly = ($("or-filter-uly") || {}).value || "";
|
var uly = ($("or-filter-uly") || {}).value || "";
|
||||||
var opt = ($("or-filter-opt") || {}).value || "";
|
var opt = ($("or-filter-opt") || {}).value || "";
|
||||||
var reviewed = ($("or-filter-reviewed") || {}).value || "";
|
|
||||||
var strategy = (($("or-filter-strategy") || {}).value || "").trim();
|
var strategy = (($("or-filter-strategy") || {}).value || "").trim();
|
||||||
var from = ($("or-filter-from") || {}).value || "";
|
var from = ($("or-filter-from") || {}).value || "";
|
||||||
var to = ($("or-filter-to") || {}).value || "";
|
var to = ($("or-filter-to") || {}).value || "";
|
||||||
if (uly) p.set("underlying", uly);
|
if (uly) p.set("underlying", uly);
|
||||||
if (opt) p.set("opt_type", opt);
|
if (opt) p.set("opt_type", opt);
|
||||||
if (reviewed) p.set("reviewed", reviewed);
|
|
||||||
if (strategy) p.set("strategy_tag", strategy);
|
if (strategy) p.set("strategy_tag", strategy);
|
||||||
if (from) p.set("closed_from", from.replace("T", " ") + ":00");
|
if (from) p.set("closed_from", from.replace("T", " ") + ":00");
|
||||||
if (to) p.set("closed_to", to.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");
|
if (($("or-include-hedge-legs") || {}).checked) p.set("include_hedge_legs", "1");
|
||||||
return p.toString();
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSyncStatus(text) {
|
function setSyncStatus(text) {
|
||||||
@@ -90,6 +116,7 @@
|
|||||||
function reloadAll() {
|
function reloadAll() {
|
||||||
setSyncStatus("读取本地记录…");
|
setSyncStatus("读取本地记录…");
|
||||||
loadTrades();
|
loadTrades();
|
||||||
|
loadReviewed();
|
||||||
loadStats();
|
loadStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +197,7 @@
|
|||||||
|
|
||||||
function setActiveTab(source) {
|
function setActiveTab(source) {
|
||||||
activeSource = source || "option_spot";
|
activeSource = source || "option_spot";
|
||||||
|
tradesPage = 0;
|
||||||
document.querySelectorAll(".or-tab").forEach(function (btn) {
|
document.querySelectorAll(".or-tab").forEach(function (btn) {
|
||||||
btn.classList.toggle("active", btn.getAttribute("data-source") === activeSource);
|
btn.classList.toggle("active", btn.getAttribute("data-source") === activeSource);
|
||||||
});
|
});
|
||||||
@@ -177,45 +205,49 @@
|
|||||||
if (title) title.textContent = TAB_LABELS[activeSource] || "记录";
|
if (title) title.textContent = TAB_LABELS[activeSource] || "记录";
|
||||||
applyFormPresets(activeSource);
|
applyFormPresets(activeSource);
|
||||||
hideJournalForm();
|
hideJournalForm();
|
||||||
|
hideDetail();
|
||||||
reloadAll();
|
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) + " 页";
|
||||||
|
if (prev) prev.disabled = tradesPage <= 0;
|
||||||
|
if (next) next.disabled = !tradesHasMore;
|
||||||
|
}
|
||||||
|
|
||||||
function loadTrades() {
|
function loadTrades() {
|
||||||
var tbody = $("or-trades-tbody");
|
var tbody = $("or-trades-tbody");
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="muted">加载中…</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" class="muted">加载中…</td></tr>';
|
||||||
fetch("/api/options/review/trades?" + qs(), { credentials: "same-origin" })
|
var p = baseQs();
|
||||||
|
p.set("reviewed", "0");
|
||||||
|
p.set("limit", String(PAGE_SIZE));
|
||||||
|
p.set("offset", String(tradesPage * PAGE_SIZE));
|
||||||
|
fetch("/api/options/review/trades?" + p.toString(), { credentials: "same-origin" })
|
||||||
.then(function (r) {
|
.then(function (r) {
|
||||||
return r.json();
|
return r.json();
|
||||||
})
|
})
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
setSyncStatus("本地记录已加载");
|
setSyncStatus("本地记录已加载");
|
||||||
if (!data.ok) {
|
if (!data.ok) {
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="muted">加载失败</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" class="muted">加载失败</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var rows = data.trades || [];
|
var rows = data.trades || [];
|
||||||
|
tradesHasMore = rows.length >= PAGE_SIZE;
|
||||||
tradesCache = {};
|
tradesCache = {};
|
||||||
|
updateTradesPager();
|
||||||
if (!rows.length) {
|
if (!rows.length) {
|
||||||
tbody.innerHTML =
|
tbody.innerHTML =
|
||||||
'<tr><td colspan="8" class="muted">暂无本地已平记录(期权页平仓后或对冲计划结束后会出现在此)</td></tr>';
|
'<tr><td colspan="6" class="muted">暂无待复盘记录</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tbody.innerHTML = rows
|
tbody.innerHTML = rows
|
||||||
.map(function (t) {
|
.map(function (t) {
|
||||||
tradesCache[t.id] = t;
|
tradesCache[t.id] = t;
|
||||||
var title =
|
|
||||||
t.source_type === "option_spot"
|
|
||||||
? t.inst_id || "—"
|
|
||||||
: (t.underlying || "") +
|
|
||||||
(t.direction ? " " + t.direction : "") +
|
|
||||||
(t.plan_close_reason ? " · " + t.plan_close_reason : "");
|
|
||||||
var pnlClass =
|
|
||||||
Number(t.realized_pnl_total) > 0
|
|
||||||
? "color:#3dd68c"
|
|
||||||
: Number(t.realized_pnl_total) < 0
|
|
||||||
? "color:#f07178"
|
|
||||||
: "";
|
|
||||||
var active = currentTradeId === t.id ? " or-row-active" : "";
|
var active = currentTradeId === t.id ? " or-row-active" : "";
|
||||||
return (
|
return (
|
||||||
'<tr class="or-trade-row' +
|
'<tr class="or-trade-row' +
|
||||||
@@ -224,41 +256,39 @@
|
|||||||
t.id +
|
t.id +
|
||||||
'">' +
|
'">' +
|
||||||
"<td><span class=\"or-badge\">" +
|
"<td><span class=\"or-badge\">" +
|
||||||
(t.source_label || t.source_type) +
|
escapeHtml(t.source_label || t.source_type) +
|
||||||
"</span></td>" +
|
"</span></td>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
title +
|
escapeHtml(tradeTitle(t)) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td style="' +
|
'<td style="' +
|
||||||
pnlClass +
|
pnlStyle(t.realized_pnl_total) +
|
||||||
'">' +
|
'">' +
|
||||||
fmtPnl(t.realized_pnl_total) +
|
fmtPnl(t.realized_pnl_total) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="muted" style="font-size:12px">' +
|
'<td class="muted" style="font-size:12px">' +
|
||||||
(t.opened_at || "—") +
|
escapeHtml(t.opened_at || "—") +
|
||||||
"<br>" +
|
"<br>" +
|
||||||
(t.closed_at || "—") +
|
escapeHtml(t.closed_at || "—") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
fmtHold(t.hold_seconds) +
|
fmtHold(t.hold_seconds) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<td>" +
|
'<td><button type="button" class="btn or-review-btn" data-id="' +
|
||||||
(t.strategy_tag || "—") +
|
t.id +
|
||||||
"</td>" +
|
'" style="font-size:.72rem;padding:2px 8px">复盘</button> ' +
|
||||||
"<td>" +
|
'<button type="button" class="btn-secondary or-hide-btn" data-id="' +
|
||||||
(t.reviewed ? "已复盘" : "待复盘") +
|
|
||||||
"</td>" +
|
|
||||||
'<td><button type="button" class="btn-secondary or-hide-btn" data-id="' +
|
|
||||||
t.id +
|
t.id +
|
||||||
'" style="font-size:.72rem;padding:2px 8px">删除</button></td>' +
|
'" style="font-size:.72rem;padding:2px 8px">删除</button></td>' +
|
||||||
"</tr>"
|
"</tr>"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
tbody.querySelectorAll(".or-trade-row").forEach(function (tr) {
|
tbody.querySelectorAll(".or-review-btn").forEach(function (btn) {
|
||||||
tr.addEventListener("click", function (ev) {
|
btn.addEventListener("click", function (ev) {
|
||||||
if (ev.target && ev.target.closest && ev.target.closest(".or-hide-btn")) return;
|
ev.preventDefault();
|
||||||
openJournalForm(Number(tr.getAttribute("data-id")));
|
ev.stopPropagation();
|
||||||
|
openJournalForm(Number(btn.getAttribute("data-id")));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
tbody.querySelectorAll(".or-hide-btn").forEach(function (btn) {
|
tbody.querySelectorAll(".or-hide-btn").forEach(function (btn) {
|
||||||
@@ -270,10 +300,217 @@
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="muted">加载失败</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" class="muted">加载失败</td></tr>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadReviewed() {
|
||||||
|
var tbody = $("or-reviewed-tbody");
|
||||||
|
if (!tbody) return;
|
||||||
|
tbody.innerHTML = '<tr><td colspan="6" class="muted">加载中…</td></tr>';
|
||||||
|
var p = baseQs();
|
||||||
|
p.set("reviewed", "1");
|
||||||
|
p.set("limit", "100");
|
||||||
|
p.set("offset", "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 = '<tr><td colspan="6" class="muted">加载失败</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var rows = data.trades || [];
|
||||||
|
reviewedCache = {};
|
||||||
|
if (!rows.length) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="6" class="muted">暂无复盘记录</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tbody.innerHTML = rows
|
||||||
|
.map(function (t) {
|
||||||
|
reviewedCache[t.id] = t;
|
||||||
|
return (
|
||||||
|
'<tr class="or-reviewed-row" data-id="' +
|
||||||
|
t.id +
|
||||||
|
'">' +
|
||||||
|
"<td><span class=\"or-badge\">" +
|
||||||
|
escapeHtml(t.source_label || t.source_type) +
|
||||||
|
"</span></td>" +
|
||||||
|
"<td>" +
|
||||||
|
escapeHtml(tradeTitle(t)) +
|
||||||
|
"</td>" +
|
||||||
|
'<td style="' +
|
||||||
|
pnlStyle(t.realized_pnl_total) +
|
||||||
|
'">' +
|
||||||
|
fmtPnl(t.realized_pnl_total) +
|
||||||
|
"</td>" +
|
||||||
|
"<td>" +
|
||||||
|
escapeHtml(t.strategy_tag || "—") +
|
||||||
|
"</td>" +
|
||||||
|
"<td>" +
|
||||||
|
escapeHtml(t.result_tag || "—") +
|
||||||
|
"</td>" +
|
||||||
|
'<td class="muted" style="font-size:12px">' +
|
||||||
|
escapeHtml(t.reviewed_at || "—") +
|
||||||
|
"</td>" +
|
||||||
|
"</tr>"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
tbody.querySelectorAll(".or-reviewed-row").forEach(function (tr) {
|
||||||
|
tr.addEventListener("click", function () {
|
||||||
|
openDetail(Number(tr.getAttribute("data-id")));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="6" class="muted">加载失败</td></tr>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"<div><div class=\"muted\" style=\"font-size:11px\">" +
|
||||||
|
escapeHtml(pair[0]) +
|
||||||
|
"</div><div>" +
|
||||||
|
escapeHtml(pair[1]) +
|
||||||
|
"</div></div>"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = $("or-detail-text");
|
||||||
|
if (text) {
|
||||||
|
var lines = [];
|
||||||
|
if (e.mistake_tags) lines.push("<div><b>心理标签</b>:" + escapeHtml(e.mistake_tags) + "</div>");
|
||||||
|
if (e.note) lines.push("<div><b>备注</b>:" + escapeHtml(e.note).replace(/\n/g, "<br>") + "</div>");
|
||||||
|
if (t.legs && t.legs.length) {
|
||||||
|
lines.push(
|
||||||
|
"<div style=\"margin-top:6px\"><b>计划腿</b></div><table class=\"options-strike-table\"><thead><tr><th>腿</th><th>合约</th><th>盈亏</th><th>原因</th></tr></thead><tbody>" +
|
||||||
|
t.legs
|
||||||
|
.map(function (leg) {
|
||||||
|
return (
|
||||||
|
"<tr><td>" +
|
||||||
|
escapeHtml(leg.leg_role || "") +
|
||||||
|
"</td><td>" +
|
||||||
|
escapeHtml(leg.inst_id || leg.symbol || "") +
|
||||||
|
"</td><td>" +
|
||||||
|
fmtPnl(leg.realized_pnl) +
|
||||||
|
"</td><td>" +
|
||||||
|
escapeHtml(leg.close_reason || "") +
|
||||||
|
"</td></tr>"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join("") +
|
||||||
|
"</tbody></table>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
text.innerHTML = lines.join("") || '<div class="muted">无额外备注</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
var imagesHost = $("or-detail-images");
|
||||||
|
if (imagesHost) {
|
||||||
|
var images = e.images || [];
|
||||||
|
if (!images.length) {
|
||||||
|
imagesHost.innerHTML = '<div class="muted">无截图</div>';
|
||||||
|
} 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 (
|
||||||
|
'<div class="or-detail-img-cell">' +
|
||||||
|
'<span class="or-detail-img-label">' +
|
||||||
|
label +
|
||||||
|
"</span>" +
|
||||||
|
'<img class="or-detail-img-thumb" src="' +
|
||||||
|
src +
|
||||||
|
'" alt="' +
|
||||||
|
label +
|
||||||
|
'" data-src="' +
|
||||||
|
src +
|
||||||
|
'">' +
|
||||||
|
"</div>"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.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) {
|
function renderGroup(title, items) {
|
||||||
if (!items || !items.length) {
|
if (!items || !items.length) {
|
||||||
return (
|
return (
|
||||||
@@ -288,7 +525,7 @@
|
|||||||
return (
|
return (
|
||||||
'<div style="display:flex;justify-content:space-between;gap:8px;font-size:13px">' +
|
'<div style="display:flex;justify-content:space-between;gap:8px;font-size:13px">' +
|
||||||
"<span>" +
|
"<span>" +
|
||||||
g.key +
|
escapeHtml(g.key) +
|
||||||
" · " +
|
" · " +
|
||||||
g.count +
|
g.count +
|
||||||
"笔</span>" +
|
"笔</span>" +
|
||||||
@@ -314,7 +551,7 @@
|
|||||||
var kpi = $("or-kpi");
|
var kpi = $("or-kpi");
|
||||||
var groups = $("or-stats-groups");
|
var groups = $("or-stats-groups");
|
||||||
if (!kpi || !groups) return;
|
if (!kpi || !groups) return;
|
||||||
fetch("/api/options/review/stats?" + qs(), { credentials: "same-origin" })
|
fetch("/api/options/review/stats?" + baseQs().toString(), { credentials: "same-origin" })
|
||||||
.then(function (r) {
|
.then(function (r) {
|
||||||
return r.json();
|
return r.json();
|
||||||
})
|
})
|
||||||
@@ -372,7 +609,7 @@
|
|||||||
input.dataset.orBound = "1";
|
input.dataset.orBound = "1";
|
||||||
input.addEventListener("change", function () {
|
input.addEventListener("change", function () {
|
||||||
var file = input.files && input.files[0];
|
var file = input.files && input.files[0];
|
||||||
var row = input.closest(".or-upload-row");
|
var row = input.closest(".journal-upload-row");
|
||||||
var status = row && row.querySelector(".or-upload-status");
|
var status = row && row.querySelector(".or-upload-status");
|
||||||
var hidden = row && row.querySelector(".or-upload-hidden");
|
var hidden = row && row.querySelector(".or-upload-hidden");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -456,7 +693,7 @@
|
|||||||
function hideJournalForm() {
|
function hideJournalForm() {
|
||||||
currentTradeId = null;
|
currentTradeId = null;
|
||||||
var card = $("or-journal-card");
|
var card = $("or-journal-card");
|
||||||
if (card) card.classList.add("or-journal-idle");
|
if (card) card.classList.add("hidden");
|
||||||
document.querySelectorAll(".or-trade-row").forEach(function (tr) {
|
document.querySelectorAll(".or-trade-row").forEach(function (tr) {
|
||||||
tr.classList.remove("or-row-active");
|
tr.classList.remove("or-row-active");
|
||||||
});
|
});
|
||||||
@@ -478,8 +715,7 @@
|
|||||||
resetUploadSlots();
|
resetUploadSlots();
|
||||||
var summary = $("or-journal-summary");
|
var summary = $("or-journal-summary");
|
||||||
if (summary) {
|
if (summary) {
|
||||||
summary.textContent =
|
summary.textContent = "截图槽位与合约复盘相同(5m / 15m / 1h / 4h).";
|
||||||
"点下方列表一笔记录自动填入;截图槽位与合约复盘相同(5m / 15m / 1h / 4h).";
|
|
||||||
}
|
}
|
||||||
var legsHost = $("or-legs-host");
|
var legsHost = $("or-legs-host");
|
||||||
if (legsHost) legsHost.innerHTML = "";
|
if (legsHost) legsHost.innerHTML = "";
|
||||||
@@ -490,7 +726,7 @@
|
|||||||
currentTradeId = tradeId;
|
currentTradeId = tradeId;
|
||||||
var card = $("or-journal-card");
|
var card = $("or-journal-card");
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
card.classList.remove("or-journal-idle");
|
card.classList.remove("hidden");
|
||||||
card.scrollIntoView({ behavior: "smooth", block: "start" });
|
card.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
document.querySelectorAll(".or-trade-row").forEach(function (tr) {
|
document.querySelectorAll(".or-trade-row").forEach(function (tr) {
|
||||||
tr.classList.toggle("or-row-active", Number(tr.getAttribute("data-id")) === tradeId);
|
tr.classList.toggle("or-row-active", Number(tr.getAttribute("data-id")) === tradeId);
|
||||||
@@ -551,7 +787,6 @@
|
|||||||
: "");
|
: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 已有截图回填状态(文件名保留,新上传会换 draft)
|
|
||||||
(e.images || []).forEach(function (img) {
|
(e.images || []).forEach(function (img) {
|
||||||
var hidden = document.querySelector(
|
var hidden = document.querySelector(
|
||||||
'#or-upload-slots .or-upload-hidden[data-tf="' + img.tf + '"]'
|
'#or-upload-slots .or-upload-hidden[data-tf="' + img.tf + '"]'
|
||||||
@@ -574,13 +809,13 @@
|
|||||||
.map(function (leg) {
|
.map(function (leg) {
|
||||||
return (
|
return (
|
||||||
"<tr><td>" +
|
"<tr><td>" +
|
||||||
(leg.leg_role || "") +
|
escapeHtml(leg.leg_role || "") +
|
||||||
"</td><td>" +
|
"</td><td>" +
|
||||||
(leg.inst_id || leg.symbol || "") +
|
escapeHtml(leg.inst_id || leg.symbol || "") +
|
||||||
"</td><td>" +
|
"</td><td>" +
|
||||||
fmtPnl(leg.realized_pnl) +
|
fmtPnl(leg.realized_pnl) +
|
||||||
"</td><td>" +
|
"</td><td>" +
|
||||||
(leg.close_reason || "") +
|
escapeHtml(leg.close_reason || "") +
|
||||||
"</td></tr>"
|
"</td></tr>"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -594,7 +829,7 @@
|
|||||||
|
|
||||||
function hideTrade(tradeId) {
|
function hideTrade(tradeId) {
|
||||||
if (!tradeId) return;
|
if (!tradeId) return;
|
||||||
if (!confirm("从复盘列表删除并隐藏?刷新后也不会再出现.")) return;
|
if (!confirm("从待复盘列表删除并隐藏?刷新后也不会再出现.")) return;
|
||||||
fetch("/api/options/review/trades/" + tradeId, {
|
fetch("/api/options/review/trades/" + tradeId, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
@@ -618,7 +853,7 @@
|
|||||||
function saveEntry() {
|
function saveEntry() {
|
||||||
var tradeId = Number(($("or-trade-id") || {}).value || 0);
|
var tradeId = Number(($("or-trade-id") || {}).value || 0);
|
||||||
if (!tradeId) {
|
if (!tradeId) {
|
||||||
alert("请先在下方列表点选一笔本地记录");
|
alert("请先点击交易记录中的「复盘」");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var strategy = (($("or-f-strategy") || {}).value || "").trim();
|
var strategy = (($("or-f-strategy") || {}).value || "").trim();
|
||||||
@@ -654,7 +889,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
($("or-save-status") || {}).textContent = "已保存";
|
($("or-save-status") || {}).textContent = "已保存";
|
||||||
|
hideJournalForm();
|
||||||
reloadAll();
|
reloadAll();
|
||||||
|
openDetail(tradeId);
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
($("or-save-status") || {}).textContent = "保存失败";
|
($("or-save-status") || {}).textContent = "保存失败";
|
||||||
@@ -664,7 +901,7 @@
|
|||||||
function deleteEntry() {
|
function deleteEntry() {
|
||||||
var tradeId = Number(($("or-trade-id") || {}).value || 0);
|
var tradeId = Number(($("or-trade-id") || {}).value || 0);
|
||||||
if (!tradeId) return;
|
if (!tradeId) return;
|
||||||
if (!confirm("删除该条复盘内容与图片?")) return;
|
if (!confirm("删除该条复盘内容与图片?交易记录会回到待复盘列表.")) return;
|
||||||
fetch("/api/options/review/entry/" + tradeId, {
|
fetch("/api/options/review/entry/" + tradeId, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
@@ -674,6 +911,7 @@
|
|||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
hideJournalForm();
|
hideJournalForm();
|
||||||
|
hideDetail();
|
||||||
reloadAll();
|
reloadAll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -689,18 +927,56 @@
|
|||||||
var saveBtn = $("or-save-btn");
|
var saveBtn = $("or-save-btn");
|
||||||
var clearBtn = $("or-clear-btn");
|
var clearBtn = $("or-clear-btn");
|
||||||
var delBtn = $("or-del-btn");
|
var delBtn = $("or-del-btn");
|
||||||
|
var prevBtn = $("or-trades-prev");
|
||||||
|
var nextBtn = $("or-trades-next");
|
||||||
|
var detailClose = $("or-detail-close-btn");
|
||||||
|
var detailEdit = $("or-detail-edit-btn");
|
||||||
if (reloadBtn) reloadBtn.addEventListener("click", reloadAll);
|
if (reloadBtn) reloadBtn.addEventListener("click", reloadAll);
|
||||||
if (saveBtn) saveBtn.addEventListener("click", saveEntry);
|
if (saveBtn) saveBtn.addEventListener("click", saveEntry);
|
||||||
if (clearBtn) clearBtn.addEventListener("click", hideJournalForm);
|
if (clearBtn) clearBtn.addEventListener("click", hideJournalForm);
|
||||||
if (delBtn) delBtn.addEventListener("click", deleteEntry);
|
if (delBtn) delBtn.addEventListener("click", deleteEntry);
|
||||||
["or-filter-uly", "or-filter-opt", "or-filter-reviewed", "or-include-hedge-legs"].forEach(
|
if (prevBtn) {
|
||||||
function (id) {
|
prevBtn.addEventListener("click", function () {
|
||||||
var el = $(id);
|
if (tradesPage <= 0) return;
|
||||||
if (el) el.addEventListener("change", reloadAll);
|
tradesPage -= 1;
|
||||||
|
loadTrades();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
if (nextBtn) {
|
||||||
|
nextBtn.addEventListener("click", function () {
|
||||||
|
if (!tradesHasMore) return;
|
||||||
|
tradesPage += 1;
|
||||||
|
loadTrades();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
reloadAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
["or-filter-strategy", "or-filter-from", "or-filter-to"].forEach(function (id) {
|
||||||
|
var el = $(id);
|
||||||
|
if (el) {
|
||||||
|
el.addEventListener("change", function () {
|
||||||
|
tradesPage = 0;
|
||||||
|
reloadAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
bindUploadSlots();
|
bindUploadSlots();
|
||||||
hideJournalForm();
|
hideJournalForm();
|
||||||
|
hideDetail();
|
||||||
setActiveTab("option_spot");
|
setActiveTab("option_spot");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -582,10 +582,12 @@ def enrich_trade_row(row: dict[str, Any], entry: dict[str, Any] | None = None) -
|
|||||||
out["entry"]["images"] = parse_options_review_images_json(entry.get("images_json"))
|
out["entry"]["images"] = parse_options_review_images_json(entry.get("images_json"))
|
||||||
out["strategy_tag"] = entry.get("strategy_tag")
|
out["strategy_tag"] = entry.get("strategy_tag")
|
||||||
out["result_tag"] = entry.get("result_tag")
|
out["result_tag"] = entry.get("result_tag")
|
||||||
|
out["reviewed_at"] = entry.get("reviewed_at") or entry.get("updated_at")
|
||||||
else:
|
else:
|
||||||
out["entry"] = None
|
out["entry"] = None
|
||||||
out["strategy_tag"] = None
|
out["strategy_tag"] = None
|
||||||
out["result_tag"] = None
|
out["result_tag"] = None
|
||||||
|
out["reviewed_at"] = None
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{# OKX 期权复盘:读本地记录;复盘上传区字体缩小,截图槽位对齐合约 #}
|
{# OKX 期权复盘:交易记录(5行) → 点复盘出表单 → 复盘记录 → 统计 #}
|
||||||
<div class="options-review-wrap" id="options-review-root" style="grid-column:1/-1">
|
<div class="options-review-wrap" id="options-review-root" style="grid-column:1/-1">
|
||||||
{% if not options_enabled %}
|
{% if not options_enabled %}
|
||||||
<div class="flash" style="margin-bottom:12px;font-size:.82rem">期权未启用:请设置 <code>OKX_OPTIONS_ENABLED=true</code> 后重启.</div>
|
<div class="flash" style="margin-bottom:12px;font-size:.82rem">期权未启用:请设置 <code>OKX_OPTIONS_ENABLED=true</code> 后重启.</div>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.options-review-wrap{font-size:.82rem}
|
.options-review-wrap{font-size:.82rem}
|
||||||
.options-review-wrap h2{font-size:1rem;margin:0 0 8px}
|
.options-review-wrap h2{font-size:1rem;margin:0 0 8px}
|
||||||
.options-review-wrap h3{font-size:.9rem}
|
.options-review-wrap h3{font-size:.9rem;margin:0 0 8px}
|
||||||
.or-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
|
.or-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
|
||||||
.or-tab{border:1px solid rgba(127,127,127,.35);background:transparent;color:inherit;padding:5px 10px;border-radius:6px;cursor:pointer;font-size:.78rem}
|
.or-tab{border:1px solid rgba(127,127,127,.35);background:transparent;color:inherit;padding:5px 10px;border-radius:6px;cursor:pointer;font-size:.78rem}
|
||||||
.or-tab.active{background:rgba(59,130,246,.25);border-color:rgba(59,130,246,.55)}
|
.or-tab.active{background:rgba(59,130,246,.25);border-color:rgba(59,130,246,.55)}
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
.or-stat-card{border:1px solid rgba(127,127,127,.25);border-radius:8px;padding:8px;font-size:.78rem}
|
.or-stat-card{border:1px solid rgba(127,127,127,.25);border-radius:8px;padding:8px;font-size:.78rem}
|
||||||
.or-trades-table{font-size:.78rem}
|
.or-trades-table{font-size:.78rem}
|
||||||
.or-trades-table tr.or-row-active{outline:1px solid rgba(59,130,246,.55);background:rgba(59,130,246,.08)}
|
.or-trades-table tr.or-row-active{outline:1px solid rgba(59,130,246,.55);background:rgba(59,130,246,.08)}
|
||||||
.or-trades-table tbody tr{cursor:pointer}
|
|
||||||
.or-journal-card{font-size:.78rem}
|
.or-journal-card{font-size:.78rem}
|
||||||
.or-journal-card h2{font-size:.92rem}
|
.or-journal-card h2{font-size:.92rem}
|
||||||
.or-journal-card input,
|
.or-journal-card input,
|
||||||
@@ -27,9 +26,19 @@
|
|||||||
.or-journal-card .or-mood-grid{display:flex;flex-wrap:wrap;gap:6px 12px;margin:8px 0;font-size:.74rem}
|
.or-journal-card .or-mood-grid{display:flex;flex-wrap:wrap;gap:6px 12px;margin:8px 0;font-size:.74rem}
|
||||||
.or-journal-card .muted,
|
.or-journal-card .muted,
|
||||||
.or-journal-card .sub{font-size:.7rem}
|
.or-journal-card .sub{font-size:.7rem}
|
||||||
.or-journal-idle .or-journal-body{opacity:.78}
|
.or-journal-card.hidden{display:none!important}
|
||||||
|
.or-reviewed-table tbody tr{cursor:pointer}
|
||||||
|
.or-detail-panel{margin-top:10px;padding-top:10px;border-top:1px solid rgba(127,127,127,.25)}
|
||||||
|
.or-detail-panel.hidden{display:none!important}
|
||||||
|
.or-detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:6px 12px;font-size:.76rem;margin-bottom:8px}
|
||||||
|
.or-detail-images{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;margin:8px 0}
|
||||||
|
.or-detail-img-cell{border:1px solid rgba(127,127,127,.25);border-radius:6px;padding:6px;text-align:center}
|
||||||
|
.or-detail-img-label{display:block;font-size:.7rem;margin-bottom:4px;opacity:.8}
|
||||||
|
.or-detail-img-thumb{max-width:100%;max-height:160px;border-radius:4px;cursor:pointer}
|
||||||
|
.or-pager{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:.74rem}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
{# 1. 交易记录(含 Tab/筛选,固定约5行) #}
|
||||||
<div class="card" style="margin-bottom:10px">
|
<div class="card" style="margin-bottom:10px">
|
||||||
<div class="form-row" style="flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:6px">
|
<div class="form-row" style="flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:6px">
|
||||||
<h2 style="margin:0;margin-right:auto">期权复盘</h2>
|
<h2 style="margin:0;margin-right:auto">期权复盘</h2>
|
||||||
@@ -41,8 +50,8 @@
|
|||||||
<button type="button" class="or-tab" data-source="options_options" role="tab">期期对冲记录</button>
|
<button type="button" class="or-tab" data-source="options_options" role="tab">期期对冲记录</button>
|
||||||
<button type="button" class="or-tab" data-source="perp_options" role="tab">永期对冲记录</button>
|
<button type="button" class="or-tab" data-source="perp_options" role="tab">永期对冲记录</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="muted" style="margin:0 0 8px;font-size:.72rem">直接读取本地已平期权与已结束对冲计划,无需同步交易所.点下方记录可填入复盘表单.</p>
|
<p class="muted" style="margin:0 0 8px;font-size:.72rem">待复盘交易(每页5条).点「复盘」填写表单;保存后进入下方复盘记录.</p>
|
||||||
<div class="form-row" style="flex-wrap:wrap;gap:6px">
|
<div class="form-row" style="flex-wrap:wrap;gap:6px;margin-bottom:8px">
|
||||||
<select id="or-filter-uly" style="font-size:.76rem">
|
<select id="or-filter-uly" style="font-size:.76rem">
|
||||||
<option value="">标的:全部</option>
|
<option value="">标的:全部</option>
|
||||||
<option value="ETH">ETH</option>
|
<option value="ETH">ETH</option>
|
||||||
@@ -53,11 +62,6 @@
|
|||||||
<option value="C">Call</option>
|
<option value="C">Call</option>
|
||||||
<option value="P">Put</option>
|
<option value="P">Put</option>
|
||||||
</select>
|
</select>
|
||||||
<select id="or-filter-reviewed" style="font-size:.76rem">
|
|
||||||
<option value="">复盘:全部</option>
|
|
||||||
<option value="0">待复盘</option>
|
|
||||||
<option value="1">已复盘</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" id="or-filter-strategy" placeholder="策略标签" style="max-width:110px;font-size:.76rem">
|
<input type="text" id="or-filter-strategy" placeholder="策略标签" style="max-width:110px;font-size:.76rem">
|
||||||
<input type="datetime-local" id="or-filter-from" title="平仓起" style="font-size:.76rem">
|
<input type="datetime-local" id="or-filter-from" title="平仓起" style="font-size:.76rem">
|
||||||
<input type="datetime-local" id="or-filter-to" title="平仓止" style="font-size:.76rem">
|
<input type="datetime-local" id="or-filter-to" title="平仓止" style="font-size:.76rem">
|
||||||
@@ -65,11 +69,35 @@
|
|||||||
<input type="checkbox" id="or-include-hedge-legs"> 含已归属对冲的期权腿
|
<input type="checkbox" id="or-include-hedge-legs"> 含已归属对冲的期权腿
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<h3 id="or-list-title" style="margin-top:0">期权交易记录</h3>
|
||||||
|
<div class="options-strike-table-wrap">
|
||||||
|
<table class="options-strike-table or-trades-table" id="or-trades-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>类型</th>
|
||||||
|
<th>标的/合约</th>
|
||||||
|
<th>盈亏</th>
|
||||||
|
<th>开/平</th>
|
||||||
|
<th>持有</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="or-trades-tbody">
|
||||||
|
<tr><td colspan="6" class="muted">加载中…</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="or-pager" id="or-trades-pager">
|
||||||
|
<button type="button" class="btn-secondary" id="or-trades-prev" style="font-size:.72rem;padding:2px 8px">上一页</button>
|
||||||
|
<span class="muted" id="or-trades-page-label">1</span>
|
||||||
|
<button type="button" class="btn-secondary" id="or-trades-next" style="font-size:.72rem;padding:2px 8px">下一页</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card journal-card or-journal-card or-journal-idle" id="or-journal-card" style="margin-bottom:10px">
|
{# 2. 复盘上传(默认隐藏,点交易「复盘」后显示) #}
|
||||||
|
<div class="card journal-card or-journal-card hidden" id="or-journal-card" style="margin-bottom:10px">
|
||||||
<h2>复盘记录上传(含截图)</h2>
|
<h2>复盘记录上传(含截图)</h2>
|
||||||
<p class="muted" id="or-journal-summary" style="margin-top:0">点下方列表一笔记录自动填入;截图槽位与合约复盘相同(5m / 15m / 1h / 4h).</p>
|
<p class="muted" id="or-journal-summary" style="margin-top:0">截图槽位与合约复盘相同(5m / 15m / 1h / 4h).</p>
|
||||||
<div class="or-journal-body">
|
<div class="or-journal-body">
|
||||||
<form id="or-journal-form" onsubmit="return false;">
|
<form id="or-journal-form" onsubmit="return false;">
|
||||||
<input type="hidden" id="or-trade-id" value="">
|
<input type="hidden" id="or-trade-id" value="">
|
||||||
@@ -126,7 +154,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<p class="sub journal-upload-hint">可只传部分周期;选文件后即时上传,保存后详情页四宫格查看</p>
|
<p class="sub journal-upload-hint">可只传部分周期;选文件后即时上传</p>
|
||||||
|
|
||||||
<div class="or-mood-grid mood-grid">
|
<div class="or-mood-grid mood-grid">
|
||||||
<label><input type="checkbox" class="or-mood" value="怕踏空">怕踏空</label>
|
<label><input type="checkbox" class="or-mood" value="怕踏空">怕踏空</label>
|
||||||
@@ -139,8 +167,8 @@
|
|||||||
<textarea id="or-f-note" rows="2" placeholder="备注" style="width:100%;box-sizing:border-box"></textarea>
|
<textarea id="or-f-note" rows="2" placeholder="备注" style="width:100%;box-sizing:border-box"></textarea>
|
||||||
<div class="form-row" style="margin-top:8px;gap:6px">
|
<div class="form-row" style="margin-top:8px;gap:6px">
|
||||||
<button type="button" class="btn" id="or-save-btn">保存复盘记录</button>
|
<button type="button" class="btn" id="or-save-btn">保存复盘记录</button>
|
||||||
<button type="button" class="btn-secondary" id="or-clear-btn">清空表单</button>
|
<button type="button" class="btn-secondary" id="or-clear-btn">取消</button>
|
||||||
<button type="button" class="btn-secondary" id="or-del-btn">删除复盘</button>
|
<button type="button" class="btn-secondary" id="or-del-btn">删除复盘内容</button>
|
||||||
<span class="muted" id="or-save-status"></span>
|
<span class="muted" id="or-save-status"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="or-legs-host" style="margin-top:10px;font-size:.74rem"></div>
|
<div id="or-legs-host" style="margin-top:10px;font-size:.74rem"></div>
|
||||||
@@ -148,35 +176,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# 3. 已复盘记录 + 详情 #}
|
||||||
<div class="card" style="margin-bottom:10px">
|
<div class="card" style="margin-bottom:10px">
|
||||||
<h3 style="margin-top:0">统计</h3>
|
<h3>复盘记录</h3>
|
||||||
<div id="or-kpi" class="form-row" style="flex-wrap:wrap;gap:10px"></div>
|
<p class="muted" style="margin-top:0;font-size:.72rem">保存后出现在此.点一行查看详情(含截图与完整复盘内容).</p>
|
||||||
<div id="or-stats-groups" style="margin-top:10px;display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:8px"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h3 style="margin-top:0" id="or-list-title">期权交易记录</h3>
|
|
||||||
<p class="muted" style="margin-top:0;font-size:.72rem">点选一行,上方复盘表单自动填入.</p>
|
|
||||||
<div class="options-strike-table-wrap">
|
<div class="options-strike-table-wrap">
|
||||||
<table class="options-strike-table or-trades-table" id="or-trades-table">
|
<table class="options-strike-table or-reviewed-table" id="or-reviewed-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>类型</th>
|
<th>类型</th>
|
||||||
<th>标的/合约</th>
|
<th>标的/合约</th>
|
||||||
<th>盈亏</th>
|
<th>盈亏</th>
|
||||||
<th>开/平</th>
|
|
||||||
<th>持有</th>
|
|
||||||
<th>策略</th>
|
<th>策略</th>
|
||||||
<th>状态</th>
|
<th>结果</th>
|
||||||
<th>操作</th>
|
<th>复盘时间</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="or-trades-tbody">
|
<tbody id="or-reviewed-tbody">
|
||||||
<tr><td colspan="8" class="muted">加载中…</td></tr>
|
<tr><td colspan="6" class="muted">加载中…</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="or-detail-panel hidden" id="or-detail-panel">
|
||||||
|
<div class="form-row" style="align-items:center;gap:8px;margin-bottom:6px">
|
||||||
|
<h3 style="margin:0;margin-right:auto" id="or-detail-title">复盘详情</h3>
|
||||||
|
<button type="button" class="btn-secondary" id="or-detail-edit-btn" style="font-size:.72rem;padding:2px 8px">编辑</button>
|
||||||
|
<button type="button" class="btn-secondary" id="or-detail-close-btn" style="font-size:.72rem;padding:2px 8px">收起</button>
|
||||||
|
</div>
|
||||||
|
<div class="or-detail-grid" id="or-detail-meta"></div>
|
||||||
|
<div id="or-detail-text" style="font-size:.76rem;line-height:1.5;margin-bottom:8px"></div>
|
||||||
|
<div class="or-detail-images" id="or-detail-images"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/options_review.js?v=7"></script>
|
{# 4. 统计 #}
|
||||||
|
<div class="card" style="margin-bottom:10px">
|
||||||
|
<h3>统计</h3>
|
||||||
|
<div id="or-kpi" class="form-row" style="flex-wrap:wrap;gap:10px"></div>
|
||||||
|
<div id="or-stats-groups" style="margin-top:10px;display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:8px"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/options_review.js?v=8"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user