fix复盘真盈亏按权利金USDT波动公式计算,忽略现货多买。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -236,68 +236,6 @@ 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,
|
||||
*,
|
||||
@@ -305,15 +243,17 @@ def settle_closed_trades_from_trading_account(
|
||||
bridge_id: int | None = None,
|
||||
trade_id: int | None = None,
|
||||
) -> int:
|
||||
"""卖回后:对本桥/本单未结算已平仓位冻结真盈亏.
|
||||
"""卖回后:拍 after 快照,并冻结复盘真盈亏(权利金 USDT 波动口径).
|
||||
|
||||
优先 after−before;账户 after 缺失时用桥「卖回−买币」.
|
||||
已 pnl_settled=1 的行永不改写.
|
||||
realized_pnl_usdt = 历史币盈亏×卖回价 − (权利金×开仓价 − 权利金×卖回价);
|
||||
不含现货多买.账户 after 仅审计.已 pnl_settled=1 的行永不改写.
|
||||
"""
|
||||
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
||||
|
||||
after = _safe_float(trading_usdt_after)
|
||||
if after is not None:
|
||||
after = round(float(after), 8)
|
||||
clauses = ["status='closed'", "COALESCE(pnl_settled, 0)=0", "trading_usdt_before IS NOT NULL"]
|
||||
clauses = ["status='closed'", "COALESCE(pnl_settled, 0)=0"]
|
||||
params: list[Any] = []
|
||||
if trade_id is not None:
|
||||
clauses.append("id=?")
|
||||
@@ -325,7 +265,8 @@ def settle_closed_trades_from_trading_account(
|
||||
return 0
|
||||
rows = conn.execute(
|
||||
f"""
|
||||
SELECT id, trading_usdt_before, realized_pnl_usdt, bridge_id
|
||||
SELECT id, trading_usdt_before, premium_paid, premium_received, realized_pnl,
|
||||
open_index_px, close_index_px, open_fee, close_fee, premium_ccy, fee_ccy
|
||||
FROM options_trades
|
||||
WHERE {" AND ".join(clauses)}
|
||||
""",
|
||||
@@ -334,30 +275,30 @@ def settle_closed_trades_from_trading_account(
|
||||
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
|
||||
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
|
||||
before = _safe_float(
|
||||
row["trading_usdt_before"] if hasattr(row, "keys") else row[1]
|
||||
)
|
||||
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)
|
||||
if after_use is None and before is not None:
|
||||
# 无 after 读数时不编造;仍可用权利金公式冻结
|
||||
after_use = None
|
||||
pnl_u, src = resolve_true_pnl_usdt_from_trade(
|
||||
premium_paid=row["premium_paid"] if hasattr(row, "keys") else row[2],
|
||||
premium_received=row["premium_received"] if hasattr(row, "keys") else row[3],
|
||||
realized_pnl=row["realized_pnl"] if hasattr(row, "keys") else row[4],
|
||||
open_index_px=row["open_index_px"] if hasattr(row, "keys") else row[5],
|
||||
close_index_px=row["close_index_px"] if hasattr(row, "keys") else row[6],
|
||||
open_fee=row["open_fee"] if hasattr(row, "keys") else row[7],
|
||||
close_fee=row["close_fee"] if hasattr(row, "keys") else row[8],
|
||||
premium_ccy=row["premium_ccy"] if hasattr(row, "keys") else row[9],
|
||||
fee_ccy=row["fee_ccy"] if hasattr(row, "keys") else row[10],
|
||||
)
|
||||
if pnl_u is None or src == "none":
|
||||
continue
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_trades
|
||||
SET trading_usdt_after = ?,
|
||||
SET trading_usdt_after = COALESCE(?, trading_usdt_after),
|
||||
realized_pnl_usdt = ?,
|
||||
pnl_settled = 1
|
||||
WHERE id = ? AND COALESCE(pnl_settled, 0)=0
|
||||
|
||||
@@ -510,9 +510,8 @@ def close_option_by_bid1(
|
||||
fee_ccy=str(row["fee_ccy"] or row["premium_ccy"] or row_ccy or ""),
|
||||
)
|
||||
pnl = settled.get("realized_pnl")
|
||||
# 有交易账户 before 快照:U 盈亏等卖回后结算,平仓不写 realized_pnl_usdt
|
||||
has_acct_snap = _safe_float(row["trading_usdt_before"]) is not None
|
||||
pnl_usdt = None if has_acct_snap else settled.get("realized_pnl_usdt")
|
||||
# 复盘真盈亏=权利金 USDT 波动口径;账户 after 另在卖回后拍快照
|
||||
pnl_usdt = settled.get("realized_pnl_usdt")
|
||||
note_sql = ""
|
||||
|
||||
# 占位: close_quote/recv/pnl/ord/closed_at/open_fee/close_fee/fee_ccy/close_idx/open_idx/pnl_u/[note]/id
|
||||
|
||||
@@ -174,6 +174,101 @@ def fee_to_usdt(
|
||||
return round(amt, 4)
|
||||
|
||||
|
||||
def true_pnl_usdt_premium_move(
|
||||
*,
|
||||
coin_pnl: Any,
|
||||
premium_paid: Any,
|
||||
open_index_px: Any,
|
||||
sell_back_px: Any,
|
||||
) -> float | None:
|
||||
"""复盘真盈亏(U):计入权利金 USDT 波动,忽略现货多买.
|
||||
|
||||
真盈亏 = 期权历史盈亏(币)×平仓卖回价
|
||||
− (权利金×开仓价 − 权利金×平仓卖回价)
|
||||
= 历史币盈亏×卖回价 − 权利金×(开仓价 − 卖回价)
|
||||
|
||||
无手续费时等价于: 收回×卖回价 − 已付×开仓价.
|
||||
币盈亏须已含期权开/平手续费(与期权历史同口径).
|
||||
"""
|
||||
pnl = _safe_float(coin_pnl)
|
||||
paid = _safe_float(premium_paid)
|
||||
open_px = _safe_float(open_index_px)
|
||||
close_px = _safe_float(sell_back_px)
|
||||
if pnl is None or paid is None:
|
||||
return None
|
||||
if open_px is None or open_px <= 0 or close_px is None or close_px <= 0:
|
||||
return None
|
||||
prem_move = float(paid) * float(open_px) - float(paid) * float(close_px)
|
||||
return round(float(pnl) * float(close_px) - prem_move, 4)
|
||||
|
||||
|
||||
def resolve_true_pnl_usdt_from_trade(
|
||||
*,
|
||||
premium_paid: Any = None,
|
||||
premium_received: Any = None,
|
||||
realized_pnl: Any = None,
|
||||
open_index_px: Any = None,
|
||||
close_index_px: Any = None,
|
||||
open_fee: Any = 0,
|
||||
close_fee: Any = 0,
|
||||
premium_ccy: str | None = None,
|
||||
fee_ccy: str | None = None,
|
||||
) -> tuple[float | None, str]:
|
||||
"""由成交字段算复盘真盈亏;不含现货多买/账户差分.
|
||||
|
||||
返回 (pnl_u, source): premium_move | stable | none
|
||||
"""
|
||||
del fee_ccy # 费率折 U 已体现在历史币盈亏里;本函数不重扣
|
||||
pc = str(premium_ccy or "").strip().upper()
|
||||
paid = _safe_float(premium_paid)
|
||||
recv = _safe_float(premium_received)
|
||||
ofee = abs(float(_safe_float(open_fee) or 0.0))
|
||||
cfee = abs(float(_safe_float(close_fee) or 0.0))
|
||||
coin = _safe_float(realized_pnl)
|
||||
if coin is None and paid is not None and recv is not None:
|
||||
coin = round(float(recv) - float(paid) - ofee - cfee, 8)
|
||||
|
||||
if pc not in ("ETH", "BTC"):
|
||||
if coin is not None:
|
||||
return round(float(coin), 4), "stable"
|
||||
return None, "none"
|
||||
|
||||
open_px = _safe_float(open_index_px)
|
||||
close_px = _safe_float(close_index_px)
|
||||
# 优先严格按用户式:历史币盈亏×卖回价 − 权利金×(开仓价−卖回价)
|
||||
usdt = true_pnl_usdt_premium_move(
|
||||
coin_pnl=coin,
|
||||
premium_paid=paid,
|
||||
open_index_px=open_px,
|
||||
sell_back_px=close_px,
|
||||
)
|
||||
if usdt is not None:
|
||||
return usdt, "premium_move"
|
||||
|
||||
# 无历史币盈亏时回退收回/已付精确折 U
|
||||
if (
|
||||
paid is not None
|
||||
and recv is not None
|
||||
and open_px is not None
|
||||
and open_px > 0
|
||||
and close_px is not None
|
||||
and close_px > 0
|
||||
):
|
||||
settled = settle_sim_coin_pnl_usdt(
|
||||
premium_paid=paid,
|
||||
premium_received=recv,
|
||||
open_fee=ofee,
|
||||
close_fee=cfee,
|
||||
open_index_px=open_px,
|
||||
sell_back_px=close_px,
|
||||
premium_ccy=pc,
|
||||
)
|
||||
usdt2 = _safe_float(settled.get("realized_pnl_usdt"))
|
||||
if usdt2 is not None:
|
||||
return usdt2, "premium_move"
|
||||
return None, "none"
|
||||
|
||||
|
||||
def settle_sim_coin_pnl_usdt(
|
||||
*,
|
||||
premium_paid: Any,
|
||||
@@ -185,11 +280,10 @@ def settle_sim_coin_pnl_usdt(
|
||||
fee_ccy: str | None = None,
|
||||
premium_ccy: str = "ETH",
|
||||
) -> dict[str, float | None]:
|
||||
"""模拟盘币本位盈亏(U).
|
||||
"""币本位真盈亏(U):含权利金 USDT 波动,不含现货多买.
|
||||
|
||||
盈利: (权利金btc+盈利btc)×卖回价 − 初始U权利金 − 手续费
|
||||
= 收回币×卖回价 − 已付币×开仓指数 − 手续费U
|
||||
亏损: 剩余权利金U − 手续费U − 初始U权利金(同一式,结果为负)
|
||||
= 历史币盈亏×卖回价 − (权利金×开仓价 − 权利金×卖回价)
|
||||
= 收回币×卖回价 − 已付币×开仓指数 − 手续费U
|
||||
|
||||
不用买币预算当成本(预算含缓冲).
|
||||
"""
|
||||
|
||||
@@ -429,7 +429,8 @@ def sync_options_from_local_trades(
|
||||
) -> dict[str, Any]:
|
||||
"""从本地 options_trades 已平仓记录导入复盘快照(不访问交易所).
|
||||
|
||||
真盈亏=卖光收回−买币(after−before / 已关桥);禁止权利金×指数冒充.
|
||||
真盈亏=历史币盈亏×卖回价 − (权利金×开仓价 − 权利金×卖回价);
|
||||
计入权利金 USDT 波动,忽略现货多买.
|
||||
"""
|
||||
init_options_review_tables(conn)
|
||||
from lib.options.options_db import init_options_tables
|
||||
@@ -555,13 +556,8 @@ def sync_options_from_local_trades(
|
||||
if not ccy:
|
||||
ccy = premium_ccy_for_mode(margin_mode_from_inst_id(inst), underly)
|
||||
frozen_usdt = _safe_float(r["realized_pnl_usdt"])
|
||||
try:
|
||||
settled = int(r["pnl_settled"] or 0) == 1
|
||||
except Exception:
|
||||
settled = False
|
||||
close_idx = _safe_float(r["close_index_px"])
|
||||
open_idx = _safe_float(r["open_index_px"])
|
||||
# 折算指数:仅用于初始权利金/期权手续费展示,不用于真盈亏
|
||||
idx = None
|
||||
if close_idx is not None and close_idx > 0:
|
||||
idx = close_idx
|
||||
@@ -570,26 +566,36 @@ def sync_options_from_local_trades(
|
||||
else:
|
||||
idx = idx_cache.get(underly)
|
||||
prem = _safe_float(r["premium_paid"])
|
||||
# 真盈亏=卖光收回−买币(账户 after−before 或已关桥);禁止权利金×指数冒充
|
||||
from lib.options.options_account_pnl_lib import resolve_true_pnl_usdt
|
||||
|
||||
pnl_u, pnl_src = resolve_true_pnl_usdt(
|
||||
# 仅期权开/平手续费折 U(不含现货);先解析再算真盈亏
|
||||
ofee = abs(_safe_float(r["open_fee"]) or 0.0)
|
||||
cfee = abs(_safe_float(r["close_fee"]) or 0.0)
|
||||
ofee, cfee = _resolve_option_fees_coin(
|
||||
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,
|
||||
r,
|
||||
open_fee=ofee,
|
||||
close_fee=cfee,
|
||||
premium_ccy=ccy,
|
||||
)
|
||||
open_idx_for_prem = open_idx if (open_idx is not None and open_idx > 0) else idx
|
||||
sell_idx = close_idx if (close_idx is not None and close_idx > 0) else idx
|
||||
# 真盈亏=币盈亏×卖回价 − (权利金×开仓价 − 权利金×卖回价);忽略现货多买
|
||||
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
||||
|
||||
pnl_u, pnl_src = resolve_true_pnl_usdt_from_trade(
|
||||
premium_paid=prem,
|
||||
premium_received=r["premium_received"],
|
||||
realized_pnl=r["realized_pnl"],
|
||||
open_index_px=open_idx_for_prem,
|
||||
close_index_px=sell_idx,
|
||||
open_fee=ofee,
|
||||
close_fee=cfee,
|
||||
premium_ccy=ccy,
|
||||
fee_ccy=str(r["premium_ccy"] or ccy or ""),
|
||||
)
|
||||
# 快照能算真盈亏但冻结值偏离:纠偏写回(仅纠正错账,不改口径)
|
||||
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)
|
||||
pnl_u is not None
|
||||
and pnl_src != "none"
|
||||
and (frozen_usdt is None or abs(float(frozen_usdt) - float(pnl_u)) > 0.05)
|
||||
):
|
||||
try:
|
||||
conn.execute(
|
||||
@@ -600,49 +606,15 @@ def sync_options_from_local_trades(
|
||||
""",
|
||||
(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
|
||||
prem_u = stored_initial
|
||||
if prem_u is None:
|
||||
prem_u = _to_usdt(prem, ccy=ccy, idx=open_idx_for_prem)
|
||||
if ccy in ("ETH", "BTC") and prem_u is None:
|
||||
prem_u = prem
|
||||
# 仅期权开/平手续费折 U(不含现货)
|
||||
ofee = abs(_safe_float(r["open_fee"]) or 0.0)
|
||||
cfee = abs(_safe_float(r["close_fee"]) or 0.0)
|
||||
# 模拟盘旧单曾按「权利金×费率」计费偏小:按 OKX 公式/订单回填
|
||||
ofee, cfee = _resolve_option_fees_coin(
|
||||
conn,
|
||||
r,
|
||||
open_fee=ofee,
|
||||
close_fee=cfee,
|
||||
premium_ccy=ccy,
|
||||
)
|
||||
fee_u = None
|
||||
if ccy in ("ETH", "BTC"):
|
||||
fee_parts = 0.0
|
||||
@@ -650,7 +622,6 @@ def sync_options_from_local_trades(
|
||||
if ofee > 0 and open_idx_for_prem is not None and open_idx_for_prem > 0:
|
||||
fee_parts += ofee * float(open_idx_for_prem)
|
||||
has_fee = True
|
||||
sell_idx = close_idx if (close_idx is not None and close_idx > 0) else idx
|
||||
if cfee > 0 and sell_idx is not None and sell_idx > 0:
|
||||
fee_parts += cfee * float(sell_idx)
|
||||
has_fee = True
|
||||
@@ -683,7 +654,7 @@ def sync_options_from_local_trades(
|
||||
"option_fee_usdt": fee_u,
|
||||
"created_at": opened_at,
|
||||
"closed_at": closed_at,
|
||||
"status_label": "待结算" if acct_pending else "已平",
|
||||
"status_label": "已平",
|
||||
},
|
||||
)
|
||||
if action == "inserted":
|
||||
|
||||
@@ -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">真盈亏=卖光币收回 USDT − 开仓买币花费;点「复盘」填写表单。期权历史仅记期权腿币收益,不含现货。</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 − 开仓买币花费(账户after−before)">真盈亏</th>
|
||||
<th title="币盈亏×卖回价 − 权利金×(开仓价−卖回价)">真盈亏</th>
|
||||
<th>盈利率</th>
|
||||
<th title="仅期权开/平手续费">手续费</th>
|
||||
<th>开仓时间</th>
|
||||
|
||||
Reference in New Issue
Block a user