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:
@@ -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()
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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, """) +
|
||||
'" 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(" · ");
|
||||
|
||||
Reference in New Issue
Block a user