chore: restore codebase to state before 2026-08-11 changes
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
"""期权目标委托:盈亏比×权利金触发后按买一限价平仓(无止损,到期结算).
|
||||
|
||||
兼容旧「目标指数」委托:无 profit_rr 时仍按指数到位触发.
|
||||
"""
|
||||
"""期权目标位委托:指数目标价仅用于监控触发;触发后按买一限价平仓(无止损,到期结算)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import time
|
||||
from typing import Any, Callable
|
||||
|
||||
from lib.options.options_db import init_options_tables, sum_open_premium_paid
|
||||
from lib.options.options_db import init_options_tables
|
||||
from lib.options.options_pricing_lib import close_ref_prices, fetch_option_mark_px
|
||||
|
||||
|
||||
@@ -21,18 +18,6 @@ def _safe_float(v: Any) -> float | None:
|
||||
return None
|
||||
|
||||
|
||||
def _ensure_column(conn: sqlite3.Connection, table: str, col: str, typedef: str) -> None:
|
||||
rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
|
||||
names: set[str] = set()
|
||||
for r in rows:
|
||||
try:
|
||||
names.add(str(r["name"]))
|
||||
except (TypeError, KeyError, IndexError):
|
||||
names.add(str(r[1]))
|
||||
if col not in names:
|
||||
conn.execute(f"ALTER TABLE {table} ADD COLUMN {col} {typedef}")
|
||||
|
||||
|
||||
def _pos_close_refs(ex: Any, pos: dict[str, Any], quote: dict[str, Any] | None = None) -> tuple[float | None, float | None]:
|
||||
from lib.exchange.okx_options_lib import option_fields_from_inst_id
|
||||
|
||||
@@ -78,44 +63,21 @@ def ensure_target_tables(conn: sqlite3.Connection) -> None:
|
||||
ON options_target_monitors(status)
|
||||
"""
|
||||
)
|
||||
# 盈亏比=目标盈利/权利金;如 2=盈利 2 倍权利金.有值时优先生效,target_index 可置 0
|
||||
_ensure_column(conn, "options_target_monitors", "profit_rr", "REAL")
|
||||
|
||||
|
||||
def target_hit(*, opt_type: str | None, index_px: float, target_index: float) -> bool:
|
||||
"""旧逻辑:Call 指数≥目标;Put 指数≤目标."""
|
||||
"""Call:指数涨到/超过目标平仓;Put:指数跌到/低于目标平仓."""
|
||||
ot = (opt_type or "").strip().upper()
|
||||
if ot == "P":
|
||||
return index_px <= target_index
|
||||
return index_px >= target_index
|
||||
|
||||
|
||||
def profit_rr_hit(
|
||||
*,
|
||||
premium: float,
|
||||
bid: float | None,
|
||||
sheets: float,
|
||||
ct_mult: float,
|
||||
profit_rr: float,
|
||||
) -> bool:
|
||||
"""买一回收 − 权利金 ≥ 盈亏比 × 权利金."""
|
||||
if premium <= 0 or profit_rr <= 0:
|
||||
return False
|
||||
if bid is None or float(bid) <= 0:
|
||||
return False
|
||||
if sheets <= 0 or ct_mult <= 0:
|
||||
return False
|
||||
recycle = float(bid) * float(sheets) * float(ct_mult)
|
||||
pnl = recycle - float(premium)
|
||||
return pnl + 1e-9 >= float(profit_rr) * float(premium)
|
||||
|
||||
|
||||
def upsert_target_monitor(
|
||||
conn: sqlite3.Connection,
|
||||
*,
|
||||
inst_id: str,
|
||||
target_index: float | None = None,
|
||||
profit_rr: float | None = None,
|
||||
target_index: float,
|
||||
underlying: str | None = None,
|
||||
opt_type: str | None = None,
|
||||
trade_id: int | None = None,
|
||||
@@ -125,18 +87,9 @@ def upsert_target_monitor(
|
||||
inst_id = (inst_id or "").strip()
|
||||
if not inst_id:
|
||||
return {"ok": False, "msg": "缺少 inst_id"}
|
||||
|
||||
rr = _safe_float(profit_rr)
|
||||
tgt = _safe_float(target_index)
|
||||
if rr is not None and rr > 0:
|
||||
tgt_store = float(tgt) if tgt is not None and tgt > 0 else 0.0
|
||||
rr_store = float(rr)
|
||||
elif tgt is not None and tgt > 0:
|
||||
tgt_store = float(tgt)
|
||||
rr_store = None
|
||||
else:
|
||||
return {"ok": False, "msg": "请填写盈亏比(相对权利金,默认2)"}
|
||||
|
||||
if target_index is None or float(target_index) <= 0:
|
||||
return {"ok": False, "msg": "目标位无效"}
|
||||
target_index = float(target_index)
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT id FROM options_target_monitors
|
||||
@@ -151,7 +104,6 @@ def upsert_target_monitor(
|
||||
"""
|
||||
UPDATE options_target_monitors
|
||||
SET target_index = ?,
|
||||
profit_rr = ?,
|
||||
underlying = COALESCE(?, underlying),
|
||||
opt_type = COALESCE(?, opt_type),
|
||||
trade_id = COALESCE(?, trade_id),
|
||||
@@ -163,13 +115,14 @@ def upsert_target_monitor(
|
||||
triggered_at = NULL
|
||||
WHERE id = ?
|
||||
""",
|
||||
(tgt_store, rr_store, underlying, opt_type, trade_id, sheets, int(row["id"])),
|
||||
(target_index, underlying, opt_type, trade_id, sheets, int(row["id"])),
|
||||
)
|
||||
mon_id = int(row["id"])
|
||||
# 同一合约其他进行中的委托取消,避免双轨触发重复推送
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_target_monitors
|
||||
SET status = 'cancelled', message = '被新目标委托覆盖'
|
||||
SET status = 'cancelled', message = '被新目标位覆盖'
|
||||
WHERE inst_id = ? AND id != ? AND status IN ('active', 'closing')
|
||||
""",
|
||||
(inst_id, mon_id),
|
||||
@@ -178,21 +131,13 @@ def upsert_target_monitor(
|
||||
cur = conn.execute(
|
||||
"""
|
||||
INSERT INTO options_target_monitors
|
||||
(inst_id, underlying, opt_type, target_index, profit_rr, trade_id, sheets, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'active')
|
||||
(inst_id, underlying, opt_type, target_index, trade_id, sheets, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'active')
|
||||
""",
|
||||
(inst_id, underlying, opt_type, tgt_store, rr_store, trade_id, sheets),
|
||||
(inst_id, underlying, opt_type, target_index, trade_id, sheets),
|
||||
)
|
||||
mon_id = int(cur.lastrowid)
|
||||
out: dict[str, Any] = {
|
||||
"ok": True,
|
||||
"id": mon_id,
|
||||
"inst_id": inst_id,
|
||||
"target_index": tgt_store if tgt_store > 0 else None,
|
||||
}
|
||||
if rr_store is not None:
|
||||
out["profit_rr"] = rr_store
|
||||
return out
|
||||
return {"ok": True, "id": mon_id, "inst_id": inst_id, "target_index": target_index}
|
||||
|
||||
|
||||
def cancel_target_monitor(conn: sqlite3.Connection, *, inst_id: str | None = None, monitor_id: int | None = None) -> int:
|
||||
@@ -221,19 +166,12 @@ def cancel_target_monitor(conn: sqlite3.Connection, *, inst_id: str | None = Non
|
||||
|
||||
|
||||
def _row_to_target(r: sqlite3.Row) -> dict[str, Any]:
|
||||
tgt = _safe_float(r["target_index"])
|
||||
rr = None
|
||||
try:
|
||||
rr = _safe_float(r["profit_rr"])
|
||||
except (KeyError, IndexError):
|
||||
rr = None
|
||||
return {
|
||||
"id": int(r["id"]),
|
||||
"inst_id": r["inst_id"],
|
||||
"underlying": r["underlying"],
|
||||
"opt_type": r["opt_type"],
|
||||
"target_index": tgt if tgt is not None and tgt > 0 else None,
|
||||
"profit_rr": rr if rr is not None and rr > 0 else None,
|
||||
"target_index": _safe_float(r["target_index"]),
|
||||
"trade_id": r["trade_id"],
|
||||
"sheets": r["sheets"],
|
||||
"status": r["status"],
|
||||
@@ -242,16 +180,16 @@ def _row_to_target(r: sqlite3.Row) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
_TARGET_SELECT = (
|
||||
"SELECT id, inst_id, underlying, opt_type, target_index, profit_rr, trade_id, sheets, "
|
||||
"status, message, created_at FROM options_target_monitors"
|
||||
)
|
||||
|
||||
|
||||
def list_active_targets(conn: sqlite3.Connection) -> list[dict[str, Any]]:
|
||||
ensure_target_tables(conn)
|
||||
rows = conn.execute(
|
||||
f"{_TARGET_SELECT} WHERE status = 'active' ORDER BY id DESC"
|
||||
"""
|
||||
SELECT id, inst_id, underlying, opt_type, target_index, trade_id, sheets,
|
||||
status, message, created_at
|
||||
FROM options_target_monitors
|
||||
WHERE status = 'active'
|
||||
ORDER BY id DESC
|
||||
"""
|
||||
).fetchall()
|
||||
return [_row_to_target(r) for r in rows]
|
||||
|
||||
@@ -260,7 +198,13 @@ def list_closing_targets(conn: sqlite3.Connection) -> list[dict[str, Any]]:
|
||||
"""已挂出平仓单、等待成交的目标(不再重复推送微信)."""
|
||||
ensure_target_tables(conn)
|
||||
rows = conn.execute(
|
||||
f"{_TARGET_SELECT} WHERE status = 'closing' ORDER BY id DESC"
|
||||
"""
|
||||
SELECT id, inst_id, underlying, opt_type, target_index, trade_id, sheets,
|
||||
status, message, created_at
|
||||
FROM options_target_monitors
|
||||
WHERE status = 'closing'
|
||||
ORDER BY id DESC
|
||||
"""
|
||||
).fetchall()
|
||||
return [_row_to_target(r) for r in rows]
|
||||
|
||||
@@ -342,23 +286,23 @@ def close_option_by_bid_depth(
|
||||
inst_id,
|
||||
sheets=sheets,
|
||||
require_recycle_gate=True,
|
||||
signal_note="盈亏比平仓",
|
||||
signal_note="目标位平仓",
|
||||
)
|
||||
|
||||
|
||||
|
||||
def _notify_target_close(
|
||||
cfg: dict[str, Any] | None,
|
||||
send_wechat: Callable[[str], None] | None,
|
||||
*,
|
||||
account_label: str,
|
||||
inst_id: str,
|
||||
target: float | None,
|
||||
profit_rr: float | None,
|
||||
idx: float | None,
|
||||
target: float,
|
||||
idx: float,
|
||||
result: dict[str, Any],
|
||||
conn: Any = None,
|
||||
) -> None:
|
||||
"""目标平仓推送:优先走统一平仓必发(幂等);无 cfg 时回退旧文案."""
|
||||
"""目标位平仓推送:优先走统一平仓必发(幂等);无 cfg 时回退旧文案."""
|
||||
if result.get("fully_closed") or result.get("already_flat"):
|
||||
if cfg is not None:
|
||||
try:
|
||||
@@ -368,13 +312,12 @@ def _notify_target_close(
|
||||
cfg,
|
||||
conn,
|
||||
inst_id=inst_id,
|
||||
reason="盈亏比平仓" if profit_rr else "目标位平仓",
|
||||
reason="目标位平仓",
|
||||
sheets=result.get("submitted_sheets"),
|
||||
premium_received=result.get("premium_received"),
|
||||
close_quote=result.get("locked_bid_px") or result.get("bid"),
|
||||
target_index=target,
|
||||
trigger_idx=idx,
|
||||
profit_rr=profit_rr,
|
||||
)
|
||||
return
|
||||
except Exception:
|
||||
@@ -382,20 +325,14 @@ def _notify_target_close(
|
||||
if not send_wechat:
|
||||
return
|
||||
try:
|
||||
if profit_rr is not None and profit_rr > 0:
|
||||
rule = f"盈亏比×{profit_rr:g}"
|
||||
elif target is not None:
|
||||
rule = f"目标指数:{target:g}"
|
||||
else:
|
||||
rule = "目标委托"
|
||||
send_wechat(
|
||||
"\n".join(
|
||||
[
|
||||
"【OKX期权·盈亏比平仓】" if profit_rr else "【OKX期权·目标位平仓】",
|
||||
"【OKX期权·目标位平仓】",
|
||||
f"账户:{account_label}",
|
||||
f"合约:{inst_id}",
|
||||
rule,
|
||||
f"触发指数:{idx:g}" if idx is not None else "触发指数:—",
|
||||
f"目标指数:{target:g}",
|
||||
f"触发指数:{idx:g}",
|
||||
f"提交张数:{result.get('submitted_sheets') or '—'}",
|
||||
f"预估收回:{result.get('premium_received') if result.get('premium_received') is not None else '—'} USDC",
|
||||
f"状态:{'已全平' if (result.get('fully_closed') or result.get('already_flat')) else '挂单中/部分'}",
|
||||
@@ -417,77 +354,18 @@ def _result_fully_done(result: dict[str, Any]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _monitor_should_close(
|
||||
conn: sqlite3.Connection,
|
||||
mon: dict[str, Any],
|
||||
pos: dict[str, Any],
|
||||
*,
|
||||
bid_fn: Callable[[str], float | None] | None,
|
||||
index_fn: Callable[[dict[str, Any]], float | None] | None,
|
||||
) -> tuple[bool, float | None]:
|
||||
"""返回 (是否触发, 当前指数)."""
|
||||
inst_id = str(mon.get("inst_id") or "")
|
||||
rr = _safe_float(mon.get("profit_rr"))
|
||||
if index_fn is not None:
|
||||
idx = index_fn(pos)
|
||||
else:
|
||||
idx = _safe_float(pos.get("idx_px") or pos.get("idxPx"))
|
||||
|
||||
if rr is not None and rr > 0:
|
||||
premium = sum_open_premium_paid(conn, inst_id)
|
||||
if premium is None or premium <= 0:
|
||||
premium = _safe_float(pos.get("premium_paid"))
|
||||
sheets = _safe_float(mon.get("sheets"))
|
||||
if sheets is None or sheets <= 0:
|
||||
sheets = _safe_float(pos.get("pos") or pos.get("avail_pos") or pos.get("availPos"))
|
||||
ct = _safe_float(pos.get("ct_mult") or pos.get("ctMult")) or 0.01
|
||||
bid = None
|
||||
if bid_fn is not None:
|
||||
try:
|
||||
bid = bid_fn(inst_id)
|
||||
except Exception:
|
||||
bid = None
|
||||
if bid is None:
|
||||
bid = _safe_float(pos.get("bid_px") or pos.get("bidPx") or pos.get("bid"))
|
||||
preview = pos.get("close_preview") if isinstance(pos.get("close_preview"), dict) else {}
|
||||
if bid is None:
|
||||
bid = _safe_float(preview.get("bid") or preview.get("best_bid"))
|
||||
if premium is None or sheets is None:
|
||||
return False, idx
|
||||
return (
|
||||
profit_rr_hit(
|
||||
premium=float(premium),
|
||||
bid=bid,
|
||||
sheets=float(sheets),
|
||||
ct_mult=float(ct),
|
||||
profit_rr=float(rr),
|
||||
),
|
||||
idx,
|
||||
)
|
||||
|
||||
target = _safe_float(mon.get("target_index"))
|
||||
if target is None or target <= 0 or idx is None:
|
||||
return False, idx
|
||||
opt_type = mon.get("opt_type") or pos.get("opt_type") or pos.get("optType")
|
||||
return (
|
||||
target_hit(opt_type=str(opt_type) if opt_type else None, index_px=idx, target_index=target),
|
||||
idx,
|
||||
)
|
||||
|
||||
|
||||
def run_options_target_closes(
|
||||
conn: sqlite3.Connection,
|
||||
positions: list[dict[str, Any]],
|
||||
*,
|
||||
close_fn: Callable[[str], dict[str, Any]],
|
||||
index_fn: Callable[[dict[str, Any]], float | None] | None = None,
|
||||
bid_fn: Callable[[str], float | None] | None = None,
|
||||
send_wechat: Callable[[str], None] | None = None,
|
||||
account_label: str = "OKX期权",
|
||||
cfg: dict[str, Any] | None = None,
|
||||
) -> int:
|
||||
"""
|
||||
扫描 active 目标委托;盈亏比达标(或旧指数到位)后限价平仓.
|
||||
扫描 active 目标委托;指数到位后限价平仓.
|
||||
状态先 commit 再推微信,避免 sync 失败回滚导致同一笔反复推送.
|
||||
未完全成交进入 closing,仅重试平仓不再推送.
|
||||
返回本次新触发(并推送)的条数.
|
||||
@@ -534,7 +412,7 @@ def run_options_target_closes(
|
||||
status="triggered",
|
||||
trigger_idx=idx,
|
||||
close_ord_id=result.get("close_ord_id"),
|
||||
message="盈亏比限价平仓完成",
|
||||
message="目标位限价平仓完成",
|
||||
)
|
||||
_commit_monitor(conn)
|
||||
continue
|
||||
@@ -551,7 +429,8 @@ def run_options_target_closes(
|
||||
triggered = 0
|
||||
for mon in list_active_targets(conn):
|
||||
inst_id = str(mon.get("inst_id") or "")
|
||||
if not inst_id:
|
||||
target = _safe_float(mon.get("target_index"))
|
||||
if not inst_id or target is None:
|
||||
continue
|
||||
if inst_id in hedge_managed:
|
||||
mark_monitor(
|
||||
@@ -565,15 +444,17 @@ def run_options_target_closes(
|
||||
pos = pos_by_inst.get(inst_id)
|
||||
if not pos:
|
||||
continue
|
||||
should, idx = _monitor_should_close(
|
||||
conn, mon, pos, bid_fn=bid_fn, index_fn=index_fn
|
||||
)
|
||||
if not should:
|
||||
if index_fn is not None:
|
||||
idx = index_fn(pos)
|
||||
else:
|
||||
idx = _safe_float(pos.get("idx_px") or pos.get("idxPx"))
|
||||
if idx is None:
|
||||
continue
|
||||
opt_type = mon.get("opt_type") or pos.get("opt_type") or pos.get("optType")
|
||||
if not target_hit(opt_type=str(opt_type) if opt_type else None, index_px=idx, target_index=target):
|
||||
continue
|
||||
|
||||
result = close_fn(inst_id)
|
||||
rr = _safe_float(mon.get("profit_rr"))
|
||||
target = _safe_float(mon.get("target_index"))
|
||||
if result.get("already_flat"):
|
||||
mark_monitor(conn, int(mon["id"]), status="expired", trigger_idx=idx, message="持仓已平")
|
||||
_commit_monitor(conn)
|
||||
@@ -591,19 +472,15 @@ def run_options_target_closes(
|
||||
|
||||
done = _result_fully_done(result)
|
||||
status = "triggered" if done else "closing"
|
||||
hit_msg = (
|
||||
"盈亏比达标限价平仓"
|
||||
if (rr is not None and rr > 0)
|
||||
else "目标位触发限价平仓"
|
||||
)
|
||||
mark_monitor(
|
||||
conn,
|
||||
int(mon["id"]),
|
||||
status=status,
|
||||
trigger_idx=idx,
|
||||
close_ord_id=result.get("close_ord_id"),
|
||||
message=hit_msg if done else "已挂买一限价,等待成交",
|
||||
message="目标位触发限价平仓" if done else "目标位已挂买一限价,等待成交",
|
||||
)
|
||||
# 关键:先落库,再推送——否则后续 sync 异常回滚会让同一笔反复推微信
|
||||
_commit_monitor(conn)
|
||||
triggered += 1
|
||||
_notify_target_close(
|
||||
@@ -612,7 +489,6 @@ def run_options_target_closes(
|
||||
account_label=account_label,
|
||||
inst_id=inst_id,
|
||||
target=target,
|
||||
profit_rr=rr,
|
||||
idx=idx,
|
||||
result=result,
|
||||
conn=conn,
|
||||
|
||||
Reference in New Issue
Block a user