Drive semi quote ladder by min remaining hours input.
Picks nearest expiry with hours >= threshold and refreshes the list as the field changes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,13 +23,14 @@ async def market_option_ladder(
|
||||
_user: Annotated[str, Depends(require_user)],
|
||||
wings: int = Query(default=5, ge=1, le=12),
|
||||
side: str = Query(default="call", pattern="^(call|put)$"),
|
||||
min_hours: float = Query(default=30, ge=1, le=720),
|
||||
) -> dict:
|
||||
"""半自动页单边报价列表:ATM 上下各 wings 档;side=call|put。"""
|
||||
"""半自动页单边报价:选剩余时长≥min_hours 的最近到期。"""
|
||||
gw = get_gateway()
|
||||
ladder = getattr(gw, "option_ladder", None)
|
||||
if not callable(ladder):
|
||||
raise HTTPException(status_code=501, detail="当前会话不支持 option-ladder")
|
||||
return ladder(wings=wings, side=side)
|
||||
return ladder(wings=wings, side=side, min_hours=min_hours)
|
||||
|
||||
|
||||
@router.post("/realign")
|
||||
|
||||
@@ -1058,13 +1058,20 @@ class StrategySession:
|
||||
d["ask_compare"] = ac
|
||||
return d
|
||||
|
||||
def option_ladder(self, *, wings: int = 5, side: str = "call") -> dict[str, Any]:
|
||||
def option_ladder(
|
||||
self,
|
||||
*,
|
||||
wings: int = 5,
|
||||
side: str = "call",
|
||||
min_hours: float = 30.0,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
半自动单边报价:ATM 上下若干档。
|
||||
side=call(看多)或 put(看空);每档含卖一/流动性/杠杆与实值|平值|虚值。
|
||||
到期:剩余时长 ≥ min_hours 的最近一档(与选约门槛一致)。
|
||||
"""
|
||||
s = self.settings
|
||||
wings = max(1, min(12, int(wings)))
|
||||
min_h = max(1.0, float(min_hours))
|
||||
opt_side = "put" if str(side).strip().lower() == "put" else "call"
|
||||
idx = self.ex.fetch_index(s.index_inst_id)
|
||||
mark = self.ex.fetch_mark(s.perp_inst_id) or idx
|
||||
@@ -1075,24 +1082,23 @@ class StrategySession:
|
||||
"rows": [],
|
||||
"index_px": None,
|
||||
"side": opt_side,
|
||||
"min_hours": min_h,
|
||||
}
|
||||
underlying = float(mark)
|
||||
contracts = self.ex.list_option_contracts(s.option_inst_family)
|
||||
from .selection import _complete_by_expiry, pick_atm_strike
|
||||
|
||||
eligible = list_eligible_expiry_ymds(contracts, min_hours=1.0)
|
||||
ymd = None
|
||||
if self._pair is not None and self._pair.expiry_ymd:
|
||||
ymd = str(self._pair.expiry_ymd)
|
||||
if not ymd and eligible:
|
||||
ymd = eligible[0]
|
||||
# 与半自动选约一致:只考虑剩余 ≥ min_hours 的到期,取最近一档
|
||||
eligible = list_eligible_expiry_ymds(contracts, min_hours=min_h)
|
||||
ymd = eligible[0] if eligible else None
|
||||
if not ymd:
|
||||
return {
|
||||
"ok": False,
|
||||
"detail": "无合格到期",
|
||||
"detail": f"无剩余≥{min_h:g}h 的到期",
|
||||
"rows": [],
|
||||
"index_px": underlying,
|
||||
"side": opt_side,
|
||||
"min_hours": min_h,
|
||||
}
|
||||
complete = _complete_by_expiry(contracts)
|
||||
if ymd not in complete:
|
||||
@@ -1103,8 +1109,10 @@ class StrategySession:
|
||||
"index_px": underlying,
|
||||
"expiry_ymd": ymd,
|
||||
"side": opt_side,
|
||||
"min_hours": min_h,
|
||||
}
|
||||
_ems, strikes_map = complete[ymd]
|
||||
hours_left = hours_until_expiry(ymd, expiry_ms=_ems)
|
||||
strikes = sorted(float(k) for k in strikes_map.keys())
|
||||
atm = pick_atm_strike(strikes, underlying)
|
||||
if atm is None:
|
||||
@@ -1172,6 +1180,8 @@ class StrategySession:
|
||||
"side": opt_side,
|
||||
"index_px": underlying,
|
||||
"expiry_ymd": ymd,
|
||||
"hours_left": round(float(hours_left), 1) if hours_left is not None else None,
|
||||
"min_hours": min_h,
|
||||
"atm_strike": float(atm),
|
||||
"rows": rows,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user