Add risk leverage basis option: actual ask vs selection leverage.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -50,6 +50,7 @@ KEYS = (
|
|||||||
"option_qty_eth",
|
"option_qty_eth",
|
||||||
"show_manual_trade_buttons",
|
"show_manual_trade_buttons",
|
||||||
"sizing_mode",
|
"sizing_mode",
|
||||||
|
"risk_leverage_basis",
|
||||||
"risk_loss_mode",
|
"risk_loss_mode",
|
||||||
"risk_loss_pct",
|
"risk_loss_pct",
|
||||||
"risk_loss_usdt",
|
"risk_loss_usdt",
|
||||||
@@ -86,6 +87,9 @@ class StrategySettingsBody(BaseModel):
|
|||||||
show_manual_trade_buttons: bool | None = None
|
show_manual_trade_buttons: bool | None = None
|
||||||
exchange: str | None = Field(default=None, pattern="^(okx|binance|bn)$")
|
exchange: str | None = Field(default=None, pattern="^(okx|binance|bn)$")
|
||||||
sizing_mode: str | None = Field(default=None, pattern="^(manual|risk_based)$")
|
sizing_mode: str | None = Field(default=None, pattern="^(manual|risk_based)$")
|
||||||
|
risk_leverage_basis: str | None = Field(
|
||||||
|
default=None, pattern="^(actual|selection)$"
|
||||||
|
)
|
||||||
risk_loss_mode: str | None = Field(default=None, pattern="^(percent|absolute)$")
|
risk_loss_mode: str | None = Field(default=None, pattern="^(percent|absolute)$")
|
||||||
risk_loss_pct: float | None = Field(default=None, ge=0.01, le=100)
|
risk_loss_pct: float | None = Field(default=None, ge=0.01, le=100)
|
||||||
risk_loss_usdt: float | None = Field(default=None, ge=0.1, le=1_000_000)
|
risk_loss_usdt: float | None = Field(default=None, ge=0.1, le=1_000_000)
|
||||||
@@ -228,6 +232,23 @@ def _read_settings() -> dict:
|
|||||||
in ("manual", "risk_based")
|
in ("manual", "risk_based")
|
||||||
else "manual"
|
else "manual"
|
||||||
),
|
),
|
||||||
|
"risk_leverage_basis": (
|
||||||
|
rb
|
||||||
|
if (
|
||||||
|
rb := str(
|
||||||
|
db.get_setting(
|
||||||
|
"risk_leverage_basis",
|
||||||
|
str(s.risk_leverage_basis),
|
||||||
|
)
|
||||||
|
or s.risk_leverage_basis
|
||||||
|
or "selection"
|
||||||
|
)
|
||||||
|
.strip()
|
||||||
|
.lower()
|
||||||
|
)
|
||||||
|
in ("actual", "selection")
|
||||||
|
else "selection"
|
||||||
|
),
|
||||||
"risk_loss_mode": (
|
"risk_loss_mode": (
|
||||||
lm
|
lm
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ class Settings(BaseSettings):
|
|||||||
perp_margin_mode: str = "cross"
|
perp_margin_mode: str = "cross"
|
||||||
min_option_hours: float = 12.0 # 期权最小剩余小时
|
min_option_hours: float = 12.0 # 期权最小剩余小时
|
||||||
min_option_leverage: float = 100.0 # 现价/卖一权利金 下限
|
min_option_leverage: float = 100.0 # 现价/卖一权利金 下限
|
||||||
|
# 以损定仓权利金口径:actual=盘口卖一;selection=指数/选约杠杆(控节奏,默认)
|
||||||
|
risk_leverage_basis: str = "selection"
|
||||||
atm_open_offset_enabled: bool = False # 开仓 ATM 偏差限制开关(默认关)
|
atm_open_offset_enabled: bool = False # 开仓 ATM 偏差限制开关(默认关)
|
||||||
max_atm_open_offset: float = 3.0 # 开启后:|ATM行权价−标的| 上限(点)
|
max_atm_open_offset: float = 3.0 # 开启后:|ATM行权价−标的| 上限(点)
|
||||||
# 固定方向:关=现有 ATM/比价规则;开=指定永续多/空,期权 Put/Call 且须实值或平值
|
# 固定方向:关=现有 ATM/比价规则;开=指定永续多/空,期权 Put/Call 且须实值或平值
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ class Database:
|
|||||||
"perp_margin_mode": str(s.perp_margin_mode),
|
"perp_margin_mode": str(s.perp_margin_mode),
|
||||||
"min_option_hours": str(s.min_option_hours),
|
"min_option_hours": str(s.min_option_hours),
|
||||||
"min_option_leverage": str(s.min_option_leverage),
|
"min_option_leverage": str(s.min_option_leverage),
|
||||||
|
"risk_leverage_basis": str(s.risk_leverage_basis),
|
||||||
"atm_open_offset_enabled": str(s.atm_open_offset_enabled),
|
"atm_open_offset_enabled": str(s.atm_open_offset_enabled),
|
||||||
"max_atm_open_offset": str(s.max_atm_open_offset),
|
"max_atm_open_offset": str(s.max_atm_open_offset),
|
||||||
"fixed_direction_enabled": str(s.fixed_direction_enabled),
|
"fixed_direction_enabled": str(s.fixed_direction_enabled),
|
||||||
|
|||||||
@@ -55,12 +55,44 @@ class RiskSizingResult:
|
|||||||
option_qty_eth: float | None = None
|
option_qty_eth: float | None = None
|
||||||
net_profit_target: float | None = None
|
net_profit_target: float | None = None
|
||||||
index_px: float | None = None
|
index_px: float | None = None
|
||||||
option_ask: float | None = None
|
option_ask: float | None = None # 定仓用权利金(可能为选约杠杆隐含)
|
||||||
|
actual_option_ask: float | None = None # 盘口真实卖一
|
||||||
|
leverage_basis: str | None = None # actual | selection
|
||||||
perp_unit: float | None = None
|
perp_unit: float | None = None
|
||||||
option_unit: float | None = None
|
option_unit: float | None = None
|
||||||
exit_unit: float | None = None
|
exit_unit: float | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_risk_leverage_basis(raw: str | None, default: str = "selection") -> str:
|
||||||
|
v = (raw or default or "selection").strip().lower()
|
||||||
|
if v in ("selection", "min_option_leverage", "select", "选约", "选约杠杆"):
|
||||||
|
return "selection"
|
||||||
|
if v in ("actual", "market", "ask", "实际", "实际杠杆"):
|
||||||
|
return "actual"
|
||||||
|
return "selection" if default == "selection" else "actual"
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_sizing_option_ask(
|
||||||
|
*,
|
||||||
|
index_px: float,
|
||||||
|
option_ask: float,
|
||||||
|
leverage_basis: str,
|
||||||
|
min_option_leverage: float,
|
||||||
|
) -> tuple[float, str]:
|
||||||
|
"""
|
||||||
|
返回 (定仓用卖一, 口径 actual|selection)。
|
||||||
|
selection:隐含卖一 = 指数 / 选约杠杆;actual:用盘口卖一。
|
||||||
|
"""
|
||||||
|
basis = normalize_risk_leverage_basis(leverage_basis, "selection")
|
||||||
|
if basis == "selection":
|
||||||
|
lev = float(min_option_leverage)
|
||||||
|
if lev > 1e-12 and math.isfinite(lev) and index_px > 0:
|
||||||
|
return float(index_px) / lev, "selection"
|
||||||
|
# 选约杠杆无效时退回实际卖一,避免拒单
|
||||||
|
return float(option_ask), "actual"
|
||||||
|
return float(option_ask), "actual"
|
||||||
|
|
||||||
|
|
||||||
def is_risk_based(ledger: Ledger | None = None) -> bool:
|
def is_risk_based(ledger: Ledger | None = None) -> bool:
|
||||||
led = ledger or Ledger()
|
led = ledger or Ledger()
|
||||||
mode = (led.get_setting_str("sizing_mode", "manual") or "manual").strip().lower()
|
mode = (led.get_setting_str("sizing_mode", "manual") or "manual").strip().lower()
|
||||||
@@ -269,13 +301,31 @@ def compute_risk_sizing(
|
|||||||
s = get_settings()
|
s = get_settings()
|
||||||
fee_rate = ledger.get_setting_float("fee_rate", s.fee_rate)
|
fee_rate = ledger.get_setting_float("fee_rate", s.fee_rate)
|
||||||
perp_u, opt_u, exit_u = read_risk_units(ledger)
|
perp_u, opt_u, exit_u = read_risk_units(ledger)
|
||||||
|
basis_raw = 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
|
||||||
|
)
|
||||||
|
sizing_ask, basis = resolve_sizing_option_ask(
|
||||||
|
index_px=float(index_px),
|
||||||
|
option_ask=float(option_ask),
|
||||||
|
leverage_basis=str(basis_raw),
|
||||||
|
min_option_leverage=float(min_opt_lev),
|
||||||
|
)
|
||||||
budget, bud_detail, capital = resolve_budget(database)
|
budget, bud_detail, capital = resolve_budget(database)
|
||||||
if budget is None:
|
if budget is None:
|
||||||
return RiskSizingResult(ok=False, detail=f"以损定仓预算失败: {bud_detail}")
|
return RiskSizingResult(
|
||||||
|
ok=False,
|
||||||
|
detail=f"以损定仓预算失败: {bud_detail}",
|
||||||
|
leverage_basis=basis,
|
||||||
|
actual_option_ask=_round2(float(option_ask)),
|
||||||
|
option_ask=_round2(float(sizing_ask)),
|
||||||
|
)
|
||||||
r = compute_k(
|
r = compute_k(
|
||||||
budget=budget,
|
budget=budget,
|
||||||
index_px=index_px,
|
index_px=index_px,
|
||||||
option_ask=option_ask,
|
option_ask=sizing_ask,
|
||||||
fee_rate=fee_rate,
|
fee_rate=fee_rate,
|
||||||
perp_unit=perp_u,
|
perp_unit=perp_u,
|
||||||
option_unit=opt_u,
|
option_unit=opt_u,
|
||||||
@@ -288,7 +338,9 @@ def compute_risk_sizing(
|
|||||||
budget=_round2(budget),
|
budget=_round2(budget),
|
||||||
capital_base=_round2(capital) if capital is not None else None,
|
capital_base=_round2(capital) if capital is not None else None,
|
||||||
index_px=float(index_px),
|
index_px=float(index_px),
|
||||||
option_ask=float(option_ask),
|
option_ask=_round2(float(sizing_ask)),
|
||||||
|
actual_option_ask=_round2(float(option_ask)),
|
||||||
|
leverage_basis=basis,
|
||||||
k=r.k,
|
k=r.k,
|
||||||
perp_unit=perp_u,
|
perp_unit=perp_u,
|
||||||
option_unit=opt_u,
|
option_unit=opt_u,
|
||||||
@@ -307,7 +359,9 @@ def compute_risk_sizing(
|
|||||||
option_qty_eth=r.option_qty_eth,
|
option_qty_eth=r.option_qty_eth,
|
||||||
net_profit_target=r.net_profit_target,
|
net_profit_target=r.net_profit_target,
|
||||||
index_px=r.index_px,
|
index_px=r.index_px,
|
||||||
option_ask=r.option_ask,
|
option_ask=_round2(float(sizing_ask)),
|
||||||
|
actual_option_ask=_round2(float(option_ask)),
|
||||||
|
leverage_basis=basis,
|
||||||
perp_unit=perp_u,
|
perp_unit=perp_u,
|
||||||
option_unit=opt_u,
|
option_unit=opt_u,
|
||||||
exit_unit=exit_u,
|
exit_unit=exit_u,
|
||||||
@@ -351,8 +405,12 @@ def apply_risk_sizing_to_ledger(
|
|||||||
f"{r.max_loss:.2f}" if r.max_loss is not None else "",
|
f"{r.max_loss:.2f}" if r.max_loss is not None else "",
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"risk_sizing applied k=%.1f perp=%.4f opt=%.4f exit=%.4f max_loss=%.4f budget=%.4f",
|
"risk_sizing applied k=%.1f basis=%s sizing_ask=%.4f actual_ask=%.4f "
|
||||||
|
"perp=%.4f opt=%.4f exit=%.4f max_loss=%.4f budget=%.4f",
|
||||||
r.k or 0,
|
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.perp_qty_eth or 0,
|
||||||
r.option_qty_eth or 0,
|
r.option_qty_eth or 0,
|
||||||
r.net_profit_target or 0,
|
r.net_profit_target or 0,
|
||||||
@@ -401,6 +459,8 @@ def preview_risk_sizing(db: Database | None = None) -> dict[str, Any]:
|
|||||||
"net_profit_target": r.net_profit_target,
|
"net_profit_target": r.net_profit_target,
|
||||||
"index_px": r.index_px,
|
"index_px": r.index_px,
|
||||||
"option_ask": r.option_ask,
|
"option_ask": r.option_ask,
|
||||||
|
"actual_option_ask": r.actual_option_ask,
|
||||||
|
"leverage_basis": r.leverage_basis,
|
||||||
"perp_unit": perp_u,
|
"perp_unit": perp_u,
|
||||||
"option_unit": opt_u,
|
"option_unit": opt_u,
|
||||||
"exit_unit": exit_u,
|
"exit_unit": exit_u,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from app.strategy.risk_sizing import (
|
|||||||
BASE_PERP_ETH,
|
BASE_PERP_ETH,
|
||||||
compute_k,
|
compute_k,
|
||||||
floor_k_1dp,
|
floor_k_1dp,
|
||||||
|
normalize_risk_leverage_basis,
|
||||||
|
resolve_sizing_option_ask,
|
||||||
unit_cost,
|
unit_cost,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,3 +80,72 @@ def test_compute_k_too_small() -> None:
|
|||||||
|
|
||||||
def test_unit_cost() -> None:
|
def test_unit_cost() -> None:
|
||||||
assert abs(unit_cost(index_px=2000, option_ask=20, fee_rate=0.0005) - 43.0) < 1e-9
|
assert abs(unit_cost(index_px=2000, option_ask=20, fee_rate=0.0005) - 43.0) < 1e-9
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_risk_leverage_basis() -> None:
|
||||||
|
assert normalize_risk_leverage_basis("actual") == "actual"
|
||||||
|
assert normalize_risk_leverage_basis("selection") == "selection"
|
||||||
|
assert normalize_risk_leverage_basis("min_option_leverage") == "selection"
|
||||||
|
assert normalize_risk_leverage_basis("weird", default="selection") == "selection"
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_sizing_ask_selection_vs_actual() -> None:
|
||||||
|
# 指数 2000、选约杠杆 100 → 隐含卖一 20;实际卖一更便宜 10
|
||||||
|
sel_ask, basis = resolve_sizing_option_ask(
|
||||||
|
index_px=2000.0,
|
||||||
|
option_ask=10.0,
|
||||||
|
leverage_basis="selection",
|
||||||
|
min_option_leverage=100.0,
|
||||||
|
)
|
||||||
|
assert basis == "selection"
|
||||||
|
assert abs(sel_ask - 20.0) < 1e-9
|
||||||
|
|
||||||
|
act_ask, basis2 = resolve_sizing_option_ask(
|
||||||
|
index_px=2000.0,
|
||||||
|
option_ask=10.0,
|
||||||
|
leverage_basis="actual",
|
||||||
|
min_option_leverage=100.0,
|
||||||
|
)
|
||||||
|
assert basis2 == "actual"
|
||||||
|
assert abs(act_ask - 10.0) < 1e-9
|
||||||
|
|
||||||
|
|
||||||
|
def test_selection_basis_yields_smaller_k_when_ask_cheap() -> None:
|
||||||
|
# 预算 43:选约隐含 ask=20 → k=1;若用实际 ask=10 → 单位成本更小 → k 更大
|
||||||
|
r_sel = compute_k(budget=43.0, index_px=2000.0, option_ask=20.0, fee_rate=0.0005)
|
||||||
|
r_act = compute_k(budget=43.0, index_px=2000.0, option_ask=10.0, fee_rate=0.0005)
|
||||||
|
assert r_sel.ok and r_act.ok
|
||||||
|
assert r_sel.k == 1.0
|
||||||
|
assert r_act.k is not None and r_act.k > r_sel.k
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_risk_sizing_respects_basis(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_basis.db")
|
||||||
|
db.set_setting("sizing_mode", "risk_based")
|
||||||
|
db.set_setting("risk_loss_mode", "absolute")
|
||||||
|
db.set_setting("risk_loss_usdt", "43")
|
||||||
|
db.set_setting("fee_rate", "0.0005")
|
||||||
|
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")
|
||||||
|
|
||||||
|
db.set_setting("risk_leverage_basis", "selection")
|
||||||
|
r1 = compute_risk_sizing(index_px=2000.0, option_ask=10.0, db=db)
|
||||||
|
assert r1.ok
|
||||||
|
assert r1.leverage_basis == "selection"
|
||||||
|
assert r1.k == 1.0
|
||||||
|
assert r1.actual_option_ask == 10.0
|
||||||
|
assert r1.option_ask == 20.0
|
||||||
|
|
||||||
|
db.set_setting("risk_leverage_basis", "actual")
|
||||||
|
r2 = compute_risk_sizing(index_px=2000.0, option_ask=10.0, db=db)
|
||||||
|
assert r2.ok
|
||||||
|
assert r2.leverage_basis == "actual"
|
||||||
|
assert r2.k is not None and r2.k > 1.0
|
||||||
|
assert r2.option_ask == 10.0
|
||||||
|
db.close()
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ export type StrategySettings = {
|
|||||||
option_qty_eth?: number;
|
option_qty_eth?: number;
|
||||||
show_manual_trade_buttons?: boolean;
|
show_manual_trade_buttons?: boolean;
|
||||||
sizing_mode?: "manual" | "risk_based";
|
sizing_mode?: "manual" | "risk_based";
|
||||||
|
risk_leverage_basis?: "actual" | "selection";
|
||||||
risk_loss_mode?: "percent" | "absolute";
|
risk_loss_mode?: "percent" | "absolute";
|
||||||
risk_loss_pct?: number;
|
risk_loss_pct?: number;
|
||||||
risk_loss_usdt?: number;
|
risk_loss_usdt?: number;
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ export default function SettingsPage() {
|
|||||||
const [optQty, setOptQty] = useState(2);
|
const [optQty, setOptQty] = useState(2);
|
||||||
const [showManualTrade, setShowManualTrade] = useState(false);
|
const [showManualTrade, setShowManualTrade] = useState(false);
|
||||||
const [sizingMode, setSizingMode] = useState<"manual" | "risk_based">("manual");
|
const [sizingMode, setSizingMode] = useState<"manual" | "risk_based">("manual");
|
||||||
|
const [riskLeverageBasis, setRiskLeverageBasis] = useState<
|
||||||
|
"actual" | "selection"
|
||||||
|
>("selection");
|
||||||
const [riskLossMode, setRiskLossMode] = useState<"percent" | "absolute">(
|
const [riskLossMode, setRiskLossMode] = useState<"percent" | "absolute">(
|
||||||
"percent",
|
"percent",
|
||||||
);
|
);
|
||||||
@@ -197,6 +200,9 @@ export default function SettingsPage() {
|
|||||||
setOptQty(s.option_qty_eth ?? 2);
|
setOptQty(s.option_qty_eth ?? 2);
|
||||||
setShowManualTrade(s.show_manual_trade_buttons === true);
|
setShowManualTrade(s.show_manual_trade_buttons === true);
|
||||||
setSizingMode(s.sizing_mode === "risk_based" ? "risk_based" : "manual");
|
setSizingMode(s.sizing_mode === "risk_based" ? "risk_based" : "manual");
|
||||||
|
setRiskLeverageBasis(
|
||||||
|
s.risk_leverage_basis === "actual" ? "actual" : "selection",
|
||||||
|
);
|
||||||
setRiskLossMode(s.risk_loss_mode === "absolute" ? "absolute" : "percent");
|
setRiskLossMode(s.risk_loss_mode === "absolute" ? "absolute" : "percent");
|
||||||
setRiskLossPct(s.risk_loss_pct ?? 1);
|
setRiskLossPct(s.risk_loss_pct ?? 1);
|
||||||
setRiskLossUsdt(s.risk_loss_usdt ?? 15);
|
setRiskLossUsdt(s.risk_loss_usdt ?? 15);
|
||||||
@@ -358,6 +364,7 @@ export default function SettingsPage() {
|
|||||||
close_bid_mark_max_pct: closeDevPct,
|
close_bid_mark_max_pct: closeDevPct,
|
||||||
show_manual_trade_buttons: showManualTrade,
|
show_manual_trade_buttons: showManualTrade,
|
||||||
sizing_mode: sizingMode,
|
sizing_mode: sizingMode,
|
||||||
|
risk_leverage_basis: riskLeverageBasis,
|
||||||
risk_loss_mode: riskLossMode,
|
risk_loss_mode: riskLossMode,
|
||||||
risk_loss_pct: riskLossPct,
|
risk_loss_pct: riskLossPct,
|
||||||
risk_loss_usdt: riskLossUsdt,
|
risk_loss_usdt: riskLossUsdt,
|
||||||
@@ -679,6 +686,30 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
{sizingMode === "risk_based" ? (
|
{sizingMode === "risk_based" ? (
|
||||||
<>
|
<>
|
||||||
|
<div className="field">
|
||||||
|
<label htmlFor="riskLevBasis">节奏杠杆(定仓口径)</label>
|
||||||
|
<select
|
||||||
|
id="riskLevBasis"
|
||||||
|
className="mono"
|
||||||
|
value={riskLeverageBasis}
|
||||||
|
onChange={(e) =>
|
||||||
|
setRiskLeverageBasis(
|
||||||
|
e.target.value === "actual"
|
||||||
|
? "actual"
|
||||||
|
: "selection",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="selection">
|
||||||
|
选约杠杆(推荐控节奏)
|
||||||
|
</option>
|
||||||
|
<option value="actual">实际杠杆(盘口卖一)</option>
|
||||||
|
</select>
|
||||||
|
<p className="hint" style={{ margin: "0.35rem 0 0" }}>
|
||||||
|
选约杠杆:按「指数÷期权杠杆≥」估权利金算 k,权利金再便宜也不放大仓位。
|
||||||
|
实际杠杆:按盘口卖一定仓,便宜时 k 会变大。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label htmlFor="riskLossMode">亏损约束</label>
|
<label htmlFor="riskLossMode">亏损约束</label>
|
||||||
<select
|
<select
|
||||||
@@ -794,7 +825,14 @@ export default function SettingsPage() {
|
|||||||
? bud.toFixed(2)
|
? bud.toFixed(2)
|
||||||
: "—";
|
: "—";
|
||||||
const mxS = Number.isFinite(mx) ? mx.toFixed(2) : "—";
|
const mxS = Number.isFinite(mx) ? mx.toFixed(2) : "—";
|
||||||
return `k=${pk ?? "—"} · 预算=${budS}U · 估亏=${mxS}U · 永续=${perp ?? "—"} · 期权=${opt ?? "—"} · 出场=${exit ?? "—"}`;
|
const basis =
|
||||||
|
riskPreview.leverage_basis === "actual"
|
||||||
|
? "实际杠杆"
|
||||||
|
: riskPreview.leverage_basis === "selection"
|
||||||
|
? "选约杠杆"
|
||||||
|
: "";
|
||||||
|
const basisS = basis ? ` · ${basis}` : "";
|
||||||
|
return `k=${pk ?? "—"} · 预算=${budS}U · 估亏=${mxS}U · 永续=${perp ?? "—"} · 期权=${opt ?? "—"} · 出场=${exit ?? "—"}${basisS}`;
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user