mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-23 15:10:15 +00:00
* GCTCLI param and linter fixes * Linter fixes * Add more basic validation and address codelingo nits * Add arg number support to cancelOrder and more validity checks
908 lines
23 KiB
Go
908 lines
23 KiB
Go
package huobi
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"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/websocket/wshandler"
|
|
)
|
|
|
|
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"
|
|
huobiMarketTickers = "market/tickers"
|
|
huobiMarketTradeHistory = "market/history/trade"
|
|
huobiSymbols = "common/symbols"
|
|
huobiCurrencies = "common/currencys"
|
|
huobiTimestamp = "common/timestamp"
|
|
huobiAccounts = "account/accounts"
|
|
huobiAccountBalance = "account/accounts/%s/balance"
|
|
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"
|
|
|
|
huobiAuthRate = 100
|
|
huobiUnauthRate = 100
|
|
)
|
|
|
|
// HUOBI is the overarching type across this package
|
|
type HUOBI struct {
|
|
exchange.Base
|
|
AccountID string
|
|
WebsocketConn *wshandler.WebsocketConnection
|
|
AuthenticatedWebsocketConn *wshandler.WebsocketConnection
|
|
}
|
|
|
|
// GetHistoricCandles returns rangesize number of candles for the given granularity and pair starting from the latest available
|
|
func (h *HUOBI) GetHistoricCandles(pair currency.Pair, rangesize, granularity int64) ([]exchange.Candle, error) {
|
|
return nil, common.ErrNotYetImplemented
|
|
}
|
|
|
|
// 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
|
|
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) {
|
|
type response struct {
|
|
Response
|
|
AccountData []Account `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, 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(http.MethodGet, endpoint, v, nil, &result)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
return result.AccountBalanceData.AccountBalanceDetails, err
|
|
}
|
|
|
|
// GetAggregatedBalance returns the balances of all the sub-account aggregated.
|
|
func (h *HUOBI) GetAggregatedBalance() ([]AggregatedBalance, error) {
|
|
type response struct {
|
|
Response
|
|
AggregatedBalances []AggregatedBalance `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
|
|
err := h.SendAuthenticatedHTTPRequest(
|
|
http.MethodGet,
|
|
huobiAggregatedBalance,
|
|
nil,
|
|
nil,
|
|
&result,
|
|
)
|
|
|
|
if result.ErrorMessage != "" {
|
|
return nil, errors.New(result.ErrorMessage)
|
|
}
|
|
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
|
|
}
|
|
|
|
type response struct {
|
|
Response
|
|
OrderID int64 `json:"data,string"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, 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(http.MethodPost, 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(_ []int64) ([]CancelOrderBatch, error) {
|
|
type response struct {
|
|
Response
|
|
Data []CancelOrderBatch `json:"data"`
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, 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(http.MethodPost, 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
|
|
urlVal := url.Values{}
|
|
urlVal.Set("clientOrderId", strconv.FormatInt(orderID, 10))
|
|
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrder, urlVal, 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(http.MethodGet, 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(http.MethodGet, 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 int64) ([]OrderInfo, error) {
|
|
type response struct {
|
|
Response
|
|
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))
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, 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(http.MethodGet, 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(http.MethodPost, 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(http.MethodPost, 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(http.MethodPost, 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(http.MethodGet, 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(http.MethodGet, 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(c currency.Code, address, 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: 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
|
|
}
|
|
|
|
var result response
|
|
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, 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(http.MethodPost, 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(http.MethodGet,
|
|
path,
|
|
nil,
|
|
nil,
|
|
result,
|
|
false,
|
|
false,
|
|
h.Verbose,
|
|
h.HTTPDebugging,
|
|
h.HTTPRecording)
|
|
}
|
|
|
|
// SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API
|
|
func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, data, result interface{}) error {
|
|
if !h.AllowAuthenticatedRequest() {
|
|
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name)
|
|
}
|
|
|
|
if values == nil {
|
|
values = url.Values{}
|
|
}
|
|
|
|
values.Set("AccessKeyId", h.API.Credentials.Key)
|
|
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 := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(h.API.Credentials.Secret))
|
|
signature := crypto.Base64Encode(hmac)
|
|
values.Set("Signature", signature)
|
|
|
|
if h.API.Credentials.PEMKey != "" && h.API.PEMKeySupport {
|
|
pemKey := strings.NewReader(h.API.Credentials.PEMKey)
|
|
pemBytes, err := ioutil.ReadAll(pemKey)
|
|
if err != nil {
|
|
return fmt.Errorf("%s unable to ioutil.ReadAll PEM key: %s", h.Name, err)
|
|
}
|
|
|
|
block, _ := pem.Decode(pemBytes)
|
|
if block == nil {
|
|
return fmt.Errorf("%s PEM block is nil", h.Name)
|
|
}
|
|
|
|
x509Encoded := block.Bytes
|
|
privKey, err := x509.ParseECPrivateKey(x509Encoded)
|
|
if err != nil {
|
|
return fmt.Errorf("%s unable to ParseECPrivKey: %s", h.Name, err)
|
|
}
|
|
|
|
r, s, err := ecdsa.Sign(rand.Reader, privKey, crypto.GetSHA256([]byte(signature)))
|
|
if err != nil {
|
|
return fmt.Errorf("%s unable to sign: %s", h.Name, err)
|
|
}
|
|
|
|
privSig := r.Bytes()
|
|
privSig = append(privSig, s.Bytes()...)
|
|
values.Set("PrivateSignature", crypto.Base64Encode(privSig))
|
|
}
|
|
|
|
urlPath := h.API.Endpoints.URL + common.EncodeURLValues(endpoint, values)
|
|
|
|
var body []byte
|
|
|
|
if data != nil {
|
|
encoded, err := json.Marshal(data)
|
|
if err != nil {
|
|
return fmt.Errorf("%s unable to marshal data: %s", h.Name, err)
|
|
}
|
|
|
|
body = encoded
|
|
}
|
|
|
|
return h.SendPayload(method,
|
|
urlPath,
|
|
headers,
|
|
bytes.NewReader(body),
|
|
result,
|
|
true,
|
|
false,
|
|
h.Verbose,
|
|
h.HTTPDebugging,
|
|
h.HTTPRecording)
|
|
}
|
|
|
|
// 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
|
|
}
|