Работа с API криптобиржи Yobit на golang - Получение собственного баланса

Все примеры работают с go version go1.9.2 linux/amd64

Работа с API для торгов

Получение своего баланса

Прежде всего для работы с api торгов нужно создать пару Ключ - Секрет на странице https://yobit.io/ru/api/keys/

Все будет немного сложнее, чем в предыдущем примере. Именно авторизация на бирже и подпись параметров вызвали у меня больше всего вопросов. Потому-то и хочется оставить рабочий код здесь.

Для начала каждый запрос нам нужно будет подписывать. И чтобы запросы (и подписи) отличались, биржа требует указывать каждый раз параметр nonce в виде целого числа в диапазоне 1..2147483646. При чем в каждом следующем запросе это число должно быть больше, чем в предыдущем. Для обнуления нужно создать новый ключ.

Для получения параметра nonce напишем отдельную функцию, чтобы мы могли пользоваться им даже после рестарта приложения:

import (
    "io/ioutil"
    "strconv"
)

func GetNonce(key string) (nonce int, err error) {
    nonceFileName := "nonce." + key[0:8] + ".txt"
    nonceBytes, err := ioutil.ReadFile(nonceFileName)
    if err == nil {
        nonce, _ = strconv.Atoi(string(nonceBytes))
    }
    nonce++
    err = ioutil.WriteFile(nonceFileName, []byte(strconv.Itoa(nonce)), 0644)

    return
}

Для того, чтобы хранить параметр для каждого ключа отдельно, используем часть ключа как часть имени файла.

Посмотрим, что сказано у нас в документации для получения баланса аккаунта:

getInfo - Метод возвращает информацию о балансах пользователя и привилегиях API-ключа, а так же время сервера.

Требования: привилегия ключа info

Параметры: отсутствуют

Пример ответа:

{
    "success":1,
    "return":{
        "funds":{
            "ltc":22,
            "nvc":423.998,
            "ppc":10,
            ...
        },
        "funds_incl_orders":{
            "ltc":32,
            "nvc":523.998,
            "ppc":20,
            ...
        },    
        "rights":{
            "info":1,
            "trade":0,
            "withdraw":0
        },
        "transaction_count":0,
        "open_orders":1,
        "server_time":1418654530
    }
}

Для работы с такой схемой JSON создадим структуры данных и функцию, создающую пустой баланс:

func NewBalance() Balance {
    balance := Balance{}
    balance.Return = BalanceReturn{}
    balance.Return.Funds = make(map[string]float64)
    balance.Return.FundsInclOrders = make(map[string]float64)
    return balance
}
type Balance struct {
    Success uint8         `json:"success"`
    Return  BalanceReturn `json:"return"`
}
type BalanceReturn struct {
    Rights           BalanceReturnRight `json:"rights"`
    Funds            map[string]float64 `json:"funds"`
    FundsInclOrders  map[string]float64 `json:"funds_incl_orders"`
    TransactionCount int64              `json:"transaction_count"`
    OpenOrders       int64              `json:"open_orders"`
    ServerTime       uint64             `json:"server_time"`
}
type BalanceReturnRight struct {
    Info     int64 `json:"info"`
    Trade    int64 `json:"trade"`
    Deposit  int64 `json:"deposit"`
    Withdraw int64 `json:"withdraw"`
}

Теперь посмотрим, как правильно авторизоваться и запрашивать приватные данные. Для начала создадим заготовку нашей функции:

func LoadBalance(key, secret string) (Balance, error) {
    balance := NewBalance()
    nonce, err := GetNonce(key)
    if nil != err {
        return balance, err
    }
    ...
    return balance, err
}

Зададим параметры нашего запроса, который будем отправлять на биржу:

    values := url.Values{
        "method": []string{"getInfo"},
        "nonce": []string{strconv.Itoa(nonce)},
    }

Теперь, чтобы в соответствии с документацией подписать их, нужно сформировать строку вида method=...&nonce=...:

    paramsList := make([]string, 0)
    for paramName, paramValues := range values {
        for _, param := range paramValues {
            paramsList = append(paramsList, paramName+"="+param)
        }
    }

    params := strings.Join(paramsList, "&")

Подпись создается с помощью библиотеки crypto

    sign := hmac.New(sha512.New, []byte(secret))
    sign.Write([]byte(params))

Теперь использую полученные данные сформируем запрос.

Тут нужно обратить внимание на то, что данные из values отправляются как данные формы, но, поскольку метод client.PostForm() не поддерживает установку дополнительных заголовков, построим request сами

    req, err := http.NewRequest("POST", domain+"/tapi/", strings.NewReader(values.Encode()))
    req.Header.Add("Key", key)
    req.Header.Add("Sign", hex.EncodeToString(sign.Sum(nil)))
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

и отправим его на биржу

    client := &http.Client{}
    resp, err := client.Do(req)
    if nil != err {
        return balance, err
    }

теперь получим тело ответа и декодируем JSON в структуру Balance

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if nil != err {
        return balance, err
    }

    err = json.Unmarshal(body, &balance)

На этом все. Чтобы использовать другие методы API, достаточно в мапу url.Values прописать нужное название метода и добавить прочие необходимые параметры. Можно даже написать более обобщенную функцию... но, это уже тема рефакторинга и, возможно, другой статьи.

Полный код выглядит так:

import (
    "net/http"
    "encoding/json"
    "io/ioutil"
    "strings"
    "strconv"
    "crypto/hmac"
    "crypto/sha512"
    "encoding/hex"
    "net/url"
)

const (
    domain = "https://yobit.io"
)

func GetNonce(key string) (nonce int, err error) {
    nonceFileName := "nonce." + key[0:8] + ".txt"
    nonceBytes, err := ioutil.ReadFile(nonceFileName)
    if err == nil {
        nonce, _ = strconv.Atoi(string(nonceBytes))
    }
    nonce++
    err = ioutil.WriteFile(nonceFileName, []byte(strconv.Itoa(nonce)), 0644)

    return
}

func LoadBalance(key, secret string) (Balance, error) {
    balance := NewBalance()
    nonce, err := GetNonce(key)
    if nil != err {
        return balance, err
    }

    values := url.Values{
        "method": []string{"getInfo"},
        "nonce":  []string{strconv.Itoa(nonce)},
    }
    paramsList := make([]string, 0)
    for paramName, paramValues := range values {
        for _, param := range paramValues {
            paramsList = append(paramsList, paramName+"="+param)
        }
    }

    params := strings.Join(paramsList, "&")
    sign := hmac.New(sha512.New, []byte(secret))
    sign.Write([]byte(params))

    req, err := http.NewRequest("POST", domain+"/tapi/", strings.NewReader(values.Encode()))
    req.Header.Add("Key", key)
    req.Header.Add("Sign", hex.EncodeToString(sign.Sum(nil)))
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    client := &http.Client{}
    resp, err := client.Do(req)
    if nil != err {
        return balance, err
    }

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if nil != err {
        return balance, err
    }

    err = json.Unmarshal(body, &balance)

    return balance, err
}

Использовать можно следующим образом, подставляя свои значения key и secret:

import (
    "github.com/username/exchange/yobit"
    log "github.com/sirupsen/logrus"
)

func main() {
    balance, err := yobit.LoadBalance(key, secret)
    if err != nil {
        log.WithError(err).Errorln("Cannot load balance")
    }

    for coin, fund := range balance.Return.Funds {
        log.WithFields(log.Fields{
            "coin": coin,
            "fund": fund,
        }).Info("Funds")
    }
}

Создано:
Автор:
« Назад на главную

comments powered by Disqus