feat期权标的白名单:可固定只显示 ETH 或 BTC。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -248,6 +248,16 @@ def create_cond_order(
|
||||
return {"ok": False, "msg": "本机已有活跃条件单"}
|
||||
|
||||
underlying = (payload.get("underlying") or "ETH").strip().upper() or "ETH"
|
||||
try:
|
||||
from lib.options.options_underly_lib import resolve_default_underly, underly_not_allowed_msg
|
||||
|
||||
if not (payload.get("underlying") or "").strip():
|
||||
underlying = resolve_default_underly()
|
||||
uly_block = underly_not_allowed_msg(underlying)
|
||||
if uly_block:
|
||||
return {"ok": False, "msg": uly_block}
|
||||
except Exception:
|
||||
pass
|
||||
opt_type = (payload.get("opt_type") or "P").strip().upper()
|
||||
if opt_type not in ("P", "C"):
|
||||
return {"ok": False, "msg": "opt_type 须为 P 或 C"}
|
||||
|
||||
@@ -133,6 +133,9 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
||||
except Exception:
|
||||
open_bridges = []
|
||||
|
||||
from lib.options.options_underly_lib import underly_cfg_snapshot
|
||||
|
||||
_uly_snap = underly_cfg_snapshot()
|
||||
out = {
|
||||
"ok": True,
|
||||
"enabled": True,
|
||||
@@ -152,7 +155,8 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
||||
"max_active_positions": options_max_active_positions(),
|
||||
"options_margin_mode": margin_mode,
|
||||
"options_margin_mode_label": "币本位" if margin_mode == "coin" else "USDC",
|
||||
"options_underly": (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper() or "ETH",
|
||||
"options_underly": _uly_snap["default_underly"],
|
||||
"options_allowed_underlys": _uly_snap["allowed_underlys"],
|
||||
"coin_budget": coin_budget,
|
||||
"bridge_status": bridge_status,
|
||||
"open_bridges": open_bridges,
|
||||
|
||||
@@ -28,6 +28,18 @@ def _env_float(key: str, default: float) -> float:
|
||||
return float(default)
|
||||
|
||||
|
||||
def _underly_blocked(underlying: str | None = None, *, inst_id: str | None = None) -> str | None:
|
||||
"""热读白名单;返回拒因或 None."""
|
||||
from lib.options.options_underly_lib import (
|
||||
inst_underly_not_allowed_msg,
|
||||
underly_not_allowed_msg,
|
||||
)
|
||||
|
||||
if inst_id:
|
||||
return inst_underly_not_allowed_msg(inst_id)
|
||||
return underly_not_allowed_msg(underlying or "")
|
||||
|
||||
|
||||
def attach_options_templates(app: Flask, repo_root: str) -> None:
|
||||
tpl_dir = os.path.join(repo_root, "lib", "options", "templates")
|
||||
if not os.path.isdir(tpl_dir):
|
||||
@@ -527,7 +539,12 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
u = (request.args.get("underlying") or cfg["default_underly"]).upper()
|
||||
from lib.options.options_underly_lib import resolve_default_underly
|
||||
|
||||
u = (request.args.get("underlying") or resolve_default_underly()).upper()
|
||||
blocked = _underly_blocked(u)
|
||||
if blocked:
|
||||
return jsonify({"ok": False, "msg": blocked})
|
||||
# 热更新:链展示天数每次读 env,保存后刷新链即可
|
||||
chain_max_dte = _env_float("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS", float(cfg.get("chain_max_dte_days") or 14))
|
||||
try:
|
||||
@@ -594,6 +611,9 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
inst_id = (request.args.get("inst_id") or "").strip()
|
||||
if not inst_id:
|
||||
return jsonify({"ok": False, "msg": "缺少 inst_id"})
|
||||
blocked = _underly_blocked(inst_id=inst_id)
|
||||
if blocked:
|
||||
return jsonify({"ok": False, "msg": blocked, "can_open": False})
|
||||
q = cfg["quote_option_contract"](ex, inst_id)
|
||||
if not q.get("ok"):
|
||||
return jsonify(q)
|
||||
@@ -817,6 +837,9 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
profit_exit_mult = normalize_profit_exit_mult(data.get("profit_exit_mult"), default=1.0)
|
||||
if not inst_id:
|
||||
return jsonify({"ok": False, "msg": "缺少 inst_id"})
|
||||
blocked = _underly_blocked(inst_id=inst_id)
|
||||
if blocked:
|
||||
return jsonify({"ok": False, "msg": blocked, "can_open": False})
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import is_coin_margin_mode
|
||||
from lib.options.options_coin_open_lib import open_coin_option_buy_full
|
||||
@@ -1006,6 +1029,9 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
|
||||
raw_eth = bal.get("trading_eth_avail") or bal.get("trading_eth")
|
||||
raw_btc = bal.get("trading_btc_avail") or bal.get("trading_btc")
|
||||
from lib.options.options_underly_lib import underly_cfg_snapshot
|
||||
|
||||
_uly_snap = underly_cfg_snapshot()
|
||||
spot_sell_hint = {
|
||||
"bridge_status": str(bridge.get("status") or "") if bridge else None,
|
||||
"bridge_underlying": str(bridge.get("underlying") or "").upper() if bridge else None,
|
||||
@@ -1014,7 +1040,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
"trading_btc": display_sellable_coin(ex, "BTC", raw_btc),
|
||||
"min_sz_eth": spot_min_sell_sz(ex, "ETH"),
|
||||
"min_sz_btc": spot_min_sell_sz(ex, "BTC"),
|
||||
"default_underly": (cfg.get("default_underly") or "ETH").strip().upper(),
|
||||
"default_underly": _uly_snap["default_underly"],
|
||||
"allowed_underlys": _uly_snap["allowed_underlys"],
|
||||
}
|
||||
finally:
|
||||
conn_b.close()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"""期权标的白名单:OKX_OPTIONS_UNDERLYINGS + DEFAULT_UNDERLY."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
_ALLOWED = ("ETH", "BTC")
|
||||
|
||||
|
||||
def parse_options_underlyings() -> list[str]:
|
||||
"""env 白名单;空或缺省为 ETH,BTC.仅认 ETH/BTC."""
|
||||
raw = (os.getenv("OKX_OPTIONS_UNDERLYINGS") or "").strip()
|
||||
if not raw:
|
||||
return ["ETH", "BTC"]
|
||||
out: list[str] = []
|
||||
for part in raw.replace(";", ",").split(","):
|
||||
u = part.strip().upper()
|
||||
if u in _ALLOWED and u not in out:
|
||||
out.append(u)
|
||||
return out or ["ETH", "BTC"]
|
||||
|
||||
|
||||
def resolve_default_underly(allowed: list[str] | None = None) -> str:
|
||||
"""DEFAULT 落在白名单内;否则取白名单第一项."""
|
||||
allow = list(allowed) if allowed is not None else parse_options_underlyings()
|
||||
if not allow:
|
||||
allow = ["ETH", "BTC"]
|
||||
d = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper() or "ETH"
|
||||
if d in allow:
|
||||
return d
|
||||
return allow[0]
|
||||
|
||||
|
||||
def underly_from_inst_id(inst_id: str) -> str:
|
||||
return (str(inst_id or "").split("-")[0] or "").strip().upper()
|
||||
|
||||
|
||||
def underly_not_allowed_msg(underlying: str, allowed: list[str] | None = None) -> str | None:
|
||||
"""白名单外返回拒因;允许则 None."""
|
||||
allow = list(allowed) if allowed is not None else parse_options_underlyings()
|
||||
u = (underlying or "").strip().upper()
|
||||
if u in allow:
|
||||
return None
|
||||
return f"标的 {u or '—'} 不在本机白名单({','.join(allow) or '—'})"
|
||||
|
||||
|
||||
def inst_underly_not_allowed_msg(inst_id: str, allowed: list[str] | None = None) -> str | None:
|
||||
return underly_not_allowed_msg(underly_from_inst_id(inst_id), allowed)
|
||||
|
||||
|
||||
def underly_cfg_snapshot() -> dict[str, Any]:
|
||||
allow = parse_options_underlyings()
|
||||
return {
|
||||
"allowed_underlys": allow,
|
||||
"default_underly": resolve_default_underly(allow),
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{% set _uly_allowed = options_allowed_underlys if options_allowed_underlys is defined and options_allowed_underlys else ['ETH', 'BTC'] %}
|
||||
{% set _uly_default = options_default_underly | default(_uly_allowed[0] if _uly_allowed else 'ETH') %}
|
||||
<div class="options-page-wrap" style="grid-column:1/-1" id="options-root"
|
||||
data-default-underly="{{ options_default_underly | default('ETH') }}"
|
||||
data-default-underly="{{ _uly_default }}"
|
||||
data-allowed-underlys="{{ _uly_allowed | join(',') }}"
|
||||
data-budget-buffer="{{ options_budget_buffer | default(1) }}"
|
||||
data-trade-budget="{{ options_trade_budget | default(10) }}"
|
||||
data-compound-full-enabled="{% if options_compound_full_enabled %}1{% else %}0{% endif %}"
|
||||
@@ -54,8 +57,9 @@
|
||||
</div>
|
||||
</details>
|
||||
<div class="form-row options-chain-toolbar">
|
||||
<button type="button" class="btn-secondary opt-uly-btn active" data-uly="ETH">ETH</button>
|
||||
<button type="button" class="btn-secondary opt-uly-btn" data-uly="BTC">BTC</button>
|
||||
{% for u in _uly_allowed %}
|
||||
<button type="button" class="btn-secondary opt-uly-btn{% if u == _uly_default %} active{% endif %}" data-uly="{{ u }}">{{ u }}</button>
|
||||
{% endfor %}
|
||||
<select id="opt-exp-select"><option value="">选择到期日</option></select>
|
||||
<span class="opt-chain-view-group">
|
||||
<button type="button" class="btn-secondary opt-view-btn active" data-view="list">列表</button>
|
||||
@@ -364,4 +368,4 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||
<script src="/static/options_panel.js?v=71"></script>
|
||||
<script src="/static/options_panel.js?v=72"></script>
|
||||
|
||||
Reference in New Issue
Block a user