Show residual bid as price/size and recovery percent from bid premium.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -909,7 +909,7 @@ class Matcher:
|
|||||||
return (None, close_bid, oq)
|
return (None, close_bid, oq)
|
||||||
|
|
||||||
def list_residual_options_enriched(self) -> list[dict[str, Any]]:
|
def list_residual_options_enriched(self) -> list[dict[str, Any]]:
|
||||||
"""pending 残留 + 买一权利金/回收占比/流动性是否可手动平。"""
|
"""pending 残留 + 买一价/量、买一权利金、回收占比、流动性是否可手动平。"""
|
||||||
out: list[dict[str, Any]] = []
|
out: list[dict[str, Any]] = []
|
||||||
for row in self.list_residual_options(pending_only=True):
|
for row in self.list_residual_options(pending_only=True):
|
||||||
d = dict(row)
|
d = dict(row)
|
||||||
@@ -918,6 +918,17 @@ class Matcher:
|
|||||||
init = float(d.get("initial_premium") or 0)
|
init = float(d.get("initial_premium") or 0)
|
||||||
oq = self._quote_held_option(option_inst_id) if option_inst_id else None
|
oq = self._quote_held_option(option_inst_id) if option_inst_id else None
|
||||||
bid = float(oq.bid) if oq is not None and oq.bid is not None else None
|
bid = float(oq.bid) if oq is not None and oq.bid is not None else None
|
||||||
|
bid_sz = None
|
||||||
|
bid_sz_eth = None
|
||||||
|
if oq is not None and getattr(oq, "bid_sz", None) is not None:
|
||||||
|
try:
|
||||||
|
bid_sz = float(oq.bid_sz)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
bid_sz = None
|
||||||
|
if bid_sz is not None and bid_sz >= 0:
|
||||||
|
ct = self._ct_mult(option_inst_id) if option_inst_id else 0.01
|
||||||
|
bid_sz_eth = eth_from_contracts(bid_sz, ct)
|
||||||
|
# 权利金 = 最新买一价 × 持仓数量;占比 = 买一权利金 / 开仓权利金
|
||||||
cur = float(bid) * opt_qty if bid is not None else None
|
cur = float(bid) * opt_qty if bid is not None else None
|
||||||
ratio = (cur / init * 100.0) if cur is not None and init > 1e-12 else None
|
ratio = (cur / init * 100.0) if cur is not None and init > 1e-12 else None
|
||||||
liq_detail: str | None
|
liq_detail: str | None
|
||||||
@@ -930,6 +941,8 @@ class Matcher:
|
|||||||
d.update(
|
d.update(
|
||||||
{
|
{
|
||||||
"bid_px": bid,
|
"bid_px": bid,
|
||||||
|
"bid_sz": bid_sz,
|
||||||
|
"bid_sz_eth": bid_sz_eth,
|
||||||
"current_premium": cur,
|
"current_premium": cur,
|
||||||
"recovery_pct": ratio,
|
"recovery_pct": ratio,
|
||||||
"liquidity_ok": liq_detail is None,
|
"liquidity_ok": liq_detail is None,
|
||||||
|
|||||||
@@ -206,7 +206,11 @@ def test_manual_close_skips_premium_ratio(tmp_path, monkeypatch) -> None:
|
|||||||
enriched = m.list_residual_options_enriched()
|
enriched = m.list_residual_options_enriched()
|
||||||
assert len(enriched) == 1
|
assert len(enriched) == 1
|
||||||
assert enriched[0]["liquidity_ok"] is True
|
assert enriched[0]["liquidity_ok"] is True
|
||||||
assert enriched[0]["recovery_pct"] is not None
|
assert enriched[0]["bid_px"] == 5.0
|
||||||
|
assert enriched[0]["bid_sz"] == 10_000.0
|
||||||
|
assert abs(float(enriched[0]["bid_sz_eth"]) - 100.0) < 1e-9 # 10000*0.01
|
||||||
|
assert abs(float(enriched[0]["current_premium"]) - 10.0) < 1e-9 # 5*2
|
||||||
|
assert abs(float(enriched[0]["recovery_pct"]) - 10.0) < 1e-9 # 10/100*100
|
||||||
assert float(enriched[0]["recovery_pct"]) < 20.0
|
assert float(enriched[0]["recovery_pct"]) < 20.0
|
||||||
|
|
||||||
r = m.close_residual_manual("G-res")
|
r = m.close_residual_manual("G-res")
|
||||||
|
|||||||
@@ -85,11 +85,22 @@ type ResidualRow = {
|
|||||||
option_qty_eth: number | null;
|
option_qty_eth: number | null;
|
||||||
initial_premium: number | null;
|
initial_premium: number | null;
|
||||||
bid_px: number | null;
|
bid_px: number | null;
|
||||||
|
bid_sz_eth: number | null;
|
||||||
current_premium: number | null;
|
current_premium: number | null;
|
||||||
recovery_pct: number | null;
|
recovery_pct: number | null;
|
||||||
liquidity_ok: boolean;
|
liquidity_ok: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function fmtBidLiquidity(
|
||||||
|
bidPx: number | null,
|
||||||
|
bidSzEth: number | null,
|
||||||
|
): string {
|
||||||
|
if (bidPx == null && bidSzEth == null) return "—";
|
||||||
|
const px = bidPx == null ? "—" : fmtExPx("option", bidPx);
|
||||||
|
const sz = bidSzEth == null ? "—" : fmt(bidSzEth, 2);
|
||||||
|
return `${px} / ${sz}`;
|
||||||
|
}
|
||||||
|
|
||||||
function collectResiduals(nodes: NodeCard[]): ResidualRow[] {
|
function collectResiduals(nodes: NodeCard[]): ResidualRow[] {
|
||||||
const out: ResidualRow[] = [];
|
const out: ResidualRow[] = [];
|
||||||
for (const n of nodes) {
|
for (const n of nodes) {
|
||||||
@@ -115,6 +126,7 @@ function collectResiduals(nodes: NodeCard[]): ResidualRow[] {
|
|||||||
option_qty_eth: numOrNull(r.option_qty_eth),
|
option_qty_eth: numOrNull(r.option_qty_eth),
|
||||||
initial_premium: numOrNull(r.initial_premium),
|
initial_premium: numOrNull(r.initial_premium),
|
||||||
bid_px: numOrNull(r.bid_px),
|
bid_px: numOrNull(r.bid_px),
|
||||||
|
bid_sz_eth: numOrNull(r.bid_sz_eth),
|
||||||
current_premium: numOrNull(r.current_premium),
|
current_premium: numOrNull(r.current_premium),
|
||||||
recovery_pct: numOrNull(r.recovery_pct),
|
recovery_pct: numOrNull(r.recovery_pct),
|
||||||
liquidity_ok: r.liquidity_ok === true,
|
liquidity_ok: r.liquidity_ok === true,
|
||||||
@@ -838,8 +850,9 @@ export default function MonitorPage() {
|
|||||||
<th>期权名</th>
|
<th>期权名</th>
|
||||||
<th>数量</th>
|
<th>数量</th>
|
||||||
<th>开仓权利金</th>
|
<th>开仓权利金</th>
|
||||||
<th>当前买一权利金</th>
|
<th title="最新买一价格 / 买一数量(ETH)">买一流动性</th>
|
||||||
<th>回收占比%</th>
|
<th title="买一价 × 持仓数量">买一权利金</th>
|
||||||
|
<th title="买一权利金 ÷ 开仓权利金">占比</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -851,9 +864,21 @@ export default function MonitorPage() {
|
|||||||
<td className="mono">{r.option_inst_id}</td>
|
<td className="mono">{r.option_inst_id}</td>
|
||||||
<td className="mono">{fmt(r.option_qty_eth, 4)}</td>
|
<td className="mono">{fmt(r.option_qty_eth, 4)}</td>
|
||||||
<td className="mono">{fmt(r.initial_premium, 2)}</td>
|
<td className="mono">{fmt(r.initial_premium, 2)}</td>
|
||||||
|
<td
|
||||||
|
className="mono"
|
||||||
|
title={
|
||||||
|
r.liquidity_ok
|
||||||
|
? "买一深度可覆盖持仓"
|
||||||
|
: "买一不足或盘口不可用"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{fmtBidLiquidity(r.bid_px, r.bid_sz_eth)}
|
||||||
|
</td>
|
||||||
<td className="mono">{fmt(r.current_premium, 2)}</td>
|
<td className="mono">{fmt(r.current_premium, 2)}</td>
|
||||||
<td className="mono">
|
<td className="mono">
|
||||||
{r.recovery_pct == null ? "—" : fmt(r.recovery_pct, 1)}
|
{r.recovery_pct == null
|
||||||
|
? "—"
|
||||||
|
: `${fmt(r.recovery_pct, 1)}%`}
|
||||||
</td>
|
</td>
|
||||||
<td className="col-actions">
|
<td className="col-actions">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user