websocket public & private examples and impl

This commit is contained in:
wuhewuhe 2023-11-03 01:19:07 +01:00
parent 19b02e129b
commit 65ab0cd6e2
8 changed files with 220 additions and 12 deletions

View File

@ -31,15 +31,6 @@ type Client struct {
type doFunc func(req *http.Request) (*http.Response, error) type doFunc func(req *http.Request) (*http.Response, error)
// Globals
const (
timestampKey = "X-BAPI-TIMESTAMP"
signatureKey = "X-BAPI-SIGN"
apiRequestKey = "X-BAPI-API-KEY"
recvWindowKey = "X-BAPI-RECV-WINDOW"
signTypeKey = "2"
)
func currentTimestamp() int64 { func currentTimestamp() int64 {
return FormatTimestamp(time.Now()) return FormatTimestamp(time.Now())
} }

150
bybit_websocket.go Normal file
View File

@ -0,0 +1,150 @@
package bybit
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"time"
)
type MessageHandler func(message string) error
func (b *WebSocket) handleIncomingMessages() {
for {
_, message, err := b.conn.ReadMessage()
if err != nil {
fmt.Println("Error reading:", err)
return
}
if b.onMessage != nil {
err := b.onMessage(string(message))
if err != nil {
fmt.Println("Error handling message:", err)
return
}
}
}
}
func (b *WebSocket) SetMessageHandler(handler MessageHandler) {
b.onMessage = handler
}
type WebSocket struct {
conn *websocket.Conn
url string
apiKey string
apiSecret string
onMessage MessageHandler
}
func NewBybitPrivateWebSocket(url, apiKey, apiSecret string, handler MessageHandler) *WebSocket {
return &WebSocket{
url: url,
apiKey: apiKey,
apiSecret: apiSecret,
onMessage: handler,
}
}
func NewBybitPublicWebSocket(url string, handler MessageHandler) *WebSocket {
return &WebSocket{
url: url,
onMessage: handler,
}
}
func (b *WebSocket) Connect(args []string) error {
var err error
b.conn, _, err = websocket.DefaultDialer.Dial(b.url, nil)
if err != nil {
return err
}
if b.requiresAuthentication() {
if err = b.sendAuth(); err != nil {
return err
}
}
go b.handleIncomingMessages()
Ping(b)
return b.sendSubscription(args)
}
func Ping(b *WebSocket) {
ticker := time.NewTicker(15 * time.Second)
go func() {
defer ticker.Stop() // Ensure the ticker is stopped when this goroutine ends
for {
select {
case <-ticker.C: // Wait until the ticker sends a signal
if err := b.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
fmt.Println("Failed to send ping:", err)
}
}
}
}()
}
func (b *WebSocket) Disconnect() error {
return b.conn.Close()
}
func (b *WebSocket) Send(message string) error {
return b.conn.WriteMessage(websocket.TextMessage, []byte(message))
}
func (b *WebSocket) requiresAuthentication() bool {
return b.url == WEBSOCKET_PRIVATE_MAINNET ||
b.url == WEBSOCKET_PRIVATE_TESTNET ||
b.url == V3_CONTRACT_PRIVATE ||
b.url == V3_UNIFIED_PRIVATE ||
b.url == V3_SPOT_PRIVATE
}
func (b *WebSocket) sendAuth() error {
// Get current Unix time in milliseconds
expires := time.Now().UnixNano()/1e6 + 10000
val := fmt.Sprintf("GET/realtime%d", expires)
h := hmac.New(sha256.New, []byte(b.apiSecret))
h.Write([]byte(val))
// Convert to hexadecimal instead of base64
signature := hex.EncodeToString(h.Sum(nil))
fmt.Println("signature generated : " + signature)
authMessage := map[string]interface{}{
"req_id": uuid.New(), // You would need to implement or find a package for generating GUID
"op": "auth",
"args": []interface{}{b.apiKey, expires, signature},
}
fmt.Println("auth args:", fmt.Sprintf("%v", authMessage["args"]))
return b.SendAsJson(authMessage)
}
func (b *WebSocket) sendSubscription(args []string) error {
subMessage := map[string]interface{}{
"req_id": uuid.New(),
"op": "subscribe",
"args": args,
}
fmt.Println("subscribe msg:", fmt.Sprintf("%v", subMessage["args"]))
return b.SendAsJson(subMessage)
}
func (b *WebSocket) SendAsJson(v interface{}) error {
data, err := json.Marshal(v)
if err != nil {
return err
}
return b.Send(string(data))
}

