130 lines
2.7 KiB
Go
130 lines
2.7 KiB
Go
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()
|
|
}
|