修复交易记录基数虚高:禁止币×价÷杠杆发明保证金,优先计划保证金/风险金额反推。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3901,6 +3901,14 @@ def margin_capital_for_trade_record(order_row):
|
|||||||
lev = order_row["leverage"]
|
lev = order_row["leverage"]
|
||||||
if "trigger_price" in keys:
|
if "trigger_price" in keys:
|
||||||
trigger = order_row["trigger_price"]
|
trigger = order_row["trigger_price"]
|
||||||
|
stop = None
|
||||||
|
risk = None
|
||||||
|
if "initial_stop_loss" in keys:
|
||||||
|
stop = order_row["initial_stop_loss"]
|
||||||
|
if (stop is None or stop == "") and "stop_loss" in keys:
|
||||||
|
stop = order_row["stop_loss"]
|
||||||
|
if "risk_amount" in keys:
|
||||||
|
risk = order_row["risk_amount"]
|
||||||
return resolve_trade_record_margin_usdt(
|
return resolve_trade_record_margin_usdt(
|
||||||
exchange_margin_usdt=_order_row_exchange_margin_usdt(order_row),
|
exchange_margin_usdt=_order_row_exchange_margin_usdt(order_row),
|
||||||
plan_margin_capital=plan,
|
plan_margin_capital=plan,
|
||||||
@@ -3908,6 +3916,8 @@ def margin_capital_for_trade_record(order_row):
|
|||||||
notional_value=notional,
|
notional_value=notional,
|
||||||
leverage=lev,
|
leverage=lev,
|
||||||
trigger_price=trigger,
|
trigger_price=trigger,
|
||||||
|
stop_loss=stop,
|
||||||
|
risk_amount=risk,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ def enrich_trade_price_displays(
|
|||||||
trigger_price=item.get("trigger_price"),
|
trigger_price=item.get("trigger_price"),
|
||||||
leverage=item.get("leverage"),
|
leverage=item.get("leverage"),
|
||||||
symbol=item.get("symbol"),
|
symbol=item.get("symbol"),
|
||||||
|
stop_loss=item.get("stop_loss"),
|
||||||
|
initial_stop_loss=item.get("initial_stop_loss"),
|
||||||
|
risk_amount=item.get("risk_amount"),
|
||||||
)
|
)
|
||||||
if fixed is not None:
|
if fixed is not None:
|
||||||
item["margin_capital"] = fixed
|
item["margin_capital"] = fixed
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""交易记录「基数」= U 保证金;防止把折算标的(币数量)误写成/误显示为基数."""
|
"""交易记录「基数」= U 保证金;防止币数量/账户保证金误入,展示时禁止用币×价÷杠杆虚增."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
@@ -55,13 +55,33 @@ def looks_like_coin_amount_as_margin(
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def margin_from_risk_amount(
|
||||||
|
risk_amount: Any,
|
||||||
|
*,
|
||||||
|
trigger_price: Any,
|
||||||
|
stop_loss: Any,
|
||||||
|
leverage: Any,
|
||||||
|
) -> Optional[float]:
|
||||||
|
"""由风险金额反推保证金: risk ≈ margin × lev × |entry-sl| / entry."""
|
||||||
|
risk = _pos_float(risk_amount)
|
||||||
|
px = _pos_float(trigger_price)
|
||||||
|
sl = _pos_float(stop_loss)
|
||||||
|
lev = _pos_float(leverage)
|
||||||
|
if risk is None or px is None or sl is None or lev is None or lev <= 0 or px <= 0:
|
||||||
|
return None
|
||||||
|
dist = abs(px - sl)
|
||||||
|
if dist <= 0:
|
||||||
|
return None
|
||||||
|
return round(risk * px / (lev * dist), 2)
|
||||||
|
|
||||||
|
|
||||||
def coin_amount_to_margin_usdt(
|
def coin_amount_to_margin_usdt(
|
||||||
coin_amount: Any,
|
coin_amount: Any,
|
||||||
*,
|
*,
|
||||||
trigger_price: Any,
|
trigger_price: Any,
|
||||||
leverage: Any,
|
leverage: Any,
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
"""币数量 × 价格 / 杠杆 ≈ USDT 保证金."""
|
"""币数量 × 价格 / 杠杆 ≈ USDT 保证金(仅作末位兜底,展示层勿优先使用)."""
|
||||||
coin = _pos_float(coin_amount)
|
coin = _pos_float(coin_amount)
|
||||||
px = _pos_float(trigger_price)
|
px = _pos_float(trigger_price)
|
||||||
lev = _pos_float(leverage)
|
lev = _pos_float(leverage)
|
||||||
@@ -77,7 +97,7 @@ def sanitize_exchange_initial_margin(
|
|||||||
order_leverage: Any = None,
|
order_leverage: Any = None,
|
||||||
coin_amount: Any = None,
|
coin_amount: Any = None,
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
"""交易所快照保证金清洗:拒绝币数量误入,必要时用名义/杠杆粗算."""
|
"""交易所快照保证金清洗:拒绝币数量/过大账户保证金,必要时用名义/杠杆粗算."""
|
||||||
m = _pos_float(initial)
|
m = _pos_float(initial)
|
||||||
coin = _pos_float(coin_amount)
|
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):
|
if m is not None and coin is not None and _near(m, coin, rel=0.05, abs_tol=1e-4):
|
||||||
@@ -87,7 +107,11 @@ def sanitize_exchange_initial_margin(
|
|||||||
approx = None
|
approx = None
|
||||||
if notional_v is not None and lev is not None and lev > 0:
|
if notional_v is not None and lev is not None and lev > 0:
|
||||||
approx = notional_v / lev
|
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):
|
if m is not None and approx is not None and approx >= 1.0:
|
||||||
|
if m < max(1.0, approx * 0.05):
|
||||||
|
m = None
|
||||||
|
elif m > approx * 2.5:
|
||||||
|
# 常见于全仓 collateral≈账户权益,远大于本仓占用
|
||||||
m = None
|
m = None
|
||||||
if m is not None:
|
if m is not None:
|
||||||
return round(m, 4)
|
return round(m, 4)
|
||||||
@@ -104,10 +128,22 @@ def resolve_trade_record_margin_usdt(
|
|||||||
notional_value: Any = None,
|
notional_value: Any = None,
|
||||||
leverage: Any = None,
|
leverage: Any = None,
|
||||||
trigger_price: Any = None,
|
trigger_price: Any = None,
|
||||||
|
stop_loss: Any = None,
|
||||||
|
risk_amount: Any = None,
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
"""写入 trade_records.基数:优先交易所快照,异常时回退计划保证金."""
|
"""写入 trade_records.基数:计划保证金优先于异常交易所快照;禁止币×价÷杠杆虚增."""
|
||||||
plan = _pos_float(plan_margin_capital)
|
plan = _pos_float(plan_margin_capital)
|
||||||
ex = _pos_float(exchange_margin_usdt)
|
ex = _pos_float(exchange_margin_usdt)
|
||||||
|
|
||||||
|
if plan is not None and looks_like_coin_amount_as_margin(
|
||||||
|
plan,
|
||||||
|
trigger_price=trigger_price,
|
||||||
|
leverage=leverage,
|
||||||
|
base_amount=base_amount,
|
||||||
|
notional_value=notional_value,
|
||||||
|
):
|
||||||
|
plan = None
|
||||||
|
|
||||||
if ex is not None and looks_like_coin_amount_as_margin(
|
if ex is not None and looks_like_coin_amount_as_margin(
|
||||||
ex,
|
ex,
|
||||||
trigger_price=trigger_price,
|
trigger_price=trigger_price,
|
||||||
@@ -116,19 +152,28 @@ def resolve_trade_record_margin_usdt(
|
|||||||
plan_margin=plan,
|
plan_margin=plan,
|
||||||
notional_value=notional_value,
|
notional_value=notional_value,
|
||||||
):
|
):
|
||||||
# 快照像币数量:优先用计划保证金;无计划时再按币×价/杠杆反推
|
ex = None
|
||||||
|
if ex is not None and plan is not None and ex > plan * 2.5:
|
||||||
|
ex = None
|
||||||
|
|
||||||
if plan is not None:
|
if plan is not None:
|
||||||
return round(plan, 2)
|
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:
|
if ex is not None:
|
||||||
return round(ex, 2)
|
return round(ex, 2)
|
||||||
if plan is not None:
|
|
||||||
return round(plan, 2)
|
from_risk = margin_from_risk_amount(
|
||||||
|
risk_amount,
|
||||||
|
trigger_price=trigger_price,
|
||||||
|
stop_loss=stop_loss,
|
||||||
|
leverage=leverage,
|
||||||
|
)
|
||||||
|
if from_risk is not None:
|
||||||
|
return from_risk
|
||||||
|
|
||||||
|
notional = _pos_float(notional_value)
|
||||||
|
lev = _pos_float(leverage)
|
||||||
|
if notional is not None and lev is not None and lev > 0:
|
||||||
|
return round(notional / lev, 2)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -138,18 +183,31 @@ def repair_stored_margin_capital(
|
|||||||
trigger_price: Any = None,
|
trigger_price: Any = None,
|
||||||
leverage: Any = None,
|
leverage: Any = None,
|
||||||
symbol: Any = None,
|
symbol: Any = None,
|
||||||
|
stop_loss: Any = None,
|
||||||
|
risk_amount: Any = None,
|
||||||
|
initial_stop_loss: Any = None,
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
"""展示/列表:修复已入库的「币数量当基数」旧数据."""
|
"""展示/列表:修复已入库的异常基数;禁止把币数量换算成虚高保证金."""
|
||||||
del symbol # 预留按币种阈值;当前用价位启发式即可
|
del symbol # 预留按币种阈值
|
||||||
m = _pos_float(margin_capital)
|
m = _pos_float(margin_capital)
|
||||||
|
sl = stop_loss if stop_loss not in (None, "") else initial_stop_loss
|
||||||
|
from_risk = margin_from_risk_amount(
|
||||||
|
risk_amount,
|
||||||
|
trigger_price=trigger_price,
|
||||||
|
stop_loss=sl,
|
||||||
|
leverage=leverage,
|
||||||
|
)
|
||||||
|
|
||||||
if m is None:
|
if m is None:
|
||||||
return None
|
return from_risk
|
||||||
|
|
||||||
if looks_like_coin_amount_as_margin(
|
if looks_like_coin_amount_as_margin(
|
||||||
m, trigger_price=trigger_price, leverage=leverage
|
m, trigger_price=trigger_price, leverage=leverage
|
||||||
):
|
):
|
||||||
repaired = coin_amount_to_margin_usdt(
|
return from_risk
|
||||||
m, trigger_price=trigger_price, leverage=leverage
|
|
||||||
)
|
if from_risk is not None and m > from_risk * 2.5:
|
||||||
if repaired is not None:
|
# 已入库虚高(如曾用币×价÷杠杆「纠偏」成 384)
|
||||||
return repaired
|
return from_risk
|
||||||
|
|
||||||
return round(m, 2)
|
return round(m, 2)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""交易记录基数(保证金)口径:币数量误记为基数时的修复."""
|
"""交易记录基数(保证金)口径:币数量误记 / 虚高纠偏."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
@@ -6,6 +6,7 @@ import unittest
|
|||||||
from lib.trade.trade_margin_record_lib import (
|
from lib.trade.trade_margin_record_lib import (
|
||||||
coin_amount_to_margin_usdt,
|
coin_amount_to_margin_usdt,
|
||||||
looks_like_coin_amount_as_margin,
|
looks_like_coin_amount_as_margin,
|
||||||
|
margin_from_risk_amount,
|
||||||
repair_stored_margin_capital,
|
repair_stored_margin_capital,
|
||||||
resolve_trade_record_margin_usdt,
|
resolve_trade_record_margin_usdt,
|
||||||
sanitize_exchange_initial_margin,
|
sanitize_exchange_initial_margin,
|
||||||
@@ -33,13 +34,21 @@ class TestTradeMarginRecord(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_coin_to_margin(self):
|
def test_coin_to_margin(self):
|
||||||
# 0.12 BTC * 64054 / 20 ≈ 384.32
|
# 仅作数学兜底函数;展示层不得优先用它虚增高额
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
coin_amount_to_margin_usdt(0.12, trigger_price=64054.1, leverage=20),
|
coin_amount_to_margin_usdt(0.12, trigger_price=64054.1, leverage=20),
|
||||||
384.32,
|
384.32,
|
||||||
places=2,
|
places=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_margin_from_risk(self):
|
||||||
|
# risk = 110 * 20 * 445.9 / 64054.1 ≈ 15.32
|
||||||
|
risk = 110.0 * 20.0 * 445.9 / 64054.1
|
||||||
|
out = margin_from_risk_amount(
|
||||||
|
risk, trigger_price=64054.1, stop_loss=64500.0, leverage=20
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(out, 110.0, places=1)
|
||||||
|
|
||||||
def test_resolve_prefers_plan_when_exchange_is_coin(self):
|
def test_resolve_prefers_plan_when_exchange_is_coin(self):
|
||||||
out = resolve_trade_record_margin_usdt(
|
out = resolve_trade_record_margin_usdt(
|
||||||
exchange_margin_usdt=0.12,
|
exchange_margin_usdt=0.12,
|
||||||
@@ -50,31 +59,60 @@ class TestTradeMarginRecord(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(out, 117.71)
|
self.assertEqual(out, 117.71)
|
||||||
|
|
||||||
def test_resolve_repairs_coin_when_no_plan(self):
|
def test_resolve_prefers_plan_over_inflated_exchange(self):
|
||||||
|
out = resolve_trade_record_margin_usdt(
|
||||||
|
exchange_margin_usdt=384.32,
|
||||||
|
plan_margin_capital=117.71,
|
||||||
|
leverage=20,
|
||||||
|
trigger_price=64054.1,
|
||||||
|
)
|
||||||
|
self.assertEqual(out, 117.71)
|
||||||
|
|
||||||
|
def test_resolve_uses_risk_when_no_plan(self):
|
||||||
|
risk = 110.0 * 20.0 * 445.9 / 64054.1
|
||||||
|
out = resolve_trade_record_margin_usdt(
|
||||||
|
exchange_margin_usdt=0.12,
|
||||||
|
plan_margin_capital=None,
|
||||||
|
leverage=20,
|
||||||
|
trigger_price=64054.1,
|
||||||
|
stop_loss=64500.0,
|
||||||
|
risk_amount=risk,
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(out, 110.0, places=1)
|
||||||
|
|
||||||
|
def test_resolve_does_not_invent_coin_times_price(self):
|
||||||
out = resolve_trade_record_margin_usdt(
|
out = resolve_trade_record_margin_usdt(
|
||||||
exchange_margin_usdt=0.12,
|
exchange_margin_usdt=0.12,
|
||||||
plan_margin_capital=None,
|
plan_margin_capital=None,
|
||||||
leverage=20,
|
leverage=20,
|
||||||
trigger_price=64054.1,
|
trigger_price=64054.1,
|
||||||
)
|
)
|
||||||
self.assertEqual(out, 384.32)
|
self.assertIsNone(out)
|
||||||
|
|
||||||
def test_resolve_uses_plan_when_cannot_repair(self):
|
def test_repair_stored_uses_risk_not_coin_times_price(self):
|
||||||
out = resolve_trade_record_margin_usdt(
|
risk = 110.0 * 20.0 * 445.9 / 64054.1
|
||||||
exchange_margin_usdt=0.05,
|
self.assertAlmostEqual(
|
||||||
plan_margin_capital=100.0,
|
|
||||||
leverage=None,
|
|
||||||
trigger_price=None,
|
|
||||||
base_amount=0.05,
|
|
||||||
)
|
|
||||||
self.assertEqual(out, 100.0)
|
|
||||||
|
|
||||||
def test_repair_stored_display(self):
|
|
||||||
self.assertEqual(
|
|
||||||
repair_stored_margin_capital(
|
repair_stored_margin_capital(
|
||||||
0.12, trigger_price=64054.1, leverage=20
|
0.12,
|
||||||
|
trigger_price=64054.1,
|
||||||
|
leverage=20,
|
||||||
|
stop_loss=64500.0,
|
||||||
|
risk_amount=risk,
|
||||||
),
|
),
|
||||||
|
110.0,
|
||||||
|
places=1,
|
||||||
|
)
|
||||||
|
# 曾被错误「纠偏」成 384 的入库值,用风险金额压回
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
repair_stored_margin_capital(
|
||||||
384.32,
|
384.32,
|
||||||
|
trigger_price=64054.1,
|
||||||
|
leverage=20,
|
||||||
|
stop_loss=64500.0,
|
||||||
|
risk_amount=risk,
|
||||||
|
),
|
||||||
|
110.0,
|
||||||
|
places=1,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
repair_stored_margin_capital(
|
repair_stored_margin_capital(
|
||||||
@@ -82,6 +120,18 @@ class TestTradeMarginRecord(unittest.TestCase):
|
|||||||
),
|
),
|
||||||
108.97,
|
108.97,
|
||||||
)
|
)
|
||||||
|
# 无风险金额时:绝不把 0.12 换成 384
|
||||||
|
self.assertIsNone(
|
||||||
|
repair_stored_margin_capital(
|
||||||
|
0.12, trigger_price=64054.1, leverage=20
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sanitize_rejects_oversized_collateral(self):
|
||||||
|
out = sanitize_exchange_initial_margin(
|
||||||
|
384.32, notional=2200.0, order_leverage=20, coin_amount=0.034
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(out, 110.0, places=1)
|
||||||
|
|
||||||
def test_sanitize_exchange_margin(self):
|
def test_sanitize_exchange_margin(self):
|
||||||
out = sanitize_exchange_initial_margin(
|
out = sanitize_exchange_initial_margin(
|
||||||
|
|||||||
Reference in New Issue
Block a user