修复交易记录基数误把折算标的币数量当保证金:写入清洗+列表展示自动纠偏。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-12 22:23:26 +08:00
parent e16b8c8dd2
commit b5955f15ae
4 changed files with 315 additions and 13 deletions
+17 -3
View File
@@ -10,7 +10,22 @@ def enrich_trade_price_displays(
format_price_fn: Optional[Callable[[Any, Any], str]] = None,
) -> dict[str, Any]:
"""为成交/止损/止盈补交易所精度展示字段(供交易记录表直接渲染)."""
if not format_price_fn or not isinstance(item, dict):
if not isinstance(item, dict):
return item
try:
from lib.trade.trade_margin_record_lib import repair_stored_margin_capital
fixed = repair_stored_margin_capital(
item.get("margin_capital"),
trigger_price=item.get("trigger_price"),
leverage=item.get("leverage"),
symbol=item.get("symbol"),
)
if fixed is not None:
item["margin_capital"] = fixed
except Exception:
pass
if not format_price_fn:
return item
sym = item.get("symbol")
stop_show = item.get("display_open_stop_loss")
@@ -59,8 +74,7 @@ def list_trade_records_page(
page = pages
offset = (page - 1) * limit
items = records[offset : offset + limit]
if format_price_fn is not None:
items = [enrich_trade_price_displays(dict(it), format_price_fn) for it in items]
items = [enrich_trade_price_displays(dict(it), format_price_fn) for it in items]
return {
"ok": True,
"items": items,
+155
View File
@@ -0,0 +1,155 @@
"""交易记录「基数」= U 保证金;防止把折算标的(币数量)误写成/误显示为基数."""
from __future__ import annotations
from typing import Any, Optional
def _pos_float(v: Any) -> Optional[float]:
if v is None or v == "":
return None
try:
x = float(v)
except (TypeError, ValueError):
return None
if x != x or x <= 0: # NaN or non-positive
return None
return x
def _near(a: float, b: float, *, rel: float = 0.05, abs_tol: float = 1e-4) -> bool:
scale = max(abs(a), abs(b), abs_tol)
return abs(a - b) <= max(abs_tol, rel * scale)
def looks_like_coin_amount_as_margin(
margin: Any,
*,
trigger_price: Any = None,
leverage: Any = None,
base_amount: Any = None,
plan_margin: Any = None,
notional_value: Any = None,
) -> bool:
"""判断 margin 是否更像折算标的币数,而非 USDT 保证金."""
m = _pos_float(margin)
if m is None:
return False
base = _pos_float(base_amount)
if base is not None and _near(m, base, rel=0.05, abs_tol=1e-4):
return True
plan = _pos_float(plan_margin)
if plan is not None and plan >= 1.0 and m < max(1.0, plan * 0.05):
return True
notional = _pos_float(notional_value)
lev = _pos_float(leverage)
if notional is not None and lev is not None and lev > 0:
approx = notional / lev
if approx >= 1.0 and m < max(1.0, approx * 0.05):
return True
px = _pos_float(trigger_price)
if px is not None and lev is not None and lev > 0 and m < 1.0 and px >= 1000:
# 高价币:保证金 <1U 极不合理;按币数量反推的保证金却合理
implied = m * px / lev
if implied >= 10.0:
return True
return False
def coin_amount_to_margin_usdt(
coin_amount: Any,
*,
trigger_price: Any,
leverage: Any,
) -> Optional[float]:
"""币数量 × 价格 / 杠杆 ≈ USDT 保证金."""
coin = _pos_float(coin_amount)
px = _pos_float(trigger_price)
lev = _pos_float(leverage)
if coin is None or px is None or lev is None or lev <= 0:
return None
return round(coin * px / lev, 2)
def sanitize_exchange_initial_margin(
initial: Any,
*,
notional: Any = None,
order_leverage: Any = None,
coin_amount: Any = None,
) -> Optional[float]:
"""交易所快照保证金清洗:拒绝币数量误入,必要时用名义/杠杆粗算."""
m = _pos_float(initial)
coin = _pos_float(coin_amount)
if m is not None and coin is not None and _near(m, coin, rel=0.05, abs_tol=1e-4):
m = None
notional_v = _pos_float(notional)
lev = _pos_float(order_leverage)
approx = None
if notional_v is not None and lev is not None and lev > 0:
approx = notional_v / lev
if m is not None and approx is not None and approx >= 1.0 and m < max(1.0, approx * 0.05):
m = None
if m is not None:
return round(m, 4)
if approx is not None and approx > 0:
return round(approx, 4)
return None
def resolve_trade_record_margin_usdt(
*,
exchange_margin_usdt: Any = None,
plan_margin_capital: Any = None,
base_amount: Any = None,
notional_value: Any = None,
leverage: Any = None,
trigger_price: Any = None,
) -> Optional[float]:
"""写入 trade_records.基数:优先交易所快照,异常时回退计划保证金."""
plan = _pos_float(plan_margin_capital)
ex = _pos_float(exchange_margin_usdt)
if ex is not None and looks_like_coin_amount_as_margin(
ex,
trigger_price=trigger_price,
leverage=leverage,
base_amount=base_amount,
plan_margin=plan,
notional_value=notional_value,
):
# 快照像币数量:优先用计划保证金;无计划时再按币×价/杠杆反推
if plan is not None:
return round(plan, 2)
repaired = coin_amount_to_margin_usdt(
ex, trigger_price=trigger_price, leverage=leverage
)
if repaired is not None:
return repaired
ex = None
if ex is not None:
return round(ex, 2)
if plan is not None:
return round(plan, 2)
return None
def repair_stored_margin_capital(
margin_capital: Any,
*,
trigger_price: Any = None,
leverage: Any = None,
symbol: Any = None,
) -> Optional[float]:
"""展示/列表:修复已入库的「币数量当基数」旧数据."""
del symbol # 预留按币种阈值;当前用价位启发式即可
m = _pos_float(margin_capital)
if m is None:
return None
if looks_like_coin_amount_as_margin(
m, trigger_price=trigger_price, leverage=leverage
):
repaired = coin_amount_to_margin_usdt(
m, trigger_price=trigger_price, leverage=leverage
)
if repaired is not None:
return repaired
return round(m, 2)