Fix options add-on premium to sum all open legs for the contract.

Display, close gate, alerts, and full-close accounting no longer use only the latest fill row.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-16 11:11:32 +08:00
parent 4a0b0def1d
commit f0d5b47f50
8 changed files with 162 additions and 72 deletions
+2
View File
@@ -17,6 +17,8 @@
开仓后写入 `options_trades`(open),并在持仓卡展示权利金、买盘深度、按买一可回收等.
**同合约加仓**:每次买入再插一条 open 记录;展示权利金 / 平仓门控 / 翻倍提醒按同合约 **SUM(premium_paid)** 汇总,不再只取最新一笔.
---
## 2. 平仓方式
+24 -15
View File
@@ -31,15 +31,10 @@ def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
try:
conn = cfg["get_db"]()
try:
from lib.options.options_db import init_options_tables
from lib.options.options_db import init_options_tables, sum_open_premium_paid
init_options_tables(conn)
row = conn.execute(
"SELECT premium_paid FROM options_trades WHERE inst_id = ? AND status = 'open' ORDER BY id DESC LIMIT 1",
(inst_id,),
).fetchone()
if row and row["premium_paid"] is not None:
return float(row["premium_paid"])
return sum_open_premium_paid(conn, inst_id)
finally:
conn.close()
except Exception:
@@ -299,16 +294,30 @@ def close_option_by_bid1(
from lib.options.options_db import init_options_tables
init_options_tables(conn)
row = conn.execute(
"SELECT id, premium_paid FROM options_trades WHERE inst_id = ? AND status = 'open' ORDER BY id DESC LIMIT 1",
open_rows = conn.execute(
"""
SELECT id, premium_paid FROM options_trades
WHERE inst_id = ? AND status = 'open'
ORDER BY id ASC
""",
(inst_id,),
).fetchone()
if row:
).fetchall()
total_paid = sum(float(r["premium_paid"] or 0) for r in open_rows)
allocated = 0.0
for i, row in enumerate(open_rows):
paid = float(row["premium_paid"] or 0)
pnl = prem_recv - paid
if i == len(open_rows) - 1:
recv = round(prem_recv - allocated, 4)
elif total_paid > 0:
recv = round(prem_recv * (paid / total_paid), 4)
allocated += recv
else:
recv = round(prem_recv / len(open_rows), 4)
allocated += recv
pnl = round(recv - paid, 4)
note_sql = ""
params: list[Any] = [px, prem_recv, pnl, oid or None]
if signal_note:
params: list[Any] = [px, recv, pnl, oid or None]
if signal_note and i == len(open_rows) - 1:
note_sql = """,
signal_note = CASE
WHEN signal_note IS NULL OR TRIM(signal_note) = '' THEN ?
@@ -326,7 +335,7 @@ def close_option_by_bid1(
""",
tuple(params),
)
conn.commit()
conn.commit()
finally:
conn.close()
elif require_recycle_gate:
+36
View File
@@ -93,3 +93,39 @@ def init_options_tables(conn: sqlite3.Connection) -> None:
ON options_target_monitors(status)
"""
)
def sum_open_premium_paid(conn: sqlite3.Connection, inst_id: str) -> float | None:
"""同合约所有 open 腿权利金合计(加仓后显示/门控用)."""
inst = (inst_id or "").strip()
if not inst:
return None
row = conn.execute(
"""
SELECT SUM(premium_paid) AS total, COUNT(*) AS n
FROM options_trades
WHERE inst_id = ? AND status = 'open' AND premium_paid IS NOT NULL
""",
(inst,),
).fetchone()
if not row or int(row["n"] or 0) < 1:
return None
return round(float(row["total"] or 0), 4)
def sum_open_sheets(conn: sqlite3.Connection, inst_id: str) -> int | None:
"""同合约所有 open 腿张数合计."""
inst = (inst_id or "").strip()
if not inst:
return None
row = conn.execute(
"""
SELECT SUM(sheets) AS total, COUNT(*) AS n
FROM options_trades
WHERE inst_id = ? AND status = 'open'
""",
(inst,),
).fetchone()
if not row or int(row["n"] or 0) < 1:
return None
return int(row["total"] or 0)
+2 -13
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any
from lib.options.options_db import init_options_tables
from lib.options.options_db import init_options_tables, sum_open_premium_paid
def enrich_position_row_display(
@@ -50,18 +50,7 @@ def load_options_history(ex: Any, cfg: dict[str, Any]) -> list[dict[str, Any]]:
}
for p in raw_live:
inst = str(p.get("instId") or "").strip()
premium_override = None
if inst:
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:
premium_override = float(rec["premium_paid"])
premium_override = sum_open_premium_paid(conn, inst) if inst else None
row = enrich_position_row_display(
cfg,
ex,
+21 -5
View File
@@ -61,18 +61,34 @@ def run_options_profit_alerts(
SELECT id, inst_id, premium_paid, profit_alert_sent
FROM options_trades
WHERE status = 'open'
ORDER BY id ASC
"""
).fetchall()
# 同合约多腿加仓:按合约汇总权利金,整仓只告警一次
by_inst: dict[str, dict[str, Any]] = {}
for row in rows:
if int(row["profit_alert_sent"] or 0):
continue
inst_id = str(row["inst_id"] or "")
if not inst_id:
continue
bucket = by_inst.setdefault(
inst_id,
{"ids": [], "premium": 0.0, "all_sent": True, "has_prem": False},
)
bucket["ids"].append(int(row["id"]))
prem = _safe_float(row["premium_paid"])
if not inst_id or prem is None or prem <= 0:
if prem is not None:
bucket["premium"] += float(prem)
bucket["has_prem"] = True
if not int(row["profit_alert_sent"] or 0):
bucket["all_sent"] = False
for inst_id, bucket in by_inst.items():
if bucket["all_sent"] or not bucket["has_prem"] or bucket["premium"] <= 0:
continue
pos = pos_by_inst.get(inst_id)
if not pos:
continue
prem = float(bucket["premium"])
upl = _safe_float(pos.get("upl"))
upl_ratio = _safe_float(pos.get("upl_ratio_pct"))
if upl_ratio is not None:
@@ -95,8 +111,8 @@ def run_options_profit_alerts(
try:
send_wechat(msg)
conn.execute(
"UPDATE options_trades SET profit_alert_sent = 1 WHERE id = ?",
(int(row["id"]),),
f"UPDATE options_trades SET profit_alert_sent = 1 WHERE id IN ({','.join('?' * len(bucket['ids']))})",
tuple(bucket["ids"]),
)
sent += 1
except Exception:
+2 -13
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any
from lib.options.options_db import init_options_tables
from lib.options.options_db import init_options_tables, sum_open_premium_paid
from lib.options.options_history_lib import enrich_position_row_display
from lib.options.options_close_gate_lib import clear_close_gate, is_close_gate_passed, update_close_gate
from lib.options.options_pricing_lib import estimate_close_by_bids, intrinsic_px_per_unit
@@ -140,18 +140,7 @@ def build_display_option_positions(
init_options_tables(conn)
for p in raw_positions:
inst = str(p.get("instId") or "").strip()
premium_override = None
if inst:
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:
premium_override = float(rec["premium_paid"])
premium_override = sum_open_premium_paid(conn, inst) if inst else None
row = enrich_position_row_display(
cfg,
ex,
+6 -26
View File
@@ -9,7 +9,7 @@ from typing import Any
from flask import Flask, jsonify, redirect, request, url_for
from jinja2 import ChoiceLoader, FileSystemLoader
from lib.options.options_db import init_options_tables
from lib.options.options_db import init_options_tables, sum_open_premium_paid, sum_open_sheets
from lib.options.options_monitor_lib import options_monitor_loop
from lib.options.options_pricing_lib import (
calc_order_size,
@@ -170,19 +170,9 @@ def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
conn = cfg["get_db"]()
try:
init_options_tables(conn)
rec = conn.execute(
"""
SELECT premium_paid FROM options_trades
WHERE inst_id = ? AND status = 'open'
ORDER BY id DESC LIMIT 1
""",
(inst_id,),
).fetchone()
if rec and rec["premium_paid"] is not None:
return round(float(rec["premium_paid"]), 4)
return sum_open_premium_paid(conn, inst_id)
finally:
conn.close()
return None
def _position_avail_sheets(pos: dict[str, Any]) -> int:
@@ -662,18 +652,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
rows = []
for p in raw:
inst = str(p.get("instId") or "").strip()
premium_override = None
if inst:
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:
premium_override = float(rec["premium_paid"])
premium_override = sum_open_premium_paid(conn, inst) if inst else None
row = _enrich_position_row_display(
cfg,
ex,
@@ -732,14 +711,15 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
try:
trade = conn.execute(
"""
SELECT id, sheets, opt_type, underlying FROM options_trades
SELECT id, opt_type, underlying FROM options_trades
WHERE inst_id = ? AND status = 'open'
ORDER BY id DESC LIMIT 1
""",
(inst_id,),
).fetchone()
trade_id = int(trade["id"]) if trade else None
sheets = int(trade["sheets"]) if trade and trade["sheets"] is not None else int(fmt.get("pos") or 0)
sheets_sum = sum_open_sheets(conn, inst_id)
sheets = sheets_sum if sheets_sum is not None else int(fmt.get("pos") or 0)
opt_type = (trade["opt_type"] if trade else None) or fmt.get("opt_type")
underlying = (trade["underlying"] if trade else None) or fmt.get("underlying")
out = upsert_target_monitor(
+69
View File
@@ -0,0 +1,69 @@
"""期权加仓后权利金汇总."""
from __future__ import annotations
import sqlite3
from lib.options.options_db import init_options_tables, sum_open_premium_paid, sum_open_sheets
def _mem_db() -> sqlite3.Connection:
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
init_options_tables(conn)
return conn
def test_sum_open_premium_after_add():
conn = _mem_db()
inst = "BTC-USD_UM-260717-65500-C"
conn.execute(
"""
INSERT INTO options_trades
(inst_id, underlying, opt_type, sheets, eth_amount, open_quote, premium_paid, status)
VALUES (?, 'BTC-USD_UM', 'C', 1, 0.01, 530, 5.3, 'open')
""",
(inst,),
)
conn.execute(
"""
INSERT INTO options_trades
(inst_id, underlying, opt_type, sheets, eth_amount, open_quote, premium_paid, status)
VALUES (?, 'BTC-USD_UM', 'C', 1, 0.01, 140, 1.4, 'open')
""",
(inst,),
)
conn.commit()
assert sum_open_premium_paid(conn, inst) == 6.7
assert sum_open_sheets(conn, inst) == 2
# 最新一笔单独是 1.4,汇总不能只取最新
latest = conn.execute(
"SELECT premium_paid FROM options_trades WHERE inst_id=? AND status='open' ORDER BY id DESC LIMIT 1",
(inst,),
).fetchone()
assert float(latest["premium_paid"]) == 1.4
conn.close()
def test_sum_open_premium_ignores_closed():
conn = _mem_db()
inst = "ETH-USD_UM-260101-2000-C"
conn.execute(
"""
INSERT INTO options_trades
(inst_id, underlying, opt_type, sheets, eth_amount, premium_paid, status)
VALUES (?, 'ETH-USD_UM', 'C', 1, 0.01, 2.0, 'closed')
""",
(inst,),
)
conn.execute(
"""
INSERT INTO options_trades
(inst_id, underlying, opt_type, sheets, eth_amount, premium_paid, status)
VALUES (?, 'ETH-USD_UM', 'C', 2, 0.02, 3.5, 'open')
""",
(inst,),
)
conn.commit()
assert sum_open_premium_paid(conn, inst) == 3.5
assert sum_open_sheets(conn, inst) == 2
conn.close()