fix(价格展示): 修复模板渲染时极小价格被折叠为 0.00 的问题

Motivation:
此前的修复仅覆盖直接拼接文案的场景,跟单等基于模板渲染的推送场景中,%.2f 格式化仍会把 PEPE 等 Meme 币的极小价格(如 0.00000059)折叠为 0.00,导致交易通知丢失真实价格、误导用户。

Changes:

* 抽取共享的价格显示逻辑到统一显示层,两个订阅者改为复用,移除重复实现
* 新增 Decimal 类型,渲染时以副本方式包装模板数据,使模板内 printf 风格格式化保留极小价格精度且不出现科学计数法,同时不修改调用方原始数据
* 渲染引擎空值判断改用反射实现,覆盖全部整型、无符号整型与浮点类型
* 补充转换到渲染的端到端回归测试,覆盖极小价格精度、科学计数法与数据不可变性
This commit is contained in:
2026-08-23 00:12:41 +08:00
parent c7e154d7cc
commit 6f5aaa9f85
6 changed files with 135 additions and 60 deletions
+6 -19
View File
@@ -4,11 +4,11 @@ import (
"bytes"
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"time"
"aiaa-notification-service/internal/display"
"aiaa-notification-service/internal/tz"
)
@@ -245,23 +245,23 @@ func format(env envelope, p payload, action string) string {
switch action {
case "CLOSE":
if p.Price > 0 {
lines = append(lines, fmt.Sprintf("平仓价格: %s", formatPrice(p.Price)))
lines = append(lines, fmt.Sprintf("平仓价格: %s", display.FormatPrice(p.Price)))
}
case "GAIN":
if p.Price > 0 {
lines = append(lines, fmt.Sprintf("止盈价格: %s", formatPrice(p.Price)))
lines = append(lines, fmt.Sprintf("止盈价格: %s", display.FormatPrice(p.Price)))
}
case "SELL":
if p.Price > 0 {
lines = append(lines, fmt.Sprintf("卖出价格: %s", formatPrice(p.Price)))
lines = append(lines, fmt.Sprintf("卖出价格: %s", display.FormatPrice(p.Price)))
}
default:
if p.Price > 0 {
lines = append(lines, fmt.Sprintf("开仓价格: %s", formatPrice(p.Price)))
lines = append(lines, fmt.Sprintf("开仓价格: %s", display.FormatPrice(p.Price)))
}
}
if p.LossPrice > 0 {
lines = append(lines, fmt.Sprintf("止损价格: %s", formatPrice(p.LossPrice)))
lines = append(lines, fmt.Sprintf("止损价格: %s", display.FormatPrice(p.LossPrice)))
}
if er := entryRange(p.Price, p.OpenPrice2); er != "" && p.OpenPrice2 != 0 {
lines = append(lines, fmt.Sprintf("介入区间: %s", er))
@@ -360,19 +360,6 @@ func formatFloat(f float64) string {
return strconv.FormatFloat(f, 'f', -1, 64)
}
// formatPrice renders a price for display. Values >= 1 keep two decimals for
// readability; smaller values use the shortest exact representation so tiny
// prices like 0.00000059 are not collapsed to 0.00.
func formatPrice(v float64) string {
if v == 0 {
return "0.00"
}
if math.Abs(v) >= 1 {
return strconv.FormatFloat(v, 'f', 2, 64)
}
return strconv.FormatFloat(v, 'f', -1, 64)
}
func firstNonEmpty(a, b string) string {
if strings.TrimSpace(a) != "" {
return a
@@ -272,6 +272,18 @@ func TestConvertTinyPriceKeepsPrecision(t *testing.T) {
if strings.Contains(formatted, "开仓价格: 0.00\n") {
t.Fatalf("tiny price rounded to 0.00:\n%s", formatted)
}
out, err := engine.NewRenderer().Render("价格:{{.price}}\n止损:{{.stopLossPrice}}\n{{line \"止盈\" .takeProfitPrice}}", data)
if err != nil {
t.Fatal(err)
}
for _, want := range []string{"价格:0.00000059", "止损:0.00000055"} {
if !strings.Contains(out, want) {
t.Fatalf("missing %q in rendered\n%s", want, out)
}
}
if strings.Contains(out, "e-") || strings.Contains(out, "E-") {
t.Fatalf("scientific notation in rendered\n%s", out)
}
}
func TestConvertInvalidJSON(t *testing.T) {