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