Show semi risk-based open size from ask and unit ratio.
Semi sizing uses market ask with semi units; Plan panel previews option/perp qty under the form. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -254,6 +254,9 @@ class StrategyEngine:
|
||||
"oo_put_qty_eth": oo_put_qty,
|
||||
"sizing_mode": sizing_mode,
|
||||
"risk_based": sizing_mode == "risk_based",
|
||||
"fee_rate": float(
|
||||
self.ledger.get_setting_float("fee_rate", s.fee_rate) or s.fee_rate
|
||||
),
|
||||
"hedge_mode": (
|
||||
hm
|
||||
if (
|
||||
|
||||
@@ -83,10 +83,15 @@ def size_and_gate(
|
||||
led = Ledger(database)
|
||||
semi_perp = None
|
||||
semi_opt = None
|
||||
semi_exit = None
|
||||
semi_basis = None
|
||||
if is_semi_auto(led):
|
||||
sp = read_semi_params(led)
|
||||
semi_perp = float(sp["perp_unit"])
|
||||
semi_opt = float(sp["option_unit"])
|
||||
semi_exit = float(sp["perp_exit_unit"])
|
||||
# 半自动以损定仓:按盘口卖一推期权量,再按配比乘 k 得永续量
|
||||
semi_basis = "actual"
|
||||
if not is_risk_based(led):
|
||||
# 手动仓:半自动直接写入 永续:期权 配比名义
|
||||
database.set_setting("perp_qty_eth", f"{semi_perp:.4f}")
|
||||
@@ -97,6 +102,8 @@ def size_and_gate(
|
||||
db=database,
|
||||
perp_unit=semi_perp,
|
||||
option_unit=semi_opt,
|
||||
exit_unit=semi_exit,
|
||||
leverage_basis=semi_basis,
|
||||
)
|
||||
if not rs.ok:
|
||||
return OpenPrepResult(ok=False, detail=rs.detail, sizing_detail=rs.detail)
|
||||
|
||||
@@ -430,6 +430,8 @@ def compute_risk_sizing(
|
||||
db: Database | None = None,
|
||||
perp_unit: float | None = None,
|
||||
option_unit: float | None = None,
|
||||
exit_unit: float | None = None,
|
||||
leverage_basis: str | None = None,
|
||||
) -> RiskSizingResult:
|
||||
database = db or get_db()
|
||||
ledger = Ledger(database)
|
||||
@@ -440,9 +442,16 @@ def compute_risk_sizing(
|
||||
perp_u = float(perp_unit)
|
||||
if option_unit is not None and float(option_unit) > 0:
|
||||
opt_u = float(option_unit)
|
||||
basis_raw = ledger.get_setting_str(
|
||||
"risk_leverage_basis", s.risk_leverage_basis
|
||||
) or s.risk_leverage_basis
|
||||
if exit_unit is not None and float(exit_unit) > 0:
|
||||
exit_u = float(exit_unit)
|
||||
basis_raw = (
|
||||
leverage_basis
|
||||
if leverage_basis is not None
|
||||
else (
|
||||
ledger.get_setting_str("risk_leverage_basis", s.risk_leverage_basis)
|
||||
or s.risk_leverage_basis
|
||||
)
|
||||
)
|
||||
min_opt_lev = ledger.get_setting_float(
|
||||
"min_option_leverage", s.min_option_leverage
|
||||
)
|
||||
@@ -690,6 +699,8 @@ def apply_risk_sizing_to_ledger(
|
||||
db: Database | None = None,
|
||||
perp_unit: float | None = None,
|
||||
option_unit: float | None = None,
|
||||
exit_unit: float | None = None,
|
||||
leverage_basis: str | None = None,
|
||||
) -> RiskSizingResult:
|
||||
"""计算并写入 perp/option/exit;非以损定仓模式直接 ok 跳过。持仓中拒绝改写。"""
|
||||
database = db or get_db()
|
||||
@@ -713,6 +724,8 @@ def apply_risk_sizing_to_ledger(
|
||||
db=database,
|
||||
perp_unit=perp_unit,
|
||||
option_unit=option_unit,
|
||||
exit_unit=exit_unit,
|
||||
leverage_basis=leverage_basis,
|
||||
)
|
||||
if not r.ok:
|
||||
return r
|
||||
@@ -793,8 +806,38 @@ def preview_risk_sizing(db: Database | None = None) -> dict[str, Any]:
|
||||
out["ok"] = False
|
||||
out["detail"] = "暂无指数或期权卖一,无法预览"
|
||||
return out
|
||||
r = compute_risk_sizing(index_px=float(idx), option_ask=float(ask), db=database)
|
||||
|
||||
# 半自动:用本单配比/净利基数,并以盘口卖一定仓(与开仓一致)
|
||||
semi_perp = semi_opt = semi_exit = None
|
||||
semi_on = False
|
||||
try:
|
||||
from .semi_auto import is_semi_auto, read_semi_params
|
||||
|
||||
if is_semi_auto(ledger):
|
||||
sp = read_semi_params(ledger)
|
||||
semi_on = True
|
||||
semi_perp = float(sp["perp_unit"])
|
||||
semi_opt = float(sp["option_unit"])
|
||||
semi_exit = float(sp["perp_exit_unit"])
|
||||
except Exception:
|
||||
logger.debug("preview semi units skipped", exc_info=True)
|
||||
|
||||
r = compute_risk_sizing(
|
||||
index_px=float(idx),
|
||||
option_ask=float(ask),
|
||||
db=database,
|
||||
perp_unit=semi_perp,
|
||||
option_unit=semi_opt,
|
||||
exit_unit=semi_exit,
|
||||
leverage_basis="actual" if semi_on else None,
|
||||
)
|
||||
perp_u, opt_u, exit_u = read_risk_units(ledger)
|
||||
if semi_perp is not None:
|
||||
perp_u = float(semi_perp)
|
||||
if semi_opt is not None:
|
||||
opt_u = float(semi_opt)
|
||||
if semi_exit is not None:
|
||||
exit_u = float(semi_exit)
|
||||
mg = resolve_martingale(database, ledger=ledger)
|
||||
s = get_settings()
|
||||
exit_mode = str(
|
||||
@@ -806,7 +849,10 @@ def preview_risk_sizing(db: Database | None = None) -> dict[str, Any]:
|
||||
)
|
||||
exit_target = r.net_profit_target
|
||||
exit_label = "基数×k"
|
||||
if exit_mode == "premium_multiple":
|
||||
if semi_on:
|
||||
exit_label = "半自动净利基数×k"
|
||||
exit_mode = "fixed_usdt"
|
||||
elif 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)
|
||||
@@ -833,6 +879,7 @@ def preview_risk_sizing(db: Database | None = None) -> dict[str, Any]:
|
||||
"perp_unit": perp_u,
|
||||
"option_unit": opt_u,
|
||||
"exit_unit": exit_u,
|
||||
"semi_units": semi_on,
|
||||
"martingale": mg,
|
||||
"risk_effective_loss_pct": mg.get("effective_pct"),
|
||||
}
|
||||
|
||||
@@ -119,6 +119,41 @@ def test_selection_basis_yields_smaller_k_when_ask_cheap() -> None:
|
||||
assert r_act.k is not None and r_act.k > r_sel.k
|
||||
|
||||
|
||||
def test_compute_risk_sizing_unit_overrides(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("MODE", "SIM")
|
||||
from app.models.db import Database
|
||||
from app.strategy.risk_sizing import compute_risk_sizing
|
||||
|
||||
db = Database(tmp_path / "risk_units.db")
|
||||
db.set_setting("sizing_mode", "risk_based")
|
||||
db.set_setting("risk_loss_mode", "absolute")
|
||||
db.set_setting("risk_loss_usdt", "83")
|
||||
db.set_setting("fee_rate", "0.0005")
|
||||
db.set_setting("risk_leverage_basis", "selection")
|
||||
db.set_setting("min_option_leverage", "100")
|
||||
db.set_setting("risk_perp_unit", "1")
|
||||
db.set_setting("risk_option_unit", "2")
|
||||
db.set_setting("risk_exit_unit", "15")
|
||||
|
||||
# 覆盖为单位 0.5:4、出场 5,并强制实际卖一(ask=20 → cost=80+3=83 → k=1)
|
||||
r = compute_risk_sizing(
|
||||
index_px=2000.0,
|
||||
option_ask=20.0,
|
||||
db=db,
|
||||
perp_unit=0.5,
|
||||
option_unit=4.0,
|
||||
exit_unit=5.0,
|
||||
leverage_basis="actual",
|
||||
)
|
||||
assert r.ok
|
||||
assert r.leverage_basis == "actual"
|
||||
assert r.k == 1.0
|
||||
assert r.perp_qty_eth == 0.5
|
||||
assert r.option_qty_eth == 4.0
|
||||
assert r.net_profit_target == 5.0
|
||||
db.close()
|
||||
|
||||
|
||||
def test_compute_risk_sizing_respects_basis(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("MODE", "SIM")
|
||||
from app.models.db import Database
|
||||
|
||||
Reference in New Issue
Block a user