feat: archive entry type from review, prune stale trades on sync, manual delete

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 12:39:27 +08:00
parent e68e29629e
commit 46963a4498
7 changed files with 260 additions and 19 deletions
+14
View File
@@ -1835,6 +1835,20 @@ def api_archive_trade_overlay(
return {"ok": True, "overlay": out}
@app.delete("/api/archive/trade/{exchange_key}/{trade_id}")
def api_archive_trade_delete(exchange_key: str, trade_id: int):
from hub_symbol_archive_lib import delete_trade_from_archive
ex_k = (exchange_key or "").strip().lower()
if not ex_k:
raise HTTPException(status_code=400, detail="缺少 exchange_key")
init_archive_db()
removed = delete_trade_from_archive(ex_k, int(trade_id))
if not removed:
raise HTTPException(status_code=404, detail="档案中无该笔交易")
return {"ok": True, "exchange_key": ex_k, "trade_id": int(trade_id)}
@app.post("/api/archive/sync")
async def api_archive_sync():
body = await _run_archive_sync_once()
+12
View File
@@ -4063,6 +4063,18 @@ body.hub-page-ai #page-ai {
.archive-trades-table td.neg {
color: #ef4444;
}
.archive-del-btn {
padding: 3px 8px;
font-size: 0.72rem;
border-radius: 6px;
border: 1px solid rgba(239, 68, 68, 0.35);
background: rgba(239, 68, 68, 0.08);
color: #f87171;
cursor: pointer;
}
.archive-del-btn:hover {
background: rgba(239, 68, 68, 0.16);
}
.archive-tag-select,
.archive-note-input {
width: 100%;
+66 -9
View File
@@ -109,14 +109,25 @@
return mins + "分钟";
}
const ENTRY_TYPE_LABELS = {
trend_pullback: "趋势回调",
roll: "顺势加仓",
trend: "趋势回调",
};
function fmtEntryType(tr) {
if (!tr) return "—";
return (
tr.entry_type ||
tr.entry_reason ||
tr.monitor_type ||
"—"
);
const raw = String(
tr.entry_type || tr.entry_reason || tr.reviewed_entry_reason || ""
).trim();
if (raw) {
return ENTRY_TYPE_LABELS[raw] || raw;
}
const mt = String(tr.monitor_type || "").trim();
if (mt && mt !== "下单监控") {
return ENTRY_TYPE_LABELS[mt] || mt;
}
return "—";
}
function reviewMark(tr) {
@@ -551,7 +562,7 @@
elTrades.innerHTML =
'<table class="archive-trades-table"><thead><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><th>操作</th>" +
"</tr></thead><tbody>" +
trades
.map(function (t) {
@@ -618,15 +629,30 @@
'" value="' +
String(t.note || "").replace(/"/g, "&quot;") +
'" placeholder="备注" /></td>' +
'<td><button type="button" class="archive-del-btn" data-id="' +
tid +
'" title="从档案移除(不影响实例复盘库)">删除</button></td>' +
"</tr>"
);
})
.join("") +
"</tbody></table>";
elTrades.querySelectorAll(".archive-del-btn").forEach(function (btn) {
btn.addEventListener("click", function (ev) {
ev.stopPropagation();
void deleteTrade(btn.getAttribute("data-id"));
});
});
elTrades.querySelectorAll(".archive-trade-row").forEach(function (row) {
row.addEventListener("click", function (ev) {
if (ev.target.closest("select") || ev.target.closest("input")) return;
if (
ev.target.closest("select") ||
ev.target.closest("input") ||
ev.target.closest(".archive-del-btn")
) {
return;
}
selectedTradeId = row.getAttribute("data-id");
renderTrades();
applyChartMarkers();
@@ -650,6 +676,32 @@
});
}
async function deleteTrade(tradeId) {
if (!selected || tradeId == null) return;
if (!window.confirm("从币种档案移除该笔交易?(不影响交易所实例里的复盘记录)")) return;
const r = await apiFetch(
"/api/archive/trade/" + selected.exchange_key + "/" + tradeId,
{ method: "DELETE" }
);
if (!r.ok) {
const j = await r.json().catch(function () {
return {};
});
setStatus(j.detail || j.msg || "删除失败");
return;
}
if (String(selectedTradeId) === String(tradeId)) {
selectedTradeId = null;
}
trades = trades.filter(function (t) {
return String(t.trade_id || t.id) !== String(tradeId);
});
renderTrades();
applyChartMarkers();
await loadList();
setStatus("已移除 1 笔档案记录");
}
async function saveOverlay(tradeId, tag, note) {
if (!selected) return;
const body = { behavior_tag: tag || "", note: note != null ? note : undefined };
@@ -742,7 +794,12 @@
if (row.ok === false) {
parts.push(label + " 失败: " + (row.msg || "未知错误"));
} else {
parts.push(label + " " + (row.trade_count != null ? row.trade_count : row.trades || 0) + " 笔");
let line =
label + " " + (row.trade_count != null ? row.trade_count : row.trades || 0) + " 笔";
if (row.trades_removed > 0) {
line += " 清" + row.trades_removed;
}
parts.push(line);
}
});
return parts.join(" · ");