바이낸스(Binance) Go 언어 API 연동은 최고의 성능과 제로 종속성 배포라는 두 가지 큰 장점을 제공합니다. 단일 바이너리 파일로 Linux 서버에서 실행할 경우, 지연 시간(Latency)이 Python이나 Node.js보다 30-50% 낮습니다. 표준 방식은 표준 라이브러리 crypto/hmac을 사용하여 서명을 생성하고 net/http로 요청을 보내거나, 커뮤니티 라이브러리인 github.com/adshao/go-binance/v2를 직접 사용하는 것입니다. 본문에서는 자체 캡슐화, SDK 사용, WebSocket 구독, 병렬 주문의 네 가지 모듈에 대한 전체 코드를 제공합니다. 바이낸스에 가입하지 않으신 분은 먼저 바이낸스 공식 사이트에서 KYC를 완료해 주시고, 신규 사용자는 무료 가입을 통해 시작할 수 있습니다.
1. Go 언어 SDK 비교
| 라이브러리 | 경로 | GitHub stars | 특징 |
|---|---|---|---|
| adshao/go-binance | github.com/adshao/go-binance/v2 | 3.2k | 커뮤니티 최우선 선택, 현물(Spot) + 선물(Futures) 전체 지원 |
| go-numb/go-binance | github.com/go-numb/go-binance | 50+ | 가볍고 WebSocket에 중점 |
| 순수 net/http | 표준 라이브러리 | — | 최고의 제어력, 외부 종속성 없음 |
추천: 빠른 개발이 필요하면 adshao/go-binance를, 커스터마이징이나 고빈도 매매(HFT) 환경에서는 순수 net/http를 사용하세요.
2. 순수 net/http 서명 구현
1. 프로젝트 초기화
mkdir binance-go && cd binance-go
go mod init binance-go
go get github.com/joho/godotenv
.env 파일 설정:
BINANCE_API_KEY=your_key
BINANCE_SECRET_KEY=your_secret
2. 핵심 서명 및 요청 로직
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. 메인 프로그램
main.go:
package main
import (
"fmt"
"log"
"strconv"
"github.com/joho/godotenv"
)
func main() {
if err := godotenv.Load(); err != nil {
log.Println(".env 파일을 찾을 수 없어 환경변수를 사용합니다")
}
c := NewClient()
// 계정 조회
account, err := c.GetAccount()
if err != nil {
log.Fatalln("계정 조회 실패:", err)
}
fmt.Printf("거래 가능 여부: %v\n", account.CanTrade)
for _, b := range account.Balances {
free, _ := strconv.ParseFloat(b.Free, 64)
if free > 0 {
fmt.Printf(" %s: %s (동결 %s)\n", b.Asset, b.Free, b.Locked)
}
}
// 시세 조회
price, _ := c.GetPrice("BTCUSDT")
fmt.Printf("BTC 현재가: %.2f\n", price)
// 지정가 주문 (현재가 대비 -10%)
testPrice := fmt.Sprintf("%.2f", price*0.9)
order, err := c.PlaceLimitOrder("BTCUSDT", "BUY", "0.001", testPrice)
if err != nil {
log.Fatalln("주문 실패:", err)
}
fmt.Printf("주문 %d 생성 완료, 상태: %s\n", order.OrderID, order.Status)
}
실행 방법:
go run *.go
3. go-binance 커뮤니티 라이브러리 사용
1. 설치
go get github.com/adshao/go-binance/v2
2. 현물(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()
// 잔액 조회
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)
}
// 가격 조회
prices, err := client.NewListPricesService().Symbol("BTCUSDT").Do(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(prices[0].Price)
// 지정가 매수 주문
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("주문 ID: %d\n", order.OrderID)
// K라인 조회
klines, _ := client.NewKlinesService().
Symbol("BTCUSDT").
Interval("1h").
Limit(100).
Do(ctx)
fmt.Printf("%d개의 K라인을 가져왔습니다\n", len(klines))
}
3. 선물(Futures) 예제
futuresClient := binance.NewFuturesClient(apiKey, secret)
// 레버리지 설정
_, err := futuresClient.NewChangeLeverageService().
Symbol("BTCUSDT").Leverage(10).Do(ctx)
// 롱 포지션 오픈
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)
// 포지션 리스크 조회
positions, _ := futuresClient.NewGetPositionRiskService().Do(ctx)
for _, p := range positions {
if p.PositionAmt != "0" {
fmt.Printf("%s 포지션: %s @ %s\n", p.Symbol, p.PositionAmt, p.EntryPrice)
}
}
4. 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("연결 실패:", err, ", 5초 후 재시도")
time.Sleep(5 * time.Second)
continue
}
log.Println("WS 연결 성공")
// 하트비트(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("읽기 오류:", 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)
}
}
}
5. goroutine을 활용한 병렬 주문
Go의 동시성 모델은 동시에 여러 주문을 넣기에 매우 적합합니다:
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
}
// 사용 예시
specs := []OrderSpec{
{"BTCUSDT", "BUY", "0.001", "60000"},
{"ETHUSDT", "BUY", "0.01", "3000"},
{"BNBUSDT", "BUY", "0.1", "500"},
}
errors := placeParallel(client, specs)
주의: 바이낸스 현물은 10초당 100건의 주문 제한이 있으므로, 병렬 처리 시에도 빈도 제한을 트리거하지 않도록 주의해야 합니다.
6. 자주 묻는 질문 FAQ
Q1: Go로 빌드한 바이너리 파일 크기는 어느 정도이며 종속성이 있나요?
A: 바이낸스 API 기능과 WebSocket을 포함한 프로그램은 빌드 후 약 8-15MB 정도입니다. Linux나 Docker에서 별도의 런타임 종속성 없이 바로 실행 가능합니다(Node.js의 수백 MB에 달하는 node_modules와 대조적임).
Q2: go-binance와 순수 net/http의 성능 차이는?
A: go-binance는 가벼운 래퍼(Wrapper)일 뿐이므로 차이가 거의 없습니다. 단일 REST 호출은 5-8ms, 1000회 병렬 테스트 시 p99 지연 시간은 30ms 미만입니다. Python의 80-100ms와 비교하면 Go가 10배 이상의 우위를 가집니다.
Q3: 바이낸스 반환 오류를 어떻게 우아하게 처리하나요?
A: 바이낸스는 {"code": -1021, "msg": "..."} 형식으로 오류를 반환합니다. 다음과 같이 BinanceError 타입을 정의하여 처리하는 것이 좋습니다:
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: Go WebSocket 사용 시 24시간 끊김 현상을 직접 처리해야 하나요?
A: 네, 그렇습니다. 바이낸스는 모든 WS 연결을 24시간마다 강제로 끊습니다. gorilla/websocket에는 자동 재연결 기능이 없으므로, 섹션 4의 for 루프 + Dial 패턴을 사용하여 자동으로 재연결되도록 구현해야 합니다.
Q5: Docker에서 어떻게 배포하나요?
A: 멀티 스테이지 빌드를 사용하면 최종 이미지를 20MB 미만으로 유지할 수 있습니다:
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"]
Go 솔루션을 확인하셨다면 카테고리로 돌아가 「API 연동」 카테고리의 다른 언어 SDK 가이드를 살펴보세요.