Files
aiaa-notification-server/internal/subscriber/tradesignal/format.go
T
ryan 6f846a0a3c feat: subscribe to RabbitMQ trade signals and notify by rules
Consume configurable queues, format signals (including period), share NotifyService with HTTP, and drop duplicate bodies within 1h.
2026-08-15 17:34:49 +08:00

199 lines
5.1 KiB
Go

package tradesignal
import (
"fmt"
"strings"
"time"
)
type FormatOptions struct {
AvgPrice *float64
}
func Format(signal *Signal, opts ...FormatOptions) string {
var opt FormatOptions
if len(opts) > 0 {
opt = opts[0]
}
title := actionTitle(signal.Side, signal.Action)
symbol := trimQuote(signal.Symbol)
lines := []string{title}
lines = append(lines, fmt.Sprintf("交易品种: %s", symbol))
if p := strings.TrimSpace(signal.Period); p != "" {
lines = append(lines, fmt.Sprintf("周期: %s", p))
}
action := strings.ToUpper(signal.Action)
switch action {
case "OPEN":
lines = append(lines, fmt.Sprintf("开仓价格: %.2f", signal.Price))
if line := sizeLine("OPEN", signal.Quantity, signal.AmountMarginRatio); line != "" {
lines = append(lines, line)
}
lines = appendAvgPrice(lines, opt.AvgPrice)
if signal.Leverage > 0 {
lines = append(lines, fmt.Sprintf("杠杆: %dx", signal.Leverage))
}
if signal.TakeProfitPrice != nil {
lines = append(lines, fmt.Sprintf("止盈价格: %.2f", *signal.TakeProfitPrice))
}
if signal.StopLossPrice != nil {
lines = append(lines, fmt.Sprintf("止损价格: %.2f", *signal.StopLossPrice))
}
case "CLOSE":
lines = append(lines, fmt.Sprintf("平仓价格: %.2f", signal.Price))
lines = append(lines, closeSizeLine(signal.Quantity, signal.PosMarginRatio))
lines = appendAvgPrice(lines, opt.AvgPrice)
if signal.PnL != nil {
lines = append(lines, fmt.Sprintf("平仓盈亏: %.2f", *signal.PnL))
}
if signal.AccountBalance != nil {
lines = append(lines, fmt.Sprintf("账户余额:%.2f", *signal.AccountBalance))
}
case "ADD":
lines = append(lines, fmt.Sprintf("加仓价格: %.2f", signal.Price))
if line := sizeLine("ADD", signal.Quantity, signal.AmountMarginRatio); line != "" {
lines = append(lines, line)
}
lines = appendAvgPrice(lines, opt.AvgPrice)
if signal.Leverage > 0 {
lines = append(lines, fmt.Sprintf("杠杆: %dx", signal.Leverage))
}
case "REDUCE":
lines = append(lines, fmt.Sprintf("减仓价格: %.2f", signal.Price))
if line := sizeLine("REDUCE", signal.Quantity, signal.PosMarginRatio); line != "" {
lines = append(lines, line)
}
lines = appendAvgPrice(lines, opt.AvgPrice)
if signal.PnL != nil {
lines = append(lines, fmt.Sprintf("减仓盈亏: %.2f", *signal.PnL))
}
if signal.AccountBalance != nil {
lines = append(lines, fmt.Sprintf("账户余额:%.2f", *signal.AccountBalance))
}
default:
lines = append(lines, fmt.Sprintf("价格: %.2f", signal.Price))
if line := sizeLine("", signal.Quantity, signal.AmountMarginRatio); line != "" {
lines = append(lines, line)
}
lines = appendAvgPrice(lines, opt.AvgPrice)
}
if signal.StrategyCode != "" {
lines = append(lines, fmt.Sprintf("策略: %s", signal.StrategyCode))
}
eventTime := signal.ParsedEventTime().In(time.Local)
lines = append(lines, fmt.Sprintf("Time: %s", eventTime.Format("2006.01.02 15:04:05")))
return strings.Join(lines, "\n")
}
func actionTitle(side, action string) string {
side = strings.ToUpper(side)
action = strings.ToUpper(action)
var pos string
switch side {
case "LONG":
pos = "多单"
case "SHORT":
pos = "空单"
default:
pos = side
}
var act string
switch action {
case "OPEN":
act = "开仓"
case "ADD":
act = "加仓"
case "CLOSE":
act = "平仓"
case "REDUCE":
act = "减仓"
default:
act = action
}
return pos + act
}
func appendAvgPrice(lines []string, avgPrice *float64) []string {
if avgPrice == nil || *avgPrice <= 0 {
return lines
}
return append(lines, fmt.Sprintf("平均单价: %.2f", *avgPrice))
}
func closeSizeLine(quantity, posMarginRatio *float64) string {
if quantity != nil && *quantity > 0 {
return fmt.Sprintf("平仓数量: %.2f", *quantity)
}
ratio := 1.0
if posMarginRatio != nil {
ratio = *posMarginRatio
}
return fmt.Sprintf("平仓比例: %s", formatPercent(ratio))
}
func sizeLine(action string, quantity, marginRatio *float64) string {
if quantity != nil && *quantity > 0 {
return fmt.Sprintf("%s: %.2f", quantityLabel(action), *quantity)
}
if marginRatio != nil {
return fmt.Sprintf("%s: %s", ratioLabel(action), formatPercent(*marginRatio))
}
if quantity != nil {
return fmt.Sprintf("%s: %.2f", quantityLabel(action), *quantity)
}
return ""
}
func quantityLabel(action string) string {
switch strings.ToUpper(action) {
case "OPEN":
return "开仓数量"
case "ADD":
return "加仓数量"
case "CLOSE":
return "平仓数量"
case "REDUCE":
return "减仓数量"
default:
return "数量"
}
}
func ratioLabel(action string) string {
switch strings.ToUpper(action) {
case "OPEN":
return "开仓比例"
case "ADD":
return "加仓比例"
case "CLOSE":
return "平仓比例"
case "REDUCE":
return "减仓比例"
default:
return "仓位比例"
}
}
func formatPercent(ratio float64) string {
return fmt.Sprintf("%.2f%%", ratio*100)
}
func trimQuote(symbol string) string {
symbol = strings.ToUpper(symbol)
for _, suffix := range []string{"USDT", "USDC", "BUSD", "USD"} {
if strings.HasSuffix(symbol, suffix) && len(symbol) > len(suffix) {
return symbol[:len(symbol)-len(suffix)]
}
}
return symbol
}