Auto-fill 期期 sheets from trading balance with same-sheets default.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-19 00:56:35 +08:00
parent f21e4d1166
commit b3548240cc
7 changed files with 374 additions and 5 deletions
+140
View File
@@ -86,6 +86,146 @@ def floor_contracts_to_precision(contracts: float, decimals: int) -> float:
return math.floor(raw * scale + 1e-12) / scale
def option_unit_cost_usdc(*, ask: float, ct_mult: float) -> float:
"""单张权利金(USDC) = 卖一价 × ct_mult."""
a = _f(ask)
if a is None or a <= 0:
return 0.0
return float(a) * float(ct_mult or 0.01)
def resolve_oo_budget_usdc(
*,
trading_usdc: Any,
trade_budget_usdc: Any,
buffer_ratio: Any = 0.95,
) -> dict[str, Any]:
"""期期可用预算 = min(交易户×buffer, 单笔预算)."""
import math
trading = _f(trading_usdc)
cap = _f(trade_budget_usdc)
buf = _f(buffer_ratio)
if buf is None or buf <= 0:
buf = 0.95
if buf > 1:
buf = 1.0
trading_cap = None if trading is None else max(0.0, float(trading) * float(buf))
trade_cap = None if cap is None else max(0.0, float(cap))
if trading_cap is None and trade_cap is None:
return {
"ok": False,
"budget_usdc": 0.0,
"trading_cap": None,
"trade_budget_cap": None,
"buffer_ratio": float(buf),
"msg": "缺少交易户余额与单笔预算",
}
if trading_cap is None:
budget = float(trade_cap or 0.0)
elif trade_cap is None:
budget = float(trading_cap)
else:
budget = min(float(trading_cap), float(trade_cap))
budget = float(math.floor(budget * 1e6 + 1e-12) / 1e6)
return {
"ok": budget > 0,
"budget_usdc": budget,
"trading_cap": None if trading_cap is None else round(float(trading_cap), 6),
"trade_budget_cap": None if trade_cap is None else round(float(trade_cap), 6),
"buffer_ratio": float(buf),
"msg": "" if budget > 0 else "可用预算为 0",
}
def _cap_sheets_by_ask_depth(sheets: int, ask_sz: Any) -> int:
import math
n = max(0, int(sheets))
depth = _f(ask_sz)
if depth is None:
return n
if depth <= 0:
return 0
return min(n, int(math.floor(float(depth) + 1e-12)))
def suggest_oo_sheets(
*,
mode: str,
budget_usdc: float,
ask_a: float,
ct_mult_a: float = 0.01,
ask_sz_a: Any = None,
ask_b: float,
ct_mult_b: float = 0.01,
ask_sz_b: Any = None,
) -> dict[str, Any]:
"""期期建议张数:same_sheets(同张数,默认) / split_budget(均分预算)."""
import math
m = (mode or "same_sheets").strip().lower()
if m in ("split", "equal_budget", "split_budget", "均分"):
m = "split_budget"
else:
m = "same_sheets"
budget = max(0.0, float(budget_usdc or 0.0))
cost_a = option_unit_cost_usdc(ask=ask_a, ct_mult=ct_mult_a)
cost_b = option_unit_cost_usdc(ask=ask_b, ct_mult=ct_mult_b)
if budget <= 0:
return {
"mode": m,
"sheets_a": 0,
"sheets_b": 0,
"cost_a": round(cost_a, 8),
"cost_b": round(cost_b, 8),
"premium_est": 0.0,
"ok": False,
"msg": "可用预算为 0",
}
if cost_a <= 0 or cost_b <= 0:
return {
"mode": m,
"sheets_a": 0,
"sheets_b": 0,
"cost_a": round(cost_a, 8),
"cost_b": round(cost_b, 8),
"premium_est": 0.0,
"ok": False,
"msg": "缺少有效卖一价,无法建议张数",
}
if m == "split_budget":
half = budget / 2.0
n_a = int(math.floor(half / cost_a + 1e-12))
n_b = int(math.floor(half / cost_b + 1e-12))
else:
pair = cost_a + cost_b
n = int(math.floor(budget / pair + 1e-12)) if pair > 0 else 0
n_a = n
n_b = n
n_a = _cap_sheets_by_ask_depth(n_a, ask_sz_a)
n_b = _cap_sheets_by_ask_depth(n_b, ask_sz_b)
if m == "same_sheets":
n = min(n_a, n_b)
n_a = n
n_b = n
prem = cost_a * n_a + cost_b * n_b
ok = n_a >= 1 and n_b >= 1
msg = ""
if not ok:
msg = "预算不够开 1+1(或卖一深度不足)"
return {
"mode": m,
"sheets_a": n_a,
"sheets_b": n_b,
"cost_a": round(cost_a, 8),
"cost_b": round(cost_b, 8),
"premium_est": round(prem, 6),
"ok": ok,
"msg": msg,
}
def build_perp_options_preview(
*,
direction: str,
+4
View File
@@ -97,6 +97,8 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
"chain_max_dte": float(os.getenv("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS") or os.getenv("OKX_OPTIONS_MAX_DTE_DAYS") or "14"),
"perp_account_label": (os.getenv("OKX_ACCOUNT_LABEL") or "合约账户").strip(),
"options_account_label": (os.getenv("OKX_OPTIONS_ACCOUNT_LABEL") or "期权账户").strip(),
"trade_budget_usdc": float(os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC") or "10"),
"budget_buffer": float(os.getenv("OKX_OPTIONS_BUDGET_BUFFER") or "0.95"),
"live_trading": _env_bool("LIVE_TRADING_ENABLED", False),
"send_wechat": getattr(app_module, "send_wechat_msg", None),
}
@@ -405,6 +407,8 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
"account_label": cfg.get("options_account_label") or "期权账户",
"account_note": "期权腿使用期权账户(交易 USDC)",
"options_account": opt_acct,
"trade_budget_usdc": cfg.get("trade_budget_usdc"),
"budget_buffer": cfg.get("budget_buffer"),
}
)
@@ -138,17 +138,23 @@
<div id="hp-oo-index" class="muted hp-quote-line"></div>
<div id="hp-oo-bal-line" class="muted hp-quote-line"></div>
<p class="muted hp-unit-hint">震荡突破:设上下两个目标价(USD);触达任一侧重平盈利腿。张数=<strong>期权张</strong> · 权利金=USDC</p>
<div class="form-row hp-oo-size-row">
<span class="muted">自动张数</span>
<button type="button" class="btn-secondary hp-oo-size-mode active" data-oo-size="same_sheets">同张数</button>
<button type="button" class="btn-secondary hp-oo-size-mode" data-oo-size="split_budget">均分预算</button>
</div>
<p class="muted" id="hp-oo-budget-line"></p>
<div id="hp-oo-legs" class="hp-oo-legs">
<div class="hp-oo-leg-row" data-leg="a">
<div class="muted" id="hp-oo-leg-a-info">腿A: 尚未选用</div>
<label>张数 <span class="hp-unit">期权张</span>
<input type="number" step="1" min="1" id="hp-oo-sheets-a" value="1" disabled />
<input type="number" step="1" min="0" id="hp-oo-sheets-a" value="1" disabled />
</label>
</div>
<div class="hp-oo-leg-row" data-leg="b">
<div class="muted" id="hp-oo-leg-b-info">腿B: 尚未选用</div>
<label>张数 <span class="hp-unit">期权张</span>
<input type="number" step="1" min="1" id="hp-oo-sheets-b" value="1" disabled />
<input type="number" step="1" min="0" id="hp-oo-sheets-b" value="1" disabled />
</label>
</div>
<p class="muted" id="hp-oo-prem-line"></p>
@@ -264,4 +270,4 @@
</div>
</div>
</div>
<script src="/static/hedge_plan.js?v=14"></script>
<script src="/static/hedge_plan.js?v=15"></script>