L'accès à l'API Binance en langage Go combine deux avantages majeurs : une performance extrême et un déploiement sans dépendance. Un fichier binaire unique s'exécutant sur un serveur Linux offre une latence de 30 à 50 % inférieure à celle de Python ou Node.js. La solution standard consiste à utiliser la bibliothèque standard crypto/hmac pour générer les signatures et net/http pour envoyer les requêtes, ou à utiliser directement la bibliothèque communautaire github.com/adshao/go-binance/v2. Cet article fournit le code complet pour quatre modules : encapsulation native, utilisation du SDK, abonnement WebSocket et passage d'ordres concurrents. Pour ceux qui ne sont pas encore inscrits sur Binance, veuillez d'abord effectuer la vérification KYC sur le Site officiel de Binance. Les nouveaux utilisateurs peuvent s' inscrire gratuitement.
I. Comparaison des SDK Go
| Bibliothèque | Chemin | Étoiles GitHub | Caractéristiques |
|---|---|---|---|
| adshao/go-binance | github.com/adshao/go-binance/v2 | 3.2k | Choix numéro 1 de la communauté, support complet Spot + Futures |
| go-numb/go-binance | github.com/go-numb/go-binance | 50+ | Léger, axé sur les WebSockets |
| net/http natif | Bibliothèque standard | — | Contrôle total, aucune dépendance |
Recommandation : Choisissez adshao/go-binance pour un développement rapide ; utilisez net/http natif pour une personnalisation poussée ou des scénarios HFT (High Frequency Trading).
II. Implémentation de la signature avec net/http natif
1. Initialisation du projet
mkdir binance-go && cd binance-go
go mod init binance-go
go get github.com/joho/godotenv
Fichier .env :
BINANCE_API_KEY=votre_cle
BINANCE_SECRET_KEY=votre_secret
2. Signature et requête de base
client.go :
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
const BaseURL = "https://api.binance.com"
type Client struct {
APIKey string
SecretKey string
HTTPClient *http.Client
}
func NewClient() *Client {
return &Client{
APIKey: os.Getenv("BINANCE_API_KEY"),
SecretKey: os.Getenv("BINANCE_SECRET_KEY"),
HTTPClient: &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
},
}
}
func (c *Client) sign(payload string) string {
h := hmac.New(sha256.New, []byte(c.SecretKey))
h.Write([]byte(payload))
return hex.EncodeToString(h.Sum(nil))
}
func (c *Client) Request(method, path string, params url.Values, signed bool) ([]byte, error) {
if params == nil {
params = url.Values{}
}
if signed {
params.Set("timestamp", strconv.FormatInt(time.Now().UnixMilli(), 10))
params.Set("recvWindow", "5000")
query := params.Encode()
params.Set("signature", c.sign(query))
}
fullURL := BaseURL + path
var req *http.Request
var err error
if method == "GET" || method == "DELETE" {
fullURL = fullURL + "?" + params.Encode()
req, err = http.NewRequest(method, fullURL, nil)
} else {
req, err = http.NewRequest(method, fullURL, nil)
req.URL.RawQuery = params.Encode()
}
if err != nil {
return nil, err
}
if signed {
req.Header.Set("X-MBX-APIKEY", c.APIKey)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
return body, nil
}
type Balance struct {
Asset string `json:"asset"`
Free string `json:"free"`
Locked string `json:"locked"`
}
type Account struct {
CanTrade bool `json:"canTrade"`
Balances []Balance `json:"balances"`
}
func (c *Client) GetAccount() (*Account, error) {
body, err := c.Request("GET", "/api/v3/account", nil, true)
if err != nil {
return nil, err
}
var acc Account
if err := json.Unmarshal(body, &acc); err != nil {
return nil, err
}
return &acc, nil
}
type Ticker struct {
Symbol string `json:"symbol"`
Price string `json:"price"`
}
func (c *Client) GetPrice(symbol string) (float64, error) {
params := url.Values{}
params.Set("symbol", symbol)
body, err := c.Request("GET", "/api/v3/ticker/price", params, false)
if err != nil {
return 0, err
}
var t Ticker
if err := json.Unmarshal(body, &t); err != nil {
return 0, err
}
return strconv.ParseFloat(t.Price, 64)
}
type Order struct {
Symbol string `json:"symbol"`
OrderID int64 `json:"orderId"`
ClientOrderID string `json:"clientOrderId"`
Status string `json:"status"`
ExecutedQty string `json:"executedQty"`
}
func (c *Client) PlaceLimitOrder(symbol, side, quantity, price string) (*Order, error) {
params := url.Values{}
params.Set("symbol", symbol)
params.Set("side", side)
params.Set("type", "LIMIT")
params.Set("timeInForce", "GTC")
params.Set("quantity", quantity)
params.Set("price", price)
body, err := c.Request("POST", "/api/v3/order", params, true)
if err != nil {
return nil, err
}
var o Order
if err := json.Unmarshal(body, &o); err != nil {
return nil, err
}
return &o, nil
}
3. Programme principal
main.go :
package main
import (
"fmt"
"log"
"strconv"
"github.com/joho/godotenv"
)
func main() {
if err := godotenv.Load(); err != nil {
log.Println("Fichier .env non trouvé, utilisation des variables d'environnement")
}
c := NewClient()
// Consulter le compte
account, err := c.GetAccount()
if err != nil {
log.Fatalln("Échec de la consultation du compte :", err)
}
fmt.Printf("Peut trader : %v\n", account.CanTrade)
for _, b := range account.Balances {
free, _ := strconv.ParseFloat(b.Free, 64)
if free > 0 {
fmt.Printf(" %s : %s (Bloqué %s)\n", b.Asset, b.Free, b.Locked)
}
}
// Consulter le prix
price, _ := c.GetPrice("BTCUSDT")
fmt.Printf("Dernier prix BTC : %.2f\n", price)
// Placer un ordre (à -10% du prix du marché)
testPrice := fmt.Sprintf("%.2f", price*0.9)
order, err := c.PlaceLimitOrder("BTCUSDT", "BUY", "0.001", testPrice)
if err != nil {
log.Fatalln("Échec de l'ordre :", err)
}
fmt.Printf("Ordre %d créé, statut %s\n", order.OrderID, order.Status)
}
Exécution :
go run *.go
III. Bibliothèque communautaire go-binance
1. Installation
go get github.com/adshao/go-binance/v2
2. Exemple Spot
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient(
os.Getenv("BINANCE_API_KEY"),
os.Getenv("BINANCE_SECRET_KEY"),
)
ctx := context.Background()
// Consulter le solde
account, err := client.NewGetAccountService().Do(ctx)
if err != nil {
log.Fatal(err)
}
for _, b := range account.Balances {
fmt.Printf("%s : %s\n", b.Asset, b.Free)
}
// Consulter le prix
prices, err := client.NewListPricesService().Symbol("BTCUSDT").Do(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(prices[0].Price)
// Ordre d'achat limite
order, err := client.NewCreateOrderService().
Symbol("BTCUSDT").
Side(binance.SideTypeBuy).
Type(binance.OrderTypeLimit).
TimeInForce(binance.TimeInForceTypeGTC).
Quantity("0.001").
Price("60000.00").
Do(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Ordre %d\n", order.OrderID)
// Klines
klines, _ := client.NewKlinesService().
Symbol("BTCUSDT").
Interval("1h").
Limit(100).
Do(ctx)
fmt.Printf("%d Klines récupérées\n", len(klines))
}
3. Exemple Futures
futuresClient := binance.NewFuturesClient(apiKey, secret)
// Définir le levier
_, err := futuresClient.NewChangeLeverageService().
Symbol("BTCUSDT").Leverage(10).Do(ctx)
// Ouvrir une position longue
futuresOrder, err := futuresClient.NewCreateOrderService().
Symbol("BTCUSDT").
Side(binance.SideTypeBuy).
Type(binance.OrderTypeLimit).
TimeInForce(binance.TimeInForceTypeGTC).
Quantity("0.01").
Price("60000").
PositionSide(binance.PositionSideTypeLong).
Do(ctx)
// Consulter les positions
positions, _ := futuresClient.NewGetPositionRiskService().Do(ctx)
for _, p := range positions {
if p.PositionAmt != "0" {
fmt.Printf("%s Position %s @ %s\n", p.Symbol, p.PositionAmt, p.EntryPrice)
}
}
IV. Abonnement WebSocket (gorilla/websocket)
go get github.com/gorilla/websocket
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/gorilla/websocket"
)
type TickerEvent struct {
EventType string `json:"e"`
EventTime int64 `json:"E"`
Symbol string `json:"s"`
Close string `json:"c"`
Change string `json:"P"`
Volume string `json:"v"`
}
func main() {
url := "wss://stream.binance.com:9443/stream?streams=btcusdt@ticker/ethusdt@ticker"
for {
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Println("Échec de connexion :", err, ", nouvel essai dans 5s")
time.Sleep(5 * time.Second)
continue
}
log.Println("WS Connecté")
// Goroutine pour le battement de cœur (heartbeat)
stop := make(chan struct{})
go func() {
ticker := time.NewTicker(3 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
conn.WriteMessage(websocket.PingMessage, nil)
case <-stop:
return
}
}
}()
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("Erreur de lecture :", err)
close(stop)
conn.Close()
break
}
var combined struct {
Stream string `json:"stream"`
Data TickerEvent `json:"data"`
}
if err := json.Unmarshal(message, &combined); err != nil {
continue
}
fmt.Printf("%s : %s (24h %s%%)\n",
combined.Data.Symbol,
combined.Data.Close,
combined.Data.Change)
}
}
}
V. Passage d'ordres concurrents avec goroutines
Le modèle de concurrence de Go est particulièrement adapté pour passer plusieurs ordres simultanément :
func placeParallel(c *Client, orders []OrderSpec) []error {
errs := make([]error, len(orders))
done := make(chan int, len(orders))
for i, spec := range orders {
go func(idx int, s OrderSpec) {
_, err := c.PlaceLimitOrder(s.Symbol, s.Side, s.Quantity, s.Price)
errs[idx] = err
done <- idx
}(i, spec)
}
for range orders {
<-done
}
return errs
}
type OrderSpec struct {
Symbol string
Side string
Quantity string
Price string
}
// Utilisation
specs := []OrderSpec{
{"BTCUSDT", "BUY", "0.001", "60000"},
{"ETHUSDT", "BUY", "0.01", "3000"},
{"BNBUSDT", "BUY", "0.1", "500"},
}
errors := placeParallel(client, specs)
Remarque : Le marché Spot de Binance limite à 100 ordres toutes les 10 secondes. Ne dépassez pas 50 goroutines simultanées pour éviter de déclencher la limitation de fréquence.
VI. Questions fréquemment posées (FAQ)
Q1 : Quelle est la taille du binaire Go compilé ? Y a-t-il des dépendances ?
R : Un programme doté des fonctionnalités complètes de l'API Binance + WebSocket pèse environ 8 à 15 Mo après compilation. Il s'exécute directement sur Linux ou Docker sans aucune dépendance d'exécution (contrairement à Node.js qui nécessite des centaines de Mo de node_modules).
Q2 : Quelle est la différence de performance entre go-binance et net/http natif ?
R : L'écart est minime car go-binance n'est qu'une fine couche d'encodage. Un appel REST unique prend 5 à 8 ms, et la latence p99 lors d'un test de charge de 1000 appels concurrents est inférieure à 30 ms. Comparativement aux 80-100 ms de Python, Go offre un avantage de plus de 10 fois.
Q3 : Comment gérer élégamment les erreurs renvoyées par Binance ?
R : Le corps de l'erreur renvoyé par Binance est {"code" : -1021, "msg" : "..."}. Il est recommandé de définir un type BinanceError pour une gestion unifiée :
type BinanceError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func (e *BinanceError) Error() string {
return fmt.Sprintf("Binance %d : %s", e.Code, e.Msg)
}
Q4 : Le WebSocket Go doit-il gérer lui-même la déconnexion après 24 heures ?
R : Oui. Binance déconnecte de force tous les WS après 24 heures. gorilla/websocket n'a pas de mécanisme de reconnexion automatique. Utilisez le modèle de boucle for + Dial de la section IV pour une reconnexion fluide.
Q5 : Comment déployer dans Docker ?
R : Utilisez une construction en plusieurs étapes pour une image finale de seulement 20 Mo :
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o bot
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app/bot /bot
ENTRYPOINT ["/bot"]
Après avoir consulté la solution en Go, revenez à la Navigation par catégorie pour découvrir d'autres tutoriels sur les SDK dans d'autres langages dans la catégorie « Intégration API ».