diff --git a/lib/options/options_close_exec_lib.py b/lib/options/options_close_exec_lib.py index 47b6b02..489b411 100644 --- a/lib/options/options_close_exec_lib.py +++ b/lib/options/options_close_exec_lib.py @@ -11,9 +11,11 @@ from lib.options.options_close_gate_lib import ( update_close_gate, ) from lib.options.options_pricing_lib import ( + PREMIUM_DECIMALS_COIN, estimate_close_by_bids, fetch_option_mark_px, is_stub_bid_px, + round_premium_amount, total_premium, ) @@ -304,7 +306,9 @@ def close_option_by_bid1( px = float(order.get("px", locked_bid_px)) 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) invalidate_option_positions_cache() raw2 = cfg["fetch_option_positions"](ex) @@ -344,14 +348,20 @@ def close_option_by_bid1( for i, row in enumerate(open_rows): paid = float(row["premium_paid"] or 0) 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: - 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 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 - pnl = round(recv - paid, 4) + pnl = round_premium_amount(recv - paid, decimals=PREMIUM_DECIMALS_COIN) or 0.0 note_sql = "" from lib.options.options_time_lib import options_app_now_str diff --git a/lib/options/options_positions_lib.py b/lib/options/options_positions_lib.py index 4931b64..7a7b434 100644 --- a/lib/options/options_positions_lib.py +++ b/lib/options/options_positions_lib.py @@ -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: """与持仓卡「净盈亏」同口径:买一可回收 − 权利金;残档买一则无净值.""" + 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 {} if preview.get("bid_invalid"): 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")) paid = _safe_float(row.get("premium_paid")) 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 diff --git a/lib/options/options_pricing_lib.py b/lib/options/options_pricing_lib.py index e392b9a..1793950 100644 --- a/lib/options/options_pricing_lib.py +++ b/lib/options/options_pricing_lib.py @@ -4,6 +4,24 @@ from __future__ import annotations import math 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: if not meta: @@ -276,7 +294,7 @@ def estimate_close_by_bids( "available_sheets": sz, "sheets": take, "eth_amount": eth_amount, - "received": round(received, 4), + "received": round_premium_amount(received, decimals=PREMIUM_DECIMALS_COIN), } ) total_received += received @@ -288,15 +306,16 @@ def estimate_close_by_bids( estimated_pnl_ratio_pct = None if premium_paid is not None and covered > 0: paid = float(premium_paid) - estimated_pnl = round(total_received - paid, 4) - if paid > 0: + # 必须用 8 位:BTC 权利金常约 1e-4,round(...,4) 会把盈亏抹成 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) return { "levels": levels, "covered_sheets": covered, "uncovered_sheets": remaining, - "total_received": round(total_received, 4), - "avg_px": round(avg_px, 4) if avg_px is not None else None, + "total_received": round_premium_amount(total_received, decimals=PREMIUM_DECIMALS_COIN), + "avg_px": round(avg_px, 8) if avg_px is not None else None, "estimated_pnl": estimated_pnl, "estimated_pnl_ratio_pct": estimated_pnl_ratio_pct, "bid_invalid": False, diff --git a/lib/options/options_profit_exit_lib.py b/lib/options/options_profit_exit_lib.py index d8fbbe7..0d5fce6 100644 --- a/lib/options/options_profit_exit_lib.py +++ b/lib/options/options_profit_exit_lib.py @@ -59,11 +59,13 @@ def profit_exit_hit( 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) m = float(mult or 0) if prem <= 0 or m <= 0: 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( diff --git a/lib/options/options_register.py b/lib/options/options_register.py index 87d0060..92981e3 100644 --- a/lib/options/options_register.py +++ b/lib/options/options_register.py @@ -1209,7 +1209,11 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None: sizing = dict(sizing) sizing["sheets"] = sheets 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"]() trade_id = None target_mon = None