Go Binance
last modified October 30, 2026
In this article we show how to work with the Binance exchange in Go using the go-binance SDK. Binance is a cryptocurrency exchange with a public market data API and a private account API. Public endpoints return symbols, prices and order books without authentication; private endpoints require an API key and a secret key.
The go-binance package wraps both APIs in services such as
NewListPricesService and NewGetAccountService. Values
that represent money are returned as strings, so that the exact precision of
the exchange is preserved.
All examples were compiled and run with Go 1.27.0 and go-binance v2.8.12. Market data changes continuously, so prices, volumes and counts in the output are from the run used to produce this article.
Installation
$ mkdir binance-demo $ cd binance-demo $ go mod init com.zetcode/binance go: creating new go.mod: module com.zetcode/binance $ go get github.com/adshao/go-binance/v2@v2.8.12 go: added github.com/adshao/go-binance/v2 v2.8.12
Server time
The server time endpoint is the simplest check that the API is reachable. It returns the current time in milliseconds since the Unix epoch.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient("", "")
res, err := client.NewServerTimeService().Do(context.Background())
if err != nil {
log.Fatal(err)
}
t := time.UnixMilli(res).UTC()
fmt.Println(res)
fmt.Println(t.Format(time.RFC3339))
}
The client is created with empty keys because the endpoint is public.
Do takes a context.Context, which can carry a
deadline for the request.
$ go run main.go 1793387612499 2026-10-30T19:13:32Z
Exchange information
NewExchangeInfoService returns the trading rules of the exchange,
including every symbol. A symbol is a trading pair made of a base asset and a
quote asset, for example BTC and USDT.
package main
import (
"context"
"fmt"
"log"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient("", "")
res, err := client.NewExchangeInfoService().Do(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("symbols:", len(res.Symbols))
fmt.Println("timezone:", res.Timezone)
n := 0
for _, s := range res.Symbols {
if s.Status == "TRADING" && s.QuoteAsset == "USDT" {
fmt.Printf("%s %s %s\n", s.Symbol, s.BaseAsset, s.QuoteAsset)
n++
if n == 5 {
break
}
}
}
}
The response is filtered to symbols that are currently trading and quoted in USDT. The total count changes as pairs are listed or removed, so it differs from the number shown here.
$ go run main.go symbols: 3742 timezone: UTC BTCUSDT BTC USDT ETHUSDT ETH USDT BNBUSDT BNB USDT NEOUSDT NEO USDT LTCUSDT LTC USDT
Prices
NewListPricesService returns the latest price for every symbol, or
for the symbols selected with Symbol and Symbols.
package main
import (
"context"
"fmt"
"log"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient("", "")
res, err := client.NewListPricesService().
Symbols([]string{"BTCUSDT", "ETHUSDT", "LTCUSDT"}).
Do(context.Background())
if err != nil {
log.Fatal(err)
}
for _, p := range res {
fmt.Printf("%-10s %s\n", p.Symbol, p.Price)
}
}
The price is a decimal string whose precision is set by the exchange;
converting it to float64 loses that exactness, so formatting for
display is safer than arithmetic on the parsed value.
$ go run main.go BTCUSDT 76500.01000000 ETHUSDT 2233.31000000 LTCUSDT 54.93000000
24-hour statistics
NewListPriceChangeStatsService returns the last price, the change
over 24 hours, the high, the low and the traded volume for each symbol.
package main
import (
"context"
"fmt"
"log"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient("", "")
res, err := client.NewListPriceChangeStatsService().
Symbols([]string{"BTCUSDT", "ETHUSDT"}).
Do(context.Background())
if err != nil {
log.Fatal(err)
}
for _, s := range res {
fmt.Printf("%-9s last=%s change=%s%% high=%s low=%s volume=%s\n",
s.Symbol, s.LastPrice, s.PriceChangePercent, s.HighPrice,
s.LowPrice, s.Volume)
}
}
PriceChangePercent is a percentage value without the percent
sign, so the sign is added by the format string. All numeric fields are strings
for the same precision reason as the prices.
$ go run main.go BTCUSDT last=76508.00000000 change=-5.340% high=80954.65000000 low=76325.98000000 volume=33982.66406000 ETHUSDT last=2233.84000000 change=-4.093% high=2339.71000000 low=2174.08000000 volume=656125.00960000
Candlestick data
NewKlinesService returns candlesticks for a symbol, an interval
and a limit. Supported intervals include 1m, 15m,
1h, 4h and 1d.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient("", "")
res, err := client.NewKlinesService().Symbol("BTCUSDT").
Interval("1h").Limit(4).Do(context.Background())
if err != nil {
log.Fatal(err)
}
for _, k := range res {
t := time.UnixMilli(k.OpenTime).UTC()
fmt.Printf("%s open=%s high=%s low=%s close=%s\n",
t.Format("15:04"), k.Open, k.High, k.Low, k.Close)
}
}
OpenTime is in milliseconds. The four candles are the last four
closed or current hours, so the times move forward as the program is run.
$ go run main.go 16:00 open=77622.01000000 high=77692.31000000 low=77150.00000000 close=77352.67000000 17:00 open=77352.68000000 high=77498.00000000 low=77126.00000000 close=77192.45000000 18:00 open=77192.46000000 high=77214.10000000 low=76325.98000000 close=76661.13000000 19:00 open=76661.13000000 high=76785.18000000 low=76435.88000000 close=76518.01000000
Order book
NewDepthService returns the current bids and asks. A bid is a buy
order, an ask is a sell order, and Limit caps the number of price
levels.
package main
import (
"context"
"fmt"
"log"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient("", "")
res, err := client.NewDepthService().Symbol("BTCUSDT").Limit(5).
Do(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("last update:", res.LastUpdateID)
fmt.Println("bids:")
for _, b := range res.Bids {
fmt.Printf(" %s %s\n", b.Price, b.Quantity)
}
fmt.Println("asks:")
for _, a := range res.Asks {
fmt.Printf(" %s %s\n", a.Price, a.Quantity)
}
}
The best bid and the best ask are the first entries of the two lists. The gap between them is the spread, and the order book changes with every trade.
$ go run main.go last update: 102124221224 bids: 76520.00000000 2.95764000 76519.82000000 0.00099000 76518.78000000 0.00014000 76518.77000000 0.01594000 76518.74000000 0.00007000 asks: 76520.01000000 0.01316000 76520.02000000 0.00049000 76520.78000000 0.00007000 76521.74000000 0.00014000 76521.75000000 0.00007000
API errors
An invalid symbol is rejected by the server with an error that carries the
Binance error code. The SDK returns it as a
*common.APIError, which can be inspected with
errors.As.
package main
import (
"context"
"errors"
"fmt"
"github.com/adshao/go-binance/v2"
"github.com/adshao/go-binance/v2/common"
)
func main() {
client := binance.NewClient("", "")
_, err := client.NewListPricesService().Symbol("NOPE").
Do(context.Background())
fmt.Println(err)
var apiErr *common.APIError
fmt.Println(errors.As(err, &apiErr))
if apiErr != nil {
fmt.Println(apiErr.Code, apiErr.Message)
}
}
The error text contains the code and the message, and the typed value exposes both fields for program logic. Checking the code is more reliable than matching the text.
$ go run main.go <APIError> code=-1121, msg=Invalid symbol. true -1121 Invalid symbol.
Private account data
Private endpoints are signed with the secret key. The keys are passed to
NewClient and are usually read from environment variables so that
they do not end up in the source code.
package main
import (
"context"
"fmt"
"os"
"strconv"
"github.com/adshao/go-binance/v2"
)
func main() {
client := binance.NewClient(
os.Getenv("BINANCE_API_KEY"),
os.Getenv("BINANCE_SECRET_KEY"),
)
res, err := client.NewGetAccountService().Do(context.Background())
if err != nil {
fmt.Println(err)
return
}
for _, b := range res.Balances {
free, _ := strconv.ParseFloat(b.Free, 64)
locked, _ := strconv.ParseFloat(b.Locked, 64)
if free > 0 || locked > 0 {
fmt.Printf("%-6s %12.4f %10.4f\n", b.Asset, free, locked)
}
}
}
Without keys the request is rejected before any account data is returned. The same program with valid keys prints one line per asset that has a non-zero free or locked amount.
$ go run main.go <APIError> code=-2014, msg=API-key format invalid.
Testing with a mock server
The client has an exported BaseURL, so a test can point it at a
local server instead of Binance. This example returns a fixed account payload
and shows how the response is processed, without using real keys.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"github.com/adshao/go-binance/v2"
)
const accountJSON = `{
"makerCommission": 10,
"takerCommission": 10,
"buyerCommission": 0,
"sellerCommission": 0,
"canTrade": true,
"canWithdraw": true,
"canDeposit": true,
"updateTime": 1793387612000,
"accountType": "SPOT",
"balances": [
{"asset": "BTC", "free": "0.50000000", "locked": "0.00000000"},
{"asset": "USDT", "free": "1500.25000000", "locked": "25.00000000"},
{"asset": "ETH", "free": "0.00000000", "locked": "0.00000000"}
]
}`
func main() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, accountJSON)
}))
defer srv.Close()
client := binance.NewClient("", "")
client.BaseURL = srv.URL
res, err := client.NewGetAccountService().Do(context.Background())
if err != nil {
fmt.Println(err)
return
}
fmt.Println("account:", res.AccountType)
for _, b := range res.Balances {
free, _ := strconv.ParseFloat(b.Free, 64)
locked, _ := strconv.ParseFloat(b.Locked, 64)
if free > 0 || locked > 0 {
fmt.Printf("%-6s %12.4f %10.4f\n", b.Asset, free, locked)
}
}
}
The mock ignores the signature and always returns the same JSON, while the SDK performs the normal request, decoding and error handling. The same technique works against the Binance testnet.
$ go run main.go account: SPOT BTC 0.5000 0.0000 USDT 1500.2500 25.0000
Source
Go binance - Github page
Binance Spot API - documentation
In this article we have worked with the Binance exchange in Go. We read the server time and the exchange information, listed prices and 24-hour statistics, fetched candlesticks and the order book, inspected an API error, called a private endpoint that needs keys, and tested the account handler against a local mock server.
Author
List all Go tutorials.