feat期权盈亏按交易账户快照结算:一次一仓、持仓禁划转、卖回后冻结。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2598,6 +2598,12 @@
|
||||
paintPositions(list);
|
||||
paintSpotSellBanner(d, list);
|
||||
paintCondOrderLock(d);
|
||||
if (typeof window.OptionsFundsSetTransferBlocked === "function") {
|
||||
window.OptionsFundsSetTransferBlocked(
|
||||
!!(d && d.transfer_blocked),
|
||||
(d && d.transfer_block_msg) || "持仓或待卖回期间不可划转"
|
||||
);
|
||||
}
|
||||
const fromPos = list.reduce(function (targets, p) {
|
||||
if (!p) return targets;
|
||||
if (p.target_index != null) {
|
||||
|
||||
@@ -353,4 +353,32 @@
|
||||
intMsg: "opt-page-int-msg",
|
||||
});
|
||||
}
|
||||
|
||||
/** 持仓/待卖回时划转按钮置灰(规格). */
|
||||
window.OptionsFundsSetTransferBlocked = function (blocked, msg) {
|
||||
const idsList = [
|
||||
["opt-page-int-btn", "opt-page-int-all-btn", "opt-page-int-amount", "opt-page-int-msg"],
|
||||
["opt-set-int-btn", "opt-set-int-all-btn", "opt-set-int-amount", "opt-set-int-msg"],
|
||||
];
|
||||
idsList.forEach(function (ids) {
|
||||
const btn = document.getElementById(ids[0]);
|
||||
const allBtn = document.getElementById(ids[1]);
|
||||
const amount = document.getElementById(ids[2]);
|
||||
const msgEl = document.getElementById(ids[3]);
|
||||
[btn, allBtn].forEach(function (el) {
|
||||
if (!el) return;
|
||||
el.disabled = !!blocked;
|
||||
el.style.opacity = blocked ? "0.45" : "";
|
||||
el.title = blocked ? String(msg || "持仓中不可划转") : "";
|
||||
});
|
||||
if (amount) amount.disabled = !!blocked;
|
||||
if (blocked && msgEl && msg) {
|
||||
msgEl.textContent = String(msg);
|
||||
msgEl.classList.add("text-warn");
|
||||
} else if (!blocked && msgEl && /持仓|划转|卖回/.test(String(msgEl.textContent || ""))) {
|
||||
msgEl.textContent = "";
|
||||
msgEl.classList.remove("text-warn");
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
"""期权已实现盈亏:交易账户 USDT 快照差分(一次一仓).
|
||||
|
||||
规格见 docs/规格-期权交易账户盈亏结算.md
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
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 fetch_trading_usdt(cfg: dict[str, Any] | None, ex: Any) -> float | None:
|
||||
"""读取交易账户 USDT(可用优先)."""
|
||||
fetch = (cfg or {}).get("fetch_options_balances") if isinstance(cfg, dict) else None
|
||||
if not callable(fetch):
|
||||
from lib.exchange.okx_options_lib import fetch_options_balances
|
||||
|
||||
fetch = fetch_options_balances
|
||||
try:
|
||||
bal = fetch(ex, force=True) or {}
|
||||
except TypeError:
|
||||
try:
|
||||
bal = fetch(ex) or {}
|
||||
except Exception:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
v = _safe_float(bal.get("trading_usdt_avail"))
|
||||
if v is None:
|
||||
v = _safe_float(bal.get("trading_usdt"))
|
||||
return round(float(v), 8) if v is not None else None
|
||||
|
||||
|
||||
def sellable_coin_blocks_open_msg(ex: Any, underlying: str) -> str | None:
|
||||
"""开仓前有可卖标的币则拒开."""
|
||||
from lib.options.options_spot_bridge_lib import (
|
||||
fetch_trading_coin_available,
|
||||
is_sellable_spot_coin,
|
||||
)
|
||||
|
||||
uly = (underlying or "").strip().upper()
|
||||
if uly not in ("ETH", "BTC"):
|
||||
return None
|
||||
try:
|
||||
avail = fetch_trading_coin_available(ex, uly)
|
||||
except Exception:
|
||||
return None
|
||||
if avail is None:
|
||||
return None
|
||||
if is_sellable_spot_coin(ex, uly, avail):
|
||||
return f"交易账户仍有可卖 {uly}({float(avail):g}),请先卖回 USDT 后再开仓"
|
||||
return None
|
||||
|
||||
|
||||
def transfer_blocked_by_position_msg(conn, ex: Any = None) -> str | None:
|
||||
"""持仓中或待卖回时禁止划转."""
|
||||
from lib.options.options_spot_bridge_lib import has_unfinished_bridge
|
||||
|
||||
try:
|
||||
if has_unfinished_bridge(conn):
|
||||
return "期权持仓或待卖回期间禁止划转,请先平仓并卖回 USDT"
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(*) AS n FROM options_trades WHERE status='open'"
|
||||
).fetchone()
|
||||
n = int(row["n"] if hasattr(row, "keys") else row[0])
|
||||
if n > 0:
|
||||
return "期权持仓中禁止划转"
|
||||
except Exception:
|
||||
pass
|
||||
if ex is not None:
|
||||
try:
|
||||
from lib.exchange.okx_options_lib import fetch_option_positions
|
||||
|
||||
for p in fetch_option_positions(ex) or []:
|
||||
try:
|
||||
if abs(float(p.get("pos") or 0)) > 1e-12:
|
||||
return "存在未平期权持仓,禁止划转"
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def try_settle_after_sellback(
|
||||
get_db: Any,
|
||||
ex: Any,
|
||||
*,
|
||||
bridge_id: int | None = None,
|
||||
cfg: dict[str, Any] | None = None,
|
||||
) -> int:
|
||||
"""卖回路径通用结算:读交易账户 after 并冻结未结算单."""
|
||||
pack = cfg if isinstance(cfg, dict) else {"fetch_options_balances": None}
|
||||
after = fetch_trading_usdt(pack, ex)
|
||||
if after is None:
|
||||
return 0
|
||||
conn = get_db() if callable(get_db) else get_db
|
||||
close_conn = callable(get_db)
|
||||
try:
|
||||
from lib.options.options_db import init_options_tables
|
||||
|
||||
init_options_tables(conn)
|
||||
n = 0
|
||||
if bridge_id is not None:
|
||||
n = settle_closed_trades_from_trading_account(
|
||||
conn, trading_usdt_after=after, bridge_id=int(bridge_id)
|
||||
)
|
||||
if n < 1:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT id FROM options_trades
|
||||
WHERE status='closed' AND COALESCE(pnl_settled,0)=0
|
||||
AND trading_usdt_before IS NOT NULL
|
||||
ORDER BY id DESC LIMIT 1
|
||||
"""
|
||||
).fetchone()
|
||||
if row:
|
||||
n = settle_closed_trades_from_trading_account(
|
||||
conn,
|
||||
trading_usdt_after=after,
|
||||
trade_id=int(row["id"] if hasattr(row, "keys") else row[0]),
|
||||
)
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
return n
|
||||
finally:
|
||||
if close_conn:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def settle_closed_trades_from_trading_account(
|
||||
conn,
|
||||
*,
|
||||
trading_usdt_after: float,
|
||||
bridge_id: int | None = None,
|
||||
trade_id: int | None = None,
|
||||
) -> int:
|
||||
"""卖回后:对本桥/本单未结算已平仓位冻结 after−before.
|
||||
|
||||
已 pnl_settled=1 的行永不改写.
|
||||
"""
|
||||
after = _safe_float(trading_usdt_after)
|
||||
if after is None:
|
||||
return 0
|
||||
after = round(float(after), 8)
|
||||
clauses = ["status='closed'", "COALESCE(pnl_settled, 0)=0", "trading_usdt_before IS NOT NULL"]
|
||||
params: list[Any] = []
|
||||
if trade_id is not None:
|
||||
clauses.append("id=?")
|
||||
params.append(int(trade_id))
|
||||
elif bridge_id is not None:
|
||||
clauses.append("bridge_id=?")
|
||||
params.append(int(bridge_id))
|
||||
else:
|
||||
return 0
|
||||
rows = conn.execute(
|
||||
f"""
|
||||
SELECT id, trading_usdt_before, realized_pnl_usdt
|
||||
FROM options_trades
|
||||
WHERE {" AND ".join(clauses)}
|
||||
""",
|
||||
tuple(params),
|
||||
).fetchall()
|
||||
updated = 0
|
||||
for row in rows:
|
||||
before = _safe_float(row["trading_usdt_before"] if hasattr(row, "keys") else row[1])
|
||||
if before is None:
|
||||
continue
|
||||
pnl_u = round(float(after) - float(before), 4)
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_trades
|
||||
SET trading_usdt_after = ?,
|
||||
realized_pnl_usdt = ?,
|
||||
pnl_settled = 1
|
||||
WHERE id = ? AND COALESCE(pnl_settled, 0)=0
|
||||
""",
|
||||
(after, pnl_u, int(row["id"] if hasattr(row, "keys") else row[0])),
|
||||
)
|
||||
updated += 1
|
||||
return updated
|
||||
@@ -379,7 +379,7 @@ def close_option_by_bid1(
|
||||
"""
|
||||
SELECT id, premium_paid, open_fee, premium_ccy, underlying,
|
||||
exchange_ord_id, budget_usdt, open_index_px, fee_ccy,
|
||||
eth_amount, open_quote
|
||||
eth_amount, open_quote, trading_usdt_before
|
||||
FROM options_trades
|
||||
WHERE inst_id = ? AND status = 'open'
|
||||
ORDER BY id ASC
|
||||
@@ -510,7 +510,9 @@ def close_option_by_bid1(
|
||||
fee_ccy=str(row["fee_ccy"] or row["premium_ccy"] or row_ccy or ""),
|
||||
)
|
||||
pnl = settled.get("realized_pnl")
|
||||
pnl_usdt = settled.get("realized_pnl_usdt")
|
||||
# 有交易账户 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")
|
||||
note_sql = ""
|
||||
|
||||
# 占位: close_quote/recv/pnl/ord/closed_at/open_fee/close_fee/fee_ccy/close_idx/open_idx/pnl_u/[note]/id
|
||||
|
||||
@@ -216,6 +216,16 @@ def open_coin_option_buy_full(
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
# 开仓前有可卖标的币 → 拒开(规格:先卖回)
|
||||
from lib.options.options_account_pnl_lib import (
|
||||
fetch_trading_usdt,
|
||||
sellable_coin_blocks_open_msg,
|
||||
)
|
||||
|
||||
coin_block = sellable_coin_blocks_open_msg(ex, underlying)
|
||||
if coin_block:
|
||||
return {"ok": False, "msg": coin_block, "can_open": False}
|
||||
trading_usdt_before = fetch_trading_usdt(cfg, ex)
|
||||
# 1) 仅买「权利金×现货缓冲」所需 USDT,不全额兑换预算
|
||||
coin_before = _spot_bridge.fetch_trading_coin_available(ex, underlying) or 0.0
|
||||
buy = _spot_bridge.spot_market_buy_coin_with_usdt(ex, underlying=underlying, usdt_amount=buy_usdt)
|
||||
@@ -514,6 +524,7 @@ def open_coin_option_buy_full(
|
||||
premium_ccy=premium_ccy,
|
||||
open_fee=open_fee,
|
||||
open_index_px=idx_open,
|
||||
trading_usdt_before=trading_usdt_before,
|
||||
profit_exit_enabled=profit_exit_enabled,
|
||||
profit_exit_mult=profit_exit_mult,
|
||||
)
|
||||
@@ -594,15 +605,20 @@ def _insert_coin_trade(conn: Any, **kwargs: Any) -> int:
|
||||
open_index_px = None
|
||||
except (TypeError, ValueError):
|
||||
open_index_px = None
|
||||
tusdt_before = kwargs.get("trading_usdt_before")
|
||||
try:
|
||||
tusdt_before = float(tusdt_before) if tusdt_before is not None else None
|
||||
except (TypeError, ValueError):
|
||||
tusdt_before = None
|
||||
cur = conn.execute(
|
||||
"""
|
||||
INSERT INTO options_trades(
|
||||
inst_id, underlying, opt_type, strike, exp_time, sheets, eth_amount,
|
||||
open_quote, premium_paid, status, signal_note, exchange_ord_id,
|
||||
margin_mode, premium_ccy, bridge_id, budget_usdt,
|
||||
open_fee, fee_ccy, open_index_px,
|
||||
open_fee, fee_ccy, open_index_px, trading_usdt_before, pnl_settled,
|
||||
profit_exit_enabled, profit_exit_mult, profit_exit_state, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', ?, ?, 'coin', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', ?, ?, 'coin', ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
kwargs["inst_id"],
|
||||
@@ -622,6 +638,7 @@ def _insert_coin_trade(conn: Any, **kwargs: Any) -> int:
|
||||
open_fee,
|
||||
fee_ccy,
|
||||
open_index_px,
|
||||
tusdt_before,
|
||||
pe,
|
||||
pe_mult,
|
||||
pe_state,
|
||||
@@ -668,18 +685,18 @@ def maybe_sell_spot_after_close(
|
||||
underlying=underlying,
|
||||
inst_id=inst_id,
|
||||
)
|
||||
# 模拟盘:卖回后按桥上真实 USDT 净额回写已实现盈亏
|
||||
# 卖回成功:交易账户 after − 开仓 before → 冻结本单盈亏
|
||||
try:
|
||||
from lib.sim.mode_lib import is_sim_mode
|
||||
from lib.options.options_stats_lib import sync_sim_cash_realized_pnl_usdt
|
||||
from lib.options.options_account_pnl_lib import try_settle_after_sellback
|
||||
|
||||
if is_sim_mode(cfg["get_db"]):
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
sync_sim_cash_realized_pnl_usdt(conn, ex)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
if (result or {}).get("ok"):
|
||||
bid = (result or {}).get("bridge_id")
|
||||
try_settle_after_sellback(
|
||||
cfg["get_db"],
|
||||
ex,
|
||||
bridge_id=int(bid) if bid is not None else None,
|
||||
cfg=cfg,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
@@ -749,6 +766,19 @@ def sweep_spot_after_options_flat(
|
||||
underlying=uly,
|
||||
inst_id=None,
|
||||
)
|
||||
if (out or {}).get("ok"):
|
||||
try:
|
||||
from lib.options.options_account_pnl_lib import try_settle_after_sellback
|
||||
|
||||
bid = (out or {}).get("bridge_id")
|
||||
try_settle_after_sellback(
|
||||
cfg["get_db"],
|
||||
exchange,
|
||||
bridge_id=int(bid) if bid is not None else None,
|
||||
cfg=cfg,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
sold.append({"underlying": uly, **(out or {})})
|
||||
except Exception as e:
|
||||
sold.append({"underlying": uly, "ok": False, "msg": str(e)})
|
||||
|
||||
@@ -129,6 +129,10 @@ def init_options_tables(conn: sqlite3.Connection) -> None:
|
||||
"ALTER TABLE options_trades ADD COLUMN close_index_px REAL",
|
||||
"ALTER TABLE options_trades ADD COLUMN open_index_px REAL",
|
||||
"ALTER TABLE options_trades ADD COLUMN realized_pnl_usdt REAL",
|
||||
# 交易账户快照盈亏(一次一仓):before→after 差分冻结
|
||||
"ALTER TABLE options_trades ADD COLUMN trading_usdt_before REAL",
|
||||
"ALTER TABLE options_trades ADD COLUMN trading_usdt_after REAL",
|
||||
"ALTER TABLE options_trades ADD COLUMN pnl_settled INTEGER DEFAULT 0",
|
||||
):
|
||||
try:
|
||||
conn.execute(ddl)
|
||||
|
||||
@@ -1026,6 +1026,21 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
conn_c.close()
|
||||
except Exception:
|
||||
pass
|
||||
transfer_blocked = False
|
||||
transfer_block_msg = ""
|
||||
try:
|
||||
from lib.options.options_account_pnl_lib import transfer_blocked_by_position_msg
|
||||
|
||||
conn_tb = cfg["get_db"]()
|
||||
try:
|
||||
tmsg = transfer_blocked_by_position_msg(conn_tb, ex)
|
||||
if tmsg:
|
||||
transfer_blocked = True
|
||||
transfer_block_msg = tmsg
|
||||
finally:
|
||||
conn_tb.close()
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify(
|
||||
{
|
||||
"ok": True,
|
||||
@@ -1034,6 +1049,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
"cond_order_locked": cond_locked,
|
||||
"cond_order_msg": cond_msg,
|
||||
"cond_order": cond_order,
|
||||
"transfer_blocked": transfer_blocked,
|
||||
"transfer_block_msg": transfer_block_msg,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1196,6 +1213,18 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
inst_id=inst_id,
|
||||
)
|
||||
if out.get("ok"):
|
||||
try:
|
||||
from lib.options.options_account_pnl_lib import try_settle_after_sellback
|
||||
|
||||
bid = out.get("bridge_id")
|
||||
try_settle_after_sellback(
|
||||
cfg["get_db"],
|
||||
ex,
|
||||
bridge_id=int(bid) if bid is not None else None,
|
||||
cfg=cfg,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
_mark_balances_stale(cfg)
|
||||
return jsonify(out)
|
||||
|
||||
@@ -1215,6 +1244,16 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
# 持仓/待卖回禁止划转(规格)
|
||||
conn_blk = cfg["get_db"]()
|
||||
try:
|
||||
from lib.options.options_account_pnl_lib import transfer_blocked_by_position_msg
|
||||
|
||||
block = transfer_blocked_by_position_msg(conn_blk, ex)
|
||||
if block:
|
||||
return jsonify({"ok": False, "msg": block, "transfer_blocked": True})
|
||||
finally:
|
||||
conn_blk.close()
|
||||
data = request.get_json(silent=True) or {}
|
||||
ccy = (data.get("ccy") or "USDC").upper()
|
||||
from_acct = (data.get("from") or "funding").strip()
|
||||
|
||||
@@ -316,7 +316,8 @@ def sync_options_from_local_trades(
|
||||
SELECT id, inst_id, underlying, opt_type, strike, exp_time, sheets,
|
||||
open_quote, close_quote, premium_paid, premium_received, realized_pnl,
|
||||
realized_pnl_usdt, close_index_px, open_index_px, budget_usdt,
|
||||
premium_ccy, created_at, closed_at, signal_note, status
|
||||
premium_ccy, created_at, closed_at, signal_note, status,
|
||||
trading_usdt_before, pnl_settled
|
||||
FROM options_trades
|
||||
WHERE status = 'closed'
|
||||
ORDER BY id DESC
|
||||
@@ -409,6 +410,12 @@ 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
|
||||
# 账户快照路径:未结算不写 U,避免指数假数进复盘
|
||||
acct_pending = _safe_float(r["trading_usdt_before"]) is not None and not settled
|
||||
close_idx = _safe_float(r["close_index_px"])
|
||||
open_idx = _safe_float(r["open_index_px"])
|
||||
# 折算指数:平仓冻结 > 开仓 > 实时(仅无冻结时兜底)
|
||||
@@ -426,7 +433,9 @@ def sync_options_from_local_trades(
|
||||
if paid0 is not None and recv0 is not None:
|
||||
pnl = recv0 - paid0
|
||||
prem = _safe_float(r["premium_paid"])
|
||||
if frozen_usdt is not None:
|
||||
if acct_pending:
|
||||
pnl_u = None
|
||||
elif frozen_usdt is not None and (settled or _safe_float(r["trading_usdt_before"]) is None):
|
||||
pnl_u = round(float(frozen_usdt), 4)
|
||||
else:
|
||||
pnl_u = _to_usdt(pnl, ccy=ccy, idx=idx)
|
||||
|
||||
@@ -303,7 +303,11 @@ _CLOSED_STATS_SELECT = """
|
||||
created_at, closed_at, inst_id, underlying, premium_ccy
|
||||
FROM options_trades
|
||||
WHERE status = 'closed'
|
||||
AND (realized_pnl IS NOT NULL OR realized_pnl_usdt IS NOT NULL)
|
||||
AND (
|
||||
(COALESCE(pnl_settled, 0) = 1 AND realized_pnl_usdt IS NOT NULL)
|
||||
OR (trading_usdt_before IS NULL
|
||||
AND (realized_pnl IS NOT NULL OR realized_pnl_usdt IS NOT NULL))
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
@@ -322,6 +326,7 @@ def freeze_missing_realized_pnl_usdt(conn, ex: Any = None) -> int:
|
||||
WHERE status = 'closed'
|
||||
AND realized_pnl_usdt IS NULL
|
||||
AND realized_pnl IS NOT NULL
|
||||
AND trading_usdt_before IS NULL
|
||||
ORDER BY id DESC
|
||||
LIMIT 300
|
||||
"""
|
||||
@@ -512,96 +517,15 @@ def _sim_net_usdt_converts_for_trade(
|
||||
|
||||
|
||||
def sync_sim_cash_realized_pnl_usdt(conn, ex: Any = None) -> int:
|
||||
"""模拟盘:收回×卖回价 − 初始U权利金 − 手续费U.
|
||||
"""兼容入口:已按交易账户结算(pnl_settled=1)的永不改写.
|
||||
|
||||
盈利=(权利金币+盈利币)×卖回价−开仓U权利金−费;
|
||||
亏损=剩余权利金U−费−开仓U(同一式).不用买币预算差额.
|
||||
有 trading_usdt_before 的待结算单留给卖回结算;
|
||||
仅无账户快照的旧单才回退权利金×指数冻结.
|
||||
"""
|
||||
del ex
|
||||
init_options_tables(conn)
|
||||
from lib.options.options_pnl_settle_lib import resolve_close_index_px, settle_closed_pnl
|
||||
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT id, inst_id, underlying, premium_ccy, premium_paid, premium_received,
|
||||
open_fee, close_fee, realized_pnl, realized_pnl_usdt,
|
||||
close_index_px, open_index_px, close_quote, eth_amount
|
||||
FROM options_trades
|
||||
WHERE status = 'closed'
|
||||
ORDER BY id DESC
|
||||
LIMIT 300
|
||||
"""
|
||||
).fetchall()
|
||||
updated = 0
|
||||
for row in rows:
|
||||
ccy = str(_row_get(row, "premium_ccy") or "").strip().upper()
|
||||
underly = str(_row_get(row, "underlying") or "").strip().upper()
|
||||
if ccy not in ("ETH", "BTC") and underly not in ("ETH", "BTC"):
|
||||
continue
|
||||
underly = underly or ccy
|
||||
paid = _safe_float(_row_get(row, "premium_paid"))
|
||||
recv = _safe_float(_row_get(row, "premium_received"))
|
||||
if recv is None:
|
||||
cq = _safe_float(_row_get(row, "close_quote"))
|
||||
eth = _safe_float(_row_get(row, "eth_amount"))
|
||||
if cq is not None and eth is not None and eth > 0:
|
||||
try:
|
||||
from lib.options.options_pricing_lib import total_premium
|
||||
|
||||
recv = float(total_premium(cq, eth))
|
||||
except Exception:
|
||||
recv = None
|
||||
sell_px = _safe_float(_row_get(row, "close_index_px"))
|
||||
open_idx = _safe_float(_row_get(row, "open_index_px"))
|
||||
if sell_px is None or sell_px <= 0:
|
||||
sell_px = resolve_close_index_px(
|
||||
ex,
|
||||
inst_id=str(_row_get(row, "inst_id") or ""),
|
||||
underlying=underly,
|
||||
hint=open_idx,
|
||||
)
|
||||
if open_idx is None or open_idx <= 0:
|
||||
# 无开仓指数时用卖回价兜底(等价旧净币×价)
|
||||
open_idx = sell_px
|
||||
if paid is None or recv is None:
|
||||
net = _safe_float(_row_get(row, "realized_pnl"))
|
||||
if net is None or sell_px is None or sell_px <= 0:
|
||||
continue
|
||||
usdt = round(float(net) * float(sell_px), 4)
|
||||
close_idx = float(sell_px)
|
||||
new_pnl = float(net)
|
||||
open_write = open_idx
|
||||
else:
|
||||
settled = settle_closed_pnl(
|
||||
premium_paid=paid,
|
||||
premium_received=recv,
|
||||
open_fee=_row_get(row, "open_fee"),
|
||||
close_fee=_row_get(row, "close_fee"),
|
||||
premium_ccy=ccy or underly or "ETH",
|
||||
index_px=sell_px,
|
||||
open_index_px=open_idx,
|
||||
)
|
||||
usdt = settled.get("realized_pnl_usdt")
|
||||
close_idx = settled.get("close_index_px") or sell_px
|
||||
new_pnl = settled.get("realized_pnl")
|
||||
open_write = settled.get("open_index_px") or open_idx
|
||||
if usdt is None:
|
||||
continue
|
||||
old = _safe_float(_row_get(row, "realized_pnl_usdt"))
|
||||
if old is not None and abs(float(old) - float(usdt)) < 1e-6:
|
||||
continue
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_trades
|
||||
SET realized_pnl_usdt = ?,
|
||||
close_index_px = COALESCE(close_index_px, ?),
|
||||
open_index_px = COALESCE(open_index_px, ?),
|
||||
realized_pnl = COALESCE(?, realized_pnl)
|
||||
WHERE id = ?
|
||||
""",
|
||||
(float(usdt), close_idx, open_write, new_pnl, int(row["id"])),
|
||||
)
|
||||
updated += 1
|
||||
return updated
|
||||
# 账户快照路径由 sell_residual / maybe_sell 结算,此处不覆盖
|
||||
return 0
|
||||
|
||||
|
||||
def load_local_options_stats_history(conn, ex: Any = None) -> list[dict[str, Any]]:
|
||||
|
||||
Reference in New Issue
Block a user