mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-25 07:26:48 +00:00
* Fix Huobi's POST request content-type If we try to send a POST request to Huobi w/ a content-type of "application/x-www-form-urlencoded" we will receive an error: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported Their english documentation is incorrect, it does not specify this atm, translating their chinese documentation, it states: "Content-Type: application/json must be declared in the POST request header; Content-Type: application/x-www-form-urlencoded must be declared in the GET request header. (Chinese users recommend setting Accept-Language: zh-cn)" * Fix Huobi's place new order request (send as json) We should not send the order details through url parameters, this needs to be sent as a json payload via the request body. Documentation: https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1orderordersplace--make-an-order-in-huobipro * Fix Huobi's margin transfer/margin order/withdraw requests This requests data need to be sent as json, not as a query string. Docs: https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1dwtransfer-inmargin--transfer-asset-from-spot-account-to-margin-account https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1marginorders--margin-application https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1dwwithdrawapicreate---create-a-withdraw-application * Fix Huobi's margin repayment request This request data needs to be sent a json, not via the query string. Also note, that the "order-id" parameter is already sent through the url path, it does not need to be included in the request body.
842 lines
21 KiB
Go
842 lines
21 KiB
Go
package huobi
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/thrasher-/gocryptotrader/common"
|
|
"github.com/thrasher-/gocryptotrader/config"
|
|
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
|
"github.com/thrasher-/gocryptotrader/exchanges/request"
|
|
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
|
)
|
|
|
|
const (
|
|
huobiAPIURL = "https://api.huobi.pro"
|
|
huobiAPIVersion = "1"
|
|
|
|
huobiMarketHistoryKline = "market/history/kline"
|
|
huobiMarketDetail = "market/detail"
|
|
huobiMarketDetailMerged = "market/detail/merged"
|
|
huobiMarketDepth = "market/depth"
|
|
huobiMarketTrade = "market/trade"
|
|
huobiMarketTradeHistory = "market/history/trade"
|
|
huobiSymbols = "common/symbols"
|
|
huobiCurrencies = "common/currencys"
|
|
huobiTimestamp = "common/timestamp"
|
|
huobiAccounts = "account/accounts"
|
|
huobiAccountBalance = "account/accounts/%s/balance"
|
|
huobiOrderPlace = "order/orders/place"
|
|
huobiOrderCancel = "order/orders/%s/submitcancel"
|
|
huobiOrderCancelBatch = "order/orders/batchcancel"
|
|
huobiGetOrder = "order/orders/%s"
|
|
huobiGetOrderMatch = "order/orders/%s/matchresults"
|
|
huobiGetOrders = "order/orders"
|
|
huobiGetOrdersMatch = "orders/matchresults"
|
|
huobiMarginTransferIn = "dw/transfer-in/margin"
|
|
huobiMarginTransferOut = "dw/transfer-out/margin"
|
|
huobiMarginOrders = "margin/orders"
|
|
huobiMarginRepay = "margin/orders/%s/repay"
|
|
huobiMarginLoanOrders = "margin/loan-orders"
|
|
huobiMarginAccountBalance = "margin/accounts/balance"
|
|
huobiWithdrawCreate = "dw/withdraw/api/create"
|
|
huobiWithdrawCancel = "dw/withdraw-virtual/%s/cancel"
|
|
|
|
huobiAuthRate = 100
|
|
huobiUnauthRate = 100
|
|
)
|
|
|
|
// HUOBI is the overarching type across this package
|
|
type HUOBI struct {
|
|
exchange.Base
|
|
}
|
|
|
|
// SetDefaults sets default values for the exchange
|
|
func (h *HUOBI) SetDefaults() {
|
|
h.Name = "Huobi"
|
|
h.Enabled = false
|
|
h.Fee = 0
|
|
h.Verbose = false
|
|
h.Websocket = false
|
|
h.RESTPollingDelay = 10
|
|
h.RequestCurrencyPairFormat.Delimiter = ""
|
|
h.RequestCurrencyPairFormat.Uppercase = false
|
|
h.ConfigCurrencyPairFormat.Delimiter = "-"
|
|
h.ConfigCurrencyPairFormat.Uppercase = true
|
|
h.AssetTypes = []string{ticker.Spot}
|
|
h.SupportsAutoPairUpdating = true
|
|
h.SupportsRESTTickerBatching = false
|
|
h.Requester = request.New(h.Name,
|
|
request.NewRateLimit(time.Second*10, huobiAuthRate),
|
|
request.NewRateLimit(time.Second*10, huobiUnauthRate),
|
|
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
|
|
h.APIUrlDefault = huobiAPIURL
|
|
h.APIUrl = h.APIUrlDefault
|
|
}
|
|
|
|
// Setup sets user configuration
|
|
func (h *HUOBI) Setup(exch config.ExchangeConfig) {
|
|
if !exch.Enabled {
|
|
h.SetEnabled(false)
|
|
} else {
|
|
h.Enabled = true
|
|
h.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
|
|
h.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
|
|
h.APIAuthPEMKeySupport = exch.APIAuthPEMKeySupport
|
|
h.APIAuthPEMKey = exch.APIAuthPEMKey
|
|
h.SetHTTPClientTimeout(exch.HTTPTimeout)
|
|
h.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
|
h.RESTPollingDelay = exch.RESTPollingDelay
|
|
h.Verbose = exch.Verbose
|
|
h.Websocket = exch.Websocket
|
|
h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
|
h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
|
h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
|
err := h.SetCurrencyPairFormat()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = h.SetAssetTypes()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = h.SetAutoPairDefaults()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = h.SetAPIURL(exch)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetFee returns Huobi fee
|
|
func (h *HUOBI) GetFee() float64 {
|
|
return h.Fee
|
|
}
|
|
|
|
// GetSpotKline returns kline data
|
|
// KlinesRequestParams contains symbol, period and size
|
|
func (h *HUOBI) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) {
|
|
vals := url.Values{}
|
|
vals.Set("symbol", arg.Symbol)
|
|
vals.Set("period", string(arg.Period))
|
|
|
|
if arg.Size != 0 {
|
|
vals.Set("size", strconv.Itoa(arg.Size))
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
Data []KlineItem `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketHistoryKline)
|
|
|
|
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Data, err
|
|
}
|
|
|
|
// GetMarketDetailMerged returns the ticker for the specified symbol
|
|
func (h *HUOBI) GetMarketDetailMerged(symbol string) (DetailMerged, error) {
|
|
vals := url.Values{}
|
|
vals.Set("symbol", symbol)
|
|
|
|
type response struct {
|
|
Response
|
|
Tick DetailMerged `json:"tick"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketDetailMerged)
|
|
|
|
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
|
|
if result.ErrorMessage != "" {
|
|
return result.Tick, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Tick, err
|
|
}
|
|
|
|
// GetDepth returns the depth for the specified symbol
|
|
func (h *HUOBI) GetDepth(obd OrderBookDataRequestParams) (Orderbook, error) {
|
|
vals := url.Values{}
|
|
vals.Set("symbol", obd.Symbol)
|
|
|
|
if obd.Type != OrderBookDataRequestParamsTypeNone {
|
|
vals.Set("type", string(obd.Type))
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
Depth Orderbook `json:"tick"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketDepth)
|
|
|
|
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
|
|
if result.ErrorMessage != "" {
|
|
return result.Depth, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Depth, err
|
|
}
|
|
|
|
// GetTrades returns the trades for the specified symbol
|
|
func (h *HUOBI) GetTrades(symbol string) ([]Trade, error) {
|
|
vals := url.Values{}
|
|
vals.Set("symbol", symbol)
|
|
|
|
type response struct {
|
|
Response
|
|
Tick struct {
|
|
Data []Trade `json:"data"`
|
|
} `json:"tick"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketTrade)
|
|
|
|
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Tick.Data, err
|
|
}
|
|
|
|
// GetLatestSpotPrice returns latest spot price of symbol
|
|
//
|
|
// symbol: string of currency pair
|
|
func (h *HUOBI) GetLatestSpotPrice(symbol string) (float64, error) {
|
|
list, err := h.GetTradeHistory(symbol, "1")
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(list) == 0 {
|
|
return 0, errors.New("The length of the list is 0")
|
|
}
|
|
|
|
return list[0].Trades[0].Price, nil
|
|
}
|
|
|
|
// GetTradeHistory returns the trades for the specified symbol
|
|
func (h *HUOBI) GetTradeHistory(symbol, size string) ([]TradeHistory, error) {
|
|
vals := url.Values{}
|
|
vals.Set("symbol", symbol)
|
|
|
|
if size != "" {
|
|
vals.Set("size", size)
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
TradeHistory []TradeHistory `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketTradeHistory)
|
|
|
|
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.TradeHistory, err
|
|
}
|
|
|
|
// GetMarketDetail returns the ticker for the specified symbol
|
|
func (h *HUOBI) GetMarketDetail(symbol string) (Detail, error) {
|
|
vals := url.Values{}
|
|
vals.Set("symbol", symbol)
|
|
|
|
type response struct {
|
|
Response
|
|
Tick Detail `json:"tick"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketDetail)
|
|
|
|
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
|
|
if result.ErrorMessage != "" {
|
|
return result.Tick, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Tick, err
|
|
}
|
|
|
|
// GetSymbols returns an array of symbols supported by Huobi
|
|
func (h *HUOBI) GetSymbols() ([]Symbol, error) {
|
|
type response struct {
|
|
Response
|
|
Symbols []Symbol `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/v%s/%s", h.APIUrl, huobiAPIVersion, huobiSymbols)
|
|
|
|
err := h.SendHTTPRequest(url, &result)
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Symbols, err
|
|
}
|
|
|
|
// GetCurrencies returns a list of currencies supported by Huobi
|
|
func (h *HUOBI) GetCurrencies() ([]string, error) {
|
|
type response struct {
|
|
Response
|
|
Currencies []string `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/v%s/%s", h.APIUrl, huobiAPIVersion, huobiCurrencies)
|
|
|
|
err := h.SendHTTPRequest(url, &result)
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Currencies, err
|
|
}
|
|
|
|
// GetTimestamp returns the Huobi server time
|
|
func (h *HUOBI) GetTimestamp() (int64, error) {
|
|
type response struct {
|
|
Response
|
|
Timestamp int64 `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
url := fmt.Sprintf("%s/v%s/%s", h.APIUrl, huobiAPIVersion, huobiTimestamp)
|
|
|
|
err := h.SendHTTPRequest(url, &result)
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Timestamp, err
|
|
}
|
|
|
|
// GetAccounts returns the Huobi user accounts
|
|
func (h *HUOBI) GetAccounts() ([]Account, error) {
|
|
type response struct {
|
|
Response
|
|
AccountData []Account `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("GET", huobiAccounts, url.Values{}, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.AccountData, err
|
|
}
|
|
|
|
// GetAccountBalance returns the users Huobi account balance
|
|
func (h *HUOBI) GetAccountBalance(accountID string) ([]AccountBalanceDetail, error) {
|
|
type response struct {
|
|
Response
|
|
AccountBalanceData AccountBalance `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
endpoint := fmt.Sprintf(huobiAccountBalance, accountID)
|
|
err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.AccountBalanceData.AccountBalanceDetails, err
|
|
}
|
|
|
|
// SpotNewOrder submits an order to Huobi
|
|
func (h *HUOBI) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
|
|
data := struct {
|
|
AccountID int `json:"account-id,string"`
|
|
Amount string `json:"amount"`
|
|
Price string `json:"price"`
|
|
Source string `json:"source"`
|
|
Symbol string `json:"symbol"`
|
|
Type string `json:"type"`
|
|
}{
|
|
AccountID: arg.AccountID,
|
|
Amount: strconv.FormatFloat(arg.Amount, 'f', -1, 64),
|
|
Symbol: arg.Symbol,
|
|
Type: string(arg.Type),
|
|
}
|
|
|
|
// Only set price if order type is not equal to buy-market or sell-market
|
|
if arg.Type != SpotNewOrderRequestTypeBuyMarket && arg.Type != SpotNewOrderRequestTypeSellMarket {
|
|
data.Price = strconv.FormatFloat(arg.Price, 'f', -1, 64)
|
|
}
|
|
|
|
if arg.Source != "" {
|
|
data.Source = arg.Source
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
OrderID int64 `json:"data,string"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("POST", huobiOrderPlace, nil, data, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.OrderID, err
|
|
}
|
|
|
|
// CancelOrder cancels an order on Huobi
|
|
func (h *HUOBI) CancelOrder(orderID int64) (int64, error) {
|
|
type response struct {
|
|
Response
|
|
OrderID int64 `json:"data,string"`
|
|
}
|
|
|
|
var result response
|
|
endpoint := fmt.Sprintf(huobiOrderCancel, strconv.FormatInt(orderID, 10))
|
|
err := h.SendAuthenticatedHTTPRequest("POST", endpoint, url.Values{}, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.OrderID, err
|
|
}
|
|
|
|
// CancelOrderBatch cancels a batch of orders -- to-do
|
|
func (h *HUOBI) CancelOrderBatch(orderIDs []int64) ([]CancelOrderBatch, error) {
|
|
type response struct {
|
|
Response
|
|
Data []CancelOrderBatch `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("POST", huobiOrderCancelBatch, url.Values{}, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Data, err
|
|
}
|
|
|
|
// GetOrder returns order information for the specified order
|
|
func (h *HUOBI) GetOrder(orderID int64) (OrderInfo, error) {
|
|
type response struct {
|
|
Response
|
|
Order OrderInfo `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
endpoint := fmt.Sprintf(huobiGetOrder, strconv.FormatInt(orderID, 10))
|
|
err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return result.Order, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Order, err
|
|
}
|
|
|
|
// GetOrderMatchResults returns matched order info for the specified order
|
|
func (h *HUOBI) GetOrderMatchResults(orderID int64) ([]OrderMatchInfo, error) {
|
|
type response struct {
|
|
Response
|
|
Orders []OrderMatchInfo `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
endpoint := fmt.Sprintf(huobiGetOrderMatch, strconv.FormatInt(orderID, 10))
|
|
err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Orders, err
|
|
}
|
|
|
|
// GetOrders returns a list of orders
|
|
func (h *HUOBI) GetOrders(symbol, types, start, end, states, from, direct, size string) ([]OrderInfo, error) {
|
|
type response struct {
|
|
Response
|
|
Orders []OrderInfo `json:"data"`
|
|
}
|
|
|
|
vals := url.Values{}
|
|
vals.Set("symbol", symbol)
|
|
vals.Set("states", states)
|
|
|
|
if types != "" {
|
|
vals.Set("types", types)
|
|
}
|
|
|
|
if start != "" {
|
|
vals.Set("start-date", start)
|
|
}
|
|
|
|
if end != "" {
|
|
vals.Set("end-date", end)
|
|
}
|
|
|
|
if from != "" {
|
|
vals.Set("from", from)
|
|
}
|
|
|
|
if direct != "" {
|
|
vals.Set("direct", direct)
|
|
}
|
|
|
|
if size != "" {
|
|
vals.Set("size", size)
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("GET", huobiGetOrders, vals, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Orders, err
|
|
}
|
|
|
|
// GetOrdersMatch returns a list of matched orders
|
|
func (h *HUOBI) GetOrdersMatch(symbol, types, start, end, from, direct, size string) ([]OrderMatchInfo, error) {
|
|
type response struct {
|
|
Response
|
|
Orders []OrderMatchInfo `json:"data"`
|
|
}
|
|
|
|
vals := url.Values{}
|
|
vals.Set("symbol", symbol)
|
|
|
|
if types != "" {
|
|
vals.Set("types", types)
|
|
}
|
|
|
|
if start != "" {
|
|
vals.Set("start-date", start)
|
|
}
|
|
|
|
if end != "" {
|
|
vals.Set("end-date", end)
|
|
}
|
|
|
|
if from != "" {
|
|
vals.Set("from", from)
|
|
}
|
|
|
|
if direct != "" {
|
|
vals.Set("direct", direct)
|
|
}
|
|
|
|
if size != "" {
|
|
vals.Set("size", size)
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("GET", huobiGetOrdersMatch, vals, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Orders, err
|
|
}
|
|
|
|
// MarginTransfer transfers assets into or out of the margin account
|
|
func (h *HUOBI) MarginTransfer(symbol, currency string, amount float64, in bool) (int64, error) {
|
|
data := struct {
|
|
Symbol string `json:"symbol"`
|
|
Currency string `json:"currency"`
|
|
Amount string `json:"amount"`
|
|
}{
|
|
Symbol: symbol,
|
|
Currency: currency,
|
|
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
|
|
}
|
|
|
|
path := huobiMarginTransferIn
|
|
if !in {
|
|
path = huobiMarginTransferOut
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
TransferID int64 `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("POST", path, nil, data, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.TransferID, err
|
|
}
|
|
|
|
// MarginOrder submits a margin order application
|
|
func (h *HUOBI) MarginOrder(symbol, currency string, amount float64) (int64, error) {
|
|
data := struct {
|
|
Symbol string `json:"symbol"`
|
|
Currency string `json:"currency"`
|
|
Amount string `json:"amount"`
|
|
}{
|
|
Symbol: symbol,
|
|
Currency: currency,
|
|
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
MarginOrderID int64 `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("POST", huobiMarginOrders, nil, data, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.MarginOrderID, err
|
|
}
|
|
|
|
// MarginRepayment repays a margin amount for a margin ID
|
|
func (h *HUOBI) MarginRepayment(orderID int64, amount float64) (int64, error) {
|
|
data := struct {
|
|
Amount string `json:"amount"`
|
|
}{
|
|
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
MarginOrderID int64 `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
endpoint := fmt.Sprintf(huobiMarginRepay, strconv.FormatInt(orderID, 10))
|
|
err := h.SendAuthenticatedHTTPRequest("POST", endpoint, nil, data, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.MarginOrderID, err
|
|
}
|
|
|
|
// GetMarginLoanOrders returns the margin loan orders
|
|
func (h *HUOBI) GetMarginLoanOrders(symbol, currency, start, end, states, from, direct, size string) ([]MarginOrder, error) {
|
|
vals := url.Values{}
|
|
vals.Set("symbol", symbol)
|
|
vals.Set("currency", currency)
|
|
|
|
if start != "" {
|
|
vals.Set("start-date", start)
|
|
}
|
|
|
|
if end != "" {
|
|
vals.Set("end-date", end)
|
|
}
|
|
|
|
if states != "" {
|
|
vals.Set("states", states)
|
|
}
|
|
|
|
if from != "" {
|
|
vals.Set("from", from)
|
|
}
|
|
|
|
if direct != "" {
|
|
vals.Set("direct", direct)
|
|
}
|
|
|
|
if size != "" {
|
|
vals.Set("size", size)
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
MarginLoanOrders []MarginOrder `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("GET", huobiMarginLoanOrders, vals, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.MarginLoanOrders, err
|
|
}
|
|
|
|
// GetMarginAccountBalance returns the margin account balances
|
|
func (h *HUOBI) GetMarginAccountBalance(symbol string) ([]MarginAccountBalance, error) {
|
|
type response struct {
|
|
Response
|
|
Balances []MarginAccountBalance `json:"data"`
|
|
}
|
|
|
|
vals := url.Values{}
|
|
if symbol != "" {
|
|
vals.Set("symbol", symbol)
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("GET", huobiMarginAccountBalance, vals, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.Balances, err
|
|
}
|
|
|
|
// Withdraw withdraws the desired amount and currency
|
|
func (h *HUOBI) Withdraw(address, currency, addrTag string, amount, fee float64) (int64, error) {
|
|
type response struct {
|
|
Response
|
|
WithdrawID int64 `json:"data"`
|
|
}
|
|
|
|
data := struct {
|
|
Address string `json:"address"`
|
|
Amount string `json:"amount"`
|
|
Currency string `json:"currency"`
|
|
Fee string `json:"fee"`
|
|
AddrTag string `json:"addr-tag"`
|
|
}{
|
|
Address: address,
|
|
Currency: currency,
|
|
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
|
|
}
|
|
|
|
if fee != 0 {
|
|
data.Fee = strconv.FormatFloat(fee, 'f', -1, 64)
|
|
}
|
|
|
|
if currency == "XRP" {
|
|
data.AddrTag = addrTag
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest("POST", huobiWithdrawCreate, nil, data, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.WithdrawID, err
|
|
}
|
|
|
|
// CancelWithdraw cancels a withdraw request
|
|
func (h *HUOBI) CancelWithdraw(withdrawID int64) (int64, error) {
|
|
type response struct {
|
|
Response
|
|
WithdrawID int64 `json:"data"`
|
|
}
|
|
|
|
vals := url.Values{}
|
|
vals.Set("withdraw-id", strconv.FormatInt(withdrawID, 10))
|
|
|
|
var result response
|
|
endpoint := fmt.Sprintf(huobiWithdrawCancel, strconv.FormatInt(withdrawID, 10))
|
|
err := h.SendAuthenticatedHTTPRequest("POST", endpoint, vals, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return 0, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.WithdrawID, err
|
|
}
|
|
|
|
// SendHTTPRequest sends an unauthenticated HTTP request
|
|
func (h *HUOBI) SendHTTPRequest(path string, result interface{}) error {
|
|
return h.SendPayload("GET", path, nil, nil, result, false, h.Verbose)
|
|
}
|
|
|
|
// SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API
|
|
func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, data interface{}, result interface{}) error {
|
|
if !h.AuthenticatedAPISupport {
|
|
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name)
|
|
}
|
|
|
|
if values == nil {
|
|
values = url.Values{}
|
|
}
|
|
|
|
values.Set("AccessKeyId", h.APIKey)
|
|
values.Set("SignatureMethod", "HmacSHA256")
|
|
values.Set("SignatureVersion", "2")
|
|
values.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05"))
|
|
|
|
endpoint = fmt.Sprintf("/v%s/%s", huobiAPIVersion, endpoint)
|
|
payload := fmt.Sprintf("%s\napi.huobi.pro\n%s\n%s",
|
|
method, endpoint, values.Encode())
|
|
|
|
headers := make(map[string]string)
|
|
|
|
if method == http.MethodGet {
|
|
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
} else {
|
|
headers["Content-Type"] = "application/json"
|
|
}
|
|
|
|
hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret))
|
|
signature := common.Base64Encode(hmac)
|
|
values.Set("Signature", signature)
|
|
|
|
if h.APIAuthPEMKeySupport == true {
|
|
pemKey := strings.NewReader(h.APIAuthPEMKey)
|
|
pemBytes, err := ioutil.ReadAll(pemKey)
|
|
if err != nil {
|
|
return fmt.Errorf("Huobi unable to ioutil.ReadAll PEM key: %s", err)
|
|
}
|
|
|
|
block, _ := pem.Decode(pemBytes)
|
|
if block == nil {
|
|
return fmt.Errorf("Huobi block is nil")
|
|
}
|
|
|
|
x509Encoded := block.Bytes
|
|
privKey, err := x509.ParseECPrivateKey(x509Encoded)
|
|
if err != nil {
|
|
return fmt.Errorf("Huobi unable to ParseECPrivKey: %s", err)
|
|
}
|
|
|
|
r, s, err := ecdsa.Sign(rand.Reader, privKey, common.GetSHA256([]byte(signature)))
|
|
if err != nil {
|
|
return fmt.Errorf("Huobi unable to sign: %s", err)
|
|
}
|
|
|
|
privSig := r.Bytes()
|
|
privSig = append(privSig, s.Bytes()...)
|
|
values.Set("PrivateSignature", common.Base64Encode(privSig))
|
|
}
|
|
|
|
url := fmt.Sprintf("%s%s", h.APIUrl, endpoint)
|
|
url = common.EncodeURLValues(url, values)
|
|
|
|
var body []byte
|
|
|
|
if data != nil {
|
|
encoded, err := json.Marshal(data)
|
|
if err != nil {
|
|
return fmt.Errorf("Huobi unable to marshal data: %s", err)
|
|
}
|
|
|
|
body = encoded
|
|
}
|
|
|
|
return h.SendPayload(method, url, headers, bytes.NewReader(body), result, true, h.Verbose)
|
|
}
|