diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css
index be0ebf6..d5ed985 100644
--- a/lib/common/static/instance_theme.css
+++ b/lib/common/static/instance_theme.css
@@ -2597,6 +2597,11 @@ html[data-theme="light"] .settings-export-link {
font-size: 1.1rem;
font-weight: 600;
}
+.options-history-table-wrap .opt-history-del {
+ font-size: 0.72rem;
+ padding: 2px 8px;
+ min-height: 24px;
+}
.options-pos-history-card {
flex: 1;
min-height: 0;
diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js
index 4ae1b81..ce781cf 100644
--- a/lib/common/static/options_panel.js
+++ b/lib/common/static/options_panel.js
@@ -383,10 +383,12 @@
const winEl = document.getElementById("opt-stats-winrate");
const plrEl = document.getElementById("opt-stats-plr");
const closedEl = document.getElementById("opt-stats-closed");
+ const profitEl = document.getElementById("opt-stats-profit");
+ const lossEl = document.getElementById("opt-stats-loss");
if (!d.ok) {
- if (winEl) winEl.textContent = "—";
- if (plrEl) plrEl.textContent = "—";
- if (closedEl) closedEl.textContent = "—";
+ [winEl, plrEl, closedEl, profitEl, lossEl].forEach(function (el) {
+ if (el) el.textContent = "—";
+ });
return;
}
if (winEl) winEl.textContent = d.total_closed ? d.win_rate + "%" : "0%";
@@ -394,6 +396,27 @@
plrEl.textContent = d.profit_loss_ratio != null ? String(d.profit_loss_ratio) : "—";
}
if (closedEl) closedEl.textContent = String(d.total_closed || 0);
+ if (profitEl) {
+ profitEl.textContent = d.total_profit != null && d.total_profit > 0
+ ? fmt(d.total_profit, 4) + " USDC" : (d.total_closed ? "0 USDC" : "—");
+ }
+ if (lossEl) {
+ lossEl.textContent = d.total_loss != null && d.total_loss > 0
+ ? fmt(d.total_loss, 4) + " USDC" : (d.total_closed ? "0 USDC" : "—");
+ }
+ }
+
+ async function deleteHistoryRow(id, status) {
+ const warn = status === "open"
+ ? "该记录仍为持仓中,仅删除本地记录,不影响交易所持仓。确认删除?"
+ : "确认删除该条期权历史记录?";
+ if (!confirm(warn)) return;
+ const r = await apiJson("/api/options/history/" + encodeURIComponent(id), { method: "DELETE" });
+ if (!r.ok) {
+ alert(r.msg || "删除失败");
+ return;
+ }
+ refreshAllPositions();
}
async function refreshHistory() {
@@ -402,7 +425,7 @@
tbody.innerHTML = "";
const list = (d.ok && d.history) || [];
if (!list.length) {
- tbody.innerHTML = '
| 暂无历史记录 |
';
+ tbody.innerHTML = '| 暂无历史记录 |
';
return;
}
list.forEach(function (h) {
@@ -410,15 +433,22 @@
const prem = h.status === "closed" ? h.premium_received : h.premium_paid;
const pnl = h.realized_pnl;
const pnlTxt = pnl != null ? fmt(pnl, 4) : "—";
+ const pnlCls = pnl > 0 ? "pos-pnl-profit" : pnl < 0 ? "pos-pnl-loss" : "";
tr.innerHTML =
"" + (h.inst_id || "") + " | " +
"" + fmt(h.sheets, 0) + " | " +
"" + fmt(prem, 4) + " | " +
"" + (h.status === "closed" ? "已平" : "持仓中") + " | " +
- "" + pnlTxt + " | " +
- "" + (h.closed_at || h.created_at || "—") + " | ";
+ '' + pnlTxt + " | " +
+ "" + (h.closed_at || h.created_at || "—") + " | " +
+ ' | ';
tbody.appendChild(tr);
});
+ tbody.querySelectorAll(".opt-history-del").forEach(function (btn) {
+ btn.addEventListener("click", function () {
+ deleteHistoryRow(btn.getAttribute("data-id"), btn.getAttribute("data-status"));
+ });
+ });
}
function refreshAllPositions() {
diff --git a/lib/options/options_register.py b/lib/options/options_register.py
index b3408d0..3a658a2 100644
--- a/lib/options/options_register.py
+++ b/lib/options/options_register.py
@@ -573,15 +573,40 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
win_rate = round(len(wins) / total_closed * 100, 2) if total_closed else 0
avg_win = sum(wins) / len(wins) if wins else None
avg_loss = sum(losses) / len(losses) if losses else None
+ total_profit = round(sum(wins), 4) if wins else 0.0
+ total_loss = round(abs(sum(losses)), 4) if losses else 0.0
return jsonify(
{
"ok": True,
"total_closed": total_closed,
"win_rate": win_rate,
"profit_loss_ratio": profit_loss_ratio_from_averages(avg_win, avg_loss),
+ "total_profit": total_profit,
+ "total_loss": total_loss,
}
)
+ @app.route("/api/options/history/", methods=["DELETE"])
+ @lr
+ def api_options_history_delete(trade_id: int):
+ ex, err = _require_options_ex(cfg)
+ if ex is None:
+ return jsonify({"ok": False, "msg": err})
+ conn = cfg["get_db"]()
+ try:
+ init_options_tables(conn)
+ row = conn.execute(
+ "SELECT id, status FROM options_trades WHERE id = ?",
+ (trade_id,),
+ ).fetchone()
+ if not row:
+ return jsonify({"ok": False, "msg": "记录不存在"})
+ conn.execute("DELETE FROM options_trades WHERE id = ?", (trade_id,))
+ conn.commit()
+ finally:
+ conn.close()
+ return jsonify({"ok": True})
+
def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
if app.extensions.get("options_monitor_started"):
diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html
index b5309f5..6370848 100644
--- a/lib/options/templates/options_panel.html
+++ b/lib/options/templates/options_panel.html
@@ -86,6 +86,14 @@
已平笔数
—
+
+ 盈利
+ —
+
+
+ 亏损
+ —
+
@@ -100,10 +108,11 @@
状态 |
盈亏 |
时间 |
+ 操作 |
- | 加载中… |
+ | 加载中… |
@@ -112,4 +121,4 @@
-
+