feat(hub): exchange price precision, entry price, and trend DCA display

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-03 21:25:24 +08:00
parent fac28c402b
commit f95118065d
5 changed files with 368 additions and 49 deletions
+50 -6
View File
@@ -22,9 +22,16 @@ from __future__ import annotations
import math
import os
import sys
import time
from pathlib import Path
from typing import Any
_REPO_ROOT = Path(__file__).resolve().parents[1]
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
from hub_ohlcv_lib import format_price_by_tick, price_tick_from_market
import ccxt
from fastapi import FastAPI, Header, HTTPException, Request
from fastapi.responses import JSONResponse
@@ -363,6 +370,44 @@ def _finite_or_none(x: Any) -> float | None:
return None
def _position_price_fmt(ex: Any, symbol: str, price: float | None) -> tuple[float | None, str | None, float | None]:
"""返回 (原价, 交易所精度字符串, price_tick)。"""
if price is None or price <= 0 or not symbol:
return None, None, None
tick: float | None = None
try:
ex.load_markets()
unified = ex.market(symbol)["symbol"]
tick = price_tick_from_market(ex, unified)
px_str = str(ex.price_to_precision(unified, price))
return _finite_or_none(float(px_str)), px_str, tick
except Exception:
return price, format_price_by_tick(price, tick), tick
def _position_entry_price(p: dict[str, Any]) -> float | None:
"""四所 ccxt 持仓统一解析开仓均价(Binance/OKX/Gate 字段名不一致)。"""
info = p.get("info") or {}
if not isinstance(info, dict):
info = {}
for key in (
p.get("entryPrice"),
p.get("entry_price"),
p.get("average"),
info.get("entryPrice"),
info.get("entry_price"),
info.get("avgPx"),
info.get("avgEntryPrice"),
info.get("avg_entry_price"),
info.get("avgPrice"),
info.get("openAvgPx"),
):
px = _finite_or_none(key)
if px is not None and px > 0:
return px
return None
def _extract_usdt_total(balance: dict[str, Any]) -> float | None:
"""从 ccxt balance 结构中尽量取出 USDT 总额(与 crypto_monitor_binance 一致)。"""
usdt_info = balance.get("USDT") or {}
@@ -536,11 +581,8 @@ def _status_inner(x_control_token: str | None) -> Any:
notional_f = float(notional) if notional is not None else None
except (TypeError, ValueError):
notional_f = None
entry = p.get("entryPrice")
try:
entry_f = float(entry) if entry is not None else None
except (TypeError, ValueError):
entry_f = None
entry_f = _position_entry_price(p)
_, entry_fmt, price_tick = _position_price_fmt(ex, sym, entry_f)
positions_out.append(
{
"symbol": sym,
@@ -549,7 +591,9 @@ def _status_inner(x_control_token: str | None) -> Any:
"contracts_signed": c,
"notional_usdt": _finite_or_none(notional_f) if notional_f is not None else None,
"unrealized_pnl": _finite_or_none(upnl_f),
"entry_price": _finite_or_none(entry_f) if entry_f is not None else None,
"entry_price": entry_f,
"entry_price_fmt": entry_fmt,
"price_tick": _finite_or_none(price_tick) if price_tick is not None else None,
}
)