Files
crypto_monitor/order_monitor_display_lib.py
T
dekun 3d55aa0975 fix(gate-bot): format mark price with exchange symbol precision
Use price_to_precision in price_snapshot so live mark price matches entry/SL display instead of fixed 8 decimals.
2026-06-04 16:33:09 +08:00

172 lines
5.3 KiB
Python

"""实时持仓展示:开仓快照盈亏比、交易所止损是否已保本。"""
from __future__ import annotations
from typing import Any, Callable, Optional
def _positive_float(value: Any) -> Optional[float]:
try:
v = float(value)
return v if v > 0 else None
except (TypeError, ValueError):
return None
def snapshot_stop_loss(initial_stop_loss: Any, stop_loss: Any) -> Optional[float]:
"""展示盈亏比时优先用开仓时止损快照。"""
sl = _positive_float(initial_stop_loss)
if sl is not None:
return sl
return _positive_float(stop_loss)
def snapshot_rr(
calc_rr_ratio_fn: Callable[..., Optional[float]],
direction: str,
trigger_price: Any,
initial_stop_loss: Any,
stop_loss: Any,
take_profit: Any,
) -> Optional[float]:
entry = _positive_float(trigger_price)
sl = snapshot_stop_loss(initial_stop_loss, stop_loss)
tp = _positive_float(take_profit)
if entry is None or sl is None or tp is None:
return None
return calc_rr_ratio_fn(direction or "long", entry, sl, tp)
def tpsl_slot_trigger_price(slot: Any) -> Optional[float]:
if not isinstance(slot, dict):
return None
for key in ("trigger_price", "trigger_display"):
v = _positive_float(slot.get(key))
if v is not None:
return v
return None
def stop_is_profit_protecting(direction: str, entry_price: Any, stop_loss: Any) -> bool:
"""
止损是否已在盈利侧(保本/锁盈),不再适用「开仓盈亏比」风控。
做空:止损 < 成交价;做多:止损 > 成交价。
"""
entry = _positive_float(entry_price)
sl = _positive_float(stop_loss)
if entry is None or sl is None:
return False
d = (direction or "long").strip().lower()
if d == "short":
return sl < entry
return sl > entry
def tpsl_update_passes_rr_gate(
direction: str,
entry_price: Any,
stop_loss: Any,
take_profit: Any,
min_rr: float,
calc_rr_ratio_fn: Callable[..., Optional[float]],
) -> tuple[bool, Optional[str]]:
"""持仓委托改价:盈利侧止损跳过最低盈亏比;否则按开仓价几何校验。"""
if stop_is_profit_protecting(direction, entry_price, stop_loss):
return True, None
rr = calc_rr_ratio_fn(direction or "long", entry_price, stop_loss, take_profit)
if rr is not None and rr >= float(min_rr):
return True, None
rr_txt = f"{rr:.4f}" if rr is not None else "无法计算"
return False, f"计划盈亏比 {rr_txt}:1 低于最低要求 {min_rr}:1(盈利侧保本止损不受此限)"
def is_sl_breakeven_secured(direction: str, entry_price: Any, exchange_sl_price: Any) -> bool:
"""
交易所当前止损相对开仓成交价是否已保本。
做多:止损 >= 成交价;做空:止损 <= 成交价。
"""
entry = _positive_float(entry_price)
sl = _positive_float(exchange_sl_price)
if entry is None or sl is None:
return False
d = (direction or "long").strip().lower()
if d == "short":
return sl <= entry
return sl >= entry
def sl_breakeven_from_exchange_tpsl(
direction: str,
entry_price: Any,
exchange_tpsl: Any,
) -> bool:
if not isinstance(exchange_tpsl, dict):
return False
sl_px = tpsl_slot_trigger_price(exchange_tpsl.get("sl"))
if sl_px is None:
return False
return is_sl_breakeven_secured(direction, entry_price, sl_px)
def enrich_order_display_fields(item: dict[str, Any], calc_rr_ratio_fn: Callable[..., Optional[float]]) -> dict[str, Any]:
item["rr_ratio"] = snapshot_rr(
calc_rr_ratio_fn,
item.get("direction") or "long",
item.get("trigger_price"),
item.get("initial_stop_loss"),
item.get("stop_loss"),
item.get("take_profit"),
)
return item
def apply_order_live_price_display(
payload: dict[str, Any],
symbol: Any,
ticker_price: Any,
exchange_mark_price: Any,
format_price_fn: Callable[[Any, Any], str],
) -> dict[str, Any]:
"""标记价/现价展示:与交易所 price_to_precision 对齐,避免前端 toFixed(8)。"""
px_for_fmt = ticker_price
mark_raw = exchange_mark_price
if mark_raw is not None:
try:
px_for_fmt = float(mark_raw)
except (TypeError, ValueError):
pass
px_disp = format_price_fn(symbol, px_for_fmt)
payload["price_display"] = px_disp
if mark_raw is not None:
try:
payload["exchange_mark_price_display"] = format_price_fn(symbol, float(mark_raw))
except (TypeError, ValueError):
payload["exchange_mark_price_display"] = px_disp
else:
payload["exchange_mark_price_display"] = None
return payload
def apply_order_price_display_fields(
payload: dict[str, Any],
*,
direction: str,
entry_price: Any,
initial_stop_loss: Any,
stop_loss: Any,
take_profit: Any,
calc_rr_ratio_fn: Callable[..., Optional[float]],
exchange_tpsl: Any = None,
) -> dict[str, Any]:
payload["rr_ratio"] = snapshot_rr(
calc_rr_ratio_fn,
direction,
entry_price,
initial_stop_loss,
stop_loss,
take_profit,
)
payload["sl_breakeven_secured"] = sl_breakeven_from_exchange_tpsl(
direction, entry_price, exchange_tpsl
)
return payload