修复 BTC 币本位期权净盈亏被 round4 抹成 0 的精度问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-02 14:51:59 +08:00
parent bc86723b39
commit 61808a5374
5 changed files with 52 additions and 13 deletions
+15 -5
View File
@@ -11,9 +11,11 @@ from lib.options.options_close_gate_lib import (
update_close_gate, update_close_gate,
) )
from lib.options.options_pricing_lib import ( from lib.options.options_pricing_lib import (
PREMIUM_DECIMALS_COIN,
estimate_close_by_bids, estimate_close_by_bids,
fetch_option_mark_px, fetch_option_mark_px,
is_stub_bid_px, is_stub_bid_px,
round_premium_amount,
total_premium, total_premium,
) )
@@ -304,7 +306,9 @@ def close_option_by_bid1(
px = float(order.get("px", locked_bid_px)) px = float(order.get("px", locked_bid_px))
oid = str((order.get("data") or {}).get("ordId") or "") oid = str((order.get("data") or {}).get("ordId") or "")
prem_recv = round(total_premium(px, level_sheets * ct_mult), 4) prem_recv = round_premium_amount(
total_premium(px, level_sheets * ct_mult), decimals=PREMIUM_DECIMALS_COIN
) or 0.0
time.sleep(0.6) time.sleep(0.6)
invalidate_option_positions_cache() invalidate_option_positions_cache()
raw2 = cfg["fetch_option_positions"](ex) raw2 = cfg["fetch_option_positions"](ex)
@@ -344,14 +348,20 @@ def close_option_by_bid1(
for i, row in enumerate(open_rows): for i, row in enumerate(open_rows):
paid = float(row["premium_paid"] or 0) paid = float(row["premium_paid"] or 0)
if i == len(open_rows) - 1: if i == len(open_rows) - 1:
recv = round(prem_recv - allocated, 4) recv = round_premium_amount(
prem_recv - allocated, decimals=PREMIUM_DECIMALS_COIN
) or 0.0
elif total_paid > 0: elif total_paid > 0:
recv = round(prem_recv * (paid / total_paid), 4) recv = round_premium_amount(
prem_recv * (paid / total_paid), decimals=PREMIUM_DECIMALS_COIN
) or 0.0
allocated += recv allocated += recv
else: else:
recv = round(prem_recv / len(open_rows), 4) recv = round_premium_amount(
prem_recv / len(open_rows), decimals=PREMIUM_DECIMALS_COIN
) or 0.0
allocated += recv allocated += recv
pnl = round(recv - paid, 4) pnl = round_premium_amount(recv - paid, decimals=PREMIUM_DECIMALS_COIN) or 0.0
note_sql = "" note_sql = ""
from lib.options.options_time_lib import options_app_now_str from lib.options.options_time_lib import options_app_now_str
+5 -1
View File
@@ -95,6 +95,8 @@ def forget_close_gate_for_inst(inst_id: str) -> None:
def net_pnl_from_display_row(row: dict[str, Any]) -> float | None: def net_pnl_from_display_row(row: dict[str, Any]) -> float | None:
"""与持仓卡「净盈亏」同口径:买一可回收 − 权利金;残档买一则无净值.""" """与持仓卡「净盈亏」同口径:买一可回收 − 权利金;残档买一则无净值."""
from lib.options.options_pricing_lib import PREMIUM_DECIMALS_COIN, round_premium_amount
preview = row.get("close_preview") if isinstance(row.get("close_preview"), dict) else {} preview = row.get("close_preview") if isinstance(row.get("close_preview"), dict) else {}
if preview.get("bid_invalid"): if preview.get("bid_invalid"):
return None return None
@@ -112,7 +114,9 @@ def net_pnl_from_display_row(row: dict[str, Any]) -> float | None:
recv = _safe_float(preview.get("total_received")) recv = _safe_float(preview.get("total_received"))
paid = _safe_float(row.get("premium_paid")) paid = _safe_float(row.get("premium_paid"))
if covered > 0 and recv is not None and paid is not None: if covered > 0 and recv is not None and paid is not None:
return round(recv - paid, 4) ccy = str(row.get("premium_ccy") or "").strip().upper()
decimals = PREMIUM_DECIMALS_COIN if ccy in ("ETH", "BTC") else None
return round_premium_amount(recv - paid, ccy=ccy or None, decimals=decimals)
return None return None
+24 -5
View File
@@ -4,6 +4,24 @@ from __future__ import annotations
import math import math
from typing import Any from typing import Any
# 币本位权利金常为 1e-4~1e-5 量级(尤其 BTC);round(...,4) 会把净盈亏抹成 0
PREMIUM_DECIMALS_COIN = 8
PREMIUM_DECIMALS_FIAT = 4
def round_premium_amount(v: float | None, *, ccy: str | None = None, decimals: int | None = None) -> float | None:
"""权利金/回收/币盈亏入账精度:ETH/BTC 用 8 位,USDC 等用法币 4 位."""
if v is None:
return None
try:
n = float(v)
except (TypeError, ValueError):
return None
if decimals is None:
unit = (ccy or "").strip().upper()
decimals = PREMIUM_DECIMALS_COIN if unit in ("ETH", "BTC") else PREMIUM_DECIMALS_FIAT
return round(n, int(decimals))
def ct_mult_from_meta(meta: dict[str, Any] | None) -> float: def ct_mult_from_meta(meta: dict[str, Any] | None) -> float:
if not meta: if not meta:
@@ -276,7 +294,7 @@ def estimate_close_by_bids(
"available_sheets": sz, "available_sheets": sz,
"sheets": take, "sheets": take,
"eth_amount": eth_amount, "eth_amount": eth_amount,
"received": round(received, 4), "received": round_premium_amount(received, decimals=PREMIUM_DECIMALS_COIN),
} }
) )
total_received += received total_received += received
@@ -288,15 +306,16 @@ def estimate_close_by_bids(
estimated_pnl_ratio_pct = None estimated_pnl_ratio_pct = None
if premium_paid is not None and covered > 0: if premium_paid is not None and covered > 0:
paid = float(premium_paid) paid = float(premium_paid)
estimated_pnl = round(total_received - paid, 4) # 必须用 8 位:BTC 权利金常约 1e-4,round(...,4) 会把盈亏抹成 0
if paid > 0: estimated_pnl = round_premium_amount(total_received - paid, decimals=PREMIUM_DECIMALS_COIN)
if paid > 0 and estimated_pnl is not None:
estimated_pnl_ratio_pct = round(estimated_pnl / paid * 100.0, 2) estimated_pnl_ratio_pct = round(estimated_pnl / paid * 100.0, 2)
return { return {
"levels": levels, "levels": levels,
"covered_sheets": covered, "covered_sheets": covered,
"uncovered_sheets": remaining, "uncovered_sheets": remaining,
"total_received": round(total_received, 4), "total_received": round_premium_amount(total_received, decimals=PREMIUM_DECIMALS_COIN),
"avg_px": round(avg_px, 4) if avg_px is not None else None, "avg_px": round(avg_px, 8) if avg_px is not None else None,
"estimated_pnl": estimated_pnl, "estimated_pnl": estimated_pnl,
"estimated_pnl_ratio_pct": estimated_pnl_ratio_pct, "estimated_pnl_ratio_pct": estimated_pnl_ratio_pct,
"bid_invalid": False, "bid_invalid": False,
+3 -1
View File
@@ -59,11 +59,13 @@ def profit_exit_hit(
def required_recycle_usdc(premium_paid: float, mult: float) -> float | None: def required_recycle_usdc(premium_paid: float, mult: float) -> float | None:
from lib.options.options_pricing_lib import PREMIUM_DECIMALS_COIN, round_premium_amount
prem = float(premium_paid or 0) prem = float(premium_paid or 0)
m = float(mult or 0) m = float(mult or 0)
if prem <= 0 or m <= 0: if prem <= 0 or m <= 0:
return None return None
return round(prem * (1.0 + m), 4) return round_premium_amount(prem * (1.0 + m), decimals=PREMIUM_DECIMALS_COIN)
def set_profit_exit( def set_profit_exit(
+5 -1
View File
@@ -1209,7 +1209,11 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
sizing = dict(sizing) sizing = dict(sizing)
sizing["sheets"] = sheets sizing["sheets"] = sheets
sizing["eth_amount"] = round(sheets * ct_mult, 8) sizing["eth_amount"] = round(sheets * ct_mult, 8)
sizing["total_premium"] = round(fill_px * sheets * ct_mult, 4) from lib.options.options_pricing_lib import PREMIUM_DECIMALS_COIN, round_premium_amount
sizing["total_premium"] = round_premium_amount(
fill_px * sheets * ct_mult, decimals=PREMIUM_DECIMALS_COIN
)
conn = cfg["get_db"]() conn = cfg["get_db"]()
trade_id = None trade_id = None
target_mon = None target_mon = None