Files
gocryptotrader/exchanges/huobi/huobi.go
Andrew 4a736fb335 (Exchange) Add GetHistoricCandles() & GetHistoricCandlesEx() support to exchanges (#479)
* implemented binance and bitfinex GetHistoricCandles wrapper methods)

* coinbene supported added

* after and before clean up

* gateio wrapper completed

* merged upstream/master

* Added bsaic KlineIntervalSupported() method

* Converted binance fixed test

* WIP

* new KlineConvertToExchangeStandardString method added

* end of day WIP

* WIP

* end of day WIP started migration of trade history

* added kline support to hitbtc huobi lbank

* added exchangehistory to all supported exchanges started work on coinbase 300 candles/request method

* end of day WIP

* removed unused ta and misc changes to flag ready for review

* yobit cleanup

* revert coinbase changES

* general code clean up and added zb support

* poloniex support added

* renamed method to FormatExchangeKlineInterval other misc fixes

* linter fixes

* linter fixes

* removed verbose

* fixed poloniex test coverage

* revert poloniex mock data

* regenerated poloniex mock data

* a very verbose clean up

* binance mock clean up

* removed unneeded t.Log()

* setting verbose to true to debug CI issue

* first pass changes addressed

* common.ErrNotYetImplemented implemented :D

* comments added

* WIP-addressed exchange requests and reverted previous GetExchangeHistory changes

* WIP-addressed exchange requests and reverted previous GetExchangeHistory changes

* increased test coverage added kraken support

* OKGroup support completed started work on address GetExchangeHistory feedback and migrating to own PR under https://github.com/xtda/gocryptotrader/tree/exchange_history

* convert zb ratelimits

* gofmt run on okcoin

* increased delay on rate limit

* gofmt package

* fixed panic with coinbene and bithumb if conversion fails

* very broken end of day WIP

* added support for GetHistoricCandlesEx to coinbase and binance

* gofmt package

* coinbase, btcmarkets, zb ex wrapper function added

* added all exchange support for ex regenerated mock data

* update bithumb to return wrapper method

* gofmt package

* end of day started work on changes

* reworked test coverage added okgroup support general fixes/change requests addressed

* Added OneMonth

* limit checks on supportedexchanges

* reverted getexchangehistory

* reworked binance tesT

* added workaround for kraken panic

* renamed command to extended removed interval check on non-implemented commands

* added wrapperconfig back

* increased test coverage for FormatExchangeKlineInterval

* WIP

* increased test coverage for FormatExchangeKlineInterval bitfinex/gateio/huobi

* linter fixes

* zb kraken lbank coinbene btcmarkets support added

* removed verbose

* OK group support for other asset types added

* swapped margin to use spot endpoint

* index support added test coverage added for asset types

* added asset type to okcoin test

* gofmt

* add asset to extended method

* removed verbose

* add support for coinbene swap increase test coverage

* removed verbose

* small clean up of okgroup wrapper functions

* verbose to troubleshoot CI issues

* removed verbose

* added error check reverted coinbasechanges

* readme updated

* removed unused start/finish started work on decoupling api requests from kline package

* restructured coinbene, bithumb methods, added bitstamp support

* kraken time fix

* BTCMarkets restructure

* typo fix

* removed test for futures due to contact changing

* added start/end date to extended method over range

* converted to assettranslator

* removed verbose

* removed invalid char

* reverted incorrectly removed return

* added import

* further template updates

* macos hates my keyboard :D

* misc canges

* x -> i

* removed verbose

* updated fixCasing to allocate var before checks

* removed time conversion

* sort all outgoing kline candles

* fixCasing fix

* after/before checks added

* added parallel to test

* logic check on BTCmarkets

* removed unused param, used correct iterator

* converted HitBTC to use time.Time

* add iszero false check to candle times

* updated resultlimit to 5000

* new line added

* added comment to exported const

* use configured ratelimit

* fixed pair for test

* panic fixed WIP on fixCasing

* fixCasing rework, started work on readme docs

* enable rate limiter for wrapper issues tool

* docs updated

* removed err from return and formatted currency

* updated Yobit supported status

* Updated HitBTC to use onehour candles due to test exeuction times

* added further details to gctcli output

* added link to docs

* added link to tempalte

* disable FTX websocket in config_example

* fix poloneix

* regenerated poloniex mock data

* removed recording flag
2020-07-08 10:51:54 +10:00

826 lines
23 KiB
Go

