fix: Bitget WS keepalive — send text ping, not WebSocket PingMessage

Bitget v2 WS requires a text message "ping" every 30s, not a WebSocket
PingMessage control frame (opcode 0x9). Using the wrong ping type caused
a silent failure: connection stays up and subscription succeeds, but NO
ticker data is pushed — zero errors, zero reconnection logs.

Changes:
- connector.go: Add TextPing bool flag, send text 'ping' when set
- bitget.go: Set TextPing=true, handle text 'pong', add debug logging
- scanner.go: Expand to 179 overlapping Bitget+HL coins
This commit is contained in:
jackyu66git
2026-05-04 00:33:03 +08:00
parent e04a165d69
commit 2ed6ffc747
5 changed files with 45246 additions and 22 deletions
+43 -15
View File
@@ -47,30 +47,58 @@ func (b *BitgetWS) Run(updateFn func(coin string, price, bid, ask float64)) erro
b.Conn = NewPriceConnector(url, "Bitget", 120*time.Second, 30*time.Second)
b.Conn.PingInterval = 25 * time.Second // Bitget requires ping within 30s
b.Conn.TextPing = true // Bitget v2 expects text "ping" message
b.Conn.OnConnect = func() {
log.Printf("[Bitget WS] Connected, subscribing")
log.Printf("[Bitget WS] Connected, subscribing (%d symbols)", len(b.Tracked))
args := make([]map[string]string, 0, len(b.Tracked))
for _, sym := range b.Tracked {
args = append(args, map[string]string{
"instType": "USDT-FUTURES",
"channel": "ticker",
"instId": sym,
})
}
sub := map[string]interface{}{
"op": "subscribe",
"args": args,
}
if err := b.Conn.SendJSON(sub); err != nil {
log.Printf("[Bitget WS] Subscribe error: %v", err)
// Batch subscriptions — Bitget WS has a limit per message
batchSize := 20
for i := 0; i < len(b.Tracked); i += batchSize {
end := i + batchSize
if end > len(b.Tracked) {
end = len(b.Tracked)
}
batch := b.Tracked[i:end]
args := make([]map[string]string, len(batch))
for j, sym := range batch {
args[j] = map[string]string{
"instType": "USDT-FUTURES",
"channel": "ticker",
"instId": sym,
}
}
sub := map[string]interface{}{
"op": "subscribe",
"args": args,
}
if err := b.Conn.SendJSON(sub); err != nil {
log.Printf("[Bitget WS] Subscribe error (batch %d): %v", i/batchSize, err)
}
}
}
b.Conn.OnMessage = func(msg []byte) {
// Handle Bitget text "pong" response
if string(msg) == "pong" {
return
}
// Check for Bitget subscription confirmation or error response
var generic map[string]interface{}
if err := json.Unmarshal(msg, &generic); err == nil {
if evt, _ := generic["event"].(string); evt == "error" {
log.Printf("[Bitget WS] Subscribe error response: %s", string(msg))
return
} else if evt == "subscribe" {
log.Printf("[Bitget WS] Subscribe confirmed: %s", string(msg))
return
}
}
var ticker bitgetTickerMsg
if err := json.Unmarshal(msg, &ticker); err != nil {
log.Printf("[Bitget WS] Unrecognized message: %s", string(msg))
return
}
if len(ticker.Data) == 0 || ticker.Data[0].LastPr == "" {
+13 -2
View File
@@ -16,6 +16,10 @@ type PriceConnector struct {
ReconnectBase time.Duration
PingInterval time.Duration // 0 = no client-side pings
// TextPing: if true, sends text message "ping" instead of WebSocket PingMessage frame.
// Used by Bitget v2 WS which expects a JSON/text "ping" string.
TextPing bool
OnConnect func()
OnMessage func([]byte)
OnError func(error)
@@ -91,8 +95,15 @@ func (pc *PriceConnector) Run() error {
for {
select {
case <-ticker.C:
if err := c.WriteMessage(websocket.PingMessage, nil); err != nil {
return
if pc.TextPing {
// Bitget v2 expects a TEXT message "ping", not WebSocket PingMessage frame
if err := c.WriteMessage(websocket.TextMessage, []byte("ping")); err != nil {
return
}
} else {
if err := c.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
case <-pingStop:
return