fix复盘真盈亏改为期权币盈亏×平仓价,不再绑卖回与初始权利金差。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -245,7 +245,7 @@ def settle_closed_trades_from_trading_account(
|
|||||||
) -> int:
|
) -> int:
|
||||||
"""卖回后:拍 after 快照,并冻结复盘真盈亏.
|
"""卖回后:拍 after 快照,并冻结复盘真盈亏.
|
||||||
|
|
||||||
realized_pnl_usdt = (权利金+币盈亏)×卖回VWAP − 初始权利金U;
|
realized_pnl_usdt = 期权币盈亏×平仓价(卖回VWAP优先);
|
||||||
不含现货多买.账户 after 仅审计.已 pnl_settled=1 的行永不改写.
|
不含现货多买.账户 after 仅审计.已 pnl_settled=1 的行永不改写.
|
||||||
"""
|
"""
|
||||||
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
||||||
|
|||||||
@@ -505,26 +505,20 @@ def close_option_by_bid1(
|
|||||||
row_open_idx = float(bv)
|
row_open_idx = float(bv)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
row_pc = str(row["premium_ccy"] or row_ccy or premium_ccy or "").strip().upper()
|
|
||||||
settled = settle_closed_pnl(
|
settled = settle_closed_pnl(
|
||||||
premium_paid=paid,
|
premium_paid=paid,
|
||||||
premium_received=recv,
|
premium_received=recv,
|
||||||
open_fee=row_ofee,
|
open_fee=row_ofee,
|
||||||
close_fee=row_cfee,
|
close_fee=row_cfee,
|
||||||
premium_ccy=row_pc or premium_ccy,
|
premium_ccy=str(row["premium_ccy"] or row_ccy or premium_ccy),
|
||||||
index_px=close_idx,
|
index_px=close_idx,
|
||||||
open_index_px=row_open_idx,
|
open_index_px=row_open_idx,
|
||||||
budget_usdt=row_budget,
|
budget_usdt=row_budget,
|
||||||
fee_ccy=str(row["fee_ccy"] or row["premium_ccy"] or row_ccy or ""),
|
fee_ccy=str(row["fee_ccy"] or row["premium_ccy"] or row_ccy or ""),
|
||||||
)
|
)
|
||||||
pnl = settled.get("realized_pnl")
|
pnl = settled.get("realized_pnl")
|
||||||
# BTC/ETH:币盈亏可落库;真盈亏U与平仓价等卖回VWAP,禁止用指数占位
|
# 真盈亏U=期权币盈亏×平仓价
|
||||||
if row_pc in ("ETH", "BTC"):
|
pnl_usdt = settled.get("realized_pnl_usdt")
|
||||||
pnl_usdt = None
|
|
||||||
close_px_store = None
|
|
||||||
else:
|
|
||||||
pnl_usdt = settled.get("realized_pnl_usdt")
|
|
||||||
close_px_store = settled.get("close_index_px")
|
|
||||||
note_sql = ""
|
note_sql = ""
|
||||||
|
|
||||||
# 占位: close_quote/recv/pnl/ord/closed_at/open_fee/close_fee/fee_ccy/close_idx/open_idx/pnl_u/[note]/id
|
# 占位: close_quote/recv/pnl/ord/closed_at/open_fee/close_fee/fee_ccy/close_idx/open_idx/pnl_u/[note]/id
|
||||||
@@ -537,7 +531,7 @@ def close_option_by_bid1(
|
|||||||
row_ofee,
|
row_ofee,
|
||||||
row_cfee,
|
row_cfee,
|
||||||
str(row["premium_ccy"] or row_ccy or premium_ccy or ""),
|
str(row["premium_ccy"] or row_ccy or premium_ccy or ""),
|
||||||
close_px_store,
|
settled.get("close_index_px"),
|
||||||
settled.get("open_index_px") or row_open_idx,
|
settled.get("open_index_px") or row_open_idx,
|
||||||
pnl_usdt,
|
pnl_usdt,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -403,14 +403,7 @@ def sync_open_options_trades(
|
|||||||
fee_ccy=fee_ccy,
|
fee_ccy=fee_ccy,
|
||||||
)
|
)
|
||||||
realized_pnl = settled.get("realized_pnl")
|
realized_pnl = settled.get("realized_pnl")
|
||||||
# BTC/ETH:真盈亏U与平仓价等卖回VWAP,禁止用指数占位
|
pnl_usdt = settled.get("realized_pnl_usdt")
|
||||||
pc = str(prem_ccy or "").strip().upper()
|
|
||||||
if pc in ("ETH", "BTC"):
|
|
||||||
pnl_usdt = None
|
|
||||||
close_px_store = None
|
|
||||||
else:
|
|
||||||
pnl_usdt = settled.get("realized_pnl_usdt")
|
|
||||||
close_px_store = settled.get("close_index_px")
|
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
@@ -438,7 +431,7 @@ def sync_open_options_trades(
|
|||||||
realized_pnl,
|
realized_pnl,
|
||||||
close_ord_id,
|
close_ord_id,
|
||||||
closed_at,
|
closed_at,
|
||||||
close_px_store,
|
settled.get("close_index_px"),
|
||||||
settled.get("open_index_px"),
|
settled.get("open_index_px"),
|
||||||
pnl_usdt,
|
pnl_usdt,
|
||||||
prem_ccy,
|
prem_ccy,
|
||||||
|
|||||||
@@ -177,33 +177,22 @@ def fee_to_usdt(
|
|||||||
def true_pnl_usdt_premium_move(
|
def true_pnl_usdt_premium_move(
|
||||||
*,
|
*,
|
||||||
coin_pnl: Any,
|
coin_pnl: Any,
|
||||||
premium_paid: Any,
|
premium_paid: Any = None,
|
||||||
sell_back_px: Any,
|
sell_back_px: Any = None,
|
||||||
initial_premium_usdt: Any = None,
|
initial_premium_usdt: Any = None,
|
||||||
open_index_px: Any = None,
|
open_index_px: Any = None,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
"""复盘真盈亏(U):币本位×卖回价 − 初始权利金U.
|
"""复盘真盈亏(U)=期权历史币盈亏×平仓价.
|
||||||
|
|
||||||
币本位 = 权利金 + 币盈亏
|
平仓价优先卖回 VWAP,否则用冻结/传入的 close 价.
|
||||||
真盈亏U = 币本位 × 卖回VWAP − 初始权利金U
|
premium_paid / initial_premium_usdt / open_index_px 仅兼容旧调用,不参与计算.
|
||||||
初始权利金U 优先用开仓冻结值;缺失时才用 权利金×开仓价 补算.
|
|
||||||
现货多买数量不进公式(只影响均价).
|
|
||||||
"""
|
"""
|
||||||
|
del premium_paid, initial_premium_usdt, open_index_px
|
||||||
pnl = _safe_float(coin_pnl)
|
pnl = _safe_float(coin_pnl)
|
||||||
paid = _safe_float(premium_paid)
|
|
||||||
close_px = _safe_float(sell_back_px)
|
close_px = _safe_float(sell_back_px)
|
||||||
if pnl is None or paid is None:
|
if pnl is None or close_px is None or close_px <= 0:
|
||||||
return None
|
return None
|
||||||
if close_px is None or close_px <= 0:
|
return round(float(pnl) * float(close_px), 4)
|
||||||
return None
|
|
||||||
initial = _safe_float(initial_premium_usdt)
|
|
||||||
if initial is None:
|
|
||||||
open_px = _safe_float(open_index_px)
|
|
||||||
if open_px is None or open_px <= 0:
|
|
||||||
return None
|
|
||||||
initial = float(paid) * float(open_px)
|
|
||||||
coin_basis = float(paid) + float(pnl)
|
|
||||||
return round(coin_basis * float(close_px) - float(initial), 4)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_spot_vwap_prices(
|
def resolve_spot_vwap_prices(
|
||||||
@@ -213,18 +202,17 @@ def resolve_spot_vwap_prices(
|
|||||||
open_index_px: Any = None,
|
open_index_px: Any = None,
|
||||||
close_index_px: Any = None,
|
close_index_px: Any = None,
|
||||||
) -> tuple[float | None, float | None, str]:
|
) -> tuple[float | None, float | None, str]:
|
||||||
"""开仓价/卖回价解析.
|
"""开仓价/平仓价解析(期权盈亏折U用).
|
||||||
|
|
||||||
开仓价:优先已冻结 open_index_px,缺失才补桥买 VWAP(不覆盖已冻值).
|
平仓价:桥上卖回 VWAP > 冻结 close_index_px > 开仓价.
|
||||||
卖回价:有桥时只认 close_sell VWAP;桥未关→pending_sell;已关无卖回→missing.
|
桥未关不再阻断折U(现货卖回与期权盈亏脱钩).
|
||||||
无桥时 close_index_px 视为调用方传入的卖回价(测试/无现货桥).
|
|
||||||
|
|
||||||
返回 (open_px, close_px, gate):
|
返回 (open_px, close_px, gate):
|
||||||
gate='' 可算; 'pending_sell' 卖回未完成; 'missing' 缺价
|
gate='' 可算; 'missing' 缺价
|
||||||
"""
|
"""
|
||||||
open_px = _safe_float(open_index_px)
|
open_px = _safe_float(open_index_px)
|
||||||
close_hint = _safe_float(close_index_px)
|
close_hint = _safe_float(close_index_px)
|
||||||
close_px: float | None = None
|
close_px = close_hint
|
||||||
bid = None
|
bid = None
|
||||||
try:
|
try:
|
||||||
if bridge_id is not None and str(bridge_id).strip() != "":
|
if bridge_id is not None and str(bridge_id).strip() != "":
|
||||||
@@ -234,40 +222,23 @@ def resolve_spot_vwap_prices(
|
|||||||
if conn is not None and bid is not None:
|
if conn is not None and bid is not None:
|
||||||
try:
|
try:
|
||||||
from lib.options.options_spot_bridge_lib import (
|
from lib.options.options_spot_bridge_lib import (
|
||||||
BRIDGE_CLOSED,
|
|
||||||
bridge_buy_vwap,
|
bridge_buy_vwap,
|
||||||
bridge_sell_vwap,
|
bridge_sell_vwap,
|
||||||
ensure_bridge_table,
|
ensure_bridge_table,
|
||||||
)
|
)
|
||||||
|
|
||||||
ensure_bridge_table(conn)
|
ensure_bridge_table(conn)
|
||||||
row = conn.execute(
|
if open_px is None or open_px <= 0:
|
||||||
"SELECT status FROM options_spot_bridge WHERE id=?",
|
bv = bridge_buy_vwap(conn, bid)
|
||||||
(bid,),
|
if bv is not None and bv > 0:
|
||||||
).fetchone()
|
open_px = bv
|
||||||
if row is None:
|
sv = bridge_sell_vwap(conn, bid)
|
||||||
# 桥记录不存在:按无桥处理,close_index 视为调用方卖回价
|
if sv is not None and sv > 0:
|
||||||
close_px = close_hint
|
close_px = sv
|
||||||
else:
|
|
||||||
if open_px is None or open_px <= 0:
|
|
||||||
bv = bridge_buy_vwap(conn, bid)
|
|
||||||
if bv is not None and bv > 0:
|
|
||||||
open_px = bv
|
|
||||||
st = str(
|
|
||||||
row["status"] if hasattr(row, "keys") else (row[0] if row else "") or ""
|
|
||||||
)
|
|
||||||
if st and st != BRIDGE_CLOSED:
|
|
||||||
return open_px, None, "pending_sell"
|
|
||||||
sv = bridge_sell_vwap(conn, bid)
|
|
||||||
if sv is not None and sv > 0:
|
|
||||||
close_px = sv
|
|
||||||
else:
|
|
||||||
# 有桥却无卖回成交:禁止用指数/旧 close_index 冒充
|
|
||||||
return open_px, None, "missing"
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
if close_px is None or close_px <= 0:
|
||||||
close_px = close_hint
|
close_px = open_px
|
||||||
if close_px is None or close_px <= 0:
|
if close_px is None or close_px <= 0:
|
||||||
return open_px, close_px, "missing"
|
return open_px, close_px, "missing"
|
||||||
return open_px, close_px, ""
|
return open_px, close_px, ""
|
||||||
@@ -288,11 +259,11 @@ def resolve_true_pnl_usdt_from_trade(
|
|||||||
conn: Any = None,
|
conn: Any = None,
|
||||||
bridge_id: Any = None,
|
bridge_id: Any = None,
|
||||||
) -> tuple[float | None, str]:
|
) -> tuple[float | None, str]:
|
||||||
"""由成交字段算复盘真盈亏: (权利金+币盈亏)×卖回价 − 初始权利金U.
|
"""由成交字段算复盘真盈亏: 期权币盈亏×平仓价.
|
||||||
|
|
||||||
返回 (pnl_u, source): premium_move | stable | pending_sell | none
|
返回 (pnl_u, source): premium_move | stable | none
|
||||||
"""
|
"""
|
||||||
del fee_ccy
|
del fee_ccy, initial_premium_usdt
|
||||||
pc = str(premium_ccy or "").strip().upper()
|
pc = str(premium_ccy or "").strip().upper()
|
||||||
paid = _safe_float(premium_paid)
|
paid = _safe_float(premium_paid)
|
||||||
recv = _safe_float(premium_received)
|
recv = _safe_float(premium_received)
|
||||||
@@ -308,20 +279,17 @@ def resolve_true_pnl_usdt_from_trade(
|
|||||||
return round(float(coin), 4), "stable"
|
return round(float(coin), 4), "stable"
|
||||||
return None, "none"
|
return None, "none"
|
||||||
|
|
||||||
open_px, close_px, gate = resolve_spot_vwap_prices(
|
_open_px, close_px, gate = resolve_spot_vwap_prices(
|
||||||
conn,
|
conn,
|
||||||
bridge_id=bridge_id,
|
bridge_id=bridge_id,
|
||||||
open_index_px=open_index_px,
|
open_index_px=open_index_px,
|
||||||
close_index_px=close_index_px,
|
close_index_px=close_index_px,
|
||||||
)
|
)
|
||||||
if gate == "pending_sell":
|
if gate == "missing":
|
||||||
return None, "pending_sell"
|
return None, "none"
|
||||||
usdt = true_pnl_usdt_premium_move(
|
usdt = true_pnl_usdt_premium_move(
|
||||||
coin_pnl=coin,
|
coin_pnl=coin,
|
||||||
premium_paid=paid,
|
|
||||||
sell_back_px=close_px,
|
sell_back_px=close_px,
|
||||||
initial_premium_usdt=initial_premium_usdt,
|
|
||||||
open_index_px=open_px,
|
|
||||||
)
|
)
|
||||||
if usdt is not None:
|
if usdt is not None:
|
||||||
return usdt, "premium_move"
|
return usdt, "premium_move"
|
||||||
@@ -339,10 +307,10 @@ def settle_sim_coin_pnl_usdt(
|
|||||||
fee_ccy: str | None = None,
|
fee_ccy: str | None = None,
|
||||||
premium_ccy: str = "ETH",
|
premium_ccy: str = "ETH",
|
||||||
) -> dict[str, float | None]:
|
) -> dict[str, float | None]:
|
||||||
"""由毛权利金生成净币盈亏,再按用户式折 U(费只扣进币盈亏一次).
|
"""由毛权利金生成净币盈亏,再按期权盈亏折 U(费只扣进币盈亏一次).
|
||||||
|
|
||||||
币盈亏 = 收回 − 已付 − 开/平手续费
|
币盈亏 = 收回 − 已付 − 开/平手续费
|
||||||
真盈亏U = (权利金+币盈亏)×卖回价 − 初始权利金U
|
真盈亏U = 币盈亏 × 平仓价
|
||||||
"""
|
"""
|
||||||
del fee_ccy
|
del fee_ccy
|
||||||
paid = float(_safe_float(premium_paid) or 0.0)
|
paid = float(_safe_float(premium_paid) or 0.0)
|
||||||
@@ -357,33 +325,39 @@ def settle_sim_coin_pnl_usdt(
|
|||||||
fee_u = round(ofee * float(open_idx) + cfee * float(sell_px), 4)
|
fee_u = round(ofee * float(open_idx) + cfee * float(sell_px), 4)
|
||||||
elif sell_px is not None and sell_px > 0 and (ofee > 0 or cfee > 0):
|
elif sell_px is not None and sell_px > 0 and (ofee > 0 or cfee > 0):
|
||||||
fee_u = round((ofee + cfee) * float(sell_px), 4)
|
fee_u = round((ofee + cfee) * float(sell_px), 4)
|
||||||
if open_idx is None or open_idx <= 0 or sell_px is None or sell_px <= 0:
|
if sell_px is None or sell_px <= 0:
|
||||||
return {
|
return {
|
||||||
"realized_pnl": net_coin,
|
"realized_pnl": net_coin,
|
||||||
"close_index_px": float(sell_px) if sell_px is not None and sell_px > 0 else None,
|
"close_index_px": None,
|
||||||
"open_index_px": float(open_idx) if open_idx is not None and open_idx > 0 else None,
|
"open_index_px": float(open_idx) if open_idx is not None and open_idx > 0 else None,
|
||||||
"realized_pnl_usdt": None,
|
"realized_pnl_usdt": None,
|
||||||
"total_fee": round(ofee + cfee, 8),
|
"total_fee": round(ofee + cfee, 8),
|
||||||
"initial_premium_usdt": None,
|
"initial_premium_usdt": (
|
||||||
|
round(float(paid) * float(open_idx), 4)
|
||||||
|
if open_idx is not None and open_idx > 0 and paid > 0
|
||||||
|
else None
|
||||||
|
),
|
||||||
"proceeds_usdt": None,
|
"proceeds_usdt": None,
|
||||||
"fee_usdt": fee_u,
|
"fee_usdt": fee_u,
|
||||||
"premium_ccy": str(premium_ccy or "").strip().upper() or "ETH",
|
"premium_ccy": str(premium_ccy or "").strip().upper() or "ETH",
|
||||||
}
|
}
|
||||||
initial_u = float(paid) * float(open_idx)
|
initial_u = (
|
||||||
|
float(paid) * float(open_idx)
|
||||||
|
if open_idx is not None and open_idx > 0
|
||||||
|
else None
|
||||||
|
)
|
||||||
proceeds_u = float(recv) * float(sell_px)
|
proceeds_u = float(recv) * float(sell_px)
|
||||||
pnl_u = true_pnl_usdt_premium_move(
|
pnl_u = true_pnl_usdt_premium_move(
|
||||||
coin_pnl=net_coin,
|
coin_pnl=net_coin,
|
||||||
premium_paid=paid,
|
|
||||||
sell_back_px=sell_px,
|
sell_back_px=sell_px,
|
||||||
initial_premium_usdt=initial_u,
|
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"realized_pnl": net_coin,
|
"realized_pnl": net_coin,
|
||||||
"close_index_px": float(sell_px),
|
"close_index_px": float(sell_px),
|
||||||
"open_index_px": float(open_idx),
|
"open_index_px": float(open_idx) if open_idx is not None and open_idx > 0 else None,
|
||||||
"realized_pnl_usdt": pnl_u,
|
"realized_pnl_usdt": pnl_u,
|
||||||
"total_fee": round(ofee + cfee, 8),
|
"total_fee": round(ofee + cfee, 8),
|
||||||
"initial_premium_usdt": round(initial_u, 4),
|
"initial_premium_usdt": round(initial_u, 4) if initial_u is not None else None,
|
||||||
"proceeds_usdt": round(proceeds_u, 4),
|
"proceeds_usdt": round(proceeds_u, 4),
|
||||||
"fee_usdt": fee_u,
|
"fee_usdt": fee_u,
|
||||||
"premium_ccy": str(premium_ccy or "").strip().upper() or "ETH",
|
"premium_ccy": str(premium_ccy or "").strip().upper() or "ETH",
|
||||||
@@ -406,9 +380,7 @@ def settle_closed_pnl(
|
|||||||
由「毛权利金」结算.
|
由「毛权利金」结算.
|
||||||
币数量净盈亏 = 收回 − 已付 − 开/平手续费(只扣一次).
|
币数量净盈亏 = 收回 − 已付 − 开/平手续费(只扣一次).
|
||||||
|
|
||||||
币本位 U:
|
币本位 U: 净币盈亏×平仓价(币量不含现货多余仓)
|
||||||
(权利金+币盈亏)×卖回价 − 初始权利金U
|
|
||||||
(开仓价缺失时回退为净币×卖回价;币量不含现货多余仓)
|
|
||||||
USDC: 净额原样为 U.
|
USDC: 净额原样为 U.
|
||||||
|
|
||||||
注意:不要用 budget_usdt 当开仓成本——它含现货买入缓冲.
|
注意:不要用 budget_usdt 当开仓成本——它含现货买入缓冲.
|
||||||
@@ -428,8 +400,8 @@ def settle_closed_pnl(
|
|||||||
if pc in ("ETH", "BTC"):
|
if pc in ("ETH", "BTC"):
|
||||||
open_idx = _safe_float(open_index_px)
|
open_idx = _safe_float(open_index_px)
|
||||||
sell_px = _safe_float(index_px)
|
sell_px = _safe_float(index_px)
|
||||||
if open_idx is not None and open_idx > 0 and sell_px is not None and sell_px > 0:
|
if sell_px is not None and sell_px > 0:
|
||||||
out = settle_sim_coin_pnl_usdt(
|
return settle_sim_coin_pnl_usdt(
|
||||||
premium_paid=paid,
|
premium_paid=paid,
|
||||||
premium_received=recv,
|
premium_received=recv,
|
||||||
open_fee=ofee,
|
open_fee=ofee,
|
||||||
@@ -439,8 +411,6 @@ def settle_closed_pnl(
|
|||||||
fee_ccy=fee_ccy,
|
fee_ccy=fee_ccy,
|
||||||
premium_ccy=pc,
|
premium_ccy=pc,
|
||||||
)
|
)
|
||||||
return out
|
|
||||||
# 无开仓指数:回退净币×平仓价
|
|
||||||
return freeze_pnl_to_usdt(
|
return freeze_pnl_to_usdt(
|
||||||
net,
|
net,
|
||||||
premium_ccy=pc,
|
premium_ccy=pc,
|
||||||
|
|||||||
@@ -607,10 +607,7 @@ def sync_options_from_local_trades(
|
|||||||
conn=conn,
|
conn=conn,
|
||||||
bridge_id=r["bridge_id"],
|
bridge_id=r["bridge_id"],
|
||||||
)
|
)
|
||||||
if pnl_src == "pending_sell":
|
if pnl_u is not None and pnl_src not in ("none", "pending_sell"):
|
||||||
status_label = "待卖回"
|
|
||||||
pnl_u = None
|
|
||||||
elif pnl_u is not None and pnl_src not in ("none", "pending_sell"):
|
|
||||||
try:
|
try:
|
||||||
# 卖回 VWAP 写入 close;开仓价/初始权利金仅缺省时补
|
# 卖回 VWAP 写入 close;开仓价/初始权利金仅缺省时补
|
||||||
heal_initial = stored_initial
|
heal_initial = stored_initial
|
||||||
|
|||||||
@@ -229,7 +229,7 @@
|
|||||||
<span class="or-step" aria-hidden="true">1</span>
|
<span class="or-step" aria-hidden="true">1</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="or-section-title" id="or-list-title">期权交易记录</div>
|
<div class="or-section-title" id="or-list-title">期权交易记录</div>
|
||||||
<p class="or-section-desc">真盈亏=历史币盈亏×卖回价 − 权利金×(开仓价−卖回价);开/卖回价为多笔 VWAP。卖回未完标「待卖回」,缺价标「缺价未核」。期权历史仅记币收益。</p>
|
<p class="or-section-desc">真盈亏=期权历史币盈亏×平仓价(有卖回 VWAP 优先,否则用冻结平仓价)。缺价标「缺价未核」。期权历史仅记币收益。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="options-strike-table-wrap or-trades-table-wrap" id="or-trades-wrap">
|
<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>标的/合约</th>
|
||||||
<th>初始权利金</th>
|
<th>初始权利金</th>
|
||||||
<th title="币盈亏×卖回价 − 权利金×(开仓价−卖回价)">真盈亏</th>
|
<th title="期权历史币盈亏×平仓价">真盈亏</th>
|
||||||
<th>盈利率</th>
|
<th>盈利率</th>
|
||||||
<th title="仅期权开/平手续费">手续费</th>
|
<th title="仅期权开/平手续费">手续费</th>
|
||||||
<th>开仓时间</th>
|
<th>开仓时间</th>
|
||||||
|
|||||||
@@ -187,10 +187,7 @@ def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
|||||||
initial_u = round(float(paid) * float(use_open), 4)
|
initial_u = round(float(paid) * float(use_open), 4)
|
||||||
new_u = true_pnl_usdt_premium_move(
|
new_u = true_pnl_usdt_premium_move(
|
||||||
coin_pnl=coin,
|
coin_pnl=coin,
|
||||||
premium_paid=paid,
|
|
||||||
sell_back_px=close_px or old_close,
|
sell_back_px=close_px or old_close,
|
||||||
initial_premium_usdt=initial_u,
|
|
||||||
open_index_px=use_open,
|
|
||||||
)
|
)
|
||||||
item = {
|
item = {
|
||||||
"id": tid,
|
"id": tid,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class AccountPnlSettleTests(unittest.TestCase):
|
|||||||
conn = sqlite3.connect(":memory:")
|
conn = sqlite3.connect(":memory:")
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
init_options_tables(conn)
|
init_options_tables(conn)
|
||||||
# coin_pnl=0.01, open=90000, close=80000 → A=700; 账户差分会是 947 但不采用
|
# coin_pnl=0.01, close=80000 → U=800; 账户差分不采用
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO options_trades(
|
INSERT INTO options_trades(
|
||||||
@@ -40,7 +40,7 @@ class AccountPnlSettleTests(unittest.TestCase):
|
|||||||
"SELECT trading_usdt_after, realized_pnl_usdt, pnl_settled FROM options_trades WHERE id=1"
|
"SELECT trading_usdt_after, realized_pnl_usdt, pnl_settled FROM options_trades WHERE id=1"
|
||||||
).fetchone()
|
).fetchone()
|
||||||
self.assertAlmostEqual(float(row["trading_usdt_after"]), 1947.05, places=2)
|
self.assertAlmostEqual(float(row["trading_usdt_after"]), 1947.05, places=2)
|
||||||
self.assertAlmostEqual(float(row["realized_pnl_usdt"]), 700.0, places=2)
|
self.assertAlmostEqual(float(row["realized_pnl_usdt"]), 800.0, places=2)
|
||||||
self.assertEqual(int(row["pnl_settled"]), 1)
|
self.assertEqual(int(row["pnl_settled"]), 1)
|
||||||
|
|
||||||
# 已冻结不再改
|
# 已冻结不再改
|
||||||
@@ -51,7 +51,7 @@ class AccountPnlSettleTests(unittest.TestCase):
|
|||||||
row2 = conn.execute(
|
row2 = conn.execute(
|
||||||
"SELECT realized_pnl_usdt FROM options_trades WHERE id=1"
|
"SELECT realized_pnl_usdt FROM options_trades WHERE id=1"
|
||||||
).fetchone()
|
).fetchone()
|
||||||
self.assertAlmostEqual(float(row2["realized_pnl_usdt"]), 700.0, places=2)
|
self.assertAlmostEqual(float(row2["realized_pnl_usdt"]), 800.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def test_stats_use_coin_times_sell_px_not_account(self):
|
def test_stats_use_coin_times_sell_px_not_account(self):
|
||||||
@@ -158,17 +158,15 @@ class AccountPnlSettleTests(unittest.TestCase):
|
|||||||
true_pnl_usdt_premium_move,
|
true_pnl_usdt_premium_move,
|
||||||
)
|
)
|
||||||
|
|
||||||
# (0.01+0.01)*80000 - 0.01*90000 = 700
|
# 0.01*80000 = 800
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
float(
|
float(
|
||||||
true_pnl_usdt_premium_move(
|
true_pnl_usdt_premium_move(
|
||||||
coin_pnl=0.01,
|
coin_pnl=0.01,
|
||||||
premium_paid=0.01,
|
|
||||||
sell_back_px=80000,
|
sell_back_px=80000,
|
||||||
initial_premium_usdt=900.0,
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
700.0,
|
800.0,
|
||||||
places=4,
|
places=4,
|
||||||
)
|
)
|
||||||
pnl, src = resolve_true_pnl_usdt_from_trade(
|
pnl, src = resolve_true_pnl_usdt_from_trade(
|
||||||
@@ -180,7 +178,7 @@ class AccountPnlSettleTests(unittest.TestCase):
|
|||||||
premium_ccy="BTC",
|
premium_ccy="BTC",
|
||||||
)
|
)
|
||||||
self.assertEqual(src, "premium_move")
|
self.assertEqual(src, "premium_move")
|
||||||
self.assertAlmostEqual(float(pnl), 700.0, places=4)
|
self.assertAlmostEqual(float(pnl), 800.0, places=4)
|
||||||
|
|
||||||
def test_settle_blocked_when_sellable_coin(self):
|
def test_settle_blocked_when_sellable_coin(self):
|
||||||
from lib.options.options_account_pnl_lib import try_settle_after_sellback
|
from lib.options.options_account_pnl_lib import try_settle_after_sellback
|
||||||
|
|||||||
@@ -26,37 +26,18 @@ class FeeConvertTests(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class PremiumMoveFormulaTests(unittest.TestCase):
|
class PremiumMoveFormulaTests(unittest.TestCase):
|
||||||
def test_user_formula_matches_coin_basis_minus_initial(self):
|
def test_option_pnl_times_close_px(self):
|
||||||
coin = 0.01
|
coin = 0.01
|
||||||
paid = 0.01
|
|
||||||
open_px = 90000.0
|
|
||||||
close_px = 80000.0
|
close_px = 80000.0
|
||||||
initial = paid * open_px
|
|
||||||
a = true_pnl_usdt_premium_move(
|
a = true_pnl_usdt_premium_move(
|
||||||
coin_pnl=coin,
|
coin_pnl=coin,
|
||||||
premium_paid=paid,
|
|
||||||
sell_back_px=close_px,
|
sell_back_px=close_px,
|
||||||
initial_premium_usdt=initial,
|
|
||||||
)
|
)
|
||||||
# (权利金+盈亏)×卖回 − 初始权利金U
|
self.assertAlmostEqual(float(a), coin * close_px, places=4)
|
||||||
expect = (paid + coin) * close_px - initial
|
|
||||||
self.assertAlmostEqual(float(a), expect, places=4)
|
|
||||||
# 无费时 ≡ 收回×平 − 已付×开
|
|
||||||
recv = paid + coin
|
|
||||||
self.assertAlmostEqual(float(a), recv * close_px - paid * open_px, places=4)
|
|
||||||
# 缺初始时用开仓价补算,结果相同
|
|
||||||
b = true_pnl_usdt_premium_move(
|
|
||||||
coin_pnl=coin,
|
|
||||||
premium_paid=paid,
|
|
||||||
sell_back_px=close_px,
|
|
||||||
open_index_px=open_px,
|
|
||||||
)
|
|
||||||
self.assertAlmostEqual(float(b), float(a), places=4)
|
|
||||||
|
|
||||||
|
|
||||||
class SettleClosedPnlTests(unittest.TestCase):
|
class SettleClosedPnlTests(unittest.TestCase):
|
||||||
def test_sim_net_times_close_index(self):
|
def test_sim_net_times_close_index(self):
|
||||||
# 无开仓指数时回退净币×平仓价
|
|
||||||
s = settle_closed_pnl(
|
s = settle_closed_pnl(
|
||||||
premium_paid=0.01,
|
premium_paid=0.01,
|
||||||
premium_received=0.02,
|
premium_received=0.02,
|
||||||
@@ -68,23 +49,23 @@ class SettleClosedPnlTests(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(float(s["realized_pnl"]), 0.009, places=8)
|
self.assertAlmostEqual(float(s["realized_pnl"]), 0.009, places=8)
|
||||||
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 27.0, places=4)
|
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 27.0, places=4)
|
||||||
|
|
||||||
def test_sim_proceeds_minus_open_premium_usdt(self):
|
def test_sim_coin_pnl_times_close(self):
|
||||||
"""盈利=(收回币)×卖回价 − 已付币×开仓指数 − 手续费U."""
|
"""真盈亏=净币盈亏×平仓价."""
|
||||||
s = settle_closed_pnl(
|
s = settle_closed_pnl(
|
||||||
premium_paid=0.011005,
|
premium_paid=0.011005,
|
||||||
premium_received=0.02270864,
|
premium_received=0.02270864,
|
||||||
open_fee=0,
|
open_fee=0,
|
||||||
close_fee=0,
|
close_fee=0,
|
||||||
premium_ccy="BTC",
|
premium_ccy="BTC",
|
||||||
index_px=81000, # 卖回价
|
index_px=81000,
|
||||||
open_index_px=86000, # 初始 U 权利金用开仓指数
|
open_index_px=86000,
|
||||||
)
|
)
|
||||||
# 0.02270864*81000 - 0.011005*86000
|
net = 0.02270864 - 0.011005
|
||||||
expect = 0.02270864 * 81000 - 0.011005 * 86000
|
expect = net * 81000
|
||||||
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), expect, places=2)
|
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), expect, places=2)
|
||||||
|
|
||||||
def test_sim_loss_remaining_minus_fees(self):
|
def test_sim_loss_remaining_minus_fees(self):
|
||||||
"""亏损:费先扣进币盈亏,再按用户式折 U(不另扣手续费U)."""
|
"""亏损:费先扣进币盈亏,再×平仓价."""
|
||||||
s = settle_closed_pnl(
|
s = settle_closed_pnl(
|
||||||
premium_paid=0.01,
|
premium_paid=0.01,
|
||||||
premium_received=0.004,
|
premium_received=0.004,
|
||||||
@@ -94,34 +75,26 @@ class SettleClosedPnlTests(unittest.TestCase):
|
|||||||
index_px=2500,
|
index_px=2500,
|
||||||
open_index_px=3000,
|
open_index_px=3000,
|
||||||
)
|
)
|
||||||
# net_coin=-0.0062; U=-0.0062*2500 - 0.01*(3000-2500)=-20.5
|
|
||||||
self.assertAlmostEqual(float(s["realized_pnl"]), -0.0062, places=8)
|
self.assertAlmostEqual(float(s["realized_pnl"]), -0.0062, places=8)
|
||||||
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), -20.5, places=2)
|
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), -15.5, places=2)
|
||||||
|
|
||||||
def test_resolve_loss_does_not_deduct_fee_again(self):
|
def test_resolve_loss_does_not_deduct_fee_again(self):
|
||||||
"""历史币盈亏已含费;resolve 只跑用户式,不再用 open_fee/close_fee 扣第二遍."""
|
|
||||||
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
||||||
|
|
||||||
# coin 已是 recv-paid-fees=-0.0062
|
|
||||||
pnl, src = resolve_true_pnl_usdt_from_trade(
|
pnl, src = resolve_true_pnl_usdt_from_trade(
|
||||||
premium_paid=0.01,
|
premium_paid=0.01,
|
||||||
premium_received=0.004,
|
premium_received=0.004,
|
||||||
realized_pnl=-0.0062,
|
realized_pnl=-0.0062,
|
||||||
open_index_px=3000,
|
open_index_px=3000,
|
||||||
close_index_px=2500,
|
close_index_px=2500,
|
||||||
initial_premium_usdt=30.0,
|
|
||||||
open_fee=0.0001,
|
open_fee=0.0001,
|
||||||
close_fee=0.0001,
|
close_fee=0.0001,
|
||||||
premium_ccy="ETH",
|
premium_ccy="ETH",
|
||||||
)
|
)
|
||||||
self.assertEqual(src, "premium_move")
|
self.assertEqual(src, "premium_move")
|
||||||
# (0.01-0.0062)*2500 - 30 = 9.5 - 30 = -20.5
|
self.assertAlmostEqual(float(pnl), -15.5, places=2)
|
||||||
self.assertAlmostEqual(float(pnl), -20.5, places=2)
|
|
||||||
# 若再扣一遍费会更亏于 -20.5
|
|
||||||
self.assertGreater(float(pnl), -20.55)
|
|
||||||
|
|
||||||
def test_budget_ignored_for_usdt(self):
|
def test_budget_ignored_for_usdt(self):
|
||||||
# budget 不得抬高开仓成本
|
|
||||||
s = settle_closed_pnl(
|
s = settle_closed_pnl(
|
||||||
premium_paid=0.01,
|
premium_paid=0.01,
|
||||||
premium_received=0.02,
|
premium_received=0.02,
|
||||||
@@ -158,7 +131,6 @@ class SettleClosedPnlTests(unittest.TestCase):
|
|||||||
|
|
||||||
class SimPremiumPnlStatsTests(unittest.TestCase):
|
class SimPremiumPnlStatsTests(unittest.TestCase):
|
||||||
def test_sync_sim_no_longer_overwrites_account_settled(self):
|
def test_sync_sim_no_longer_overwrites_account_settled(self):
|
||||||
"""账户结算后 sync 兼容入口不得改写."""
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from lib.options.options_db import init_options_tables
|
from lib.options.options_db import init_options_tables
|
||||||
@@ -235,13 +207,11 @@ class ExchangeFreezeStatsTests(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
frozen = apply_exchange_pnl_freeze(conn, history, _Ex())
|
frozen = apply_exchange_pnl_freeze(conn, history, _Ex())
|
||||||
converted = history_pnl_to_usdt(frozen, _Ex())
|
converted = history_pnl_to_usdt(frozen, _Ex())
|
||||||
# 0.000138*100000=13.8; -0.00648*2500=-16.2; net=-2.4
|
|
||||||
self.assertAlmostEqual(float(converted[0]["realized_pnl"]), 13.8, places=4)
|
self.assertAlmostEqual(float(converted[0]["realized_pnl"]), 13.8, places=4)
|
||||||
self.assertAlmostEqual(float(converted[1]["realized_pnl"]), -16.2, places=4)
|
self.assertAlmostEqual(float(converted[1]["realized_pnl"]), -16.2, places=4)
|
||||||
stats = compute_options_stats_from_history(converted)
|
stats = compute_options_stats_from_history(converted)
|
||||||
self.assertAlmostEqual(float(stats["net_realized_pnl"]), -2.4, places=4)
|
self.assertAlmostEqual(float(stats["net_realized_pnl"]), -2.4, places=4)
|
||||||
|
|
||||||
# 指数再变也不改冻结 U
|
|
||||||
class _Ex2:
|
class _Ex2:
|
||||||
def public_get_market_ticker(self, params):
|
def public_get_market_ticker(self, params):
|
||||||
return {"data": [{"last": "999999"}]}
|
return {"data": [{"last": "999999"}]}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""复盘真盈亏=(权利金+币盈亏)×卖回价 − 初始权利金U."""
|
"""复盘真盈亏=期权历史币盈亏×平仓价."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
@@ -7,7 +7,7 @@ import unittest
|
|||||||
|
|
||||||
class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
||||||
def test_refresh_recomputes_u_keeps_frozen_qty_px(self):
|
def test_refresh_recomputes_u_keeps_frozen_qty_px(self):
|
||||||
"""刷新按公式重算U;权利金币/币盈亏不变."""
|
"""刷新按币盈亏×平仓价重算U;币盈亏/平仓价不变."""
|
||||||
from lib.options.options_db import init_options_tables
|
from lib.options.options_db import init_options_tables
|
||||||
from lib.options.options_review_lib import sync_options_from_local_trades
|
from lib.options.options_review_lib import sync_options_from_local_trades
|
||||||
from lib.sim.mode_lib import MODE_SIM, TRADING_MODE_KEY
|
from lib.sim.mode_lib import MODE_SIM, TRADING_MODE_KEY
|
||||||
@@ -41,22 +41,21 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
trade = conn.execute(
|
trade = conn.execute(
|
||||||
"SELECT realized_pnl_usdt, open_index_px, close_index_px, pnl_settled, realized_pnl, premium_paid FROM options_trades WHERE id=1"
|
"SELECT realized_pnl_usdt, open_index_px, close_index_px, pnl_settled, realized_pnl, premium_paid FROM options_trades WHERE id=1"
|
||||||
).fetchone()
|
).fetchone()
|
||||||
# 0.01*80000 - 0.01*(90000-80000)=700;旧错账 -51 被刷新纠正
|
# 0.01*80000=800;旧错账 -51 被刷新纠正
|
||||||
self.assertAlmostEqual(float(trade["realized_pnl_usdt"]), 700.0, places=2)
|
self.assertAlmostEqual(float(trade["realized_pnl_usdt"]), 800.0, places=2)
|
||||||
self.assertAlmostEqual(float(trade["open_index_px"]), 90000.0, places=2)
|
self.assertAlmostEqual(float(trade["open_index_px"]), 90000.0, places=2)
|
||||||
self.assertAlmostEqual(float(trade["close_index_px"]), 80000.0, places=2)
|
self.assertAlmostEqual(float(trade["close_index_px"]), 80000.0, places=2)
|
||||||
self.assertEqual(int(trade["pnl_settled"]), 1)
|
self.assertEqual(int(trade["pnl_settled"]), 1)
|
||||||
self.assertAlmostEqual(float(trade["realized_pnl"]), 0.01, places=8)
|
self.assertAlmostEqual(float(trade["realized_pnl"]), 0.01, places=8)
|
||||||
self.assertAlmostEqual(float(trade["premium_paid"]), 0.01, places=8)
|
|
||||||
row = conn.execute(
|
row = conn.execute(
|
||||||
"SELECT realized_pnl_total FROM options_review_trades WHERE history_key=?",
|
"SELECT realized_pnl_total FROM options_review_trades WHERE history_key=?",
|
||||||
("local_opt:1",),
|
("local_opt:1",),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
self.assertAlmostEqual(float(row["realized_pnl_total"]), 700.0, places=2)
|
self.assertAlmostEqual(float(row["realized_pnl_total"]), 800.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def test_refresh_prefers_bridge_sell_vwap_over_wrong_index(self):
|
def test_refresh_prefers_bridge_sell_vwap_over_wrong_index(self):
|
||||||
"""库内卖回价若是指数占位,刷新须用桥上卖回 VWAP;初始权利金U冻结不改."""
|
"""有卖回 VWAP 时优先用它折U."""
|
||||||
from lib.options.options_db import init_options_tables
|
from lib.options.options_db import init_options_tables
|
||||||
from lib.options.options_review_lib import sync_options_from_local_trades
|
from lib.options.options_review_lib import sync_options_from_local_trades
|
||||||
from lib.options.options_spot_bridge_lib import (
|
from lib.options.options_spot_bridge_lib import (
|
||||||
@@ -86,13 +85,13 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
inst_id, underlying, opt_type, sheets, eth_amount,
|
inst_id, underlying, opt_type, sheets, eth_amount,
|
||||||
premium_paid, premium_received, realized_pnl, premium_ccy,
|
premium_paid, premium_received, realized_pnl, premium_ccy,
|
||||||
open_fee, close_fee, status, realized_pnl_usdt,
|
open_fee, close_fee, status, realized_pnl_usdt,
|
||||||
open_index_px, close_index_px, initial_premium_usdt, pnl_settled, bridge_id,
|
open_index_px, close_index_px, pnl_settled, bridge_id,
|
||||||
created_at, closed_at
|
created_at, closed_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
|
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
|
||||||
0.01, 0.02, 0.01, 'BTC',
|
0.01, 0.02, 0.01, 'BTC',
|
||||||
0, 0, 'closed', 1544.0,
|
0, 0, 'closed', 1544.0,
|
||||||
88086.0, 85400.0, 840.10, 1, ?,
|
88086.0, 85400.0, 1, ?,
|
||||||
'2026-10-12 01:46:50', '2026-10-13 08:07:35'
|
'2026-10-12 01:46:50', '2026-10-13 08:07:35'
|
||||||
)
|
)
|
||||||
""",
|
""",
|
||||||
@@ -101,14 +100,11 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
sync_options_from_local_trades(conn, None)
|
sync_options_from_local_trades(conn, None)
|
||||||
trade = conn.execute(
|
trade = conn.execute(
|
||||||
"SELECT realized_pnl_usdt, open_index_px, close_index_px, initial_premium_usdt FROM options_trades WHERE id=1"
|
"SELECT realized_pnl_usdt, close_index_px FROM options_trades WHERE id=1"
|
||||||
).fetchone()
|
).fetchone()
|
||||||
# 已冻开仓价/初始权利金不覆盖;卖回价用桥 VWAP
|
|
||||||
self.assertAlmostEqual(float(trade["open_index_px"]), 88086.0, places=2)
|
|
||||||
self.assertAlmostEqual(float(trade["initial_premium_usdt"]), 840.10, places=2)
|
|
||||||
self.assertAlmostEqual(float(trade["close_index_px"]), 81418.0, places=2)
|
self.assertAlmostEqual(float(trade["close_index_px"]), 81418.0, places=2)
|
||||||
# (0.01+0.01)*81418 - 840.10 = 788.26
|
# 0.01*81418
|
||||||
self.assertAlmostEqual(float(trade["realized_pnl_usdt"]), 788.26, places=2)
|
self.assertAlmostEqual(float(trade["realized_pnl_usdt"]), 814.18, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def test_premium_usdt_move_formula(self):
|
def test_premium_usdt_move_formula(self):
|
||||||
@@ -122,9 +118,6 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
init_options_tables(conn)
|
init_options_tables(conn)
|
||||||
ensure_runtime_settings_table(conn)
|
ensure_runtime_settings_table(conn)
|
||||||
runtime_set(conn, TRADING_MODE_KEY, MODE_SIM)
|
runtime_set(conn, TRADING_MODE_KEY, MODE_SIM)
|
||||||
# paid=0.01, recv=0.02, coin_pnl=0.01; open=90000, close=80000
|
|
||||||
# A = 0.01*80000 - (0.01*90000 - 0.01*80000) = 800 - 100 = 700
|
|
||||||
# 亦= recv*close - paid*open = 1600 - 900 = 700
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO options_trades(
|
INSERT INTO options_trades(
|
||||||
@@ -151,12 +144,11 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
"SELECT realized_pnl_total FROM options_review_trades WHERE history_key=?",
|
"SELECT realized_pnl_total FROM options_review_trades WHERE history_key=?",
|
||||||
("local_opt:1",),
|
("local_opt:1",),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
self.assertAlmostEqual(float(row["realized_pnl_total"]), 700.0, places=2)
|
self.assertAlmostEqual(float(row["realized_pnl_total"]), 800.0, places=2)
|
||||||
# 纠偏写回,不用账户差分 1506
|
|
||||||
healed = conn.execute(
|
healed = conn.execute(
|
||||||
"SELECT realized_pnl_usdt FROM options_trades WHERE id=1"
|
"SELECT realized_pnl_usdt FROM options_trades WHERE id=1"
|
||||||
).fetchone()
|
).fetchone()
|
||||||
self.assertAlmostEqual(float(healed["realized_pnl_usdt"]), 700.0, places=2)
|
self.assertAlmostEqual(float(healed["realized_pnl_usdt"]), 800.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def test_uncomputable_old_trade_marked(self):
|
def test_uncomputable_old_trade_marked(self):
|
||||||
@@ -170,7 +162,6 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
init_options_tables(conn)
|
init_options_tables(conn)
|
||||||
ensure_runtime_settings_table(conn)
|
ensure_runtime_settings_table(conn)
|
||||||
runtime_set(conn, TRADING_MODE_KEY, MODE_SIM)
|
runtime_set(conn, TRADING_MODE_KEY, MODE_SIM)
|
||||||
# 未冻结且无开仓/卖回价 → 缺价未核(不瞎写)
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO options_trades(
|
INSERT INTO options_trades(
|
||||||
@@ -233,7 +224,6 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
"SELECT realized_pnl_total FROM options_review_trades WHERE history_key=?",
|
"SELECT realized_pnl_total FROM options_review_trades WHERE history_key=?",
|
||||||
("local_opt:1",),
|
("local_opt:1",),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
# 开平价相同:波动项为0 → 等于币×卖回价=850
|
|
||||||
self.assertAlmostEqual(float(row["realized_pnl_total"]), 850.0, places=2)
|
self.assertAlmostEqual(float(row["realized_pnl_total"]), 850.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ class SpotVwapBridgeTests(unittest.TestCase):
|
|||||||
bid = insert_bridge(conn, underlying="BTC", status="holding", budget_usdt=2200)
|
bid = insert_bridge(conn, underlying="BTC", status="holding", budget_usdt=2200)
|
||||||
add_bridge_buy_fill(conn, bid, usdt_spent=1000.0, coin_bought=0.5)
|
add_bridge_buy_fill(conn, bid, usdt_spent=1000.0, coin_bought=0.5)
|
||||||
add_bridge_buy_fill(conn, bid, usdt_spent=1200.0, coin_bought=0.6)
|
add_bridge_buy_fill(conn, bid, usdt_spent=1200.0, coin_bought=0.6)
|
||||||
# 2200 / 1.1 = 2000
|
|
||||||
self.assertAlmostEqual(float(bridge_buy_vwap(conn, bid)), 2000.0, places=4)
|
self.assertAlmostEqual(float(bridge_buy_vwap(conn, bid)), 2000.0, places=4)
|
||||||
|
|
||||||
add_bridge_close_sell_fill(conn, bid, usdt_recovered=800.0, coin_sold=0.4)
|
add_bridge_close_sell_fill(conn, bid, usdt_recovered=800.0, coin_sold=0.4)
|
||||||
add_bridge_close_sell_fill(conn, bid, usdt_recovered=1200.0, coin_sold=0.6)
|
add_bridge_close_sell_fill(conn, bid, usdt_recovered=1200.0, coin_sold=0.6)
|
||||||
# 2000 / 1.0 = 2000
|
|
||||||
self.assertAlmostEqual(float(bridge_sell_vwap(conn, bid)), 2000.0, places=4)
|
self.assertAlmostEqual(float(bridge_sell_vwap(conn, bid)), 2000.0, places=4)
|
||||||
update_bridge(conn, bid, status=BRIDGE_CLOSED, close=True)
|
update_bridge(conn, bid, status=BRIDGE_CLOSED, close=True)
|
||||||
self.assertAlmostEqual(float(bridge_sell_vwap(conn, bid)), 2000.0, places=4)
|
self.assertAlmostEqual(float(bridge_sell_vwap(conn, bid)), 2000.0, places=4)
|
||||||
@@ -56,15 +54,13 @@ class SpotVwapBridgeTests(unittest.TestCase):
|
|||||||
add_bridge_close_sell_fill(conn, bid, usdt_recovered=1600.0, coin_sold=0.8)
|
add_bridge_close_sell_fill(conn, bid, usdt_recovered=1600.0, coin_sold=0.8)
|
||||||
add_bridge_close_sell_fill(conn, bid, usdt_recovered=400.0, coin_sold=0.2)
|
add_bridge_close_sell_fill(conn, bid, usdt_recovered=400.0, coin_sold=0.2)
|
||||||
update_bridge(conn, bid, status=BRIDGE_CLOSED, close=True)
|
update_bridge(conn, bid, status=BRIDGE_CLOSED, close=True)
|
||||||
# buy_vwap=2000, sell_vwap=2000; 初始权利金按买 VWAP=20
|
# sell_vwap=2000; U=0.01*2000=20; 不用指数 80000
|
||||||
# U=(0.01+0.01)*2000 - 20 = 20; 若误用指数 80000 会得到大数
|
|
||||||
pnl, src = resolve_true_pnl_usdt_from_trade(
|
pnl, src = resolve_true_pnl_usdt_from_trade(
|
||||||
premium_paid=0.01,
|
premium_paid=0.01,
|
||||||
premium_received=0.02,
|
premium_received=0.02,
|
||||||
realized_pnl=0.01,
|
realized_pnl=0.01,
|
||||||
open_index_px=2000.0,
|
open_index_px=90000.0,
|
||||||
close_index_px=80000.0,
|
close_index_px=80000.0,
|
||||||
initial_premium_usdt=20.0,
|
|
||||||
premium_ccy="BTC",
|
premium_ccy="BTC",
|
||||||
conn=conn,
|
conn=conn,
|
||||||
bridge_id=bid,
|
bridge_id=bid,
|
||||||
@@ -73,13 +69,12 @@ class SpotVwapBridgeTests(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(float(pnl), 20.0, places=2)
|
self.assertAlmostEqual(float(pnl), 20.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def test_pending_sell_blocks_true_pnl(self):
|
def test_pending_sell_still_allows_option_pnl(self):
|
||||||
|
"""现货待卖回不阻断期权盈亏折U(用冻结平仓价)."""
|
||||||
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
||||||
from lib.options.options_spot_bridge_lib import (
|
from lib.options.options_spot_bridge_lib import (
|
||||||
BRIDGE_HOLDING,
|
|
||||||
BRIDGE_PENDING_SELL,
|
BRIDGE_PENDING_SELL,
|
||||||
add_bridge_buy_fill,
|
add_bridge_buy_fill,
|
||||||
add_bridge_close_sell_fill,
|
|
||||||
ensure_bridge_table,
|
ensure_bridge_table,
|
||||||
insert_bridge,
|
insert_bridge,
|
||||||
update_bridge,
|
update_bridge,
|
||||||
@@ -90,7 +85,6 @@ class SpotVwapBridgeTests(unittest.TestCase):
|
|||||||
ensure_bridge_table(conn)
|
ensure_bridge_table(conn)
|
||||||
bid = insert_bridge(conn, underlying="ETH", status="holding", budget_usdt=3000)
|
bid = insert_bridge(conn, underlying="ETH", status="holding", budget_usdt=3000)
|
||||||
add_bridge_buy_fill(conn, bid, usdt_spent=3000.0, coin_bought=1.0)
|
add_bridge_buy_fill(conn, bid, usdt_spent=3000.0, coin_bought=1.0)
|
||||||
add_bridge_close_sell_fill(conn, bid, usdt_recovered=1500.0, coin_sold=0.5)
|
|
||||||
update_bridge(conn, bid, status=BRIDGE_PENDING_SELL)
|
update_bridge(conn, bid, status=BRIDGE_PENDING_SELL)
|
||||||
pnl, src = resolve_true_pnl_usdt_from_trade(
|
pnl, src = resolve_true_pnl_usdt_from_trade(
|
||||||
premium_paid=0.01,
|
premium_paid=0.01,
|
||||||
@@ -102,26 +96,12 @@ class SpotVwapBridgeTests(unittest.TestCase):
|
|||||||
conn=conn,
|
conn=conn,
|
||||||
bridge_id=bid,
|
bridge_id=bid,
|
||||||
)
|
)
|
||||||
self.assertEqual(src, "pending_sell")
|
self.assertEqual(src, "premium_move")
|
||||||
self.assertIsNone(pnl)
|
self.assertAlmostEqual(float(pnl), 30.0, places=2)
|
||||||
# holding 未关桥同样不得用指数提前结算
|
|
||||||
update_bridge(conn, bid, status=BRIDGE_HOLDING)
|
|
||||||
pnl2, src2 = resolve_true_pnl_usdt_from_trade(
|
|
||||||
premium_paid=0.01,
|
|
||||||
premium_received=0.02,
|
|
||||||
realized_pnl=0.01,
|
|
||||||
open_index_px=3000.0,
|
|
||||||
close_index_px=3000.0,
|
|
||||||
premium_ccy="ETH",
|
|
||||||
conn=conn,
|
|
||||||
bridge_id=bid,
|
|
||||||
)
|
|
||||||
self.assertEqual(src2, "pending_sell")
|
|
||||||
self.assertIsNone(pnl2)
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def test_closed_bridge_without_sell_vwap_is_missing(self):
|
def test_closed_bridge_without_sell_falls_back_close_index(self):
|
||||||
"""已关桥但无 close_sell 时不得用指数冒充卖回价."""
|
"""已关桥无卖回 VWAP 时回退冻结平仓价."""
|
||||||
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
||||||
from lib.options.options_spot_bridge_lib import (
|
from lib.options.options_spot_bridge_lib import (
|
||||||
BRIDGE_CLOSED,
|
BRIDGE_CLOSED,
|
||||||
@@ -142,17 +122,15 @@ class SpotVwapBridgeTests(unittest.TestCase):
|
|||||||
realized_pnl=0.01,
|
realized_pnl=0.01,
|
||||||
open_index_px=100000.0,
|
open_index_px=100000.0,
|
||||||
close_index_px=90000.0,
|
close_index_px=90000.0,
|
||||||
initial_premium_usdt=1000.0,
|
|
||||||
premium_ccy="BTC",
|
premium_ccy="BTC",
|
||||||
conn=conn,
|
conn=conn,
|
||||||
bridge_id=bid,
|
bridge_id=bid,
|
||||||
)
|
)
|
||||||
self.assertEqual(src, "none")
|
self.assertEqual(src, "premium_move")
|
||||||
self.assertIsNone(pnl)
|
self.assertAlmostEqual(float(pnl), 900.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def test_buy_vwap_ignores_shrunk_coin_bought(self):
|
def test_buy_vwap_ignores_shrunk_coin_bought(self):
|
||||||
"""开仓后 coin_bought 被改成权利金量时,不得用 budget/缩水量当买价."""
|
|
||||||
from lib.options.options_spot_bridge_lib import (
|
from lib.options.options_spot_bridge_lib import (
|
||||||
add_bridge_buy_fill,
|
add_bridge_buy_fill,
|
||||||
bridge_buy_vwap,
|
bridge_buy_vwap,
|
||||||
@@ -166,7 +144,7 @@ class SpotVwapBridgeTests(unittest.TestCase):
|
|||||||
ensure_bridge_table(conn)
|
ensure_bridge_table(conn)
|
||||||
bid = insert_bridge(conn, underlying="BTC", status="holding", budget_usdt=2200)
|
bid = insert_bridge(conn, underlying="BTC", status="holding", budget_usdt=2200)
|
||||||
add_bridge_buy_fill(conn, bid, usdt_spent=2200.0, coin_bought=1.1)
|
add_bridge_buy_fill(conn, bid, usdt_spent=2200.0, coin_bought=1.1)
|
||||||
update_bridge(conn, bid, coin_bought=0.01) # 缩成权利金
|
update_bridge(conn, bid, coin_bought=0.01)
|
||||||
self.assertAlmostEqual(float(bridge_buy_vwap(conn, bid)), 2000.0, places=4)
|
self.assertAlmostEqual(float(bridge_buy_vwap(conn, bid)), 2000.0, places=4)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user