f7ea2e7cf9
Motivation: 新增巴菲特激进/稳健策略(PUTEJJ/PUTEWJ)的跟单推送能力。该类信号仅有 rawMessage、无 action 字段,需按原文直接推送,并过滤启动文案、本地化时间字段。 Changes: * 支持无 action 的纯 rawMessage 信号,映射为 trade.message 事件 * 新增 not_contains 条件操作符,用于过滤启动文案 * 新增 replace 模板函数,将 Time: 替换为推送时间: * 补充巴菲特策略模板、规则与渠道配置文档
193 lines
6.4 KiB
Go
193 lines
6.4 KiB
Go
package engine
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func TestRenderer(t *testing.T) {
|
||
r := NewRenderer()
|
||
tmpl := "🚀 {{.symbol}} 开仓通知\n价格: {{.price}}"
|
||
data := map[string]interface{}{"symbol": "BTC", "price": 65000}
|
||
result, err := r.Render(tmpl, data)
|
||
if err != nil {
|
||
t.Fatalf("unexpected error: %v", err)
|
||
}
|
||
if !strings.Contains(result, "BTC") || !strings.Contains(result, "65000") {
|
||
t.Errorf("unexpected output: %s", result)
|
||
}
|
||
}
|
||
|
||
func TestRendererMissingKeyEmpty(t *testing.T) {
|
||
r := NewRenderer()
|
||
_, err := r.Render("x{{.nonexistent}}y", map[string]interface{}{})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
|
||
func TestRendererLineOmitsEmpty(t *testing.T) {
|
||
r := NewRenderer()
|
||
tmpl := `{{line "币种" .symbol}}{{line "平均价" .totalAvgPx}}{{line "价格" .price}}{{line "止损价" .stopLossPrice}}`
|
||
out, err := r.Render(tmpl, map[string]interface{}{
|
||
"symbol": "ICP",
|
||
"price": 2.273,
|
||
"totalAvgPx": "",
|
||
"stopLossPrice": 0,
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !strings.Contains(out, "币种:ICP") || !strings.Contains(out, "价格:2.273") {
|
||
t.Fatalf("out=%q", out)
|
||
}
|
||
if strings.Contains(out, "平均价") || strings.Contains(out, "止损价") {
|
||
t.Fatalf("empty lines should be omitted, out=%q", out)
|
||
}
|
||
}
|
||
|
||
func TestRendererLineLabelAlreadyHasColon(t *testing.T) {
|
||
r := NewRenderer()
|
||
out, err := r.Render(`{{line "平均价:" .totalAvgPx}}`, map[string]interface{}{"totalAvgPx": 1029})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if strings.Count(out, ":") != 1 || !strings.Contains(out, "平均价:1029") {
|
||
t.Fatalf("out=%q", out)
|
||
}
|
||
}
|
||
func TestRendererLineMissingKey(t *testing.T) {
|
||
r := NewRenderer()
|
||
out, err := r.Render(`{{line "平均价" .totalAvgPx}}{{line "币种" .symbol}}`, map[string]interface{}{"symbol": "ICP"})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if strings.Contains(out, "平均价") || !strings.Contains(out, "币种:ICP") {
|
||
t.Fatalf("out=%q", out)
|
||
}
|
||
}
|
||
|
||
func TestRendererCaseSwitch(t *testing.T) {
|
||
r := NewRenderer()
|
||
tmpl := `{{case .action "OPEN" "开仓" "CLOSE" "平仓" "GAIN" "止盈"}}`
|
||
out, err := r.Render(tmpl, map[string]interface{}{"action": "CLOSE"})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if out != "平仓" {
|
||
t.Fatalf("action CLOSE: %q", out)
|
||
}
|
||
out, err = r.Render(tmpl, map[string]interface{}{"action": "open"})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if out != "开仓" {
|
||
t.Fatalf("action open: %q", out)
|
||
}
|
||
}
|
||
|
||
func TestRendererCaseEventSuffix(t *testing.T) {
|
||
r := NewRenderer()
|
||
tmpl := `{{case .event ".open" "开仓" ".close" "平仓"}}`
|
||
out, err := r.Render(tmpl, map[string]interface{}{"event": "trade.close"})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if out != "平仓" {
|
||
t.Fatalf("event trade.close: %q", out)
|
||
}
|
||
}
|
||
|
||
func TestRendererReplace(t *testing.T) {
|
||
r := NewRenderer()
|
||
out, err := r.Render(`{{replace .rawMessage "Time:" "推送时间:"}}`, map[string]interface{}{
|
||
"rawMessage": "激进版AI 1.0\n市价开空\nTime: 2026.08.17 14:46:04",
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !strings.Contains(out, "推送时间: 2026.08.17 14:46:04") || strings.Contains(out, "Time:") {
|
||
t.Fatalf("out=%q", out)
|
||
}
|
||
}
|
||
|
||
func TestRendererCaseDefault(t *testing.T) {
|
||
r := NewRenderer()
|
||
out, err := r.Render(`{{case .action "OPEN" "开仓" "未知"}}`, map[string]interface{}{"action": "HOLD"})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if out != "未知" {
|
||
t.Fatalf("got %q", out)
|
||
}
|
||
}
|
||
|
||
func TestCopyTradeTemplate(t *testing.T) {
|
||
tmpl := "{{case .side \"LONG\" \"多单\" \"SHORT\" \"空单\"}}{{case .action \"OPEN\" \"开仓\" \"ADD\" \"加仓\" \"CLOSE\" \"平仓\" \"REDUCE\" \"减仓\"}}\n交易品种: {{case .symbol \"ETHUSDT\" \"ETH\" \"BTCUSDT\" \"BTC\" \"SOLUSDT\" \"SOL\" \"BNBUSDT\" \"BNB\" .symbol}}\n{{case .action \"OPEN\" \"开仓价格\" \"ADD\" \"加仓价格\" \"CLOSE\" \"平仓价格\" \"REDUCE\" \"减仓价格\"}}: {{printf \"%.2f\" .price}}\n{{case .action \"OPEN\" \"开仓数量\" \"ADD\" \"加仓数量\" \"CLOSE\" \"平仓数量\" \"REDUCE\" \"减仓数量\"}}: {{printf \"%.2f\" .quantity}}\n平均单价: {{printf \"%.2f\" .avgPrice}}\n{{if or (eq .action \"OPEN\") (eq .action \"ADD\")}}{{if .leverage}}杠杆: {{.leverage}}x\n{{end}}{{end}}策略: {{case .strategyCode \"BLONG\" \"B龙策略\" .strategyCode}}\n推送时间: {{.pushedAt}}"
|
||
r := NewRenderer()
|
||
cases := []struct {
|
||
name string
|
||
data map[string]interface{}
|
||
want []string
|
||
not []string
|
||
}{
|
||
{
|
||
name: "open",
|
||
data: map[string]interface{}{
|
||
"side": "SHORT", "action": "OPEN", "symbol": "ETHUSDT",
|
||
"price": 1898.76, "quantity": 2.0, "avgPrice": 1898.76,
|
||
"leverage": 100, "strategyCode": "BLONG", "pushedAt": "2026.08.13 13:15:42",
|
||
},
|
||
want: []string{"空单开仓", "交易品种: ETH", "开仓价格: 1898.76", "开仓数量: 2.00", "平均单价: 1898.76", "杠杆: 100x", "策略: B龙策略", "推送时间: 2026.08.13 13:15:42"},
|
||
},
|
||
{
|
||
name: "close",
|
||
data: map[string]interface{}{
|
||
"side": "SHORT", "action": "CLOSE", "symbol": "ETHUSDT",
|
||
"price": 1883.35, "quantity": 2.0, "avgPrice": 1898.76,
|
||
"leverage": 100, "strategyCode": "BLONG", "pushedAt": "2026.08.13 17:14:31",
|
||
},
|
||
want: []string{"空单平仓", "平仓价格: 1883.35", "平仓数量: 2.00", "策略: B龙策略", "推送时间: 2026.08.13 17:14:31"},
|
||
not: []string{"杠杆:"},
|
||
},
|
||
{
|
||
name: "reduce",
|
||
data: map[string]interface{}{
|
||
"side": "SHORT", "action": "REDUCE", "symbol": "ETHUSDT",
|
||
"price": 1889.43, "quantity": 3.1, "avgPrice": 1899.03,
|
||
"strategyCode": "BLONG", "pushedAt": "2026.08.12 22:05:02",
|
||
},
|
||
want: []string{"空单减仓", "减仓价格: 1889.43", "减仓数量: 3.10", "平均单价: 1899.03", "策略: B龙策略"},
|
||
not: []string{"杠杆:"},
|
||
},
|
||
{
|
||
name: "add",
|
||
data: map[string]interface{}{
|
||
"side": "SHORT", "action": "ADD", "symbol": "ETHUSDT",
|
||
"price": 1933.05, "quantity": 3.8, "avgPrice": 1933.05,
|
||
"leverage": 100, "strategyCode": "BLONG", "pushedAt": "2026.08.10 06:14:28",
|
||
},
|
||
want: []string{"空单加仓", "加仓价格: 1933.05", "加仓数量: 3.80", "杠杆: 100x", "策略: B龙策略", "推送时间: 2026.08.10 06:14:28"},
|
||
},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
out, err := r.Render(tmpl, tc.data)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
t.Logf("\n%s", out)
|
||
for _, w := range tc.want {
|
||
if !strings.Contains(out, w) {
|
||
t.Errorf("missing %q in\n%s", w, out)
|
||
}
|
||
}
|
||
for _, n := range tc.not {
|
||
if strings.Contains(out, n) {
|
||
t.Errorf("unexpected %q in\n%s", n, out)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|