翻倍出场监控中按钮改为取消;中控目标监控列显示倍数如1倍。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-12 10:37:31 +08:00
parent cd23ea74a6
commit 8605efa2ed
6 changed files with 121 additions and 18 deletions
+16 -6
View File
@@ -1500,6 +1500,13 @@
);
}
function formatProfitExitMultLabel(mult) {
const n = Number(mult);
if (!Number.isFinite(n) || n <= 0) return "1倍";
if (Math.abs(n - Math.round(n)) < 1e-9) return String(Math.round(n)) + "倍";
return fmt(n, 2) + "倍";
}
function renderProfitExitRow(p) {
const inst = p.inst_id || "";
if (p.hedge_plan_target) {
@@ -1511,10 +1518,9 @@
: 1;
const state = String(p.profit_exit_state || (enabled ? "active" : "idle"));
const req = p.profit_exit_required_recycle;
let statusTxt = enabled
? ("监控中 · " + fmt(mult, 2) + "")
: "未开启";
if (enabled && state === "closing") statusTxt = "平仓挂单中 · " + fmt(mult, 2) + "倍";
const multLabel = formatProfitExitMultLabel(mult);
let statusTxt = enabled ? ("监控中 · " + multLabel) : "未开启";
if (enabled && state === "closing") statusTxt = "平仓挂单中 · " + multLabel;
return (
'<div class="opt-target-row opt-profit-exit-pos-row" data-inst="' + inst + '">' +
'<span class="opt-target-row-label">翻倍</span>' +
@@ -1523,7 +1529,8 @@
'<input type="number" class="opt-pos-profit-exit-mult" data-inst="' + inst +
'" min="0.1" step="0.1" value="' + mult + '"' + (enabled ? "" : " disabled") + ">" +
'<button type="button" class="btn-secondary opt-profit-exit-save-btn" data-inst="' +
inst + '">应用</button>' +
inst + '" data-mode="' + (enabled ? "cancel" : "apply") + '">' +
(enabled ? "取消" : "应用") + "</button>" +
'<span class="opt-target-armed">' + statusTxt + "</span>" +
'<span class="muted opt-target-row-hint">' +
(enabled
@@ -1853,7 +1860,10 @@
const row = card ? card.querySelector(".opt-profit-exit-pos-row") : null;
const enabledEl = row ? row.querySelector(".opt-pos-profit-exit-enabled") : null;
const multEl = row ? row.querySelector(".opt-pos-profit-exit-mult") : null;
const enabled = !!(enabledEl && enabledEl.checked);
const mode = btn && btn.getAttribute("data-mode");
let enabled = !!(enabledEl && enabledEl.checked);
if (mode === "cancel") enabled = false;
if (mode === "apply") enabled = true;
let mult = 1;
if (enabled) {
mult = parseFloat(multEl ? multEl.value : "1");
+51 -3
View File
@@ -121,20 +121,40 @@ def _resolve_options_source(conn, inst_id: str) -> tuple[str, str, int | None]:
return default
def _format_profit_exit_mult(mult: Any) -> str:
try:
n = float(mult)
except (TypeError, ValueError):
return "1倍"
if n <= 0:
return "1倍"
if abs(n - round(n)) < 1e-9:
return f"{int(round(n))}"
return f"{n:g}"
def _format_options_target(p: dict[str, Any]) -> str:
hedge = p.get("hedge_plan_target") if isinstance(p.get("hedge_plan_target"), dict) else None
opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
if hedge:
rr = _safe_float(hedge.get("profit_rr"))
pid = hedge.get("plan_id")
if rr is not None and rr > 0:
return f"对冲#{pid} 盈亏比 {rr:g}" if pid is not None else f"盈亏比 {rr:g}"
ot = str(hedge.get("opt_type") or opt_type).upper()
side = "Put ≤" if ot == "P" else "Call ≥"
tgt = _safe_float(hedge.get("target_index"))
pid = hedge.get("plan_id")
if tgt is not None:
return f"对冲#{pid} {side} {tgt:g}" if pid is not None else f"{side} {tgt:g}"
parts: list[str] = []
tgt = _safe_float(p.get("target_index"))
if tgt is not None and tgt > 0:
side = "Put ≤" if opt_type == "P" else "Call ≥"
return f"{side} {tgt:g}"
parts.append(f"{side} {tgt:g}")
if p.get("profit_exit_enabled"):
parts.append(_format_profit_exit_mult(p.get("profit_exit_mult")))
if parts:
return " · ".join(parts)
return ""
@@ -350,11 +370,39 @@ def collect_options_items(
raw = fetch_options_positions() or []
except Exception:
return []
pe_map: dict[str, dict[str, Any]] = {}
tgt_map: dict[str, dict[str, Any]] = {}
hedge_map: dict[str, dict[str, Any]] = {}
if conn is not None:
try:
from lib.options.options_profit_exit_lib import profit_exit_by_inst
from lib.options.options_target_lib import targets_by_inst
from lib.hedge_plan.hedge_plan_db import active_options_targets_by_inst
pe_map = profit_exit_by_inst(conn)
tgt_map = targets_by_inst(conn)
hedge_map = active_options_targets_by_inst(conn)
except Exception:
pe_map, tgt_map, hedge_map = {}, {}, {}
out: list[dict[str, Any]] = []
for p in raw:
if not isinstance(p, dict):
continue
out.append(_format_options_item(p, conn=conn))
row = dict(p)
inst = str(row.get("inst_id") or row.get("instId") or "").strip()
mon = tgt_map.get(inst)
if mon:
row["target_index"] = mon.get("target_index")
pe = pe_map.get(inst)
if pe:
row["profit_exit_enabled"] = pe.get("profit_exit_enabled")
row["profit_exit_mult"] = pe.get("profit_exit_mult")
hedge = hedge_map.get(inst)
if hedge:
row["hedge_plan_target"] = hedge
if not mon:
row["target_index"] = hedge.get("target_index")
out.append(_format_options_item(row, conn=conn))
return out
+18
View File
@@ -28,18 +28,36 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
conn = cfg["get_db"]()
try:
from lib.hedge_plan.hedge_plan_db import active_options_targets_by_inst
from lib.options.options_profit_exit_lib import profit_exit_by_inst
from lib.options.options_target_lib import list_active_targets, list_closing_targets, targets_by_inst
target_monitors = list_active_targets(conn) + list_closing_targets(conn)
tgt_map = targets_by_inst(conn)
hedge_target_map = active_options_targets_by_inst(conn)
profit_exit_map = profit_exit_by_inst(conn)
target_monitors.extend(hedge_target_map.values())
for pe in profit_exit_map.values():
if pe.get("profit_exit_enabled"):
target_monitors.append(
{
"inst_id": pe.get("inst_id"),
"exit_mode": "profit_exit",
"profit_exit_mult": pe.get("profit_exit_mult"),
"profit_exit_enabled": True,
}
)
for p in positions:
mon = tgt_map.get(str(p.get("inst_id") or ""))
if mon:
p["target_index"] = mon.get("target_index")
p["target_monitor_id"] = mon.get("id")
p["target_monitor"] = mon
pe = profit_exit_map.get(str(p.get("inst_id") or ""))
if pe:
p["profit_exit_enabled"] = pe.get("profit_exit_enabled")
p["profit_exit_mult"] = pe.get("profit_exit_mult")
p["profit_exit_state"] = pe.get("profit_exit_state")
p["profit_exit_required_recycle"] = pe.get("required_recycle")
hedge_target = hedge_target_map.get(str(p.get("inst_id") or ""))
if hedge_target:
p["hedge_plan_target"] = hedge_target
+1 -1
View File
@@ -350,4 +350,4 @@
</div>
</div>
<script src="/static/options_expiry_countdown.js?v=1"></script>
<script src="/static/options_panel.js?v=60"></script>
<script src="/static/options_panel.js?v=61"></script>
+34 -7
View File
@@ -3928,9 +3928,15 @@
);
}
function renderOptionsTargetCell(target) {
if (!target) return "<td>—</td>";
if (target.managed_by === "hedge_plan") {
function formatProfitExitMultLabel(mult) {
const n = Number(mult);
if (!Number.isFinite(n) || n <= 0) return "1倍";
if (Math.abs(n - Math.round(n)) < 1e-9) return String(Math.round(n)) + "倍";
return fmt(n, 2) + "倍";
}
function renderOptionsTargetCell(target, pos) {
if (target && target.managed_by === "hedge_plan") {
const rr = target.profit_rr != null ? Number(target.profit_rr) : null;
if (rr != null && rr > 0) {
return `<td class="hub-opt-target-cell is-on is-hedge" title="由对冲计划监控">对冲#${esc(target.plan_id)} 盈亏比 ${esc(fmt(rr, 2))}</td>`;
@@ -3939,9 +3945,30 @@
const px = target.target_index != null ? fmt(target.target_index, 1) : "—";
return `<td class="hub-opt-target-cell is-on is-hedge" title="由对冲计划监控">对冲#${esc(target.plan_id)} ${esc(side)} ${esc(px)}</td>`;
}
const side = String(target.opt_type || "").toUpperCase() === "P" ? "Put≤" : "Call≥";
const px = target.target_index != null ? fmt(target.target_index, 1) : "—";
return `<td class="hub-opt-target-cell is-on" title="目标监控">${esc(side)} ${esc(px)}</td>`;
const parts = [];
const hasIndex =
target &&
target.exit_mode !== "profit_exit" &&
target.target_index != null &&
Number(target.target_index) > 0;
if (hasIndex) {
const side = String(target.opt_type || (pos && pos.opt_type) || "").toUpperCase() === "P" ? "Put≤" : "Call≥";
parts.push(side + " " + fmt(target.target_index, 1));
}
const peOn =
!!(pos && pos.profit_exit_enabled) ||
!!(target && (target.exit_mode === "profit_exit" || target.profit_exit_enabled));
if (peOn) {
const mult =
pos && pos.profit_exit_mult != null
? pos.profit_exit_mult
: target && target.profit_exit_mult != null
? target.profit_exit_mult
: 1;
parts.push(formatProfitExitMultLabel(mult));
}
if (!parts.length) return "<td>—</td>";
return `<td class="hub-opt-target-cell is-on" title="目标监控">${esc(parts.join(" · "))}</td>`;
}
function renderOptionsPositionsTable(pos, targets) {
@@ -3970,7 +3997,7 @@
<td>${esc(optType)}</td>
<td>${esc(p.pos)}</td>
<td>${optionsExpiryCdHtml(p.exp_time_ms != null ? p.exp_time_ms : p.exp_time)}</td>
${renderOptionsTargetCell(target)}`;
${renderOptionsTargetCell(target, p)}`;
if (showPnl) {
html += `<td class="${pnlCls(net)}">${net == null ? "—" : fmt(net, 2)}</td>
<td class="${pnlCls(net)}">${roi == null ? "—" : esc(Number(roi).toFixed(2)) + "%"}</td>`;
+1 -1
View File
@@ -1767,6 +1767,6 @@
<script src="/assets/options_expiry_countdown.js?v=1"></script>
<script src="/assets/options_position_cards.js?v=4"></script>
<script src="/assets/backup.js?v=1"></script>
<script src="/assets/app.js?v=20260807-opt-float"></script>
<script src="/assets/app.js?v=20260812-profit-exit"></script>
</body>
</html>