Enable hedge-plan live opens with path validation, DB, and monitor.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -135,7 +135,12 @@
|
||||
if (gates.reasons && gates.reasons.length) parts.push(gates.reasons.join("; "));
|
||||
el.textContent = parts.join(" · ");
|
||||
const start = $("hp-start-btn");
|
||||
if (start) start.disabled = !gates.can_start;
|
||||
const startOo = $("hp-start-btn-oo");
|
||||
if ((gates.plan_type || state.mode) === "options_options") {
|
||||
if (startOo) startOo.disabled = !gates.can_start;
|
||||
} else {
|
||||
if (start) start.disabled = !gates.can_start;
|
||||
}
|
||||
}
|
||||
|
||||
function setOptionsBalance(chain) {
|
||||
@@ -702,14 +707,13 @@
|
||||
syncTabUI();
|
||||
if (state.tab === "perp_options" || state.tab === "options_options") {
|
||||
void loadGates();
|
||||
} else if (state.tab === "history") {
|
||||
void loadHistory();
|
||||
} else if (state.tab === "stats") {
|
||||
void loadStats();
|
||||
} else {
|
||||
const el = $("hp-gate-line");
|
||||
if (el) {
|
||||
el.textContent =
|
||||
state.tab === "history"
|
||||
? "历史记录:独立对冲表,与普通交易记录分离"
|
||||
: "统计:止盈=盈利−保费;止损=期权盈利−永续亏损";
|
||||
}
|
||||
if (el) el.textContent = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -768,6 +772,141 @@
|
||||
state.mode = "options_options";
|
||||
void runPreview();
|
||||
});
|
||||
if ($("hp-start-btn"))
|
||||
$("hp-start-btn").addEventListener("click", function () {
|
||||
void startPlan("perp_options");
|
||||
});
|
||||
if ($("hp-start-btn-oo"))
|
||||
$("hp-start-btn-oo").addEventListener("click", function () {
|
||||
void startPlan("options_options");
|
||||
});
|
||||
}
|
||||
|
||||
async function loadHistory() {
|
||||
const tbody = $("hp-history-tbody");
|
||||
if (!tbody) return;
|
||||
try {
|
||||
const d = await apiJson("/api/hedge-plan/history");
|
||||
const rows = d.plans || [];
|
||||
if (!rows.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="muted">暂无已结束计划</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = "";
|
||||
rows.forEach(function (p) {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML =
|
||||
"<td>" +
|
||||
p.id +
|
||||
"</td><td>" +
|
||||
(p.plan_type === "perp_options" ? "永期" : "期期") +
|
||||
"</td><td>" +
|
||||
(p.underlying || "") +
|
||||
"</td><td>" +
|
||||
(p.status || "") +
|
||||
"</td><td>" +
|
||||
fmt(p.realized_pnl_total) +
|
||||
"</td><td>" +
|
||||
(p.close_reason || "—") +
|
||||
"</td><td>" +
|
||||
(p.opened_at || "—") +
|
||||
"</td><td>" +
|
||||
(p.closed_at || "—") +
|
||||
"</td>";
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} catch (e) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="err">' + (e.message || e) + "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
const box = $("hp-stats-box");
|
||||
if (!box) return;
|
||||
try {
|
||||
const d = await apiJson("/api/hedge-plan/stats");
|
||||
const parts = [
|
||||
"活跃计划 <strong>" + (d.active || 0) + "</strong>",
|
||||
"已结笔数 <strong>" + (d.closed_count || 0) + "</strong>",
|
||||
"已结合计 <strong>" + fmt(d.closed_pnl_total) + "</strong> ≈U",
|
||||
];
|
||||
const by = d.by_reason || [];
|
||||
if (by.length) {
|
||||
parts.push(
|
||||
"<br/>按原因: " +
|
||||
by
|
||||
.map(function (r) {
|
||||
return (
|
||||
(r.plan_type || "") +
|
||||
"/" +
|
||||
(r.close_reason || "") +
|
||||
" ×" +
|
||||
r.n +
|
||||
" pnl=" +
|
||||
fmt(r.pnl)
|
||||
);
|
||||
})
|
||||
.join(" · ")
|
||||
);
|
||||
}
|
||||
box.innerHTML = parts.join(" · ");
|
||||
} catch (e) {
|
||||
box.textContent = e.message || String(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function startPlan(planType) {
|
||||
const isOo = planType === "options_options";
|
||||
try {
|
||||
let body;
|
||||
if (isOo) {
|
||||
if (!state.legA || !state.legB) throw new Error("请选用两条期权腿");
|
||||
const target = Number(($("hp-target") && $("hp-target").value) || 0);
|
||||
if (!target) throw new Error("请填写目标价");
|
||||
body = {
|
||||
plan_type: "options_options",
|
||||
underlying: state.underlying,
|
||||
target_price: target,
|
||||
leg_a: legPayload(state.legA, ooSheets("hp-oo-sheets-a")),
|
||||
leg_b: legPayload(state.legB, ooSheets("hp-oo-sheets-b")),
|
||||
};
|
||||
} else {
|
||||
if (!state.selected) throw new Error("请选用期权腿");
|
||||
const entry = Number(($("hp-entry") && $("hp-entry").value) || 0);
|
||||
const tp = Number(($("hp-tp") && $("hp-tp").value) || 0);
|
||||
const sl = Number(($("hp-sl") && $("hp-sl").value) || 0);
|
||||
const contracts = Number(($("hp-contracts") && $("hp-contracts").value) || 0);
|
||||
const sheets = Number(($("hp-sheets") && $("hp-sheets").value) || 1);
|
||||
if (!entry || !tp || !sl || !contracts) throw new Error("请完整填写开仓/止盈/止损/张数");
|
||||
body = {
|
||||
plan_type: "perp_options",
|
||||
underlying: state.underlying,
|
||||
direction: ($("hp-direction") && $("hp-direction").value) || "long",
|
||||
entry: entry,
|
||||
tp: tp,
|
||||
sl: sl,
|
||||
contracts: contracts,
|
||||
sheets: sheets,
|
||||
opt_inst_id: state.selected.inst_id,
|
||||
opt_type: state.selected.opt_type,
|
||||
strike: state.selected.strike,
|
||||
exchange_symbol: (state.market && state.market.exchange_symbol) || "",
|
||||
leverage: 10,
|
||||
margin: state.market && state.market.full_margin_sizing && state.market.full_margin_sizing.margin_capital,
|
||||
};
|
||||
}
|
||||
if (!window.confirm("确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)")) return;
|
||||
const d = await apiJson("/api/hedge-plan/start", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
setGateLine(d.gates);
|
||||
alert("计划已启动 #" + (d.plan_id || "") + (d.dry_run ? " (dry_run)" : ""));
|
||||
void loadGates();
|
||||
} catch (e) {
|
||||
alert(e.message || String(e));
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
|
||||
Reference in New Issue
Block a user