View File

@ -1,5 +1,33 @@
package bybit package bybit
const Name = "bybit.api.go" const (
Name = "bybit.api.go"
Version = "1.0.0"
// WebSocket public channel - Mainnet
SPOT_MAINNET = "wss://stream.bybit.com/v5/public/spot"
LINEAR_MAINNET = "wss://stream.bybit.com/v5/public/linear"
INVERSE_MAINNET = "wss://stream.bybit.com/v5/public/inverse"
OPTION_MAINNET = "wss://stream.bybit.com/v5/public/option"
const Version = "1.0.0" // WebSocket public channel - Testnet
SPOT_TESTNET = "wss://stream-testnet.bybit.com/v5/public/spot"
LINEAR_TESTNET = "wss://stream-testnet.bybit.com/v5/public/linear"
INVERSE_TESTNET = "wss://stream-testnet.bybit.com/v5/public/inverse"
OPTION_TESTNET = "wss://stream-testnet.bybit.com/v5/public/option"
// WebSocket private channel
WEBSOCKET_PRIVATE_MAINNET = "wss://stream.bybit.com/v5/private"
WEBSOCKET_PRIVATE_TESTNET = "wss://stream-testnet.bybit.com/v5/private"
// V3
V3_CONTRACT_PRIVATE = "wss://stream.bybit.com/contract/private/v3"
V3_UNIFIED_PRIVATE = "wss://stream.bybit.com/unified/private/v3"
V3_SPOT_PRIVATE = "wss://stream.bybit.com/spot/private/v3"
// Globals
timestampKey = "X-BAPI-TIMESTAMP"
signatureKey = "X-BAPI-SIGN"
apiRequestKey = "X-BAPI-API-KEY"
recvWindowKey = "X-BAPI-RECV-WINDOW"
signTypeKey = "2"
)

View File

@ -0,0 +1,16 @@
package main
import (
bybit "bybit.go.api"
"fmt"
)
func main() {
ws := bybit.NewBybitPrivateWebSocket("wss://stream-testnet.bybit.com/v5/private", "8wYkmpLsMg10eNQyPm", "Ouxc34myDnXvei54XsBZgoQzfGxO4bkr2Zsj", func(message string) error {
fmt.Println("Received:", message)
return nil
})
// Connect and subscribe to the desired topic
_ = ws.Connect([]string{"order"})
select {}
}

View File

@ -0,0 +1,17 @@
package main
import (
bybit "bybit.go.api"
"fmt"
)
func main() {
// Create a public WebSocket client
ws := bybit.NewBybitPublicWebSocket("wss://stream.bybit.com/v5/public/spot", func(message string) error {
fmt.Println("Received:", message)
return nil
})
// Connect and subscribe to the desired topic
_ = ws.Connect([]string{"orderbook.1.BTCUSDT"})
select {}
}

1
go.mod
View File

@ -4,5 +4,6 @@ go 1.21
require ( require (
github.com/bitly/go-simplejson v0.5.1 github.com/bitly/go-simplejson v0.5.1
github.com/google/uuid v1.4.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
) )

4
go.sum
View File

@ -1,3 +1,7 @@
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

1
trade.go Normal file
View File

@ -0,0 +1 @@
package bybit