Files
gocryptotrader/exchanges/huobi/huobi_futures.go
Scott 5ea5245afb Improvement: Subsystem separation (#664)
* Initial codes for a trade tracker

* Moving everything in a broken fashion

* Removes tradetracker. Removes some errors for subsystems

* Cleans up some subsystems, renames stuttering types. Removes some global Bot usage

* More basic subsystem renaming and file moving

* Removes engine dependency from events,ntpserver,ordermanager,comms manager

* Exports eventManager, fixes rpcserver. puts rpcserver back for now

* Removes redundant error message, further removes engine dependencies

* experimental end of day interface usage

* adds ability to build the application

* Withdraw and event manager handling

* cleans up apiserver and communications manager

* Cleans up some start/setup processes. Though should separate

* More consistency with Setup Start Stop IsRunning funcs

* Final consistency pass before testing phase

* Fixes engine tests. Fixes stop nil issue

* api server tests

* Communications manager testing

* Connection manager tests and nilsubsystem error

* End of day currencypairsyncer tests

* Adds databaseconnection/databaseconnection_test.go

* Adds withdrawal manager tests

* Deposit address testing. Moved orderbook sync first as its more important

* Adds test for event manager

* More full eventmanager testing

* Adds testfile. Enables skipped test.

* ntp manager tests

* Adds ordermanager tests, Extracts a whole new subsystem from engine and fanangles import cycles

* Adds websocket routine manager tests

* Basic portfolio manager testing

* Fixes issue with currency pair sync startup

* Fixes issue with event manager startup

* Starts the order manager before backtester starts

* Fixes fee tests. Expands testing. Doesnt fix races

* Fixes most test races

* Resolves data races

* Fixes subsystem test issues

* currency pair syncer coverage tests

* Refactors portfolio. Fixes tests. Withdraw validation

Portfolio didn't need to exist with a portfolio manager. Now the porfolio manager
is in charge how the portfolio is handled and all portfolio functions are attached
to the base instead of just exported at the package level

Withdrawal validation occurred at the exchange level when it can just be run at the
withdrawal manager level. All withdrawal requests go through that endpoint

* lint -fix

* golang lint fixes

* lints and comments everything

* Updates GCT logo, adds documentation for some subsystems

* More documentation and more logo updates

* Fixes backtesting and apiserver errors encountered

* Fixes errors and typos from reviewing

* More minor fixes

* Changes %h verb to %w

* reverbs to %s

* Humbly begins reverting to more flat engine package

The main reasoning for this is that the subsystem split doesn't make sense
in a golang environment. The subsystems are only meant to be used with engine
and so by placing them in a non-engine area, it does not work and is
inconsistent with the rest of the application's package layout.

This will begin salvaging the changes made by reverting to a flat
engine package, but maintaining the consistent designs introduced.
Further, I will look to remove any TestMains and decrease the scope
of testing to be more local and decrease the issues that have been
caused from our style of testing.

* Manages to re-flatten things. Everything is within its own file

* mini fixes

* Fixes tests and data races and lints

* Updates docs tool for engine to create filename readmes

* os -> ioutil

* remove err

* Appveyor version increase test

* Removes tCleanup as its unsupported on appveyor

* Adds stuff that I thought was in previous merge master commit

* Removes cancel from test

* Fixes really fun test-exclusive data race

* minor nit fixes

* niterinos

* docs gen

* rm;rf test

* Remove typoline. expands startstop helper. Splits apiserver

* Removes accidental folder

* Uses update instead of replace for order upsert

* addresses nits. Renames files. Regenerates documentation.

* lint and removal of comments

* Add new test for default scenario

* Fixes typo

* regen docs
2021-05-31 10:17:12 +10:00

1183 lines
41 KiB
Go

package huobi
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
)
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?"
// 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"
)
// FGetContractInfo gets contract info for futures
func (h *HUOBI) FGetContractInfo(symbol, contractType string, code currency.Pair) (FContractInfoData, error) {
var resp FContractInfoData
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
params.Set("contract_type", contractType)
}
if code != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(code, asset.Futures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
}
path := fContractInfo + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FIndexPriceInfo gets index price info for a futures contract
func (h *HUOBI) FIndexPriceInfo(symbol currency.Code) (FContractIndexPriceInfo, error) {
var resp FContractIndexPriceInfo
params := url.Values{}
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", codeValue)
}
path := fContractIndexPrice + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FContractPriceLimitations gets price limits for a futures contract
func (h *HUOBI) FContractPriceLimitations(symbol, contractType string, code currency.Pair) (FContractIndexPriceInfo, error) {
var resp FContractIndexPriceInfo
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType: %s", contractType)
}
params.Set("contract_type", contractType)
}
if code != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(code, asset.Futures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
}
path := fContractPriceLimitation + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FContractOpenInterest gets open interest data for futures contracts
func (h *HUOBI) FContractOpenInterest(symbol, contractType string, code currency.Pair) (FContractOIData, error) {
var resp FContractOIData
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
params.Set("contract_type", contractType)
}
if code != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(code, asset.Futures)
if err != nil {
return resp, err
}
params.Set("contract_code", codeValue)
}
path := fContractOpenInterest + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FGetEstimatedDeliveryPrice gets estimated delivery price info for futures
func (h *HUOBI) FGetEstimatedDeliveryPrice(symbol currency.Code) (FEstimatedDeliveryPriceInfo, error) {
var resp FEstimatedDeliveryPriceInfo
params := url.Values{}
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", codeValue)
path := fEstimatedDeliveryPrice + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FGetMarketDepth gets market depth data for futures contracts
func (h *HUOBI) FGetMarketDepth(symbol currency.Pair, dataType string) (OBData, error) {
var resp OBData
var tempData FMarketDepth
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
params.Set("type", dataType)
path := fContractMarketDepth + params.Encode()
err = h.SendHTTPRequest(exchange.RestFutures, path, &tempData)
if err != nil {
return resp, err
}
resp.Symbol = symbolValue
for x := range tempData.Tick.Asks {
resp.Asks = append(resp.Asks, obItem{
Price: tempData.Tick.Asks[x][0],
Quantity: tempData.Tick.Asks[x][1],
})
}
for y := range tempData.Tick.Bids {
resp.Bids = append(resp.Bids, obItem{
Price: tempData.Tick.Bids[y][0],
Quantity: tempData.Tick.Bids[y][1],
})
}
return resp, nil
}
// FGetKlineData gets kline data for futures
func (h *HUOBI) FGetKlineData(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)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if !common.StringDataCompare(validFuturesPeriods, 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")
}
params.Set("size", strconv.FormatInt(size, 10))
if !startTime.IsZero() && !endTime.IsZero() {
if startTime.After(endTime) {
return resp, errors.New("startTime cannot be after endTime")
}
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
path := fContractKline + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FGetMarketOverviewData gets market overview data for futures
func (h *HUOBI) FGetMarketOverviewData(symbol currency.Pair) (FMarketOverviewData, error) {
var resp FMarketOverviewData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
path := fMarketOverview + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FLastTradeData gets last trade data for a futures contract
func (h *HUOBI) FLastTradeData(symbol currency.Pair) (FLastTradeData, error) {
var resp FLastTradeData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
path := fLastTradeContract + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FRequestPublicBatchTrades gets public batch trades for a futures contract
func (h *HUOBI) FRequestPublicBatchTrades(symbol currency.Pair, size int64) (FBatchTradesForContractData, error) {
var resp FBatchTradesForContractData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if size > 1 && size < 2000 {
params.Set("size", strconv.FormatInt(size, 10))
}
path := fContractBatchTradeRecords + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FQueryInsuranceAndClawbackData gets insurance and clawback data for a futures contract
func (h *HUOBI) FQueryInsuranceAndClawbackData(symbol currency.Code) (FClawbackRateAndInsuranceData, error) {
var resp FClawbackRateAndInsuranceData
params := url.Values{}
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", codeValue)
}
path := fInsuranceAndClawback + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FQueryHistoricalInsuranceData gets insurance data
func (h *HUOBI) FQueryHistoricalInsuranceData(symbol currency.Code) (FHistoricalInsuranceRecordsData, error) {
var resp FHistoricalInsuranceRecordsData
params := url.Values{}
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", codeValue)
}
path := fInsuranceBalanceHistory + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FQueryTieredAdjustmentFactor gets tiered adjustment factor for futures contracts
func (h *HUOBI) FQueryTieredAdjustmentFactor(symbol currency.Code) (FTieredAdjustmentFactorInfo, error) {
var resp FTieredAdjustmentFactorInfo
params := url.Values{}
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", codeValue)
}
path := fTieredAdjustmentFactor + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FQueryHisOpenInterest gets open interest for futures contract
func (h *HUOBI) FQueryHisOpenInterest(symbol, contractType, period, amountType string, size int64) (FOIData, error) {
var resp FOIData
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contract type")
}
params.Set("contract_type", contractType)
if !common.StringDataCompare(validPeriods, period) {
return resp, fmt.Errorf("invalid period")
}
params.Set("period", period)
if size > 0 || size <= 200 {
params.Set("size", strconv.FormatInt(size, 10))
}
validAmount, ok := validAmountType[amountType]
if !ok {
return resp, fmt.Errorf("invalid amountType")
}
params.Set("amount_type", strconv.FormatInt(validAmount, 10))
path := fHisContractOpenInterest + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FQuerySystemStatus gets system status data
func (h *HUOBI) FQuerySystemStatus(symbol currency.Code) (FContractOIData, error) {
var resp FContractOIData
params := url.Values{}
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
params.Set("symbol", codeValue)
}
path := fSystemStatus + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FQueryTopAccountsRatio gets top accounts' ratio
func (h *HUOBI) FQueryTopAccountsRatio(symbol, period string) (FTopAccountsLongShortRatio, error) {
var resp FTopAccountsLongShortRatio
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if !common.StringDataCompare(validPeriods, period) {
return resp, fmt.Errorf("invalid period")
}
params.Set("period", period)
path := fTopAccountsSentiment + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FQueryTopPositionsRatio gets top positions' long/short ratio for futures
func (h *HUOBI) FQueryTopPositionsRatio(symbol, period string) (FTopPositionsLongShortRatio, error) {
var resp FTopPositionsLongShortRatio
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if !common.StringDataCompare(validPeriods, period) {
return resp, fmt.Errorf("invalid period")
}
params.Set("period", period)
path := fTopPositionsSentiment + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FLiquidationOrders gets liquidation orders for futures contracts
func (h *HUOBI) FLiquidationOrders(symbol, tradeType string, pageIndex, pageSize, createDate int64) (FLiquidationOrdersInfo, error) {
var resp FLiquidationOrdersInfo
params := url.Values{}
params.Set("symbol", symbol)
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.Set("trade_type", strconv.FormatInt(tType, 10))
if pageIndex != 0 {
params.Set("page_index", strconv.FormatInt(pageIndex, 10))
}
if pageSize != 0 {
params.Set("page_size", strconv.FormatInt(pageIndex, 10))
}
path := fLiquidationOrders + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FIndexKline gets index kline data for futures contracts
func (h *HUOBI) FIndexKline(symbol currency.Pair, period string, size int64) (FIndexKlineData, error) {
var resp FIndexKlineData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if !common.StringDataCompare(validFuturesPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if size <= 0 || size > 2000 {
return resp, fmt.Errorf("invalid size")
}
params.Set("size", strconv.FormatInt(size, 10))
path := fIndexKline + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FGetBasisData gets basis data futures contracts
func (h *HUOBI) FGetBasisData(symbol currency.Pair, period, basisPriceType string, size int64) (FBasisData, error) {
var resp FBasisData
params := url.Values{}
symbolValue, err := h.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if !common.StringDataCompare(validFuturesPeriods, period) {
return resp, fmt.Errorf("invalid period value received")
}
params.Set("period", period)
if basisPriceType != "" {
if common.StringDataCompare(validBasisPriceTypes, basisPriceType) {
params.Set("basis_price_type", basisPriceType)
}
}
if size > 0 && size <= 2000 {
params.Set("size", strconv.FormatInt(size, 10))
}
path := fBasisData + params.Encode()
return resp, h.SendHTTPRequest(exchange.RestFutures, path, &resp)
}
// FGetAccountInfo gets user info for futures account
func (h *HUOBI) FGetAccountInfo(symbol currency.Code) (FUserAccountData, error) {
var resp FUserAccountData
req := make(map[string]interface{})
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
req["symbol"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fAccountData, nil, req, &resp)
}
// FGetPositionsInfo gets positions info for futures account
func (h *HUOBI) FGetPositionsInfo(symbol currency.Code) (FUserAccountData, error) {
var resp FUserAccountData
req := make(map[string]interface{})
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
req["symbol"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fPositionInformation, nil, req, &resp)
}
// FGetAllSubAccountAssets gets assets info for all futures subaccounts
func (h *HUOBI) FGetAllSubAccountAssets(symbol currency.Code) (FSubAccountAssetsInfo, error) {
var resp FSubAccountAssetsInfo
req := make(map[string]interface{})
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
req["symbol"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fAllSubAccountAssets, nil, req, &resp)
}
// FGetSingleSubAccountInfo gets assets info for a futures subaccount
func (h *HUOBI) FGetSingleSubAccountInfo(symbol, subUID string) (FSingleSubAccountAssetsInfo, error) {
var resp FSingleSubAccountAssetsInfo
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
req["sub_uid"] = subUID
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fSingleSubAccountAssets, nil, req, &resp)
}
// FGetSingleSubPositions gets positions info for a single sub account
func (h *HUOBI) FGetSingleSubPositions(symbol, subUID string) (FSingleSubAccountPositionsInfo, error) {
var resp FSingleSubAccountPositionsInfo
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
req["sub_uid"] = subUID
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fSingleSubAccountPositions, nil, req, &resp)
}
// FGetFinancialRecords gets financial records for futures
func (h *HUOBI) FGetFinancialRecords(symbol, recordType string, createDate, pageIndex, pageSize int64) (FFinancialRecords, error) {
var resp FFinancialRecords
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
if recordType != "" {
rType, ok := validFuturesRecordTypes[recordType]
if !ok {
return resp, fmt.Errorf("invalid recordType")
}
req["type"] = rType
}
if createDate > 0 && createDate < 90 {
req["create_date"] = createDate
}
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fFinancialRecords, nil, req, &resp)
}
// FGetSettlementRecords gets settlement records for futures
func (h *HUOBI) FGetSettlementRecords(symbol currency.Code, pageIndex, pageSize int64, startTime, endTime time.Time) (FSettlementRecords, error) {
var resp FSettlementRecords
req := make(map[string]interface{})
req["symbol"] = symbol
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
if !startTime.IsZero() && !endTime.IsZero() {
if startTime.After(endTime) {
return resp, errors.New("startTime cannot be after endTime")
}
req["start_time"] = strconv.FormatInt(startTime.Unix()*1000, 10)
req["end_time"] = strconv.FormatInt(endTime.Unix()*1000, 10)
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fSettlementRecords, nil, req, &resp)
}
// FGetOrderLimits gets order limits for futures contracts
func (h *HUOBI) FGetOrderLimits(symbol, orderPriceType string) (FContractInfoOnOrderLimit, error) {
var resp FContractInfoOnOrderLimit
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
if orderPriceType != "" {
if !common.StringDataCompare(validFuturesOrderPriceTypes, orderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
req["order_price_type"] = orderPriceType
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fOrderLimitInfo, nil, req, &resp)
}
// FContractTradingFee gets futures contract trading fees
func (h *HUOBI) FContractTradingFee(symbol currency.Code) (FContractTradingFeeData, error) {
var resp FContractTradingFeeData
req := make(map[string]interface{})
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
req["symbol"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fContractTradingFee, nil, req, &resp)
}
// FGetTransferLimits gets transfer limits for futures
func (h *HUOBI) FGetTransferLimits(symbol currency.Code) (FTransferLimitData, error) {
var resp FTransferLimitData
req := make(map[string]interface{})
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
req["symbol"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fTransferLimitInfo, nil, req, &resp)
}
// FGetPositionLimits gets position limits for futures
func (h *HUOBI) FGetPositionLimits(symbol currency.Code) (FPositionLimitData, error) {
var resp FPositionLimitData
req := make(map[string]interface{})
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
req["symbol"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fPositionLimitInfo, nil, req, &resp)
}
// FGetAssetsAndPositions gets assets and positions for futures
func (h *HUOBI) FGetAssetsAndPositions(symbol currency.Code) (FAssetsAndPositionsData, error) {
var resp FAssetsAndPositionsData
req := make(map[string]interface{})
req["symbol"] = symbol
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fQueryAssetsAndPositions, nil, req, &resp)
}
// FTransfer transfers assets between master and subaccounts
func (h *HUOBI) FTransfer(subUID, symbol, transferType string, amount float64) (FAccountTransferData, error) {
var resp FAccountTransferData
req := make(map[string]interface{})
req["symbol"] = symbol
req["subUid"] = subUID
req["amount"] = amount
if !common.StringDataCompare(validTransferType, transferType) {
return resp, fmt.Errorf("inavlid transferType received")
}
req["type"] = transferType
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fTransfer, nil, req, &resp)
}
// FGetTransferRecords gets transfer records data for futures
func (h *HUOBI) FGetTransferRecords(symbol, transferType string, createDate, pageIndex, pageSize int64) (FTransferRecords, error) {
var resp FTransferRecords
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
if !common.StringDataCompare(validTransferType, transferType) {
return resp, fmt.Errorf("inavlid transferType received")
}
req["type"] = transferType
if createDate < 0 || createDate > 90 {
return resp, fmt.Errorf("invalid create date value: only supports up to 90 days")
}
req["create_date"] = strconv.FormatInt(createDate, 10)
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize > 0 && pageSize <= 50 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fTransferRecords, nil, req, &resp)
}
// FGetAvailableLeverage gets available leverage data for futures
func (h *HUOBI) FGetAvailableLeverage(symbol currency.Code) (FAvailableLeverageData, error) {
var resp FAvailableLeverageData
req := make(map[string]interface{})
if symbol != (currency.Code{}) {
codeValue, err := h.formatFuturesCode(symbol)
if err != nil {
return resp, err
}
req["symbol"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fAvailableLeverage, nil, req, &resp)
}
// FOrder places an order for futures
func (h *HUOBI) FOrder(contractCode currency.Pair, symbol, contractType, clientOrderID, direction, offset, orderPriceType string, price, volume, leverageRate float64) (FOrderData, error) {
var resp FOrderData
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
req["contract_type"] = contractType
}
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
if clientOrderID != "" {
req["client_order_id"] = clientOrderID
}
req["direction"] = direction
if !common.StringDataCompare(validOffsetTypes, offset) {
return resp, fmt.Errorf("invalid offset amounts")
}
if !common.StringDataCompare(validFuturesOrderPriceTypes, orderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
req["order_price_type"] = orderPriceType
req["lever_rate"] = leverageRate
req["volume"] = volume
req["price"] = price
req["offset"] = offset
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fOrder, nil, req, &resp)
}
// FPlaceBatchOrder places a batch of orders for futures
func (h *HUOBI) FPlaceBatchOrder(data []fBatchOrderData) (FBatchOrderResponse, error) {
var resp FBatchOrderResponse
req := make(map[string]interface{})
if len(data) > 10 || len(data) == 0 {
return resp, fmt.Errorf("invalid data provided: maximum of 10 batch orders supported")
}
for x := range data {
if data[x].ContractCode != "" {
unformattedPair, err := currency.NewPairFromString(data[x].ContractCode)
if err != nil {
return resp, err
}
formattedPair, err := h.FormatExchangeCurrency(unformattedPair, asset.Futures)
if err != nil {
return resp, err
}
data[x].ContractCode = formattedPair.String()
}
if data[x].ContractType != "" {
if !common.StringDataCompare(validContractTypes, data[x].ContractType) {
return resp, fmt.Errorf("invalid contractType")
}
}
if !common.StringDataCompare(validOffsetTypes, data[x].Offset) {
return resp, fmt.Errorf("invalid offset amounts")
}
if !common.StringDataCompare(validFuturesOrderPriceTypes, data[x].OrderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
}
req["orders_data"] = data
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fBatchOrder, nil, req, &resp)
}
// FCancelOrder cancels a futures order
func (h *HUOBI) FCancelOrder(symbol, orderID, clientOrderID string) (FCancelOrderData, error) {
var resp FCancelOrderData
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
if orderID != "" {
req["order_id"] = orderID
}
if clientOrderID != "" {
req["client_order_id"] = clientOrderID
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fCancelOrder, nil, req, &resp)
}
// FCancelAllOrders cancels all futures order for a given symbol
func (h *HUOBI) FCancelAllOrders(contractCode currency.Pair, symbol, contractType string) (FCancelOrderData, error) {
var resp FCancelOrderData
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
req["contract_type"] = contractType
}
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fCancelAllOrders, nil, req, &resp)
}
// FFlashCloseOrder flash closes a futures order
func (h *HUOBI) FFlashCloseOrder(contractCode currency.Pair, symbol, contractType, direction, orderPriceType, clientOrderID string, volume float64) (FOrderData, error) {
var resp FOrderData
req := make(map[string]interface{})
req["symbol"] = symbol
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType")
}
req["contract_type"] = contractType
}
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
req["direction"] = direction
req["volume"] = volume
if clientOrderID != "" {
req["client_order_id"] = clientOrderID
}
if orderPriceType != "" {
if !common.StringDataCompare(validOPTypes, orderPriceType) {
return resp, fmt.Errorf("invalid orderPriceType")
}
req["orderPriceType"] = orderPriceType
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fFlashCloseOrder, nil, req, &resp)
}
// FGetOrderInfo gets order info for futures
func (h *HUOBI) FGetOrderInfo(symbol, clientOrderID, orderID string) (FOrderInfo, error) {
var resp FOrderInfo
req := make(map[string]interface{})
req["symbol"] = symbol
if orderID != "" {
req["order_id"] = orderID
}
if clientOrderID != "" {
req["client_order_id"] = clientOrderID
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fOrderInfo, nil, req, &resp)
}
// FOrderDetails gets order details for futures orders
func (h *HUOBI) FOrderDetails(symbol, orderID, orderType string, createdAt time.Time, pageIndex, pageSize int64) (FOrderDetailsData, error) {
var resp FOrderDetailsData
req := make(map[string]interface{})
req["symbol"] = symbol
req["order_id"] = orderID
req["created_at"] = strconv.FormatInt(createdAt.Unix(), 10)
oType, ok := validOrderType[orderType]
if !ok {
return resp, fmt.Errorf("invalid orderType")
}
req["order_type"] = oType
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fOrderDetails, nil, req, &resp)
}
// FGetOpenOrders gets order details for futures orders
func (h *HUOBI) FGetOpenOrders(symbol currency.Code, pageIndex, pageSize int64) (FOpenOrdersData, error) {
var resp FOpenOrdersData
req := make(map[string]interface{})
req["symbol"] = symbol
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fQueryOpenOrders, nil, req, &resp)
}
// FGetOrderHistory gets order order history for futures
func (h *HUOBI) FGetOrderHistory(contractCode currency.Pair, symbol, tradeType, reqType, orderType string, status []order.Status, createDate, pageIndex, pageSize int64) (FOrderHistoryData, error) {
var resp FOrderHistoryData
req := make(map[string]interface{})
req["symbol"] = symbol
tType, ok := validFuturesTradeType[tradeType]
if !ok {
return resp, fmt.Errorf("invalid tradeType")
}
req["trade_type"] = tType
rType, ok := validFuturesReqType[reqType]
if !ok {
return resp, fmt.Errorf("invalid reqType")
}
req["type"] = rType
var reqStatus string = "0"
if len(status) > 0 {
var firstTime bool = true
for x := range status {
sType, ok := validOrderStatus[status[x]]
if !ok {
return resp, fmt.Errorf("invalid status")
}
if firstTime {
firstTime = false
reqStatus = strconv.FormatInt(sType, 10)
continue
}
reqStatus = reqStatus + "," + strconv.FormatInt(sType, 10)
}
}
req["status"] = reqStatus
if createDate < 0 || createDate > 90 {
return resp, fmt.Errorf("invalid createDate")
}
req["create_date"] = createDate
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
if orderType != "" {
oType, ok := validFuturesOrderTypes[orderType]
if !ok {
return resp, fmt.Errorf("invalid orderType")
}
req["order_type"] = oType
}
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fOrderHistory, nil, req, &resp)
}
// FTradeHistory gets trade history data for futures
func (h *HUOBI) FTradeHistory(contractCode currency.Pair, symbol, tradeType string, createDate, pageIndex, pageSize int64) (FOrderHistoryData, error) {
var resp FOrderHistoryData
req := make(map[string]interface{})
req["symbol"] = symbol
tType, ok := validTradeType[tradeType]
if !ok {
return resp, fmt.Errorf("invalid tradeType")
}
req["trade_type"] = tType
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
if createDate <= 0 || createDate > 90 {
return resp, fmt.Errorf("invalid createDate")
}
req["create_date"] = createDate
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fMatchResult, nil, req, &resp)
}
// FPlaceTriggerOrder places a trigger order for futures
func (h *HUOBI) FPlaceTriggerOrder(contractCode currency.Pair, symbol, contractType, triggerType, orderPriceType, direction, offset string, triggerPrice, orderPrice, volume, leverageRate float64) (FTriggerOrderData, error) {
var resp FTriggerOrderData
req := make(map[string]interface{})
if symbol != "" {
req["symbol"] = symbol
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, fmt.Errorf("invalid contractType: %s", contractType)
}
req["contract_type"] = contractType
}
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
tType, ok := validTriggerType[triggerType]
if !ok {
return resp, fmt.Errorf("invalid trigger type")
}
req["trigger_type"] = tType
req["direction"] = direction
if !common.StringDataCompare(validOffsetTypes, offset) {
return resp, fmt.Errorf("invalid offset")
}
req["offset"] = offset
req["trigger_price"] = triggerPrice
req["volume"] = volume
req["lever_rate"] = leverageRate
req["order_price"] = orderPrice
if !common.StringDataCompare(validOrderPriceType, orderPriceType) {
return resp, fmt.Errorf("invalid order price type")
}
req["order_price_type"] = orderPriceType
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fTriggerOrder, nil, req, &resp)
}
// FCancelTriggerOrder cancels trigger order for futures
func (h *HUOBI) FCancelTriggerOrder(symbol, orderID string) (FCancelOrderData, error) {
var resp FCancelOrderData
req := make(map[string]interface{})
req["symbol"] = symbol
req["order_id"] = orderID
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fCancelTriggerOrder, nil, req, &resp)
}
// FCancelAllTriggerOrders cancels all trigger order for futures
func (h *HUOBI) FCancelAllTriggerOrders(contractCode currency.Pair, symbol, contractType string) (FCancelOrderData, error) {
var resp FCancelOrderData
req := make(map[string]interface{})
req["symbol"] = symbol
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
if contractType != "" {
if !common.StringDataCompare(validContractTypes, contractType) {
return resp, nil
}
req["contract_type"] = contractType
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fCancelAllTriggerOrders, nil, req, &resp)
}
// FQueryTriggerOpenOrders queries open trigger orders for futures
func (h *HUOBI) FQueryTriggerOpenOrders(contractCode currency.Pair, symbol string, pageIndex, pageSize int64) (FTriggerOpenOrders, error) {
var resp FTriggerOpenOrders
req := make(map[string]interface{})
req["symbol"] = symbol
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fTriggerOpenOrders, nil, req, &resp)
}
// FQueryTriggerOrderHistory queries trigger order history for futures
func (h *HUOBI) FQueryTriggerOrderHistory(contractCode currency.Pair, symbol, tradeType, status string, createDate, pageIndex, pageSize int64) (FTriggerOrderHistoryData, error) {
var resp FTriggerOrderHistoryData
req := make(map[string]interface{})
req["symbol"] = symbol
if contractCode != (currency.Pair{}) {
codeValue, err := h.FormatSymbol(contractCode, asset.Futures)
if err != nil {
return resp, err
}
req["contract_code"] = codeValue
}
if tradeType != "" {
tType, ok := validTradeType[tradeType]
if !ok {
return resp, fmt.Errorf("invalid tradeType")
}
req["trade_type"] = tType
}
validStatus, ok := validStatusTypes[status]
if !ok {
return resp, fmt.Errorf("invalid status")
}
req["status"] = validStatus
if createDate <= 0 || createDate > 90 {
return resp, fmt.Errorf("invalid createDate")
}
req["create_date"] = createDate
if pageIndex != 0 {
req["page_index"] = pageIndex
}
if pageSize != 0 {
req["page_size"] = pageSize
}
return resp, h.FuturesAuthenticatedHTTPRequest(exchange.RestFutures, http.MethodPost, fTriggerOrderHistory, nil, req, &resp)
}
// FuturesAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API
func (h *HUOBI) FuturesAuthenticatedHTTPRequest(ep exchange.URL, method, endpoint string, values url.Values, data, result interface{}) error {
if !h.AllowAuthenticatedRequest() {
return fmt.Errorf("%s %w", h.Name, exchange.ErrAuthenticatedRequestWithoutCredentialsSet)
}
ePoint, err := h.API.Endpoints.GetURL(ep)
if err != nil {
return err
}
if values == nil {
values = url.Values{}
}
now := time.Now()
values.Set("AccessKeyId", h.API.Credentials.Key)
values.Set("SignatureMethod", "HmacSHA256")
values.Set("SignatureVersion", "2")
values.Set("Timestamp", now.UTC().Format("2006-01-02T15:04:05"))
sigPath := fmt.Sprintf("%s\napi.hbdm.com\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(sigPath), []byte(h.API.Credentials.Secret))
sigValues := url.Values{}
sigValues.Add("Signature", crypto.Base64Encode(hmac))
urlPath :=
common.EncodeURLValues(ePoint+endpoint, values) + "&" + sigValues.Encode()
var body io.Reader
var payload []byte
if data != nil {
payload, err = json.Marshal(data)
if err != nil {
return err
}
body = bytes.NewBuffer(payload)
}
var tempResp json.RawMessage
var errCap errorCapture
ctx, cancel := context.WithDeadline(context.Background(), now.Add(15*time.Second))
defer cancel()
if err := h.SendPayload(ctx, &request.Item{
Method: method,
Path: urlPath,
Headers: headers,
Body: body,
Result: &tempResp,
AuthRequest: true,
Verbose: h.Verbose,
HTTPDebugging: h.HTTPDebugging,
HTTPRecording: h.HTTPRecording,
}); err != nil {
return err
}
if err := json.Unmarshal(tempResp, &errCap); err == nil {
if errCap.Code != 200 && errCap.ErrMsg != "" {
return errors.New(errCap.ErrMsg)
}
}
return json.Unmarshal(tempResp, result)
}
func (h *HUOBI) formatFuturesCode(p currency.Code) (string, error) {
pairFmt, err := h.GetPairFormat(asset.Futures, true)
if err != nil {
return "", err
}
if pairFmt.Uppercase {
return p.Upper().String(), nil
}
return p.Lower().String(), nil
}