Restructure Plan UI for semi-auto tabs and T-quote ladder.
Hide amplitude card when filter off; compact semi form with rules; monitor tab keeps positions and market. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
||||
from ..market import get_gateway
|
||||
from .auth import require_user
|
||||
@@ -18,6 +18,19 @@ async def market_snapshot(_user: Annotated[str, Depends(require_user)]) -> dict:
|
||||
return snap
|
||||
|
||||
|
||||
@router.get("/option-ladder")
|
||||
async def market_option_ladder(
|
||||
_user: Annotated[str, Depends(require_user)],
|
||||
wings: int = Query(default=4, ge=1, le=12),
|
||||
) -> dict:
|
||||
"""半自动页 T 型报价:ATM 上下各 wings 档。"""
|
||||
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)
|
||||
|
||||
|
||||
@router.post("/realign")
|
||||
async def market_realign(_user: Annotated[str, Depends(require_user)]) -> dict:
|
||||
"""手动重对齐次日到期 ATM 合约(运维/调试用)。"""
|
||||
|
||||
@@ -1058,6 +1058,99 @@ class StrategySession:
|
||||
d["ask_compare"] = ac
|
||||
return d
|
||||
|
||||
def option_ladder(self, *, wings: int = 4) -> dict[str, Any]:
|
||||
"""
|
||||
T 型报价:当前监控到期附近若干档 Call/Put 盘口。
|
||||
行:虚值 Call(上行)/ ATM / 虚值 Put(下行);列:Call 卖一·杠杆 | 行权价 | Put 卖一·杠杆。
|
||||
"""
|
||||
s = self.settings
|
||||
wings = max(1, min(12, int(wings)))
|
||||
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}
|
||||
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]
|
||||
if not ymd:
|
||||
return {
|
||||
"ok": False,
|
||||
"detail": "无合格到期",
|
||||
"rows": [],
|
||||
"index_px": underlying,
|
||||
}
|
||||
complete = _complete_by_expiry(contracts)
|
||||
if ymd not in complete:
|
||||
return {
|
||||
"ok": False,
|
||||
"detail": f"到期 {ymd} 无完整对",
|
||||
"rows": [],
|
||||
"index_px": underlying,
|
||||
"expiry_ymd": ymd,
|
||||
}
|
||||
_ems, strikes_map = complete[ymd]
|
||||
strikes = sorted(float(k) for k in strikes_map.keys())
|
||||
atm = pick_atm_strike(strikes, underlying)
|
||||
if atm is None:
|
||||
return {
|
||||
"ok": False,
|
||||
"detail": "无 ATM",
|
||||
"rows": [],
|
||||
"index_px": underlying,
|
||||
"expiry_ymd": ymd,
|
||||
}
|
||||
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)
|
||||
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
|
||||
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 实值
|
||||
else:
|
||||
tag = "otm_put" # Put 虚值 / Call 实值
|
||||
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,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"detail": "",
|
||||
"index_px": underlying,
|
||||
"expiry_ymd": ymd,
|
||||
"atm_strike": float(atm),
|
||||
"rows": rows,
|
||||
}
|
||||
|
||||
async def _refresh_loop(self) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(30 if self._pair is not None else 10)
|
||||
|
||||
Reference in New Issue
Block a user