Allow risk-based 永期 exits via premium multiple as well as base×k.
Settings UI picks 基数 or 权利金×n; sizing preview and control monitor follow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -524,10 +524,14 @@ async def put_strategy_settings(
|
||||
)
|
||||
).strip().lower()
|
||||
if sizing_mode == "risk_based":
|
||||
data["exit_mode"] = "fixed_usdt"
|
||||
# 永期以损:允许 exit_mode=fixed_usdt(基数×k)或 premium_multiple(权利金×倍)
|
||||
# 期期仍强制 fixed(目标由预算×盈亏比写入)
|
||||
if hedge_mode == "option_option":
|
||||
data["exit_mode"] = "fixed_usdt"
|
||||
data.pop("perp_qty_eth", None)
|
||||
data.pop("option_qty_eth", None)
|
||||
data.pop("net_profit_target", None)
|
||||
if str(data.get("exit_mode") or db.get_setting("exit_mode", "") or "").strip().lower() != "premium_multiple":
|
||||
data.pop("net_profit_target", None)
|
||||
loss_mode = str(
|
||||
data.get(
|
||||
"risk_loss_mode",
|
||||
|
||||
@@ -703,26 +703,42 @@ def apply_risk_sizing_to_ledger(
|
||||
if not r.ok:
|
||||
return r
|
||||
|
||||
# 以损定仓强制 fixed_usdt;出场 = exit_unit × k
|
||||
database.set_setting("exit_mode", "fixed_usdt")
|
||||
exit_mode = str(
|
||||
ledger.get_setting_str("exit_mode", "fixed_usdt") or "fixed_usdt"
|
||||
).strip().lower()
|
||||
s = get_settings()
|
||||
prem_mult = float(
|
||||
ledger.get_setting_float("premium_exit_multiple", s.premium_exit_multiple)
|
||||
or s.premium_exit_multiple
|
||||
)
|
||||
database.set_setting("perp_qty_eth", str(r.perp_qty_eth))
|
||||
database.set_setting("option_qty_eth", str(r.option_qty_eth))
|
||||
database.set_setting("net_profit_target", str(r.net_profit_target))
|
||||
database.set_setting("risk_last_k", str(r.k))
|
||||
database.set_setting(
|
||||
"risk_last_max_loss",
|
||||
f"{r.max_loss:.2f}" if r.max_loss is not None else "",
|
||||
)
|
||||
if exit_mode == "premium_multiple":
|
||||
database.set_setting("exit_mode", "premium_multiple")
|
||||
# 预估展示用:估权利金×倍数;开仓后以真实 initial_premium 锁定
|
||||
est = float(r.premium_est or 0) * max(0.0, prem_mult)
|
||||
if est > 0:
|
||||
database.set_setting("net_profit_target", f"{est:.4f}")
|
||||
exit_log = f"prem×{prem_mult:g}≈{est:.2f}"
|
||||
else:
|
||||
database.set_setting("exit_mode", "fixed_usdt")
|
||||
database.set_setting("net_profit_target", str(r.net_profit_target))
|
||||
exit_log = f"{r.net_profit_target or 0:.4f}"
|
||||
logger.info(
|
||||
"risk_sizing applied k=%.1f basis=%s sizing_ask=%.4f actual_ask=%.4f "
|
||||
"perp=%.4f opt=%.4f exit=%.4f max_loss=%.4f budget=%.4f",
|
||||
"perp=%.4f opt=%.4f exit=%s max_loss=%.4f budget=%.4f",
|
||||
r.k or 0,
|
||||
r.leverage_basis or "?",
|
||||
r.option_ask or 0,
|
||||
r.actual_option_ask or 0,
|
||||
r.perp_qty_eth or 0,
|
||||
r.option_qty_eth or 0,
|
||||
r.net_profit_target or 0,
|
||||
exit_log,
|
||||
r.max_loss or 0,
|
||||
r.budget or 0,
|
||||
)
|
||||
@@ -766,6 +782,20 @@ def preview_risk_sizing(db: Database | None = None) -> dict[str, Any]:
|
||||
r = compute_risk_sizing(index_px=float(idx), option_ask=float(ask), db=database)
|
||||
perp_u, opt_u, exit_u = read_risk_units(ledger)
|
||||
mg = resolve_martingale(database, ledger=ledger)
|
||||
s = get_settings()
|
||||
exit_mode = str(
|
||||
ledger.get_setting_str("exit_mode", s.exit_mode) or s.exit_mode
|
||||
).strip().lower()
|
||||
prem_mult = float(
|
||||
ledger.get_setting_float("premium_exit_multiple", s.premium_exit_multiple)
|
||||
or s.premium_exit_multiple
|
||||
)
|
||||
exit_target = r.net_profit_target
|
||||
exit_label = "基数×k"
|
||||
if exit_mode == "premium_multiple":
|
||||
exit_label = f"权利金×{prem_mult:g}"
|
||||
if r.ok and r.premium_est is not None:
|
||||
exit_target = round(float(r.premium_est) * max(0.0, prem_mult), 2)
|
||||
out.update(
|
||||
{
|
||||
"ok": r.ok,
|
||||
@@ -778,7 +808,10 @@ def preview_risk_sizing(db: Database | None = None) -> dict[str, Any]:
|
||||
"max_loss": r.max_loss,
|
||||
"perp_qty_eth": r.perp_qty_eth,
|
||||
"option_qty_eth": r.option_qty_eth,
|
||||
"net_profit_target": r.net_profit_target,
|
||||
"net_profit_target": exit_target,
|
||||
"exit_mode": exit_mode if exit_mode in ("fixed_usdt", "premium_multiple") else "fixed_usdt",
|
||||
"premium_exit_multiple": prem_mult,
|
||||
"exit_label": exit_label,
|
||||
"index_px": r.index_px,
|
||||
"option_ask": r.option_ask,
|
||||
"actual_option_ask": r.actual_option_ask,
|
||||
|
||||
@@ -208,10 +208,13 @@ function riskLines(strat: Record<string, unknown>): RiskLines {
|
||||
const minH = Number(
|
||||
strat.oo_min_option_hours ?? strat.min_option_hours,
|
||||
);
|
||||
const ampFilt = strat.oo_amplitude_filter_enabled === true;
|
||||
const amp =
|
||||
Number.isFinite(ampN) && Number.isFinite(ampH)
|
||||
? `≤${fmt(ampN, ampN % 1 === 0 ? 0 : 1)}%·${fmt(ampH, 0)}h`
|
||||
: "≤—";
|
||||
? ampFilt
|
||||
? `≤${fmt(ampN, ampN % 1 === 0 ? 0 : 1)}%·${fmt(ampH, 0)}h`
|
||||
: `${fmt(ampH, 0)}h`
|
||||
: "—";
|
||||
const callQ =
|
||||
strat.risk_sizing_preview &&
|
||||
typeof strat.risk_sizing_preview === "object" &&
|
||||
@@ -246,13 +249,15 @@ function riskLines(strat: Record<string, unknown>): RiskLines {
|
||||
isOo: true,
|
||||
sizing: riskBased ? "以损定仓(A)" : "手动开仓(B)",
|
||||
sizingShort: riskBased ? "A" : "B",
|
||||
lossPct: Number.isFinite(ampN)
|
||||
lossPct: ampFilt && Number.isFinite(ampN)
|
||||
? `≤${fmt(ampN, ampN % 1 === 0 ? 0 : 1)}%`
|
||||
: null,
|
||||
exit,
|
||||
openRatio,
|
||||
riskOrOpen: `${amp}/${openRatio}/${minHours}`,
|
||||
riskOrOpenTitle: "振幅上限·回看 / Call:Put 数量 / 最短剩余(h)",
|
||||
riskOrOpenTitle: ampFilt
|
||||
? "振幅上限·回看 / Call:Put 数量 / 最短剩余(h)"
|
||||
: "振幅回看 / Call:Put 数量 / 最短剩余(h)",
|
||||
minHours,
|
||||
leverage,
|
||||
leverageTitle: "期期单腿最低杠杆",
|
||||
@@ -261,7 +266,12 @@ function riskLines(strat: Record<string, unknown>): RiskLines {
|
||||
|
||||
const exitMode = String(strat.exit_mode || "fixed_usdt");
|
||||
let exit: string;
|
||||
if (riskBased) {
|
||||
if (riskBased && exitMode === "premium_multiple") {
|
||||
exit =
|
||||
strat.premium_exit_multiple != null
|
||||
? `权利金×${fmt(strat.premium_exit_multiple, 2)}`
|
||||
: "权利金×—";
|
||||
} else if (riskBased) {
|
||||
exit =
|
||||
strat.risk_exit_unit != null && Number.isFinite(Number(strat.risk_exit_unit))
|
||||
? `基数${unitLabel(strat.risk_exit_unit)}`
|
||||
|
||||
@@ -216,7 +216,11 @@ export default function PlanPage() {
|
||||
}${fmt(ooRatio, 1)})`
|
||||
: `净盈≥预算×${fmt(ooRatio, 1)}(达标只平盈利腿)`
|
||||
: exitMode === "premium_multiple"
|
||||
? `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
|
||||
? riskBased
|
||||
? exitTarget != null
|
||||
? `${riskLocked ? "锁定 " : ""}净盈≥${fmt(exitTarget)} U(权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)})`
|
||||
: `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}(开仓后锁定)`
|
||||
: `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
|
||||
: riskBased
|
||||
? exitTarget != null
|
||||
? `${riskLocked ? "锁定 " : "固定 "}${fmt(exitTarget)} U(基数${fmt(plan?.risk_exit_unit ?? 15)})`
|
||||
|
||||
@@ -376,8 +376,7 @@ export default function SettingsPage() {
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
fee_rate: fee,
|
||||
exit_mode:
|
||||
oo || sizingMode === "risk_based" ? "fixed_usdt" : exitMode,
|
||||
exit_mode: oo ? "fixed_usdt" : exitMode,
|
||||
premium_exit_multiple: premMult,
|
||||
rest_seconds: rest,
|
||||
live_order_interval_sec: orderInterval,
|
||||
@@ -1029,10 +1028,23 @@ export default function SettingsPage() {
|
||||
pk != null
|
||||
? Number((riskOptUnit * pk).toFixed(4))
|
||||
: riskPreview.option_qty_eth;
|
||||
const premEst = Number(riskPreview.premium_est);
|
||||
const exit =
|
||||
pk != null
|
||||
? Number((riskExitUnit * pk).toFixed(2))
|
||||
: riskPreview.net_profit_target;
|
||||
exitMode === "premium_multiple"
|
||||
? Number.isFinite(premEst)
|
||||
? Number(
|
||||
(
|
||||
premEst * Number(premMult || 1)
|
||||
).toFixed(2),
|
||||
)
|
||||
: riskPreview.net_profit_target
|
||||
: pk != null
|
||||
? Number((riskExitUnit * pk).toFixed(2))
|
||||
: riskPreview.net_profit_target;
|
||||
const exitDisp =
|
||||
exitMode === "premium_multiple"
|
||||
? `权利金×${Number(premMult || 1)}≈${exit ?? "—"}`
|
||||
: String(exit ?? "—");
|
||||
const budS = Number.isFinite(bud)
|
||||
? bud.toFixed(2)
|
||||
: "—";
|
||||
@@ -1076,7 +1088,7 @@ export default function SettingsPage() {
|
||||
} · Put≈${
|
||||
riskPreview.put_qty_eth ?? "—"
|
||||
} · 出场=预算×${ooRewardRatio}${mgS}`
|
||||
: `k=${pk ?? "—"} · 预算=${budS}U · 估亏=${mxS}U · 永续=${perp ?? "—"} · 期权=${opt ?? "—"} · 出场=${exit ?? "—"}${basisS}${mgS}`;
|
||||
: `k=${pk ?? "—"} · 预算=${budS}U · 估亏=${mxS}U · 永续=${perp ?? "—"} · 期权=${opt ?? "—"} · 出场=${exitDisp}${basisS}${mgS}`;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1120,9 +1132,72 @@ export default function SettingsPage() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="riskExitKind">出场资金</label>
|
||||
<select
|
||||
id="riskExitKind"
|
||||
className="mono"
|
||||
value={
|
||||
exitMode === "premium_multiple"
|
||||
? "premium_multiple"
|
||||
: "fixed_usdt"
|
||||
}
|
||||
onChange={(e) =>
|
||||
setExitMode(
|
||||
e.target.value === "premium_multiple"
|
||||
? "premium_multiple"
|
||||
: "fixed_usdt",
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="fixed_usdt">基数(×k)</option>
|
||||
<option value="premium_multiple">权利金倍数</option>
|
||||
</select>
|
||||
</div>
|
||||
{exitMode === "premium_multiple" ? (
|
||||
<div className="field">
|
||||
<label htmlFor="riskPremMult">
|
||||
盈利权利金倍数
|
||||
</label>
|
||||
<select
|
||||
id="riskPremMultQuick"
|
||||
className="mono"
|
||||
value={
|
||||
[1, 1.5, 2, 3].some(
|
||||
(x) => Math.abs(x - Number(premMult)) < 1e-9,
|
||||
)
|
||||
? String(Number(premMult))
|
||||
: "custom"
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (e.target.value === "custom") return;
|
||||
setPremMult(Number(e.target.value));
|
||||
}}
|
||||
>
|
||||
<option value="1">1 倍权利金</option>
|
||||
<option value="1.5">1.5 倍权利金</option>
|
||||
<option value="2">2 倍权利金</option>
|
||||
<option value="3">3 倍权利金</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select>
|
||||
<input
|
||||
id="riskPremMult"
|
||||
className="mono"
|
||||
style={{ marginTop: 6 }}
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
value={premMult}
|
||||
onChange={(e) =>
|
||||
setPremMult(Number(e.target.value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="field">
|
||||
<label htmlFor="riskExitU">
|
||||
出场资金(k=1;改后按比例联动)
|
||||
出场基数(k=1;改后按比例联动)
|
||||
</label>
|
||||
<input
|
||||
id="riskExitU"
|
||||
@@ -1178,6 +1253,8 @@ export default function SettingsPage() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
@@ -1399,10 +1476,7 @@ export default function SettingsPage() {
|
||||
<select
|
||||
id="exitMode"
|
||||
className="mono"
|
||||
disabled={sizingMode === "risk_based"}
|
||||
value={
|
||||
sizingMode === "risk_based" ? "fixed_usdt" : exitMode
|
||||
}
|
||||
value={exitMode}
|
||||
onChange={(e) =>
|
||||
setExitMode(
|
||||
e.target.value === "premium_multiple"
|
||||
@@ -1411,15 +1485,19 @@ export default function SettingsPage() {
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="fixed_usdt">固定净盈利(USDT)</option>
|
||||
<option value="fixed_usdt">
|
||||
{sizingMode === "risk_based"
|
||||
? "基数(×k)"
|
||||
: "固定净盈利(USDT)"}
|
||||
</option>
|
||||
<option value="premium_multiple">权利金倍数</option>
|
||||
</select>
|
||||
</div>
|
||||
{sizingMode === "risk_based" || exitMode === "fixed_usdt" ? (
|
||||
{exitMode === "fixed_usdt" ? (
|
||||
<div className="field">
|
||||
<label htmlFor="netTarget">
|
||||
{sizingMode === "risk_based"
|
||||
? "出场资金(k=1 基数)"
|
||||
? "出场基数(k=1)"
|
||||
: "净盈利出场目标(USDT)"}
|
||||
</label>
|
||||
<input
|
||||
@@ -1635,6 +1713,10 @@ export default function SettingsPage() {
|
||||
k,权利金再便宜也不放大仓位;实际杠杆按盘口卖一定仓,便宜时
|
||||
k 会变大。
|
||||
</li>
|
||||
<li>
|
||||
出场资金:可选「基数×k」或「权利金倍数」(如盈利 1
|
||||
倍/2 倍权利金);开仓后按真实权利金锁定目标。
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
<li>
|
||||
|
||||
Reference in New Issue
Block a user