Работа с 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")
}
}
Автор: keltanas
comments powered by Disqus