Keep OO amplitude refreshed while holding a position.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2011,9 +2011,19 @@ class Matcher:
|
|||||||
if index_px is None and snap.perp:
|
if index_px is None and snap.perp:
|
||||||
index_px = snap.perp.mark_px
|
index_px = snap.perp.mark_px
|
||||||
qty = float(pos.get("option_qty_eth") or 0)
|
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)
|
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)
|
prem1 = float(pos.get("initial_premium") or 0)
|
||||||
prem2 = float(pos.get("initial_premium2") or 0)
|
prem2 = float(pos.get("initial_premium2") or 0)
|
||||||
call_id = str(pos.get("option_inst_id") or "")
|
call_id = str(pos.get("option_inst_id") or "")
|
||||||
@@ -2022,6 +2032,19 @@ class Matcher:
|
|||||||
entry2 = float(pos.get("option2_entry_px") or 0)
|
entry2 = float(pos.get("option2_entry_px") or 0)
|
||||||
oq1 = self._quote_held_option(call_id) if call_id else None
|
oq1 = self._quote_held_option(call_id) if call_id else None
|
||||||
oq2 = self._quote_held_option(put_id) if put_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
|
option_upl = 0.0
|
||||||
option2_upl = 0.0
|
option2_upl = 0.0
|
||||||
fees = 0.0
|
fees = 0.0
|
||||||
|
|||||||
@@ -332,6 +332,8 @@ class StrategySession:
|
|||||||
|
|
||||||
def align_to_held_position(self) -> OptionPair | None:
|
def align_to_held_position(self) -> OptionPair | None:
|
||||||
"""有活跃仓时:监控对锁定为持仓合约的到期/行权价。"""
|
"""有活跃仓时:监控对锁定为持仓合约的到期/行权价。"""
|
||||||
|
if _hedge_mode() == "option_option":
|
||||||
|
self.refresh_oo_amplitude()
|
||||||
call_id, put_id = _held_option_legs()
|
call_id, put_id = _held_option_legs()
|
||||||
held = call_id or _held_option_inst_id()
|
held = call_id or _held_option_inst_id()
|
||||||
if not held:
|
if not held:
|
||||||
@@ -441,11 +443,37 @@ class StrategySession:
|
|||||||
)
|
)
|
||||||
return self._apply_pair(pair, mark=float(mark), idx=idx)
|
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:
|
def align_oo_instruments(self) -> OptionPair | None:
|
||||||
"""期期监控:按振幅高低点选虚值 Call/Put(展示用;振幅超限仍对齐候选)。"""
|
"""期期监控:按振幅高低点选虚值 Call/Put(展示用;振幅超限仍对齐候选)。"""
|
||||||
from ..exchange.candles import fetch_amplitude_hl_for_runtime
|
|
||||||
from .oo_selection import select_oo_pair
|
from .oo_selection import select_oo_pair
|
||||||
|
|
||||||
|
# 有仓也刷新振幅(钉仓不再走选约,否则 _oo_amp 一直空)
|
||||||
|
self.refresh_oo_amplitude()
|
||||||
if _has_open_position():
|
if _has_open_position():
|
||||||
return self.align_to_held_position()
|
return self.align_to_held_position()
|
||||||
s = self.settings
|
s = self.settings
|
||||||
@@ -455,26 +483,17 @@ class StrategySession:
|
|||||||
if mark is None or mark <= 0:
|
if mark is None or mark <= 0:
|
||||||
raise RuntimeError("无法获取标的标记/指数价格,无法选期期虚值")
|
raise RuntimeError("无法获取标的标记/指数价格,无法选期期虚值")
|
||||||
underlying = float(mark)
|
underlying = float(mark)
|
||||||
amp = fetch_amplitude_hl_for_runtime(amp_hours)
|
if self._oo_amp is None:
|
||||||
if amp is None:
|
|
||||||
self._oo_amp = None
|
|
||||||
raise RuntimeError("无法获取振幅 K 线高低点")
|
raise RuntimeError("无法获取振幅 K 线高低点")
|
||||||
self._oo_amp = {
|
amp_high = float(self._oo_amp["high"])
|
||||||
"high": float(amp.high),
|
amp_low = float(self._oo_amp["low"])
|
||||||
"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,
|
|
||||||
}
|
|
||||||
contracts = self.ex.list_option_contracts(s.option_inst_family)
|
contracts = self.ex.list_option_contracts(s.option_inst_family)
|
||||||
skip = _skip_expiry_ymds_for_next()
|
skip = _skip_expiry_ymds_for_next()
|
||||||
picked = select_oo_pair(
|
picked = select_oo_pair(
|
||||||
contracts,
|
contracts,
|
||||||
spot=underlying,
|
spot=underlying,
|
||||||
high=float(amp.high),
|
high=amp_high,
|
||||||
low=float(amp.low),
|
low=amp_low,
|
||||||
min_hours=float(min_hours),
|
min_hours=float(min_hours),
|
||||||
skip_expiry_ymds=skip,
|
skip_expiry_ymds=skip,
|
||||||
)
|
)
|
||||||
@@ -518,6 +537,16 @@ class StrategySession:
|
|||||||
if amp is None:
|
if amp is None:
|
||||||
logger.info("oo: amplitude candles unavailable")
|
logger.info("oo: amplitude candles unavailable")
|
||||||
return None
|
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:
|
if float(amp.range_pct) > float(amp_pct) + 1e-12:
|
||||||
logger.info(
|
logger.info(
|
||||||
"oo: amplitude %.3f%% > max %.3f%% (H=%.2f L=%.2f)",
|
"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:
|
async def ensure_atm_async(self, *, force: bool = False) -> OptionPair | None:
|
||||||
if _has_open_position():
|
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()
|
call_id, put_id = _held_option_legs()
|
||||||
held = call_id or _held_option_inst_id()
|
held = call_id or _held_option_inst_id()
|
||||||
if held and (
|
if held and (
|
||||||
|
|||||||
@@ -766,14 +766,25 @@ export default function PlanPage() {
|
|||||||
: "—"}
|
: "—"}
|
||||||
</span>
|
</span>
|
||||||
<span className="pos-meta-item mono">
|
<span className="pos-meta-item mono">
|
||||||
{fmt(pos?.option2_qty_eth, 2)} ETH ·{" "}
|
{fmt(
|
||||||
{fmt(pos?.option2_qty_contracts, 0)} 张
|
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>
|
||||||
<span
|
<span
|
||||||
className="pos-meta-item mono"
|
className="pos-meta-item mono"
|
||||||
title="开仓指数÷开仓均价"
|
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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="pos-grid">
|
<div className="pos-grid">
|
||||||
@@ -788,8 +799,8 @@ export default function PlanPage() {
|
|||||||
<span className="pos-value mono">
|
<span className="pos-value mono">
|
||||||
{fmtBidLiqEx(
|
{fmtBidLiqEx(
|
||||||
"option",
|
"option",
|
||||||
pos?.option2_mark_px,
|
pos?.option2_mark_px ?? snap?.put?.bid,
|
||||||
pos?.option2_bid_sz,
|
pos?.option2_bid_sz ?? snap?.put?.bid_sz,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user