API 연동

바이낸스 API Go 언어 호출 방법? net/http 및 go-binance 코드 예제

바이낸스 Go 언어 API 가이드: 표준 라이브러리 crypto/hmac 서명, net/http 요청 캡슐화, go-binance 공식 커뮤니티 라이브러리, gorilla/websocket 실시간 구독, goroutine 병렬 주문 실전 코드.

바이낸스(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 가이드를 살펴보세요.

계속 둘러보기

바이낸스 사용에 대한 추가 질문이 있으신가요? 카테고리 페이지로 돌아가 같은 주제의 다른 가이드를 찾아보세요.

카테고리

관련 가이드

바이낸스 API 신청 방법? 키 및 서명 생성 가이드 2026-04-14 바이낸스 현물(Spot) API 사용법: 첫 주문까지 가능한 실행 코드 가이드 2026-04-14 바이낸스 선물(Futures)과 현물(Spot) API의 차이점은? 엔드포인트와 가중치 비교 2026-04-14 바이낸스 API 사용 시 IP 차단될까? 제한 정책 및 가중치 계산법 총정리 2026-04-14