Show trade record prices at exchange tick precision.

API enriches entry/SL/TP with price_to_precision display strings; OKX formatter now uses the same path as Gate/Binance.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-18 15:41:49 +08:00
parent 0a14fe73c1
commit faf21da955
7 changed files with 84 additions and 5 deletions
+1
View File
@@ -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):
+1
View File
@@ -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):
+10 -1
View File
@@ -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,
)
+22 -3
View File
@@ -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 +
"</span></td>" +
"<td>" +
fmtNum(t.trigger_price, 4) +
fmtPx(t.trigger_price_display, t.trigger_price) +
"</td>" +
"<td>" +
fmtNum(stopShow, 4) +
fmtPx(t.stop_loss_display, stopShow) +
"</td>" +
"<td>" +
fmtNum(tpShow, 4) +
fmtPx(t.take_profit_display, tpShow) +
"</td>" +
"<td>" +
margin +
+2
View File
@@ -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:
+29 -1
View File
@@ -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,
+19
View File
@@ -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()