feat(crypto策略): 新增 AG 趋势、异动与波段跟踪信号支持

Motivation:
crypto-strategy 源新增 AG 趋势(AGTS)、异动(AMA)、波段跟踪(BTS) 三类量化策略信号,需与既有 trade.* 事件区分以避免和 AI crypto signals 精确匹配冲突,并为缺省方向提供兜底推导。

Changes:

* 事件名映射按 strategyCode 使用独立前缀(AGTS./AMA./BTS.,HLSS 保持不变)
* direction/side 缺失时依据 isSale 兜底推导为 LONG/SHORT
* 补充三类策略的模板、规则示例文档与渲染/转换测试
This commit is contained in:
2026-08-17 11:07:34 +08:00
parent a93078dc00
commit a2c0590353
4 changed files with 315 additions and 7 deletions
@@ -433,3 +433,198 @@ func TestRenderAICryptoGainAndCloseTemplates(t *testing.T) {
}
}
}
const agTrendTmpl = `监控告警提醒
操作策略:AG趋势{{case .symbol "BTCUSDT" "BTC" "ETHUSDT" "ETH" "SOLUSDT" "SOL" "BNBUSDT" "BNB" .symbol}}-{{case .period "1h" "1小时" "2h" "2小时" "4h" "4小时" "6h" "6小时" "15m" "15分钟" "5m" "5分钟" "30m" "30分钟" "1d" "1日" .period}}周期{{case .side "LONG" "做多" "SHORT" "做空"}}
提醒时间:{{.pushedAt}}
{{with .takeProfitRange}}止盈目标:{{.}}
{{else}}{{with .takeProfitPrice}}止盈目标:{{.}}
{{end}}{{end}}{{with .entryRange}}介入区间:{{.}}
{{else}}{{with .price}}介入区间:{{.}}
{{end}}{{end}}{{with .stopLossPrice}}止损价位:{{.}}
{{end}}有效期:6天`
const anomalyAlertTmpl = `监控告警提醒
监控名称:异动预警
监控时间:{{.pushedAt}}
监控目标:{{case .symbol "BTCUSDT" "BTC" "ETHUSDT" "ETH" "SOLUSDT" "SOL" "BNBUSDT" "BNB" .symbol}}异动预警(暴涨/跌)生效
监控提醒:异动发生概率v1(v1<v2<v3)
有效期:2-4天`
const swingTrackTmpl = `监控告警提醒
监控名称:波段跟踪触发{{case .symbol "BTCUSDT" "BTC" "ETHUSDT" "ETH" "SOLUSDT" "SOL" "BNBUSDT" "BNB" .symbol}}-{{case .period "1h" "1小时" "2h" "2小时" "4h" "4小时" "6h" "6小时" "15m" "15分钟" "5m" "5分钟" "30m" "30分钟" "1d" "1日" .period}}周期{{case .side "LONG" "做多" "SHORT" "做空"}}
监控时间:{{.pushedAt}}
监控提醒:当前提醒价格{{with .takeProfitRange}}{{.}}{{else}}{{with .entryRange}}{{.}}{{else}}{{.price}}{{end}}{{end}}
监控状态:等待量化信号平仓
有效期: 17h`
func TestRenderAGAnomalySwingTemplates(t *testing.T) {
r := engine.NewRenderer()
ag, err := r.Render(agTrendTmpl, map[string]interface{}{
"symbol": "BTC",
"period": "6h",
"side": "LONG",
"pushedAt": "2026-08-07 16:00:20",
"takeProfitRange": "66821.8-67536.1",
"entryRange": "64938.6-64938.6",
"stopLossPrice": "62406.0",
})
if err != nil {
t.Fatal(err)
}
for _, want := range []string{
"操作策略:AG趋势BTC-6小时周期做多",
"提醒时间:2026-08-07 16:00:20",
"止盈目标:66821.8-67536.1",
"介入区间:64938.6-64938.6",
"止损价位:62406.0",
"有效期:6天",
} {
if !strings.Contains(ag, want) {
t.Fatalf("missing %q in\n%s", want, ag)
}
}
yd, err := r.Render(anomalyAlertTmpl, map[string]interface{}{
"symbol": "BTC",
"pushedAt": "2026-07-29 00:00:50",
})
if err != nil {
t.Fatal(err)
}
for _, want := range []string{
"监控名称:异动预警",
"监控时间:2026-07-29 00:00:50",
"监控目标:BTC异动预警(暴涨/跌)生效",
"监控提醒:异动发生概率v1(v1<v2<v3)",
"有效期:2-4天",
} {
if !strings.Contains(yd, want) {
t.Fatalf("missing %q in\n%s", want, yd)
}
}
bd, err := r.Render(swingTrackTmpl, map[string]interface{}{
"symbol": "ETH",
"period": "1h",
"side": "LONG",
"pushedAt": "2026-07-24 08:00:07",
"takeProfitRange": "1878.4-1894.2",
})
if err != nil {
t.Fatal(err)
}
for _, want := range []string{
"监控名称:波段跟踪触发ETH-1小时周期做多",
"监控时间:2026-07-24 08:00:07",
"监控提醒:当前提醒价格1878.4-1894.2",
"监控状态:等待量化信号平仓",
"有效期: 17h",
} {
if !strings.Contains(bd, want) {
t.Fatalf("missing %q in\n%s", want, bd)
}
}
}
func TestConvertAGTSAMAAndBTS(t *testing.T) {
agBody := []byte(`{
"eventType":"SIGNAL_RECEIVED","symbol":"BTC","direction":"SHORT",
"payload":"{\"strategyCode\":\"AGTS\",\"period\":\"6h\",\"currency\":\"BTC\",\"isSale\":true,\"isClose\":false,\"price\":63303,\"lossPrice\":65771.82,\"gainPrices\":\"61467.210000000000000,60770.880000000000000\",\"openPrice2\":63303.03,\"totalGainTarget\":2,\"leverage\":100}",
"eventTime":1786780820000
}`)
event, data, err := Convert(agBody)
if err != nil {
t.Fatal(err)
}
if event != "AGTS.open" {
t.Fatalf("AGTS event=%q", event)
}
if data["takeProfitRange"] != "61467.21-60770.88" {
t.Fatalf("takeProfitRange=%v", data["takeProfitRange"])
}
if data["entryRange"] != "63303-63303.03" {
t.Fatalf("entryRange=%v", data["entryRange"])
}
out, err := engine.NewRenderer().Render(agTrendTmpl, data)
if err != nil {
t.Fatal(err)
}
for _, want := range []string{
"操作策略:AG趋势BTC-6小时周期做空",
"止盈目标:61467.21-60770.88",
"介入区间:63303-63303.03",
"止损价位:65771.82",
"有效期:6天",
} {
if !strings.Contains(out, want) {
t.Fatalf("missing %q in\n%s", want, out)
}
}
amaBody := []byte(`{
"eventType":"SIGNAL_RECEIVED","symbol":"BTC","direction":"LONG",
"payload":"{\"strategyCode\":\"AMA\",\"period\":\"4h\",\"currency\":\"BTC\",\"isSale\":false,\"isClose\":false,\"price\":63119.9,\"leverage\":100}",
"eventTime":1785312050000
}`)
event, data, err = Convert(amaBody)
if err != nil {
t.Fatal(err)
}
if event != "AMA.open" || data["strategyCode"] != "AMA" {
t.Fatalf("AMA event=%q data=%v", event, data)
}
out, err = engine.NewRenderer().Render(anomalyAlertTmpl, data)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "监控目标:BTC异动预警(暴涨/跌)生效") {
t.Fatalf("%s", out)
}
btsBody := []byte(`{
"eventType":"SIGNAL_RECEIVED","symbol":"BTC","direction":"LONG",
"payload":"{\"strategyCode\":\"BTS\",\"period\":\"2h\",\"currency\":\"BTC\",\"isSale\":false,\"isClose\":true,\"isGain\":false,\"price\":63533.2}",
"eventTime":1784865607000
}`)
event, data, err = Convert(btsBody)
if err != nil {
t.Fatal(err)
}
if event != "BTS.close" {
t.Fatalf("BTS event=%q want BTS.close", event)
}
out, err = engine.NewRenderer().Render(swingTrackTmpl, data)
if err != nil {
t.Fatal(err)
}
for _, want := range []string{
"监控名称:波段跟踪触发BTC-2小时周期做多",
"监控提醒:当前提醒价格63533.2",
"监控状态:等待量化信号平仓",
"有效期: 17h",
} {
if !strings.Contains(out, want) {
t.Fatalf("missing %q in\n%s", want, out)
}
}
}