mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
* 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>
2813 lines
112 KiB
Go
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)
|
|
}
|