feat(订阅): 支持 HLSS 策略止盈与介入区间格式化
Motivation: HLSS 策略信号携带的止盈价位列表与介入价位含冗余小数位,直接推送可读性差,需转换为紧凑区间并补齐推送时间,提升通知的信息密度与可读性。 Changes: * 新增止盈目标区间,将止盈价位列表去零后连接为区间 * 新增介入区间,由开仓价与介入价组合生成 * 压缩价格数值多余小数位,保留原始精度 * 注入推送时间,便于模板渲染提醒时间
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -28,6 +29,7 @@ type payload struct {
|
||||
Price float64 `json:"price"`
|
||||
LossPrice float64 `json:"lossPrice"`
|
||||
GainPrices string `json:"gainPrices"`
|
||||
OpenPrice2 float64 `json:"openPrice2"`
|
||||
Remark string `json:"remark"`
|
||||
TotalGainTarget float64 `json:"totalGainTarget"`
|
||||
Leverage int `json:"leverage"`
|
||||
@@ -51,13 +53,16 @@ func Convert(body []byte) (string, map[string]interface{}, error) {
|
||||
if err := json.Unmarshal(body, &env); err != nil {
|
||||
return "", nil, fmt.Errorf("invalid envelope: %w", err)
|
||||
}
|
||||
p, err := parsePayload(body, env.Payload)
|
||||
p, payloadJSON, err := parsePayload(body, env.Payload)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
action := inferAction(p)
|
||||
event := "trade." + strings.ToLower(action)
|
||||
if strings.EqualFold(strings.TrimSpace(p.StrategyCode), "HLSS") {
|
||||
event = "HLSS." + strings.ToLower(action)
|
||||
}
|
||||
text := format(env, p, action)
|
||||
|
||||
data := map[string]interface{}{
|
||||
@@ -78,12 +83,19 @@ func Convert(body []byte) (string, map[string]interface{}, error) {
|
||||
"price": p.Price,
|
||||
"lossPrice": p.LossPrice,
|
||||
"gainPrices": p.GainPrices,
|
||||
"openPrice2": p.OpenPrice2,
|
||||
"leverage": p.Leverage,
|
||||
"formatted": text,
|
||||
"stopLossPrice": p.LossPrice,
|
||||
"takeProfitPrice": takeProfitPrice(p),
|
||||
"takeProfitRange": formatPriceRange(p.GainPrices),
|
||||
"entryRange": entryRange(p.Price, p.OpenPrice2),
|
||||
"totalAvgPx": "",
|
||||
}
|
||||
mergePayloadFields(data, payloadJSON)
|
||||
if env.EventTime > 0 {
|
||||
data["pushedAt"] = time.UnixMilli(env.EventTime).In(time.Local).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
if p.TotalGainTarget != 0 {
|
||||
data["totalGainTarget"] = p.TotalGainTarget
|
||||
}
|
||||
@@ -98,30 +110,46 @@ func Convert(body []byte) (string, map[string]interface{}, error) {
|
||||
return event, data, nil
|
||||
}
|
||||
|
||||
func parsePayload(body []byte, raw json.RawMessage) (payload, error) {
|
||||
func parsePayload(body []byte, raw json.RawMessage) (payload, []byte, error) {
|
||||
var p payload
|
||||
raw = bytes.TrimSpace(raw)
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
return p, fmt.Errorf("invalid payload: %w", err)
|
||||
return p, nil, fmt.Errorf("invalid payload: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
return p, body, nil
|
||||
}
|
||||
var asString string
|
||||
if err := json.Unmarshal(raw, &asString); err == nil {
|
||||
asString = strings.TrimSpace(asString)
|
||||
if asString == "" {
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
return p, fmt.Errorf("invalid payload: %w", err)
|
||||
return p, nil, fmt.Errorf("invalid payload: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
return p, body, nil
|
||||
}
|
||||
raw = []byte(asString)
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return p, fmt.Errorf("invalid payload: %w", err)
|
||||
return p, nil, fmt.Errorf("invalid payload: %w", err)
|
||||
}
|
||||
return p, raw, nil
|
||||
}
|
||||
|
||||
func mergePayloadFields(data map[string]interface{}, payloadJSON []byte) {
|
||||
if len(bytes.TrimSpace(payloadJSON)) == 0 {
|
||||
return
|
||||
}
|
||||
var extra map[string]interface{}
|
||||
if err := json.Unmarshal(payloadJSON, &extra); err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range extra {
|
||||
if _, ok := data[k]; ok {
|
||||
continue
|
||||
}
|
||||
data[k] = v
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func inferAction(p payload) string {
|
||||
@@ -189,6 +217,9 @@ func format(env envelope, p payload, action string) string {
|
||||
if p.LossPrice > 0 {
|
||||
lines = append(lines, fmt.Sprintf("止损价格: %.2f", p.LossPrice))
|
||||
}
|
||||
if er := entryRange(p.Price, p.OpenPrice2); er != "" && p.OpenPrice2 != 0 {
|
||||
lines = append(lines, fmt.Sprintf("介入区间: %s", er))
|
||||
}
|
||||
if gp := strings.TrimSpace(p.GainPrices); gp != "" && action != "GAIN" {
|
||||
lines = append(lines, fmt.Sprintf("止盈价格: %s", strings.Join(splitPrices(gp), ", ")))
|
||||
}
|
||||
@@ -246,6 +277,43 @@ func splitPrices(s string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func formatPriceRange(s string) string {
|
||||
parts := splitPrices(s)
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
out = append(out, compactPrice(p))
|
||||
}
|
||||
return strings.Join(out, "-")
|
||||
}
|
||||
|
||||
func entryRange(price, open2 float64) string {
|
||||
switch {
|
||||
case price != 0 && open2 != 0:
|
||||
return formatFloat(price) + "-" + formatFloat(open2)
|
||||
case price != 0:
|
||||
return formatFloat(price)
|
||||
case open2 != 0:
|
||||
return formatFloat(open2)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func compactPrice(s string) string {
|
||||
f, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||
if err != nil {
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
return formatFloat(f)
|
||||
}
|
||||
|
||||
func formatFloat(f float64) string {
|
||||
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||
}
|
||||
|
||||
func firstNonEmpty(a, b string) string {
|
||||
if strings.TrimSpace(a) != "" {
|
||||
return a
|
||||
|
||||
Reference in New Issue
Block a user