dc313fda13
Motivation: crypto-strategy 交易信号采用 envelope 外壳与字符串化/嵌套的 payload,原有 trade_signal 格式化器无法解析,需要独立的转换逻辑以生成通知文本和事件数据。 Changes: * 新增 crypto_strategy 消息转换器,解析 envelope 及嵌套、字符串化、扁平三种 payload 形态 * 根据平仓/止盈/卖出等标志推断交易动作,生成对应事件类型与中文通知文本 * 提取订单号、策略代码、价格、杠杆等字段用于通知数据 * 将消息转换器抽象为接口,订阅器按 formatter 配置选择对应实现 * 更新配置校验以支持 crypto_strategy 格式化器,并补充单元测试
133 lines
3.5 KiB
Go
133 lines
3.5 KiB
Go
package cryptostrategy
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const sampleBody = `{
|
|
"eventType": "SIGNAL_RECEIVED",
|
|
"correlationId": "0_0_0",
|
|
"symbol": "QNT",
|
|
"direction": "LONG",
|
|
"payload": "{\"strategyCode\":\"ai-crypto-signals\",\"period\":\"1h\",\"currency\":\"QNT\",\"isSale\":false,\"isClose\":false,\"isGain\":false,\"gainTarget\":5,\"price\":58.23,\"lossPrice\":57.82,\"gainPrices\":\"58.435,58.64,58.845,59.05,59.255\",\"remark\":\"{\\\"orderId\\\":\\\"jeJY8l5YnYwfJbmj6zb4\\\"}\",\"totalGainTarget\":5,\"leverage\":43}",
|
|
"eventTime": 1786802842899
|
|
}`
|
|
|
|
func TestConvertParsesNestedPayload(t *testing.T) {
|
|
event, data, err := Convert([]byte(sampleBody))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if event != "trade.open" {
|
|
t.Fatalf("event=%q", event)
|
|
}
|
|
if data["symbol"] != "QNT" || data["strategyCode"] != "ai-crypto-signals" {
|
|
t.Fatalf("data=%v", data)
|
|
}
|
|
if data["period"] != "1h" || data["direction"] != "LONG" {
|
|
t.Fatalf("data=%v", data)
|
|
}
|
|
if data["orderId"] != "jeJY8l5YnYwfJbmj6zb4" {
|
|
t.Fatalf("orderId=%v", data["orderId"])
|
|
}
|
|
formatted, _ := data["formatted"].(string)
|
|
for _, want := range []string{
|
|
"多单开仓",
|
|
"交易品种: QNT",
|
|
"周期: 1h",
|
|
"开仓价格: 58.23",
|
|
"止损价格: 57.82",
|
|
"止盈价格: 58.435, 58.64, 58.845, 59.05, 59.255",
|
|
"杠杆: 43x",
|
|
"策略: ai-crypto-signals",
|
|
} {
|
|
if !strings.Contains(formatted, want) {
|
|
t.Fatalf("missing %q in\n%s", want, formatted)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConvertCloseFlag(t *testing.T) {
|
|
body := []byte(`{
|
|
"eventType":"SIGNAL_RECEIVED","symbol":"BTC","direction":"SHORT",
|
|
"payload":"{\"isClose\":true,\"price\":64000,\"strategyCode\":\"x\",\"period\":\"4h\"}",
|
|
"eventTime":1786802842899
|
|
}`)
|
|
event, data, err := Convert(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if event != "trade.close" {
|
|
t.Fatalf("event=%q", event)
|
|
}
|
|
formatted, _ := data["formatted"].(string)
|
|
if !strings.Contains(formatted, "空单平仓") {
|
|
t.Fatalf("%s", formatted)
|
|
}
|
|
}
|
|
|
|
func TestConvertFlatOneLayer(t *testing.T) {
|
|
body := []byte(`{
|
|
"eventType":"SIGNAL_RECEIVED",
|
|
"correlationId":"0_0_0",
|
|
"symbol":"QNT",
|
|
"direction":"LONG",
|
|
"strategyCode":"ai-crypto-signals",
|
|
"period":"1h",
|
|
"currency":"QNT",
|
|
"isSale":false,
|
|
"isClose":false,
|
|
"isGain":false,
|
|
"gainTarget":5,
|
|
"price":58.23,
|
|
"lossPrice":57.82,
|
|
"gainPrices":"58.435,58.64",
|
|
"remark":"{\"orderId\":\"jeJY8l5YnYwfJbmj6zb4\"}",
|
|
"leverage":43,
|
|
"eventTime":1786802842899
|
|
}`)
|
|
event, data, err := Convert(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if event != "trade.open" {
|
|
t.Fatalf("event=%q", event)
|
|
}
|
|
if data["strategyCode"] != "ai-crypto-signals" || data["price"] != 58.23 {
|
|
t.Fatalf("data=%v", data)
|
|
}
|
|
if data["orderId"] != "jeJY8l5YnYwfJbmj6zb4" {
|
|
t.Fatalf("orderId=%v", data["orderId"])
|
|
}
|
|
formatted, _ := data["formatted"].(string)
|
|
if !strings.Contains(formatted, "多单开仓") || !strings.Contains(formatted, "开仓价格: 58.23") {
|
|
t.Fatalf("%s", formatted)
|
|
}
|
|
}
|
|
|
|
func TestConvertPayloadObject(t *testing.T) {
|
|
body := []byte(`{
|
|
"eventType":"SIGNAL_RECEIVED","symbol":"ETH","direction":"SHORT",
|
|
"payload":{"isClose":true,"price":3200,"strategyCode":"x","period":"1h"},
|
|
"eventTime":1786802842899
|
|
}`)
|
|
event, data, err := Convert(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if event != "trade.close" {
|
|
t.Fatalf("event=%q", event)
|
|
}
|
|
if data["price"] != float64(3200) {
|
|
t.Fatalf("data=%v", data)
|
|
}
|
|
}
|
|
|
|
func TestConvertInvalidJSON(t *testing.T) {
|
|
_, _, err := Convert([]byte(`{not json`))
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
}
|