diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 7fb3f6e..962c6e0 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -9530,6 +9530,7 @@ register_trade_records_api( to_effective_trade_dict=to_effective_trade_dict, filter_trade_records_excluding_miss=filter_trade_records_excluding_miss, app_tz=APP_TZ, + format_price_fn=format_price_for_symbol, ) def _dashboard_enrich_orders(items): diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 07b89af..a070a3b 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -9368,6 +9368,7 @@ register_trade_records_api( to_effective_trade_dict=to_effective_trade_dict, filter_trade_records_excluding_miss=filter_trade_records_excluding_miss, app_tz=APP_TZ, + format_price_fn=format_price_for_symbol, ) def _dashboard_enrich_orders(items): diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 36ba081..8555625 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -2107,6 +2107,7 @@ def to_effective_trade_dict(row): def format_price_for_symbol(symbol, value): + """价格展示:与交易所 price_to_precision 一致(与入库 round_price_to_exchange 对齐).""" if value in (None, ""): return "-" try: @@ -2115,8 +2116,15 @@ def format_price_for_symbol(symbol, value): return str(value) if v == 0: return "0" + try: + ex_sym = normalize_okx_symbol(str(symbol or "").strip()) if symbol else "" + if ex_sym: + ensure_markets_loaded() + return str(exchange.price_to_precision(ex_sym, v)) + except Exception: + pass av = abs(v) - # 根据币价量级动态精度:低价币保留更多小数,高价币减少噪音位数 + # 无法加载市场或无该合约时:按价格量级回退(尽量不阻断页面) if av >= 10000: d = 2 elif av >= 100: @@ -9035,6 +9043,7 @@ register_trade_records_api( to_effective_trade_dict=to_effective_trade_dict, filter_trade_records_excluding_miss=filter_trade_records_excluding_miss, app_tz=APP_TZ, + format_price_fn=format_price_for_symbol, ) diff --git a/lib/common/static/records_review_page.js b/lib/common/static/records_review_page.js index 3010ecd..43b853c 100644 --- a/lib/common/static/records_review_page.js +++ b/lib/common/static/records_review_page.js @@ -42,6 +42,25 @@ return n.toFixed(digits == null ? 2 : digits); } + /** 优先用后端交易所精度字符串;否则回退量级格式(与 formatPriceForInput 一致). */ + function fmtPx(display, raw) { + if (display != null && display !== "") return esc(display); + if (raw == null || raw === "") return "—"; + var n = Number(raw); + if (!Number.isFinite(n)) return esc(raw); + var av = Math.abs(n); + var d; + if (av >= 10000) d = 2; + else if (av >= 100) d = 3; + else if (av >= 1) d = 4; + else if (av >= 0.01) d = 6; + else if (av >= 0.0001) d = 8; + else d = 10; + var text = n.toFixed(d); + if (text.indexOf(".") >= 0) text = text.replace(/\.?0+$/, ""); + return text; + } + function fmtTime(s) { if (!s) return "—"; return esc(String(s).slice(0, 16)); @@ -199,13 +218,13 @@ dirTxt + "" + "" + - fmtNum(t.trigger_price, 4) + + fmtPx(t.trigger_price_display, t.trigger_price) + "" + "" + - fmtNum(stopShow, 4) + + fmtPx(t.stop_loss_display, stopShow) + "" + "" + - fmtNum(tpShow, 4) + + fmtPx(t.take_profit_display, tpShow) + "" + "" + margin + diff --git a/lib/instance/records_api_register.py b/lib/instance/records_api_register.py index fb7d541..dd96ffd 100644 --- a/lib/instance/records_api_register.py +++ b/lib/instance/records_api_register.py @@ -18,6 +18,7 @@ def register_trade_records_api( to_effective_trade_dict: Callable[[Any], dict[str, Any]], filter_trade_records_excluding_miss: Callable[[list], list], app_tz: Any, + format_price_fn: Callable[[Any, Any], str] | None = None, ) -> None: from lib.instance.records_list_lib import list_trade_records_page @@ -48,6 +49,7 @@ def register_trade_records_api( filter_fn=filter_trade_records_excluding_miss, limit=limit, offset=offset, + format_price_fn=format_price_fn, ) return jsonify(payload) finally: diff --git a/lib/instance/records_list_lib.py b/lib/instance/records_list_lib.py index dbda60d..8b8062b 100644 --- a/lib/instance/records_list_lib.py +++ b/lib/instance/records_list_lib.py @@ -2,7 +2,32 @@ from __future__ import annotations -from typing import Any, Callable +from typing import Any, Callable, Optional + + +def enrich_trade_price_displays( + item: dict[str, Any], + format_price_fn: Optional[Callable[[Any, Any], str]] = None, +) -> dict[str, Any]: + """为成交/止损/止盈补交易所精度展示字段(供交易记录表直接渲染).""" + if not format_price_fn or not isinstance(item, dict): + return item + sym = item.get("symbol") + stop_show = item.get("display_open_stop_loss") + if stop_show in (None, ""): + stop_show = item.get("initial_stop_loss") + if stop_show in (None, ""): + stop_show = item.get("stop_loss") + tp_show = item.get("effective_take_profit") + if tp_show in (None, ""): + tp_show = item.get("take_profit") + try: + item["trigger_price_display"] = format_price_fn(sym, item.get("trigger_price")) + item["stop_loss_display"] = format_price_fn(sym, stop_show) + item["take_profit_display"] = format_price_fn(sym, tp_show) + except Exception: + pass + return item def list_trade_records_page( @@ -16,6 +41,7 @@ def list_trade_records_page( limit: int = 5, offset: int = 0, fetch_cap: int = 1000, + format_price_fn: Optional[Callable[[Any, Any], str]] = None, ) -> dict[str, Any]: """按列表窗拉取、enrich、过滤「错过」后分页.""" limit = max(1, min(100, int(limit or 5))) @@ -33,6 +59,8 @@ 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] return { "ok": True, "items": items, diff --git a/tests/test_records_list_lib.py b/tests/test_records_list_lib.py index bde81b0..b9a1fcf 100644 --- a/tests/test_records_list_lib.py +++ b/tests/test_records_list_lib.py @@ -78,6 +78,25 @@ class RecordsListLibTest(unittest.TestCase): self.assertEqual(out["page"], 2) self.assertEqual(len(out["items"]), 5) + def test_price_display_enrich(self): + from lib.instance.records_list_lib import enrich_trade_price_displays + + item = { + "symbol": "BTC/USDT", + "trigger_price": 63902.0, + "display_open_stop_loss": 64500.0, + "effective_take_profit": 62800.0, + } + + def _fmt(sym, v): + self.assertEqual(sym, "BTC/USDT") + return f"{float(v):.1f}" + + out = enrich_trade_price_displays(item, _fmt) + self.assertEqual(out["trigger_price_display"], "63902.0") + self.assertEqual(out["stop_loss_display"], "64500.0") + self.assertEqual(out["take_profit_display"], "62800.0") + if __name__ == "__main__": unittest.main()