单独期权增加全仓复利模式:可选上限开关,仅允许一仓

用期权户全部可用×缓冲开仓,默认不设上限;开启上限后按 env 封顶,全仓时禁止已有持仓再开。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-12 09:45:36 +08:00
parent a354811a6e
commit 1522117eeb
8 changed files with 240 additions and 19 deletions
+94 -7
View File
@@ -103,6 +103,8 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
"render_main_page": app_module.render_main_page,
"trade_budget": _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", 10.0),
"budget_buffer": _env_float("OKX_OPTIONS_BUDGET_BUFFER", 0.95),
"compound_full_cap_enabled": _env_bool("OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", False),
"compound_full_cap_usdc": _env_float("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", 300.0),
"default_underly": (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper(),
"max_dte_days": _env_float("OKX_OPTIONS_MAX_DTE_DAYS", 2.0),
"chain_max_dte_days": _env_float("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS", 14.0),
@@ -174,6 +176,34 @@ def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
return resolve_budget_full_usdc(trading, float(cap)), ""
def _compound_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
"""全仓复利 = 期权交易户可用(可选上限封顶);再由 calc_order_size × budget_buffer."""
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
from lib.options.options_pricing_lib import resolve_compound_full_usdc
raw = fetch_options_trading_usdc(ex)
if raw is None or float(raw) <= 0:
return None, "交易账户 USDC 可用余额不足"
trading = float(raw)
# 额度热更读 env(与模板启动值无关)
cap_on = _env_bool("OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", False)
cap_v = _env_float("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", 300.0)
if cap_on and cap_v <= 0:
return None, "全仓上限无效(OKX_OPTIONS_COMPOUND_FULL_CAP_USDC)"
return (
resolve_compound_full_usdc(
trading,
cap_enabled=cap_on,
cap_usdc=cap_v,
),
"",
)
def _is_budget_mode(mode: str) -> bool:
return mode in ("budget_full", "compound_full")
def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
conn = cfg["get_db"]()
try:
@@ -440,6 +470,14 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
budget_cap = budget
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
available_usdc = fetch_options_trading_usdc(ex)
elif mode == "compound_full":
budget, budget_err = _compound_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
budget_cap = budget
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
available_usdc = fetch_options_trading_usdc(ex)
eth_amount = None
try:
@@ -471,6 +509,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
},
"available_usdc": available_usdc,
"budget_full_usdc": budget if mode == "budget_full" else None,
"compound_full_usdc": budget if mode == "compound_full" else None,
}
)
except Exception as e:
@@ -507,6 +546,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
},
"available_usdc": available_usdc,
"budget_full_usdc": budget if mode == "budget_full" else None,
"compound_full_usdc": budget if mode == "compound_full" else None,
}
)
except Exception as e:
@@ -531,9 +571,39 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
},
"available_usdc": available_usdc,
"budget_full_usdc": budget if mode == "budget_full" else None,
"compound_full_usdc": budget if mode == "compound_full" else None,
}
)
from lib.options.options_position_limit_lib import option_position_limit_block_msg
from lib.options.options_position_limit_lib import (
compound_full_single_position_block_msg,
option_position_limit_block_msg,
)
if mode == "compound_full":
compound_block = compound_full_single_position_block_msg(
ex, fetch_positions=cfg.get("fetch_option_positions")
)
if compound_block:
return jsonify(
{
**q,
"ok": True,
"can_open": False,
"msg": compound_block,
"quote_per_unit": ask,
"premium_per_sheet": None,
"sizing": {
"ok": False,
"msg": compound_block,
"sheets": 0,
"eth_amount": 0.0,
"total_premium": 0.0,
},
"available_usdc": available_usdc,
"budget_full_usdc": None,
"compound_full_usdc": budget,
}
)
pos_limit_msg = option_position_limit_block_msg(
ex,
@@ -558,17 +628,18 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
},
"available_usdc": available_usdc,
"budget_full_usdc": budget if mode == "budget_full" else None,
"compound_full_usdc": budget if mode == "compound_full" else None,
}
)
sizing = calc_order_size(
quote_per_unit=float(ask),
ct_mult=float(ct_mult),
min_sz=int(min_sz),
budget_usdc=budget if mode == "budget_full" else None,
budget_usdc=budget if _is_budget_mode(mode) else None,
budget_buffer=cfg["budget_buffer"],
eth_amount=eth_amount if mode == "eth_amount" else None,
sheets=sheet_count if mode == "sheets" else None,
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
budget_cap=budget_cap if mode in ("budget_full", "compound_full", "sheets", "eth_amount") else None,
)
if sizing.get("ok"):
capped, cap_msg = cap_option_buy_sheets_to_ask_depth(
@@ -590,7 +661,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
ct_mult=float(ct_mult),
min_sz=int(min_sz),
sheets=capped,
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
budget_cap=budget_cap if mode in ("budget_full", "compound_full", "sheets", "eth_amount") else None,
)
if sizing.get("ok"):
sizing["ask_depth_capped"] = True
@@ -612,6 +683,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
"sizing": sizing,
"available_usdc": available_usdc,
"budget_full_usdc": budget if mode == "budget_full" else None,
"compound_full_usdc": budget if mode == "compound_full" else None,
}
)
@@ -672,7 +744,17 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
"ref_ask": q.get("ref_ask"),
}
)
from lib.options.options_position_limit_lib import option_position_limit_block_msg
from lib.options.options_position_limit_lib import (
compound_full_single_position_block_msg,
option_position_limit_block_msg,
)
if mode == "compound_full":
compound_block = compound_full_single_position_block_msg(
ex, fetch_positions=cfg.get("fetch_option_positions")
)
if compound_block:
return jsonify({"ok": False, "msg": compound_block, "can_open": False})
pos_limit_msg = option_position_limit_block_msg(
ex,
@@ -702,15 +784,20 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
budget_cap = budget
elif mode == "compound_full":
budget, budget_err = _compound_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
budget_cap = budget
sizing = calc_order_size(
quote_per_unit=float(ask),
ct_mult=ct_mult,
min_sz=min_sz,
budget_usdc=budget if mode == "budget_full" else None,
budget_usdc=budget if _is_budget_mode(mode) else None,
budget_buffer=cfg["budget_buffer"],
eth_amount=eth_amount,
sheets=sheet_count,
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
budget_cap=budget_cap if mode in ("budget_full", "compound_full", "sheets", "eth_amount") else None,
)
if not sizing.get("ok"):
return jsonify({"ok": False, "msg": sizing.get("msg") or "张数计算失败", "sizing": sizing})