package huobi
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
const (
huobiAPIURL = "https://api.huobi.pro"
huobiAPIVersion = "1"
huobiAPIVersion2 = "2"
huobiMarketHistoryKline = "market/history/kline"
huobiMarketDetail = "market/detail"
huobiMarketDetailMerged = "market/detail/merged"
huobiMarketDepth = "market/depth"
huobiMarketTrade = "market/trade"
huobiMarketTickers = "market/tickers"
huobiMarketTradeHistory = "market/history/trade"
huobiSymbols = "common/symbols"
huobiCurrencies = "common/currencys"
huobiTimestamp = "common/timestamp"
huobiAccounts = "account/accounts"
huobiAccountBalance = "account/accounts/%s/balance"
huobiAccountDepositAddress = "account/deposit/address"
huobiAccountWithdrawQuota = "account/withdraw/quota"
huobiAggregatedBalance = "subuser/aggregate-balance"
huobiOrderPlace = "order/orders/place"
huobiOrderCancel = "order/orders/%s/submitcancel"
huobiOrderCancelBatch = "order/orders/batchcancel"
huobiBatchCancelOpenOrders = "order/orders/batchCancelOpenOrders"
huobiGetOrder = "order/orders/getClientOrder"
huobiGetOrderMatch = "order/orders/%s/matchresults"
huobiGetOrders = "order/orders"
huobiGetOpenOrders = "order/openOrders"
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"
huobiStatusError = "error"
)
// HUOBI is the overarching type across this package
type HUOBI struct {
exchange.Base
AccountID string
WebsocketConn *wshandler.WebsocketConnection
AuthenticatedWebsocketConn *wshandler.WebsocketConnection
}
// 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", arg.Period)
if arg.Size != 0 {
vals.Set("size", strconv.Itoa(arg.Size))
}
type response struct {
Response
Data []KlineItem `json:"data"`
}
var result response
urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketHistoryKline)
err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Data, err
}
// GetTickers returns the ticker for the specified symbol
func (h *HUOBI) GetTickers() (Tickers, error) {
var result Tickers
urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketTickers)
return result, h.SendHTTPRequest(urlPath, &result)
}
// 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
urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketDetailMerged)
err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, 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
urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketDepth)
err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, 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
urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketTrade)
err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, 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
urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketTradeHistory)
err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, 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
urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketDetail)
err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, 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
urlPath := fmt.Sprintf("%s/v%s/%s", h.API.Endpoints.URL, huobiAPIVersion, huobiSymbols)
err := h.SendHTTPRequest(urlPath, &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
urlPath := fmt.Sprintf("%s/v%s/%s", h.API.Endpoints.URL, huobiAPIVersion, huobiCurrencies)
err := h.SendHTTPRequest(urlPath, &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
urlPath := fmt.Sprintf("%s/v%s/%s", h.API.Endpoints.URL, huobiAPIVersion, huobiTimestamp)
err := h.SendHTTPRequest(urlPath, &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) {
result := struct {
Accounts []Account `json:"data"`
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiAccounts, url.Values{}, nil, &result, false)
return result.Accounts, err
}
// GetAccountBalance returns the users Huobi account balance
func (h *HUOBI) GetAccountBalance(accountID string) ([]AccountBalanceDetail, error) {
result := struct {
AccountBalanceData AccountBalance `json:"data"`
}{}
endpoint := fmt.Sprintf(huobiAccountBalance, accountID)
v := url.Values{}
v.Set("account-id", accountID)
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, v, nil, &result, false)
return result.AccountBalanceData.AccountBalanceDetails, err
}
// GetAggregatedBalance returns the balances of all the sub-account aggregated.
func (h *HUOBI) GetAggregatedBalance() ([]AggregatedBalance, error) {
result := struct {
AggregatedBalances []AggregatedBalance `json:"data"`
}{}
err := h.SendAuthenticatedHTTPRequest(
http.MethodGet,
huobiAggregatedBalance,
nil,
nil,
&result,
false,
)
return result.AggregatedBalances, 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
}
result := struct {
OrderID int64 `json:"data,string"`
}{}
err := h.SendAuthenticatedHTTPRequest(
http.MethodPost,
huobiOrderPlace,
nil,
data,
&result,
false,
)
return result.OrderID, err
}
// CancelExistingOrder cancels an order on Huobi
func (h *HUOBI) CancelExistingOrder(orderID int64) (int64, error) {
resp := struct {
OrderID int64 `json:"data,string"`
}{}
endpoint := fmt.Sprintf(huobiOrderCancel, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, url.Values{}, nil, &resp, false)
return resp.OrderID, err
}
// CancelOrderBatch cancels a batch of orders -- to-do
func (h *HUOBI) CancelOrderBatch(_ []int64) ([]CancelOrderBatch, error) {
type response struct {
Response
Data []CancelOrderBatch `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiOrderCancelBatch, url.Values{}, nil, &result, false)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Data, err
}
// CancelOpenOrdersBatch cancels a batch of orders -- to-do
func (h *HUOBI) CancelOpenOrdersBatch(accountID, symbol string) (CancelOpenOrdersBatch, error) {
params := url.Values{}
params.Set("account-id", accountID)
var result CancelOpenOrdersBatch
data := struct {
AccountID string `json:"account-id"`
Symbol string `json:"symbol"`
}{
AccountID: accountID,
Symbol: symbol,
}
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiBatchCancelOpenOrders, url.Values{}, data, &result, false)
if result.Data.FailedCount > 0 {
return result, fmt.Errorf("there were %v failed order cancellations", result.Data.FailedCount)
}
return result, err
}
// GetOrder returns order information for the specified order
func (h *HUOBI) GetOrder(orderID int64) (OrderInfo, error) {
resp := struct {
Order OrderInfo `json:"data"`
}{}
urlVal := url.Values{}
urlVal.Set("clientOrderId", strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodGet,
huobiGetOrder,
urlVal,
nil,
&resp,
false)
return resp.Order, err
}
// GetOrderMatchResults returns matched order info for the specified order
func (h *HUOBI) GetOrderMatchResults(orderID int64) ([]OrderMatchInfo, error) {
resp := struct {
Orders []OrderMatchInfo `json:"data"`
}{}
endpoint := fmt.Sprintf(huobiGetOrderMatch, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, url.Values{}, nil, &resp, false)
return resp.Orders, err
}
// GetOrders returns a list of orders
func (h *HUOBI) GetOrders(symbol, types, start, end, states, from, direct, size string) ([]OrderInfo, error) {
resp := struct {
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)
}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrders, vals, nil, &resp, false)
return resp.Orders, err
}
// GetOpenOrders returns a list of orders
func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int64) ([]OrderInfo, error) {
resp := struct {
Orders []OrderInfo `json:"data"`
}{}
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("accountID", accountID)
if len(side) > 0 {
vals.Set("side", side)
}
vals.Set("size", strconv.FormatInt(size, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOpenOrders, vals, nil, &resp, false)
return resp.Orders, err
}
// GetOrdersMatch returns a list of matched orders
func (h *HUOBI) GetOrdersMatch(symbol, types, start, end, from, direct, size string) ([]OrderMatchInfo, error) {
resp := struct {
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)
}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrdersMatch, vals, nil, &resp, false)
return resp.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
}
resp := struct {
TransferID int64 `json:"data"`
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, path, nil, data, &resp, false)
return resp.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),
}
resp := struct {
MarginOrderID int64 `json:"data"`
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiMarginOrders, nil, data, &resp, false)
return resp.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),
}
resp := struct {
MarginOrderID int64 `json:"data"`
}{}
endpoint := fmt.Sprintf(huobiMarginRepay, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, nil, data, &resp, false)
return resp.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)
}
resp := struct {
MarginLoanOrders []MarginOrder `json:"data"`
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiMarginLoanOrders, vals, nil, &resp, false)
return resp.MarginLoanOrders, err
}
// GetMarginAccountBalance returns the margin account balances
func (h *HUOBI) GetMarginAccountBalance(symbol string) ([]MarginAccountBalance, error) {
resp := struct {
Balances []MarginAccountBalance `json:"data"`
}{}
vals := url.Values{}
if symbol != "" {
vals.Set("symbol", symbol)
}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiMarginAccountBalance, vals, nil, &resp, false)
return resp.Balances, err
}
// Withdraw withdraws the desired amount and currency
func (h *HUOBI) Withdraw(c currency.Code, address, addrTag string, amount, fee float64) (int64, error) {
resp := struct {
WithdrawID int64 `json:"data"`
}{}
data := struct {
Address string `json:"address"`
Amount string `json:"amount"`
Currency string `json:"currency"`
Fee string `json:"fee,omitempty"`
AddrTag string `json:"addr-tag,omitempty"`
}{
Address: address,
Currency: c.Lower().String(),
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
}
if fee > 0 {
data.Fee = strconv.FormatFloat(fee, 'f', -1, 64)
}
if c == currency.XRP {
data.AddrTag = addrTag
}
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiWithdrawCreate, nil, data, &resp.WithdrawID, false)
return resp.WithdrawID, err
}
// CancelWithdraw cancels a withdraw request
func (h *HUOBI) CancelWithdraw(withdrawID int64) (int64, error) {
resp := struct {
WithdrawID int64 `json:"data"`
}{}
vals := url.Values{}
vals.Set("withdraw-id", strconv.FormatInt(withdrawID, 10))
endpoint := fmt.Sprintf(huobiWithdrawCancel, strconv.FormatInt(withdrawID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, vals, nil, &resp, false)
return resp.WithdrawID, err
}
// QueryDepositAddress returns the deposit address for a specified currency
func (h *HUOBI) QueryDepositAddress(cryptocurrency string) (DepositAddress, error) {
resp := struct {
DepositAddress []DepositAddress `json:"data"`
}{}
vals := url.Values{}
vals.Set("currency", cryptocurrency)
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiAccountDepositAddress, vals, nil, &resp, true)
if err != nil {
return DepositAddress{}, err
}
if len(resp.DepositAddress) == 0 {
return DepositAddress{}, errors.New("deposit address data isn't populated")
}
return resp.DepositAddress[0], nil
}
// QueryWithdrawQuotas returns the users cryptocurrency withdraw quotas
func (h *HUOBI) QueryWithdrawQuotas(cryptocurrency string) (WithdrawQuota, error) {
resp := struct {
WithdrawQuota WithdrawQuota `json:"data"`
}{}
vals := url.Values{}
vals.Set("currency", cryptocurrency)
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiAccountWithdrawQuota, vals, nil, &resp, true)
if err != nil {
return WithdrawQuota{}, err
}
return resp.WithdrawQuota, nil
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (h *HUOBI) SendHTTPRequest(path string, result interface{}) error {
return h.SendPayload(context.Background(), &request.Item{
Method: http.MethodGet,
Path: path,
Result: result,
Verbose: h.Verbose,
HTTPDebugging: h.HTTPDebugging,
HTTPRecording: h.HTTPRecording,
})
}
// SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API
func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, data, result interface{}, isVersion2API bool) error {
if !h.AllowAuthenticatedRequest() {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name)
}
if values == nil {
values = url.Values{}
}
now := time.Now()
values.Set("AccessKeyId", h.API.Credentials.Key)
values.Set("SignatureMethod", "HmacSHA256")
values.Set("SignatureVersion", "2")
values.Set("Timestamp", now.UTC().Format("2006-01-02T15:04:05"))
if isVersion2API {
endpoint = fmt.Sprintf("/v%s/%s", huobiAPIVersion2, endpoint)
} else {
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 := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(h.API.Credentials.Secret))
values.Set("Signature", crypto.Base64Encode(hmac))
urlPath := h.API.Endpoints.URL + common.EncodeURLValues(endpoint, values)
var body []byte
if data != nil {
encoded, err := json.Marshal(data)
if err != nil {
return err
}
body = encoded
}
// Time difference between your timestamp and standard should be less than 1 minute.
ctx, cancel := context.WithDeadline(context.Background(), now.Add(time.Minute))
defer cancel()
interim := json.RawMessage{}
err := h.SendPayload(ctx, &request.Item{
Method: method,
Path: urlPath,
Headers: headers,
Body: bytes.NewReader(body),
Result: &interim,
AuthRequest: true,
Verbose: h.Verbose,
HTTPDebugging: h.HTTPDebugging,
HTTPRecording: h.HTTPRecording,
})
if err != nil {
return err
}
if isVersion2API {
var errCap ResponseV2
if err = json.Unmarshal(interim, &errCap); err == nil {
if errCap.Code != 200 && errCap.Message != "" {
return errors.New(errCap.Message)
}
}
} else {
var errCap Response
if err = json.Unmarshal(interim, &errCap); err == nil {
if errCap.Status == huobiStatusError && errCap.ErrorMessage != "" {
return errors.New(errCap.ErrorMessage)
}
}
}
return json.Unmarshal(interim, result)
}
// GetFee returns an estimate of fee based on type of transaction
func (h *HUOBI) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
var fee float64
if feeBuilder.FeeType == exchange.OfflineTradeFee || feeBuilder.FeeType == exchange.CryptocurrencyTradeFee {
fee = calculateTradingFee(feeBuilder.Pair, feeBuilder.PurchasePrice, feeBuilder.Amount)
}
if fee < 0 {
fee = 0
}
return fee, nil
}
func calculateTradingFee(c currency.Pair, price, amount float64) float64 {
if c.IsCryptoFiatPair() {
return 0.001 * price * amount
}
return 0.002 * price * amount
}