package tradesignal import ( "encoding/json" "errors" "fmt" "strings" "aiaa-notification-service/internal/config" ) var ErrInvalidSignal = errors.New("invalid signal") type Converter struct { overrides map[string]config.StrategyOverride positions *Tracker } func NewConverter(overrides map[string]config.StrategyOverride) *Converter { return &Converter{ overrides: overrides, positions: NewTracker(), } } func (c *Converter) Convert(body []byte) (string, map[string]interface{}, error) { var sig Signal if err := json.Unmarshal(body, &sig); err != nil { return "", nil, fmt.Errorf("%w: %v", ErrInvalidSignal, err) } if strings.TrimSpace(sig.Action) == "" { return "", nil, fmt.Errorf("%w: missing action", ErrInvalidSignal) } out := Apply(&sig, c.overrideFor(sig.StrategyCode)) snap := c.positions.Apply(out) var opts FormatOptions if snap.HasAvg { avg := snap.AvgPrice opts.AvgPrice = &avg } text := Format(out, opts) data, err := toData(body, out) if err != nil { return "", nil, err } data["formatted"] = text if snap.HasAvg { data["avgPrice"] = snap.AvgPrice } return "trade." + strings.ToLower(out.Action), data, nil } func (c *Converter) overrideFor(code string) *config.StrategyOverride { if c == nil || len(c.overrides) == 0 || code == "" { return nil } override, ok := c.overrides[code] if !ok { return nil } return &override } func toData(body []byte, sig *Signal) (map[string]interface{}, error) { data := make(map[string]interface{}) if err := json.Unmarshal(body, &data); err != nil { return nil, err } raw, err := json.Marshal(sig) if err != nil { return nil, err } overlay := make(map[string]interface{}) if err := json.Unmarshal(raw, &overlay); err != nil { return nil, err } for k, v := range overlay { data[k] = v } return data, nil }