Files
gocryptotrader/exchanges/kucoin/kucoin.go
Gareth Kirwan b41fe27684 Kucoin: Add subscription templating and various fixes (#1579)
* Currency: Variadic Pairs.Add

This version of Pairs.Add is simpler and [more
performant](https://gist.github.com/gbjk/06a1fc1832d04ee41213ca518938cf74)

Behavioural difference: If there's nothing to add, the same slice is
returned unaltered. This seems like good sauce

* Currency: Variadic Remove

* Common: Add Batch function

* Common: Add common.SortStrings for stringers

* Subscriptions: Add batching to templates

* Subscriptions: Sort list of pairs

* Kucoin: Switch to sub templating

* Kucoin: Simplify channel prefix usage

* Kucoin: Fix race on fetchedFuturesOrderbook

* Subscriptions: Filter AssetPairs

Now only the assetPairs relevant to the subscription are in the context

* Subscriptions: Respect subscription Pairs

* Subscriptions: Trim AssetSeparator early

We want to trim before checking for "AssetSeparator vs All" because a
template should be allowed to reuse a range template and generate just one trailing AssetSeparator
whilst using a specific Asset

* Kucoin: Fix empty margin asset added

* Kucoin: Add Subscription batching

Turns out that contary to the documentation, kucoin supports batching of
all symbols and currencies

* Kucoin: Fix checkSubscriptions and coverage

* Subscriptions: Simplify error checking

This reduces the complexity of error checking to just be "do we get the
correct numbers".

Fixes Asset.All with only one asset erroring on xpandPairs, because we
trimmed the only asset separator, and then errored that we're not
xpanding Assets and the asset on the sub is asset.All

This use-case conflicted with commit 6bbd546d74, which required:
```
Subscriptions: Trim AssetSeparator early

We want to trim before checking for "AssetSeparator vs All" because a
template should be allowed to reuse a range template and generate just one trailing AssetSeparator
whilst using a specific Asset
```

Now we set up the assets earlier, and we remove the check for xpandAssets, since the number of asset lines matching is all that matters.

I've removed the asset tests for this, but they were correctly erroring
on the number of asset lines instead.

Everything hits coverage, as well.

* Kucoin: Remove deprecated fundingBook endpoint

* BTCMarkets: Use common.Batch
2024-08-09 12:33:15 +10:00

1863 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"
tradeBaseURL = "https://www.kucoin.com/"
tradeSpot = "trade/"
tradeMargin = "margin/"
tradeFutures = "futures/"
// 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.Tranche, error) {
o := make([]orderbook.Tranche, 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.Tranche{
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 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())
}
}