fix无法按新真盈亏公式更新的旧单标记为缺价未核。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -451,6 +451,11 @@
|
|||||||
var badgeExtra = reviewed
|
var badgeExtra = reviewed
|
||||||
? ' <span class="or-badge" style="background:rgba(61,214,140,.2)">已复盘</span>'
|
? ' <span class="or-badge" style="background:rgba(61,214,140,.2)">已复盘</span>'
|
||||||
: "";
|
: "";
|
||||||
|
var needsMark = String(t.status_raw || "") === "缺价未核";
|
||||||
|
if (needsMark) {
|
||||||
|
badgeExtra +=
|
||||||
|
' <span class="or-badge" style="background:rgba(255,170,60,.28)" title="缺开仓价或卖回价,未能按新真盈亏公式更新">缺价未核</span>';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
'<tr class="or-trade-row' +
|
'<tr class="or-trade-row' +
|
||||||
active +
|
active +
|
||||||
@@ -471,7 +476,7 @@
|
|||||||
'<td class="' +
|
'<td class="' +
|
||||||
pnlClass(t.realized_pnl_total) +
|
pnlClass(t.realized_pnl_total) +
|
||||||
'">' +
|
'">' +
|
||||||
fmtPnl(t.realized_pnl_total) +
|
(needsMark ? '<span class="muted" title="未能按新公式更新">—</span>' : fmtPnl(t.realized_pnl_total)) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="' +
|
'<td class="' +
|
||||||
pnlClass(pr) +
|
pnlClass(pr) +
|
||||||
@@ -565,13 +570,18 @@
|
|||||||
var entry = t.entry || {};
|
var entry = t.entry || {};
|
||||||
var direction = t.direction_view || entry.direction_view || "";
|
var direction = t.direction_view || entry.direction_view || "";
|
||||||
var entryLogic = t.entry_logic || entry.entry_logic || "";
|
var entryLogic = t.entry_logic || entry.entry_logic || "";
|
||||||
|
var needsMarkR = String(t.status_raw || "") === "缺价未核";
|
||||||
return (
|
return (
|
||||||
'<tr class="or-reviewed-row" data-id="' +
|
'<tr class="or-reviewed-row" data-id="' +
|
||||||
t.id +
|
t.id +
|
||||||
'">' +
|
'">' +
|
||||||
"<td><span class=\"or-badge\">" +
|
"<td><span class=\"or-badge\">" +
|
||||||
escapeHtml(t.source_label || t.source_type) +
|
escapeHtml(t.source_label || t.source_type) +
|
||||||
"</span></td>" +
|
"</span>" +
|
||||||
|
(needsMarkR
|
||||||
|
? ' <span class="or-badge" style="background:rgba(255,170,60,.28)" title="缺开仓价或卖回价,未能按新真盈亏公式更新">缺价未核</span>'
|
||||||
|
: "") +
|
||||||
|
"</td>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
escapeHtml(tradeContractLabel(t)) +
|
escapeHtml(tradeContractLabel(t)) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
@@ -584,7 +594,7 @@
|
|||||||
'<td class="' +
|
'<td class="' +
|
||||||
pnlClass(t.realized_pnl_total) +
|
pnlClass(t.realized_pnl_total) +
|
||||||
'">' +
|
'">' +
|
||||||
fmtPnl(t.realized_pnl_total) +
|
(needsMarkR ? '<span class="muted" title="未能按新公式更新">—</span>' : fmtPnl(t.realized_pnl_total)) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="' +
|
'<td class="' +
|
||||||
pnlClass(resolveProfitRate(t)) +
|
pnlClass(resolveProfitRate(t)) +
|
||||||
|
|||||||
@@ -576,35 +576,52 @@ def sync_options_from_local_trades(
|
|||||||
close_fee=cfee,
|
close_fee=cfee,
|
||||||
premium_ccy=ccy,
|
premium_ccy=ccy,
|
||||||
)
|
)
|
||||||
|
# 初始权利金可用开仓价,缺则兜底指数;真盈亏必须用冻结的开仓价+卖回价,禁止实时价冒充
|
||||||
open_idx_for_prem = open_idx if (open_idx is not None and open_idx > 0) else idx
|
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
|
sell_idx = close_idx if (close_idx is not None and close_idx > 0) else None
|
||||||
# 真盈亏=币盈亏×卖回价 − (权利金×开仓价 − 权利金×卖回价);忽略现货多买
|
|
||||||
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
|
||||||
|
|
||||||
pnl_u, pnl_src = resolve_true_pnl_usdt_from_trade(
|
pnl_u, pnl_src = resolve_true_pnl_usdt_from_trade(
|
||||||
premium_paid=prem,
|
premium_paid=prem,
|
||||||
premium_received=r["premium_received"],
|
premium_received=r["premium_received"],
|
||||||
realized_pnl=r["realized_pnl"],
|
realized_pnl=r["realized_pnl"],
|
||||||
open_index_px=open_idx_for_prem,
|
open_index_px=open_idx if (open_idx is not None and open_idx > 0) else None,
|
||||||
close_index_px=sell_idx,
|
close_index_px=sell_idx,
|
||||||
open_fee=ofee,
|
open_fee=ofee,
|
||||||
close_fee=cfee,
|
close_fee=cfee,
|
||||||
premium_ccy=ccy,
|
premium_ccy=ccy,
|
||||||
fee_ccy=str(r["premium_ccy"] or ccy or ""),
|
fee_ccy=str(r["premium_ccy"] or ccy or ""),
|
||||||
)
|
)
|
||||||
if (
|
# 能算新公式则写回;算不出则清空旧冻结并标记「缺价未核」
|
||||||
pnl_u is not None
|
status_label = "已平"
|
||||||
and pnl_src != "none"
|
if pnl_u is not None and pnl_src != "none":
|
||||||
and (frozen_usdt is None or abs(float(frozen_usdt) - float(pnl_u)) > 0.05)
|
if 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),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
status_label = "缺价未核"
|
||||||
|
pnl_u = None
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE options_trades
|
UPDATE options_trades
|
||||||
SET realized_pnl_usdt = ?, pnl_settled = 1
|
SET realized_pnl_usdt = NULL, pnl_settled = 0
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
|
AND (
|
||||||
|
COALESCE(pnl_settled, 0) = 1
|
||||||
|
OR realized_pnl_usdt IS NOT NULL
|
||||||
|
)
|
||||||
""",
|
""",
|
||||||
(pnl_u, trade_id),
|
(trade_id,),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -654,7 +671,7 @@ def sync_options_from_local_trades(
|
|||||||
"option_fee_usdt": fee_u,
|
"option_fee_usdt": fee_u,
|
||||||
"created_at": opened_at,
|
"created_at": opened_at,
|
||||||
"closed_at": closed_at,
|
"closed_at": closed_at,
|
||||||
"status_label": "已平",
|
"status_label": status_label,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if action == "inserted":
|
if action == "inserted":
|
||||||
|
|||||||
@@ -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">真盈亏=历史币盈亏×卖回价 − 权利金×(开仓价−卖回价);计入权利金 USDT 波动,忽略现货多买。期权历史仅记币收益。</p>
|
<p class="or-section-desc">真盈亏=历史币盈亏×卖回价 − 权利金×(开仓价−卖回价)。缺开仓/卖回价的旧单标记「缺价未核」,不沿用旧冻结。期权历史仅记币收益。</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">
|
||||||
|
|||||||
@@ -51,12 +51,15 @@ def _ensure_columns(conn: sqlite3.Connection) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
||||||
|
"""按最新口径 A 重算历史:币盈亏×卖回价 − 权利金×(开仓价−卖回价)."""
|
||||||
|
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
|
||||||
|
|
||||||
_ensure_columns(conn)
|
_ensure_columns(conn)
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"""
|
"""
|
||||||
SELECT id, inst_id, premium_paid, premium_received, realized_pnl,
|
SELECT id, inst_id, premium_paid, premium_received, realized_pnl,
|
||||||
open_index_px, close_index_px, budget_usdt,
|
open_index_px, close_index_px, budget_usdt, open_fee, close_fee,
|
||||||
trading_usdt_before, trading_usdt_after,
|
premium_ccy, trading_usdt_before, trading_usdt_after,
|
||||||
realized_pnl_usdt, pnl_settled, bridge_id, initial_premium_usdt
|
realized_pnl_usdt, pnl_settled, bridge_id, initial_premium_usdt
|
||||||
FROM options_trades
|
FROM options_trades
|
||||||
WHERE status='closed'
|
WHERE status='closed'
|
||||||
@@ -72,107 +75,55 @@ def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
|||||||
coin_pnl = _safe_float(r["realized_pnl"])
|
coin_pnl = _safe_float(r["realized_pnl"])
|
||||||
open_idx = _safe_float(r["open_index_px"])
|
open_idx = _safe_float(r["open_index_px"])
|
||||||
close_idx = _safe_float(r["close_index_px"])
|
close_idx = _safe_float(r["close_index_px"])
|
||||||
before = _safe_float(r["trading_usdt_before"])
|
|
||||||
after = _safe_float(r["trading_usdt_after"])
|
|
||||||
settled_u = _safe_float(r["realized_pnl_usdt"])
|
settled_u = _safe_float(r["realized_pnl_usdt"])
|
||||||
settled = int(r["pnl_settled"] or 0) == 1
|
settled = int(r["pnl_settled"] or 0) == 1
|
||||||
initial = _safe_float(r["initial_premium_usdt"])
|
initial = _safe_float(r["initial_premium_usdt"])
|
||||||
|
ccy = str(r["premium_ccy"] or "").strip().upper() or None
|
||||||
|
|
||||||
new_initial = initial
|
new_initial = initial
|
||||||
if new_initial is None and open_idx and paid and open_idx > 0 and paid > 0:
|
if new_initial is None and open_idx and paid and open_idx > 0 and paid > 0:
|
||||||
new_initial = round(open_idx * paid, 4)
|
new_initial = round(open_idx * paid, 4)
|
||||||
|
|
||||||
|
# 口径 B:历史币×卖回价(数据统计)
|
||||||
stats_u = None
|
stats_u = None
|
||||||
if coin_pnl is not None and close_idx is not None and close_idx > 0:
|
if coin_pnl is not None and close_idx is not None and close_idx > 0:
|
||||||
stats_u = round(coin_pnl * close_idx, 4)
|
stats_u = round(coin_pnl * close_idx, 4)
|
||||||
elif coin_pnl is not None and open_idx is not None and open_idx > 0:
|
elif coin_pnl is not None and open_idx is not None and open_idx > 0:
|
||||||
stats_u = round(coin_pnl * open_idx, 4)
|
stats_u = round(coin_pnl * open_idx, 4)
|
||||||
|
|
||||||
# 错账:币盈亏为正、账户冻结为负(典型卖回封顶)
|
# 口径 A:权利金 USDT 波动式(忽略现货多买/账户差分)
|
||||||
suspicious = bool(
|
# 真盈亏必须同时有开仓价与卖回价;缺一则标记缺价未核(不用开仓价冒充卖回价)
|
||||||
settled
|
true_a, src = resolve_true_pnl_usdt_from_trade(
|
||||||
and coin_pnl is not None
|
premium_paid=paid,
|
||||||
and coin_pnl > 0
|
premium_received=recv,
|
||||||
and settled_u is not None
|
realized_pnl=coin_pnl,
|
||||||
and settled_u < 0
|
open_index_px=open_idx if open_idx and open_idx > 0 else None,
|
||||||
|
close_index_px=close_idx if close_idx and close_idx > 0 else None,
|
||||||
|
open_fee=r["open_fee"],
|
||||||
|
close_fee=r["close_fee"],
|
||||||
|
premium_ccy=ccy,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 桥侧:coin_sold ≈ premium 且远小于 recv → 历史上曾按权利金封顶卖
|
|
||||||
bridge_hint = ""
|
|
||||||
bid = r["bridge_id"]
|
|
||||||
coin_bought = coin_sold = None
|
|
||||||
if bid is not None:
|
|
||||||
try:
|
|
||||||
b = conn.execute(
|
|
||||||
"SELECT coin_bought, coin_sold FROM options_spot_bridge WHERE id=?",
|
|
||||||
(int(bid),),
|
|
||||||
).fetchone()
|
|
||||||
if b:
|
|
||||||
coin_bought = _safe_float(b["coin_bought"])
|
|
||||||
coin_sold = _safe_float(b["coin_sold"])
|
|
||||||
if (
|
|
||||||
paid
|
|
||||||
and coin_sold is not None
|
|
||||||
and recv
|
|
||||||
and coin_sold <= paid * 1.05
|
|
||||||
and recv > paid * 1.2
|
|
||||||
):
|
|
||||||
bridge_hint = "sell_capped_at_premium"
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
action = "ok"
|
action = "ok"
|
||||||
new_settled_u = settled_u
|
new_settled_u = settled_u
|
||||||
new_pnl_settled = 1 if settled else 0
|
new_pnl_settled = 1 if settled else 0
|
||||||
|
|
||||||
# A 已为正且与口径 B 接近 → 视为已恢复,勿因桥历史反复 unsettle
|
status_label = "已平"
|
||||||
a_ok_near_b = (
|
if true_a is not None and src != "none":
|
||||||
settled_u is not None
|
if settled_u is None or abs(float(settled_u) - float(true_a)) > 0.05:
|
||||||
and settled_u > 0
|
action = "rewrite_A_premium_move"
|
||||||
and stats_u is not None
|
new_settled_u = round(float(true_a), 4)
|
||||||
and stats_u > 0
|
new_pnl_settled = 1
|
||||||
and abs(settled_u - stats_u) / max(abs(stats_u), 1.0) <= 0.15
|
else:
|
||||||
)
|
action = "ok_premium_move"
|
||||||
|
new_settled_u = round(float(true_a), 4)
|
||||||
# 桥真盈亏=卖回累计−买币预算(口径 A 回退;禁止用 B 冒充 A)
|
new_pnl_settled = 1
|
||||||
bridge_true_a = None
|
else:
|
||||||
if bid is not None:
|
# 缺开仓价/卖回价/币盈亏等:无法按新公式更新 → 标记「缺价未核」
|
||||||
try:
|
action = "mark_uncomputable_A"
|
||||||
b2 = conn.execute(
|
|
||||||
"SELECT status, budget_usdt, usdt_recovered FROM options_spot_bridge WHERE id=?",
|
|
||||||
(int(bid),),
|
|
||||||
).fetchone()
|
|
||||||
if b2 and str(b2["status"] or "") == "closed":
|
|
||||||
spent = _safe_float(b2["budget_usdt"])
|
|
||||||
recovered = _safe_float(b2["usdt_recovered"])
|
|
||||||
if spent is not None and recovered is not None:
|
|
||||||
bridge_true_a = round(float(recovered) - float(spent), 4)
|
|
||||||
except Exception:
|
|
||||||
bridge_true_a = None
|
|
||||||
|
|
||||||
if before is not None and after is not None and (not settled or suspicious):
|
|
||||||
action = "settle_from_snapshots"
|
|
||||||
new_settled_u = round(after - before, 4)
|
|
||||||
new_pnl_settled = 1
|
|
||||||
elif bridge_true_a is not None and (not settled or suspicious):
|
|
||||||
action = "settle_from_bridge_cash"
|
|
||||||
new_settled_u = bridge_true_a
|
|
||||||
new_pnl_settled = 1
|
|
||||||
elif suspicious and not a_ok_near_b:
|
|
||||||
# 负冻结且无快照/桥可重建:解除错误冻结
|
|
||||||
action = "unsettle_bad_account_freeze"
|
|
||||||
new_pnl_settled = 0
|
new_pnl_settled = 0
|
||||||
new_settled_u = None
|
new_settled_u = None
|
||||||
elif (
|
status_label = "缺价未核"
|
||||||
a_ok_near_b
|
|
||||||
and settled_u is not None
|
|
||||||
and settled_u > 0
|
|
||||||
and bridge_hint == "sell_capped_at_premium"
|
|
||||||
):
|
|
||||||
# 仅当 A 已为正且接近 B 时保留,不再用 B 回填假 A
|
|
||||||
action = "accept_recovered_account_A"
|
|
||||||
new_settled_u = round(float(settled_u), 4)
|
|
||||||
new_pnl_settled = 1
|
|
||||||
|
|
||||||
profit_rate = None
|
profit_rate = None
|
||||||
a_for_rate = new_settled_u if new_pnl_settled == 1 else None
|
a_for_rate = new_settled_u if new_pnl_settled == 1 else None
|
||||||
@@ -183,8 +134,7 @@ def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
|||||||
"trade_id": tid,
|
"trade_id": tid,
|
||||||
"inst_id": r["inst_id"],
|
"inst_id": r["inst_id"],
|
||||||
"action": action,
|
"action": action,
|
||||||
"suspicious": suspicious,
|
"pnl_source": src,
|
||||||
"bridge_hint": bridge_hint,
|
|
||||||
"coin_pnl": coin_pnl,
|
"coin_pnl": coin_pnl,
|
||||||
"stats_pnl_usdt_B": stats_u,
|
"stats_pnl_usdt_B": stats_u,
|
||||||
"old_realized_pnl_usdt_A": settled_u,
|
"old_realized_pnl_usdt_A": settled_u,
|
||||||
@@ -193,10 +143,10 @@ def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
|||||||
"new_pnl_settled": new_pnl_settled,
|
"new_pnl_settled": new_pnl_settled,
|
||||||
"initial_premium_usdt": new_initial,
|
"initial_premium_usdt": new_initial,
|
||||||
"profit_rate": profit_rate,
|
"profit_rate": profit_rate,
|
||||||
"coin_bought": coin_bought,
|
|
||||||
"coin_sold": coin_sold,
|
|
||||||
"premium_paid": paid,
|
"premium_paid": paid,
|
||||||
"premium_received": recv,
|
"premium_received": recv,
|
||||||
|
"open_index_px": open_idx,
|
||||||
|
"close_index_px": close_idx,
|
||||||
}
|
}
|
||||||
report.append(rec)
|
report.append(rec)
|
||||||
|
|
||||||
@@ -208,20 +158,16 @@ def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
|||||||
"UPDATE options_trades SET initial_premium_usdt=? WHERE id=?",
|
"UPDATE options_trades SET initial_premium_usdt=? WHERE id=?",
|
||||||
(new_initial, tid),
|
(new_initial, tid),
|
||||||
)
|
)
|
||||||
if action == "unsettle_bad_account_freeze":
|
if action == "mark_uncomputable_A":
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE options_trades
|
UPDATE options_trades
|
||||||
SET pnl_settled=0, realized_pnl_usdt=NULL, trading_usdt_after=NULL
|
SET pnl_settled=0, realized_pnl_usdt=NULL
|
||||||
WHERE id=?
|
WHERE id=?
|
||||||
""",
|
""",
|
||||||
(tid,),
|
(tid,),
|
||||||
)
|
)
|
||||||
elif action in (
|
elif action in ("rewrite_A_premium_move", "ok_premium_move") and new_settled_u is not None:
|
||||||
"settle_from_snapshots",
|
|
||||||
"settle_from_bridge_cash",
|
|
||||||
"accept_recovered_account_A",
|
|
||||||
) and new_settled_u is not None:
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE options_trades
|
UPDATE options_trades
|
||||||
@@ -231,7 +177,7 @@ def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
|||||||
(new_settled_u, tid),
|
(new_settled_u, tid),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 复盘表
|
# 复盘表同步最新 A;无法更新的打「缺价未核」
|
||||||
hk = f"local_opt:{tid}"
|
hk = f"local_opt:{tid}"
|
||||||
rev = conn.execute(
|
rev = conn.execute(
|
||||||
"SELECT id FROM options_review_trades WHERE history_key=?", (hk,)
|
"SELECT id FROM options_review_trades WHERE history_key=?", (hk,)
|
||||||
@@ -253,7 +199,7 @@ def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
|||||||
pnl_total,
|
pnl_total,
|
||||||
pnl_total,
|
pnl_total,
|
||||||
profit_rate,
|
profit_rate,
|
||||||
"待结算" if new_pnl_settled == 0 and before is not None else "已平",
|
status_label,
|
||||||
hk,
|
hk,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -305,13 +251,13 @@ def main() -> int:
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
bad = [x for x in report if x.get("suspicious") or x.get("action") != "ok"]
|
bad = [x for x in report if x.get("action") not in ("ok", "ok_premium_move")]
|
||||||
print(f"scanned={len(report)} need_attention={len(bad)} apply={bool(args.apply)}")
|
print(f"scanned={len(report)} need_attention={len(bad)} apply={bool(args.apply)}")
|
||||||
for x in bad[:50]:
|
for x in bad[:50]:
|
||||||
print(
|
print(
|
||||||
f" #{x['trade_id']} {x['inst_id']} action={x['action']} "
|
f" #{x['trade_id']} {x['inst_id']} action={x['action']} "
|
||||||
f"A:{x['old_realized_pnl_usdt_A']}->{x['new_realized_pnl_usdt_A']} "
|
f"A:{x['old_realized_pnl_usdt_A']}->{x['new_realized_pnl_usdt_A']} "
|
||||||
f"B={x['stats_pnl_usdt_B']} hint={x['bridge_hint']}"
|
f"B={x['stats_pnl_usdt_B']} src={x.get('pnl_source')}"
|
||||||
)
|
)
|
||||||
if args.csv:
|
if args.csv:
|
||||||
out = Path(args.csv)
|
out = Path(args.csv)
|
||||||
|
|||||||
@@ -54,6 +54,48 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(float(healed["realized_pnl_usdt"]), 700.0, places=2)
|
self.assertAlmostEqual(float(healed["realized_pnl_usdt"]), 700.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
def test_uncomputable_old_trade_marked(self):
|
||||||
|
from lib.options.options_db import init_options_tables
|
||||||
|
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.instance.runtime_settings_lib import ensure_runtime_settings_table, runtime_set
|
||||||
|
|
||||||
|
conn = sqlite3.connect(":memory:")
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
init_options_tables(conn)
|
||||||
|
ensure_runtime_settings_table(conn)
|
||||||
|
runtime_set(conn, TRADING_MODE_KEY, MODE_SIM)
|
||||||
|
# 无开仓/卖回价 → 无法按新公式更新
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO options_trades(
|
||||||
|
inst_id, underlying, opt_type, sheets, eth_amount,
|
||||||
|
premium_paid, premium_received, realized_pnl, premium_ccy,
|
||||||
|
status, realized_pnl_usdt, pnl_settled,
|
||||||
|
created_at, closed_at
|
||||||
|
) VALUES (
|
||||||
|
'BTC-USD-261008-86500-P', 'BTC', 'P', 1, 0.01,
|
||||||
|
0.01, 0.02, 0.01, 'BTC',
|
||||||
|
'closed', -51.0, 1,
|
||||||
|
'2026-10-05 05:11:30', '2026-10-07 10:03:53'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
sync_options_from_local_trades(conn, None)
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT realized_pnl_total, status_raw FROM options_review_trades WHERE history_key=?",
|
||||||
|
("local_opt:1",),
|
||||||
|
).fetchone()
|
||||||
|
self.assertEqual(str(row["status_raw"]), "缺价未核")
|
||||||
|
self.assertIsNone(row["realized_pnl_total"])
|
||||||
|
trade = conn.execute(
|
||||||
|
"SELECT realized_pnl_usdt, pnl_settled FROM options_trades WHERE id=1"
|
||||||
|
).fetchone()
|
||||||
|
self.assertIsNone(trade["realized_pnl_usdt"])
|
||||||
|
self.assertEqual(int(trade["pnl_settled"] or 0), 0)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
def test_same_open_close_px_equals_coin_times_close(self):
|
def test_same_open_close_px_equals_coin_times_close(self):
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user