package exchange import ( "encoding/json" "log" "time" ) // AevoWS connects to Aevo WebSocket for ticker data. type AevoWS struct { Conn *PriceConnector Tracked []TrackedSymbol // coin + instrument name pairs } type TrackedSymbol struct { Coin string // "BTC" InstrumentID string // "BTC-PERP" } type aevoTickerMsg struct { Op string `json:"op"` Data json.RawMessage `json:"data"` } type aevoTickerData struct { Timestamp string `json:"timestamp"` Tickers []aevoInstrument `json:"tickers"` } type aevoInstrument struct { InstrumentName string `json:"instrument_name"` Mark *aevoPriceObj `json:"mark,omitempty"` LastPrice string `json:"last_price,omitempty"` } type aevoPriceObj struct { Price string `json:"price"` } func NewAevoWS(tracked []TrackedSymbol) *AevoWS { ae := &AevoWS{ Tracked: tracked, Conn: NewPriceConnector("wss://ws.aevo.xyz", "Aevo", 120*time.Second, 30*time.Second), } ae.Conn.PingInterval = 45 * time.Second return ae } // Run connects to Aevo WS and streams ticker data. func (a *AevoWS) Run(updateFn func(coin string, price float64)) error { a.Conn.OnConnect = func() { log.Printf("[Aevo WS] Connected, subscribing to %d tickers", len(a.Tracked)) for _, t := range a.Tracked { sub := map[string]interface{}{ "op": "subscribe", "data": []string{"ticker:" + t.Coin}, } if err := a.Conn.SendJSON(sub); err != nil { log.Printf("[Aevo WS] Subscribe %s error: %v", t.InstrumentID, err) } } } a.Conn.OnMessage = func(msg []byte) { var raw map[string]json.RawMessage if err := json.Unmarshal(msg, &raw); err != nil { return } // Check for error if errMsg, hasErr := raw["error"]; hasErr { var errStr string json.Unmarshal(errMsg, &errStr) if errStr != "" { // Log once, skip errors return } } // Parse ticker data op, hasOp := raw["op"] if !hasOp { return } var opStr string if err := json.Unmarshal(op, &opStr); err != nil || opStr != "ticker" { return } dataRaw, hasData := raw["data"] if !hasData { return } var data aevoTickerData if err := json.Unmarshal(dataRaw, &data); err != nil { return } for _, ticker := range data.Tickers { // Find the coin for this instrument coin := "" for _, t := range a.Tracked { if t.InstrumentID == ticker.InstrumentName { coin = t.Coin break } } if coin == "" { continue } // Try mark price first, then last_price var price float64 if ticker.Mark != nil && ticker.Mark.Price != "" { price = parseFloat(ticker.Mark.Price) } else if ticker.LastPrice != "" { price = parseFloat(ticker.LastPrice) } if price > 0 { updateFn(coin, price) } } } return a.Conn.Run() }