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 @@ - +