Files
gocryptotrader/exchanges/kucoin/kucoin.go
Ryan O'Hara-Reid 6ccb0e0c2f gateio/kucoin: assortment of fixes (#1404)
* gateio: fix unmarshal bug and update fields

* gateio: fix wrapper function function, add helper methods

* update order types and add kucoin wrapper fix

* currency pairs

* Add tests

* gateio; inspect error and continue for no funds in account, kucoin: fetch all settlement amounts

* futures: order fixit

* finish off gateio updates for market orders

* cute line

* Update exchanges/kucoin/kucoin_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/kucoin/kucoin_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/gateio/gateio.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/gateio/gateio_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/gateio/gateio_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* glorious: nits - filter by pair match and fix bug where the endpoint returns details instead of message

* Add fix for leverage check (non-merge) my ip has been blocked from gateio still... scammmmmmmm

* glorious: nitters

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
2024-03-06 13:06:13 +11:00

1859 lines
66 KiB
Go

package kucoin
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"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/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
)
// Kucoin is the overarching type across this package
type Kucoin struct {
exchange.Base
obm *orderbookManager
}
var locker sync.Mutex
const (
kucoinAPIURL = "https://api.kucoin.com/api"
kucoinAPIKeyVersion = "2"
// Public endpoints
kucoinGetSymbols = "/v2/symbols"
kucoinGetTicker = "/v1/market/orderbook/level1"
kucoinGetAllTickers = "/v1/market/allTickers"
kucoinGet24hrStats = "/v1/market/stats"
kucoinGetMarketList = "/v1/markets"
kucoinGetPartOrderbook20 = "/v1/market/orderbook/level2_20"
kucoinGetPartOrderbook100 = "/v1/market/orderbook/level2_100"
kucoinGetTradeHistory = "/v1/market/histories"
kucoinGetKlines = "/v1/market/candles"
kucoinGetCurrencies = "/v1/currencies"
kucoinGetCurrency = "/v2/currencies/"
kucoinGetFiatPrice = "/v1/prices"
kucoinGetMarkPrice = "/v1/mark-price/%s/current"
kucoinGetMarginConfiguration = "/v1/margin/config"
kucoinGetServerTime = "/v1/timestamp"
kucoinGetServiceStatus = "/v1/status"
// Authenticated endpoints
kucoinGetOrderbook = "/v3/market/orderbook/level2"
kucoinGetMarginAccount = "/v1/margin/account"
kucoinGetMarginRiskLimit = "/v1/risk/limit/strategy"
kucoinBorrowOrder = "/v1/margin/borrow"
kucoinGetOutstandingRecord = "/v1/margin/borrow/outstanding"
kucoinGetRepaidRecord = "/v1/margin/borrow/repaid"
kucoinOneClickRepayment = "/v1/margin/repay/all"
kucoinRepaySingleOrder = "/v1/margin/repay/single"
kucoinLendOrder = "/v1/margin/lend"
kucoinSetAutoLend = "/v1/margin/toggle-auto-lend"
kucoinGetActiveOrder = "/v1/margin/lend/active"
kucoinGetLendHistory = "/v1/margin/lend/done"
kucoinGetUnsettleLendOrder = "/v1/margin/lend/trade/unsettled"
kucoinGetSettleLendOrder = "/v1/margin/lend/trade/settled"
kucoinGetAccountLendRecord = "/v1/margin/lend/assets"
kucoinGetLendingMarketData = "/v1/margin/market"
kucoinGetMarginTradeData = "/v1/margin/trade/last"
kucoinGetIsolatedMarginPairConfig = "/v1/isolated/symbols"
kucoinGetIsolatedMarginAccountInfo = "/v1/isolated/accounts"
kucoinGetSingleIsolatedMarginAccountInfo = "/v1/isolated/account/"
kucoinInitiateIsolatedMarginBorrowing = "/v1/isolated/borrow"
kucoinGetIsolatedOutstandingRepaymentRecords = "/v1/isolated/borrow/outstanding"
kucoinGetIsolatedMarginRepaymentRecords = "/v1/isolated/borrow/repaid"
kucoinInitiateIsolatedMarginQuickRepayment = "/v1/isolated/repay/all"
kucoinInitiateIsolatedMarginSingleRepayment = "/v1/isolated/repay/single"
kucoinPostOrder = "/v1/orders"
kucoinPostMarginOrder = "/v1/margin/order"
kucoinPostBulkOrder = "/v1/orders/multi"
kucoinOrderByID = "/v1/orders/" // used by CancelSingleOrder and GetOrderByID
kucoinOrderByClientOID = "/v1/order/client-order/" // used by CancelOrderByClientOID and GetOrderByClientOID
kucoinOrders = "/v1/orders" // used by CancelAllOpenOrders and GetOrders
kucoinGetRecentOrders = "/v1/limit/orders"
kucoinGetFills = "/v1/fills"
kucoinGetRecentFills = "/v1/limit/fills"
kucoinStopOrder = "/v1/stop-order"
kucoinStopOrderByID = "/v1/stop-order/"
kucoinCancelAllStopOrder = "/v1/stop-order/cancel"
kucoinGetStopOrderByClientID = "/v1/stop-order/queryOrderByClientOid"
kucoinCancelStopOrderByClientID = "/v1/stop-order/cancelOrderByClientOid"
// user info endpoints
kucoinSubUserCreated = "/v2/sub/user/created"
kucoinSubUser = "/v2/sub/user"
kucoinSubAccountSpotAPIs = "/v1/sub/api-key"
kucoinUpdateModifySubAccountSpotAPIs = "/v1/sub/api-key/update"
// account
kucoinAccount = "/v1/accounts"
kucoinGetAccount = "/v1/accounts/"
kucoinGetAccountLedgers = "/v1/accounts/ledgers"
kucoinUserInfo = "/v2/user-info"
kucoinGetSubAccountBalance = "/v1/sub-accounts/"
kucoinGetAggregatedSubAccountBalance = "/v1/sub-accounts"
kucoinGetTransferableBalance = "/v1/accounts/transferable"
kucoinTransferMainToSubAccount = "/v2/accounts/sub-transfer"
kucoinInnerTransfer = "/v2/accounts/inner-transfer"
// deposit
kucoinGetDepositAddressesV2 = "/v2/deposit-addresses"
kucoinGetDepositAddressV1 = "/v1/deposit-addresses"
kucoinGetDepositList = "/v1/deposits"
kucoinGetHistoricalDepositList = "/v1/hist-deposits"
// withdrawal
kucoinWithdrawal = "/v1/withdrawals"
kucoinGetHistoricalWithdrawalList = "/v1/hist-withdrawals"
kucoinGetWithdrawalQuotas = "/v1/withdrawals/quotas"
kucoinCancelWithdrawal = "/v1/withdrawals/"
kucoinBasicFee = "/v1/base-fee"
kucoinTradingFee = "/v1/trade-fees"
)
// GetSymbols gets pairs details on the exchange
// For market details see endpoint: https://www.kucoin.com/docs/rest/spot-trading/market-data/get-market-list
func (ku *Kucoin) GetSymbols(ctx context.Context, market string) ([]SymbolInfo, error) {
params := url.Values{}
if market != "" {
params.Set("market", market)
}
var resp []SymbolInfo
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetSymbols, params), &resp)
}
// GetTicker gets pair ticker information
func (ku *Kucoin) GetTicker(ctx context.Context, pair string) (*Ticker, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
params := url.Values{}
params.Set("symbol", pair)
var resp *Ticker
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetTicker, params), &resp)
if err != nil {
return nil, err
}
if resp == nil {
return nil, common.ErrNoResponse
}
return resp, nil
}
// GetTickers gets all trading pair ticker information including 24h volume
func (ku *Kucoin) GetTickers(ctx context.Context) (*TickersResponse, error) {
var resp *TickersResponse
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, kucoinGetAllTickers, &resp)
}
// Get24hrStats get the statistics of the specified pair in the last 24 hours
func (ku *Kucoin) Get24hrStats(ctx context.Context, pair string) (*Stats24hrs, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
params := url.Values{}
params.Set("symbol", pair)
var resp *Stats24hrs
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGet24hrStats, params), &resp)
}
// GetMarketList get the transaction currency for the entire trading market
func (ku *Kucoin) GetMarketList(ctx context.Context) ([]string, error) {
var resp []string
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, kucoinGetMarketList, &resp)
}
func processOB(ob [][2]string) ([]orderbook.Item, error) {
o := make([]orderbook.Item, len(ob))
for x := range ob {
amount, err := strconv.ParseFloat(ob[x][1], 64)
if err != nil {
return nil, err
}
price, err := strconv.ParseFloat(ob[x][0], 64)
if err != nil {
return nil, err
}
o[x] = orderbook.Item{
Price: price,
Amount: amount,
}
}
return o, nil
}
func constructOrderbook(o *orderbookResponse) (*Orderbook, error) {
var (
s Orderbook
err error
)
s.Bids, err = processOB(o.Bids)
if err != nil {
return nil, err
}
s.Asks, err = processOB(o.Asks)
if err != nil {
return nil, err
}
s.Time = o.Time.Time()
if o.Sequence != "" {
s.Sequence, err = strconv.ParseInt(o.Sequence, 10, 64)
if err != nil {
return nil, err
}
}
return &s, err
}
// GetPartOrderbook20 gets orderbook for a specified pair with depth 20
func (ku *Kucoin) GetPartOrderbook20(ctx context.Context, pair string) (*Orderbook, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
params := url.Values{}
params.Set("symbol", pair)
var o *orderbookResponse
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetPartOrderbook20, params), &o)
if err != nil {
return nil, err
}
return constructOrderbook(o)
}
// GetPartOrderbook100 gets orderbook for a specified pair with depth 100
func (ku *Kucoin) GetPartOrderbook100(ctx context.Context, pair string) (*Orderbook, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
params := url.Values{}
params.Set("symbol", pair)
var o *orderbookResponse
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetPartOrderbook100, params), &o)
if err != nil {
return nil, err
}
return constructOrderbook(o)
}
// GetOrderbook gets full orderbook for a specified pair
func (ku *Kucoin) GetOrderbook(ctx context.Context, pair string) (*Orderbook, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
params := url.Values{}
params.Set("symbol", pair)
var o *orderbookResponse
err := ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, retrieveFullOrderbookEPL, http.MethodGet, common.EncodeURLValues(kucoinGetOrderbook, params), nil, &o)
if err != nil {
return nil, err
}
return constructOrderbook(o)
}
// GetTradeHistory gets trade history of the specified pair
func (ku *Kucoin) GetTradeHistory(ctx context.Context, pair string) ([]Trade, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
params := url.Values{}
params.Set("symbol", pair)
var resp []Trade
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetTradeHistory, params), &resp)
}
// GetKlines gets kline of the specified pair
func (ku *Kucoin) GetKlines(ctx context.Context, pair, period string, start, end time.Time) ([]Kline, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
params := url.Values{}
params.Set("symbol", pair)
if period == "" {
return nil, errors.New("period can not be empty")
}
if !common.StringDataContains(validPeriods, period) {
return nil, errors.New("invalid period")
}
params.Set("type", period)
if !start.IsZero() {
params.Set("startAt", strconv.FormatInt(start.Unix(), 10))
}
if !end.IsZero() {
params.Set("endAt", strconv.FormatInt(end.Unix(), 10))
}
var resp [][7]string
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetKlines, params), &resp)
if err != nil {
return nil, err
}
klines := make([]Kline, len(resp))
for i := range resp {
t, err := strconv.ParseInt(resp[i][0], 10, 64)
if err != nil {
return nil, err
}
klines[i].StartTime = time.Unix(t, 0)
klines[i].Open, err = strconv.ParseFloat(resp[i][1], 64)
if err != nil {
return nil, err
}
klines[i].Close, err = strconv.ParseFloat(resp[i][2], 64)
if err != nil {
return nil, err
}
klines[i].High, err = strconv.ParseFloat(resp[i][3], 64)
if err != nil {
return nil, err
}
klines[i].Low, err = strconv.ParseFloat(resp[i][4], 64)
if err != nil {
return nil, err
}
klines[i].Volume, err = strconv.ParseFloat(resp[i][5], 64)
if err != nil {
return nil, err
}
klines[i].Amount, err = strconv.ParseFloat(resp[i][6], 64)
if err != nil {
return nil, err
}
}
return klines, nil
}
// GetCurrencies gets list of currencies
func (ku *Kucoin) GetCurrencies(ctx context.Context) ([]Currency, error) {
var resp []Currency
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, kucoinGetCurrencies, &resp)
}
// GetCurrencyDetail gets currency detail using currency code and chain information.
func (ku *Kucoin) GetCurrencyDetail(ctx context.Context, ccy, chain string) (*CurrencyDetail, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
if chain != "" {
params.Set("chain", chain)
}
var resp *CurrencyDetail
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetCurrency+strings.ToUpper(ccy), params), &resp)
}
// GetFiatPrice gets fiat prices of currencies, default base currency is USD
func (ku *Kucoin) GetFiatPrice(ctx context.Context, base, currencies string) (map[string]string, error) {
params := url.Values{}
if base != "" {
params.Set("base", base)
}
if currencies != "" {
params.Set("currencies", currencies)
}
var resp map[string]string
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetFiatPrice, params), &resp)
}
// GetMarkPrice gets index price of the specified pair
func (ku *Kucoin) GetMarkPrice(ctx context.Context, pair string) (*MarkPrice, error) {
if pair == "" {
return nil, currency.ErrCurrencyPairEmpty
}
var resp *MarkPrice
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, fmt.Sprintf(kucoinGetMarkPrice, pair), &resp)
}
// GetMarginConfiguration gets configure info of the margin
func (ku *Kucoin) GetMarginConfiguration(ctx context.Context) (*MarginConfiguration, error) {
var resp *MarginConfiguration
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, kucoinGetMarginConfiguration, &resp)
}
// GetMarginAccount gets configure info of the margin
func (ku *Kucoin) GetMarginAccount(ctx context.Context) (*MarginAccounts, error) {
var resp *MarginAccounts
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinGetMarginAccount, nil, &resp)
}
// GetMarginRiskLimit gets cross/isolated margin risk limit, default model is cross margin
func (ku *Kucoin) GetMarginRiskLimit(ctx context.Context, marginModel string) ([]MarginRiskLimit, error) {
params := url.Values{}
if marginModel != "" {
params.Set("marginModel", marginModel)
}
var resp []MarginRiskLimit
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, retrieveMarginAccountEPL, http.MethodGet, common.EncodeURLValues(kucoinGetMarginRiskLimit, params), nil, &resp)
}
// PostBorrowOrder used to post borrow order
func (ku *Kucoin) PostBorrowOrder(ctx context.Context, ccy, orderType, term string, size, maxRate float64) (*PostBorrowOrderResp, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
if orderType == "" {
return nil, errors.New("orderType can not be empty")
}
if size == 0 {
return nil, errors.New("size can not be zero")
}
params := make(map[string]interface{})
params["currency"] = strings.ToUpper(ccy)
params["type"] = orderType
params["size"] = strconv.FormatFloat(size, 'f', -1, 64)
if maxRate != 0 {
params["maxRate"] = strconv.FormatFloat(maxRate, 'f', -1, 64)
}
if term != "" {
params["term"] = term
}
var resp *PostBorrowOrderResp
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinBorrowOrder, params, &resp)
}
// GetBorrowOrder gets borrow order information
func (ku *Kucoin) GetBorrowOrder(ctx context.Context, orderID string) (*BorrowOrder, error) {
if orderID == "" {
return nil, errors.New("empty orderID")
}
params := url.Values{}
params.Set("orderId", orderID)
var resp *BorrowOrder
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinBorrowOrder, params), nil, &resp)
}
// GetOutstandingRecord gets outstanding record information
func (ku *Kucoin) GetOutstandingRecord(ctx context.Context, ccy string) (*OutstandingRecordResponse, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
var resp *OutstandingRecordResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetOutstandingRecord, params), nil, &resp)
}
// GetRepaidRecord gets repaid record information
func (ku *Kucoin) GetRepaidRecord(ctx context.Context, ccy string) (*RepaidRecordsResponse, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
var resp *RepaidRecordsResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetRepaidRecord, params), nil, &resp)
}
// OneClickRepayment used to complete repayment in single go
func (ku *Kucoin) OneClickRepayment(ctx context.Context, ccy, sequence string, size float64) error {
if ccy == "" {
return currency.ErrCurrencyCodeEmpty
}
params := make(map[string]interface{})
params["currency"] = ccy
if sequence == "" {
return errors.New("sequence can not be empty")
}
params["sequence"] = sequence
if size == 0 {
return errors.New("size can not be zero")
}
params["size"] = strconv.FormatFloat(size, 'f', -1, 64)
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinOneClickRepayment, params, &struct{}{})
}
// SingleOrderRepayment used to repay single order
func (ku *Kucoin) SingleOrderRepayment(ctx context.Context, ccy, tradeID string, size float64) error {
if ccy == "" {
return currency.ErrCurrencyCodeEmpty
}
params := make(map[string]interface{})
params["currency"] = ccy
if tradeID == "" {
return errors.New("tradeId can not be empty")
}
params["tradeId"] = tradeID
if size == 0 {
return errors.New("size can not be zero")
}
params["size"] = strconv.FormatFloat(size, 'f', -1, 64)
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinRepaySingleOrder, params, &struct{}{})
}
// PostLendOrder used to create lend order
func (ku *Kucoin) PostLendOrder(ctx context.Context, ccy string, dailyInterestRate, size float64, term int64) (string, error) {
if ccy == "" {
return "", currency.ErrCurrencyPairEmpty
}
params := make(map[string]interface{})
params["currency"] = ccy
if dailyInterestRate == 0 {
return "", errors.New("dailyIntRate can not be zero")
}
params["dailyIntRate"] = strconv.FormatFloat(dailyInterestRate, 'f', -1, 64)
if size == 0 {
return "", errors.New("size can not be zero")
}
params["size"] = strconv.FormatFloat(size, 'f', -1, 64)
if term == 0 {
return "", errors.New("term can not be zero")
}
params["term"] = strconv.FormatInt(term, 10)
resp := struct {
OrderID string `json:"orderId"`
Error
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinLendOrder, params, &resp)
}
// CancelLendOrder used to cancel lend order
func (ku *Kucoin) CancelLendOrder(ctx context.Context, orderID string) error {
resp := struct {
Error
}{}
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodDelete, kucoinLendOrder+"/"+orderID, nil, &resp)
}
// SetAutoLend used to set up the automatic lending for a specified currency
func (ku *Kucoin) SetAutoLend(ctx context.Context, ccy string, dailyInterestRate, retainSize float64, term int64, isEnable bool) error {
if ccy == "" {
return currency.ErrCurrencyCodeEmpty
}
params := make(map[string]interface{})
params["currency"] = ccy
if dailyInterestRate == 0 {
return errors.New("dailyIntRate can not be zero")
}
params["dailyIntRate"] = strconv.FormatFloat(dailyInterestRate, 'f', -1, 64)
if retainSize == 0 {
return errors.New("retainSize can not be zero")
}
params["retainSize"] = strconv.FormatFloat(retainSize, 'f', -1, 64)
if term == 0 {
return errors.New("term can not be zero")
}
params["term"] = strconv.FormatInt(term, 10)
params["isEnable"] = isEnable
resp := struct {
Error
}{}
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinSetAutoLend, params, &resp)
}
// GetActiveOrder gets active lend orders
func (ku *Kucoin) GetActiveOrder(ctx context.Context, ccy string) ([]LendOrder, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
resp := struct {
Data []LendOrder `json:"items"`
Error
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetActiveOrder, params), nil, &resp)
}
// GetLendHistory gets lend orders
func (ku *Kucoin) GetLendHistory(ctx context.Context, ccy string) ([]LendOrderHistory, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
resp := struct {
Data []LendOrderHistory `json:"items"`
Error
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetLendHistory, params), nil, &resp)
}
// GetUnsettledLendOrder gets outstanding lend order list
func (ku *Kucoin) GetUnsettledLendOrder(ctx context.Context, ccy string) ([]UnsettleLendOrder, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
resp := struct {
Data []UnsettleLendOrder `json:"items"`
Error
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetUnsettleLendOrder, params), nil, &resp)
}
// GetSettledLendOrder gets settle lend orders
func (ku *Kucoin) GetSettledLendOrder(ctx context.Context, ccy string) ([]SettleLendOrder, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
resp := struct {
Data []SettleLendOrder `json:"items"`
Error
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetSettleLendOrder, params), nil, &resp)
}
// GetAccountLendRecord get the lending history of the main account
func (ku *Kucoin) GetAccountLendRecord(ctx context.Context, ccy string) ([]LendRecord, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
var resp []LendRecord
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetAccountLendRecord, params), nil, &resp)
}
// GetLendingMarketData get the lending market data
func (ku *Kucoin) GetLendingMarketData(ctx context.Context, ccy string, term int64) ([]LendMarketData, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy)
if term != 0 {
params.Set("term", strconv.FormatInt(term, 10))
}
var resp []LendMarketData
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetLendingMarketData, params), nil, &resp)
}
// GetMarginTradeData get the last 300 fills in the lending and borrowing market
func (ku *Kucoin) GetMarginTradeData(ctx context.Context, ccy string) ([]MarginTradeData, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy)
var resp []MarginTradeData
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetMarginTradeData, params), nil, &resp)
}
// GetIsolatedMarginPairConfig get the current isolated margin trading pair configuration
func (ku *Kucoin) GetIsolatedMarginPairConfig(ctx context.Context) ([]IsolatedMarginPairConfig, error) {
var resp []IsolatedMarginPairConfig
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinGetIsolatedMarginPairConfig, nil, &resp)
}
// GetIsolatedMarginAccountInfo get all isolated margin accounts of the current user
func (ku *Kucoin) GetIsolatedMarginAccountInfo(ctx context.Context, balanceCurrency string) (*IsolatedMarginAccountInfo, error) {
params := url.Values{}
if balanceCurrency != "" {
params.Set("balanceCurrency", balanceCurrency)
}
var resp *IsolatedMarginAccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetIsolatedMarginAccountInfo, params), nil, &resp)
}
// GetSingleIsolatedMarginAccountInfo get single isolated margin accounts of the current user
func (ku *Kucoin) GetSingleIsolatedMarginAccountInfo(ctx context.Context, symbol string) (*AssetInfo, error) {
if symbol == "" {
return nil, errors.New("symbol can not be empty")
}
var resp *AssetInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinGetSingleIsolatedMarginAccountInfo+symbol, nil, &resp)
}
// InitiateIsolatedMarginBorrowing initiates isolated margin borrowing
func (ku *Kucoin) InitiateIsolatedMarginBorrowing(ctx context.Context, symbol, ccy, borrowStrategy, period string, size, maxRate int64) (*IsolatedMarginBorrowing, error) {
if symbol == "" {
return nil, errors.New("symbol can not be empty")
}
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := make(map[string]interface{})
params["symbol"] = symbol
params["currency"] = ccy
if borrowStrategy == "" {
return nil, errors.New("borrowStrategy can not be empty")
}
params["borrowStrategy"] = borrowStrategy
if size == 0 {
return nil, errors.New("size can not be zero")
}
params["size"] = strconv.FormatInt(size, 10)
if period != "" {
params["period"] = period
}
if maxRate == 0 {
params["maxRate"] = strconv.FormatInt(maxRate, 10)
}
var resp *IsolatedMarginBorrowing
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinInitiateIsolatedMarginBorrowing, params, &resp)
}
// GetIsolatedOutstandingRepaymentRecords get the outstanding repayment records of isolated margin positions
func (ku *Kucoin) GetIsolatedOutstandingRepaymentRecords(ctx context.Context, symbol, ccy string, pageSize, currentPage int64) (*OutstandingRepaymentRecordsResponse, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if ccy != "" {
params.Set("currency", ccy)
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
var resp *OutstandingRepaymentRecordsResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetIsolatedOutstandingRepaymentRecords, params), nil, &resp)
}
// GetIsolatedMarginRepaymentRecords get the repayment records of isolated margin positions
func (ku *Kucoin) GetIsolatedMarginRepaymentRecords(ctx context.Context, symbol, ccy string, pageSize, currentPage int64) (*CompletedRepaymentRecordsResponse, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if ccy != "" {
params.Set("currency", ccy)
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
var resp *CompletedRepaymentRecordsResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetIsolatedMarginRepaymentRecords, params), nil, &resp)
}
// InitiateIsolatedMarginQuickRepayment is used to initiate quick repayment for isolated margin accounts
func (ku *Kucoin) InitiateIsolatedMarginQuickRepayment(ctx context.Context, symbol, ccy, seqStrategy string, size int64) error {
if symbol == "" {
return currency.ErrCurrencyPairEmpty
}
if size == 0 {
return errors.New("size can not be zero")
}
if seqStrategy == "" {
return errors.New("seqStrategy can not be empty")
}
if ccy == "" {
return currency.ErrCurrencyCodeEmpty
}
params := make(map[string]interface{})
params["symbol"] = symbol
params["currency"] = ccy
params["seqStrategy"] = seqStrategy
params["size"] = strconv.FormatInt(size, 10)
resp := struct {
Error
}{}
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinInitiateIsolatedMarginQuickRepayment, params, &resp)
}
// InitiateIsolatedMarginSingleRepayment is used to initiate quick repayment for single margin accounts
func (ku *Kucoin) InitiateIsolatedMarginSingleRepayment(ctx context.Context, symbol, ccy, loanID string, size int64) error {
if symbol == "" {
return currency.ErrCurrencyPairEmpty
}
params := make(map[string]interface{})
params["symbol"] = symbol
if ccy == "" {
return currency.ErrCurrencyCodeEmpty
}
params["currency"] = ccy
if loanID == "" {
return errors.New("loanId can not be empty")
}
params["loanId"] = loanID
if size == 0 {
return errors.New("size can not be zero")
}
params["size"] = strconv.FormatInt(size, 10)
resp := struct {
Error
}{}
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinInitiateIsolatedMarginSingleRepayment, params, &resp)
}
// GetCurrentServerTime gets the server time
func (ku *Kucoin) GetCurrentServerTime(ctx context.Context) (time.Time, error) {
resp := struct {
Timestamp convert.ExchangeTime `json:"data"`
Error
}{}
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, kucoinGetServerTime, &resp)
if err != nil {
return time.Time{}, err
}
return resp.Timestamp.Time(), nil
}
// GetServiceStatus gets the service status
func (ku *Kucoin) GetServiceStatus(ctx context.Context) (*ServiceStatus, error) {
var resp *ServiceStatus
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, kucoinGetServiceStatus, &resp)
}
// PostOrder used to place two types of orders: limit and market
// Note: use this only for SPOT trades
func (ku *Kucoin) PostOrder(ctx context.Context, arg *SpotOrderParam) (string, error) {
if arg.ClientOrderID == "" {
// NOTE: 128 bit max length character string. UUID recommended.
return "", errInvalidClientOrderID
}
if arg.Side == "" {
return "", order.ErrSideIsInvalid
}
if arg.Symbol.IsEmpty() {
return "", fmt.Errorf("%w, empty symbol", currency.ErrCurrencyPairEmpty)
}
switch arg.OrderType {
case "limit", "":
if arg.Price <= 0 {
return "", fmt.Errorf("%w, price =%.3f", errInvalidPrice, arg.Price)
}
if arg.Size <= 0 {
return "", errInvalidSize
}
if arg.VisibleSize < 0 {
return "", fmt.Errorf("%w, visible size must be non-zero positive value", errInvalidSize)
}
case "market":
if arg.Size == 0 && arg.Funds == 0 {
return "", errSizeOrFundIsRequired
}
default:
return "", fmt.Errorf("%w %s", order.ErrTypeIsInvalid, arg.OrderType)
}
var resp struct {
Data struct {
OrderID string `json:"orderId"`
} `json:"data"`
Error
}
return resp.Data.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeOrderEPL, http.MethodPost, kucoinPostOrder, &arg, &resp)
}
// PostMarginOrder used to place two types of margin orders: limit and market
func (ku *Kucoin) PostMarginOrder(ctx context.Context, arg *MarginOrderParam) (*PostMarginOrderResp, error) {
if arg.ClientOrderID == "" {
return nil, errInvalidClientOrderID
}
if arg.Side == "" {
return nil, order.ErrSideIsInvalid
}
if arg.Symbol.IsEmpty() {
return nil, fmt.Errorf("%w, empty symbol", currency.ErrCurrencyPairEmpty)
}
arg.OrderType = strings.ToLower(arg.OrderType)
switch arg.OrderType {
case "limit", "":
if arg.Price <= 0 {
return nil, fmt.Errorf("%w, price=%.3f", errInvalidPrice, arg.Price)
}
if arg.Size <= 0 {
return nil, errInvalidSize
}
if arg.VisibleSize < 0 {
return nil, fmt.Errorf("%w, visible size must be non-zero positive value", errInvalidSize)
}
case "market":
sum := arg.Size + arg.Funds
if sum <= 0 || (sum != arg.Size && sum != arg.Funds) {
return nil, fmt.Errorf("%w, either 'size' or 'funds' has to be set, but not both", errSizeOrFundIsRequired)
}
default:
return nil, fmt.Errorf("%w %s", order.ErrTypeIsInvalid, arg.OrderType)
}
resp := struct {
PostMarginOrderResp
Error
}{}
return &resp.PostMarginOrderResp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeMarginOrdersEPL, http.MethodPost, kucoinPostMarginOrder, &arg, &resp)
}
// PostBulkOrder used to place 5 orders at the same time. The order type must be a limit order of the same symbol
// Note: it supports only SPOT trades
// Note: To check if order was posted successfully, check status field in response
func (ku *Kucoin) PostBulkOrder(ctx context.Context, symbol string, orderList []OrderRequest) ([]PostBulkOrderResp, error) {
if symbol == "" {
return nil, errors.New("symbol can not be empty")
}
for i := range orderList {
if orderList[i].ClientOID == "" {
return nil, errors.New("clientOid can not be empty")
}
if orderList[i].Side == "" {
return nil, errors.New("side can not be empty")
}
if orderList[i].Price <= 0 {
return nil, errors.New("price must be positive")
}
if orderList[i].Size <= 0 {
return nil, errors.New("size must be positive")
}
}
params := make(map[string]interface{})
params["symbol"] = symbol
params["orderList"] = orderList
resp := &struct {
Data []PostBulkOrderResp `json:"data"`
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeBulkOrdersEPL, http.MethodPost, kucoinPostBulkOrder, params, &resp)
}
// CancelSingleOrder used to cancel single order previously placed
func (ku *Kucoin) CancelSingleOrder(ctx context.Context, orderID string) ([]string, error) {
if orderID == "" {
return nil, errors.New("orderID can not be empty")
}
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
Error
}{}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelOrderEPL, http.MethodDelete, kucoinOrderByID+orderID, nil, &resp)
}
// CancelOrderByClientOID used to cancel order via the clientOid
func (ku *Kucoin) CancelOrderByClientOID(ctx context.Context, orderID string) (*CancelOrderResponse, error) {
var resp *CancelOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodDelete, kucoinOrderByClientOID+orderID, nil, &resp)
}
// CancelAllOpenOrders used to cancel all order based upon the parameters passed
func (ku *Kucoin) CancelAllOpenOrders(ctx context.Context, symbol, tradeType string) ([]string, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if tradeType != "" {
params.Set("tradeType", tradeType)
}
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
Error
}{}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelAllOrdersEPL, http.MethodDelete, common.EncodeURLValues(kucoinOrders, params), nil, &resp)
}
// ListOrders gets the user order list
func (ku *Kucoin) ListOrders(ctx context.Context, status, symbol, side, orderType, tradeType string, startAt, endAt time.Time) (*OrdersListResponse, error) {
params := fillParams(symbol, side, orderType, tradeType, startAt, endAt)
if status != "" {
params.Set("status", status)
}
var resp *OrdersListResponse
err := ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, listOrdersEPL, http.MethodGet, common.EncodeURLValues(kucoinOrders, params), nil, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
func fillParams(symbol, side, orderType, tradeType string, startAt, endAt time.Time) url.Values {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if side != "" {
params.Set("side", side)
}
if orderType != "" {
params.Set("type", orderType)
}
if tradeType != "" {
params.Set("tradeType", tradeType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
return params
}
// GetRecentOrders get orders in the last 24 hours.
func (ku *Kucoin) GetRecentOrders(ctx context.Context) ([]OrderDetail, error) {
var resp []OrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinGetRecentOrders, nil, &resp)
}
// GetOrderByID get a single order info by order ID
func (ku *Kucoin) GetOrderByID(ctx context.Context, orderID string) (*OrderDetail, error) {
if orderID == "" {
return nil, errors.New("orderID can not be empty")
}
var resp *OrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinOrderByID+orderID, nil, &resp)
}
// GetOrderByClientSuppliedOrderID get a single order info by client order ID
func (ku *Kucoin) GetOrderByClientSuppliedOrderID(ctx context.Context, clientOID string) (*OrderDetail, error) {
if clientOID == "" {
return nil, errors.New("client order ID can not be empty")
}
var resp *OrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinOrderByClientOID+clientOID, nil, &resp)
}
// GetFills get fills
func (ku *Kucoin) GetFills(ctx context.Context, orderID, symbol, side, orderType, tradeType string, startAt, endAt time.Time) (*ListFills, error) {
params := fillParams(symbol, side, orderType, tradeType, startAt, endAt)
if orderID != "" {
params.Set("orderId", orderID)
}
var resp *ListFills
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, listFillsEPL, http.MethodGet, common.EncodeURLValues(kucoinGetFills, params), nil, &resp)
}
// GetRecentFills get a list of 1000 fills in last 24 hours
func (ku *Kucoin) GetRecentFills(ctx context.Context) ([]Fill, error) {
var resp []Fill
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinGetRecentFills, nil, &resp)
}
// PostStopOrder used to place two types of stop orders: limit and market
func (ku *Kucoin) PostStopOrder(ctx context.Context, clientOID, side, symbol, orderType, remark, stop, stp, tradeType, timeInForce string, size, price, stopPrice, cancelAfter, visibleSize, funds float64, postOnly, hidden, iceberg bool) (string, error) {
params := make(map[string]interface{})
if clientOID == "" {
return "", errors.New("clientOid can not be empty")
}
params["clientOid"] = clientOID
if side == "" {
return "", errors.New("side can not be empty")
}
params["side"] = side
if symbol == "" {
return "", fmt.Errorf("%w, empty symbol", currency.ErrCurrencyPairEmpty)
}
params["symbol"] = symbol
if remark != "" {
params["remark"] = remark
}
if stop != "" {
params["stop"] = stop
if stopPrice <= 0 {
return "", errors.New("stopPrice is required")
}
params["stopPrice"] = strconv.FormatFloat(stopPrice, 'f', -1, 64)
}
if stp != "" {
params["stp"] = stp
}
if tradeType != "" {
params["tradeType"] = tradeType
}
orderType = strings.ToLower(orderType)
switch orderType {
case "limit", "":
if price <= 0 {
return "", errors.New("price is required")
}
params["price"] = strconv.FormatFloat(price, 'f', -1, 64)
if size <= 0 {
return "", errors.New("size can not be zero or negative")
}
params["size"] = strconv.FormatFloat(size, 'f', -1, 64)
if timeInForce != "" {
params["timeInForce"] = timeInForce
}
if cancelAfter > 0 && timeInForce == "GTT" {
params["cancelAfter"] = strconv.FormatFloat(cancelAfter, 'f', -1, 64)
}
params["postOnly"] = postOnly
params["hidden"] = hidden
params["iceberg"] = iceberg
if visibleSize > 0 {
params["visibleSize"] = strconv.FormatFloat(visibleSize, 'f', -1, 64)
}
case "market":
switch {
case size > 0:
params["size"] = strconv.FormatFloat(size, 'f', -1, 64)
case funds > 0:
params["funds"] = strconv.FormatFloat(funds, 'f', -1, 64)
default:
return "", errSizeOrFundIsRequired
}
default:
return "", fmt.Errorf("%w, order type: %s", order.ErrTypeIsInvalid, orderType)
}
if orderType != "" {
params["type"] = orderType
}
resp := struct {
OrderID string `json:"orderId"`
Error
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeOrderEPL, http.MethodPost, kucoinStopOrder, params, &resp)
}
// CancelStopOrder used to cancel single stop order previously placed
func (ku *Kucoin) CancelStopOrder(ctx context.Context, orderID string) ([]string, error) {
if orderID == "" {
return nil, errors.New("orderID can not be empty")
}
resp := struct {
Data []string `json:"cancelledOrderIds"`
Error
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodDelete, kucoinStopOrderByID+orderID, nil, &resp)
}
// CancelStopOrders used to cancel all order based upon the parameters passed
func (ku *Kucoin) CancelStopOrders(ctx context.Context, symbol, tradeType, orderIDs string) ([]string, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if tradeType != "" {
params.Set("tradeType", tradeType)
}
if orderIDs != "" {
params.Set("orderIds", orderIDs)
}
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
Error
}{}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodDelete, common.EncodeURLValues(kucoinCancelAllStopOrder, params), nil, &resp)
}
// GetStopOrder used to cancel single stop order previously placed
func (ku *Kucoin) GetStopOrder(ctx context.Context, orderID string) (*StopOrder, error) {
if orderID == "" {
return nil, errors.New("orderID can not be empty")
}
resp := struct {
StopOrder
Error
}{}
return &resp.StopOrder, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinStopOrderByID+orderID, nil, &resp)
}
// ListStopOrders get all current untriggered stop orders
func (ku *Kucoin) ListStopOrders(ctx context.Context, symbol, side, orderType, tradeType, orderIDs string, startAt, endAt time.Time, currentPage, pageSize int64) (*StopOrderListResponse, error) {
params := fillParams(symbol, side, orderType, tradeType, startAt, endAt)
if orderIDs != "" {
params.Set("orderIds", orderIDs)
}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *StopOrderListResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinStopOrder, params), nil, &resp)
}
// GetStopOrderByClientID get a stop order information via the clientOID
func (ku *Kucoin) GetStopOrderByClientID(ctx context.Context, symbol, clientOID string) ([]StopOrder, error) {
if clientOID == "" {
return nil, errors.New("clientOID can not be empty")
}
params := url.Values{}
params.Set("clientOid", clientOID)
if symbol != "" {
params.Set("symbol", symbol)
}
var resp []StopOrder
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetStopOrderByClientID, params), nil, &resp)
}
// CancelStopOrderByClientID used to cancel a stop order via the clientOID.
func (ku *Kucoin) CancelStopOrderByClientID(ctx context.Context, symbol, clientOID string) (*CancelOrderResponse, error) {
if clientOID == "" {
return nil, errors.New("clientOID can not be empty")
}
params := url.Values{}
params.Set("clientOid", clientOID)
if symbol != "" {
params.Set("symbol", symbol)
}
var resp *CancelOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodDelete, common.EncodeURLValues(kucoinCancelStopOrderByClientID, params), nil, &resp)
}
// CreateSubUser creates a new sub-user for the account.
func (ku *Kucoin) CreateSubUser(ctx context.Context, subAccountName, password, remarks, access string) (*SubAccount, error) {
params := make(map[string]interface{})
if regexp.MustCompile("^[a-zA-Z0-9]{7-32}$").MatchString(subAccountName) {
return nil, errors.New("invalid sub-account name")
}
if regexp.MustCompile("^[a-zA-Z0-9]{7-24}$").MatchString(password) {
return nil, errInvalidPassPhraseInstance
}
params["subName"] = subAccountName
params["password"] = password
if remarks != "" {
params["remarks"] = remarks
}
if access != "" {
params["access"] = access
}
var resp *SubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinSubUserCreated, params, &resp)
}
// GetSubAccountSpotAPIList used to obtain a list of Spot APIs pertaining to a sub-account.
func (ku *Kucoin) GetSubAccountSpotAPIList(ctx context.Context, subAccountName, apiKeys string) (*SubAccountResponse, error) {
params := url.Values{}
if subAccountRegExp.MatchString(subAccountName) {
return nil, errInvalidSubAccountName
}
params.Set("subName", subAccountName)
if apiKeys != "" {
params.Set("apiKey", apiKeys)
}
var resp SubAccountResponse
return &resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinSubAccountSpotAPIs, params), nil, &resp)
}
// CreateSpotAPIsForSubAccount can be used to create Spot APIs for sub-accounts.
func (ku *Kucoin) CreateSpotAPIsForSubAccount(ctx context.Context, arg *SpotAPISubAccountParams) (*SpotAPISubAccount, error) {
if subAccountRegExp.MatchString(arg.SubAccountName) {
return nil, errInvalidSubAccountName
}
if subAccountPassphraseRegExp.MatchString(arg.Passphrase) {
return nil, fmt.Errorf("%w, must contain 7-32 characters. cannot contain any spaces", errInvalidPassPhraseInstance)
}
if arg.Remark == "" {
return nil, errors.New("remark is required")
}
var resp *SpotAPISubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinSubAccountSpotAPIs, &arg, &resp)
}
// ModifySubAccountSpotAPIs modifies sub-account Spot APIs.
func (ku *Kucoin) ModifySubAccountSpotAPIs(ctx context.Context, arg *SpotAPISubAccountParams) (*SpotAPISubAccount, error) {
if subAccountRegExp.MatchString(arg.SubAccountName) {
return nil, errInvalidSubAccountName
}
if subAccountPassphraseRegExp.MatchString(arg.Passphrase) {
return nil, fmt.Errorf("%w, must contain 7-32 characters. cannot contain any spaces", errInvalidPassPhraseInstance)
}
if arg.Remark == "" {
return nil, errors.New("remark is required")
}
var resp *SpotAPISubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPut, kucoinUpdateModifySubAccountSpotAPIs, &arg, &resp)
}
// DeleteSubAccountSpotAPI delete sub-account Spot APIs.
func (ku *Kucoin) DeleteSubAccountSpotAPI(ctx context.Context, apiKey, passphrase, subAccountName string) (*DeleteSubAccountResponse, error) {
if subAccountRegExp.MatchString(subAccountName) {
return nil, errInvalidSubAccountName
}
if subAccountPassphraseRegExp.MatchString(passphrase) {
return nil, fmt.Errorf("%w, must contain 7-32 characters. cannot contain any spaces", errInvalidPassPhraseInstance)
}
if apiKey == "" {
return nil, errors.New("apiKey is required")
}
params := url.Values{}
params.Set("apiKey", apiKey)
params.Set("passphrase", passphrase)
params.Set("subName", subAccountName)
var resp *DeleteSubAccountResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodDelete, common.EncodeURLValues(kucoinSubAccountSpotAPIs, params), nil, &resp)
}
// GetUserInfoOfAllSubAccounts get the user info of all sub-users via this interface.
func (ku *Kucoin) GetUserInfoOfAllSubAccounts(ctx context.Context) (*SubAccountResponse, error) {
var resp *SubAccountResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinSubUser, nil, &resp)
}
// GetPaginatedListOfSubAccounts to retrieve a paginated list of sub-accounts. Pagination is required.
func (ku *Kucoin) GetPaginatedListOfSubAccounts(ctx context.Context, currentPage, pageSize int64) (*SubAccountResponse, error) {
params := url.Values{}
if pageSize > 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
if currentPage > 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
var resp *SubAccountResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinSubUser, params), nil, &resp)
}
// GetAllAccounts get all accounts
func (ku *Kucoin) GetAllAccounts(ctx context.Context, ccy, accountType string) ([]AccountInfo, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
if accountType != "" {
params.Set("type", accountType)
}
var resp []AccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinAccount, params), nil, &resp)
}
// GetAccount get information of single account
func (ku *Kucoin) GetAccount(ctx context.Context, accountID string) (*AccountInfo, error) {
var resp *AccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinGetAccount+accountID, nil, &resp)
}
// GetAccountLedgers get the history of deposit/withdrawal of all accounts, supporting inquiry of various currencies
func (ku *Kucoin) GetAccountLedgers(ctx context.Context, ccy, direction, bizType string, startAt, endAt time.Time) (*AccountLedgerResponse, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
if direction != "" {
params.Set("direction", direction)
}
if bizType != "" {
params.Set("bizType", bizType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *AccountLedgerResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, retrieveAccountLedgerEPL, http.MethodGet, common.EncodeURLValues(kucoinGetAccountLedgers, params), nil, &resp)
}
// GetAccountSummaryInformation this can be used to obtain account summary information.
func (ku *Kucoin) GetAccountSummaryInformation(ctx context.Context) (*AccountSummaryInformation, error) {
var resp *AccountSummaryInformation
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinUserInfo, nil, &resp)
}
// GetSubAccountBalance get account info of a sub-user specified by the subUserID
func (ku *Kucoin) GetSubAccountBalance(ctx context.Context, subUserID string, includeBaseAmount bool) (*SubAccountInfo, error) {
params := url.Values{}
if includeBaseAmount {
params.Set("includeBaseAmount", "true")
}
var resp *SubAccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetSubAccountBalance+subUserID, params), nil, &resp)
}
// GetAggregatedSubAccountBalance get the account info of all sub-users
func (ku *Kucoin) GetAggregatedSubAccountBalance(ctx context.Context) ([]SubAccountInfo, error) {
var resp []SubAccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, kucoinGetAggregatedSubAccountBalance, nil, &resp)
}
// GetPaginatedSubAccountInformation this endpoint can be used to get paginated sub-account information. Pagination is required.
func (ku *Kucoin) GetPaginatedSubAccountInformation(ctx context.Context, currentPage, pageSize int64) ([]SubAccountInfo, error) {
params := url.Values{}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp []SubAccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetAggregatedSubAccountBalance, params), nil, &resp)
}
// GetTransferableBalance get the transferable balance of a specified account
func (ku *Kucoin) GetTransferableBalance(ctx context.Context, ccy, accountType, tag string) (*TransferableBalanceInfo, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy)
if accountType == "" {
return nil, errors.New("accountType can not be empty")
}
params.Set("type", accountType)
if tag != "" {
params.Set("tag", tag)
}
var resp *TransferableBalanceInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetTransferableBalance, params), nil, &resp)
}
// TransferMainToSubAccount used to transfer funds from main account to sub-account
func (ku *Kucoin) TransferMainToSubAccount(ctx context.Context, clientOID, ccy, amount, direction, accountType, subAccountType, subUserID string) (string, error) {
if clientOID == "" {
return "", errors.New("clientOID can not be empty")
}
if ccy == "" {
return "", currency.ErrCurrencyPairEmpty
}
if amount == "" {
return "", errors.New("amount can not be empty")
}
if direction == "" {
return "", errors.New("direction can not be empty")
}
if subUserID == "" {
return "", errors.New("subUserID can not be empty")
}
params := make(map[string]interface{})
params["clientOid"] = clientOID
params["currency"] = ccy
params["amount"] = amount
params["direction"] = direction
if accountType != "" {
params["accountType"] = accountType
}
if subAccountType != "" {
params["subAccountType"] = subAccountType
}
params["subUserId"] = subUserID
resp := struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, masterSubUserTransferEPL, http.MethodPost, kucoinTransferMainToSubAccount, params, &resp)
}
// MakeInnerTransfer used to transfer funds between accounts internally
func (ku *Kucoin) MakeInnerTransfer(ctx context.Context, clientOID, ccy, from, to, amount, fromTag, toTag string) (string, error) {
if clientOID == "" {
return "", errors.New("clientOID can not be empty")
}
if ccy == "" {
return "", currency.ErrCurrencyPairEmpty
}
if amount == "" {
return "", errors.New("amount can not be empty")
}
if from == "" {
return "", errors.New("from can not be empty")
}
if to == "" {
return "", errors.New("to can not be empty")
}
params := make(map[string]interface{})
params["clientOid"] = clientOID
params["currency"] = ccy
params["amount"] = amount
params["from"] = from
params["to"] = to
if fromTag != "" {
params["fromTag"] = fromTag
}
if toTag != "" {
params["toTag"] = toTag
}
resp := struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinInnerTransfer, params, &resp)
}
// CreateDepositAddress create a deposit address for a currency you intend to deposit
func (ku *Kucoin) CreateDepositAddress(ctx context.Context, ccy, chain string) (*DepositAddress, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := make(map[string]interface{})
params["currency"] = ccy
if chain != "" {
params["chain"] = chain
}
var resp *DepositAddress
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinGetDepositAddressV1, params, &resp)
}
// GetDepositAddressesV2 get all deposit addresses for the currency you intend to deposit
func (ku *Kucoin) GetDepositAddressesV2(ctx context.Context, ccy string) ([]DepositAddress, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy)
var resp []DepositAddress
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetDepositAddressesV2, params), nil, &resp)
}
// GetDepositAddressV1 get a deposit address for the currency you intend to deposit
func (ku *Kucoin) GetDepositAddressV1(ctx context.Context, ccy, chain string) (*DepositAddress, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy)
if chain != "" {
params.Set("chain", chain)
}
var resp DepositAddress
return &resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetDepositAddressV1, params), nil, &resp)
}
// GetDepositList get deposit list items and sorted to show the latest first
func (ku *Kucoin) GetDepositList(ctx context.Context, ccy, status string, startAt, endAt time.Time) (*DepositResponse, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
if status != "" {
params.Set("status", status)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *DepositResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, retrieveDepositListEPL, http.MethodGet, common.EncodeURLValues(kucoinGetDepositList, params), nil, &resp)
}
// GetHistoricalDepositList get historical deposit list items
func (ku *Kucoin) GetHistoricalDepositList(ctx context.Context, ccy, status string, startAt, endAt time.Time) (*HistoricalDepositWithdrawalResponse, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
if status != "" {
params.Set("status", status)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *HistoricalDepositWithdrawalResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, retrieveV1HistoricalDepositListEPL, http.MethodGet, common.EncodeURLValues(kucoinGetHistoricalDepositList, params), nil, &resp)
}
// GetWithdrawalList get withdrawal list items
func (ku *Kucoin) GetWithdrawalList(ctx context.Context, ccy, status string, startAt, endAt time.Time) (*WithdrawalsResponse, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
if status != "" {
params.Set("status", status)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *WithdrawalsResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, retrieveWithdrawalListEPL, http.MethodGet, common.EncodeURLValues(kucoinWithdrawal, params), nil, &resp)
}
// GetHistoricalWithdrawalList get historical withdrawal list items
func (ku *Kucoin) GetHistoricalWithdrawalList(ctx context.Context, ccy, status string, startAt, endAt time.Time, currentPage, pageSize int64) (*HistoricalDepositWithdrawalResponse, error) {
params := url.Values{}
if ccy != "" {
params.Set("currency", ccy)
}
if status != "" {
params.Set("status", status)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *HistoricalDepositWithdrawalResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, retrieveV1HistoricalWithdrawalListEPL, http.MethodGet, common.EncodeURLValues(kucoinGetHistoricalWithdrawalList, params), nil, &resp)
}
// GetWithdrawalQuotas get withdrawal quota details
func (ku *Kucoin) GetWithdrawalQuotas(ctx context.Context, ccy, chain string) (*WithdrawalQuota, error) {
if ccy == "" {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy)
if chain != "" {
params.Set("chain", chain)
}
var resp *WithdrawalQuota
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinGetWithdrawalQuotas, params), nil, &resp)
}
// ApplyWithdrawal create a withdrawal request
// The endpoint was deprecated for futures, please transfer assets from the FUTURES account to the MAIN account first, and then withdraw from the MAIN account
func (ku *Kucoin) ApplyWithdrawal(ctx context.Context, ccy, address, memo, remark, chain, feeDeductType string, isInner bool, amount float64) (string, error) {
if ccy == "" {
return "", currency.ErrCurrencyPairEmpty
}
params := make(map[string]interface{})
params["currency"] = ccy
if address == "" {
return "", errors.New("address can not be empty")
}
params["address"] = address
if amount == 0 {
return "", errors.New("amount can not be empty")
}
params["amount"] = amount
if memo != "" {
params["memo"] = memo
}
params["isInner"] = isInner
if remark != "" {
params["remark"] = remark
}
if chain != "" {
params["chain"] = chain
}
if feeDeductType != "" {
params["feeDeductType"] = feeDeductType
}
resp := struct {
WithdrawalID string `json:"withdrawalId"`
Error
}{}
return resp.WithdrawalID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodPost, kucoinWithdrawal, params, &resp)
}
// CancelWithdrawal used to cancel a withdrawal request
func (ku *Kucoin) CancelWithdrawal(ctx context.Context, withdrawalID string) error {
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodDelete, kucoinCancelWithdrawal+withdrawalID, nil, &struct{}{})
}
// GetBasicFee get basic fee rate of users
func (ku *Kucoin) GetBasicFee(ctx context.Context, currencyType string) (*Fees, error) {
params := url.Values{}
if currencyType != "" {
params.Set("currencyType", currencyType)
}
var resp *Fees
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, common.EncodeURLValues(kucoinBasicFee, params), nil, &resp)
}
// GetTradingFee get fee rate of trading pairs
// WARNING: There is a limit of 10 currency pairs allowed to be requested per call.
func (ku *Kucoin) GetTradingFee(ctx context.Context, pairs currency.Pairs) ([]Fees, error) {
if len(pairs) == 0 {
return nil, currency.ErrCurrencyPairsEmpty
}
path := kucoinTradingFee + "?symbols=" + pairs.Upper().Join()
var resp []Fees
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, http.MethodGet, path, nil, &resp)
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (ku *Kucoin) SendHTTPRequest(ctx context.Context, ePath exchange.URL, epl request.EndpointLimit, path string, result interface{}) error {
value := reflect.ValueOf(result)
if value.Kind() != reflect.Pointer {
return errInvalidResultInterface
}
resp, okay := result.(UnmarshalTo)
if !okay {
resp = &Response{Data: result}
}
endpointPath, err := ku.API.Endpoints.GetURL(ePath)
if err != nil {
return err
}
err = ku.SendPayload(ctx, epl, func() (*request.Item, error) {
return &request.Item{
Method: http.MethodGet,
Path: endpointPath + path,
Result: resp,
Verbose: ku.Verbose,
HTTPDebugging: ku.HTTPDebugging,
HTTPRecording: ku.HTTPRecording}, nil
}, request.UnauthenticatedRequest)
if err != nil {
return err
}
if result == nil {
return errNoValidResponseFromServer
}
return resp.GetError()
}
// SendAuthHTTPRequest sends an authenticated HTTP request
// Request parameters are added to path variable for GET and DELETE request and for other requests its passed in params variable
func (ku *Kucoin) SendAuthHTTPRequest(ctx context.Context, ePath exchange.URL, epl request.EndpointLimit, method, path string, arg, result interface{}) error {
value := reflect.ValueOf(result)
if value.Kind() != reflect.Pointer {
return errInvalidResultInterface
}
creds, err := ku.GetCredentials(ctx)
if err != nil {
return err
}
resp, okay := result.(UnmarshalTo)
if !okay {
resp = &Response{Data: result}
}
endpointPath, err := ku.API.Endpoints.GetURL(ePath)
if err != nil {
return err
}
if value.IsNil() || value.Kind() != reflect.Pointer {
return fmt.Errorf("%w receiver has to be non-nil pointer", errInvalidResponseReceiver)
}
err = ku.SendPayload(ctx, epl, func() (*request.Item, error) {
var (
body io.Reader
payload []byte
)
if arg != nil {
payload, err = json.Marshal(arg)
if err != nil {
return nil, err
}
body = bytes.NewBuffer(payload)
}
timeStamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
var signHash, passPhraseHash []byte
signHash, err = crypto.GetHMAC(crypto.HashSHA256, []byte(timeStamp+method+"/api"+path+string(payload)), []byte(creds.Secret))
if err != nil {
return nil, err
}
passPhraseHash, err = crypto.GetHMAC(crypto.HashSHA256, []byte(creds.ClientID), []byte(creds.Secret))
if err != nil {
return nil, err
}
headers := map[string]string{
"KC-API-KEY": creds.Key,
"KC-API-SIGN": crypto.Base64Encode(signHash),
"KC-API-TIMESTAMP": timeStamp,
"KC-API-PASSPHRASE": crypto.Base64Encode(passPhraseHash),
"KC-API-KEY-VERSION": kucoinAPIKeyVersion,
"Content-Type": "application/json",
}
return &request.Item{
Method: method,
Path: endpointPath + path,
Headers: headers,
Body: body,
Result: &resp,
Verbose: ku.Verbose,
HTTPDebugging: ku.HTTPDebugging,
HTTPRecording: ku.HTTPRecording}, nil
}, request.AuthenticatedRequest)
if err != nil {
return err
}
if result == nil {
return errNoValidResponseFromServer
}
return resp.GetError()
}
func (ku *Kucoin) intervalToString(interval kline.Interval) (string, error) {
switch interval {
case kline.OneMin:
return "1min", nil
case kline.ThreeMin:
return "3min", nil
case kline.FiveMin:
return "5min", nil
case kline.FifteenMin:
return "15min", nil
case kline.ThirtyMin:
return "30min", nil
case kline.OneHour:
return "1hour", nil
case kline.TwoHour:
return "2hour", nil
case kline.FourHour:
return "4hour", nil
case kline.SixHour:
return "6hour", nil
case kline.EightHour:
return "8hour", nil
case kline.TwelveHour:
return "12hour", nil
case kline.OneDay:
return "1day", nil
case kline.OneWeek:
return "1week", nil
default:
return "", kline.ErrUnsupportedInterval
}
}
func (ku *Kucoin) stringToOrderStatus(status string) (order.Status, error) {
switch status {
case "match":
return order.Filled, nil
case "open":
return order.Open, nil
case "done":
return order.Closed, nil
default:
return order.StringToOrderStatus(status)
}
}
func (ku *Kucoin) accountTypeToString(a asset.Item) string {
switch a {
case asset.Spot:
return "trade"
case asset.Margin:
return "margin"
case asset.Empty:
return ""
default:
return "main"
}
}
func (ku *Kucoin) accountToTradeTypeString(a asset.Item, marginMode string) string {
switch a {
case asset.Spot:
return "TRADE"
case asset.Margin:
if strings.EqualFold(marginMode, "isolated") {
return "MARGIN_ISOLATED_TRADE"
}
return "MARGIN_TRADE"
default:
return ""
}
}
func (ku *Kucoin) orderTypeToString(orderType order.Type) (string, error) {
switch orderType {
case order.AnyType, order.UnknownType:
return "", nil
case order.Market, order.Limit:
return orderType.Lower(), nil
default:
return "", order.ErrUnsupportedOrderType
}
}
func (ku *Kucoin) orderSideString(side order.Side) (string, error) {
switch {
case side.IsLong():
return order.Buy.Lower(), nil
case side.IsShort():
return order.Sell.Lower(), nil
case side == order.AnySide:
return "", nil
default:
return "", fmt.Errorf("%w, side:%s", order.ErrSideIsInvalid, side.String())
}
}