diff --git a/docs/期权用法.md b/docs/期权用法.md
index 9ed3158..c870737 100644
--- a/docs/期权用法.md
+++ b/docs/期权用法.md
@@ -118,9 +118,13 @@ OKX_OPTIONS_API_PASSPHRASE=...
### 图片
- 目录:`static/images/options_journal/`
-- 文件名:`options_journal_{draftId}_{chart|entry|exit|other}.ext`
+- 文件名:`options_journal_{draftId}_{5m|15m|1h|4h}.ext`(与合约复盘同周期槽位)
- 备份时与 `crypto.db` 一并打包即可;勿与合约 `journal_*` 截图混用.
+### 页面
+
+顶部三个 Tab:**期权交易记录** / **期期对冲记录** / **永期对冲记录**.点击列表行后在下方打开「复盘记录上传」,支持四周期即时截图与情绪标签.
+
### 统计
同页 KPI + 分组:类型、标的、策略标签、对冲结束原因、持有周期、Call/Put.策略维度仅统计已填策略标签的记录.
diff --git a/lib/common/static/options_review.js b/lib/common/static/options_review.js
index 8c55e53..346a850 100644
--- a/lib/common/static/options_review.js
+++ b/lib/common/static/options_review.js
@@ -1,13 +1,19 @@
/**
- * OKX 期权复盘(含对冲):列表 / 同步 / 统计 / 编辑上传.
+ * OKX 期权复盘:三类 Tab + 列表点选 + 下方多周期(5m/15m/1h/4h)复盘表单.
*/
(function (global) {
"use strict";
- var SLOT_TFS = ["chart", "entry", "exit", "other"];
- var SLOT_LABELS = { chart: "走势图", entry: "入场", exit: "离场", other: "其他" };
+ var SLOT_TFS = ["5m", "15m", "1h", "4h"];
+ var TAB_LABELS = {
+ option_spot: "期权交易记录",
+ options_options: "期期对冲记录",
+ perp_options: "永期对冲记录",
+ };
+ var activeSource = "option_spot";
var currentTradeId = null;
var draftId = "";
+ var tradesCache = {};
function $(id) {
return document.getElementById(id);
@@ -17,8 +23,7 @@
if (v == null || v === "") return "—";
var n = Number(v);
if (Number.isNaN(n)) return "—";
- var s = (n >= 0 ? "+" : "") + n.toFixed(2);
- return s;
+ return (n >= 0 ? "+" : "") + n.toFixed(2);
}
function fmtHold(sec) {
@@ -29,24 +34,11 @@
return (s / 86400).toFixed(1) + "d";
}
- function qs() {
- var p = new URLSearchParams();
- var source = ($("or-filter-source") || {}).value || "";
- var uly = ($("or-filter-uly") || {}).value || "";
- var opt = ($("or-filter-opt") || {}).value || "";
- var reviewed = ($("or-filter-reviewed") || {}).value || "";
- var strategy = (($("or-filter-strategy") || {}).value || "").trim();
- var from = ($("or-filter-from") || {}).value || "";
- var to = ($("or-filter-to") || {}).value || "";
- if (source) p.set("source_type", source);
- if (uly) p.set("underlying", uly);
- if (opt) p.set("opt_type", opt);
- if (reviewed) p.set("reviewed", reviewed);
- 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.toString();
+ 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 newDraftId() {
@@ -58,6 +50,25 @@
return s;
}
+ function qs() {
+ var p = new URLSearchParams();
+ p.set("source_type", activeSource);
+ var uly = ($("or-filter-uly") || {}).value || "";
+ var opt = ($("or-filter-opt") || {}).value || "";
+ var reviewed = ($("or-filter-reviewed") || {}).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 (reviewed) p.set("reviewed", reviewed);
+ 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.toString();
+ }
+
function setSyncStatus(text) {
var el = $("or-sync-status");
if (el) el.textContent = text || "";
@@ -93,6 +104,17 @@
});
}
+ function setActiveTab(source) {
+ activeSource = source || "option_spot";
+ 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] || "记录";
+ hideJournalForm();
+ reloadAll();
+ }
+
function reloadAll() {
loadTrades();
loadStats();
@@ -101,23 +123,25 @@
function loadTrades() {
var tbody = $("or-trades-tbody");
if (!tbody) return;
- tbody.innerHTML = '
| 加载中… |
';
+ tbody.innerHTML = '| 加载中… |
';
fetch("/api/options/review/trades?" + qs(), { credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (data) {
if (!data.ok) {
- tbody.innerHTML = '| 加载失败 |
';
+ tbody.innerHTML = '| 加载失败 |
';
return;
}
var rows = data.trades || [];
+ tradesCache = {};
if (!rows.length) {
- tbody.innerHTML = '| 暂无记录,请先同步 |
';
+ tbody.innerHTML = '| 暂无记录,请先同步 |
';
return;
}
tbody.innerHTML = rows
.map(function (t) {
+ tradesCache[t.id] = t;
var title =
t.source_type === "option_spot"
? t.inst_id || "—"
@@ -130,22 +154,25 @@
: Number(t.realized_pnl_total) < 0
? "color:#f07178"
: "";
+ var active = currentTradeId === t.id ? " or-row-active" : "";
return (
- "" +
+ '">' +
"| " +
(t.source_label || t.source_type) +
" | " +
"" +
title +
" | " +
- "" +
+ '">' +
fmtPnl(t.realized_pnl_total) +
" | " +
- "" +
+ ' | ' +
(t.opened_at || "—") +
" " +
(t.closed_at || "—") +
@@ -159,21 +186,18 @@
" | " +
(t.reviewed ? "已复盘" : "待复盘") +
" | " +
- " | " +
"
"
);
})
.join("");
- tbody.querySelectorAll(".or-edit-btn").forEach(function (btn) {
- btn.addEventListener("click", function () {
- openEdit(Number(btn.getAttribute("data-id")));
+ tbody.querySelectorAll(".or-trade-row").forEach(function (tr) {
+ tr.addEventListener("click", function () {
+ openJournalForm(Number(tr.getAttribute("data-id")));
});
});
})
.catch(function () {
- tbody.innerHTML = '| 加载失败 |
';
+ tbody.innerHTML = '| 加载失败 |
';
});
}
@@ -189,7 +213,7 @@
.slice(0, 8)
.map(function (g) {
return (
- "" +
+ '
' +
"
" +
g.key +
" · " +
@@ -204,7 +228,13 @@
);
})
.join("");
- return '' + title + "
" + lines + "
";
+ return (
+ '' +
+ title +
+ "
" +
+ lines +
+ "
"
+ );
}
function loadStats() {
@@ -248,197 +278,231 @@
.catch(function () {});
}
- function uploadSlot(input, file) {
- if (!draftId || !file) return;
- var status = input.parentElement && input.parentElement.querySelector(".or-upload-status");
- var hidden = input.parentElement && input.parentElement.querySelector(".or-upload-hidden");
- if (status) status.textContent = "上传中…";
- 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 = "已上传";
- input.value = "";
- })
- .catch(function () {
- if (hidden) hidden.value = "";
- if (status) status.textContent = "失败";
- });
+ 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 openEdit(tradeId) {
- currentTradeId = tradeId;
- draftId = newDraftId();
- var modal = $("or-edit-modal");
- var body = $("or-edit-body");
- var title = $("or-edit-title");
- if (!modal || !body) return;
- body.innerHTML = '加载中…
';
- modal.style.display = "flex";
- fetch("/api/options/review/trades/" + tradeId, { credentials: "same-origin" })
- .then(function (r) {
- return r.json();
- })
- .then(function (data) {
- if (!data.ok || !data.trade) {
- body.innerHTML = '加载失败
';
+ 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(".or-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 = "";
return;
}
- var t = data.trade;
- var e = t.entry || {};
- if (title) title.textContent = (t.source_label || "") + " · 复盘 #" + t.id;
- var legsHtml = "";
- if (t.legs && t.legs.length) {
- legsHtml =
- '| 腿 | 合约 | 盈亏 | 原因 |
' +
- t.legs
- .map(function (leg) {
- return (
- "| " +
- (leg.leg_role || "") +
- " | " +
- (leg.inst_id || leg.symbol || "") +
- " | " +
- fmtPnl(leg.realized_pnl) +
- " | " +
- (leg.close_reason || "") +
- " |
"
- );
- })
- .join("") +
- "
";
- }
- var slots = SLOT_TFS.map(function (tf) {
- return (
- '' +
- "" +
- '' +
- '
'
- );
- }).join("");
- body.innerHTML =
- '' +
- (t.inst_id || t.underlying || "") +
- " · 盈亏 " +
- fmtPnl(t.realized_pnl_total) +
- (t.is_hedge
- ? " (永续 " +
- fmtPnl(t.realized_pnl_perp) +
- " / 期权 " +
- fmtPnl(t.realized_pnl_options) +
- ")"
- : "") +
- "
开 " +
- (t.opened_at || "—") +
- " → 平 " +
- (t.closed_at || "—") +
- "
" +
- legsHtml +
- '' +
- '' +
- '' +
- '' +
- '" +
- '' +
- '' +
- "
" +
- '" +
- '" +
- '截图' +
- slots +
- "
" +
- '' +
- '' +
- '' +
- "
";
- body.querySelectorAll(".or-upload-input").forEach(function (input) {
- input.addEventListener("change", function () {
- var file = input.files && input.files[0];
- if (file) uploadSlot(input, file);
+ if (!draftId) draftId = newDraftId();
+ if (status) status.textContent = "上传中…";
+ 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;
+ input.value = "";
+ })
+ .catch(function () {
+ if (hidden) hidden.value = "";
+ if (status) status.textContent = "上传失败";
});
- });
- // 回填已有图片文件名到 hidden(仅展示状态)
- (e.images || []).forEach(function (img) {
- var hidden = body.querySelector('.or-upload-hidden[data-tf="' + img.tf + '"]');
- var status = hidden && hidden.parentElement.querySelector(".or-upload-status");
- if (hidden && img.file) {
- hidden.value = img.file;
- if (status) status.textContent = "已有 " + img.file;
- }
- });
- $("or-save-btn").addEventListener("click", saveEntry);
- $("or-del-btn").addEventListener("click", deleteEntry);
});
+ });
}
- function esc(s) {
- return String(s == null ? "" : s)
- .replace(/&/g, "&")
- .replace(/" + v + "";
+ function collectMoodTags() {
+ var out = [];
+ document.querySelectorAll(".or-mood:checked").forEach(function (cb) {
+ out.push(cb.value);
+ });
+ return out.join(",");
}
function collectImages() {
- var body = $("or-edit-body");
- if (!body) return [];
var out = [];
- body.querySelectorAll(".or-upload-hidden").forEach(function (el) {
+ 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.hidden = true;
+ document.querySelectorAll(".or-trade-row").forEach(function (tr) {
+ tr.classList.remove("or-row-active");
+ });
+ }
+
+ function openJournalForm(tradeId) {
+ currentTradeId = tradeId;
+ var card = $("or-journal-card");
+ if (!card) return;
+ card.hidden = false;
+ 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 = "";
+ })
+ .catch(function () {
+ ($("or-save-status") || {}).textContent = "加载失败";
+ });
+ }
+
+ function fillForm(t) {
+ var e = t.entry || {};
+ ($("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);
+ ($("or-f-strategy") || {}).value = e.strategy_tag || "";
+ ($("or-f-direction") || {}).value = e.direction_view || t.direction || "";
+ ($("or-f-exit") || {}).value = e.exit_reason || t.plan_close_reason || "";
+ ($("or-f-followed") || {}).value = e.followed_plan || "";
+ ($("or-f-result") || {}).value = e.result_tag || "";
+ ($("or-f-entry") || {}).value = 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) + ")"
+ : "");
+ }
+
+ // 已有截图回填状态(文件名保留,新上传会换 draft)
+ (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 (
+ "| " +
+ (leg.leg_role || "") +
+ " | " +
+ (leg.inst_id || leg.symbol || "") +
+ " | " +
+ fmtPnl(leg.realized_pnl) +
+ " | " +
+ (leg.close_reason || "") +
+ " |
"
+ );
+ })
+ .join("") +
+ "
";
+ } else {
+ legsHost.innerHTML = "";
+ }
+ }
+ }
+
function saveEntry() {
- if (!currentTradeId) return;
+ var tradeId = Number(($("or-trade-id") || {}).value || 0);
+ if (!tradeId) return;
+ var strategy = (($("or-f-strategy") || {}).value || "").trim();
+ if (!strategy) {
+ alert("请填写策略标签");
+ return;
+ }
var payload = {
- trade_id: currentTradeId,
- strategy_tag: ($("or-f-strategy") || {}).value || "",
+ 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: ($("or-f-mistakes") || {}).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",
@@ -450,21 +514,22 @@
})
.then(function (data) {
if (!data.ok) {
- alert(data.msg || "保存失败");
+ ($("or-save-status") || {}).textContent = data.msg || "保存失败";
return;
}
- closeModal();
+ ($("or-save-status") || {}).textContent = "已保存";
reloadAll();
})
.catch(function () {
- alert("保存失败");
+ ($("or-save-status") || {}).textContent = "保存失败";
});
}
function deleteEntry() {
- if (!currentTradeId) return;
+ var tradeId = Number(($("or-trade-id") || {}).value || 0);
+ if (!tradeId) return;
if (!confirm("删除该条复盘内容与图片?")) return;
- fetch("/api/options/review/entry/" + currentTradeId, {
+ fetch("/api/options/review/entry/" + tradeId, {
method: "DELETE",
credentials: "same-origin",
})
@@ -472,34 +537,43 @@
return r.json();
})
.then(function () {
- closeModal();
+ hideJournalForm();
reloadAll();
});
}
- function closeModal(ev) {
- if (ev && ev.target && ev.target.id !== "or-edit-modal") return;
- var modal = $("or-edit-modal");
- if (modal) modal.style.display = "none";
- currentTradeId = null;
- }
-
function init() {
if (!$("options-review-root")) return;
+ document.querySelectorAll(".or-tab").forEach(function (btn) {
+ btn.addEventListener("click", function () {
+ setActiveTab(btn.getAttribute("data-source"));
+ });
+ });
var syncBtn = $("or-sync-btn");
var reloadBtn = $("or-reload-btn");
+ var saveBtn = $("or-save-btn");
+ var clearBtn = $("or-clear-btn");
+ var delBtn = $("or-del-btn");
if (syncBtn) syncBtn.addEventListener("click", syncNow);
if (reloadBtn) reloadBtn.addEventListener("click", reloadAll);
- ["or-filter-source", "or-filter-uly", "or-filter-opt", "or-filter-reviewed", "or-include-hedge-legs"].forEach(
+ if (saveBtn) saveBtn.addEventListener("click", saveEntry);
+ if (clearBtn) clearBtn.addEventListener("click", hideJournalForm);
+ if (delBtn) delBtn.addEventListener("click", deleteEntry);
+ ["or-filter-uly", "or-filter-opt", "or-filter-reviewed", "or-include-hedge-legs"].forEach(
function (id) {
var el = $(id);
if (el) el.addEventListener("change", reloadAll);
}
);
- reloadAll();
+ bindUploadSlots();
+ setActiveTab("option_spot");
}
- global.OptionsReview = { init: init, closeModal: closeModal, openEdit: openEdit };
+ global.OptionsReview = {
+ init: init,
+ openJournalForm: openJournalForm,
+ hideJournalForm: hideJournalForm,
+ };
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
diff --git a/lib/options/options_review_images_lib.py b/lib/options/options_review_images_lib.py
index d22d5f0..b1845a8 100644
--- a/lib/options/options_review_images_lib.py
+++ b/lib/options/options_review_images_lib.py
@@ -1,4 +1,4 @@
-"""期权复盘截图:独立命名空间,不与合约 journal 混用."""
+"""期权复盘截图:独立命名空间,与合约同款四周期 5m/15m/1h/4h."""
from __future__ import annotations
import json
@@ -6,11 +6,11 @@ import os
import re
from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence
-OPTIONS_REVIEW_UPLOAD_TFS: tuple[str, ...] = ("chart", "entry", "exit", "other")
+OPTIONS_REVIEW_UPLOAD_TFS: tuple[str, ...] = ("5m", "15m", "1h", "4h")
OPTIONS_REVIEW_ALLOWED_EXT = frozenset({".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp"})
_DRAFT_ID_RE = re.compile(r"^[a-f0-9]{32}$")
_SLOT_FILE_RE = re.compile(
- r"^options_journal_([a-f0-9]{32})_(chart|entry|exit|other)\.(png|jpg|jpeg|webp|gif|bmp)$",
+ r"^options_journal_([a-f0-9]{32})_(5m|15m|1h|4h)\.(png|jpg|jpeg|webp|gif|bmp)$",
re.I,
)
diff --git a/lib/options/templates/options_review_panel.html b/lib/options/templates/options_review_panel.html
index c66384f..2753a20 100644
--- a/lib/options/templates/options_review_panel.html
+++ b/lib/options/templates/options_review_panel.html
@@ -1,28 +1,38 @@
-{# OKX 期权复盘(含对冲):独立页,不混合约 journal #}
+{# OKX 期权复盘:三类 Tab + 列表 + 下方多周期截图复盘表单 #}
{% if not options_enabled %}
期权未启用:请设置 OKX_OPTIONS_ENABLED=true 后重启.对冲计划仍可单独同步(若本地有已结束计划).
{% endif %}
+
+
期权复盘
-
同一列表三类:纯期权(交易所已平仓)、永期对冲 / 期期对冲(本地已结束计划,计划级一条).对冲盈亏用合计;归属对冲的期权腿默认不重复计入.
-
+
+
+
+
+
+
点列表行进入下方「复盘记录上传」;截图槽位与合约一致(5m / 15m / 1h / 4h).对冲盈亏用计划合计;归属对冲的期权腿默认不重复计入.
-
-
交易列表
+
+
期权交易记录
-
+
| 类型 |
@@ -67,25 +77,81 @@
持有 |
策略 |
状态 |
- 操作 |
- | 加载中… |
+ | 加载中… |
-
-
-
-
+
diff --git a/tests/test_options_review_lib.py b/tests/test_options_review_lib.py
index e2c1c4e..9d68a3b 100644
--- a/tests/test_options_review_lib.py
+++ b/tests/test_options_review_lib.py
@@ -230,14 +230,14 @@ class OptionsReviewTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
folder = options_review_upload_dir(tmp)
fname = build_options_review_slot_filename(
- "a" * 32, "chart", ".png", secure_filename_fn=lambda x: x
+ "a" * 32, "5m", ".png", secure_filename_fn=lambda x: x
)
self.assertTrue(fname.startswith("options_journal_"))
- self.assertTrue(is_valid_options_review_file(fname, "a" * 32, "chart"))
+ self.assertTrue(is_valid_options_review_file(fname, "a" * 32, "5m"))
item = save_options_review_slot_file(
_FakeFile("x.png"),
"a" * 32,
- "chart",
+ "5m",
folder,
secure_filename_fn=lambda x: x,
)