翻倍出场监控中按钮改为取消;中控目标监控列显示倍数如1倍。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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) {
|
function renderProfitExitRow(p) {
|
||||||
const inst = p.inst_id || "";
|
const inst = p.inst_id || "";
|
||||||
if (p.hedge_plan_target) {
|
if (p.hedge_plan_target) {
|
||||||
@@ -1511,10 +1518,9 @@
|
|||||||
: 1;
|
: 1;
|
||||||
const state = String(p.profit_exit_state || (enabled ? "active" : "idle"));
|
const state = String(p.profit_exit_state || (enabled ? "active" : "idle"));
|
||||||
const req = p.profit_exit_required_recycle;
|
const req = p.profit_exit_required_recycle;
|
||||||
let statusTxt = enabled
|
const multLabel = formatProfitExitMultLabel(mult);
|
||||||
? ("监控中 · " + fmt(mult, 2) + "倍")
|
let statusTxt = enabled ? ("监控中 · " + multLabel) : "未开启";
|
||||||
: "未开启";
|
if (enabled && state === "closing") statusTxt = "平仓挂单中 · " + multLabel;
|
||||||
if (enabled && state === "closing") statusTxt = "平仓挂单中 · " + fmt(mult, 2) + "倍";
|
|
||||||
return (
|
return (
|
||||||
'<div class="opt-target-row opt-profit-exit-pos-row" data-inst="' + inst + '">' +
|
'<div class="opt-target-row opt-profit-exit-pos-row" data-inst="' + inst + '">' +
|
||||||
'<span class="opt-target-row-label">翻倍</span>' +
|
'<span class="opt-target-row-label">翻倍</span>' +
|
||||||
@@ -1523,7 +1529,8 @@
|
|||||||
'<input type="number" class="opt-pos-profit-exit-mult" data-inst="' + inst +
|
'<input type="number" class="opt-pos-profit-exit-mult" data-inst="' + inst +
|
||||||
'" min="0.1" step="0.1" value="' + mult + '"' + (enabled ? "" : " disabled") + ">" +
|
'" min="0.1" step="0.1" value="' + mult + '"' + (enabled ? "" : " disabled") + ">" +
|
||||||
'<button type="button" class="btn-secondary opt-profit-exit-save-btn" data-inst="' +
|
'<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="opt-target-armed">' + statusTxt + "</span>" +
|
||||||
'<span class="muted opt-target-row-hint">' +
|
'<span class="muted opt-target-row-hint">' +
|
||||||
(enabled
|
(enabled
|
||||||
@@ -1853,7 +1860,10 @@
|
|||||||
const row = card ? card.querySelector(".opt-profit-exit-pos-row") : null;
|
const row = card ? card.querySelector(".opt-profit-exit-pos-row") : null;
|
||||||
const enabledEl = row ? row.querySelector(".opt-pos-profit-exit-enabled") : null;
|
const enabledEl = row ? row.querySelector(".opt-pos-profit-exit-enabled") : null;
|
||||||
const multEl = row ? row.querySelector(".opt-pos-profit-exit-mult") : 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;
|
let mult = 1;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
mult = parseFloat(multEl ? multEl.value : "1");
|
mult = parseFloat(multEl ? multEl.value : "1");
|
||||||
|
|||||||
@@ -121,20 +121,40 @@ def _resolve_options_source(conn, inst_id: str) -> tuple[str, str, int | None]:
|
|||||||
return default
|
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:
|
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
|
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()
|
opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
|
||||||
if hedge:
|
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()
|
ot = str(hedge.get("opt_type") or opt_type).upper()
|
||||||
side = "Put ≤" if ot == "P" else "Call ≥"
|
side = "Put ≤" if ot == "P" else "Call ≥"
|
||||||
tgt = _safe_float(hedge.get("target_index"))
|
tgt = _safe_float(hedge.get("target_index"))
|
||||||
pid = hedge.get("plan_id")
|
|
||||||
if tgt is not None:
|
if tgt is not None:
|
||||||
return f"对冲#{pid} {side} {tgt:g}" if pid is not None else f"{side} {tgt:g}"
|
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"))
|
tgt = _safe_float(p.get("target_index"))
|
||||||
if tgt is not None and tgt > 0:
|
if tgt is not None and tgt > 0:
|
||||||
side = "Put ≤" if opt_type == "P" else "Call ≥"
|
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 "—"
|
return "—"
|
||||||
|
|
||||||
|
|
||||||
@@ -350,11 +370,39 @@ def collect_options_items(
|
|||||||
raw = fetch_options_positions() or []
|
raw = fetch_options_positions() or []
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
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]] = []
|
out: list[dict[str, Any]] = []
|
||||||
for p in raw:
|
for p in raw:
|
||||||
if not isinstance(p, dict):
|
if not isinstance(p, dict):
|
||||||
continue
|
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
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,18 +28,36 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
|||||||
conn = cfg["get_db"]()
|
conn = cfg["get_db"]()
|
||||||
try:
|
try:
|
||||||
from lib.hedge_plan.hedge_plan_db import active_options_targets_by_inst
|
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
|
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)
|
target_monitors = list_active_targets(conn) + list_closing_targets(conn)
|
||||||
tgt_map = targets_by_inst(conn)
|
tgt_map = targets_by_inst(conn)
|
||||||
hedge_target_map = active_options_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())
|
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:
|
for p in positions:
|
||||||
mon = tgt_map.get(str(p.get("inst_id") or ""))
|
mon = tgt_map.get(str(p.get("inst_id") or ""))
|
||||||
if mon:
|
if mon:
|
||||||
p["target_index"] = mon.get("target_index")
|
p["target_index"] = mon.get("target_index")
|
||||||
p["target_monitor_id"] = mon.get("id")
|
p["target_monitor_id"] = mon.get("id")
|
||||||
p["target_monitor"] = mon
|
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 ""))
|
hedge_target = hedge_target_map.get(str(p.get("inst_id") or ""))
|
||||||
if hedge_target:
|
if hedge_target:
|
||||||
p["hedge_plan_target"] = hedge_target
|
p["hedge_plan_target"] = hedge_target
|
||||||
|
|||||||
@@ -350,4 +350,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
<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>
|
||||||
|
|||||||
@@ -3928,9 +3928,15 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOptionsTargetCell(target) {
|
function formatProfitExitMultLabel(mult) {
|
||||||
if (!target) return "<td>—</td>";
|
const n = Number(mult);
|
||||||
if (target.managed_by === "hedge_plan") {
|
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;
|
const rr = target.profit_rr != null ? Number(target.profit_rr) : null;
|
||||||
if (rr != null && rr > 0) {
|
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>`;
|
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) : "—";
|
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>`;
|
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 parts = [];
|
||||||
const px = target.target_index != null ? fmt(target.target_index, 1) : "—";
|
const hasIndex =
|
||||||
return `<td class="hub-opt-target-cell is-on" title="目标监控">${esc(side)} ${esc(px)}</td>`;
|
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) {
|
function renderOptionsPositionsTable(pos, targets) {
|
||||||
@@ -3970,7 +3997,7 @@
|
|||||||
<td>${esc(optType)}</td>
|
<td>${esc(optType)}</td>
|
||||||
<td>${esc(p.pos)}</td>
|
<td>${esc(p.pos)}</td>
|
||||||
<td>${optionsExpiryCdHtml(p.exp_time_ms != null ? p.exp_time_ms : p.exp_time)}</td>
|
<td>${optionsExpiryCdHtml(p.exp_time_ms != null ? p.exp_time_ms : p.exp_time)}</td>
|
||||||
${renderOptionsTargetCell(target)}`;
|
${renderOptionsTargetCell(target, p)}`;
|
||||||
if (showPnl) {
|
if (showPnl) {
|
||||||
html += `<td class="${pnlCls(net)}">${net == null ? "—" : fmt(net, 2)}</td>
|
html += `<td class="${pnlCls(net)}">${net == null ? "—" : fmt(net, 2)}</td>
|
||||||
<td class="${pnlCls(net)}">${roi == null ? "—" : esc(Number(roi).toFixed(2)) + "%"}</td>`;
|
<td class="${pnlCls(net)}">${roi == null ? "—" : esc(Number(roi).toFixed(2)) + "%"}</td>`;
|
||||||
|
|||||||
@@ -1767,6 +1767,6 @@
|
|||||||
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/assets/options_position_cards.js?v=4"></script>
|
<script src="/assets/options_position_cards.js?v=4"></script>
|
||||||
<script src="/assets/backup.js?v=1"></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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user