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