Initial commit
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user