fix真盈亏只认卖光收回减买币,禁止权利金指数冒充。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-13 10:03:06 +08:00
parent 09989d2709
commit c2c69e580c
9 changed files with 338 additions and 118 deletions
+93 -10
View File
@@ -190,9 +190,13 @@ def try_settle_after_sellback(
block = sellable_coin_blocks_settle(ex, uly)
if block:
return 0
try:
from lib.exchange.okx_options_lib import invalidate_options_balance_cache
invalidate_options_balance_cache()
except Exception:
pass
after = fetch_trading_usdt(pack, ex)
if after is None:
return 0
n = 0
if bridge_id is not None:
n = settle_closed_trades_from_trading_account(
@@ -232,21 +236,83 @@ def try_settle_after_sellback(
pass
def resolve_true_pnl_usdt(
conn,
*,
trading_usdt_before: Any = None,
trading_usdt_after: Any = None,
bridge_id: Any = None,
realized_pnl_usdt: Any = None,
pnl_settled: Any = False,
) -> tuple[float | None, str]:
"""真盈亏(口径 A)=卖光标的币收回 USDT − 开仓买币花费.
优先账户快照 after−before(与上式等价);其次已关桥的「卖回累计−买币预算」.
绝不回退权利金×指数(那是口径 B / 期权腿).
返回 (pnl_u, source): account | bridge | frozen | none
"""
before = _safe_float(trading_usdt_before)
after = _safe_float(trading_usdt_after)
if before is not None and after is not None:
return round(float(after) - float(before), 4), "account"
bid = None
try:
if bridge_id is not None and str(bridge_id).strip() != "":
bid = int(bridge_id)
except (TypeError, ValueError):
bid = None
if bid is not None:
try:
from lib.options.options_spot_bridge_lib import (
BRIDGE_CLOSED,
cash_pnl_usdt_from_bridge,
ensure_bridge_table,
)
ensure_bridge_table(conn)
brow = conn.execute(
"SELECT status, usdt_recovered FROM options_spot_bridge WHERE id=?",
(bid,),
).fetchone()
if brow:
st = str(brow["status"] if hasattr(brow, "keys") else brow[0] or "")
recovered = _safe_float(
brow["usdt_recovered"] if hasattr(brow, "keys") else brow[1]
)
if st == BRIDGE_CLOSED and recovered is not None:
bp = cash_pnl_usdt_from_bridge(conn, bid)
if bp is not None:
return bp, "bridge"
except Exception:
pass
try:
settled = int(pnl_settled or 0) == 1
except (TypeError, ValueError):
settled = bool(pnl_settled)
frozen = _safe_float(realized_pnl_usdt)
# 仅承认「有 before 且已账户结算」的旧冻结;无快照的指数折 U 不算真盈亏
if settled and before is not None and frozen is not None:
return round(float(frozen), 4), "frozen"
return None, "none"
def settle_closed_trades_from_trading_account(
conn,
*,
trading_usdt_after: float,
trading_usdt_after: float | None = None,
bridge_id: int | None = None,
trade_id: int | None = None,
) -> int:
"""卖回后:对本桥/本单未结算已平仓位冻结 after−before.
"""卖回后:对本桥/本单未结算已平仓位冻结真盈亏.
优先 after−before;账户 after 缺失时用桥「卖回−买币」.
已 pnl_settled=1 的行永不改写.
"""
after = _safe_float(trading_usdt_after)
if after is None:
return 0
after = round(float(after), 8)
if after is not None:
after = round(float(after), 8)
clauses = ["status='closed'", "COALESCE(pnl_settled, 0)=0", "trading_usdt_before IS NOT NULL"]
params: list[Any] = []
if trade_id is not None:
@@ -259,7 +325,7 @@ def settle_closed_trades_from_trading_account(
return 0
rows = conn.execute(
f"""
SELECT id, trading_usdt_before, realized_pnl_usdt
SELECT id, trading_usdt_before, realized_pnl_usdt, bridge_id
FROM options_trades
WHERE {" AND ".join(clauses)}
""",
@@ -267,10 +333,27 @@ def settle_closed_trades_from_trading_account(
).fetchall()
updated = 0
for row in rows:
tid = int(row["id"] if hasattr(row, "keys") else row[0])
before = _safe_float(row["trading_usdt_before"] if hasattr(row, "keys") else row[1])
if before is None:
continue
pnl_u = round(float(after) - float(before), 4)
row_bid = row["bridge_id"] if hasattr(row, "keys") else row[3]
if row_bid is None and bridge_id is not None:
row_bid = bridge_id
after_use = after
if after_use is not None:
pnl_u = round(float(after_use) - float(before), 4)
else:
pnl_u, src = resolve_true_pnl_usdt(
conn,
trading_usdt_before=before,
trading_usdt_after=None,
bridge_id=row_bid,
pnl_settled=0,
)
if pnl_u is None or src != "bridge":
continue
after_use = round(float(before) + float(pnl_u), 8)
conn.execute(
"""
UPDATE options_trades
@@ -279,7 +362,7 @@ def settle_closed_trades_from_trading_account(
pnl_settled = 1
WHERE id = ? AND COALESCE(pnl_settled, 0)=0
""",
(after, pnl_u, int(row["id"] if hasattr(row, "keys") else row[0])),
(after_use, pnl_u, tid),
)
updated += 1
return updated
+57 -14
View File
@@ -429,8 +429,7 @@ def sync_options_from_local_trades(
) -> dict[str, Any]:
"""从本地 options_trades 已平仓记录导入复盘快照(不访问交易所).
盈亏优先用已冻结 realized_pnl_usdt(模拟盘现金口径 / 平仓时指数),
禁止每次同步用实时指数重算,否则复盘数字会跟盘跳动.
真盈亏=卖光收回−买币(after−before / 已关桥);禁止权利金×指数冒充.
"""
init_options_review_tables(conn)
from lib.options.options_db import init_options_tables
@@ -462,8 +461,8 @@ def sync_options_from_local_trades(
eth_amount, open_quote, close_quote, premium_paid, premium_received,
realized_pnl, realized_pnl_usdt, close_index_px, open_index_px,
budget_usdt, premium_ccy, created_at, closed_at, signal_note, status,
trading_usdt_before, pnl_settled, initial_premium_usdt,
open_fee, close_fee, exchange_ord_id, close_ord_id
trading_usdt_before, trading_usdt_after, bridge_id, pnl_settled,
initial_premium_usdt, open_fee, close_fee, exchange_ord_id, close_ord_id
FROM options_trades
WHERE status = 'closed'
ORDER BY id DESC
@@ -560,8 +559,6 @@ def sync_options_from_local_trades(
settled = int(r["pnl_settled"] or 0) == 1
except Exception:
settled = False
# 有账户快照未结算:交易记录显示待结算(真盈亏=卖回后 after−before)
acct_pending = _safe_float(r["trading_usdt_before"]) is not None and not settled
close_idx = _safe_float(r["close_index_px"])
open_idx = _safe_float(r["open_index_px"])
# 折算指数:仅用于初始权利金/期权手续费展示,不用于真盈亏
@@ -573,14 +570,60 @@ def sync_options_from_local_trades(
else:
idx = idx_cache.get(underly)
prem = _safe_float(r["premium_paid"])
# 交易记录真盈亏=口径 A(卖光币后账户差分);未结算不写假 U
if acct_pending:
pnl_u = None
elif frozen_usdt is not None and (settled or _safe_float(r["trading_usdt_before"]) is None):
pnl_u = round(float(frozen_usdt), 4)
else:
# 旧单无账户快照:不回退币×价冒充真盈亏
pnl_u = None
# 真盈亏=卖光收回−买币(账户 after−before 或已关桥);禁止权利金×指数冒充
from lib.options.options_account_pnl_lib import resolve_true_pnl_usdt
pnl_u, pnl_src = resolve_true_pnl_usdt(
conn,
trading_usdt_before=r["trading_usdt_before"],
trading_usdt_after=r["trading_usdt_after"],
bridge_id=r["bridge_id"],
realized_pnl_usdt=frozen_usdt,
pnl_settled=settled,
)
# 快照能算真盈亏但冻结值偏离:纠偏写回(仅纠正错账,不改口径)
before_f = _safe_float(r["trading_usdt_before"])
after_f = _safe_float(r["trading_usdt_after"])
if (
pnl_src == "account"
and pnl_u is not None
and before_f is not None
and after_f is not None
and (not settled or frozen_usdt is None or abs(float(frozen_usdt) - float(pnl_u)) > 0.05)
):
try:
conn.execute(
"""
UPDATE options_trades
SET realized_pnl_usdt = ?, pnl_settled = 1
WHERE id = ?
""",
(pnl_u, trade_id),
)
settled = True
except Exception:
pass
elif pnl_src == "bridge" and pnl_u is not None and not settled:
try:
after_heal = (
round(float(before_f) + float(pnl_u), 8) if before_f is not None else None
)
conn.execute(
"""
UPDATE options_trades
SET realized_pnl_usdt = ?,
trading_usdt_after = COALESCE(trading_usdt_after, ?),
pnl_settled = 1
WHERE id = ? AND COALESCE(pnl_settled, 0)=0
""",
(pnl_u, after_heal, trade_id),
)
settled = True
except Exception:
pass
acct_pending = pnl_u is None and (
before_f is not None or r["bridge_id"] is not None
)
# 初始权利金(U)=开仓指数×权利金币量;不含多买
stored_initial = _safe_float(r["initial_premium_usdt"])
open_idx_for_prem = open_idx if (open_idx is not None and open_idx > 0) else idx
+3 -2
View File
@@ -184,9 +184,10 @@ def add_bridge_usdt_recovered(
def cash_pnl_usdt_from_bridge(conn: sqlite3.Connection, bridge_id: int | None) -> float | None:
"""桥上现货净 USDT(卖回−买币预算).仅供对账,不得当作期权已实现盈亏.
"""桥上真盈亏(U)=累计卖回 USDT − 开仓买币预算.
买币预算含权利金本金与缓冲;用此值当盈利会把权利金本金算进去.
与账户 after−before 等价(一次一仓、无中途划转时);可作口径 A 回退.
不是期权腿币盈亏(口径 B).
"""
if bridge_id is None:
return None
@@ -229,7 +229,7 @@
<span class="or-step" aria-hidden="true">1</span>
<div>
<div class="or-section-title" id="or-list-title">期权交易记录</div>
<p class="or-section-desc">盈亏为卖光币后的账户真盈亏;点「复盘」填写表单。期权历史仅记期权腿币收益,不含现货。</p>
<p class="or-section-desc">真盈亏=卖光币收回 USDT − 开仓买币花费;点「复盘」填写表单。期权历史仅记期权腿币收益,不含现货。</p>
</div>
</div>
<div class="options-strike-table-wrap or-trades-table-wrap" id="or-trades-wrap">
@@ -239,7 +239,7 @@
<th>类型</th>
<th>标的/合约</th>
<th>初始权利金</th>
<th title="卖回完成后交易账户 USDT 差分">真盈亏</th>
<th title="卖光收回USDT − 开仓买币花费(账户after−before)">真盈亏</th>
<th>盈利率</th>
<th title="仅期权开/平手续费">手续费</th>
<th>开仓时间</th>