fix卖回VWAP必须落库并覆盖指数占位价,禁止预算反推开仓价。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -495,9 +495,16 @@ def close_option_by_bid1(
|
|||||||
|
|
||||||
row_budget = _safe_float(row["budget_usdt"])
|
row_budget = _safe_float(row["budget_usdt"])
|
||||||
row_open_idx = _safe_float(row["open_index_px"])
|
row_open_idx = _safe_float(row["open_index_px"])
|
||||||
if (row_open_idx is None or row_open_idx <= 0) and row_budget and paid > 1e-12:
|
# 禁止 budget/premium 反推开仓价(会把缓冲算进均价,开仓价被抬飞)
|
||||||
# 无开仓指数时用预算/已付反推
|
if (row_open_idx is None or row_open_idx <= 0) and row.get("bridge_id") is not None:
|
||||||
row_open_idx = float(row_budget) / paid
|
try:
|
||||||
|
from lib.options.options_spot_bridge_lib import bridge_buy_vwap
|
||||||
|
|
||||||
|
bv = bridge_buy_vwap(conn, int(row["bridge_id"]))
|
||||||
|
if bv is not None and float(bv) > 0:
|
||||||
|
row_open_idx = float(bv)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
settled = settle_closed_pnl(
|
settled = settle_closed_pnl(
|
||||||
premium_paid=paid,
|
premium_paid=paid,
|
||||||
premium_received=recv,
|
premium_received=recv,
|
||||||
|
|||||||
@@ -511,6 +511,16 @@ def open_coin_option_buy_full(
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# 开仓价必须在缩减 coin_bought 之前按买币 VWAP 冻住
|
||||||
|
# (buy_usdt_spent/buy_coin_accum;禁止事后 budget/缩水权利金量反推)
|
||||||
|
open_px_freeze = idx_open
|
||||||
|
try:
|
||||||
|
bv = _spot_bridge.bridge_buy_vwap(conn, bridge_id)
|
||||||
|
if bv is not None and float(bv) > 0:
|
||||||
|
open_px_freeze = float(bv)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
_spot_bridge.update_bridge(
|
_spot_bridge.update_bridge(
|
||||||
conn,
|
conn,
|
||||||
bridge_id,
|
bridge_id,
|
||||||
@@ -520,14 +530,6 @@ def open_coin_option_buy_full(
|
|||||||
message="期权持仓中",
|
message="期权持仓中",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 开仓价一次冻结:优先买币 VWAP(之后不再改);权利金币量 premium_paid 固定
|
|
||||||
open_px_freeze = idx_open
|
|
||||||
try:
|
|
||||||
bv = _spot_bridge.bridge_buy_vwap(conn, bridge_id)
|
|
||||||
if bv is not None and float(bv) > 0:
|
|
||||||
open_px_freeze = float(bv)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
trade_id = _insert_coin_trade(
|
trade_id = _insert_coin_trade(
|
||||||
conn,
|
conn,
|
||||||
inst_id=inst_id,
|
inst_id=inst_id,
|
||||||
|
|||||||
@@ -585,52 +585,48 @@ def sync_options_from_local_trades(
|
|||||||
|
|
||||||
status_label = "已平"
|
status_label = "已平"
|
||||||
pnl_u = None
|
pnl_u = None
|
||||||
# 价已冻则只用库内价;缺价才问桥 VWAP(避免刷新改均价)
|
# 始终问桥:买/卖回 VWAP 优先于库内指数占位价(防预算反推开仓价、平仓指数冒充卖回价)
|
||||||
prices_frozen = (
|
opx, cpx, _gate = resolve_spot_vwap_prices(
|
||||||
open_idx is not None
|
conn,
|
||||||
and open_idx > 0
|
bridge_id=r["bridge_id"],
|
||||||
and sell_idx is not None
|
open_index_px=open_idx,
|
||||||
and sell_idx > 0
|
close_index_px=sell_idx,
|
||||||
)
|
)
|
||||||
use_bridge = None if prices_frozen else r["bridge_id"]
|
|
||||||
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 if (open_idx is not None and open_idx > 0) else None,
|
open_index_px=opx,
|
||||||
close_index_px=sell_idx,
|
close_index_px=cpx,
|
||||||
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 ""),
|
||||||
conn=conn if use_bridge is not None else None,
|
conn=conn,
|
||||||
bridge_id=use_bridge,
|
bridge_id=r["bridge_id"],
|
||||||
)
|
)
|
||||||
if pnl_src == "pending_sell":
|
if pnl_src == "pending_sell":
|
||||||
status_label = "待卖回"
|
status_label = "待卖回"
|
||||||
pnl_u = None
|
pnl_u = None
|
||||||
elif pnl_u is not None and pnl_src not in ("none", "pending_sell"):
|
elif pnl_u is not None and pnl_src not in ("none", "pending_sell"):
|
||||||
try:
|
try:
|
||||||
opx, cpx = open_idx, sell_idx
|
# VWAP 非空时覆盖指数占位;币量字段不改
|
||||||
if not prices_frozen:
|
|
||||||
opx, cpx, _g = resolve_spot_vwap_prices(
|
|
||||||
conn,
|
|
||||||
bridge_id=r["bridge_id"],
|
|
||||||
open_index_px=open_idx,
|
|
||||||
close_index_px=sell_idx,
|
|
||||||
)
|
|
||||||
# 真盈亏U每次刷新按固定量+价重算;均价只补缺不覆盖
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE options_trades
|
UPDATE options_trades
|
||||||
SET realized_pnl_usdt = ?,
|
SET realized_pnl_usdt = ?,
|
||||||
open_index_px = COALESCE(open_index_px, ?),
|
open_index_px = COALESCE(?, open_index_px),
|
||||||
close_index_px = COALESCE(close_index_px, ?),
|
close_index_px = COALESCE(?, close_index_px),
|
||||||
pnl_settled = 1
|
pnl_settled = 1
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
""",
|
""",
|
||||||
(pnl_u, opx, cpx, trade_id),
|
(pnl_u, opx, cpx, trade_id),
|
||||||
)
|
)
|
||||||
|
if opx is not None and opx > 0:
|
||||||
|
open_idx = opx
|
||||||
|
open_idx_for_prem = opx
|
||||||
|
if cpx is not None and cpx > 0:
|
||||||
|
sell_idx = cpx
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -778,6 +778,13 @@ def sell_residual_after_option_flat(
|
|||||||
}
|
}
|
||||||
recovered = _safe_float(sell.get("usdt_recovered"))
|
recovered = _safe_float(sell.get("usdt_recovered"))
|
||||||
sold_amt = _safe_float(sell.get("coin_sold"))
|
sold_amt = _safe_float(sell.get("coin_sold"))
|
||||||
|
# 缺 USDT 回报时用成交价×币量补,保证 close_sell VWAP 能落库
|
||||||
|
if (recovered is None or recovered <= 0) and sold_amt is not None and sold_amt > 0:
|
||||||
|
fill_px = _safe_float(sell.get("fill_px"))
|
||||||
|
if fill_px is not None and fill_px > 0:
|
||||||
|
recovered = round(float(sold_amt) * float(fill_px), 8)
|
||||||
|
sell = dict(sell)
|
||||||
|
sell["usdt_recovered"] = recovered
|
||||||
# 平仓后卖回(可多笔):累加 close_sell_* 供卖回 VWAP;开仓多余卖回不走这里
|
# 平仓后卖回(可多笔):累加 close_sell_* 供卖回 VWAP;开仓多余卖回不走这里
|
||||||
add_bridge_close_sell_fill(
|
add_bridge_close_sell_fill(
|
||||||
c,
|
c,
|
||||||
@@ -809,6 +816,7 @@ def sell_residual_after_option_flat(
|
|||||||
"bridge_id": bid,
|
"bridge_id": bid,
|
||||||
"bridge_status": BRIDGE_PENDING_SELL,
|
"bridge_status": BRIDGE_PENDING_SELL,
|
||||||
}
|
}
|
||||||
|
sell_vwap = bridge_sell_vwap(c, bid)
|
||||||
update_bridge(
|
update_bridge(
|
||||||
c,
|
c,
|
||||||
bid,
|
bid,
|
||||||
@@ -818,13 +826,26 @@ def sell_residual_after_option_flat(
|
|||||||
message="期权已平,币已卖回 USDT",
|
message="期权已平,币已卖回 USDT",
|
||||||
close=True,
|
close=True,
|
||||||
)
|
)
|
||||||
|
# 卖回 VWAP 写入关联已平仓(覆盖平仓时指数占位价)
|
||||||
|
if sell_vwap is not None and float(sell_vwap) > 0:
|
||||||
|
try:
|
||||||
|
c.execute(
|
||||||
|
"""
|
||||||
|
UPDATE options_trades
|
||||||
|
SET close_index_px = ?
|
||||||
|
WHERE bridge_id = ? AND status = 'closed'
|
||||||
|
""",
|
||||||
|
(float(sell_vwap), int(bid)),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"sell": sell,
|
"sell": sell,
|
||||||
"bridge_id": bid,
|
"bridge_id": bid,
|
||||||
"bridge_status": BRIDGE_CLOSED,
|
"bridge_status": BRIDGE_CLOSED,
|
||||||
"sell_complete": True,
|
"sell_complete": True,
|
||||||
"sell_vwap": bridge_sell_vwap(c, bid),
|
"sell_vwap": sell_vwap,
|
||||||
}
|
}
|
||||||
|
|
||||||
if get_db is not None:
|
if get_db is not None:
|
||||||
|
|||||||
+12
-1
@@ -421,6 +421,10 @@ def _patch_spot_bridge_lib() -> None:
|
|||||||
if not is_sellable_spot_coin(ex, coin, amt):
|
if not is_sellable_spot_coin(ex, coin, amt):
|
||||||
min_sz = spot_min_sell_sz(ex, coin)
|
min_sz = spot_min_sell_sz(ex, coin)
|
||||||
return {"ok": False, "msg": f"{coin} 可卖数量过小(低于现货最小 {min_sz:g})"}
|
return {"ok": False, "msg": f"{coin} 可卖数量过小(低于现货最小 {min_sz:g})"}
|
||||||
|
# 平仓卖回(coin_amount=None)一次卖光;限量卖才留粉尘余量
|
||||||
|
if coin_amount is None:
|
||||||
|
sell_sz = float(amt)
|
||||||
|
else:
|
||||||
sell_sz = max(0.0, float(amt) * 0.999)
|
sell_sz = max(0.0, float(amt) * 0.999)
|
||||||
if sell_sz <= 0 or not is_sellable_spot_coin(ex, coin, sell_sz):
|
if sell_sz <= 0 or not is_sellable_spot_coin(ex, coin, sell_sz):
|
||||||
return {"ok": False, "msg": f"{coin} 可卖数量过小"}
|
return {"ok": False, "msg": f"{coin} 可卖数量过小"}
|
||||||
@@ -445,13 +449,20 @@ def _patch_spot_bridge_lib() -> None:
|
|||||||
notify_instance_balance_changed()
|
notify_instance_balance_changed()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
recovered = result.get("usdt_recovered")
|
||||||
|
if recovered is None and result.get("fill_px") is not None:
|
||||||
|
try:
|
||||||
|
recovered = round(float(sell_sz) * float(result.get("fill_px")), 8)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
recovered = None
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"inst_id": result.get("inst_id") or "",
|
"inst_id": result.get("inst_id") or "",
|
||||||
"ord_id": f"sim-spot-sell-{result.get('fill_px') or 0}",
|
"ord_id": f"sim-spot-sell-{result.get('fill_px') or 0}",
|
||||||
"coin_sold": float(sell_sz),
|
"coin_sold": float(sell_sz),
|
||||||
|
"fill_px": result.get("fill_px"),
|
||||||
"sim": True,
|
"sim": True,
|
||||||
"usdt_recovered": result.get("usdt_recovered"),
|
"usdt_recovered": recovered,
|
||||||
"data": {"sCode": "0", "sMsg": "sim filled"},
|
"data": {"sCode": "0", "sMsg": "sim filled"},
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""从模拟盘流水重建买/卖回 VWAP,并按真盈亏公式重算 realized_pnl_usdt.
|
||||||
|
|
||||||
|
用于 close_sell_* 漏记、开仓价被 budget/premium 抬飞的历史单.
|
||||||
|
默认 dry-run;加 --apply 才写库.
|
||||||
|
|
||||||
|
python scripts/repair_options_spot_vwap_from_sim_ledger.py --db crypto.db
|
||||||
|
python scripts/repair_options_spot_vwap_from_sim_ledger.py --db crypto.db --apply
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_float(v: Any) -> float | None:
|
||||||
|
if v is None or v == "":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _ledger_buy_sell(
|
||||||
|
conn: sqlite3.Connection,
|
||||||
|
*,
|
||||||
|
underly: str,
|
||||||
|
inst_id: str,
|
||||||
|
open_ts: str,
|
||||||
|
close_ts: str | None,
|
||||||
|
) -> tuple[float | None, float | None, float | None, float | None]:
|
||||||
|
"""在开仓前后窗口内汇总 USDT→币 与 平仓后币→USDT."""
|
||||||
|
u = (underly or "").upper()
|
||||||
|
inst = str(inst_id or "").strip()
|
||||||
|
buys = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT amount, ts FROM sim_ledger_entries
|
||||||
|
WHERE ccy='USDT' AND account='trading' AND kind='convert'
|
||||||
|
AND amount < 0 AND note LIKE ?
|
||||||
|
AND ts >= datetime(?, '-14 hour')
|
||||||
|
AND ts <= datetime(?, '+14 hour')
|
||||||
|
ORDER BY id
|
||||||
|
""",
|
||||||
|
(f"{u}/USDT%", open_ts, open_ts),
|
||||||
|
).fetchall()
|
||||||
|
buy_usdt = 0.0
|
||||||
|
buy_coin = 0.0
|
||||||
|
for row in buys:
|
||||||
|
coin_row = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT amount FROM sim_ledger_entries
|
||||||
|
WHERE ccy=? AND account='trading' AND kind='convert' AND amount > 0
|
||||||
|
AND ts=? AND note LIKE 'from usdt%'
|
||||||
|
ORDER BY id LIMIT 1
|
||||||
|
""",
|
||||||
|
(u, row["ts"]),
|
||||||
|
).fetchone()
|
||||||
|
if not coin_row:
|
||||||
|
continue
|
||||||
|
buy_usdt += abs(float(row["amount"]))
|
||||||
|
buy_coin += float(coin_row["amount"])
|
||||||
|
sell_usdt = 0.0
|
||||||
|
sell_coin = 0.0
|
||||||
|
last_close = None
|
||||||
|
if inst:
|
||||||
|
last_close = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT ts FROM sim_ledger_entries
|
||||||
|
WHERE kind='option_close' AND note LIKE ?
|
||||||
|
ORDER BY id DESC LIMIT 1
|
||||||
|
""",
|
||||||
|
(f"%{inst}%",),
|
||||||
|
).fetchone()
|
||||||
|
if last_close or close_ts:
|
||||||
|
close_anchor = str(last_close["ts"]) if last_close else str(close_ts)
|
||||||
|
sells = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT amount, ts FROM sim_ledger_entries
|
||||||
|
WHERE ccy=? AND account='trading' AND kind='convert'
|
||||||
|
AND amount < 0 AND note LIKE ?
|
||||||
|
AND ts >= ?
|
||||||
|
ORDER BY id
|
||||||
|
""",
|
||||||
|
(u, f"{u}/USDT%", close_anchor),
|
||||||
|
).fetchall()
|
||||||
|
for row in sells:
|
||||||
|
usdt_row = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT amount FROM sim_ledger_entries
|
||||||
|
WHERE ccy='USDT' AND account='trading' AND kind='convert'
|
||||||
|
AND amount > 0 AND ts=? AND note LIKE 'from %'
|
||||||
|
ORDER BY id LIMIT 1
|
||||||
|
""",
|
||||||
|
(row["ts"],),
|
||||||
|
).fetchone()
|
||||||
|
if not usdt_row:
|
||||||
|
continue
|
||||||
|
sell_coin += abs(float(row["amount"]))
|
||||||
|
sell_usdt += float(usdt_row["amount"])
|
||||||
|
return (
|
||||||
|
buy_usdt if buy_usdt > 0 else None,
|
||||||
|
buy_coin if buy_coin > 0 else None,
|
||||||
|
sell_usdt if sell_usdt > 0 else None,
|
||||||
|
sell_coin if sell_coin > 0 else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def repair(conn: sqlite3.Connection, *, apply: bool) -> list[dict[str, Any]]:
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
from lib.options.options_pnl_settle_lib import true_pnl_usdt_premium_move
|
||||||
|
from lib.options.options_spot_bridge_lib import ensure_bridge_table
|
||||||
|
|
||||||
|
ensure_bridge_table(conn)
|
||||||
|
rows = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT id, inst_id, underlying, premium_paid, premium_received, realized_pnl,
|
||||||
|
open_index_px, close_index_px, realized_pnl_usdt, bridge_id,
|
||||||
|
created_at, closed_at, budget_usdt
|
||||||
|
FROM options_trades
|
||||||
|
WHERE status='closed'
|
||||||
|
ORDER BY id
|
||||||
|
"""
|
||||||
|
).fetchall()
|
||||||
|
report: list[dict[str, Any]] = []
|
||||||
|
for r in rows:
|
||||||
|
tid = int(r["id"])
|
||||||
|
underly = str(r["underlying"] or (str(r["inst_id"] or "").split("-")[0]) or "").upper()
|
||||||
|
open_ts = str(r["created_at"] or "")
|
||||||
|
close_ts = str(r["closed_at"] or "") or None
|
||||||
|
buy_u, buy_c, sell_u, sell_c = _ledger_buy_sell(
|
||||||
|
conn,
|
||||||
|
underly=underly,
|
||||||
|
inst_id=str(r["inst_id"] or ""),
|
||||||
|
open_ts=open_ts,
|
||||||
|
close_ts=close_ts,
|
||||||
|
)
|
||||||
|
open_px = (buy_u / buy_c) if buy_u and buy_c and buy_c > 0 else None
|
||||||
|
close_px = (sell_u / sell_c) if sell_u and sell_c and sell_c > 0 else None
|
||||||
|
paid = _safe_float(r["premium_paid"])
|
||||||
|
coin = _safe_float(r["realized_pnl"])
|
||||||
|
old_open = _safe_float(r["open_index_px"])
|
||||||
|
old_close = _safe_float(r["close_index_px"])
|
||||||
|
old_u = _safe_float(r["realized_pnl_usdt"])
|
||||||
|
new_u = true_pnl_usdt_premium_move(
|
||||||
|
coin_pnl=coin,
|
||||||
|
premium_paid=paid,
|
||||||
|
open_index_px=open_px or old_open,
|
||||||
|
sell_back_px=close_px or old_close,
|
||||||
|
)
|
||||||
|
item = {
|
||||||
|
"id": tid,
|
||||||
|
"inst_id": r["inst_id"],
|
||||||
|
"buy_vwap": round(open_px, 4) if open_px else None,
|
||||||
|
"sell_vwap": round(close_px, 4) if close_px else None,
|
||||||
|
"old_open": old_open,
|
||||||
|
"old_close": old_close,
|
||||||
|
"old_u": old_u,
|
||||||
|
"new_u": new_u,
|
||||||
|
"bridge_id": r["bridge_id"],
|
||||||
|
"sell_usdt": sell_u,
|
||||||
|
"sell_coin": sell_c,
|
||||||
|
"buy_usdt": buy_u,
|
||||||
|
"buy_coin": buy_c,
|
||||||
|
}
|
||||||
|
report.append(item)
|
||||||
|
if not apply:
|
||||||
|
continue
|
||||||
|
bid = r["bridge_id"]
|
||||||
|
if bid is not None and buy_u and buy_c:
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
UPDATE options_spot_bridge
|
||||||
|
SET buy_usdt_spent = ?, buy_coin_accum = ?
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(float(buy_u), float(buy_c), int(bid)),
|
||||||
|
)
|
||||||
|
if bid is not None and sell_u and sell_c:
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
UPDATE options_spot_bridge
|
||||||
|
SET close_sell_usdt = ?, close_sell_coin = ?,
|
||||||
|
usdt_recovered = ?, coin_sold = ?
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(float(sell_u), float(sell_c), float(sell_u), float(sell_c), int(bid)),
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
UPDATE options_trades
|
||||||
|
SET open_index_px = COALESCE(?, open_index_px),
|
||||||
|
close_index_px = COALESCE(?, close_index_px),
|
||||||
|
realized_pnl_usdt = COALESCE(?, realized_pnl_usdt),
|
||||||
|
initial_premium_usdt = CASE
|
||||||
|
WHEN ? IS NOT NULL AND premium_paid IS NOT NULL
|
||||||
|
THEN round(premium_paid * ?, 4)
|
||||||
|
ELSE initial_premium_usdt
|
||||||
|
END,
|
||||||
|
pnl_settled = 1
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
open_px,
|
||||||
|
close_px,
|
||||||
|
new_u,
|
||||||
|
open_px,
|
||||||
|
open_px,
|
||||||
|
tid,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if apply:
|
||||||
|
conn.commit()
|
||||||
|
# 复盘行跟着刷
|
||||||
|
try:
|
||||||
|
from lib.options.options_review_lib import sync_options_from_local_trades
|
||||||
|
|
||||||
|
sync_options_from_local_trades(conn, None)
|
||||||
|
conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print("review_sync_warn", e)
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--db", required=True)
|
||||||
|
ap.add_argument("--apply", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
path = Path(args.db)
|
||||||
|
if not path.is_file():
|
||||||
|
print("db missing", path)
|
||||||
|
return 1
|
||||||
|
conn = sqlite3.connect(str(path))
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
report = repair(conn, apply=bool(args.apply))
|
||||||
|
conn.close()
|
||||||
|
import json
|
||||||
|
|
||||||
|
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||||
|
print("apply" if args.apply else "dry-run", "rows", len(report))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -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
|
||||||
@@ -55,6 +55,60 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(float(row["realized_pnl_total"]), 700.0, places=2)
|
self.assertAlmostEqual(float(row["realized_pnl_total"]), 700.0, places=2)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
def test_refresh_prefers_bridge_sell_vwap_over_wrong_index(self):
|
||||||
|
"""库内卖回价若是指数占位,刷新须用桥上卖回 VWAP 纠正."""
|
||||||
|
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_spot_bridge_lib import (
|
||||||
|
BRIDGE_CLOSED,
|
||||||
|
add_bridge_buy_fill,
|
||||||
|
add_bridge_close_sell_fill,
|
||||||
|
ensure_bridge_table,
|
||||||
|
insert_bridge,
|
||||||
|
update_bridge,
|
||||||
|
)
|
||||||
|
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_bridge_table(conn)
|
||||||
|
ensure_runtime_settings_table(conn)
|
||||||
|
runtime_set(conn, TRADING_MODE_KEY, MODE_SIM)
|
||||||
|
bid = insert_bridge(conn, underlying="BTC", status="holding", budget_usdt=990.0)
|
||||||
|
add_bridge_buy_fill(conn, bid, usdt_spent=840.10, coin_bought=0.01)
|
||||||
|
add_bridge_close_sell_fill(conn, bid, usdt_recovered=814.18, coin_sold=0.01)
|
||||||
|
update_bridge(conn, bid, status=BRIDGE_CLOSED, close=True)
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO options_trades(
|
||||||
|
inst_id, underlying, opt_type, sheets, eth_amount,
|
||||||
|
premium_paid, premium_received, realized_pnl, premium_ccy,
|
||||||
|
open_fee, close_fee, status, realized_pnl_usdt,
|
||||||
|
open_index_px, close_index_px, pnl_settled, bridge_id,
|
||||||
|
created_at, closed_at
|
||||||
|
) VALUES (
|
||||||
|
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
|
||||||
|
0.01, 0.02, 0.01, 'BTC',
|
||||||
|
0, 0, 'closed', 1544.0,
|
||||||
|
88086.0, 85400.0, 1, ?,
|
||||||
|
'2026-10-12 01:46:50', '2026-10-13 08:07:35'
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(bid,),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
sync_options_from_local_trades(conn, None)
|
||||||
|
trade = conn.execute(
|
||||||
|
"SELECT realized_pnl_usdt, open_index_px, close_index_px FROM options_trades WHERE id=1"
|
||||||
|
).fetchone()
|
||||||
|
self.assertAlmostEqual(float(trade["open_index_px"]), 84010.0, places=2)
|
||||||
|
self.assertAlmostEqual(float(trade["close_index_px"]), 81418.0, places=2)
|
||||||
|
# A = 0.01*81418 - 0.01*(84010-81418) = 814.18 - 25.92 = 788.26
|
||||||
|
self.assertAlmostEqual(float(trade["realized_pnl_usdt"]), 788.26, places=2)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
def test_premium_usdt_move_formula(self):
|
def test_premium_usdt_move_formula(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