Fix semi moneyness reset and show Call/Put ladder by view.

Use dirty ref so refresh cannot overwrite unsaved params; ladder fetches books for the selected side.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-08 14:13:37 +08:00
parent d98bf46549
commit 58c12aa160
4 changed files with 149 additions and 107 deletions
+4 -3
View File
@@ -21,14 +21,15 @@ async def market_snapshot(_user: Annotated[str, Depends(require_user)]) -> dict:
@router.get("/option-ladder")
async def market_option_ladder(
_user: Annotated[str, Depends(require_user)],
wings: int = Query(default=4, ge=1, le=12),
wings: int = Query(default=5, ge=1, le=12),
side: str = Query(default="call", pattern="^(call|put)$"),
) -> dict:
"""半自动页 T 型报价ATM 上下各 wings 档。"""
"""半自动页单边报价列表ATM 上下各 wings 档side=call|put"""
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)
return ladder(wings=wings, side=side)
@router.post("/realign")
+48 -23
View File
@@ -1058,17 +1058,24 @@ class StrategySession:
d["ask_compare"] = ac
return d
def option_ladder(self, *, wings: int = 4) -> dict[str, Any]:
def option_ladder(self, *, wings: int = 5, side: str = "call") -> dict[str, Any]:
"""
T 型报价:当前监控到期附近若干档 Call/Put 盘口
行:虚值 Call(上行)/ ATM / 虚值 Put(下行);列:Call 卖一·杠杆 | 行权价 | Put 卖一·杠杆
半自动单边报价:ATM 上下若干档
side=call(看多)或 put(看空);每档含卖一/流动性/杠杆与实值|平值|虚值
"""
s = self.settings
wings = max(1, min(12, int(wings)))
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
if mark is None or float(mark) <= 0:
return {"ok": False, "detail": "无标的价", "rows": [], "index_px": None}
return {
"ok": False,
"detail": "无标的价",
"rows": [],
"index_px": None,
"side": opt_side,
}
underlying = float(mark)
contracts = self.ex.list_option_contracts(s.option_inst_family)
from .selection import _complete_by_expiry, pick_atm_strike
@@ -1085,6 +1092,7 @@ class StrategySession:
"detail": "无合格到期",
"rows": [],
"index_px": underlying,
"side": opt_side,
}
complete = _complete_by_expiry(contracts)
if ymd not in complete:
@@ -1094,6 +1102,7 @@ class StrategySession:
"rows": [],
"index_px": underlying,
"expiry_ymd": ymd,
"side": opt_side,
}
_ems, strikes_map = complete[ymd]
strikes = sorted(float(k) for k in strikes_map.keys())
@@ -1105,46 +1114,62 @@ class StrategySession:
"rows": [],
"index_px": underlying,
"expiry_ymd": ymd,
"side": opt_side,
}
atm_i = min(range(len(strikes)), key=lambda i: abs(strikes[i] - float(atm)))
lo = max(0, atm_i - wings)
hi = min(len(strikes), atm_i + wings + 1)
def _quote_side(inst_id: str | None) -> tuple[float | None, float | None]:
if not inst_id:
return None, None
q = self.ex.quote(str(inst_id))
ask = float(q.ask) if q and q.ask is not None else None
ask_sz = float(q.ask_sz) if q and q.ask_sz is not None else None
if ask is not None:
return ask, ask_sz
try:
_bids, asks, _ = self.ex.fetch_book(str(inst_id), depth=1)
if asks:
return float(asks[0].px), (
float(asks[0].sz) if asks[0].sz is not None else None
)
except Exception:
logger.debug("ladder fetch_book failed inst=%s", inst_id, exc_info=True)
return None, None
rows: list[dict[str, Any]] = []
for k in strikes[lo:hi]:
legs = strikes_map[k]
call_id = legs.get("C")
put_id = legs.get("P")
cq = self.ex.quote(str(call_id)) if call_id else None
pq = self.ex.quote(str(put_id)) if put_id else None
c_ask = float(cq.ask) if cq and cq.ask is not None else None
p_ask = float(pq.ask) if pq and pq.ask is not None else None
c_bid = float(cq.bid) if cq and cq.bid is not None else None
p_bid = float(pq.bid) if pq and pq.bid is not None else None
inst = legs.get("C" if opt_side == "call" else "P")
ask, ask_sz = _quote_side(inst)
off = float(k) - underlying
if abs(float(k) - float(atm)) < 1e-9:
tag = "atm"
elif float(k) > underlying + 1e-9:
tag = "otm_call" # Call 虚值 / Put 实值
elif opt_side == "call":
tag = "itm" if float(k) < underlying - 1e-9 else "otm"
else:
tag = "otm_put" # Put 虚值 / Call 实值
tag = "itm" if float(k) > underlying + 1e-9 else "otm"
rows.append(
{
"strike": float(k),
"offset": round(off, 2),
"tag": tag,
"call_ask": c_ask,
"call_bid": c_bid,
"call_lev": option_leverage(underlying, c_ask),
"put_ask": p_ask,
"put_bid": p_bid,
"put_lev": option_leverage(underlying, p_ask),
"call_inst_id": call_id,
"put_inst_id": put_id,
"ask": ask,
"ask_sz": ask_sz,
"lev": option_leverage(underlying, ask) if ask else None,
"inst_id": inst,
}
)
# Call:高行权价在上(虚值在上);Put:低行权价在上(虚值在上)
if opt_side == "call":
rows.sort(key=lambda r: -float(r["strike"]))
else:
rows.sort(key=lambda r: float(r["strike"]))
return {
"ok": True,
"detail": "",
"side": opt_side,
"index_px": underlying,
"expiry_ymd": ymd,
"atm_strike": float(atm),