package tradesignal import ( "encoding/json" "errors" "fmt" "strings" "aiaa-notification-service/internal/cache" "aiaa-notification-service/internal/config" ) var ErrInvalidSignal = errors.New("invalid signal") var ErrPositionStore = errors.New("position store") type Converter struct { overrides map[string]config.StrategyOverride positions *Tracker } func NewConverter(overrides map[string]config.StrategyOverride) *Converter { return NewConverterWithCache(overrides, nil) } func NewConverterWithCache(overrides map[string]config.StrategyOverride, c *cache.Cache) *Converter { return &Converter{ overrides: overrides, positions: NewTrackerWithStore(newRedisStore(c)), } } 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) == "" { if strings.TrimSpace(sig.RawMessage) == "" { return "", nil, fmt.Errorf("%w: missing action", ErrInvalidSignal) } data, err := toData(body, &sig) if err != nil { return "", nil, err } return "trade.message", data, nil } out := Apply(&sig, c.overrideFor(sig.StrategyCode)) snap, err := c.positions.Apply(out) if err != nil { return "", nil, fmt.Errorf("%w: %v", ErrPositionStore, err) } 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 } // viper lower-cases nested map keys, so match case-insensitively. if override, ok := c.overrides[code]; ok { return &override } if override, ok := c.overrides[strings.ToLower(code)]; ok { return &override } return nil } 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 }