Show premium paid on options position card and parse strike/type from instId when OKX omits them.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -444,6 +444,7 @@
|
|||||||
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
|
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
'<div class="pos-grid">' +
|
'<div class="pos-grid">' +
|
||||||
|
'<div class="pos-cell"><span class="pos-label">权利金</span><span class="pos-value">' + fmt(p.premium_paid, 4) + " USDC</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">开仓均价</span><span class="pos-value">' + fmt(p.avg_px, 4) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">开仓均价</span><span class="pos-value">' + fmt(p.avg_px, 4) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">' + fmt(p.mark_px, 4) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">' + fmt(p.mark_px, 4) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
|
||||||
|
|||||||
@@ -216,6 +216,17 @@ def inst_family_from_inst_id(inst_id: str) -> str | None:
|
|||||||
return "-".join(parts[:-3])
|
return "-".join(parts[:-3])
|
||||||
|
|
||||||
|
|
||||||
|
def option_fields_from_inst_id(inst_id: str) -> tuple[str | None, float | None]:
|
||||||
|
"""从 instId 解析 optType 与 strike,如 ETH-USD_UM-260709-1700-P。"""
|
||||||
|
parts = (inst_id or "").strip().split("-")
|
||||||
|
if len(parts) < 2:
|
||||||
|
return None, None
|
||||||
|
tail = parts[-1].upper()
|
||||||
|
opt_type = tail if tail in ("C", "P") else None
|
||||||
|
strike = _safe_float(parts[-2]) if len(parts) >= 2 else None
|
||||||
|
return opt_type, strike
|
||||||
|
|
||||||
|
|
||||||
def fetch_option_instrument_meta(ex: ccxt.okx, inst_id: str) -> dict[str, Any] | None:
|
def fetch_option_instrument_meta(ex: ccxt.okx, inst_id: str) -> dict[str, Any] | None:
|
||||||
family = inst_family_from_inst_id(inst_id)
|
family = inst_family_from_inst_id(inst_id)
|
||||||
if not family:
|
if not family:
|
||||||
@@ -778,6 +789,7 @@ def format_position_row(pos: dict[str, Any], ct_mult: float = 0.01) -> dict[str,
|
|||||||
close_breakeven_idx,
|
close_breakeven_idx,
|
||||||
expiry_breakeven_px,
|
expiry_breakeven_px,
|
||||||
idx_distance_to_be,
|
idx_distance_to_be,
|
||||||
|
total_premium,
|
||||||
)
|
)
|
||||||
|
|
||||||
sheets = _safe_float(pos.get("pos")) or 0.0
|
sheets = _safe_float(pos.get("pos")) or 0.0
|
||||||
@@ -786,8 +798,18 @@ def format_position_row(pos: dict[str, Any], ct_mult: float = 0.01) -> dict[str,
|
|||||||
upl = _safe_float(pos.get("upl"))
|
upl = _safe_float(pos.get("upl"))
|
||||||
upl_ratio = _safe_float(pos.get("uplRatio"))
|
upl_ratio = _safe_float(pos.get("uplRatio"))
|
||||||
idx_px = _safe_float(pos.get("idxPx"))
|
idx_px = _safe_float(pos.get("idxPx"))
|
||||||
|
inst_id = str(pos.get("instId") or "")
|
||||||
opt_type = pos.get("optType")
|
opt_type = pos.get("optType")
|
||||||
strike = _safe_float(pos.get("stk"))
|
strike = _safe_float(pos.get("stk"))
|
||||||
|
parsed_type, parsed_strike = option_fields_from_inst_id(inst_id)
|
||||||
|
if not opt_type:
|
||||||
|
opt_type = parsed_type
|
||||||
|
if strike is None:
|
||||||
|
strike = parsed_strike
|
||||||
|
eth_amount = round(abs(sheets) * ct_mult, 8)
|
||||||
|
premium_paid = (
|
||||||
|
round(total_premium(avg, eth_amount), 4) if avg is not None and eth_amount > 0 else None
|
||||||
|
)
|
||||||
delta_pa = _safe_float(pos.get("deltaPA"))
|
delta_pa = _safe_float(pos.get("deltaPA"))
|
||||||
expiry_be = expiry_breakeven_px(
|
expiry_be = expiry_breakeven_px(
|
||||||
opt_type=str(opt_type or ""),
|
opt_type=str(opt_type or ""),
|
||||||
@@ -805,12 +827,13 @@ def format_position_row(pos: dict[str, Any], ct_mult: float = 0.01) -> dict[str,
|
|||||||
ct_mult=ct_mult,
|
ct_mult=ct_mult,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"inst_id": pos.get("instId"),
|
"inst_id": inst_id or pos.get("instId"),
|
||||||
"pos": sheets,
|
"pos": sheets,
|
||||||
"eth_amount": round(abs(sheets) * ct_mult, 8),
|
"eth_amount": eth_amount,
|
||||||
"avg_px": avg,
|
"avg_px": avg,
|
||||||
"mark_px": mark,
|
"mark_px": mark,
|
||||||
"idx_px": idx_px,
|
"idx_px": idx_px,
|
||||||
|
"premium_paid": premium_paid,
|
||||||
"upl": upl,
|
"upl": upl,
|
||||||
"upl_ratio_pct": round(upl_ratio * 100, 2) if upl_ratio is not None else None,
|
"upl_ratio_pct": round(upl_ratio * 100, 2) if upl_ratio is not None else None,
|
||||||
"exp_time": pos.get("expTime"),
|
"exp_time": pos.get("expTime"),
|
||||||
|
|||||||
@@ -306,6 +306,24 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
return jsonify({"ok": False, "msg": err})
|
return jsonify({"ok": False, "msg": err})
|
||||||
raw = cfg["fetch_option_positions"](ex)
|
raw = cfg["fetch_option_positions"](ex)
|
||||||
rows = [cfg["format_position_row"](p) for p in raw]
|
rows = [cfg["format_position_row"](p) for p in raw]
|
||||||
|
conn = cfg["get_db"]()
|
||||||
|
try:
|
||||||
|
for row in rows:
|
||||||
|
inst = row.get("inst_id")
|
||||||
|
if not inst:
|
||||||
|
continue
|
||||||
|
rec = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT premium_paid FROM options_trades
|
||||||
|
WHERE inst_id = ? AND status = 'open'
|
||||||
|
ORDER BY id DESC LIMIT 1
|
||||||
|
""",
|
||||||
|
(inst,),
|
||||||
|
).fetchone()
|
||||||
|
if rec and rec["premium_paid"] is not None:
|
||||||
|
row["premium_paid"] = round(float(rec["premium_paid"]), 4)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
return jsonify({"ok": True, "positions": rows})
|
return jsonify({"ok": True, "positions": rows})
|
||||||
|
|
||||||
@app.route("/api/options/close", methods=["POST"])
|
@app.route("/api/options/close", methods=["POST"])
|
||||||
|
|||||||
@@ -136,4 +136,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_panel.js?v=11"></script>
|
<script src="/static/options_panel.js?v=12"></script>
|
||||||
|
|||||||
@@ -202,6 +202,25 @@ def test_format_options_breakeven_line():
|
|||||||
assert "指数3480" in s
|
assert "指数3480" in s
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_position_row_premium_and_inst_parse():
|
||||||
|
from lib.exchange.okx_options_lib import format_position_row
|
||||||
|
|
||||||
|
row = format_position_row(
|
||||||
|
{
|
||||||
|
"instId": "ETH-USD_UM-260709-1700-P",
|
||||||
|
"pos": "20",
|
||||||
|
"avgPx": "6.2",
|
||||||
|
"markPx": "6.3241",
|
||||||
|
"idxPx": "1746",
|
||||||
|
"upl": "0.0248",
|
||||||
|
"uplRatio": "0.02",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert row["opt_type"] == "P"
|
||||||
|
assert row["strike"] == 1700.0
|
||||||
|
assert row["premium_paid"] == 1.24
|
||||||
|
|
||||||
|
|
||||||
def test_format_position_row_breakeven():
|
def test_format_position_row_breakeven():
|
||||||
from lib.exchange.okx_options_lib import format_position_row
|
from lib.exchange.okx_options_lib import format_position_row
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user