Files
gocryptotrader/exchanges/kucoin/kucoin.go
Ryan O'Hara-Reid d31fa3ff3d types: Add Time type from Gateio and share across codebase (#1648)
* consolidate type to types package and share across code base

* rm convert type and convert codebase

* rm irrelavant test cases

* Fix tests

* glorious nits

* Update exchanges/gateio/gateio_types.go

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

* thrasher: nits

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
2024-10-01 10:46:55 +10:00

2813 lines
112 KiB
Go

package kucoin
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/types"
)
// 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"
tradeBaseURL = "https://www.kucoin.com/"
tradeSpot = "trade/"
tradeMargin = "margin/"
tradeFutures = "futures/"
symbolQuery = "?symbol="
)
// 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(order.Market.Lower(), market)
}
var resp []SymbolInfo
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, symbolsEPL, common.EncodeURLValues("/v2/symbols", params), &resp)
}
// GetTicker gets pair ticker information
func (ku *Kucoin) GetTicker(ctx context.Context, symbol string) (*Ticker, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
var resp *Ticker
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, tickersEPL, common.EncodeURLValues("/v1/market/orderbook/level1", 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, allTickersEPL, "/v1/market/allTickers", &resp)
}
// Get24hrStats get the statistics of the specified pair in the last 24 hours
func (ku *Kucoin) Get24hrStats(ctx context.Context, symbol string) (*Stats24hrs, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
var resp *Stats24hrs
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, statistics24HrEPL, common.EncodeURLValues("/v1/market/stats", 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, marketListEPL, "/v1/markets", &resp)
}
// processOB constructs an orderbook.Tranche instances from slice of numbers.
func processOB(ob [][2]types.Number) []orderbook.Tranche {
o := make([]orderbook.Tranche, len(ob))
for x := range ob {
o[x].Amount = ob[x][1].Float64()
o[x].Price = ob[x][0].Float64()
}
return o
}
// constructOrderbook parse checks and constructs an *Orderbook instance from *orderbookResponse.
func constructOrderbook(o *orderbookResponse) (*Orderbook, error) {
var (
s = Orderbook{
Bids: processOB(o.Bids),
Asks: processOB(o.Asks),
Time: o.Time.Time(),
}
)
if o.Sequence != "" {
var err error
s.Sequence, err = strconv.ParseInt(o.Sequence, 10, 64)
if err != nil {
return nil, err
}
}
return &s, nil
}
// GetPartOrderbook20 gets orderbook for a specified pair with depth 20
func (ku *Kucoin) GetPartOrderbook20(ctx context.Context, symbol string) (*Orderbook, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
var o *orderbookResponse
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, partOrderbook20EPL, common.EncodeURLValues("/v1/market/orderbook/level2_20", 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, symbol string) (*Orderbook, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
var o *orderbookResponse
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, partOrderbook100EPL, common.EncodeURLValues("/v1/market/orderbook/level2_100", 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, symbol string) (*Orderbook, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
var o *orderbookResponse
err := ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, fullOrderbookEPL, http.MethodGet, common.EncodeURLValues("/v3/market/orderbook/level2", 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, symbol string) ([]Trade, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
var resp []Trade
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, tradeHistoryEPL, common.EncodeURLValues("/v1/market/histories", params), &resp)
}
// GetKlines gets kline of the specified pair
func (ku *Kucoin) GetKlines(ctx context.Context, symbol, period string, start, end time.Time) ([]Kline, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
if period == "" {
return nil, fmt.Errorf("%w, period can not be empty", errInvalidPeriod)
}
if !slices.Contains(validPeriods, period) {
return nil, errInvalidPeriod
}
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]types.Number
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, klinesEPL, common.EncodeURLValues("/v1/market/candles", params), &resp)
if err != nil {
return nil, err
}
klines := make([]Kline, len(resp))
for i := range resp {
klines[i].StartTime = time.Unix(resp[i][0].Int64(), 0)
klines[i].Open = resp[i][1].Float64()
klines[i].Close = resp[i][2].Float64()
klines[i].High = resp[i][3].Float64()
klines[i].Low = resp[i][4].Float64()
klines[i].Volume = resp[i][5].Float64()
klines[i].Amount = resp[i][6].Float64()
}
return klines, nil
}
// GetCurrenciesV3 the V3 of retrieving list of currencies
func (ku *Kucoin) GetCurrenciesV3(ctx context.Context) ([]CurrencyDetail, error) {
var resp []CurrencyDetail
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, spotCurrenciesV3EPL, "/v3/currencies", &resp)
}
// GetCurrencyDetailV3 V3 endpoint to gets currency detail using currency code and chain information.
func (ku *Kucoin) GetCurrencyDetailV3(ctx context.Context, ccy currency.Code, chain string) (*CurrencyDetail, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
if chain != "" {
params.Set("chain", chain)
}
var resp *CurrencyDetail
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, spotCurrencyDetailEPL, common.EncodeURLValues("/v3/currencies/"+ccy.Upper().String(), 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]types.Number, error) {
params := url.Values{}
if base != "" {
params.Set("base", base)
}
if currencies != "" {
params.Set("currencies", currencies)
}
var resp map[string]types.Number
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, fiatPriceEPL, common.EncodeURLValues("/v1/prices", params), &resp)
}
// GetLeveragedTokenInfo returns leveraged token information
func (ku *Kucoin) GetLeveragedTokenInfo(ctx context.Context, ccy currency.Code) ([]LeveragedTokenInfo, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp []LeveragedTokenInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, leveragedTokenInfoEPL, http.MethodGet, common.EncodeURLValues("/v3/etf/info", params), nil, &resp)
}
// GetMarkPrice gets index price of the specified pair
func (ku *Kucoin) GetMarkPrice(ctx context.Context, symbol string) (*MarkPrice, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
var resp *MarkPrice
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, getMarkPriceEPL, "/v1/mark-price/"+symbol+"/current", &resp)
}
// GetAllMarginTradingPairsMarkPrices retrieves all margin trading pairs ticker mark price information
func (ku *Kucoin) GetAllMarginTradingPairsMarkPrices(ctx context.Context) ([]MarkPrice, error) {
var resp []MarkPrice
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, getAllMarginMarkPriceEPL, "/v3/mark-price/all-symbols", &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, getMarginConfigurationEPL, "/v1/margin/config", &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, marginAccountDetailEPL, http.MethodGet, "/v1/margin/account", nil, &resp)
}
// GetCrossMarginRiskLimitCurrencyConfig risk limit and currency configuration of cross margin account
// isIsolated: true - isolated, false - cross ; default false
func (ku *Kucoin) GetCrossMarginRiskLimitCurrencyConfig(ctx context.Context, symbol string, ccy currency.Code) ([]CrossMarginRiskLimitCurrencyConfig, error) {
var resp []CrossMarginRiskLimitCurrencyConfig
return resp, ku.getCrossOrIsolatedMarginRiskLimitCurrencyConfig(ctx, false, symbol, ccy, &resp)
}
// GetIsolatedMarginRiskLimitCurrencyConfig risk limit and currency configuration of cross isolated margin
func (ku *Kucoin) GetIsolatedMarginRiskLimitCurrencyConfig(ctx context.Context, symbol string, ccy currency.Code) ([]IsolatedMarginRiskLimitCurrencyConfig, error) {
var resp []IsolatedMarginRiskLimitCurrencyConfig
return resp, ku.getCrossOrIsolatedMarginRiskLimitCurrencyConfig(ctx, true, symbol, ccy, &resp)
}
func (ku *Kucoin) getCrossOrIsolatedMarginRiskLimitCurrencyConfig(ctx context.Context, isIsolated bool, symbol string, ccy currency.Code, resp any) error {
params := url.Values{}
if isIsolated {
params.Set("isIsolated", "true")
}
if symbol != "" {
params.Set("symbol", symbol)
}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, crossIsolatedMarginRiskLimitCurrencyConfigEPL, http.MethodGet, common.EncodeURLValues("/v3/margin/currencies", params), nil, &resp)
}
// PostMarginBorrowOrder used to post borrow order
func (ku *Kucoin) PostMarginBorrowOrder(ctx context.Context, arg *MarginBorrowParam) (*BorrowAndRepaymentOrderResp, error) {
if *arg == (MarginBorrowParam{}) {
return nil, common.ErrNilPointer
}
if arg.Currency.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if arg.TimeInForce == "" {
return nil, errTimeInForceRequired
}
if arg.Size <= 0 {
return nil, fmt.Errorf("%w , size = %f", order.ErrAmountBelowMin, arg.Size)
}
var resp *BorrowAndRepaymentOrderResp
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, postMarginBorrowOrderEPL, http.MethodPost, "/v3/margin/borrow", arg, &resp)
}
// GetMarginBorrowingHistory retrieves the borrowing orders for cross and isolated margin accounts
func (ku *Kucoin) GetMarginBorrowingHistory(ctx context.Context, ccy currency.Code, isIsolated bool,
symbol, orderNo string,
startTime, endTime time.Time,
currentPage, pageSize int64) (*BorrowRepayDetailResponse, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("currency", ccy.String())
if isIsolated {
params.Set("isIsolated", "true")
}
params.Set("symbol", symbol)
if orderNo != "" {
params.Set("orderNo", orderNo)
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UnixMilli(), 10))
}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *BorrowRepayDetailResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, marginBorrowingHistoryEPL, http.MethodGet, common.EncodeURLValues("/v3/margin/borrow", params), nil, &resp)
}
// PostRepayment used to initiate an application for the repayment of cross or isolated margin borrowing.
func (ku *Kucoin) PostRepayment(ctx context.Context, arg *RepayParam) (*BorrowAndRepaymentOrderResp, error) {
if *arg == (RepayParam{}) {
return nil, common.ErrNilPointer
}
if arg.Currency.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if arg.Size <= 0 {
return nil, fmt.Errorf("%w , size = %f", order.ErrAmountBelowMin, arg.Size)
}
var resp *BorrowAndRepaymentOrderResp
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, postMarginRepaymentEPL, http.MethodPost, "/v3/margin/repay", arg, &resp)
}
// GetCrossIsolatedMarginInterestRecords request via this endpoint to get the interest records of the cross/isolated margin lending
func (ku *Kucoin) GetCrossIsolatedMarginInterestRecords(ctx context.Context, isIsolated bool, symbol string, ccy currency.Code, startTime, endTime time.Time, currentPage, pageSize int64) (*MarginInterestRecords, error) {
params := url.Values{}
if isIsolated {
params.Set("isIsolated", "true")
}
if symbol != "" {
params.Set("symbol", symbol)
}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UnixMilli(), 10))
}
if currentPage > 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize > 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *MarginInterestRecords
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getCrossIsolatedMarginInterestRecordsEPL, http.MethodGet, common.EncodeURLValues("/v3/margin/interest", params), nil, &resp)
}
// GetRepaymentHistory retrieves the repayment orders for cross and isolated margin accounts.
func (ku *Kucoin) GetRepaymentHistory(ctx context.Context, ccy currency.Code, isIsolated bool,
symbol, orderNo string,
startTime, endTime time.Time,
currentPage, pageSize int64) (*BorrowRepayDetailResponse, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy.String())
if symbol != "" {
params.Set("symbol", symbol)
}
if isIsolated {
params.Set("isIsolated", "true")
}
if orderNo != "" {
params.Set("orderNo", orderNo)
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UnixMilli(), 10))
}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *BorrowRepayDetailResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, marginRepaymentHistoryEPL, http.MethodGet, common.EncodeURLValues("/v3/margin/repay", 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, isolatedMarginPairConfigEPL, http.MethodGet, "/v1/isolated/symbols", 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, isolatedMarginAccountInfoEPL, http.MethodGet, common.EncodeURLValues("/v1/isolated/accounts", 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, currency.ErrSymbolStringEmpty
}
var resp *AssetInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, singleIsolatedMarginAccountInfoEPL, http.MethodGet, "/v1/isolated/account/"+symbol, nil, &resp)
}
// GetCurrentServerTime gets the server time
func (ku *Kucoin) GetCurrentServerTime(ctx context.Context) (time.Time, error) {
resp := struct {
Timestamp types.Time `json:"data"`
Error
}{}
err := ku.SendHTTPRequest(ctx, exchange.RestSpot, currentServerTimeEPL, "/v1/timestamp", &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, serviceStatusEPL, "/v1/status", &resp)
}
// --------------------------------------------- Spot High Frequency(HF) Pro Account ---------------------------
// HFSpotPlaceOrder places a high frequency spot order
// There are two types of orders: (limit) order: set price and quantity for the transaction. (market) order : set amount or quantity for the transaction.
func (ku *Kucoin) HFSpotPlaceOrder(ctx context.Context, arg *PlaceHFParam) (string, error) {
return ku.SendSpotHFPlaceOrder(ctx, arg, "/v1/hf/orders")
}
// SpotPlaceHFOrderTest order test endpoint, the request parameters and return parameters of this endpoint are exactly the same as the order endpoint,
// and can be used to verify whether the signature is correct and other operations.
func (ku *Kucoin) SpotPlaceHFOrderTest(ctx context.Context, arg *PlaceHFParam) (string, error) {
return ku.SendSpotHFPlaceOrder(ctx, arg, "/v1/hf/orders/test")
}
// ValidatePlaceOrderParams validates an order placement parameters.
func (a *PlaceHFParam) ValidatePlaceOrderParams() error {
if *a == (PlaceHFParam{}) {
return common.ErrNilPointer
}
if a.Symbol.IsEmpty() {
return currency.ErrSymbolStringEmpty
}
if a.OrderType == "" {
return order.ErrTypeIsInvalid
}
if a.Side == "" {
return order.ErrSideIsInvalid
}
a.Side = strings.ToLower(a.Side)
if a.Price <= 0 {
return order.ErrPriceBelowMin
}
if a.Size <= 0 {
return order.ErrAmountBelowMin
}
return nil
}
// SendSpotHFPlaceOrder sends a spot high-frequency order to the specified path
// Use HFSpotPlaceOrder to place an order or SpotPlaceHFOrderTest to send a test order
func (ku *Kucoin) SendSpotHFPlaceOrder(ctx context.Context, arg *PlaceHFParam, path string) (string, error) {
err := arg.ValidatePlaceOrderParams()
if err != nil {
return "", err
}
resp := &struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfPlaceOrderEPL, http.MethodPost, path, arg, &resp)
}
// SyncPlaceHFOrder this interface will synchronously return the order information after the order matching is completed.
func (ku *Kucoin) SyncPlaceHFOrder(ctx context.Context, arg *PlaceHFParam) (*SyncPlaceHFOrderResp, error) {
err := arg.ValidatePlaceOrderParams()
if err != nil {
return nil, err
}
var resp *SyncPlaceHFOrderResp
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfSyncPlaceOrderEPL, http.MethodPost, "/v1/hf/orders/sync", arg, &resp)
}
// PlaceMultipleOrders endpoint supports sequential batch order placement from a single endpoint. A maximum of 5 orders can be placed simultaneously.
func (ku *Kucoin) PlaceMultipleOrders(ctx context.Context, args []PlaceHFParam) ([]PlaceOrderResp, error) {
if len(args) == 0 {
return nil, common.ErrEmptyParams
}
for i := range args {
err := args[i].ValidatePlaceOrderParams()
if err != nil {
return nil, err
}
}
var resp []PlaceOrderResp
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfMultipleOrdersEPL, http.MethodPost, "/v1/hf/orders/multi", &PlaceOrderParams{OrderList: args}, &resp)
}
// SyncPlaceMultipleHFOrders this interface will synchronously return the order information after the order matching is completed
func (ku *Kucoin) SyncPlaceMultipleHFOrders(ctx context.Context, args []PlaceHFParam) ([]SyncPlaceHFOrderResp, error) {
if len(args) == 0 {
return nil, common.ErrEmptyParams
}
for i := range args {
err := args[i].ValidatePlaceOrderParams()
if err != nil {
return nil, err
}
}
var resp []SyncPlaceHFOrderResp
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfSyncPlaceMultipleHFOrdersEPL, http.MethodPost, "/v1/hf/orders/multi/sync", args, &resp)
}
// ModifyHFOrder modifies a high frequency order.
func (ku *Kucoin) ModifyHFOrder(ctx context.Context, arg *ModifyHFOrderParam) (string, error) {
if *arg == (ModifyHFOrderParam{}) {
return "", common.ErrNilPointer
}
if arg.Symbol.IsEmpty() {
return "", currency.ErrCurrencyPairEmpty
}
resp := &struct {
NewOrderID string `json:"newOrderId"`
}{}
return resp.NewOrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfModifyOrderEPL, http.MethodPost, "/v1/hf/orders/alter", arg, &resp)
}
// CancelHFOrder used to cancel a high-frequency order by orderId.
func (ku *Kucoin) CancelHFOrder(ctx context.Context, orderID, symbol string) (string, error) {
if orderID == "" {
return "", order.ErrOrderIDNotSet
}
if symbol == "" {
return "", currency.ErrSymbolStringEmpty
}
resp := &struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelHFOrderEPL, http.MethodDelete, "/v1/hf/orders/"+orderID+symbolQuery+symbol, nil, &resp)
}
// SyncCancelHFOrder this interface will synchronously return the order information after the order canceling is completed.
func (ku *Kucoin) SyncCancelHFOrder(ctx context.Context, orderID, symbol string) (*SyncCancelHFOrderResp, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
return ku.SendSyncCancelHFOrder(ctx, orderID, symbol, "/v1/hf/orders/sync/")
}
// SyncCancelHFOrderByClientOrderID this interface will synchronously return the order information after the order canceling is completed.
func (ku *Kucoin) SyncCancelHFOrderByClientOrderID(ctx context.Context, clientOrderID, symbol string) (*SyncCancelHFOrderResp, error) {
if clientOrderID == "" {
return nil, order.ErrClientOrderIDMustBeSet
}
return ku.SendSyncCancelHFOrder(ctx, clientOrderID, symbol, "/v1/hf/orders/sync/client-order/")
}
// SendSyncCancelHFOrder sends a sync-cancel high-frequency order by order ID or client supplied order ID.
func (ku *Kucoin) SendSyncCancelHFOrder(ctx context.Context, id, symbol, path string) (*SyncCancelHFOrderResp, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
var resp *SyncCancelHFOrderResp
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfSyncCancelOrderEPL, http.MethodDelete, path+id+symbolQuery+symbol, nil, &resp)
}
// CancelHFOrderByClientOrderID sends out a request to cancel a high-frequency order using clientOid.
func (ku *Kucoin) CancelHFOrderByClientOrderID(ctx context.Context, clientOrderID, symbol string) (string, error) {
if clientOrderID == "" {
return "", order.ErrClientOrderIDMustBeSet
}
if symbol == "" {
return "", currency.ErrSymbolStringEmpty
}
resp := &struct {
ClientOrderID string `json:"clientOid"`
}{}
return resp.ClientOrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfCancelOrderByClientOrderIDEPL, http.MethodDelete, "/v1/hf/orders/client-order/"+clientOrderID+symbolQuery+symbol, nil, &resp)
}
// CancelSpecifiedNumberHFOrdersByOrderID cancel the specified quantity of the order according to the orderId.
func (ku *Kucoin) CancelSpecifiedNumberHFOrdersByOrderID(ctx context.Context, orderID, symbol string, cancelSize float64) (*CancelOrderByNumberResponse, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
if cancelSize == 0 {
return nil, fmt.Errorf("%w, cancel size is required", order.ErrAmountBelowMin)
}
params := url.Values{}
params.Set("symbol", symbol)
params.Set("cancelSize", strconv.FormatFloat(cancelSize, 'f', -1, 64))
var resp *CancelOrderByNumberResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelSpecifiedNumberHFOrdersByOrderIDEPL, http.MethodDelete, common.EncodeURLValues("/v1/hf/orders/cancel/"+orderID, params), nil, &resp)
}
// CancelAllHFOrdersBySymbol cancel all open high-frequency orders
func (ku *Kucoin) CancelAllHFOrdersBySymbol(ctx context.Context, symbol string) (string, error) {
if symbol == "" {
return "", currency.ErrSymbolStringEmpty
}
var resp string
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfCancelAllOrdersBySymbolEPL, http.MethodDelete, "/v1/hf/orders?symbol="+symbol, nil, &resp)
}
// CancelAllHFOrders cancels all high-frequency orders for all symbols
func (ku *Kucoin) CancelAllHFOrders(ctx context.Context) (*CancelAllHFOrdersResponse, error) {
var resp *CancelAllHFOrdersResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfCancelAllOrdersEPL, http.MethodDelete, "/v1/hf/orders/cancelAll", nil, &resp)
}
// GetActiveHFOrders retrieves all high-frequency active orders
func (ku *Kucoin) GetActiveHFOrders(ctx context.Context, symbol string) ([]OrderDetail, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
var resp []OrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfGetAllActiveOrdersEPL, http.MethodGet, "/v1/hf/orders/active?symbol="+symbol, nil, &resp)
}
// GetSymbolsWithActiveHFOrderList retrieves all trading pairs that the user has active orders
func (ku *Kucoin) GetSymbolsWithActiveHFOrderList(ctx context.Context) ([]string, error) {
resp := &struct {
Symbols []string `json:"symbols"`
}{}
return resp.Symbols, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfSymbolsWithActiveOrdersEPL, http.MethodGet, "/v1/hf/orders/active/symbols", nil, &resp)
}
// GetHFCompletedOrderList obtains a list of filled HF orders and returns paginated data. The returned data is sorted in descending order based on the latest order update times.
func (ku *Kucoin) GetHFCompletedOrderList(ctx context.Context, symbol, side, orderType, lastID string, startAt, endAt time.Time, limit int64) (*CompletedHFOrder, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
if side != "" {
params.Set("side", strings.ToLower(side))
}
if orderType != "" {
params.Set("type", orderType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if lastID == "" {
params.Set("lastId", lastID)
}
if limit > 0 {
params.Set(order.Limit.Lower(), strconv.FormatInt(limit, 10))
}
var resp *CompletedHFOrder
err := ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfCompletedOrderListEPL, http.MethodGet, common.EncodeURLValues("/v1/hf/orders/done", params), nil, &resp)
if err != nil {
return nil, err
}
if resp == nil {
return nil, common.ErrNoResponse
}
return resp, nil
}
// GetHFOrderDetailsByOrderID obtain information for a single HF order using the order id.
// If the order is not an active order, you can only get data within the time range of 3 _ 24 hours (ie: from the current time to 3 _ 24 hours ago).
func (ku *Kucoin) GetHFOrderDetailsByOrderID(ctx context.Context, orderID, symbol string) (*OrderDetail, error) {
return ku.GetHFOrderDetailsByID(ctx, orderID, symbol, "/v1/hf/orders/")
}
// GetHFOrderDetailsByClientOrderID used to obtain information about a single order using clientOid. If the order does not exist, then there will be a prompt saying that the order does not exist.
func (ku *Kucoin) GetHFOrderDetailsByClientOrderID(ctx context.Context, clientOrderID, symbol string) (*OrderDetail, error) {
return ku.GetHFOrderDetailsByID(ctx, clientOrderID, symbol, "/v1/hf/orders/client-order/")
}
// GetHFOrderDetailsByID retrieves a high-frequency order by order ID or client supplied ID.
func (ku *Kucoin) GetHFOrderDetailsByID(ctx context.Context, orderID, symbol, path string) (*OrderDetail, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
var resp *OrderDetail
err := ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfOrderDetailByOrderIDEPL, http.MethodGet, path+orderID+symbolQuery+symbol, nil, &resp)
if err != nil {
return nil, err
}
if resp == nil {
return nil, order.ErrOrderNotFound
}
return resp, nil
}
// AutoCancelHFOrderSetting automatically cancel all orders of the set trading pair after the specified time.
// If this interface is not called again for renewal or cancellation before the set time,
// the system will help the user to cancel the order of the corresponding trading pair. Otherwise it will not.
func (ku *Kucoin) AutoCancelHFOrderSetting(ctx context.Context, timeout int64, symbols []string) (*AutoCancelHFOrderResponse, error) {
if timeout == 0 {
return nil, errTimeoutRequired
}
arg := &struct {
Timeout int64 `json:"timeout,string"`
Symbols []string `json:"symbols,omitempty"`
}{
Timeout: timeout,
Symbols: symbols,
}
var resp *AutoCancelHFOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, autoCancelHFOrderSettingEPL, http.MethodPost, "/v1/hf/orders/dead-cancel-all", arg, &resp)
}
// AutoCancelHFOrderSettingQuery query the settings of automatic order cancellation
func (ku *Kucoin) AutoCancelHFOrderSettingQuery(ctx context.Context) (*AutoCancelHFOrderResponse, error) {
var resp *AutoCancelHFOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, autoCancelHFOrderSettingQueryEPL, http.MethodGet, "/v1/hf/orders/dead-cancel-all/query", nil, &resp)
}
// GetHFFilledList retrieves a list of the latest HF transaction details. The returned results are paginated. The data is sorted in descending order according to time.
func (ku *Kucoin) GetHFFilledList(ctx context.Context, orderID, symbol, side, orderType, lastID string, startAt, endAt time.Time, limit int64) (*HFOrderFills, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
if orderID != "" {
params.Set("orderId", orderID)
}
if side != "" {
params.Set("side", strings.ToLower(side))
}
if orderType != "" {
params.Set("type", orderType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if lastID != "" {
params.Set("lastId", lastID)
}
if limit > 0 {
params.Set(order.Limit.Lower(), strconv.FormatInt(limit, 10))
}
var resp *HFOrderFills
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfFilledListEPL, http.MethodGet, common.EncodeURLValues("/v1/hf/fills", params), nil, &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) {
return ku.HandlePostOrder(ctx, arg, "/v1/orders")
}
// PostOrderTest used to verify whether the signature is correct and other operations.
// After placing an order, the order will not enter the matching system, and the order cannot be queried.
func (ku *Kucoin) PostOrderTest(ctx context.Context, arg *SpotOrderParam) (string, error) {
return ku.HandlePostOrder(ctx, arg, "/v1/orders/test")
}
// HandlePostOrder applies a spot order placement or tests the order placement process.
func (ku *Kucoin) HandlePostOrder(ctx context.Context, arg *SpotOrderParam, path string) (string, error) {
if arg.ClientOrderID == "" {
// NOTE: 128 bit max length character string. UUID recommended.
return "", order.ErrClientOrderIDMustBeSet
}
if arg.Side == "" {
return "", order.ErrSideIsInvalid
}
arg.Side = strings.ToLower(arg.Side)
if arg.Symbol.IsEmpty() {
return "", fmt.Errorf("%w, empty symbol", currency.ErrCurrencyPairEmpty)
}
switch arg.OrderType {
case order.Limit.Lower(), "":
if arg.Price <= 0 {
return "", fmt.Errorf("%w, price =%.3f", order.ErrPriceBelowMin, arg.Price)
}
if arg.Size <= 0 {
return "", order.ErrAmountBelowMin
}
if arg.VisibleSize < 0 {
return "", fmt.Errorf("%w, visible size must be non-zero positive value", order.ErrAmountBelowMin)
}
case order.Market.Lower():
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, path, &arg, &resp)
}
// PostMarginOrderTest a test endpoint used to place two types of margin orders: limit and margin.
func (ku *Kucoin) PostMarginOrderTest(ctx context.Context, arg *MarginOrderParam) (*PostMarginOrderResp, error) {
return ku.SendPostMarginOrder(ctx, arg, "/v1/margin/order/test")
}
// PostMarginOrder used to place two types of margin orders: limit and market
func (ku *Kucoin) PostMarginOrder(ctx context.Context, arg *MarginOrderParam) (*PostMarginOrderResp, error) {
return ku.SendPostMarginOrder(ctx, arg, "/v1/margin/order")
}
// SendPostMarginOrder applies a margin order placement or tests the order placement process.
func (ku *Kucoin) SendPostMarginOrder(ctx context.Context, arg *MarginOrderParam, path string) (*PostMarginOrderResp, error) {
if arg.ClientOrderID == "" {
return nil, order.ErrClientOrderIDMustBeSet
}
if arg.Side == "" {
return nil, order.ErrSideIsInvalid
}
arg.Side = strings.ToLower(arg.Side)
if arg.Symbol.IsEmpty() {
return nil, currency.ErrSymbolStringEmpty
}
arg.OrderType = strings.ToLower(arg.OrderType)
switch arg.OrderType {
case order.Limit.Lower(), "":
if arg.Price <= 0 {
return nil, fmt.Errorf("%w, price=%.3f", order.ErrPriceBelowMin, arg.Price)
}
if arg.Size <= 0 {
return nil, order.ErrAmountBelowMin
}
if arg.VisibleSize < 0 {
return nil, fmt.Errorf("%w, visible size must be non-zero positive value", order.ErrAmountBelowMin)
}
case order.Market.Lower():
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, path, &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, currency.ErrSymbolStringEmpty
}
if len(orderList) == 0 {
return nil, common.ErrEmptyParams
}
for i := range orderList {
if orderList[i].ClientOID == "" {
return nil, order.ErrClientOrderIDMustBeSet
}
if orderList[i].Side == "" {
return nil, order.ErrSideIsInvalid
}
orderList[i].Side = strings.ToLower(orderList[i].Side)
if orderList[i].Price <= 0 {
return nil, order.ErrPriceBelowMin
}
if orderList[i].Size <= 0 {
return nil, order.ErrAmountBelowMin
}
}
arg := &struct {
Symbol string `json:"symbol"`
OrderList []OrderRequest `json:"orderList"`
}{
Symbol: symbol,
OrderList: orderList,
}
resp := &struct {
Data []PostBulkOrderResp `json:"data"`
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeBulkOrdersEPL, http.MethodPost, "/v1/orders/multi", arg, &resp)
}
// CancelSingleOrder used to cancel single order previously placed
func (ku *Kucoin) CancelSingleOrder(ctx context.Context, orderID string) ([]string, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
Error
}{}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelOrderEPL, http.MethodDelete, "/v1/orders/"+orderID, nil, &resp)
}
// CancelOrderByClientOID used to cancel order via the clientOid
func (ku *Kucoin) CancelOrderByClientOID(ctx context.Context, orderID string) (*CancelOrderResponse, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
var resp *CancelOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelOrderByClientOrderIDEPL, http.MethodDelete, "/v1/order/client-order/"+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
}{
CancelledOrderIDs: []string{},
}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelAllOrdersEPL, http.MethodDelete, common.EncodeURLValues("/v1/orders", 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("/v1/orders", params), nil, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
// FillParams fills request parameters for orders and order fills.
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", strings.ToLower(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, recentOrdersEPL, http.MethodGet, "/v1/limit/orders", 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, order.ErrOrderIDNotSet
}
var resp *OrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, orderDetailByIDEPL, http.MethodGet, "/v1/orders/"+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, order.ErrClientOrderIDMustBeSet
}
var resp *OrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getOrderByClientSuppliedOrderIDEPL, http.MethodGet, "/v1/order/client-order/"+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("/v1/fills", 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, getRecentFillsEPL, http.MethodGet, "/v1/limit/fills", 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) {
if clientOID == "" {
return "", order.ErrClientOrderIDMustBeSet
}
if side == "" {
return "", fmt.Errorf("%w order side cannot be empty", order.ErrSideIsInvalid)
}
if symbol == "" {
return "", currency.ErrSymbolStringEmpty
}
arg := make(map[string]interface{})
arg["clientOid"] = clientOID
arg["side"] = strings.ToLower(side)
arg["symbol"] = symbol
if remark != "" {
arg["remark"] = remark
}
if stop != "" {
arg["stop"] = stop
if stopPrice <= 0 {
return "", fmt.Errorf("%w, stopPrice is required", order.ErrPriceBelowMin)
}
arg["stopPrice"] = strconv.FormatFloat(stopPrice, 'f', -1, 64)
}
if stp != "" {
arg["stp"] = stp
}
if tradeType != "" {
arg["tradeType"] = tradeType
}
orderType = strings.ToLower(orderType)
switch orderType {
case order.Limit.Lower(), "":
if price <= 0 {
return "", order.ErrPriceBelowMin
}
arg["price"] = strconv.FormatFloat(price, 'f', -1, 64)
if size <= 0 {
return "", fmt.Errorf("%w, size is required", order.ErrAmountBelowMin)
}
arg["size"] = strconv.FormatFloat(size, 'f', -1, 64)
if timeInForce != "" {
arg["timeInForce"] = timeInForce
}
if cancelAfter > 0 && timeInForce == "GTT" {
arg["cancelAfter"] = strconv.FormatFloat(cancelAfter, 'f', -1, 64)
}
arg["postOnly"] = postOnly
arg["hidden"] = hidden
arg["iceberg"] = iceberg
if visibleSize > 0 {
arg["visibleSize"] = strconv.FormatFloat(visibleSize, 'f', -1, 64)
}
case order.Market.Lower():
switch {
case size > 0:
arg["size"] = strconv.FormatFloat(size, 'f', -1, 64)
case funds > 0:
arg["funds"] = strconv.FormatFloat(funds, 'f', -1, 64)
default:
return "", errSizeOrFundIsRequired
}
default:
return "", fmt.Errorf("%w, order type: %s", order.ErrTypeIsInvalid, orderType)
}
if orderType != "" {
arg["type"] = orderType
}
resp := struct {
OrderID string `json:"orderId"`
Error
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeStopOrderEPL, http.MethodPost, "/v1/stop-order", arg, &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, order.ErrOrderIDNotSet
}
resp := struct {
Data []string `json:"cancelledOrderIds"`
Error
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelStopOrderEPL, http.MethodDelete, "/v1/stop-order/"+orderID, nil, &resp)
}
// CancelStopOrderByClientOrderID used to cancel single stop order previously placed by client supplied order ID.
func (ku *Kucoin) CancelStopOrderByClientOrderID(ctx context.Context, clientOrderID, symbol string) ([]string, error) {
if clientOrderID == "" {
return nil, order.ErrClientOrderIDMustBeSet
}
params := url.Values{}
params.Set("clientOid", clientOrderID)
if symbol != "" {
params.Set("symbol", symbol)
}
resp := struct {
Data []string `json:"cancelledOrderIds"`
Error
}{}
return resp.Data, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelStopOrderEPL, http.MethodDelete, common.EncodeURLValues("/v1/stop-order/cancelOrderByClientOid", params), nil, &resp)
}
// CancelStopOrders used to cancel all order based upon the parameters passed
func (ku *Kucoin) CancelStopOrders(ctx context.Context, symbol, tradeType string, orderIDs []string) ([]string, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if tradeType != "" {
params.Set("tradeType", tradeType)
}
if len(orderIDs) > 0 {
params.Set("orderIds", strings.Join(orderIDs, ","))
}
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
Error
}{
CancelledOrderIDs: []string{},
}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelStopOrdersEPL, http.MethodDelete, common.EncodeURLValues("/v1/stop-order/cancel", 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, order.ErrOrderIDNotSet
}
resp := struct {
StopOrder
Error
}{}
return &resp.StopOrder, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getStopOrderDetailEPL, http.MethodGet, "/v1/stop-order/"+orderID, nil, &resp)
}
// ListStopOrders get all current untriggered stop orders
func (ku *Kucoin) ListStopOrders(ctx context.Context, symbol, side, orderType, tradeType string, orderIDs []string, startAt, endAt time.Time, currentPage, pageSize int64) (*StopOrderListResponse, error) {
params := FillParams(symbol, side, orderType, tradeType, startAt, endAt)
if len(orderIDs) > 0 {
params.Set("orderIds", strings.Join(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, listStopOrdersEPL, http.MethodGet, common.EncodeURLValues("/v1/stop-order", 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, order.ErrClientOrderIDMustBeSet
}
params := url.Values{}
params.Set("clientOid", clientOID)
if symbol != "" {
params.Set("symbol", symbol)
}
var resp []StopOrder
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getStopOrderByClientIDEPL, http.MethodGet, common.EncodeURLValues("/v1/stop-order/queryOrderByClientOid", 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, order.ErrClientOrderIDMustBeSet
}
params := url.Values{}
params.Set("clientOid", clientOID)
if symbol != "" {
params.Set("symbol", symbol)
}
var resp *CancelOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelStopOrderByClientIDEPL, http.MethodDelete, common.EncodeURLValues("/v1/stop-order/cancelOrderByClientOid", params), nil, &resp)
}
// ------------------------------------------------ OCO Order -----------------------------------------------------------------
// PlaceOCOOrder creates a new One cancel other(OCO) order.
func (ku *Kucoin) PlaceOCOOrder(ctx context.Context, arg *OCOOrderParams) (string, error) {
if *arg == (OCOOrderParams{}) {
return "", common.ErrNilPointer
}
if arg.Symbol.IsEmpty() {
return "", currency.ErrCurrencyPairEmpty
}
if arg.Side == "" {
return "", order.ErrSideIsInvalid
}
arg.Side = strings.ToLower(arg.Side)
if arg.Price <= 0 {
return "", order.ErrPriceBelowMin
}
if arg.Size <= 0 {
return "", order.ErrAmountBelowMin
}
if arg.StopPrice <= 0 {
return "", fmt.Errorf("%w stop price = %f", order.ErrPriceBelowMin, arg.StopPrice)
}
if arg.LimitPrice <= 0 {
return "", fmt.Errorf("%w limit price = %f", order.ErrPriceBelowMin, arg.LimitPrice)
}
if arg.ClientOrderID == "" {
return "", order.ErrClientOrderIDMustBeSet
}
arg.Side = strings.ToLower(arg.Side)
resp := &struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeOCOOrderEPL, http.MethodPost, "/v3/oco/order", &arg, &resp)
}
// CancelOCOOrderByOrderID cancels a single oco order previously placed by order ID.
func (ku *Kucoin) CancelOCOOrderByOrderID(ctx context.Context, orderID string) (*OCOOrderCancellationResponse, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
return ku.CancelOCOOrderByID(ctx, "/v3/oco/order/", orderID)
}
// CancelOCOOrderByClientOrderID cancels a single oco order previously placed by client order ID.
func (ku *Kucoin) CancelOCOOrderByClientOrderID(ctx context.Context, clientOrderID string) (*OCOOrderCancellationResponse, error) {
if clientOrderID == "" {
return nil, order.ErrClientOrderIDMustBeSet
}
return ku.CancelOCOOrderByID(ctx, "/v3/oco/client-order/", clientOrderID)
}
// CancelOCOOrderByID sends a cancel OCO order by order ID or client supplied order ID.
func (ku *Kucoin) CancelOCOOrderByID(ctx context.Context, path, id string) (*OCOOrderCancellationResponse, error) {
var resp *OCOOrderCancellationResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelOCOOrderByIDEPL, http.MethodDelete, path+id, nil, &resp)
}
// CancelOCOMultipleOrders batch cancel OCO orders through orderIds.
func (ku *Kucoin) CancelOCOMultipleOrders(ctx context.Context, orderIDs []string, symbol string) (*OCOOrderCancellationResponse, error) {
params := url.Values{}
if len(orderIDs) > 0 {
params.Set("orderIds", strings.Join(orderIDs, ","))
}
if symbol != "" {
params.Set("symbol", symbol)
}
var resp *OCOOrderCancellationResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelMultipleOCOOrdersEPL, http.MethodDelete, common.EncodeURLValues("/v3/oco/orders", params), nil, &resp)
}
// GetOCOOrderInfoByOrderID to get a oco order information via the order ID.
func (ku *Kucoin) GetOCOOrderInfoByOrderID(ctx context.Context, orderID string) (*OCOOrderInfo, error) {
return ku.GetOCOOrderInfoByID(ctx, orderID, "/v3/oco/order/")
}
// GetOCOOrderInfoByClientOrderID to get a oco order information via the client order ID.
func (ku *Kucoin) GetOCOOrderInfoByClientOrderID(ctx context.Context, clientOrderID string) (*OCOOrderInfo, error) {
return ku.GetOCOOrderInfoByID(ctx, clientOrderID, "/v3/oco/client-order/")
}
// GetOCOOrderInfoByID sends a request to get an OCO order by order ID or client supplied order ID.
func (ku *Kucoin) GetOCOOrderInfoByID(ctx context.Context, id, path string) (*OCOOrderInfo, error) {
if id == "" {
return nil, order.ErrOrderIDNotSet
}
var resp *OCOOrderInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getOCOOrderByIDEPL, http.MethodGet, path+id, nil, &resp)
}
// GetOCOOrderDetailsByOrderID get a oco order detail via the order ID.
func (ku *Kucoin) GetOCOOrderDetailsByOrderID(ctx context.Context, orderID string) (*OCOOrderDetail, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
var resp *OCOOrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getOCOOrderDetailsByOrderIDEPL, http.MethodGet, "/v3/oco/order/details/"+orderID, nil, &resp)
}
// GetOCOOrderList retrieves list of OCO orders.
func (ku *Kucoin) GetOCOOrderList(ctx context.Context, pageSize, currentPage int64, symbol string, startAt, endAt time.Time, orderIDs []string) (*OCOOrders, error) {
if pageSize < 10 {
return nil, fmt.Errorf("%w, pageSize must be between 10 and 500", errPageSizeRequired)
}
if currentPage <= 0 {
return nil, fmt.Errorf("%w, must be greater than 1", errCurrentPageRequired)
}
params := url.Values{}
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
if symbol != "" {
params.Set("symbol", symbol)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if len(orderIDs) == 0 {
params.Set("orderIds", strings.Join(orderIDs, ","))
}
var resp *OCOOrders
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getOCOOrdersEPL, http.MethodGet, common.EncodeURLValues("/v3/oco/orders", params), nil, &resp)
}
// ----------------------------------------------------------- Margin HF Trade -------------------------------------------------------------
// PlaceMarginHFOrder used to place cross-margin or isolated-margin high-frequency margin trading
func (ku *Kucoin) PlaceMarginHFOrder(ctx context.Context, arg *PlaceMarginHFOrderParam) (*MarginHFOrderResponse, error) {
return ku.SendPlaceMarginHFOrder(ctx, arg, "/v3/hf/margin/order")
}
// PlaceMarginHFOrderTest used to verify whether the signature is correct and other operations. After placing an order,
// the order will not enter the matching system, and the order cannot be queried.
func (ku *Kucoin) PlaceMarginHFOrderTest(ctx context.Context, arg *PlaceMarginHFOrderParam) (*MarginHFOrderResponse, error) {
return ku.SendPlaceMarginHFOrder(ctx, arg, "/v3/hf/margin/order/test")
}
// SendPlaceMarginHFOrder applies a high-frequency margin order placement or tests the order placement process.
func (ku *Kucoin) SendPlaceMarginHFOrder(ctx context.Context, arg *PlaceMarginHFOrderParam, path string) (*MarginHFOrderResponse, error) {
if *arg == (PlaceMarginHFOrderParam{}) {
return nil, common.ErrNilPointer
}
if arg.ClientOrderID == "" {
return nil, order.ErrClientOrderIDNotSupported
}
if arg.Side == "" {
return nil, order.ErrSideIsInvalid
}
arg.Side = strings.ToLower(arg.Side)
if arg.Symbol.IsEmpty() {
return nil, currency.ErrCurrencyPairEmpty
}
if arg.Price <= 0 {
return nil, order.ErrPriceBelowMin
}
if arg.Size <= 0 {
return nil, order.ErrAmountBelowMin
}
var resp *MarginHFOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeMarginOrderEPL, http.MethodPost, path, arg, &resp)
}
// CancelMarginHFOrderByOrderID cancels a single order by orderId. If the order cannot be canceled (sold or canceled),
// an error message will be returned, and the reason can be obtained according to the returned msg.
func (ku *Kucoin) CancelMarginHFOrderByOrderID(ctx context.Context, orderID, symbol string) (string, error) {
return ku.CancelMarginHFOrderByID(ctx, orderID, symbol, "/v3/hf/margin/orders/")
}
// CancelMarginHFOrderByClientOrderID to cancel a single order by clientOid.
func (ku *Kucoin) CancelMarginHFOrderByClientOrderID(ctx context.Context, clientOrderID, symbol string) (string, error) {
return ku.CancelMarginHFOrderByID(ctx, clientOrderID, symbol, "/v3/hf/margin/orders/client-order/")
}
// CancelMarginHFOrderByID sends a cancel order high frequency margin orders by order ID or client supplied order ID.
func (ku *Kucoin) CancelMarginHFOrderByID(ctx context.Context, id, symbol, path string) (string, error) {
if id == "" {
return "", order.ErrOrderIDNotSet
}
if symbol == "" {
return "", currency.ErrSymbolStringEmpty
}
resp := &struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelMarginHFOrderByIDEPL, http.MethodDelete, path+id+symbolQuery+symbol, nil, &resp)
}
// CancelAllMarginHFOrdersBySymbol cancel all open high-frequency Margin orders(orders created through POST /api/v3/hf/margin/order).
// Transaction type: MARGIN_TRADE - cross margin trade, MARGIN_ISOLATED_TRADE - isolated margin trade
func (ku *Kucoin) CancelAllMarginHFOrdersBySymbol(ctx context.Context, symbol, tradeType string) (string, error) {
if symbol == "" {
return "", currency.ErrSymbolStringEmpty
}
if tradeType == "" {
return "", errTradeTypeMissing
}
params := url.Values{}
params.Set("symbol", symbol)
params.Set("tradeType", tradeType)
var resp string
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelAllMarginHFOrdersBySymbolEPL, http.MethodDelete, common.EncodeURLValues("/v3/hf/margin/orders", params), nil, &resp)
}
// GetActiveMarginHFOrders retrieves list if active high-frequency margin orders
func (ku *Kucoin) GetActiveMarginHFOrders(ctx context.Context, symbol, tradeType string) ([]OrderDetail, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if tradeType != "" {
params.Set("tradeType", tradeType)
}
var resp []OrderDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getActiveMarginHFOrdersEPL, http.MethodGet, common.EncodeURLValues("/v3/hf/margin/orders/active", params), nil, &resp)
}
// GetFilledHFMarginOrders list of filled margin HF orders and returns paginated data.
// The returned data is sorted in descending order based on the latest order update times.
func (ku *Kucoin) GetFilledHFMarginOrders(ctx context.Context, symbol, tradeType, side, orderType string, startAt, endAt time.Time, lastID, limit int64) (*FilledMarginHFOrdersResponse, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
if tradeType == "" {
return nil, errTradeTypeMissing
}
params := url.Values{}
params.Set("symbol", symbol)
params.Set("tradeType", tradeType)
if side != "" {
params.Set("side", strings.ToLower(side))
}
if orderType != "" {
params.Set("type", orderType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if lastID > 0 {
params.Set("lastId", strconv.FormatInt(lastID, 10))
}
if limit > 0 {
params.Set(order.Limit.Lower(), strconv.FormatInt(limit, 10))
}
var resp *FilledMarginHFOrdersResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getFilledHFMarginOrdersEPL, http.MethodGet, common.EncodeURLValues("/v3/hf/margin/orders/done", params), nil, &resp)
}
// GetMarginHFOrderDetailByOrderID retrieves the detail of a HF margin order by order ID.
func (ku *Kucoin) GetMarginHFOrderDetailByOrderID(ctx context.Context, orderID, symbol string) (*OrderDetail, error) {
return ku.GetMarginHFOrderDetailByID(ctx, orderID, symbol, "/v3/hf/margin/orders/")
}
// GetMarginHFOrderDetailByClientOrderID retrieves the detaul of a HF margin order by client order ID.
func (ku *Kucoin) GetMarginHFOrderDetailByClientOrderID(ctx context.Context, clientOrderID, symbol string) (*OrderDetail, error) {
return ku.GetMarginHFOrderDetailByID(ctx, clientOrderID, symbol, "/v3/hf/margin/orders/client-order/")
}
// GetMarginHFOrderDetailByID sends an HTTP request to fetch margin high frequency orders by order ID or client supplied order ID.
func (ku *Kucoin) GetMarginHFOrderDetailByID(ctx context.Context, orderID, symbol, path string) (*OrderDetail, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
params := url.Values{}
params.Set("symbol", symbol)
params.Set("orderId", orderID)
var resp *OrderDetail
err := ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getMarginHFOrderDetailByOrderIDEPL, http.MethodGet, path+orderID+symbolQuery+symbol, nil, &resp)
if err != nil {
return nil, err
}
if resp == nil {
return nil, fmt.Errorf("%w, where orderId %s and symbol %s", order.ErrOrderNotFound, orderID, symbol)
}
return resp, nil
}
// GetMarginHFTradeFills to obtain a list of the latest margin HF transaction details. The returned results are paginated. The data is sorted in descending order according to time.
func (ku *Kucoin) GetMarginHFTradeFills(ctx context.Context, orderID, symbol, tradeType, side, orderType string, startAt, endAt time.Time, lastID, limit int64) (*HFMarginOrderTransaction, error) {
if tradeType == "" {
return nil, errTradeTypeMissing
}
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("tradeType", tradeType)
params.Set("symbol", symbol)
if orderID != "" {
params.Set("orderId", orderID)
}
if side != "" {
params.Set("side", strings.ToLower(side))
}
if orderType != "" {
params.Set("type", orderType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if lastID > 0 {
params.Set("lastId", strconv.FormatInt(lastID, 10))
}
if limit > 0 {
params.Set(order.Limit.Lower(), strconv.FormatInt(limit, 10))
}
var resp *HFMarginOrderTransaction
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getMarginHFTradeFillsEPL, http.MethodGet, common.EncodeURLValues("/v3/hf/margin/fills", 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) {
if subAccountName == "" {
return nil, fmt.Errorf("%w, subaccount name is required", errInvalidSubAccountName)
}
if password == "" {
return nil, errInvalidPassPhraseInstance
}
arg := &struct {
SubAccountName string `json:"subName"`
Password string `json:"password"`
Remarks string `json:"remarks,omitempty"`
Access string `json:"access,omitempty"`
}{
SubAccountName: subAccountName,
Password: password,
Remarks: remarks,
Access: access,
}
var resp *SubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, createSubUserEPL, http.MethodPost, "/v2/sub/user/created", arg, &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) ([]SpotAPISubAccount, error) {
if subAccountName == "" {
return nil, errInvalidSubAccountName
}
params := url.Values{}
params.Set("subName", subAccountName)
if apiKeys != "" {
params.Set("apiKey", apiKeys)
}
var resp []SpotAPISubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, subAccountSpotAPIListEPL, http.MethodGet, common.EncodeURLValues("/v1/sub/api-key", 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 arg.SubAccountName == "" {
return nil, errInvalidSubAccountName
}
if arg.Passphrase == "" {
return nil, fmt.Errorf("%w, must contain 7-32 characters. cannot contain any spaces", errInvalidPassPhraseInstance)
}
if arg.Remark == "" {
return nil, errRemarkIsRequired
}
var resp *SpotAPISubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, createSpotAPIForSubAccountEPL, http.MethodPost, "/v1/sub/api-key", &arg, &resp)
}
// ModifySubAccountSpotAPIs modifies sub-account Spot APIs.
func (ku *Kucoin) ModifySubAccountSpotAPIs(ctx context.Context, arg *SpotAPISubAccountParams) (*SpotAPISubAccount, error) {
if arg.SubAccountName == "" {
return nil, errInvalidSubAccountName
}
if arg.APIKey == "" {
return nil, errAPIKeyRequired
}
if arg.Passphrase == "" {
return nil, fmt.Errorf("%w, must contain 7-32 characters. cannot contain any spaces", errInvalidPassPhraseInstance)
}
var resp *SpotAPISubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, modifySubAccountSpotAPIEPL, http.MethodPut, "/v1/sub/api-key/update", &arg, &resp)
}
// DeleteSubAccountSpotAPI delete sub-account Spot APIs.
func (ku *Kucoin) DeleteSubAccountSpotAPI(ctx context.Context, apiKey, subAccountName, passphrase string) (*DeleteSubAccountResponse, error) {
if subAccountName == "" {
return nil, errInvalidSubAccountName
}
if apiKey == "" {
return nil, errAPIKeyRequired
}
if passphrase == "" {
return nil, errInvalidPassPhraseInstance
}
params := url.Values{}
params.Set("apiKey", apiKey)
params.Set("subName", subAccountName)
params.Set("passphrase", passphrase)
var resp *DeleteSubAccountResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, deleteSubAccountSpotAPIEPL, http.MethodDelete, common.EncodeURLValues("/v1/sub/api-key", 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, allUserSubAccountsV2EPL, http.MethodGet, "/v2/sub/user", 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, modifySubAccountAPIEPL, http.MethodGet, common.EncodeURLValues("/v1/sub/api-key/update", params), nil, &resp)
}
// GetAllAccounts get all accounts
// accountType possible values are main、trade、margin、trade_hf
func (ku *Kucoin) GetAllAccounts(ctx context.Context, ccy currency.Code, accountType string) ([]AccountInfo, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
if accountType != "" {
params.Set("type", accountType)
}
var resp []AccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, allAccountEPL, http.MethodGet, common.EncodeURLValues("/v1/accounts", params), nil, &resp)
}
// GetAccountDetail get information of single account
func (ku *Kucoin) GetAccountDetail(ctx context.Context, accountID string) (*AccountInfo, error) {
if accountID == "" {
return nil, errAccountIDMissing
}
var resp *AccountInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, accountDetailEPL, http.MethodGet, "/v1/accounts/"+accountID, nil, &resp)
}
// GetCrossMarginAccountsDetail retrieves the info of the cross margin account.
func (ku *Kucoin) GetCrossMarginAccountsDetail(ctx context.Context, quoteCurrency, queryType string) (*CrossMarginAccountDetail, error) {
params := url.Values{}
if quoteCurrency != "" {
params.Set("quoteCurrency", quoteCurrency)
}
if queryType != "" {
params.Set("queryType", queryType)
}
var resp *CrossMarginAccountDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, crossMarginAccountsDetailEPL, http.MethodGet, common.EncodeURLValues("/v3/margin/accounts", params), nil, &resp)
}
// GetIsolatedMarginAccountDetail to get the info of the isolated margin account.
func (ku *Kucoin) GetIsolatedMarginAccountDetail(ctx context.Context, symbol, queryCurrency, queryType string) (*IsolatedMarginAccountDetail, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if queryCurrency != "" {
params.Set("quoteCurrency", queryCurrency)
}
if queryType != "" {
params.Set("queryType", queryType)
}
var resp *IsolatedMarginAccountDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, isolatedMarginAccountDetailEPL, http.MethodGet, common.EncodeURLValues("/v3/isolated/accounts", params), nil, &resp)
}
// GetFuturesAccountDetail retrieves futures account detail information
func (ku *Kucoin) GetFuturesAccountDetail(ctx context.Context, ccy currency.Code) (*FuturesAccountOverview, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp *FuturesAccountOverview
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresAccountsDetailEPL, http.MethodGet, common.EncodeURLValues("/v1/account-overview", params), nil, &resp)
}
// GetSubAccounts retrieves all sub-account information
func (ku *Kucoin) GetSubAccounts(ctx context.Context, subUserID string, includeBaseAmount bool) (*SubAccounts, error) {
if subUserID == "" {
return nil, fmt.Errorf("%w, sub-users ID is required", order.ErrOrderIDNotSet)
}
params := url.Values{}
if includeBaseAmount {
params.Set("includeBaseAmount", "true")
} else {
params.Set("includeBaseAmount", "false")
}
var resp *SubAccounts
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, subAccountsEPL, http.MethodGet, common.EncodeURLValues("/v1/sub-accounts/"+subUserID, params), nil, &resp)
}
// GetAllFuturesSubAccountBalances retrieves all futures subaccount balances
func (ku *Kucoin) GetAllFuturesSubAccountBalances(ctx context.Context, ccy currency.Code) (*FuturesSubAccountBalance, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp *FuturesSubAccountBalance
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, allFuturesSubAccountBalancesEPL, http.MethodGet, common.EncodeURLValues("/v1/account-overview-all", params), nil, &resp)
}
// populateParams populates account ledger request parameters.
func populateParams(ccy currency.Code, direction, bizType string, lastID, limit int64, startTime, endTime time.Time) url.Values {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
if direction != "" {
params.Set("direction", direction)
}
if bizType != "" {
params.Set("bizType", bizType)
}
if lastID != 0 {
params.Set("lastId", strconv.FormatInt(lastID, 10))
}
if limit != 0 {
params.Set(order.Limit.Lower(), strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UnixMilli(), 10))
}
return params
}
// GetAccountLedgers retrieves the transaction records from all types of your accounts, supporting inquiry of various currencies.
// bizType possible values: 'DEPOSIT' -deposit, 'WITHDRAW' -withdraw, 'TRANSFER' -transfer, 'SUB_TRANSFER' -subaccount transfer,'TRADE_EXCHANGE' -trade, 'MARGIN_EXCHANGE' -margin trade, 'KUCOIN_BONUS' -bonus
func (ku *Kucoin) GetAccountLedgers(ctx context.Context, ccy currency.Code, direction, bizType string, startAt, endAt time.Time) (*AccountLedgerResponse, error) {
params := populateParams(ccy, direction, bizType, 0, 0, time.Time{}, time.Time{})
if !startAt.IsZero() && !endAt.IsZero() {
err := common.StartEndTimeCheck(startAt, endAt)
if err != nil {
return nil, err
}
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *AccountLedgerResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, accountLedgersEPL, http.MethodGet, common.EncodeURLValues("/v1/accounts/ledgers", params), nil, &resp)
}
// GetAccountLedgersHFTrade returns all transfer (in and out) records in high-frequency trading account and supports multi-coin queries.
// The query results are sorted in descending order by createdAt and id.
func (ku *Kucoin) GetAccountLedgersHFTrade(ctx context.Context, ccy currency.Code, direction, bizType string, lastID, limit int64, startTime, endTime time.Time) ([]LedgerInfo, error) {
params := populateParams(ccy, direction, bizType, lastID, limit, startTime, endTime)
var resp []LedgerInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfAccountLedgersEPL, http.MethodGet, common.EncodeURLValues("/v1/hf/accounts/ledgers", params), nil, &resp)
}
// GetAccountLedgerHFMargin returns all transfer (in and out) records in high-frequency margin trading account and supports multi-coin queries.
func (ku *Kucoin) GetAccountLedgerHFMargin(ctx context.Context, ccy currency.Code, direction, bizType string, lastID, limit int64, startTime, endTime time.Time) ([]LedgerInfo, error) {
params := populateParams(ccy, direction, bizType, lastID, limit, startTime, endTime)
var resp []LedgerInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, hfAccountLedgersMarginEPL, http.MethodGet, common.EncodeURLValues("/v3/hf/margin/account/ledgers", params), nil, &resp)
}
// GetFuturesAccountLedgers If there are open positions, the status of the first page returned will be Pending,
// indicating the realised profit and loss in the current 8-hour settlement period.
// Type RealisedPNL-Realised profit and loss, Deposit-Deposit, Withdrawal-withdraw, Transferin-Transfer in, TransferOut-Transfer out
func (ku *Kucoin) GetFuturesAccountLedgers(ctx context.Context, ccy currency.Code, forward bool, startAt, endAt time.Time, offset, maxCount int64) (*FuturesLedgerInfo, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
if forward {
params.Set("forward", "true")
}
if !startAt.IsZero() && !endAt.IsZero() {
err := common.StartEndTimeCheck(startAt, endAt)
if err != nil {
return nil, err
}
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if offset != 0 {
params.Set("offset", strconv.FormatInt(offset, 10))
}
if maxCount != 0 {
params.Set("maxCount", strconv.FormatInt(maxCount, 10))
}
var resp *FuturesLedgerInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresAccountLedgersEPL, http.MethodGet, common.EncodeURLValues("/v1/transaction-history", params), nil, &resp)
}
// GetAllSubAccountsInfoV1 retrieves the user info of all sub-account via this interface.
func (ku *Kucoin) GetAllSubAccountsInfoV1(ctx context.Context) ([]SubAccount, error) {
var resp []SubAccount
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, subAccountInfoV1EPL, http.MethodGet, "/v1/sub/user", nil, &resp)
}
// GetAllSubAccountsInfoV2 retrieves list of sub-accounts.
func (ku *Kucoin) GetAllSubAccountsInfoV2(ctx context.Context, currentPage, pageSize int64) (*SubAccountV2Response, 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 *SubAccountV2Response
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, allSubAccountsInfoV2EPL, http.MethodGet, "/v2/sub/user", 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, accountSummaryInfoEPL, http.MethodGet, "/v2/user-info", 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, subAccountBalancesEPL, http.MethodGet, "/v1/sub-accounts", nil, &resp)
}
// GetAllSubAccountsBalanceV2 retrieves sub-account balance information through the V2 API
func (ku *Kucoin) GetAllSubAccountsBalanceV2(ctx context.Context) (*SubAccountsBalanceV2, error) {
var resp *SubAccountsBalanceV2
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, allSubAccountBalancesV2EPL, http.MethodGet, "/v2/sub-accounts", 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, allSubAccountsBalanceEPL, http.MethodGet, common.EncodeURLValues("/v1/sub-accounts", params), nil, &resp)
}
// GetTransferableBalance get the transferable balance of a specified account
// The account type:MAIN、TRADE、TRADE_HF、MARGIN、ISOLATED
func (ku *Kucoin) GetTransferableBalance(ctx context.Context, ccy currency.Code, accountType, tag string) (*TransferableBalanceInfo, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if accountType == "" {
return nil, errAccountTypeMissing
}
params := url.Values{}
params.Set("currency", ccy.String())
params.Set("type", accountType)
if tag != "" {
params.Set("tag", tag)
}
var resp *TransferableBalanceInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getTransferablesEPL, http.MethodGet, common.EncodeURLValues("/v1/accounts/transferable", params), nil, &resp)
}
// GetUniversalTransfer support transfer between master and sub accounts (only applicable to master account APIKey).
func (ku *Kucoin) GetUniversalTransfer(ctx context.Context, arg *UniversalTransferParam) (string, error) {
if *arg == (UniversalTransferParam{}) {
return "", common.ErrNilPointer
}
if arg.ClientSuppliedOrderID == "" {
return "", order.ErrClientOrderIDMustBeSet
}
if arg.Amount <= 0 {
return "", order.ErrAmountBelowMin
}
if arg.FromAccountType == "" {
return "", fmt.Errorf("%w, empty fromAccountType", errAccountTypeMissing)
}
if arg.TransferType == "" {
return "", fmt.Errorf("%w, transfer type is empty", errTransferTypeMissing)
}
if arg.ToAccountType == "" {
return "", fmt.Errorf("%w, toAccountType is empty", errAccountTypeMissing)
}
var resp string
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, flexiTransferEPL, http.MethodPost, "/v3/accounts/universal-transfer", arg, &resp)
}
// TransferMainToSubAccount used to transfer funds from main account to sub-account
func (ku *Kucoin) TransferMainToSubAccount(ctx context.Context, ccy currency.Code, amount float64, clientOID, direction, accountType, subAccountType, subUserID string) (string, error) {
if clientOID == "" {
return "", order.ErrClientOrderIDMustBeSet
}
if ccy.IsEmpty() {
return "", currency.ErrCurrencyCodeEmpty
}
if amount <= 0 {
return "", order.ErrAmountBelowMin
}
if direction == "" {
return "", errTransferDirectionRequired
}
if subUserID == "" {
return "", fmt.Errorf("%w, sub-user ID is required", errSubUserIDRequired)
}
arg := &struct {
ClientOrderID string `json:"clientOid"`
SubUserID string `json:"subUserId"`
Currency currency.Code `json:"currency"`
Amount float64 `json:"amount,string"`
Direction string `json:"direction"`
AccountType string `json:"accountType,omitempty"`
SubAccountType string `json:"subAccountType,omitempty"`
}{
ClientOrderID: clientOID,
SubUserID: subUserID,
Currency: ccy,
Amount: amount,
Direction: direction,
AccountType: accountType,
SubAccountType: subAccountType,
}
resp := struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, masterSubUserTransferEPL, http.MethodPost, "/v2/accounts/sub-transfer", arg, &resp)
}
// MakeInnerTransfer used to transfer funds between accounts internally
// possible account types: main, trade, trade_hf, margin, isolated, margin_v2, isolated_v2, contract
func (ku *Kucoin) MakeInnerTransfer(ctx context.Context, amount float64, ccy currency.Code, clientOID, paymentAccountType, receivingAccountType, fromTag, toTag string) (string, error) {
if ccy.IsEmpty() {
return "", currency.ErrCurrencyCodeEmpty
}
if clientOID == "" {
return "", order.ErrClientOrderIDMustBeSet
}
if amount <= 0 {
return "", order.ErrAmountBelowMin
}
if paymentAccountType == "" {
return "", fmt.Errorf("%w sending account type is required", errAccountTypeMissing)
}
if receivingAccountType == "" {
return "", fmt.Errorf("%w receiving account type is required", errAccountTypeMissing)
}
arg := &struct {
ClientOrderID string `json:"clientOid"`
Currency currency.Code `json:"currency"`
Amount float64 `json:"amount,string"`
PaymentAccountType string `json:"from"`
ReceivingAccountType string `json:"to"`
FromTag string `json:"fromTag,omitempty"`
ToTag string `json:"toTag,omitempty"`
}{
ClientOrderID: clientOID,
Amount: amount,
Currency: ccy,
PaymentAccountType: paymentAccountType,
ReceivingAccountType: receivingAccountType,
FromTag: fromTag,
ToTag: toTag,
}
resp := struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, innerTransferEPL, http.MethodPost, "/v2/accounts/inner-transfer", arg, &resp)
}
// TransferToMainOrTradeAccount transfers fund from KuCoin Futures account to Main or Trade accounts.
func (ku *Kucoin) TransferToMainOrTradeAccount(ctx context.Context, arg *FundTransferFuturesParam) (*InnerTransferToMainAndTradeResponse, error) {
if *arg == (FundTransferFuturesParam{}) {
return nil, common.ErrNilPointer
}
if arg.Amount <= 0 {
return nil, order.ErrAmountBelowMin
}
if arg.Currency.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if arg.RecieveAccountType != "MAIN" && arg.RecieveAccountType != SpotTradeType {
return nil, fmt.Errorf("invalid receive account type %s, only TRADE and MAIN are supported", arg.RecieveAccountType)
}
var resp *InnerTransferToMainAndTradeResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, toMainOrTradeAccountEPL, http.MethodPost, "/v3/transfer-out", arg, &resp)
}
// TransferToFuturesAccount transfers fund from KuCoin Futures account to Main or Trade accounts.
func (ku *Kucoin) TransferToFuturesAccount(ctx context.Context, arg *FundTransferToFuturesParam) (*FundTransferToFuturesResponse, error) {
if *arg == (FundTransferToFuturesParam{}) {
return nil, common.ErrNilPointer
}
if arg.Amount <= 0 {
return nil, order.ErrAmountBelowMin
}
if arg.Currency.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if arg.PaymentAccountType != "MAIN" && arg.PaymentAccountType != SpotTradeType {
return nil, fmt.Errorf("invalid receive account type %s, only TRADE and MAIN are supported", arg.PaymentAccountType)
}
var resp *FundTransferToFuturesResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, toFuturesAccountEPL, http.MethodPost, "/v1/transfer-in", arg, &resp)
}
// GetFuturesTransferOutRequestRecords retrieves futures transfers out requests.
func (ku *Kucoin) GetFuturesTransferOutRequestRecords(ctx context.Context, startAt, endAt time.Time, status, queryStatus string, ccy currency.Code, currentPage, pageSize int64) (*FuturesTransferOutResponse, error) {
params := url.Values{}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
if status != "" {
params.Set("status", status)
}
if queryStatus != "" {
params.Set("queryStatus", queryStatus)
}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
if currentPage != 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize != 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *FuturesTransferOutResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresTransferOutRequestRecordsEPL, http.MethodGet, common.EncodeURLValues("/v1/transfer-list", params), nil, &resp)
}
// CreateDepositAddress create a deposit address for a currency you intend to deposit
func (ku *Kucoin) CreateDepositAddress(ctx context.Context, arg *DepositAddressParams) (*DepositAddress, error) {
if arg.Currency.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
var resp *DepositAddress
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, createDepositAddressEPL, http.MethodPost, "/v1/deposit-addresses", arg, &resp)
}
// GetDepositAddressesV2 get all deposit addresses for the currency you intend to deposit
func (ku *Kucoin) GetDepositAddressesV2(ctx context.Context, ccy currency.Code) ([]DepositAddress, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy.String())
var resp []DepositAddress
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, depositAddressesV2EPL, http.MethodGet, common.EncodeURLValues("/v2/deposit-addresses", params), nil, &resp)
}
// GetDepositAddressV1 get a deposit address for the currency you intend to deposit
func (ku *Kucoin) GetDepositAddressV1(ctx context.Context, ccy currency.Code, chain string) (*DepositAddress, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy.String())
if chain != "" {
params.Set("chain", chain)
}
var resp *DepositAddress
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, depositAddressesV1EPL, http.MethodGet, common.EncodeURLValues("/v1/deposit-addresses", params), nil, &resp)
}
// GetDepositList get deposit list items and sorted to show the latest first
// Status. Available value: PROCESSING, SUCCESS, and FAILURE
func (ku *Kucoin) GetDepositList(ctx context.Context, ccy currency.Code, status string, startAt, endAt time.Time) (*DepositResponse, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
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, depositListEPL, http.MethodGet, common.EncodeURLValues("/v1/deposits", params), nil, &resp)
}
// GetHistoricalDepositList get historical deposit list items
func (ku *Kucoin) GetHistoricalDepositList(ctx context.Context, ccy currency.Code, status string, startAt, endAt time.Time) (*HistoricalDepositWithdrawalResponse, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
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, historicDepositListEPL, http.MethodGet, common.EncodeURLValues("/v1/hist-deposits", params), nil, &resp)
}
// GetWithdrawalList get withdrawal list items
func (ku *Kucoin) GetWithdrawalList(ctx context.Context, ccy currency.Code, status string, startAt, endAt time.Time) (*WithdrawalsResponse, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
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, withdrawalListEPL, http.MethodGet, common.EncodeURLValues("/v1/withdrawals", params), nil, &resp)
}
// GetHistoricalWithdrawalList get historical withdrawal list items
func (ku *Kucoin) GetHistoricalWithdrawalList(ctx context.Context, ccy currency.Code, status string, startAt, endAt time.Time) (*HistoricalDepositWithdrawalResponse, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
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, retrieveV1HistoricalWithdrawalListEPL, http.MethodGet, common.EncodeURLValues("/v1/hist-withdrawals", params), nil, &resp)
}
// GetWithdrawalQuotas get withdrawal quota details
func (ku *Kucoin) GetWithdrawalQuotas(ctx context.Context, ccy currency.Code, chain string) (*WithdrawalQuota, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
params := url.Values{}
params.Set("currency", ccy.String())
if chain != "" {
params.Set("chain", chain)
}
var resp *WithdrawalQuota
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, withdrawalQuotaEPL, http.MethodGet, common.EncodeURLValues("/v1/withdrawals/quotas", 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
// Withdrawal fee deduct types are: INTERNAL and EXTERNAL
//
// TIP: On the WEB end, you can open the switch of specified favorite addresses for withdrawal, and when it is turned on,
// it will verify whether your withdrawal address(including chain) is a favorite address(it is case sensitive); if it fails validation,
// it will respond with the error message {"msg":"Already set withdraw whitelist, this address is not favorite address","code":"260325"}.
func (ku *Kucoin) ApplyWithdrawal(ctx context.Context, ccy currency.Code, address, memo, remark, chain, feeDeductType string, isInner bool, amount float64) (string, error) {
if ccy.IsEmpty() {
return "", currency.ErrCurrencyCodeEmpty
}
if address == "" {
return "", fmt.Errorf("%w, empty withdrawal address", errAddressRequired)
}
if amount <= 0 {
return "", order.ErrAmountBelowMin
}
arg := &struct {
Currency currency.Code `json:"currency"`
Address string `json:"address"`
Amount float64 `json:"amount"`
IsInner bool `json:"isInner"`
Memo string `json:"memo,omitempty"`
Remark string `json:"remark,omitempty"`
Chain string `json:"chain,omitempty"`
FeeDeductType string `json:"feeDeductType,omitempty"`
}{
Currency: ccy,
Address: address,
Amount: amount,
Memo: memo,
IsInner: isInner,
Remark: remark,
Chain: chain,
FeeDeductType: feeDeductType,
}
resp := struct {
WithdrawalID string `json:"withdrawalId"`
Error
}{}
return resp.WithdrawalID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, applyWithdrawalEPL, http.MethodPost, "/v1/withdrawals", arg, &resp)
}
// CancelWithdrawal used to cancel a withdrawal request
func (ku *Kucoin) CancelWithdrawal(ctx context.Context, withdrawalID string) error {
if withdrawalID == "" {
return fmt.Errorf("%w withdrawal ID is required", order.ErrOrderIDNotSet)
}
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, cancelWithdrawalsEPL, http.MethodDelete, "/v1/withdrawals/"+withdrawalID, nil, &struct{}{})
}
// GetBasicFee get basic fee rate of users
// Currency type: '0'-crypto currency, '1'-fiat currency. default is '0'-crypto currency
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, basicFeesEPL, http.MethodGet, common.EncodeURLValues("/v1/base-fee", 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
}
var resp []Fees
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, tradeFeesEPL, http.MethodGet, "/v1/trade-fees?symbols="+pairs.Upper().Join(), nil, &resp)
}
// ---------------------------------------------------------- Lending Market ----------------------------------------------------------------------------
// GetLendingCurrencyInformation retrieves a lending currency information.
func (ku *Kucoin) GetLendingCurrencyInformation(ctx context.Context, ccy currency.Code) ([]LendingCurrencyInfo, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp []LendingCurrencyInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, lendingCurrencyInfoEPL, http.MethodGet, common.EncodeURLValues("/v3/project/list", params), nil, &resp)
}
// GetInterestRate retrieves the interest rates of the margin lending market over the past 7 days.
func (ku *Kucoin) GetInterestRate(ctx context.Context, ccy currency.Code) ([]InterestRate, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
var resp []InterestRate
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, interestRateEPL, http.MethodGet, "/v3/project/marketInterestRate?currency="+ccy.String(), nil, &resp)
}
// MarginLendingSubscription retrieves margin lending subscription information.
func (ku *Kucoin) MarginLendingSubscription(ctx context.Context, ccy currency.Code, size, interestRate float64) (*OrderNumberResponse, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if size <= 0 {
return nil, order.ErrAmountBelowMin
}
if interestRate <= 0 {
return nil, errMissingInterestRate
}
arg := &struct {
Currency currency.Code `json:"currency"`
Size float64 `json:"size,string"`
InterestRate float64 `json:"interestRate"`
}{
Currency: ccy,
Size: size,
InterestRate: interestRate,
}
var resp *OrderNumberResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, marginLendingSubscriptionEPL, http.MethodPost, "/v3/purchase", arg, &resp)
}
// Redemption initiate redemptions of margin lending.
func (ku *Kucoin) Redemption(ctx context.Context, ccy currency.Code, size float64, purchaseOrderNo string) (*OrderNumberResponse, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if size <= 0 {
return nil, order.ErrAmountBelowMin
}
if purchaseOrderNo == "" {
return nil, errMissingPurchaseOrderNumber
}
arg := &struct {
Currency currency.Code `json:"currency"`
Size float64 `json:"size,string"`
PurchaseOrderNumber string `json:"purchaseOrderNo"`
}{
Currency: ccy,
Size: size,
PurchaseOrderNumber: purchaseOrderNo,
}
var resp *OrderNumberResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, redemptionEPL, http.MethodPost, "/v3/redeem", arg, &resp)
}
// ModifySubscriptionOrder is used to update the interest rates of subscription orders, which will take effect at the beginning of the next hour.
func (ku *Kucoin) ModifySubscriptionOrder(ctx context.Context, ccy currency.Code, purchaseOrderNo string, interestRate float64) (*ModifySubscriptionOrderResponse, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if interestRate <= 0 {
return nil, errMissingInterestRate
}
if purchaseOrderNo == "" {
return nil, errMissingPurchaseOrderNumber
}
arg := map[string]interface{}{
"currency": ccy.String(),
"interestRate": interestRate,
"purchaseOrderNo": purchaseOrderNo,
}
var resp *ModifySubscriptionOrderResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, modifySubscriptionEPL, http.MethodPost, "/v3/lend/purchase/update", arg, &resp)
}
// GetRedemptionOrders query for the redemption orders.
// Status: DONE-completed; PENDING-settling
func (ku *Kucoin) GetRedemptionOrders(ctx context.Context, ccy currency.Code, status, redeemOrderNo string, currentPage, pageSize int64) (*RedemptionOrdersResponse, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if status == "" {
return nil, errStatusMissing
}
params := url.Values{}
params.Set("currency", ccy.String())
params.Set("status", status)
if redeemOrderNo != "" {
params.Set("redeemOrderNo", redeemOrderNo)
}
if currentPage > 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize > 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *RedemptionOrdersResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getRedemptionOrdersEPL, http.MethodGet, common.EncodeURLValues("/v3/redeem/orders", params), nil, &resp)
}
// GetSubscriptionOrders provides pagination query for the subscription orders.
func (ku *Kucoin) GetSubscriptionOrders(ctx context.Context, ccy currency.Code, purchaseOrderNo, status string, currentPage, pageSize int64) (*PurchaseSubscriptionOrdersResponse, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
if status == "" {
return nil, errStatusMissing
}
params := url.Values{}
params.Set("currency", ccy.String())
params.Set("status", status)
if purchaseOrderNo != "" {
params.Set("purchaseOrderNo", purchaseOrderNo)
}
if currentPage > 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize > 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *PurchaseSubscriptionOrdersResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, getSubscriptionOrdersEPL, http.MethodGet, common.EncodeURLValues("/v3/purchase/orders", params), 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": "3",
"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()
}
var intervalMap = map[kline.Interval]string{
kline.OneMin: "1min", kline.ThreeMin: "3min", kline.FiveMin: "5min", kline.FifteenMin: "15min", kline.ThirtyMin: "30min", kline.OneHour: "1hour", kline.TwoHour: "2hour", kline.FourHour: "4hour", kline.SixHour: "6hour", kline.EightHour: "8hour", kline.TwelveHour: "12hour", kline.OneDay: "1day", kline.OneWeek: "1week",
}
// IntervalToString returns a string from kline.Interval input.
func IntervalToString(interval kline.Interval) (string, error) {
intervalString, okay := intervalMap[interval]
if okay {
return intervalString, nil
}
return "", fmt.Errorf("%w interval: %v", kline.ErrUnsupportedInterval, interval)
}
// StringToOrderStatus returns an order.Status instance from string.
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)
}
}
// AccountToTradeTypeString returns the account trade type given the asset type and margin mode information for spot and margin assets.
func (ku *Kucoin) AccountToTradeTypeString(a asset.Item, marginMode string) string {
switch a {
case asset.Spot:
return SpotTradeType
case asset.Margin:
if strings.EqualFold(marginMode, "isolated") {
return IsolatedMarginTradeType
}
return CrossMarginTradeType
default:
return ""
}
}
// OrderSideString converts an order.Side instance to a string representation
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.Lower())
}
}
// GetTradingPairActualFees retrieves list of trading pairs and fees.
func (ku *Kucoin) GetTradingPairActualFees(ctx context.Context, symbols []string) ([]TradingPairFee, error) {
params := url.Values{}
if len(symbols) > 0 {
params.Set("symbols", strings.Join(symbols, ","))
}
var resp []TradingPairFee
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, tradingPairActualFeeEPL, http.MethodGet, common.EncodeURLValues("/v1/trade-fees", params), nil, &resp)
}
// ----------------------------------------------------------- Earn Endpoints ----------------------------------------------------------------
// SubscribeToEarnFixedIncomeProduct allows subscribing to fixed income products. If the subscription fails, it returns the corresponding error code.
func (ku *Kucoin) SubscribeToEarnFixedIncomeProduct(ctx context.Context, productID, accountType string, amount float64) (*SusbcribeEarn, error) {
if productID == "" {
return nil, errProductIDMissing
}
if amount <= 0 {
return nil, order.ErrAmountBelowMin
}
if accountType == "" {
return nil, errAccountTypeMissing
}
var resp *SusbcribeEarn
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, subscribeToEarnEPL, http.MethodPost, "/v1/earn/orders",
&map[string]interface{}{
"productId": productID,
"accountType": accountType,
"amount,string": amount,
}, &resp)
}
// RedeemByEarnHoldingID allows initiating redemption by holding ID.
// If the current holding is fully redeemed or in the process of being redeemed, it indicates that the holding does not exist.
// Confirmation field for early redemption penalty: 1 (confirm early redemption, and the current holding will be fully redeemed).
// This parameter is valid only for fixed-term products
func (ku *Kucoin) RedeemByEarnHoldingID(ctx context.Context, orderID, fromAccountType, confirmPunishRedeem string, amount float64) (*EarnRedeem, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
if amount <= 0 {
return nil, order.ErrAmountBelowMin
}
params := url.Values{}
params.Set("orderId", orderID)
params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
if fromAccountType != "" {
params.Set("fromAccountType", fromAccountType)
}
if confirmPunishRedeem != "" {
params.Set("confirmPunishRedeem", confirmPunishRedeem)
}
var resp *EarnRedeem
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, earnRedemptionEPL, http.MethodDelete, common.EncodeURLValues("/v1/earn/orders", params), nil, &resp)
}
// GetEarnRedeemPreviewByHoldingID retrieves redemption preview information by holding ID.
// If the current holding is fully redeemed or in the process of being redeemed, it indicates that the holding does not exist.
func (ku *Kucoin) GetEarnRedeemPreviewByHoldingID(ctx context.Context, orderID, fromAccountType string) (*EarnRedemptionPreview, error) {
if orderID == "" {
return nil, order.ErrOrderIDNotSet
}
params := url.Values{}
params.Set("orderId", orderID)
if fromAccountType != "" {
params.Set("fromAccountType", fromAccountType)
}
var resp *EarnRedemptionPreview
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, earnRedemptionPreviewEPL, http.MethodGet, common.EncodeURLValues("/v1/earn/redeem-preview", params), nil, &resp)
}
// ---------------------------------------------------------------- Kucoin Earn ----------------------------------------------------------------
// GetEarnSavingsProducts retrieves savings products. If no savings products are available, an empty list is returned.
func (ku *Kucoin) GetEarnSavingsProducts(ctx context.Context, ccy currency.Code) ([]EarnSavingProduct, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp []EarnSavingProduct
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, kucoinEarnSavingsProductsEPL, http.MethodGet, common.EncodeURLValues("/v1/earn/saving/products", params), nil, &resp)
}
// GetEarnFixedIncomeCurrentHoldings retrieves current holding assets of fixed income products. If no current holding assets are available, an empty list is returned.
func (ku *Kucoin) GetEarnFixedIncomeCurrentHoldings(ctx context.Context, productID, productCategory string, ccy currency.Code, currentPage, pageSize int64) (*FixedIncomeEarnHoldings, error) {
params := url.Values{}
if productID != "" {
params.Set("productId", productID)
}
if productCategory != "" {
params.Set("productCategory", productCategory)
}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
if currentPage > 0 {
params.Set("currentPage", strconv.FormatInt(currentPage, 10))
}
if pageSize > 0 {
params.Set("pageSize", strconv.FormatInt(pageSize, 10))
}
var resp *FixedIncomeEarnHoldings
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, kucoinEarnFixedIncomeCurrentHoldingEPL, http.MethodGet, common.EncodeURLValues("/v1/earn/hold-assets", params), nil, &resp)
}
// GetLimitedTimePromotionProducts retrieves limited-time promotion products. If no products are available, an empty list is returned.
func (ku *Kucoin) GetLimitedTimePromotionProducts(ctx context.Context, ccy currency.Code) ([]EarnProduct, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp []EarnProduct
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, earnLimitedTimePromotionProductEPL, http.MethodGet, common.EncodeURLValues("/v1/earn/promotion/products", params), nil, &resp)
}
// ---------------------------------------------------------------- Staking Endpoints ----------------------------------------------------------------
// GetEarnKCSStakingProducts retrieves KCS Staking products. If no KCS Staking products are available, an empty list is returned.
func (ku *Kucoin) GetEarnKCSStakingProducts(ctx context.Context, ccy currency.Code) ([]EarnProduct, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp []EarnProduct
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, earnKCSStakingProductEPL, http.MethodGet, common.EncodeURLValues("/v1/earn/kcs-staking/products", params), nil, &resp)
}
// GetEarnStakingProducts retrieves staking products. If no staking products are available, an empty list is returned.
func (ku *Kucoin) GetEarnStakingProducts(ctx context.Context, ccy currency.Code) ([]EarnProduct, error) {
params := url.Values{}
if !ccy.IsEmpty() {
params.Set("currency", ccy.String())
}
var resp []EarnProduct
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, earnStakingProductEPL, http.MethodGet, common.EncodeURLValues("/v1/earn/staking/products", params), nil, &resp)
}
// GetEarnETHStakingProducts retrieves ETH Staking products. If no ETH Staking products are available, an empty list is returned.
func (ku *Kucoin) GetEarnETHStakingProducts(ctx context.Context) ([]EarnProduct, error) {
var resp []EarnProduct
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, earnStakingProductEPL, http.MethodGet, "/v1/earn/eth-staking/products", nil, &resp)
}
// ---------------------------------------------------------------- VIP Lending ----------------------------------------------------------------
// GetInformationOnOffExchangeFundingAndLoans retrieves accounts that are currently involved in loans.
func (ku *Kucoin) GetInformationOnOffExchangeFundingAndLoans(ctx context.Context) (*OffExchangeFundingAndLoan, error) {
var resp *OffExchangeFundingAndLoan
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, vipLendingEPL, http.MethodGet, "/v1/otc-loan/loan", nil, &resp)
}
// GetInformationOnAccountInvolvedInOffExchangeLoans retrieves accounts that are currently involved in off-exchange loans.
func (ku *Kucoin) GetInformationOnAccountInvolvedInOffExchangeLoans(ctx context.Context) ([]VIPLendingAccounts, error) {
var resp []VIPLendingAccounts
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, vipLendingEPL, http.MethodGet, "/v1/otc-loan/accounts", nil, &resp)
}
// GetAffilateUserRebateInformation allows getting affiliate user rebate information.
func (ku *Kucoin) GetAffilateUserRebateInformation(ctx context.Context, date time.Time, offset string, maxCount int64) ([]UserRebateInfo, error) {
if date.IsZero() {
return nil, errQueryDateIsRequired
}
if offset == "" {
return nil, errOffsetIsRequired
}
params := url.Values{}
formattedDate := date.Format("20060102")
params.Set("date", formattedDate)
params.Set("offset", offset)
if maxCount > 0 {
params.Set("maxCount", strconv.FormatInt(maxCount, 10))
}
var resp []UserRebateInfo
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, affilateUserRebateInfoEPL, http.MethodGet, common.EncodeURLValues("/v2/affiliate/inviter/statistics", params), nil, &resp)
}
// GetMarginPairsConfigurations allows querying the configuration of cross margin trading pairs.
func (ku *Kucoin) GetMarginPairsConfigurations(ctx context.Context, symbol string) (*MarginPairConfigs, error) {
if symbol == "" {
return nil, currency.ErrSymbolStringEmpty
}
params := url.Values{}
params.Set("symbol", symbol)
var resp *MarginPairConfigs
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, marginPairsConfigurationEPL, http.MethodGet, common.EncodeURLValues("/v3/margin/symbols", params), nil, &resp)
}
// ModifyLeverageMultiplier this endpoint allows modifying the leverage multiplier for cross margin or isolated margin
func (ku *Kucoin) ModifyLeverageMultiplier(ctx context.Context, symbol string, leverage int64, isIsolated bool) error {
if leverage <= 0 {
return errInvalidLeverage
}
arg := &struct {
Symbol string `json:"symbol,omitempty"`
Leverage int64 `json:"leverage"`
IsIsolated bool `json:"isIsolated,omitempty"`
}{
Symbol: symbol,
Leverage: leverage,
IsIsolated: isIsolated,
}
return ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, modifyLeverageMultiplierEPL, http.MethodPost, "/v3/position/update-user-leverage", arg, &struct{}{})
}
// GetActiveHFOrderSymbols retrieves the symbols of active high-frequency orders.
// Possible values for tradeType are MARGIN_TRADE for cross-margin trading
// and MARGIN_ISOLATED_TRADE for isolated margin trading.
func (ku *Kucoin) GetActiveHFOrderSymbols(ctx context.Context, tradeType string) (*MarginActiveSymbolDetail, error) {
if tradeType == "" {
return nil, errTradeTypeMissing
}
params := url.Values{}
params.Set("tradeType", tradeType)
var resp *MarginActiveSymbolDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, marginActiveHFOrdersEPL, http.MethodGet, common.EncodeURLValues("/v3/hf/margin/order/active/symbols", params), nil, &resp)
}