Files
gocryptotrader/exchanges/huobi/huobi.go
Scott b1e6534e7c Withdraw Crypto wrapper mapping (#226)
* Initial commit

* Updates signature for all withdrawal methods to use new withdrawRequest struct type

* Implements crypto withdraw features & tests for Alphapoint, ANX, Binance, Bitfinex, Bitflyer, Bithumb, Bitmex, Bitstamp, Bittrex, BTCC, BTCmarkets, CoinbasePro, Coinut. Updates WithdrawRequest type with more members. Breaking change to update real order testing for increased code coverage

* Updates all realOrder tests to run when no API key is present. Updates exchange functions to handle errors better

* Implements crypto withdrawals for Exmo, GateIO, Gemini, HitBTC, Huobi, HuobiHadax, Kraken, LakeBTC, Liqui, Localbitcoins, OKCoin, OKEX, Poloniex, Wex, Yobit and ZB. Updates real order test formatting for all real order tests

* Update alphapoint. Fixes anx typos. Adds function WithdrawFiatFundsToInternationalBank to exchange wrapper interface. Adds WithdrawFiatFundsToInternationalBank to alphapoint, bitmex, coinbasepro. Updates Kraken to use TradePassword property

* Reverts alphapoint to use ErrNotYetImplemented

* Fixes line spacing and removes unnecessary line
2019-01-03 13:15:07 +11:00

926 lines
24 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/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/symbol"
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"
huobiBatchCancelOpenOrders = "order/orders/batchCancelOpenOrders"
huobiGetOrder = "order/orders/%s"
huobiGetOrderMatch = "order/orders/%s/matchresults"
huobiGetOrders = "order/orders"
huobiGetOpenOrders = "order/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"
huobiAuthRate = 100
huobiUnauthRate = 100
)
// HUOBI is the overarching type across this package
type HUOBI struct {
exchange.Base
AccountID string
WebsocketConn *websocket.Conn
}
// SetDefaults sets default values for the exchange
func (h *HUOBI) SetDefaults() {
h.Name = "Huobi"
h.Enabled = false
h.Fee = 0
h.Verbose = false
h.RESTPollingDelay = 10
h.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithSetup
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
h.WebsocketInit()
}
// 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.SetEnabled(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)
}
err = h.SetClientProxyAddress(exch.ProxyAddress)
if err != nil {
log.Fatal(err)
}
err = h.WebsocketSetup(h.WsConnect,
exch.Name,
exch.Websocket,
huobiSocketIOAddress,
exch.WebsocketURL)
if err != nil {
log.Fatal(err)
}
}
}
// 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)
v := url.Values{}
v.Set("account-id", accountID)
err := h.SendAuthenticatedHTTPRequest("GET", endpoint, v, 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
}
// CancelExistingOrder cancels an order on Huobi
func (h *HUOBI) CancelExistingOrder(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
}
// 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("POST", huobiBatchCancelOpenOrders, url.Values{}, data, &result)
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) {
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
}
// GetOpenOrders returns a list of orders
func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int) ([]OrderInfo, error) {
type response struct {
Response
Orders []OrderInfo `json:"data"`
}
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("accountID", accountID)
vals.Set("side", side)
vals.Set("size", fmt.Sprintf("%v", size))
var result response
err := h.SendAuthenticatedHTTPRequest("GET", huobiGetOpenOrders, 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,omitempty"`
AddrTag string `json:"addr-tag,omitempty"`
}{
Address: address,
Currency: currency,
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
}
if fee > 0 {
data.Fee = strconv.FormatFloat(fee, 'f', -1, 64)
}
if currency == symbol.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)
}
// GetFee returns an estimate of fee based on type of transaction
func (h *HUOBI) GetFee(feeBuilder exchange.FeeBuilder) (float64, error) {
var fee float64
switch feeBuilder.FeeType {
case exchange.CryptocurrencyTradeFee:
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
}
if fee < 0 {
fee = 0
}
return fee, nil
}
func calculateTradingFee(purchasePrice, amount float64) float64 {
feePercent := 0.002
return feePercent * purchasePrice * amount
}