exchanges/engine: Add multichain deposit/withdrawal support (#794)

* Add exchange multichain support

* Start tidying up

* Add multichain transfer support for Bitfinex and fix poloniex bug

* Add Coinbene multichain support

* Start adjusting the deposit address manager

* Fix deposit tests and further enhancements

* Cleanup

* Add bypass flag, expand tests plus error coverage for Huobi

Adjust helpers

* Address nitterinos

* BFX wd changes

* Address nitterinos

* Minor fixes rebasing on master

* Fix BFX acceptableMethods test

* Add some TO-DOs for 2 tests WRT races

* Fix acceptableMethods test round 2

* Address nitterinos
This commit is contained in:
Adrian Gallagher
2021-10-15 15:55:38 +11:00
committed by GitHub
parent b093a7df19
commit 0c00b7e1df
145 changed files with 46329 additions and 5507 deletions

View File

@@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
@@ -21,47 +22,49 @@ import (
const (
huobiAPIURL = "https://api.huobi.pro"
huobiURL = "https://api.hbdm.com/"
huobiFuturesURL = "https://api.hbdm.com"
huobiURL = "https://api.hbdm.com"
huobiFuturesURL = huobiURL
huobiAPIVersion = "1"
huobiAPIVersion2 = "2"
// Spot endpoints
huobiMarketHistoryKline = "market/history/kline"
huobiMarketDetail = "market/detail"
huobiMarketDetailMerged = "market/detail/merged"
huobi24HrMarketSummary = "/market/detail?"
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"
huobiMarginRates = "margin/loan-info"
huobiMarketHistoryKline = "/market/history/kline"
huobiMarketDetail = "/market/detail"
huobiMarketDetailMerged = "/market/detail/merged"
huobi24HrMarketSummary = "/market/detail?"
huobiMarketDepth = "/market/depth"
huobiMarketTrade = "/market/trade"
huobiMarketTickers = "/market/tickers"
huobiMarketTradeHistory = "/market/history/trade"
huobiSymbols = "/v1/common/symbols"
huobiCurrencies = "/v1/common/currencys"
huobiTimestamp = "/common/timestamp"
huobiAccounts = "/account/accounts"
huobiAccountBalance = "/account/accounts/%s/balance"
huobiAccountDepositAddress = "/account/deposit/address"
huobiAccountWithdrawQuota = "/account/withdraw/quota"
huobiAccountQueryWithdrawAddress = "/account/withdraw/"
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"
huobiMarginRates = "/margin/loan-info"
huobiCurrenciesReference = "/v2/reference/currencies"
)
// HUOBI is the overarching type across this package
@@ -106,7 +109,7 @@ func (h *HUOBI) GetSpotKline(ctx context.Context, arg KlinesRequestParams) ([]Kl
var result response
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("/"+huobiMarketHistoryKline, vals), &result)
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(huobiMarketHistoryKline, vals), &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
@@ -128,7 +131,7 @@ func (h *HUOBI) Get24HrMarketSummary(ctx context.Context, symbol currency.Pair)
// GetTickers returns the ticker for the specified symbol
func (h *HUOBI) GetTickers(ctx context.Context) (Tickers, error) {
var result Tickers
return result, h.SendHTTPRequest(ctx, exchange.RestSpot, "/"+huobiMarketTickers, &result)
return result, h.SendHTTPRequest(ctx, exchange.RestSpot, huobiMarketTickers, &result)
}
// GetMarketDetailMerged returns the ticker for the specified symbol
@@ -147,7 +150,7 @@ func (h *HUOBI) GetMarketDetailMerged(ctx context.Context, symbol currency.Pair)
var result response
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("/"+huobiMarketDetailMerged, vals), &result)
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(huobiMarketDetailMerged, vals), &result)
if result.ErrorMessage != "" {
return result.Tick, errors.New(result.ErrorMessage)
}
@@ -174,7 +177,7 @@ func (h *HUOBI) GetDepth(ctx context.Context, obd OrderBookDataRequestParams) (O
var result response
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("/"+huobiMarketDepth, vals), &result)
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(huobiMarketDepth, vals), &result)
if result.ErrorMessage != "" {
return result.Depth, errors.New(result.ErrorMessage)
}
@@ -199,7 +202,7 @@ func (h *HUOBI) GetTrades(ctx context.Context, symbol currency.Pair) ([]Trade, e
var result response
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("/"+huobiMarketTrade, vals), &result)
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(huobiMarketTrade, vals), &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
@@ -242,7 +245,7 @@ func (h *HUOBI) GetTradeHistory(ctx context.Context, symbol currency.Pair, size
var result response
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("/"+huobiMarketTradeHistory, vals), &result)
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(huobiMarketTradeHistory, vals), &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
@@ -265,7 +268,7 @@ func (h *HUOBI) GetMarketDetail(ctx context.Context, symbol currency.Pair) (Deta
var result response
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("/"+huobiMarketDetail, vals), &result)
err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(huobiMarketDetail, vals), &result)
if result.ErrorMessage != "" {
return result.Tick, errors.New(result.ErrorMessage)
}
@@ -281,7 +284,7 @@ func (h *HUOBI) GetSymbols(ctx context.Context) ([]Symbol, error) {
var result response
err := h.SendHTTPRequest(ctx, exchange.RestSpot, "/v"+huobiAPIVersion+"/"+huobiSymbols, &result)
err := h.SendHTTPRequest(ctx, exchange.RestSpot, huobiSymbols, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
@@ -296,16 +299,32 @@ func (h *HUOBI) GetCurrencies(ctx context.Context) ([]string, error) {
}
var result response
err := h.SendHTTPRequest(ctx,
exchange.RestSpot,
"/v"+huobiAPIVersion+"/"+huobiCurrencies,
&result)
err := h.SendHTTPRequest(ctx, exchange.RestSpot, huobiCurrencies, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Currencies, err
}
// GetCurrenciesIncludingChains returns currency and chain data
func (h *HUOBI) GetCurrenciesIncludingChains(ctx context.Context, curr currency.Code) ([]CurrenciesChainData, error) {
resp := struct {
Data []CurrenciesChainData `json:"data"`
}{}
vals := url.Values{}
if !curr.IsEmpty() {
vals.Set("currency", curr.Lower().String())
}
path := common.EncodeURLValues(huobiCurrenciesReference, vals)
err := h.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp)
if err != nil {
return nil, err
}
return resp.Data, nil
}
// GetTimestamp returns the Huobi server time
func (h *HUOBI) GetTimestamp(ctx context.Context) (int64, error) {
type response struct {
@@ -714,7 +733,11 @@ func (h *HUOBI) GetMarginAccountBalance(ctx context.Context, symbol currency.Pai
}
// Withdraw withdraws the desired amount and currency
func (h *HUOBI) Withdraw(ctx context.Context, c currency.Code, address, addrTag string, amount, fee float64) (int64, error) {
func (h *HUOBI) Withdraw(ctx context.Context, c currency.Code, address, addrTag, chain string, amount, fee float64) (int64, error) {
if c.IsEmpty() || address == "" || amount <= 0 {
return 0, errors.New("currency, address and amount must be set")
}
resp := struct {
WithdrawID int64 `json:"data"`
}{}
@@ -724,6 +747,7 @@ func (h *HUOBI) Withdraw(ctx context.Context, c currency.Code, address, addrTag
Amount string `json:"amount"`
Currency string `json:"currency"`
Fee string `json:"fee,omitempty"`
Chain string `json:"chain,omitempty"`
AddrTag string `json:"addr-tag,omitempty"`
}{
Address: address,
@@ -735,11 +759,15 @@ func (h *HUOBI) Withdraw(ctx context.Context, c currency.Code, address, addrTag
data.Fee = strconv.FormatFloat(fee, 'f', -1, 64)
}
if c == currency.XRP {
if addrTag != "" {
data.AddrTag = addrTag
}
err := h.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, huobiWithdrawCreate, nil, data, &resp.WithdrawID, false)
if chain != "" {
data.Chain = strings.ToLower(chain)
}
err := h.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, huobiWithdrawCreate, nil, data, &resp, false)
return resp.WithdrawID, err
}
@@ -757,22 +785,22 @@ func (h *HUOBI) CancelWithdraw(ctx context.Context, withdrawID int64) (int64, er
}
// QueryDepositAddress returns the deposit address for a specified currency
func (h *HUOBI) QueryDepositAddress(ctx context.Context, cryptocurrency string) (DepositAddress, error) {
func (h *HUOBI) QueryDepositAddress(ctx context.Context, cryptocurrency currency.Code) ([]DepositAddress, error) {
resp := struct {
DepositAddress []DepositAddress `json:"data"`
}{}
vals := url.Values{}
vals.Set("currency", cryptocurrency)
vals.Set("currency", cryptocurrency.Lower().String())
err := h.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, huobiAccountDepositAddress, vals, nil, &resp, true)
if err != nil {
return DepositAddress{}, err
return nil, err
}
if len(resp.DepositAddress) == 0 {
return DepositAddress{}, errors.New("deposit address data isn't populated")
return nil, errors.New("deposit address data isn't populated")
}
return resp.DepositAddress[0], nil
return resp.DepositAddress, nil
}
// QueryWithdrawQuotas returns the users cryptocurrency withdraw quotas
@@ -817,8 +845,13 @@ func (h *HUOBI) SendHTTPRequest(ctx context.Context, ep exchange.URL, path strin
var errCap errorCapture
if err := json.Unmarshal(tempResp, &errCap); err == nil {
if errCap.Code != 200 && errCap.ErrMsg != "" {
return errors.New(errCap.ErrMsg)
if errCap.ErrMsgType1 != "" {
return fmt.Errorf("error code: %v error message: %s", errCap.CodeType1,
errors.New(errCap.ErrMsgType1))
}
if errCap.ErrMsgType2 != "" {
return fmt.Errorf("error code: %v error message: %s", errCap.CodeType2,
errors.New(errCap.ErrMsgType2))
}
}
return json.Unmarshal(tempResp, result)
@@ -839,16 +872,15 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.UR
interim := json.RawMessage{}
newRequest := func() (*request.Item, error) {
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"))
values.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05"))
if isVersion2API {
endpoint = "/v" + huobiAPIVersion2 + "/" + endpoint
endpoint = "/v" + huobiAPIVersion2 + endpoint
} else {
endpoint = "/v" + huobiAPIVersion + "/" + endpoint
endpoint = "/v" + huobiAPIVersion + endpoint
}
payload := fmt.Sprintf("%s\napi.huobi.pro\n%s\n%s",
@@ -903,14 +935,14 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.UR
var errCap ResponseV2
if err = json.Unmarshal(interim, &errCap); err == nil {
if errCap.Code != 200 && errCap.Message != "" {
return errors.New(errCap.Message)
return fmt.Errorf("error code: %v error message: %s", errCap.Code, 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 fmt.Errorf("error code: %v error message: %s", errCap.ErrorCode, errCap.ErrorMessage)
}
}
}

View File

@@ -18,43 +18,43 @@ import (
const (
// Coin Margined Swap (perpetual futures) endpoints
huobiSwapMarkets = "swap-api/v1/swap_contract_info?"
huobiSwapFunding = "swap-api/v1/swap_funding_rate?"
huobiSwapIndexPriceInfo = "swap-api/v1/swap_index?"
huobiSwapPriceLimitation = "swap-api/v1/swap_price_limit?"
huobiSwapOpenInterestInfo = "swap-api/v1/swap_open_interest?"
huobiSwapMarketDepth = "swap-ex/market/depth?"
huobiKLineData = "swap-ex/market/history/kline?"
huobiMarketDataOverview = "swap-ex/market/detail/merged?"
huobiLastTradeContract = "swap-ex/market/trade?"
huobiRequestBatchOfTradingRecords = "swap-ex/market/history/trade?"
huobiInsuranceBalanceAndClawbackRate = "swap-api/v1/swap_risk_info?"
huobiInsuranceBalanceHistory = "swap-api/v1/swap_insurance_fund?"
huobiTieredAdjustmentFactor = "swap-api/v1/swap_adjustfactor?"
huobiOpenInterestInfo = "swap-api/v1/swap_his_open_interest?"
huobiSwapSystemStatus = "swap-api/v1/swap_api_state?"
huobiSwapSentimentAccountData = "swap-api/v1/swap_elite_account_ratio?"
huobiSwapSentimentPosition = "swap-api/v1/swap_elite_position_ratio?"
huobiSwapLiquidationOrders = "swap-api/v1/swap_liquidation_orders?"
huobiSwapHistoricalFundingRate = "swap-api/v1/swap_historical_funding_rate?"
huobiPremiumIndexKlineData = "index/market/history/swap_premium_index_kline?"
huobiPredictedFundingRateData = "index/market/history/swap_estimated_rate_kline?"
huobiBasisData = "index/market/history/swap_basis?"
huobiSwapAccInfo = "swap-api/v1/swap_account_info"
huobiSwapPosInfo = "swap-api/v1/swap_position_info"
huobiSwapAssetsAndPos = "swap-api/v1/swap_account_position_info" // nolint // false positive gosec
huobiSwapSubAccList = "swap-api/v1/swap_sub_account_list"
huobiSwapSubAccInfo = "swap-api/v1/swap_sub_account_info"
huobiSwapSubAccPosInfo = "swap-api/v1/swap_sub_position_info"
huobiSwapFinancialRecords = "swap-api/v1/swap_financial_record"
huobiSwapSettlementRecords = "swap-api/v1/swap_user_settlement_records"
huobiSwapAvailableLeverage = "swap-api/v1/swap_available_level_rate"
huobiSwapOrderLimitInfo = "swap-api/v1/swap_order_limit"
huobiSwapTradingFeeInfo = "swap-api/v1/swap_fee"
huobiSwapTransferLimitInfo = "swap-api/v1/swap_transfer_limit"
huobiSwapPositionLimitInfo = "swap-api/v1/swap_position_limit"
huobiSwapInternalTransferData = "swap-api/v1/swap_master_sub_transfer"
huobiSwapInternalTransferRecords = "swap-api/v1/swap_master_sub_transfer_record"
huobiSwapMarkets = "/swap-api/v1/swap_contract_info"
huobiSwapFunding = "/swap-api/v1/swap_funding_rate"
huobiSwapIndexPriceInfo = "/swap-api/v1/swap_index"
huobiSwapPriceLimitation = "/swap-api/v1/swap_price_limit"
huobiSwapOpenInterestInfo = "/swap-api/v1/swap_open_interest"
huobiSwapMarketDepth = "/swap-ex/market/depth"
huobiKLineData = "/swap-ex/market/history/kline"
huobiMarketDataOverview = "/swap-ex/market/detail/merged"
huobiLastTradeContract = "/swap-ex/market/trade"
huobiRequestBatchOfTradingRecords = "/swap-ex/market/history/trade"
huobiInsuranceBalanceAndClawbackRate = "/swap-api/v1/swap_risk_info"
huobiInsuranceBalanceHistory = "/swap-api/v1/swap_insurance_fund"
huobiTieredAdjustmentFactor = "/swap-api/v1/swap_adjustfactor"
huobiOpenInterestInfo = "/swap-api/v1/swap_his_open_interest"
huobiSwapSystemStatus = "/swap-api/v1/swap_api_state"
huobiSwapSentimentAccountData = "/swap-api/v1/swap_elite_account_ratio"
huobiSwapSentimentPosition = "/swap-api/v1/swap_elite_position_ratio"
huobiSwapLiquidationOrders = "/swap-api/v1/swap_liquidation_orders"
huobiSwapHistoricalFundingRate = "/swap-api/v1/swap_historical_funding_rate"
huobiPremiumIndexKlineData = "/index/market/history/swap_premium_index_kline"
huobiPredictedFundingRateData = "/index/market/history/swap_estimated_rate_kline"
huobiBasisData = "/index/market/history/swap_basis"
huobiSwapAccInfo = "/swap-api/v1/swap_account_info"
huobiSwapPosInfo = "/swap-api/v1/swap_position_info"
huobiSwapAssetsAndPos = "/swap-api/v1/swap_account_position_info" // nolint // false positive gosec
huobiSwapSubAccList = "/swap-api/v1/swap_sub_account_list"
huobiSwapSubAccInfo = "/swap-api/v1/swap_sub_account_info"
huobiSwapSubAccPosInfo = "/swap-api/v1/swap_sub_position_info"
huobiSwapFinancialRecords = "/swap-api/v1/swap_financial_record"
huobiSwapSettlementRecords = "/swap-api/v1/swap_user_settlement_records"
huobiSwapAvailableLeverage = "/swap-api/v1/swap_available_level_rate"
huobiSwapOrderLimitInfo = "/swap-api/v1/swap_order_limit"
huobiSwapTradingFeeInfo = "/swap-api/v1/swap_fee"
huobiSwapTransferLimitInfo = "/swap-api/v1/swap_transfer_limit"
huobiSwapPositionLimitInfo = "/swap-api/v1/swap_position_limit"
huobiSwapInternalTransferData = "/swap-api/v1/swap_master_sub_transfer"
huobiSwapInternalTransferRecords = "/swap-api/v1/swap_master_sub_transfer_record"
huobiSwapPlaceOrder = "/swap-api/v1/swap_order"
huobiSwapPlaceBatchOrder = "/swap-api/v1/swap_batchorder"
huobiSwapCancelOrder = "/swap-api/v1/swap_cancel"
@@ -65,7 +65,7 @@ const (
huobiSwapOpenOrders = "/swap-api/v1/swap_openorders"
huobiSwapOrderHistory = "/swap-api/v1/swap_hisorders"
huobiSwapTradeHistory = "/swap-api/v1/swap_matchresults"
huobiSwapTriggerOrder = "swap-api/v1/swap_trigger_order"
huobiSwapTriggerOrder = "/swap-api/v1/swap_trigger_order"
huobiSwapCancelTriggerOrder = "/swap-api/v1/swap_trigger_cancel"
huobiSwapCancelAllTriggerOrders = "/swap-api/v1/swap_trigger_cancelall"
huobiSwapTriggerOrderHistory = "/swap-api/v1/swap_trigger_hisorders"
@@ -82,7 +82,7 @@ func (h *HUOBI) QuerySwapIndexPriceInfo(ctx context.Context, code currency.Pair)
}
params := url.Values{}
params.Set("contract_code", codeValue)
path = huobiSwapIndexPriceInfo + params.Encode()
path = common.EncodeURLValues(path, params)
}
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -90,57 +90,59 @@ func (h *HUOBI) QuerySwapIndexPriceInfo(ctx context.Context, code currency.Pair)
// GetSwapPriceLimits gets price caps for perpetual futures
func (h *HUOBI) GetSwapPriceLimits(ctx context.Context, code currency.Pair) (SwapPriceLimitsData, error) {
var resp SwapPriceLimitsData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapPriceLimitation+params.Encode(),
&resp)
path := common.EncodeURLValues(huobiSwapPriceLimitation, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// SwapOpenInterestInformation gets open interest data for perpetual futures
func (h *HUOBI) SwapOpenInterestInformation(ctx context.Context, code currency.Pair) (SwapOpenInterestData, error) {
var resp SwapOpenInterestData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapOpenInterestInfo+params.Encode(), &resp)
path := common.EncodeURLValues(huobiSwapOpenInterestInfo, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetSwapMarketDepth gets market depth for perpetual futures
func (h *HUOBI) GetSwapMarketDepth(ctx context.Context, code currency.Pair, dataType string) (SwapMarketDepthData, error) {
var resp SwapMarketDepthData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("type", dataType)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapMarketDepth+params.Encode(), &resp)
path := common.EncodeURLValues(huobiSwapMarketDepth, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetSwapKlineData gets kline data for perpetual futures
func (h *HUOBI) GetSwapKlineData(ctx context.Context, code currency.Pair, period string, size int64, startTime, endTime time.Time) (SwapKlineData, error) {
var resp SwapKlineData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if size == 1 || size > 2000 {
return resp, fmt.Errorf("invalid size")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("period", period)
params.Set("size", strconv.FormatInt(size, 10))
if !startTime.IsZero() && !endTime.IsZero() {
if startTime.After(endTime) {
@@ -149,69 +151,74 @@ func (h *HUOBI) GetSwapKlineData(ctx context.Context, code currency.Pair, period
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiKLineData+params.Encode(), &resp)
path := common.EncodeURLValues(huobiKLineData, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetSwapMarketOverview gets market data overview for perpetual futures
func (h *HUOBI) GetSwapMarketOverview(ctx context.Context, code currency.Pair) (MarketOverviewData, error) {
var resp MarketOverviewData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiMarketDataOverview+params.Encode(), &resp)
path := common.EncodeURLValues(huobiMarketDataOverview, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetLastTrade gets the last trade for a given perpetual contract
func (h *HUOBI) GetLastTrade(ctx context.Context, code currency.Pair) (LastTradeData, error) {
var resp LastTradeData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiLastTradeContract+params.Encode(), &resp)
path := common.EncodeURLValues(huobiLastTradeContract, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetBatchTrades gets batch trades for a specified contract (fetching size cannot be bigger than 2000)
func (h *HUOBI) GetBatchTrades(ctx context.Context, code currency.Pair, size int64) (BatchTradesData, error) {
var resp BatchTradesData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if size <= 0 || size > 1200 {
return resp, fmt.Errorf("invalid size provided values from 1-1200 supported")
return resp, fmt.Errorf("invalid size provided, only values between 1-1200 are supported")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("size", strconv.FormatInt(size, 10))
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiRequestBatchOfTradingRecords+params.Encode(), &resp)
path := common.EncodeURLValues(huobiRequestBatchOfTradingRecords, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetInsuranceData gets insurance fund data and clawback rates
func (h *HUOBI) GetInsuranceData(ctx context.Context, code currency.Pair) (InsuranceAndClawbackData, error) {
var resp InsuranceAndClawbackData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiInsuranceBalanceAndClawbackRate+params.Encode(), &resp)
path := common.EncodeURLValues(huobiInsuranceBalanceAndClawbackRate, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetHistoricalInsuranceData gets historical insurance fund data and clawback rates
func (h *HUOBI) GetHistoricalInsuranceData(ctx context.Context, code currency.Pair, pageIndex, pageSize int64) (HistoricalInsuranceFundBalance, error) {
var resp HistoricalInsuranceFundBalance
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
if pageIndex != 0 {
params.Set("page_index", strconv.FormatInt(pageIndex, 10))
@@ -219,119 +226,114 @@ func (h *HUOBI) GetHistoricalInsuranceData(ctx context.Context, code currency.Pa
if pageSize != 0 {
params.Set("page_size", strconv.FormatInt(pageIndex, 10))
}
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiInsuranceBalanceHistory+params.Encode(), &resp)
path := common.EncodeURLValues(huobiInsuranceBalanceHistory, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetTieredAjustmentFactorInfo gets tiered adjustment factor data
func (h *HUOBI) GetTieredAjustmentFactorInfo(ctx context.Context, code currency.Pair) (TieredAdjustmentFactorData, error) {
var resp TieredAdjustmentFactorData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiTieredAdjustmentFactor+params.Encode(), &resp)
path := common.EncodeURLValues(huobiTieredAdjustmentFactor, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetOpenInterestInfo gets open interest data
func (h *HUOBI) GetOpenInterestInfo(ctx context.Context, code currency.Pair, period, amountType string, size int64) (OpenInterestData, error) {
var resp OpenInterestData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if size <= 0 || size > 1200 {
return resp, fmt.Errorf("invalid size provided values from 1-1200 supported")
return resp, fmt.Errorf("invalid size provided, only values between 1-1200 are supported")
}
params.Set("size", strconv.FormatInt(size, 10))
aType, ok := validAmountType[amountType]
if !ok {
return resp, fmt.Errorf("invalid trade type")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("period", period)
params.Set("size", strconv.FormatInt(size, 10))
params.Set("amount_type", strconv.FormatInt(aType, 10))
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiOpenInterestInfo+params.Encode(), &resp)
path := common.EncodeURLValues(huobiOpenInterestInfo, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetSystemStatusInfo gets system status data
func (h *HUOBI) GetSystemStatusInfo(ctx context.Context, code currency.Pair, period, amountType string, size int64) (SystemStatusData, error) {
func (h *HUOBI) GetSystemStatusInfo(ctx context.Context, code currency.Pair) (SystemStatusData, error) {
var resp SystemStatusData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if size > 0 && size <= 1200 {
params.Set("size", strconv.FormatInt(size, 10))
}
aType, ok := validAmountType[amountType]
if !ok {
return resp, fmt.Errorf("invalid trade type")
}
params.Set("amount_type", strconv.FormatInt(aType, 10))
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapSystemStatus+params.Encode(), &resp)
path := common.EncodeURLValues(huobiSwapSystemStatus, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetTraderSentimentIndexAccount gets top trader sentiment function-account
func (h *HUOBI) GetTraderSentimentIndexAccount(ctx context.Context, code currency.Pair, period string) (TraderSentimentIndexAccountData, error) {
var resp TraderSentimentIndexAccountData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("period", period)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapSentimentAccountData+params.Encode(), &resp)
path := common.EncodeURLValues(huobiSwapSentimentAccountData, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetTraderSentimentIndexPosition gets top trader sentiment function-position
func (h *HUOBI) GetTraderSentimentIndexPosition(ctx context.Context, code currency.Pair, period string) (TraderSentimentIndexPositionData, error) {
var resp TraderSentimentIndexPositionData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("period", period)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapSentimentPosition+params.Encode(), &resp)
path := common.EncodeURLValues(huobiSwapSentimentPosition, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetLiquidationOrders gets liquidation orders for a given perp
func (h *HUOBI) GetLiquidationOrders(ctx context.Context, code currency.Pair, tradeType string, pageIndex, pageSize, createDate int64) (LiquidationOrdersData, error) {
var resp LiquidationOrdersData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if createDate != 7 && createDate != 90 {
return resp, fmt.Errorf("invalid createDate. 7 and 90 are the only supported values")
}
params.Set("create_date", strconv.FormatInt(createDate, 10))
tType, ok := validTradeTypes[tradeType]
if !ok {
return resp, fmt.Errorf("invalid trade type")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("create_date", strconv.FormatInt(createDate, 10))
params.Set("trade_type", strconv.FormatInt(tType, 10))
if pageIndex != 0 {
params.Set("page_index", strconv.FormatInt(pageIndex, 10))
@@ -339,17 +341,18 @@ func (h *HUOBI) GetLiquidationOrders(ctx context.Context, code currency.Pair, tr
if pageSize != 0 {
params.Set("page_size", strconv.FormatInt(pageIndex, 10))
}
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapLiquidationOrders+params.Encode(), &resp)
path := common.EncodeURLValues(huobiSwapLiquidationOrders, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetHistoricalFundingRates gets historical funding rates for perpetual futures
func (h *HUOBI) GetHistoricalFundingRates(ctx context.Context, code currency.Pair, pageSize, pageIndex int64) (HistoricalFundingRateData, error) {
var resp HistoricalFundingRateData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("contract_code", codeValue)
if pageIndex != 0 {
params.Set("page_index", strconv.FormatInt(pageIndex, 10))
@@ -357,70 +360,74 @@ func (h *HUOBI) GetHistoricalFundingRates(ctx context.Context, code currency.Pai
if pageSize != 0 {
params.Set("page_size", strconv.FormatInt(pageIndex, 10))
}
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiSwapHistoricalFundingRate+params.Encode(), &resp)
path := common.EncodeURLValues(huobiSwapHistoricalFundingRate, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetPremiumIndexKlineData gets kline data for premium index
func (h *HUOBI) GetPremiumIndexKlineData(ctx context.Context, code currency.Pair, period string, size int64) (PremiumIndexKlineData, error) {
var resp PremiumIndexKlineData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if size <= 0 || size > 1200 {
return resp, fmt.Errorf("invalid size provided values from 1-1200 supported")
return resp, fmt.Errorf("invalid size provided, only values between 1-1200 are supported")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("size", strconv.FormatInt(size, 10))
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiPremiumIndexKlineData+params.Encode(), &resp)
params.Set("period", period)
path := common.EncodeURLValues(huobiPremiumIndexKlineData, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetEstimatedFundingRates gets estimated funding rates for perpetual futures
func (h *HUOBI) GetEstimatedFundingRates(ctx context.Context, code currency.Pair, period string, size int64) (EstimatedFundingRateData, error) {
var resp EstimatedFundingRateData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if size <= 0 || size > 1200 {
return resp, fmt.Errorf("invalid size provided values from 1-1200 supported")
return resp, fmt.Errorf("invalid size provided, only values between 1-1200 are supported")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("period", period)
params.Set("size", strconv.FormatInt(size, 10))
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiPredictedFundingRateData+params.Encode(), &resp)
path := common.EncodeURLValues(huobiPredictedFundingRateData, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetBasisData gets basis data for perpetual futures
func (h *HUOBI) GetBasisData(ctx context.Context, code currency.Pair, period, basisPriceType string, size int64) (BasisData, error) {
var resp BasisData
params := url.Values{}
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if size <= 0 || size > 1200 {
return resp, fmt.Errorf("invalid size provided values from 1-1200 supported")
return resp, fmt.Errorf("invalid size provided, only values between 1-1200 are supported")
}
params.Set("size", strconv.FormatInt(size, 10))
if !common.StringDataCompare(validBasisPriceTypes, basisPriceType) {
if !common.StringDataCompareInsensitive(validBasisPriceTypes, basisPriceType) {
return resp, fmt.Errorf("invalid period value received")
}
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, huobiBasisData+params.Encode(), &resp)
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("period", period)
params.Set("size", strconv.FormatInt(size, 10))
path := common.EncodeURLValues(huobiBasisData, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
// GetSwapAccountInfo gets swap account info
@@ -573,7 +580,7 @@ func (h *HUOBI) GetSwapOrderLimitInfo(ctx context.Context, code currency.Pair, o
return resp, err
}
req["contract_code"] = codeValue
if !common.StringDataCompare(validOrderTypes, orderType) {
if !common.StringDataCompareInsensitive(validOrderTypes, orderType) {
return resp, fmt.Errorf("inavlid ordertype provided")
}
req["order_price_type"] = orderType
@@ -627,7 +634,7 @@ func (h *HUOBI) AccountTransferData(ctx context.Context, code currency.Pair, sub
req["contract_code"] = codeValue
req["subUid"] = subUID
req["amount"] = amount
if !common.StringDataCompare(validTransferType, transferType) {
if !common.StringDataCompareInsensitive(validTransferType, transferType) {
return resp, fmt.Errorf("inavlid transferType received")
}
req["type"] = transferType
@@ -643,7 +650,7 @@ func (h *HUOBI) AccountTransferRecords(ctx context.Context, code currency.Pair,
return resp, err
}
req["contract_code"] = codeValue
if !common.StringDataCompare(validTransferType, transferType) {
if !common.StringDataCompareInsensitive(validTransferType, transferType) {
return resp, fmt.Errorf("inavlid transferType received")
}
req["type"] = transferType
@@ -674,7 +681,7 @@ func (h *HUOBI) PlaceSwapOrders(ctx context.Context, code currency.Pair, clientO
}
req["direction"] = direction
req["offset"] = offset
if !common.StringDataCompare(validOrderTypes, orderPriceType) {
if !common.StringDataCompareInsensitive(validOrderTypes, orderPriceType) {
return resp, fmt.Errorf("inavlid ordertype provided")
}
req["order_price_type"] = orderPriceType
@@ -742,7 +749,7 @@ func (h *HUOBI) PlaceLightningCloseOrder(ctx context.Context, contractCode curre
req["client_order_id"] = clientOrderID
}
if orderPriceType != "" {
if !common.StringDataCompare(validLightningOrderPriceType, orderPriceType) {
if !common.StringDataCompareInsensitive(validLightningOrderPriceType, orderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
req["order_price_type"] = orderPriceType
@@ -905,7 +912,7 @@ func (h *HUOBI) PlaceSwapTriggerOrder(ctx context.Context, contractCode currency
req["volume"] = volume
req["lever_rate"] = leverageRate
req["order_price"] = orderPrice
if !common.StringDataCompare(validOrderPriceType, orderPriceType) {
if !common.StringDataCompareInsensitive(validOrderPriceType, orderPriceType) {
return resp, fmt.Errorf("invalid order price type")
}
req["order_price_type"] = orderPriceType

View File

@@ -23,58 +23,58 @@ import (
const (
// Unauth
fContractInfo = "api/v1/contract_contract_info?"
fContractIndexPrice = "api/v1/contract_index?"
fContractPriceLimitation = "api/v1/contract_price_limit?"
fContractOpenInterest = "api/v1/contract_open_interest?"
fEstimatedDeliveryPrice = "api/v1/contract_delivery_price?"
fContractMarketDepth = "/market/depth?"
fContractKline = "/market/history/kline?"
fMarketOverview = "/market/detail/merged?"
fLastTradeContract = "/market/trade?"
fContractBatchTradeRecords = "/market/history/trade?"
fInsuranceAndClawback = "api/v1/contract_risk_info?"
fInsuranceBalanceHistory = "api/v1/contract_insurance_fund?"
fTieredAdjustmentFactor = "api/v1/contract_adjustfactor?"
fHisContractOpenInterest = "api/v1/contract_his_open_interest?"
fSystemStatus = "api/v1/contract_api_state?"
fTopAccountsSentiment = "api/v1/contract_elite_account_ratio?"
fTopPositionsSentiment = "api/v1/contract_elite_position_ratio?"
fLiquidationOrders = "api/v1/contract_liquidation_orders?"
fIndexKline = "/index/market/history/index?"
fBasisData = "/index/market/history/basis?"
fContractInfo = "/api/v1/contract_contract_info"
fContractIndexPrice = "/api/v1/contract_index"
fContractPriceLimitation = "/api/v1/contract_price_limit"
fContractOpenInterest = "/api/v1/contract_open_interest"
fEstimatedDeliveryPrice = "/api/v1/contract_delivery_price"
fContractMarketDepth = "/market/depth"
fContractKline = "/market/history/kline"
fMarketOverview = "/market/detail/merged"
fLastTradeContract = "/market/trade"
fContractBatchTradeRecords = "/market/history/trade"
fInsuranceAndClawback = "/api/v1/contract_risk_info"
fInsuranceBalanceHistory = "/api/v1/contract_insurance_fund"
fTieredAdjustmentFactor = "/api/v1/contract_adjustfactor"
fHisContractOpenInterest = "/api/v1/contract_his_open_interest"
fSystemStatus = "/api/v1/contract_api_state"
fTopAccountsSentiment = "/api/v1/contract_elite_account_ratio"
fTopPositionsSentiment = "/api/v1/contract_elite_position_ratio"
fLiquidationOrders = "/api/v1/contract_liquidation_orders"
fIndexKline = "/index/market/history/index"
fBasisData = "/index/market/history/basis"
// Auth
fAccountData = "api/v1/contract_account_info"
fPositionInformation = "api/v1/contract_position_info"
fAllSubAccountAssets = "api/v1/contract_sub_account_list"
fSingleSubAccountAssets = "api/v1/contract_sub_account_info"
fSingleSubAccountPositions = "api/v1/contract_sub_position_info"
fFinancialRecords = "api/v1/contract_financial_record"
fSettlementRecords = "api/v1/contract_user_settlement_records"
fOrderLimitInfo = "api/v1/contract_order_limit"
fContractTradingFee = "api/v1/contract_fee"
fTransferLimitInfo = "api/v1/contract_transfer_limit"
fPositionLimitInfo = "api/v1/contract_position_limit"
fQueryAssetsAndPositions = "api/v1/contract_account_position_info"
fTransfer = "api/v1/contract_master_sub_transfer"
fTransferRecords = "api/v1/contract_master_sub_transfer_record"
fAvailableLeverage = "api/v1/contract_available_level_rate"
fOrder = "api/v1/contract_order"
fBatchOrder = "api/v1/contract_batchorder"
fCancelOrder = "api/v1/contract_cancel"
fCancelAllOrders = "api/v1/contract_cancelall"
fFlashCloseOrder = "api/v1/lightning_close_position"
fOrderInfo = "api/v1/contract_order_info"
fOrderDetails = "api/v1/contract_order_detail"
fQueryOpenOrders = "api/v1/contract_openorders"
fOrderHistory = "api/v1/contract_hisorders"
fMatchResult = "api/v1/contract_matchresults"
fTriggerOrder = "api/v1/contract_trigger_order"
fCancelTriggerOrder = "api/v1/contract_trigger_cancel"
fCancelAllTriggerOrders = "api/v1/contract_trigger_cancelall"
fTriggerOpenOrders = "api/v1/contract_trigger_openorders"
fTriggerOrderHistory = "api/v1/contract_trigger_hisorders"
fAccountData = "/api/v1/contract_account_info"
fPositionInformation = "/api/v1/contract_position_info"
fAllSubAccountAssets = "/api/v1/contract_sub_account_list"
fSingleSubAccountAssets = "/api/v1/contract_sub_account_info"
fSingleSubAccountPositions = "/api/v1/contract_sub_position_info"
fFinancialRecords = "/api/v1/contract_financial_record"
fSettlementRecords = "/api/v1/contract_user_settlement_records"
fOrderLimitInfo = "/api/v1/contract_order_limit"
fContractTradingFee = "/api/v1/contract_fee"
fTransferLimitInfo = "/api/v1/contract_transfer_limit"
fPositionLimitInfo = "/api/v1/contract_position_limit"
fQueryAssetsAndPositions = "/api/v1/contract_account_position_info"
fTransfer = "/api/v1/contract_master_sub_transfer"
fTransferRecords = "/api/v1/contract_master_sub_transfer_record"
fAvailableLeverage = "/api/v1/contract_available_level_rate"
fOrder = "/api/v1/contract_order"
fBatchOrder = "/api/v1/contract_batchorder"
fCancelOrder = "/api/v1/contract_cancel"
fCancelAllOrders = "/api/v1/contract_cancelall"
fFlashCloseOrder = "/api/v1/lightning_close_position"
fOrderInfo = "/api/v1/contract_order_info"
fOrderDetails = "/api/v1/contract_order_detail"
fQueryOpenOrders = "/api/v1/contract_openorders"
fOrderHistory = "/api/v1/contract_hisorders"
fMatchResult = "/api/v1/contract_matchresults"
fTriggerOrder = "/api/v1/contract_trigger_order"
fCancelTriggerOrder = "/api/v1/contract_trigger_cancel"
fCancelAllTriggerOrders = "/api/v1/contract_trigger_cancelall"
fTriggerOpenOrders = "/api/v1/contract_trigger_openorders"
fTriggerOrderHistory = "/api/v1/contract_trigger_hisorders"
)
// FGetContractInfo gets contract info for futures
@@ -85,7 +85,7 @@ func (h *HUOBI) FGetContractInfo(ctx context.Context, symbol, contractType strin
params.Set("symbol", symbol)
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
params.Set("contract_type", contractType)
@@ -97,7 +97,7 @@ func (h *HUOBI) FGetContractInfo(ctx context.Context, symbol, contractType strin
}
params.Set("contract_code", codeValue)
}
path := fContractInfo + params.Encode()
path := common.EncodeURLValues(fContractInfo, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -112,7 +112,7 @@ func (h *HUOBI) FIndexPriceInfo(ctx context.Context, symbol currency.Code) (FCon
}
params.Set("symbol", codeValue)
}
path := fContractIndexPrice + params.Encode()
path := common.EncodeURLValues(fContractIndexPrice, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -124,7 +124,7 @@ func (h *HUOBI) FContractPriceLimitations(ctx context.Context, symbol, contractT
params.Set("symbol", symbol)
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType: %s", contractType)
}
params.Set("contract_type", contractType)
@@ -136,7 +136,7 @@ func (h *HUOBI) FContractPriceLimitations(ctx context.Context, symbol, contractT
}
params.Set("contract_code", codeValue)
}
path := fContractPriceLimitation + params.Encode()
path := common.EncodeURLValues(fContractPriceLimitation, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -148,7 +148,7 @@ func (h *HUOBI) FContractOpenInterest(ctx context.Context, symbol, contractType
params.Set("symbol", symbol)
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
params.Set("contract_type", contractType)
@@ -160,7 +160,7 @@ func (h *HUOBI) FContractOpenInterest(ctx context.Context, symbol, contractType
}
params.Set("contract_code", codeValue)
}
path := fContractOpenInterest + params.Encode()
path := common.EncodeURLValues(fContractOpenInterest, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -173,7 +173,7 @@ func (h *HUOBI) FGetEstimatedDeliveryPrice(ctx context.Context, symbol currency.
return resp, err
}
params.Set("symbol", codeValue)
path := fEstimatedDeliveryPrice + params.Encode()
path := common.EncodeURLValues(fEstimatedDeliveryPrice, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -182,13 +182,13 @@ func (h *HUOBI) FGetMarketDepth(ctx context.Context, symbol currency.Pair, dataT
var resp OBData
var tempData FMarketDepth
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
symbolValue, err := h.formatFuturesPair(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
params.Set("type", dataType)
path := fContractMarketDepth + params.Encode()
path := common.EncodeURLValues(fContractMarketDepth, params)
err = h.SendHTTPRequest(ctx, exchange.RestFutures, path, &tempData)
if err != nil {
return resp, err
@@ -213,12 +213,12 @@ func (h *HUOBI) FGetMarketDepth(ctx context.Context, symbol currency.Pair, dataT
func (h *HUOBI) FGetKlineData(ctx context.Context, symbol currency.Pair, period string, size int64, startTime, endTime time.Time) (FKlineData, error) {
var resp FKlineData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
symbolValue, err := h.formatFuturesPair(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if !common.StringDataCompare(validFuturesPeriods, period) {
if !common.StringDataCompareInsensitive(validFuturesPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
@@ -233,7 +233,7 @@ func (h *HUOBI) FGetKlineData(ctx context.Context, symbol currency.Pair, period
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
path := fContractKline + params.Encode()
path := common.EncodeURLValues(fContractKline, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -241,12 +241,12 @@ func (h *HUOBI) FGetKlineData(ctx context.Context, symbol currency.Pair, period
func (h *HUOBI) FGetMarketOverviewData(ctx context.Context, symbol currency.Pair) (FMarketOverviewData, error) {
var resp FMarketOverviewData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
symbolValue, err := h.formatFuturesPair(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
path := fMarketOverview + params.Encode()
path := common.EncodeURLValues(fMarketOverview, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -254,12 +254,12 @@ func (h *HUOBI) FGetMarketOverviewData(ctx context.Context, symbol currency.Pair
func (h *HUOBI) FLastTradeData(ctx context.Context, symbol currency.Pair) (FLastTradeData, error) {
var resp FLastTradeData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
symbolValue, err := h.formatFuturesPair(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
path := fLastTradeContract + params.Encode()
path := common.EncodeURLValues(fLastTradeContract, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -267,7 +267,7 @@ func (h *HUOBI) FLastTradeData(ctx context.Context, symbol currency.Pair) (FLast
func (h *HUOBI) FRequestPublicBatchTrades(ctx context.Context, symbol currency.Pair, size int64) (FBatchTradesForContractData, error) {
var resp FBatchTradesForContractData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
symbolValue, err := h.formatFuturesPair(symbol)
if err != nil {
return resp, err
}
@@ -275,7 +275,7 @@ func (h *HUOBI) FRequestPublicBatchTrades(ctx context.Context, symbol currency.P
if size > 1 && size < 2000 {
params.Set("size", strconv.FormatInt(size, 10))
}
path := fContractBatchTradeRecords + params.Encode()
path := common.EncodeURLValues(fContractBatchTradeRecords, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -290,7 +290,7 @@ func (h *HUOBI) FQueryInsuranceAndClawbackData(ctx context.Context, symbol curre
}
params.Set("symbol", codeValue)
}
path := fInsuranceAndClawback + params.Encode()
path := common.EncodeURLValues(fInsuranceAndClawback, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -305,7 +305,7 @@ func (h *HUOBI) FQueryHistoricalInsuranceData(ctx context.Context, symbol curren
}
params.Set("symbol", codeValue)
}
path := fInsuranceBalanceHistory + params.Encode()
path := common.EncodeURLValues(fInsuranceBalanceHistory, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -320,7 +320,7 @@ func (h *HUOBI) FQueryTieredAdjustmentFactor(ctx context.Context, symbol currenc
}
params.Set("symbol", codeValue)
}
path := fTieredAdjustmentFactor + params.Encode()
path := common.EncodeURLValues(fTieredAdjustmentFactor, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -331,11 +331,11 @@ func (h *HUOBI) FQueryHisOpenInterest(ctx context.Context, symbol, contractType,
if symbol != "" {
params.Set("symbol", symbol)
}
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contract type")
}
params.Set("contract_type", contractType)
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period")
}
params.Set("period", period)
@@ -347,7 +347,7 @@ func (h *HUOBI) FQueryHisOpenInterest(ctx context.Context, symbol, contractType,
return resp, fmt.Errorf("invalid amountType")
}
params.Set("amount_type", strconv.FormatInt(validAmount, 10))
path := fHisContractOpenInterest + params.Encode()
path := common.EncodeURLValues(fHisContractOpenInterest, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -362,7 +362,7 @@ func (h *HUOBI) FQuerySystemStatus(ctx context.Context, symbol currency.Code) (F
}
params.Set("symbol", codeValue)
}
path := fSystemStatus + params.Encode()
path := common.EncodeURLValues(fSystemStatus, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -373,11 +373,11 @@ func (h *HUOBI) FQueryTopAccountsRatio(ctx context.Context, symbol, period strin
if symbol != "" {
params.Set("symbol", symbol)
}
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period")
}
params.Set("period", period)
path := fTopAccountsSentiment + params.Encode()
path := common.EncodeURLValues(fTopAccountsSentiment, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -388,11 +388,11 @@ func (h *HUOBI) FQueryTopPositionsRatio(ctx context.Context, symbol, period stri
if symbol != "" {
params.Set("symbol", symbol)
}
if !common.StringDataCompare(validPeriods, period) {
if !common.StringDataCompareInsensitive(validPeriods, period) {
return resp, fmt.Errorf("invalid period")
}
params.Set("period", period)
path := fTopPositionsSentiment + params.Encode()
path := common.EncodeURLValues(fTopPositionsSentiment, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -416,7 +416,7 @@ func (h *HUOBI) FLiquidationOrders(ctx context.Context, symbol, tradeType string
if pageSize != 0 {
params.Set("page_size", strconv.FormatInt(pageIndex, 10))
}
path := fLiquidationOrders + params.Encode()
path := common.EncodeURLValues(fLiquidationOrders, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -424,12 +424,12 @@ func (h *HUOBI) FLiquidationOrders(ctx context.Context, symbol, tradeType string
func (h *HUOBI) FIndexKline(ctx context.Context, symbol currency.Pair, period string, size int64) (FIndexKlineData, error) {
var resp FIndexKlineData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
symbolValue, err := h.formatFuturesPair(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if !common.StringDataCompare(validFuturesPeriods, period) {
if !common.StringDataCompareInsensitive(validFuturesPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
@@ -437,7 +437,7 @@ func (h *HUOBI) FIndexKline(ctx context.Context, symbol currency.Pair, period st
return resp, fmt.Errorf("invalid size")
}
params.Set("size", strconv.FormatInt(size, 10))
path := fIndexKline + params.Encode()
path := common.EncodeURLValues(fIndexKline, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -445,24 +445,24 @@ func (h *HUOBI) FIndexKline(ctx context.Context, symbol currency.Pair, period st
func (h *HUOBI) FGetBasisData(ctx context.Context, symbol currency.Pair, period, basisPriceType string, size int64) (FBasisData, error) {
var resp FBasisData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
symbolValue, err := h.formatFuturesPair(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if !common.StringDataCompare(validFuturesPeriods, period) {
if !common.StringDataCompareInsensitive(validFuturesPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if basisPriceType != "" {
if common.StringDataCompare(validBasisPriceTypes, basisPriceType) {
if common.StringDataCompareInsensitive(validBasisPriceTypes, basisPriceType) {
params.Set("basis_price_type", basisPriceType)
}
}
if size > 0 && size <= 2000 {
params.Set("size", strconv.FormatInt(size, 10))
}
path := fBasisData + params.Encode()
path := common.EncodeURLValues(fBasisData, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)
}
@@ -585,7 +585,7 @@ func (h *HUOBI) FGetOrderLimits(ctx context.Context, symbol, orderPriceType stri
req["symbol"] = symbol
}
if orderPriceType != "" {
if !common.StringDataCompare(validFuturesOrderPriceTypes, orderPriceType) {
if !common.StringDataCompareInsensitive(validFuturesOrderPriceTypes, orderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
req["order_price_type"] = orderPriceType
@@ -650,7 +650,7 @@ func (h *HUOBI) FTransfer(ctx context.Context, subUID, symbol, transferType stri
req["symbol"] = symbol
req["subUid"] = subUID
req["amount"] = amount
if !common.StringDataCompare(validTransferType, transferType) {
if !common.StringDataCompareInsensitive(validTransferType, transferType) {
return resp, fmt.Errorf("inavlid transferType received")
}
req["type"] = transferType
@@ -664,7 +664,7 @@ func (h *HUOBI) FGetTransferRecords(ctx context.Context, symbol, transferType st
if symbol != "" {
req["symbol"] = symbol
}
if !common.StringDataCompare(validTransferType, transferType) {
if !common.StringDataCompareInsensitive(validTransferType, transferType) {
return resp, fmt.Errorf("inavlid transferType received")
}
req["type"] = transferType
@@ -703,7 +703,7 @@ func (h *HUOBI) FOrder(ctx context.Context, contractCode currency.Pair, symbol,
req["symbol"] = symbol
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
req["contract_type"] = contractType
@@ -719,10 +719,10 @@ func (h *HUOBI) FOrder(ctx context.Context, contractCode currency.Pair, symbol,
req["client_order_id"] = clientOrderID
}
req["direction"] = direction
if !common.StringDataCompare(validOffsetTypes, offset) {
if !common.StringDataCompareInsensitive(validOffsetTypes, offset) {
return resp, fmt.Errorf("invalid offset amounts")
}
if !common.StringDataCompare(validFuturesOrderPriceTypes, orderPriceType) {
if !common.StringDataCompareInsensitive(validFuturesOrderPriceTypes, orderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
req["order_price_type"] = orderPriceType
@@ -753,14 +753,14 @@ func (h *HUOBI) FPlaceBatchOrder(ctx context.Context, data []fBatchOrderData) (F
data[x].ContractCode = formattedPair.String()
}
if data[x].ContractType != "" {
if !common.StringDataCompare(validContractTypes, data[x].ContractType) {
if !common.StringDataCompareInsensitive(validContractTypes, data[x].ContractType) {
return resp, fmt.Errorf("invalid contractType")
}
}
if !common.StringDataCompare(validOffsetTypes, data[x].Offset) {
if !common.StringDataCompareInsensitive(validOffsetTypes, data[x].Offset) {
return resp, fmt.Errorf("invalid offset amounts")
}
if !common.StringDataCompare(validFuturesOrderPriceTypes, data[x].OrderPriceType) {
if !common.StringDataCompareInsensitive(validFuturesOrderPriceTypes, data[x].OrderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
}
@@ -792,7 +792,7 @@ func (h *HUOBI) FCancelAllOrders(ctx context.Context, contractCode currency.Pair
req["symbol"] = symbol
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
req["contract_type"] = contractType
@@ -813,7 +813,7 @@ func (h *HUOBI) FFlashCloseOrder(ctx context.Context, contractCode currency.Pair
req := make(map[string]interface{})
req["symbol"] = symbol
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
req["contract_type"] = contractType
@@ -831,7 +831,7 @@ func (h *HUOBI) FFlashCloseOrder(ctx context.Context, contractCode currency.Pair
req["client_order_id"] = clientOrderID
}
if orderPriceType != "" {
if !common.StringDataCompare(validOPTypes, orderPriceType) {
if !common.StringDataCompareInsensitive(validOPTypes, orderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
req["orderPriceType"] = orderPriceType
@@ -985,7 +985,7 @@ func (h *HUOBI) FPlaceTriggerOrder(ctx context.Context, contractCode currency.Pa
req["symbol"] = symbol
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType: %s", contractType)
}
req["contract_type"] = contractType
@@ -1003,7 +1003,7 @@ func (h *HUOBI) FPlaceTriggerOrder(ctx context.Context, contractCode currency.Pa
}
req["trigger_type"] = tType
req["direction"] = direction
if !common.StringDataCompare(validOffsetTypes, offset) {
if !common.StringDataCompareInsensitive(validOffsetTypes, offset) {
return resp, fmt.Errorf("invalid offset")
}
req["offset"] = offset
@@ -1011,7 +1011,7 @@ func (h *HUOBI) FPlaceTriggerOrder(ctx context.Context, contractCode currency.Pa
req["volume"] = volume
req["lever_rate"] = leverageRate
req["order_price"] = orderPrice
if !common.StringDataCompare(validOrderPriceType, orderPriceType) {
if !common.StringDataCompareInsensitive(validOrderPriceType, orderPriceType) {
return resp, fmt.Errorf("invalid order price type")
}
req["order_price_type"] = orderPriceType
@@ -1040,7 +1040,7 @@ func (h *HUOBI) FCancelAllTriggerOrders(ctx context.Context, contractCode curren
req["contract_code"] = codeValue
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
if !common.StringDataCompareInsensitive(validContractTypes, contractType) {
return resp, nil
}
req["contract_type"] = contractType
@@ -1115,18 +1115,22 @@ func (h *HUOBI) FuturesAuthenticatedHTTPRequest(ctx context.Context, ep exchange
if err != nil {
return err
}
if ep == exchange.RestFutures && ePoint[len(ePoint)-1] == '/' {
// prevent signature errors for non-standard paths until we can
// have a method to force update endpoints
ePoint = ePoint[:len(ePoint)-1]
}
if values == nil {
values = url.Values{}
}
var tempResp json.RawMessage
newRequest := func() (*request.Item, error) {
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"))
sigPath := fmt.Sprintf("%s\napi.hbdm.com\n/%s\n%s",
values.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05"))
sigPath := fmt.Sprintf("%s\napi.hbdm.com\n%s\n%s",
method, endpoint, values.Encode())
headers := make(map[string]string)
if method == http.MethodGet {
@@ -1142,10 +1146,8 @@ func (h *HUOBI) FuturesAuthenticatedHTTPRequest(ctx context.Context, ep exchange
if err != nil {
return nil, err
}
sigValues := url.Values{}
sigValues.Add("Signature", crypto.Base64Encode(hmac))
urlPath :=
common.EncodeURLValues(ePoint+endpoint, values) + "&" + sigValues.Encode()
values.Add("Signature", crypto.Base64Encode(hmac))
urlPath := common.EncodeURLValues(ePoint+endpoint, values)
var body io.Reader
var payload []byte
if data != nil {
@@ -1176,8 +1178,13 @@ func (h *HUOBI) FuturesAuthenticatedHTTPRequest(ctx context.Context, ep exchange
var errCap errorCapture
if err := json.Unmarshal(tempResp, &errCap); err == nil {
if errCap.Code != 200 && errCap.ErrMsg != "" {
return errors.New(errCap.ErrMsg)
if errCap.ErrMsgType1 != "" {
return fmt.Errorf("error code: %v error message: %s", errCap.CodeType1,
errors.New(errCap.ErrMsgType1))
}
if errCap.ErrMsgType2 != "" {
return fmt.Errorf("error code: %v error message: %s", errCap.CodeType2,
errors.New(errCap.ErrMsgType2))
}
}
return json.Unmarshal(tempResp, result)
@@ -1193,3 +1200,11 @@ func (h *HUOBI) formatFuturesCode(p currency.Code) (string, error) {
}
return p.Lower().String(), nil
}
// formatFuturesPair handles pairs in the format as "BTC-NW" and "BTC210827"
func (h *HUOBI) formatFuturesPair(p currency.Pair) (string, error) {
if common.StringDataCompareInsensitive(validContractShortTypes, p.Quote.String()) {
return p.Format("_", true).String(), nil
}
return h.FormatSymbol(p, asset.Futures)
}

View File

@@ -5,6 +5,7 @@ import (
"log"
"os"
"strconv"
"strings"
"testing"
"time"
@@ -30,8 +31,11 @@ const (
testSymbol = "btcusdt"
)
var h HUOBI
var wsSetupRan bool
var (
h HUOBI
wsSetupRan bool
futuresTestPair = currency.NewPair(currency.BTC, currency.NewCode("NQ"))
)
func TestMain(m *testing.M) {
h.SetDefaults()
@@ -79,6 +83,24 @@ func setupWsTests(t *testing.T) {
wsSetupRan = true
}
func TestGetCurrenciesIncludingChains(t *testing.T) {
t.Parallel()
r, err := h.GetCurrenciesIncludingChains(context.Background(), currency.Code{})
if err != nil {
t.Error(err)
}
if len(r) == 1 {
t.Error("expected 1 result")
}
r, err = h.GetCurrenciesIncludingChains(context.Background(), currency.USDT)
if err != nil {
t.Error(err)
}
if len(r) < 1 {
t.Error("expected >= 1 results")
}
}
func TestFGetContractInfo(t *testing.T) {
t.Parallel()
_, err := h.FGetContractInfo(context.Background(), "", "", currency.Pair{})
@@ -123,11 +145,7 @@ func TestFGetEstimatedDeliveryPrice(t *testing.T) {
func TestFGetMarketDepth(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString("BTC_NW")
if err != nil {
t.Error(err)
}
_, err = h.FGetMarketDepth(context.Background(), cp, "step5")
_, err := h.FGetMarketDepth(context.Background(), futuresTestPair, "step5")
if err != nil {
t.Error(err)
}
@@ -135,12 +153,7 @@ func TestFGetMarketDepth(t *testing.T) {
func TestFGetKlineData(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString("BTC_NW")
if err != nil {
t.Error(err)
}
_, err = h.FGetKlineData(context.Background(),
cp, "5min", 5, time.Now().Add(-time.Minute*5), time.Now())
_, err := h.FGetKlineData(context.Background(), futuresTestPair, "5min", 5, time.Now().Add(-time.Minute*5), time.Now())
if err != nil {
t.Error(err)
}
@@ -148,11 +161,7 @@ func TestFGetKlineData(t *testing.T) {
func TestFGetMarketOverviewData(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString("BTC_NW")
if err != nil {
t.Error(err)
}
_, err = h.FGetMarketOverviewData(context.Background(), cp)
_, err := h.FGetMarketOverviewData(context.Background(), futuresTestPair)
if err != nil {
t.Error(err)
}
@@ -160,11 +169,7 @@ func TestFGetMarketOverviewData(t *testing.T) {
func TestFLastTradeData(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString("BTC_NW")
if err != nil {
t.Error(err)
}
_, err = h.FLastTradeData(context.Background(), cp)
_, err := h.FLastTradeData(context.Background(), futuresTestPair)
if err != nil {
t.Error(err)
}
@@ -172,11 +177,7 @@ func TestFLastTradeData(t *testing.T) {
func TestFRequestPublicBatchTrades(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString("BTC_NQ")
if err != nil {
t.Error(err)
}
a, err := h.FRequestPublicBatchTrades(context.Background(), cp, 50)
a, err := h.FRequestPublicBatchTrades(context.Background(), futuresTestPair, 50)
if err != nil {
t.Error(err)
}
@@ -253,11 +254,7 @@ func TestFLiquidationOrders(t *testing.T) {
func TestFIndexKline(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString("BTC_NQ")
if err != nil {
t.Error(err)
}
_, err = h.FIndexKline(context.Background(), cp, "5min", 5)
_, err := h.FIndexKline(context.Background(), futuresTestPair, "5min", 5)
if err != nil {
t.Error(err)
}
@@ -265,11 +262,7 @@ func TestFIndexKline(t *testing.T) {
func TestFGetBasisData(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString("BTC_NQ")
if err != nil {
t.Error(err)
}
_, err = h.FGetBasisData(context.Background(), cp, "5min", "open", 3)
_, err := h.FGetBasisData(context.Background(), futuresTestPair, "5min", "open", 3)
if err != nil {
t.Error(err)
}
@@ -682,11 +675,11 @@ func TestFetchTradablePairs(t *testing.T) {
func TestUpdateTickerSpot(t *testing.T) {
t.Parallel()
sp, err := currency.NewPairFromString("BTC_USDT")
if err != nil {
t.Error(err)
_, err := h.UpdateTicker(context.Background(), currency.NewPairWithDelimiter("INV", "ALID", "-"), asset.Spot)
if err == nil {
t.Error("exepcted invalid pair")
}
_, err = h.UpdateTicker(context.Background(), sp, asset.Spot)
_, err = h.UpdateTicker(context.Background(), currency.NewPairWithDelimiter("BTC", "USDT", "_"), asset.Spot)
if err != nil {
t.Error(err)
}
@@ -694,11 +687,11 @@ func TestUpdateTickerSpot(t *testing.T) {
func TestUpdateTickerCMF(t *testing.T) {
t.Parallel()
cp1, err := currency.NewPairFromString("BTC-USD")
if err != nil {
t.Error(err)
_, err := h.UpdateTicker(context.Background(), currency.NewPairWithDelimiter("INV", "ALID", "_"), asset.CoinMarginedFutures)
if err == nil {
t.Error("exepcted invalid contract code")
}
_, err = h.UpdateTicker(context.Background(), cp1, asset.CoinMarginedFutures)
_, err = h.UpdateTicker(context.Background(), currency.NewPairWithDelimiter("BTC", "USD", "_"), asset.CoinMarginedFutures)
if err != nil {
t.Error(err)
}
@@ -778,7 +771,7 @@ func TestUpdateOrderbookFuture(t *testing.T) {
if err != nil {
t.Error(err)
}
_, err = h.UpdateOrderbook(context.Background(), cp2, asset.Futures)
_, err = h.UpdateOrderbook(context.Background(), cp2, asset.CoinMarginedFutures)
if err != nil {
t.Error(err)
}
@@ -1077,8 +1070,7 @@ func TestGetSystemStatusInfo(t *testing.T) {
if err != nil {
t.Error(err)
}
_, err = h.GetSystemStatusInfo(context.Background(),
cp, "5min", "cryptocurrency", 50)
_, err = h.GetSystemStatusInfo(context.Background(), cp)
if err != nil {
t.Error(err)
}
@@ -1801,7 +1793,7 @@ func TestSpotNewOrder(t *testing.T) {
}
arg := SpotNewOrderRequestParams{
Symbol: cp,
AccountID: 1,
AccountID: 1997024,
Amount: 0.01,
Price: 10.1,
Type: SpotNewOrderRequestTypeBuyLimit,
@@ -2019,7 +2011,7 @@ func TestSubmitOrder(t *testing.T) {
},
Side: order.Buy,
Type: order.Limit,
Price: 1,
Price: 5,
Amount: 1,
ClientID: strconv.FormatInt(accounts[0].ID, 10),
AssetType: asset.Spot,
@@ -2125,6 +2117,7 @@ func TestModifyOrder(t *testing.T) {
func TestWithdraw(t *testing.T) {
withdrawCryptoRequest := withdraw.Request{
Exchange: h.Name,
Amount: -1,
Currency: currency.BTC,
Description: "WITHDRAW IT ALL",
@@ -2173,8 +2166,17 @@ func TestWithdrawInternationalBank(t *testing.T) {
}
func TestQueryDepositAddress(t *testing.T) {
_, err := h.QueryDepositAddress(context.Background(),
currency.BTC.Lower().String())
_, err := h.QueryDepositAddress(context.Background(), currency.USDT)
if !areTestAPIKeysSet() && err == nil {
t.Error("Expecting an error when no keys are set")
}
if areTestAPIKeysSet() && err != nil {
t.Error(err)
}
}
func TestGetDepositAddress(t *testing.T) {
_, err := h.GetDepositAddress(context.Background(), currency.USDT, "", "uSdTeRc20")
if !areTestAPIKeysSet() && err == nil {
t.Error("Expecting an error when no keys are set")
}
@@ -2677,3 +2679,44 @@ func TestGetHistoricTrades(t *testing.T) {
t.Error(err)
}
}
func TestGetAvailableTransferChains(t *testing.T) {
t.Parallel()
r, err := h.GetAvailableTransferChains(context.Background(), currency.USDT)
if err != nil {
t.Fatal(err)
}
if len(r) < 2 {
t.Error("expected more than one result")
}
}
func TestFormatFuturesPair(t *testing.T) {
r, err := h.formatFuturesPair(futuresTestPair)
if err != nil {
t.Fatal(err)
}
if r != "BTC_NQ" {
t.Errorf("expected BTC_NQ, got %s", r)
}
availInstruments, err := h.FetchTradablePairs(context.Background(), asset.Futures)
if err != nil {
t.Fatal(err)
}
if len(availInstruments) == 0 {
t.Fatal("expected instruments, got 0")
}
// test getting a tradable pair in the format of BTC210827 but make it lower
// case to test correct formatting
p, err := currency.NewPairFromString(strings.ToLower(availInstruments[0]))
if err != nil {
t.Fatal(err)
}
r, err = h.formatFuturesPair(p)
if err != nil {
t.Fatal(err)
}
if r != availInstruments[0] {
t.Errorf("expected %s, got %s", availInstruments[0], r)
}
}

View File

@@ -6,10 +6,12 @@ import (
)
type errorCapture struct {
Status string `json:"status"`
Code int64 `json:"err_code"`
ErrMsg string `json:"err_msg"`
Timestamp int64 `json:"ts"`
Status string `json:"status"`
CodeType1 interface{} `json:"err-code"` // can be either a string or int depending on the endpoint
ErrMsgType1 string `json:"err-msg"`
CodeType2 interface{} `json:"err_code"`
ErrMsgType2 string `json:"err_msg"`
Timestamp int64 `json:"ts"`
}
// MarketSummary24Hr stores past 24hr market summary data of a given symbol
@@ -27,6 +29,35 @@ type MarketSummary24Hr struct {
}
}
// CurrenciesChainData stores currency and chain info
type CurrenciesChainData struct {
Currency string `json:"currency"`
AssetType uint8 `json:"assetType"`
InstStatus string `json:"instStatus"`
ChainData []struct {
Chain string `json:"chain"`
DisplayName string `json:"displayName"`
BaseChain string `json:"baseChain"`
BaseChainProtocol string `json:"baseChainProtocol"`
IsDynamic bool `json:"isDynamic"`
NumberOfConfirmations uint16 `json:"numOfConfirmations"`
NumberOfFastConfirmations uint16 `json:"numOfFastConfirmations"`
DepositStatus string `json:"depositStatus"`
MinimumDepositAmount float64 `json:"minDepositAmt,string"`
WithdrawStatus string `json:"withdrawStatus"`
MinimumWithdrawalAmount float64 `json:"minWithdrawAmt,string"`
WithdrawPrecision int16 `json:"withdrawPrecision"`
MaximumWithdrawAmount float64 `json:"maxWithdrawwAmt,string"`
WithdrawQuotaPerDay float64 `json:"withdrawQuotaPerDay,string"`
WithdrawQuotaPerYear float64 `json:"withdrawQuotaPerYear,string"`
WithdrawQuotaTotal float64 `json:"withdrawQuotaTotal,string"`
WithdrawFeeType string `json:"withdrawFeeType"`
TransactFeeWithdraw float64 `json:"transactFeeWithdraw,string"`
AddressWithTag bool `json:"addrWithTag"`
AddressDepositTag bool `json:"addrDepositTag"`
} `json:"chains"`
}
// WsKlineData stores kline data for futures and swap websocket
type WsKlineData struct {
Channel string `json:"ch"`
@@ -699,6 +730,7 @@ type SpotNewOrderRequestParams struct {
// DepositAddress stores the users deposit address info
type DepositAddress struct {
UserID int64 `json:"userId"`
Currency string `json:"currency"`
Address string `json:"address"`
AddressTag string `json:"addressTag"`
@@ -1023,21 +1055,6 @@ type WsPong struct {
Pong int64 `json:"pong"`
}
type wsKlineResponse struct {
Data []struct {
Amount float64 `json:"amount"`
Close float64 `json:"close"`
Count float64 `json:"count"`
High float64 `json:"high"`
ID int64 `json:"id"`
Low float64 `json:"low"`
Open float64 `json:"open"`
Volume float64 `json:"vol"`
} `json:"data"`
Rep string `json:"rep"`
Status string `json:"status"`
}
type authenticationPing struct {
OP string `json:"op"`
TS int64 `json:"ts"`
@@ -1127,6 +1144,10 @@ var (
"this_week", "next_week", "quarter", "next_quarter",
}
validContractShortTypes = []string{
"cw", "nw", "cq", "nq",
}
validFuturesPeriods = []string{
"1min", "5min", "15min", "30min", "60min", "1hour", "4hour", "1day",
}

View File

@@ -16,6 +16,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
@@ -78,7 +79,6 @@ func (h *HUOBI) SetDefaults() {
futures := currency.PairStore{
RequestFormat: &currency.PairFormat{
Uppercase: true,
Delimiter: currency.UnderscoreDelimiter,
},
ConfigFormat: &currency.PairFormat{
Uppercase: true,
@@ -102,20 +102,22 @@ func (h *HUOBI) SetDefaults() {
REST: true,
Websocket: true,
RESTCapabilities: protocol.Features{
TickerFetching: true,
KlineFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
GetOrder: true,
GetOrders: true,
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
TradeFee: true,
TickerFetching: true,
KlineFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
GetOrder: true,
GetOrders: true,
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
TradeFee: true,
MultiChainDeposits: true,
MultiChainWithdrawals: true,
},
WebsocketCapabilities: protocol.Features{
KlineFetching: true,
@@ -162,7 +164,7 @@ func (h *HUOBI) SetDefaults() {
h.API.Endpoints = h.NewEndpoints()
err = h.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{
exchange.RestSpot: huobiAPIURL,
exchange.RestFutures: huobiURL,
exchange.RestFutures: huobiFuturesURL,
exchange.RestCoinMargined: huobiFuturesURL,
exchange.WebsocketSpot: wsMarketURL,
})
@@ -1244,9 +1246,26 @@ func (h *HUOBI) GetOrderInfo(ctx context.Context, orderID string, pair currency.
}
// GetDepositAddress returns a deposit address for a specified currency
func (h *HUOBI) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
resp, err := h.QueryDepositAddress(ctx, cryptocurrency.Lower().String())
return resp.Address, err
func (h *HUOBI) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) {
resp, err := h.QueryDepositAddress(ctx, cryptocurrency)
if err != nil {
return nil, err
}
for x := range resp {
if chain != "" && strings.EqualFold(resp[x].Chain, chain) {
return &deposit.Address{
Address: resp[x].Address,
Tag: resp[x].AddressTag,
}, nil
} else if chain == "" && strings.EqualFold(resp[x].Currency, cryptocurrency.String()) {
return &deposit.Address{
Address: resp[x].Address,
Tag: resp[x].AddressTag,
}, nil
}
}
return nil, fmt.Errorf("unable to match deposit address currency or chain")
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
@@ -1259,6 +1278,7 @@ func (h *HUOBI) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest
withdrawRequest.Currency,
withdrawRequest.Crypto.Address,
withdrawRequest.Crypto.AddressTag,
withdrawRequest.Crypto.Chain,
withdrawRequest.Amount,
withdrawRequest.Crypto.FeeAmount)
if err != nil {
@@ -1395,6 +1415,7 @@ func (h *HUOBI) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest
if err != nil {
return orders, err
}
var orderVars OrderVars
for x := range openOrders.Data.Orders {
orderVars, err = compatibleVars(openOrders.Data.Orders[x].Direction,
@@ -1424,6 +1445,8 @@ func (h *HUOBI) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest
Pair: p,
})
}
currentPage++
done = currentPage == openOrders.Data.TotalPage
}
}
case asset.Futures:
@@ -1464,6 +1487,8 @@ func (h *HUOBI) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest
Pair: p,
})
}
currentPage++
done = currentPage == openOrders.Data.TotalPage
}
}
}
@@ -1562,9 +1587,7 @@ func (h *HUOBI) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest
})
}
currentPage++
if currentPage == orderHistory.Data.TotalPage {
done = true
}
done = currentPage == orderHistory.Data.TotalPage
}
}
case asset.Futures:
@@ -1623,9 +1646,7 @@ func (h *HUOBI) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest
})
}
currentPage++
if currentPage == openOrders.Data.TotalPage {
done = true
}
done = currentPage == openOrders.Data.TotalPage
}
}
}
@@ -1762,3 +1783,22 @@ func compatibleVars(side, orderPriceType string, status int64) (OrderVars, error
}
return resp, nil
}
// GetAvailableTransferChains returns the available transfer blockchains for the specific
// cryptocurrency
func (h *HUOBI) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
chains, err := h.GetCurrenciesIncludingChains(ctx, cryptocurrency)
if err != nil {
return nil, err
}
if len(chains) == 0 {
return nil, errors.New("chain data isn't populated")
}
var availableChains []string
for x := range chains[0].ChainData {
availableChains = append(availableChains, chains[0].ChainData[x].Chain)
}
return availableChains, nil
}