fix(hedge): filter options-chain by min hours and strike interval for option-primary
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -59,7 +59,7 @@ contracts = perp_eth / contract_size
|
||||
| `lib/hedge_plan/hedge_plan_option_primary_lib.py` | 定仓/方向/目标/净利/校验 |
|
||||
| `hedge_plan_orders_lib.py` | 路径、开平永续、启动前定仓刷新 |
|
||||
| `hedge_plan_monitor_lib.py` | `_tick_po_option_primary*` |
|
||||
| `hedge_plan_register.py` / `hedge_plan_db.py` | preview/start/persist 列 |
|
||||
| `hedge_plan_register.py` / `hedge_plan_db.py` | preview/start/persist;`options-chain?option_primary&min_hours&strike_interval` |
|
||||
| `hedge_plan.js` + `hedge_plan_panel.html` | 开关与左右卡 |
|
||||
|
||||
## 6. 测试
|
||||
|
||||
@@ -1036,9 +1036,16 @@
|
||||
}
|
||||
|
||||
async function loadChain() {
|
||||
const d = await apiJson(
|
||||
"/api/hedge-plan/options-chain?underlying=" + encodeURIComponent(state.underlying)
|
||||
);
|
||||
let url =
|
||||
"/api/hedge-plan/options-chain?underlying=" + encodeURIComponent(state.underlying);
|
||||
if (isOptionPrimary()) {
|
||||
url +=
|
||||
"&option_primary=1&min_hours=" +
|
||||
encodeURIComponent(String(numInput("hp-min-hours", 36))) +
|
||||
"&strike_interval=" +
|
||||
encodeURIComponent(String(numInput("hp-strike-interval", 15)));
|
||||
}
|
||||
const d = await apiJson(url);
|
||||
state.chain = d;
|
||||
const idx = $("hp-index-line");
|
||||
if (idx) idx.textContent = "指数 " + fmt(d.index_px, 2);
|
||||
|
||||
@@ -559,6 +559,57 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
)
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "msg": f"拉取期权链失败: {e}"}), 500
|
||||
# 以期权为主:可选最短剩余小时 / 行权间隔过滤(也回写 hours_to_expiry 供前端)
|
||||
option_primary = (request.args.get("option_primary") or "").strip().lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"on",
|
||||
)
|
||||
min_hours = None
|
||||
strike_interval = None
|
||||
try:
|
||||
if request.args.get("min_hours") not in (None, ""):
|
||||
min_hours = float(request.args.get("min_hours"))
|
||||
except (TypeError, ValueError):
|
||||
min_hours = 36.0 if option_primary else None
|
||||
try:
|
||||
if request.args.get("strike_interval") not in (None, ""):
|
||||
strike_interval = float(request.args.get("strike_interval"))
|
||||
except (TypeError, ValueError):
|
||||
strike_interval = 15.0 if option_primary else None
|
||||
if option_primary and min_hours is None:
|
||||
min_hours = 36.0
|
||||
if option_primary and strike_interval is None:
|
||||
strike_interval = 15.0
|
||||
if min_hours is not None or strike_interval is not None:
|
||||
from lib.hedge_plan.hedge_plan_option_primary_lib import hours_to_expiry_from_ms
|
||||
|
||||
idx = None
|
||||
try:
|
||||
idx = float(chain.get("index_px") or 0) or None
|
||||
except (TypeError, ValueError):
|
||||
idx = None
|
||||
filtered = []
|
||||
for exp in chain.get("expiries") or []:
|
||||
h = hours_to_expiry_from_ms(exp.get("exp_time"))
|
||||
if min_hours is not None and h is not None and h < min_hours:
|
||||
continue
|
||||
contracts = []
|
||||
for c in exp.get("contracts") or []:
|
||||
row = dict(c)
|
||||
row["hours_to_expiry"] = h
|
||||
if strike_interval is not None and idx and idx > 0:
|
||||
try:
|
||||
k = float(row.get("strike") or 0)
|
||||
except (TypeError, ValueError):
|
||||
k = 0.0
|
||||
if k > 0 and abs(k - idx) > strike_interval + 1e-9:
|
||||
continue
|
||||
contracts.append(row)
|
||||
if contracts:
|
||||
filtered.append({**exp, "contracts": contracts, "hours_to_expiry": h})
|
||||
chain = {**chain, "expiries": filtered}
|
||||
opt_acct = _options_account_snapshot(cfg)
|
||||
return jsonify(
|
||||
{
|
||||
@@ -572,6 +623,9 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
"options_account": opt_acct,
|
||||
"trade_budget_usdc": cfg.get("trade_budget_usdc"),
|
||||
"budget_buffer": cfg.get("budget_buffer"),
|
||||
"option_primary": option_primary,
|
||||
"min_hours": min_hours,
|
||||
"strike_interval": strike_interval,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -368,4 +368,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/hedge_plan.js?v=37"></script>
|
||||
<script src="/static/hedge_plan.js?v=38"></script>
|
||||
|
||||
@@ -139,6 +139,14 @@ class TestOptionPrimary(unittest.TestCase):
|
||||
self.assertTrue(out["option_primary"])
|
||||
self.assertEqual(len(out["scenarios"]), 2)
|
||||
|
||||
def test_hours_to_expiry_from_ms(self):
|
||||
from lib.hedge_plan.hedge_plan_option_primary_lib import hours_to_expiry_from_ms
|
||||
|
||||
now = 1_700_000_000_000.0
|
||||
# +40h in ms
|
||||
h = hours_to_expiry_from_ms(now + 40 * 3600 * 1000, now_ms=now)
|
||||
self.assertAlmostEqual(h, 40.0, places=3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user