Keep OO amplitude refreshed while holding a position.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-07 17:30:51 +08:00
parent 0ac03cc726
commit 43a2a9634d
3 changed files with 88 additions and 23 deletions
+25 -2
View File
@@ -2011,9 +2011,19 @@ class Matcher:
if index_px is None and snap.perp:
index_px = snap.perp.mark_px
qty = float(pos.get("option_qty_eth") or 0)
qty2 = float(pos.get("option2_qty_eth") or qty)
raw_q2 = pos.get("option2_qty_eth")
try:
qty2 = float(raw_q2) if raw_q2 is not None else 0.0
except (TypeError, ValueError):
qty2 = 0.0
if qty2 <= 0:
qty2 = qty
contracts1 = float(pos.get("option_qty_contracts") or 0)
contracts2 = float(pos.get("option2_qty_contracts") or 0)
raw_c2 = pos.get("option2_qty_contracts")
try:
contracts2 = float(raw_c2) if raw_c2 is not None else 0.0
except (TypeError, ValueError):
contracts2 = 0.0
prem1 = float(pos.get("initial_premium") or 0)
prem2 = float(pos.get("initial_premium2") or 0)
call_id = str(pos.get("option_inst_id") or "")
@@ -2022,6 +2032,19 @@ class Matcher:
entry2 = float(pos.get("option2_entry_px") or 0)
oq1 = self._quote_held_option(call_id) if call_id else None
oq2 = self._quote_held_option(put_id) if put_id else None
# 盘口缓存未订阅到 Put 时,回退 session 监控腿(与「持仓虚值」同数据源)
if (oq2 is None or (oq2.bid is None and oq2.mark_px is None)) and put_id:
try:
if snap.put and str(getattr(snap.put, "inst_id", "") or "") == put_id:
oq2 = snap.put
except Exception:
pass
if (oq1 is None or (oq1.bid is None and oq1.mark_px is None)) and call_id:
try:
if snap.call and str(getattr(snap.call, "inst_id", "") or "") == call_id:
oq1 = snap.call
except Exception:
pass
option_upl = 0.0
option2_upl = 0.0
fees = 0.0
+47 -16
View File
@@ -332,6 +332,8 @@ class StrategySession:
def align_to_held_position(self) -> OptionPair | None:
"""有活跃仓时:监控对锁定为持仓合约的到期/行权价。"""
if _hedge_mode() == "option_option":
self.refresh_oo_amplitude()
call_id, put_id = _held_option_legs()
held = call_id or _held_option_inst_id()
if not held:
@@ -441,11 +443,37 @@ class StrategySession:
)
return self._apply_pair(pair, mark=float(mark), idx=idx)
def refresh_oo_amplitude(self) -> dict[str, Any] | None:
"""刷新振幅高低(有仓/无仓都要,否则持仓后 UI 指数/振幅会空)。"""
if _hedge_mode() != "option_option":
return None
try:
from ..exchange.candles import fetch_amplitude_hl_for_runtime
amp_pct, amp_hours, _, _ = _oo_settings()
amp = fetch_amplitude_hl_for_runtime(amp_hours)
if amp is None:
return self._oo_amp
self._oo_amp = {
"high": float(amp.high),
"low": float(amp.low),
"mid": float(amp.mid),
"range_pct": float(amp.range_pct),
"hours": float(amp_hours),
"max_pct": float(amp_pct),
"ok": float(amp.range_pct) <= float(amp_pct) + 1e-12,
}
return self._oo_amp
except Exception:
logger.exception("refresh_oo_amplitude failed")
return self._oo_amp
def align_oo_instruments(self) -> OptionPair | None:
"""期期监控:按振幅高低点选虚值 Call/Put(展示用;振幅超限仍对齐候选)。"""
from ..exchange.candles import fetch_amplitude_hl_for_runtime
from .oo_selection import select_oo_pair
# 有仓也刷新振幅(钉仓不再走选约,否则 _oo_amp 一直空)
self.refresh_oo_amplitude()
if _has_open_position():
return self.align_to_held_position()
s = self.settings
@@ -455,26 +483,17 @@ class StrategySession:
if mark is None or mark <= 0:
raise RuntimeError("无法获取标的标记/指数价格,无法选期期虚值")
underlying = float(mark)
amp = fetch_amplitude_hl_for_runtime(amp_hours)
if amp is None:
self._oo_amp = None
if self._oo_amp is None:
raise RuntimeError("无法获取振幅 K 线高低点")
self._oo_amp = {
"high": float(amp.high),
"low": float(amp.low),
"mid": float(amp.mid),
"range_pct": float(amp.range_pct),
"hours": float(amp_hours),
"max_pct": float(amp_pct),
"ok": float(amp.range_pct) <= float(amp_pct) + 1e-12,
}
amp_high = float(self._oo_amp["high"])
amp_low = float(self._oo_amp["low"])
contracts = self.ex.list_option_contracts(s.option_inst_family)
skip = _skip_expiry_ymds_for_next()
picked = select_oo_pair(
contracts,
spot=underlying,
high=float(amp.high),
low=float(amp.low),
high=amp_high,
low=amp_low,
min_hours=float(min_hours),
skip_expiry_ymds=skip,
)
@@ -518,6 +537,16 @@ class StrategySession:
if amp is None:
logger.info("oo: amplitude candles unavailable")
return None
# 无论是否超限都写入,供「指数/振幅」面板展示
self._oo_amp = {
"high": float(amp.high),
"low": float(amp.low),
"mid": float(amp.mid),
"range_pct": float(amp.range_pct),
"hours": float(amp_hours),
"max_pct": float(amp_pct),
"ok": float(amp.range_pct) <= float(amp_pct) + 1e-12,
}
if float(amp.range_pct) > float(amp_pct) + 1e-12:
logger.info(
"oo: amplitude %.3f%% > max %.3f%% (H=%.2f L=%.2f)",
@@ -862,7 +891,9 @@ class StrategySession:
async def ensure_atm_async(self, *, force: bool = False) -> OptionPair | None:
if _has_open_position():
# 持仓期间:钉住持仓行权价(禁止漂到新 ATM/虚值)
# 持仓期间:钉住持仓行权价(禁止漂到新 ATM/虚值);期期仍刷新振幅供 UI
if _hedge_mode() == "option_option":
await asyncio.to_thread(self.refresh_oo_amplitude)
call_id, put_id = _held_option_legs()
held = call_id or _held_option_inst_id()
if held and (
+16 -5
View File
@@ -766,14 +766,25 @@ export default function PlanPage() {
: "—"}
</span>
<span className="pos-meta-item mono">
{fmt(pos?.option2_qty_eth, 2)} ETH ·{" "}
{fmt(pos?.option2_qty_contracts, 0)}
{fmt(
pos?.option2_qty_eth ??
plan?.oo_put_qty_eth ??
plan?.risk_sizing_preview?.put_qty_eth,
2,
)}{" "}
ETH · {fmt(pos?.option2_qty_contracts, 0)}
</span>
<span
className="pos-meta-item mono"
title="开仓指数÷开仓均价"
>
{fmt(pos?.option2_leverage, 0)}x
{" "}
{pos?.option2_leverage != null
? `${fmt(pos.option2_leverage, 0)}x`
: optionLevLabel(
pos?.entry_index_px ?? snap?.index_px,
pos?.option2_entry_px,
)}
</span>
</div>
<div className="pos-grid">
@@ -788,8 +799,8 @@ export default function PlanPage() {
<span className="pos-value mono">
{fmtBidLiqEx(
"option",
pos?.option2_mark_px,
pos?.option2_bid_sz,
pos?.option2_mark_px ?? snap?.put?.bid,
pos?.option2_bid_sz ?? snap?.put?.bid_sz,
)}
</span>
</div>