Fix option history to full closes only, 2-decimal USDC, restore hide-delete.
Filter OKX positions-history to type 2/3/6, format premium and bid recovery to 2 decimals, and allow locally hiding rows via delete button. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3727,6 +3727,11 @@ html[data-theme="light"] .options-estimate-row {
|
||||
.opt-history-table td:nth-child(6) {
|
||||
width: 20%;
|
||||
}
|
||||
.opt-history-table th:nth-child(7),
|
||||
.opt-history-table td:nth-child(7) {
|
||||
width: 8%;
|
||||
text-align: center;
|
||||
}
|
||||
.opt-hist-time {
|
||||
font-size: 0.64rem;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
|
||||
function fmtUsdc(v) {
|
||||
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
||||
return Number(v).toFixed(4).replace(/\.?0+$/, "") || "0";
|
||||
return Number(v).toFixed(2);
|
||||
}
|
||||
|
||||
function fmtClosePreview(preview, premiumPaid) {
|
||||
@@ -525,7 +525,7 @@
|
||||
const closePreview = p.close_preview || {};
|
||||
const closeSheets = p.avail_pos != null && Number(p.avail_pos) > 0 ? p.avail_pos : p.pos;
|
||||
const tickSz = p.tick_sz;
|
||||
const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmt(p.premium_paid, 4).replace(/\.?0+$/, "") : null);
|
||||
const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null);
|
||||
const avgTxt = fmtDisplay(p.avg_px_fmt, fmtOptionPx(p.avg_px, tickSz));
|
||||
const markTxt = fmtDisplay(p.mark_px_fmt, fmtOptionPx(p.mark_px, tickSz));
|
||||
return (
|
||||
@@ -899,6 +899,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteHistoryRow(key, status) {
|
||||
const warn = status === "open"
|
||||
? "该记录仍为持仓中,仅从列表隐藏,不影响交易所持仓.确认删除?"
|
||||
: "确认从列表隐藏该条历史记录?";
|
||||
if (!confirm(warn)) return;
|
||||
const r = await apiJson("/api/options/history/" + encodeURIComponent(key), { method: "DELETE" });
|
||||
if (!r.ok) {
|
||||
alert(r.msg || "删除失败");
|
||||
return;
|
||||
}
|
||||
refreshAllPositions();
|
||||
}
|
||||
|
||||
function optHistoryStatus(h) {
|
||||
if (h.status_label) return h.status_label;
|
||||
if (h.status === "open") return "持仓中";
|
||||
@@ -921,26 +934,33 @@
|
||||
tbody.innerHTML = "";
|
||||
const list = (d.ok && d.history) || [];
|
||||
if (!list.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="muted">暂无历史记录</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="muted">暂无历史记录</td></tr>';
|
||||
return;
|
||||
}
|
||||
list.forEach(function (h) {
|
||||
const tr = document.createElement("tr");
|
||||
const premTxt = fmtDisplay(h.premium_paid_fmt, h.premium_paid != null ? fmt(h.premium_paid, 2) : null);
|
||||
const premTxt = fmtDisplay(h.premium_paid_fmt, h.premium_paid != null ? fmtUsdc(h.premium_paid) : null);
|
||||
const isOpen = h.status === "open";
|
||||
const pnl = isOpen ? null : h.realized_pnl;
|
||||
const pnlTxt = pnl != null ? fmt(pnl, 2) : "—";
|
||||
const pnlCls = pnl > 0 ? "pos-pnl-profit" : pnl < 0 ? "pos-pnl-loss" : "";
|
||||
const timeTxt = (h.closed_at || h.created_at || "—").replace("T", " ").slice(0, 19);
|
||||
const histKey = h.history_key || "";
|
||||
tr.innerHTML =
|
||||
'<td class="opt-hist-inst"><code>' + (h.inst_id || "") + "</code></td>" +
|
||||
"<td>" + fmt(h.sheets, 0) + "</td>" +
|
||||
"<td>" + premTxt + "</td>" +
|
||||
"<td>" + optHistoryStatusHtml(h) + "</td>" +
|
||||
'<td class="' + pnlCls + '">' + pnlTxt + "</td>" +
|
||||
'<td class="opt-hist-time">' + timeTxt + "</td>";
|
||||
'<td class="opt-hist-time">' + timeTxt + "</td>" +
|
||||
'<td><button type="button" class="btn-secondary btn-sm opt-history-del" data-key="' + histKey + '" data-status="' + (h.status || "") + '">删除</button></td>';
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
tbody.querySelectorAll(".opt-history-del").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
deleteHistoryRow(btn.getAttribute("data-key"), btn.getAttribute("data-status"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAllPositions() {
|
||||
|
||||
@@ -117,10 +117,32 @@ def format_option_px(px: float, tick_sz: Any) -> str:
|
||||
|
||||
|
||||
def format_usdc_amount(v: float | None) -> str | None:
|
||||
"""USDC 金额展示(与交易所持仓/历史一致,最多 4 位小数)."""
|
||||
"""USDC 金额展示(权利金/回收等,固定 2 位小数)."""
|
||||
if v is None:
|
||||
return None
|
||||
return f"{float(v):.4f}".rstrip("0").rstrip(".") or "0"
|
||||
return f"{float(v):.2f}"
|
||||
|
||||
|
||||
def is_option_full_close_history(raw: dict[str, Any]) -> bool:
|
||||
"""仅保留 OKX 历史仓位中的「全部平仓/强平/ADL 全平」记录,排除部分平仓."""
|
||||
close_type = str(raw.get("type") or "").strip()
|
||||
return close_type in ("2", "3", "6")
|
||||
|
||||
|
||||
def option_history_row_key(
|
||||
*,
|
||||
source: str,
|
||||
inst_id: str = "",
|
||||
pos_id: str | None = None,
|
||||
close_ms: int | None = None,
|
||||
) -> str:
|
||||
inst_id = (inst_id or "").strip()
|
||||
pos_id = (pos_id or "").strip()
|
||||
if source == "live":
|
||||
return f"live:{inst_id}:{pos_id or close_ms or '0'}"
|
||||
if pos_id:
|
||||
return f"ex:{pos_id}"
|
||||
return f"ex:{inst_id}:{close_ms or 0}"
|
||||
|
||||
|
||||
def _ms_to_iso(ms: Any) -> str | None:
|
||||
@@ -815,6 +837,7 @@ def fetch_all_option_positions_history(
|
||||
if after is not None and str(oldest) == after:
|
||||
break
|
||||
after = str(oldest)
|
||||
out = [r for r in out if is_option_full_close_history(r)]
|
||||
out.sort(key=lambda r: int(_safe_float(r.get("uTime")) or 0), reverse=True)
|
||||
return out[:cap]
|
||||
|
||||
@@ -854,9 +877,17 @@ def format_option_history_row(
|
||||
status_label = "强平"
|
||||
else:
|
||||
status_label = "已平"
|
||||
pos_id = str(raw.get("posId") or "").strip() or None
|
||||
close_ms = int(utime) if utime is not None else None
|
||||
return {
|
||||
"source": "exchange",
|
||||
"pos_id": str(raw.get("posId") or "").strip() or None,
|
||||
"history_key": option_history_row_key(
|
||||
source="exchange",
|
||||
inst_id=inst_id,
|
||||
pos_id=pos_id,
|
||||
close_ms=close_ms,
|
||||
),
|
||||
"pos_id": pos_id,
|
||||
"inst_id": inst_id,
|
||||
"underlying": uly,
|
||||
"opt_type": opt_type,
|
||||
@@ -876,7 +907,7 @@ def format_option_history_row(
|
||||
"close_type": close_type,
|
||||
"created_at": _ms_to_iso(ctime),
|
||||
"closed_at": _ms_to_iso(utime),
|
||||
"close_ms": int(utime) if utime is not None else None,
|
||||
"close_ms": close_ms,
|
||||
"tick_sz": tick_sz,
|
||||
"raw": raw,
|
||||
}
|
||||
@@ -889,9 +920,17 @@ def format_live_option_history_row(
|
||||
) -> dict[str, Any]:
|
||||
"""将当前持仓格式化为历史列表中的「持仓中」行."""
|
||||
inst_id = str(row.get("inst_id") or "").strip()
|
||||
pos_id = str((row.get("raw") or {}).get("posId") or "").strip() or None
|
||||
close_ms = open_ms
|
||||
return {
|
||||
"source": "live",
|
||||
"pos_id": str((row.get("raw") or {}).get("posId") or "").strip() or None,
|
||||
"history_key": option_history_row_key(
|
||||
source="live",
|
||||
inst_id=inst_id,
|
||||
pos_id=pos_id,
|
||||
close_ms=close_ms,
|
||||
),
|
||||
"pos_id": pos_id,
|
||||
"inst_id": inst_id,
|
||||
"underlying": str(row.get("underlying") or inst_id.split("-")[0] or ""),
|
||||
"opt_type": row.get("opt_type"),
|
||||
|
||||
@@ -46,6 +46,14 @@ def init_options_tables(conn: sqlite3.Connection) -> None:
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS options_history_hidden (
|
||||
history_key TEXT PRIMARY KEY,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS options_transfer_log (
|
||||
|
||||
@@ -915,6 +915,17 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
hidden_keys: set[str] = set()
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
hidden_keys = {
|
||||
str(r["history_key"])
|
||||
for r in conn.execute("SELECT history_key FROM options_history_hidden").fetchall()
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
hist_raw = fetch_all_option_positions_history(ex, limit=200)
|
||||
for raw in hist_raw:
|
||||
inst_id = str(raw.get("instId") or "").strip()
|
||||
@@ -925,7 +936,11 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
closed = [x for x in items if x.get("status") != "open"]
|
||||
closed.sort(key=lambda x: int(x.get("close_ms") or 0), reverse=True)
|
||||
open_rows.sort(key=lambda x: int(x.get("close_ms") or 0), reverse=True)
|
||||
history = open_rows + closed
|
||||
history = [
|
||||
x
|
||||
for x in (open_rows + closed)
|
||||
if str(x.get("history_key") or "") not in hidden_keys
|
||||
]
|
||||
live_ids = {str(x.get("inst_id") or "") for x in open_rows}
|
||||
return jsonify({"ok": True, "history": history, "live_inst_ids": sorted(live_ids)})
|
||||
|
||||
@@ -939,13 +954,26 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
|
||||
return jsonify({"ok": True, **compute_options_stats(cfg["get_db"])})
|
||||
|
||||
@app.route("/api/options/history/<path:trade_id>", methods=["DELETE"])
|
||||
@app.route("/api/options/history/<path:history_key>", methods=["DELETE"])
|
||||
@lr
|
||||
def api_options_history_delete(trade_id: str):
|
||||
def api_options_history_delete(history_key: str):
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
return jsonify({"ok": False, "msg": "历史仓位来自交易所,不支持本地删除"})
|
||||
key = (history_key or "").strip()
|
||||
if not key:
|
||||
return jsonify({"ok": False, "msg": "缺少 history_key"})
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO options_history_hidden (history_key) VALUES (?)",
|
||||
(key,),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
|
||||
@@ -198,10 +198,11 @@
|
||||
<th>状态</th>
|
||||
<th>盈亏</th>
|
||||
<th>时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="opt-history-tbody">
|
||||
<tr><td colspan="6" class="muted">加载中…</td></tr>
|
||||
<tr><td colspan="7" class="muted">加载中…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -211,4 +212,4 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||
<script src="/static/options_panel.js?v=24"></script>
|
||||
<script src="/static/options_panel.js?v=25"></script>
|
||||
|
||||
Reference in New Issue
Block a user