exchanges: Add Kucoin support (#1102)

* init

* updates config

* wrapper configuration

* updates exchange readme

* adds SendAuthHTTPRequest and SendHTTPRequest

* adds ratelimit file

* adds test case and minor fixes

* improve error handling

* update testcases and improve GetSymbols API

* adds SPOT API's

* minor fix

* WIP

* WIP

* adds test case

* adds check in test case

* fixes in Auth. HTTP

* improvements

* adds trade, kline support and testcases

* adds SPOT API and testcases for same

* adds SPOT API and testcases

* adds SPOT API and testcase

* WIP

* adds API's

* adds API's

* adds test cases

* adds comment to exported data types

* adds API and test cases

* adds API

* adds API

* rearrange functions

* WIP: adds API

* adds API for Post Order SPOT

* adds API and few fixes

* fixes

* WIP

* WIP

* add PostBulkOrder API and its test case

* fix issues

* adds cancel order APIs and test cases for same

* add minor test fixes

* add API

* adds API

* fixes

* add API

* adds API and test cases

* fix test

* adds API

* adds test

* fix test

* adds API and test

* adds deposit API and test cases

* WIP

* adds API and test cases

* WIP

* WIP

* add public future API and test cases

* WIP

* remove v2 API and replace them with v1

* update test cases

* adds future order API and test cases

* adds futures order API

* adds API

* add API and test cases

* adds API and test cases

* adds API and test cases

* adds API and test cases

* Adding wrapper functions

* Fix on wrapper function

* Adding websocket support

* Complete addressing WS push datas

* Adding spot push data unit tests

* adding futures websocket push data handlers

* Adding futures websocket push data handlers

* Added unit tests

* Updating unit tests

* Updating wrapper and unit test functions

* Adding missing wrapper functions and code cleaning up

* Resolved linter issues

* Fixing websocket issues

* Fixing websocket issues

* Slight fix on config_example file

* Minor update

* Basic nits updates

* Fix minor linter issues

* Minor update

* Minor unit test update

* Minor unit test update

* Code update and linter issues fix

* Removed unnecessary type conversion codes

* Monor update based on review comment

* Fix based on review comments

* Adding rate-limiter

* Websocket update and overall minor fixes

* Removed IsAssetTypeEnabled method implementation

* Fix connection and formatting issues

* Updating orderbook issues

* Very minor label fix

* Minor error returning fix

* code cleaning up and minor spelling fix

* Updates on unit test

* Update on unit tests and slight code structure

* unit test update

* orderbook update and minor fix

* fix on race

* Mini linter fix

* fix minor parameter and unit test issues

* handler funcs and models update

* Fixing websocket and unit test issues

* order side string for active orders

* Fix on websocket and unit tests

* Minor type changes

* Minor Orderbook fix and unit test update

* Small fix on orderbook

* Updating orderbook functionality

* FIx on websocket orderbook handlers

* Small update on kucoin websocket

* fix missed review comments

* fix based on review comments

* Updating websocket orderbook and fixing unit tests

* Minor fixes

* unit test update

* Updating unit test according to enabled asset type

* toggle canManipulateRealOrders const

* Unit test update

* Fix minor issues

* minor fix

* documentation fix

* wrapper coverage and unused params fix

* testing and minor changes

* documentation, websocket and unit test update

* minor linter fix

* Websocket spot/margin subscription update

* minor ticker update fix

* minor fixes on endpoints

* timestamp and number convert method and unit tests

* timestamp convert minor update

* minor type and conversion fix

* create a common timestamp convert and fix minor issues

* linter and ticker fix

* Updating unit tests and order placing endpoint methods

* Added a pairs check

* Fix config test error

* rm unused error variable

* Fix source of linter issue

* code update: convert, wrapper and websocket fix

* minor code update

* Websocket code and unit tests update

* Websocket ticker ask/bid type change and small error msg fix

* docs update

* fix: websocket orderbook handling

* change orderbook channel to marketOrderbookLevel2Channels and fix websocket orderbook update

* Minor func rename and reciever change

* Minor orderbook unit test issue fix

* comment: about why we used a random delimiter '-' for futures

* update config files and FetchTradablePair func for futures pairs

* futures config pairs update

* remove ConnextionMonitorDelay from websocket setup

* fix on types and futures pair conversion

* updating config pairs

* change NewPairFromString to DeriveFrom

* unit tests update

* unit tests update

* Added TickerBatching

* added GetStandardConfig to GetDefaultConfig

---------

Co-authored-by: Jaydeep Rajpurohit <jaydeeppurohit1996@gmail.com>
This commit is contained in:
Samuael A
2023-09-27 05:09:38 +00:00
committed by GitHub
parent 5f2f6f884b
commit 6105071114
29 changed files with 11306 additions and 13 deletions

2
.gitignore vendored
View File

@@ -49,4 +49,4 @@ __debug_bin
# Coverage reports
coverage.txt
wrapperconfig.json
wrapperconfig.json

View File

@@ -6,8 +6,8 @@ gloriousCode | https://github.com/gloriousCode
dependabot[bot] | https://github.com/apps/dependabot
dependabot-preview[bot] | https://github.com/apps/dependabot-preview
xtda | https://github.com/xtda
lrascao | https://github.com/lrascao
gbjk | https://github.com/gbjk
lrascao | https://github.com/lrascao
Rots | https://github.com/Rots
vazha | https://github.com/vazha
ydm | https://github.com/ydm
@@ -19,12 +19,12 @@ marcofranssen | https://github.com/marcofranssen
geseq | https://github.com/geseq
Beadko | https://github.com/Beadko
TaltaM | https://github.com/TaltaM
samuael | https://github.com/samuael
dackroyd | https://github.com/dackroyd
cranktakular | https://github.com/cranktakular
khcchiu | https://github.com/khcchiu
samuael | https://github.com/samuael
woshidama323 | https://github.com/woshidama323
yangrq1018 | https://github.com/yangrq1018
woshidama323 | https://github.com/woshidama323
crackcomm | https://github.com/crackcomm
azhang | https://github.com/azhang
andreygrehov | https://github.com/andreygrehov

View File

@@ -39,6 +39,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Huobi.Pro | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| Kucoin | Yes | Yes | NA |
| Lbank | Yes | No | NA |
| Okcoin | Yes | Yes | No |
| Okx | Yes | Yes | NA |
@@ -143,14 +144,14 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Contribution Amount|
|--|--|
| [thrasher-](https://github.com/thrasher-) | 682 |
| [shazbert](https://github.com/shazbert) | 299 |
| [gloriousCode](https://github.com/gloriousCode) | 217 |
| [dependabot[bot]](https://github.com/apps/dependabot) | 202 |
| [thrasher-](https://github.com/thrasher-) | 683 |
| [shazbert](https://github.com/shazbert) | 301 |
| [gloriousCode](https://github.com/gloriousCode) | 219 |
| [dependabot[bot]](https://github.com/apps/dependabot) | 207 |
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 |
| [xtda](https://github.com/xtda) | 47 |
| [gbjk](https://github.com/gbjk) | 35 |
| [lrascao](https://github.com/lrascao) | 27 |
| [gbjk](https://github.com/gbjk) | 26 |
| [Rots](https://github.com/Rots) | 15 |
| [vazha](https://github.com/vazha) | 15 |
| [ydm](https://github.com/ydm) | 15 |
@@ -162,12 +163,12 @@ Binaries will be published once the codebase reaches a stable condition.
| [geseq](https://github.com/geseq) | 8 |
| [Beadko](https://github.com/Beadko) | 6 |
| [TaltaM](https://github.com/TaltaM) | 6 |
| [samuael](https://github.com/samuael) | 6 |
| [dackroyd](https://github.com/dackroyd) | 5 |
| [cranktakular](https://github.com/cranktakular) | 5 |
| [khcchiu](https://github.com/khcchiu) | 5 |
| [samuael](https://github.com/samuael) | 5 |
| [yangrq1018](https://github.com/yangrq1018) | 4 |
| [woshidama323](https://github.com/woshidama323) | 3 |
| [yangrq1018](https://github.com/yangrq1018) | 3 |
| [crackcomm](https://github.com/crackcomm) | 3 |
| [azhang](https://github.com/azhang) | 2 |
| [andreygrehov](https://github.com/andreygrehov) | 2 |

View File

@@ -62,6 +62,7 @@ _b in this context is an `IBotExchange` implemented struct_
| Huobi.Pro | Yes | Yes | No |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | No |
| Kucoin | Yes | No | Yes |
| Lbank | Yes | No | Yes |
| Okcoin | Yes | Yes | Yes |
| Okx | Yes | Yes | Yes |

View File

@@ -40,6 +40,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Huobi.Pro | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| Kucoin | Yes | Yes | NA |
| Lbank | Yes | No | NA |
| Okcoin | Yes | Yes | No |
| Okx | Yes | Yes | NA |

View File

@@ -136,6 +136,11 @@
"secret": "Secret",
"otpSecret": "-"
},
"kucoin":{
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"lbank": {
"key": "Key",
"secret": "Secret",

View File

@@ -2,6 +2,7 @@ package convert
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math"
@@ -242,3 +243,56 @@ func (f StringToFloat64) Float64() float64 {
func (f StringToFloat64) Decimal() decimal.Decimal {
return decimal.NewFromFloat(float64(f))
}
// ExchangeTime provides timestamp to time conversion method.
type ExchangeTime time.Time
// UnmarshalJSON is custom type json unmarshaller for ExchangeTime
func (k *ExchangeTime) UnmarshalJSON(data []byte) error {
var timestamp interface{}
err := json.Unmarshal(data, &timestamp)
if err != nil {
return err
}
var standard int64
switch value := timestamp.(type) {
case string:
if value == "" {
// Setting the time to zero value because some timestamp fields could return an empty string while there is no error
// So, in such cases, Time returns zero timestamp.
break
}
standard, err = strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
case int64:
standard = value
case float64:
// Warning: converting float64 to int64 instance may create loss of precision in the timestamp information.
// be aware or consider customizing this section if found necessary.
standard = int64(value)
case nil:
// for some exchange timestamp fields, if the timestamp information is not specified,
// the data is 'nil' instead of zero value string or integer value.
default:
return fmt.Errorf("unsupported timestamp type %T", timestamp)
}
switch {
case standard == 0:
*k = ExchangeTime(time.Time{})
case standard >= 1e13:
*k = ExchangeTime(time.Unix(standard/1e9, standard%1e9))
case standard > 9999999999:
*k = ExchangeTime(time.UnixMilli(standard))
default:
*k = ExchangeTime(time.Unix(standard, 0))
}
return nil
}
// Time returns a time.Time instance from ExchangeTime instance object.
func (k ExchangeTime) Time() time.Time {
return time.Time(k)
}

View File

@@ -400,3 +400,86 @@ func BenchmarkStringToFloat64(b *testing.B) {
}
}
}
func TestExchangeTimeUnmarshalJSON(t *testing.T) {
t.Parallel()
unmarshaledResult := &struct {
Timestamp ExchangeTime `json:"ts"`
}{}
data1 := `{"ts":""}`
result := time.Time{}
err := json.Unmarshal([]byte(data1), &unmarshaledResult)
if err != nil {
t.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
t.Errorf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
data2 := `{"ts":"1685564775371"}`
result = time.UnixMilli(1685564775371)
err = json.Unmarshal([]byte(data2), &unmarshaledResult)
if err != nil {
t.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
t.Errorf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
data3 := `{"ts":1685564775371}`
err = json.Unmarshal([]byte(data3), &unmarshaledResult)
if err != nil {
t.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
t.Errorf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
data4 := `{"ts":"1685564775"}`
result = time.Unix(1685564775, 0)
err = json.Unmarshal([]byte(data4), &unmarshaledResult)
if err != nil {
t.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
t.Errorf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
data5 := `{"ts":1685564775}`
err = json.Unmarshal([]byte(data5), &unmarshaledResult)
if err != nil {
t.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
t.Errorf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
data6 := `{"ts":"1685564775371320000"}`
result = time.Unix(int64(1685564775371320000)/1e9, int64(1685564775371320000)%1e9)
err = json.Unmarshal([]byte(data6), &unmarshaledResult)
if err != nil {
t.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
t.Errorf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
data7 := `{"ts":"abcdefg"}`
err = json.Unmarshal([]byte(data7), &unmarshaledResult)
if err == nil {
t.Fatal("expecting error but found nil")
}
data8 := `{"ts":0}`
result = time.Time{}
err = json.Unmarshal([]byte(data8), &unmarshaledResult)
if err != nil {
t.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
t.Errorf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
}
// 2239239 516.1 ns/op 424 B/op 9 allocs/op
func BenchmarkExchangeTimeUnmarshaling(b *testing.B) {
unmarshaledResult := &struct {
Timestamp ExchangeTime `json:"ts"`
}{}
data5 := `{"ts":1685564775}`
result := time.Unix(1685564775, 0)
var err error
for i := 0; i < b.N; i++ {
if err = json.Unmarshal([]byte(data5), &unmarshaledResult); err != nil {
b.Fatal(err)
} else if !unmarshaledResult.Timestamp.Time().Equal(result) {
b.Fatalf("found %v, but expected %v", unmarshaledResult.Timestamp.Time(), result)
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1679,7 +1679,6 @@ var (
YFI = NewCode("YFI")
BAL = NewCode("BAL")
UMA = NewCode("UMA")
KDA = NewCode("KDA")
SNX = NewCode("SNX")
CRV = NewCode("CRV")
OXT = NewCode("OXT")
@@ -3008,6 +3007,13 @@ var (
USDFL = NewCode("USDFL")
FLUSD = NewCode("FLUSD")
DUSD = NewCode("DUSD")
USDD = NewCode("USDD")
KDA = NewCode("KDA")
XCN = NewCode("XCN")
TEL = NewCode("TEL")
XDC = NewCode("XDC")
MHC = NewCode("MHC")
OXEN = NewCode("OXEN")
STETH = NewCode("STETH")
stables = Currencies{

View File

@@ -215,6 +215,7 @@ Yes means supported, No means not yet implemented and NA means protocol unsuppor
| Huobi.Pro | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| Kucoin | Yes | Yes | No |
| Lbank | Yes | No | NA |
| Okcoin | Yes | Yes | No |
| Okx | Yes | Yes | NA |
@@ -245,6 +246,7 @@ var Exchanges = []string{
"huobi",
"itbit",
"kraken",
"kucoin",
"lbank",
"okcoin",
"okx",

View File

@@ -63,6 +63,7 @@ $ ./gctcli withdrawcryptofunds --exchange=binance --currency=USDT --address=TJU9
| Huobi.Pro | Yes | Yes | |
| ItBit | No | No | |
| Kraken | Yes | Yes | Front-end and API don't match total available transfer chains |
| Kucoin | Yes | Yes | |
| Lbank | No | No | |
| Okcoin | Yes | Yes | |
| Okx | Yes | Yes | |

View File

@@ -84,7 +84,8 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
| HitBTC | Y |
| Huobi | Y |
| itBIT | |
| Kraken | Y |
| Kraken | Y |
| Kucoin | Y |
| lBank | Y |
| Okcoin | Y |
| Okx | Y |

View File

@@ -28,6 +28,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/huobi"
"github.com/thrasher-corp/gocryptotrader/exchanges/itbit"
"github.com/thrasher-corp/gocryptotrader/exchanges/kraken"
"github.com/thrasher-corp/gocryptotrader/exchanges/kucoin"
"github.com/thrasher-corp/gocryptotrader/exchanges/lbank"
"github.com/thrasher-corp/gocryptotrader/exchanges/okcoin"
"github.com/thrasher-corp/gocryptotrader/exchanges/okx"
@@ -196,6 +197,8 @@ func (m *ExchangeManager) NewExchangeByName(name string) (exchange.IBotExchange,
exch = new(itbit.ItBit)
case "kraken":
exch = new(kraken.Kraken)
case "kucoin":
exch = new(kucoin.Kucoin)
case "lbank":
exch = new(lbank.Lbank)
case "okcoin":

140
exchanges/kucoin/README.md Normal file
View File

@@ -0,0 +1,140 @@
# GoCryptoTrader package Kucoin
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/kucoin)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
This kucoin package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
## Kucoin Exchange
### Current Features
+ REST Support
+ Websocket Support
### How to enable
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
+ Individual package example below:
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### How to do REST public/private calls
+ If enabled via "configuration".json file the exchange will be added to the
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
the wrapper interface functions for accessing exchange data. View routines.go
for an example of integration usage with GoCryptoTrader. Rudimentary example
below:
main.go
```go
var b exchange.IBotExchange
for i := range bot.Exchanges {
if bot.Exchanges[i].GetName() == "Kucoin" {
b = bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := b.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := b.FetchOrderbook()
if err != nil {
// Handle error
}
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
// set and AuthenticatedAPISupport is set to true
// Fetches current account information
accountInfo, err := b.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := b.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := b.GetOrderBook()
if err != nil {
// Handle error
}
// Private calls - make sure your APIKEY and APISECRET are set and
// AuthenticatedAPISupport is set to true
// GetUserInfo returns account info
accountInfo, err := b.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := b.Trade(...)
if err != nil {
// Handle error
}
```
### How to do Websocket public/private calls
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
Please feel free to submit any pull requests or suggest any desired features to be added.
When submitting a PR, please abide by our coding guidelines:
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Donations
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***

1851
exchanges/kucoin/kucoin.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
package kucoin
import (
"encoding/json"
"fmt"
"strconv"
)
// UnmarshalJSON valid data to SubAccountsResponse of return nil if the data is empty list.
// this is added to handle the empty list returned when there are no accounts.
func (a *SubAccountsResponse) UnmarshalJSON(data []byte) error {
var result interface{}
err := json.Unmarshal(data, &result)
if err != nil {
return err
}
var ok bool
if a, ok = result.(*SubAccountsResponse); ok {
if a == nil {
return errNoValidResponseFromServer
}
return nil
} else if _, ok := result.([]interface{}); ok {
return nil
}
return fmt.Errorf("%w can not unmarshal to SubAccountsResponse", errMalformedData)
}
// kucoinNumber unmarshals and extract numeric value from a byte slice.
type kucoinNumber float64
// Float64 returns an float64 value from kucoinNumeric instance
func (a *kucoinNumber) Float64() float64 {
return float64(*a)
}
// UnmarshalJSON decerializes integer and string data having an integer value to int64
func (a *kucoinNumber) UnmarshalJSON(data []byte) error {
var value interface{}
err := json.Unmarshal(data, &value)
if err != nil {
return err
}
switch val := value.(type) {
case float64:
*a = kucoinNumber(val)
case float32:
*a = kucoinNumber(val)
case string:
if val == "" {
*a = kucoinNumber(0) // setting empty string value to zero to reset previous value if exist.
return nil
}
value, err := strconv.ParseFloat(val, 64)
if err != nil {
return err
}
*a = kucoinNumber(value)
case int64:
*a = kucoinNumber(val)
case int32:
*a = kucoinNumber(val)
default:
return fmt.Errorf("unsupported input numeric type %T", value)
}
return nil
}

View File

@@ -0,0 +1,821 @@
package kucoin
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
const (
kucoinFuturesAPIURL = "https://api-futures.kucoin.com/api"
kucoinWebsocketURL = "wss://ws-api.kucoin.com/endpoint"
// Public market endpoints
kucoinFuturesOpenContracts = "/v1/contracts/active"
kucoinFuturesContract = "/v1/contracts/"
kucoinFuturesRealTimeTicker = "/v1/ticker"
kucoinFuturesFullOrderbook = "/v1/level2/snapshot"
kucoinFuturesPartOrderbook20 = "/v1/level2/depth20"
kucoinFuturesPartOrderbook100 = "/v1/level2/depth100"
kucoinFuturesTradeHistory = "/v1/trade/history"
kucoinFuturesInterestRate = "/v1/interest/query"
kucoinFuturesIndex = "/v1/index/query"
kucoinFuturesMarkPrice = "/v1/mark-price/%s/current"
kucoinFuturesPremiumIndex = "/v1/premium/query"
kucoinFuturesFundingRate = "/v1/funding-rate/%s/current"
kucoinFuturesServerTime = "/v1/timestamp"
kucoinFuturesServiceStatus = "/v1/status"
kucoinFuturesKline = "/v1/kline/query"
// Authenticated endpoints
kucoinFuturesOrder = "/v1/orders"
kucoinFuturesCancelOrder = "/v1/orders/"
kucoinFuturesStopOrder = "/v1/stopOrders"
kucoinFuturesRecentCompletedOrder = "/v1/recentDoneOrders"
kucoinFuturesGetOrderDetails = "/v1/orders/"
kucoinFuturesGetOrderDetailsByClientID = "/v1/orders/byClientOid"
kucoinFuturesFills = "/v1/fills"
kucoinFuturesRecentFills = "/v1/recentFills"
kucoinFuturesOpenOrderStats = "/v1/openOrderStatistics"
kucoinFuturesPosition = "/v1/position"
kucoinFuturesPositionList = "/v1/positions"
kucoinFuturesSetAutoDeposit = "/v1/position/margin/auto-deposit-status"
kucoinFuturesAddMargin = "/v1/position/margin/deposit-margin"
kucoinFuturesRiskLimitLevel = "/v1/contracts/risk-limit/"
kucoinFuturesUpdateRiskLmitLevel = "/v1/position/risk-limit-level/change"
kucoinFuturesFundingHistory = "/v1/funding-history"
kucoinFuturesAccountOverview = "/v1/account-overview"
kucoinFuturesTransactionHistory = "/v1/transaction-history"
kucoinFuturesSubAccountAPI = "/v1/sub/api-key"
kucoinFuturesDepositAddress = "/v1/deposit-address"
kucoinFuturesDepositsList = "/v1/deposit-list"
kucoinFuturesWithdrawalLimit = "/v1/withdrawals/quotas"
kucoinFuturesWithdrawalList = "/v1/withdrawal-list"
kucoinFuturesCancelWithdrawal = "/v1/withdrawals/"
kucoinFuturesTransferFundtoMainAccount = "/v3/transfer-out"
kucoinFuturesTransferFundtoFuturesAccount = "/v1/transfer-in"
kucoinFuturesTransferOutList = "/v1/transfer-list"
kucoinFuturesCancelTransferOut = "/v1/cancel/transfer-out"
)
// GetFuturesOpenContracts gets all open futures contract with its details
func (ku *Kucoin) GetFuturesOpenContracts(ctx context.Context) ([]Contract, error) {
var resp []Contract
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, kucoinFuturesOpenContracts, &resp)
}
// GetFuturesContract get contract details
func (ku *Kucoin) GetFuturesContract(ctx context.Context, symbol string) (*Contract, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
var resp *Contract
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, kucoinFuturesContract+symbol, &resp)
}
// GetFuturesRealTimeTicker get real time ticker
func (ku *Kucoin) GetFuturesRealTimeTicker(ctx context.Context, symbol string) (*FuturesTicker, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
var resp *FuturesTicker
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesRealTimeTicker, params), &resp)
}
// GetFuturesOrderbook gets full orderbook for a specified symbol
func (ku *Kucoin) GetFuturesOrderbook(ctx context.Context, symbol string) (*Orderbook, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
var o futuresOrderbookResponse
err := ku.SendHTTPRequest(ctx, exchange.RestFutures, futuresRetrieveFullOrderbookLevel2EPL, common.EncodeURLValues(kucoinFuturesFullOrderbook, params), &o)
if err != nil {
return nil, err
}
return constructFuturesOrderbook(&o)
}
// GetFuturesPartOrderbook20 gets orderbook for a specified symbol with depth 20
func (ku *Kucoin) GetFuturesPartOrderbook20(ctx context.Context, symbol string) (*Orderbook, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
var o futuresOrderbookResponse
err := ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesPartOrderbook20, params), &o)
if err != nil {
return nil, err
}
return constructFuturesOrderbook(&o)
}
// GetFuturesPartOrderbook100 gets orderbook for a specified symbol with depth 100
func (ku *Kucoin) GetFuturesPartOrderbook100(ctx context.Context, symbol string) (*Orderbook, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
var o futuresOrderbookResponse
err := ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesPartOrderbook100, params), &o)
if err != nil {
return nil, err
}
return constructFuturesOrderbook(&o)
}
// GetFuturesTradeHistory get last 100 trades for symbol
func (ku *Kucoin) GetFuturesTradeHistory(ctx context.Context, symbol string) ([]FuturesTrade, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
var resp []FuturesTrade
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesTradeHistory, params), &resp)
}
// GetFuturesInterestRate get interest rate
func (ku *Kucoin) GetFuturesInterestRate(ctx context.Context, symbol string, startAt, endAt time.Time, reverse, forward bool, offset, maxCount int64) (*FundingInterestRateResponse, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
params.Set("reverse", strconv.FormatBool(reverse))
params.Set("forward", strconv.FormatBool(forward))
if offset != 0 {
params.Set("offset", strconv.FormatInt(offset, 10))
}
if maxCount != 0 {
params.Set("maxCount", strconv.FormatInt(maxCount, 10))
}
var resp *FundingInterestRateResponse
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesInterestRate, params), &resp)
}
// GetFuturesIndexList retrieves futures index information for a symbol
func (ku *Kucoin) GetFuturesIndexList(ctx context.Context, symbol string, startAt, endAt time.Time, reverse, forward bool, offset, maxCount int64) (*FuturesIndexResponse, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
params.Set("reverse", strconv.FormatBool(reverse))
params.Set("forward", strconv.FormatBool(forward))
if offset != 0 {
params.Set("offset", strconv.FormatInt(offset, 10))
}
if maxCount != 0 {
params.Set("maxCount", strconv.FormatInt(maxCount, 10))
}
var resp *FuturesIndexResponse
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesIndex, params), &resp)
}
// GetFuturesCurrentMarkPrice get current mark price
func (ku *Kucoin) GetFuturesCurrentMarkPrice(ctx context.Context, symbol string) (*FuturesMarkPrice, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
var resp *FuturesMarkPrice
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, fmt.Sprintf(kucoinFuturesMarkPrice, symbol), &resp)
}
// GetFuturesPremiumIndex get premium index
func (ku *Kucoin) GetFuturesPremiumIndex(ctx context.Context, symbol string, startAt, endAt time.Time, reverse, forward bool, offset, maxCount int64) (*FuturesInterestRateResponse, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
params.Set("reverse", strconv.FormatBool(reverse))
params.Set("forward", strconv.FormatBool(forward))
if offset != 0 {
params.Set("offset", strconv.FormatInt(offset, 10))
}
if maxCount != 0 {
params.Set("maxCount", strconv.FormatInt(maxCount, 10))
}
var resp *FuturesInterestRateResponse
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesPremiumIndex, params), &resp)
}
// GetFuturesCurrentFundingRate get current funding rate
func (ku *Kucoin) GetFuturesCurrentFundingRate(ctx context.Context, symbol string) (*FuturesFundingRate, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
var resp *FuturesFundingRate
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, fmt.Sprintf(kucoinFuturesFundingRate, symbol), &resp)
}
// GetFuturesServerTime get server time
func (ku *Kucoin) GetFuturesServerTime(ctx context.Context) (time.Time, error) {
resp := struct {
Data convert.ExchangeTime `json:"data"`
Error
}{}
err := ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, kucoinFuturesServerTime, &resp)
if err != nil {
return time.Time{}, err
}
return resp.Data.Time(), nil
}
// GetFuturesServiceStatus get service status
func (ku *Kucoin) GetFuturesServiceStatus(ctx context.Context) (*FuturesServiceStatus, error) {
var resp *FuturesServiceStatus
return resp, ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, kucoinFuturesServiceStatus, &resp)
}
// GetFuturesKline get contract's kline data
func (ku *Kucoin) GetFuturesKline(ctx context.Context, granularity int64, symbol string, from, to time.Time) ([]FuturesKline, error) {
if granularity == 0 {
return nil, errors.New("granularity can not be empty")
}
if !common.StringDataContains(validGranularity, strconv.FormatInt(granularity, 10)) {
return nil, errors.New("invalid granularity")
}
params := url.Values{}
// The granularity (granularity parameter of K-line) represents the number of minutes, the available granularity scope is: 1,5,15,30,60,120,240,480,720,1440,10080. Requests beyond the above range will be rejected.
params.Set("granularity", strconv.FormatInt(granularity, 10))
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params.Set("symbol", symbol)
if !from.IsZero() {
params.Set("from", strconv.FormatInt(from.UnixMilli(), 10))
}
if !to.IsZero() {
params.Set("to", strconv.FormatInt(to.UnixMilli(), 10))
}
var resp [][6]float64
err := ku.SendHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, common.EncodeURLValues(kucoinFuturesKline, params), &resp)
if err != nil {
return nil, err
}
kline := make([]FuturesKline, len(resp))
for i := range resp {
kline[i] = FuturesKline{
StartTime: time.UnixMilli(int64(resp[i][0])),
Open: resp[i][1],
High: resp[i][2],
Low: resp[i][3],
Close: resp[i][4],
Volume: resp[i][5],
}
}
return kline, nil
}
// PostFuturesOrder used to place two types of futures orders: limit and market
func (ku *Kucoin) PostFuturesOrder(ctx context.Context, arg *FuturesOrderParam) (string, error) {
if arg.Leverage < 0.01 {
return "", fmt.Errorf("%w must be greater than 0.01", errInvalidLeverage)
}
if arg.ClientOrderID == "" {
return "", errInvalidClientOrderID
}
if arg.Side == "" {
return "", fmt.Errorf("%w, empty order side", order.ErrSideIsInvalid)
}
if arg.Symbol.IsEmpty() {
return "", currency.ErrCurrencyPairEmpty
}
if arg.Stop != "" {
if arg.StopPriceType == "" {
return "", errInvalidStopPriceType
}
if arg.StopPrice <= 0 {
return "", fmt.Errorf("%w, stopPrice is required", errInvalidPrice)
}
}
switch arg.OrderType {
case "limit", "":
if arg.Price <= 0 {
return "", fmt.Errorf("%w %f", errInvalidPrice, arg.Price)
}
if arg.Size <= 0 {
return "", fmt.Errorf("%w, must be non-zero positive value", errInvalidSize)
}
if arg.VisibleSize < 0 {
return "", fmt.Errorf("%w, visible size must be non-zero positive value", errInvalidSize)
}
case "market":
if arg.Size <= 0 {
return "", fmt.Errorf("%w, market size must be > 0", errInvalidSize)
}
default:
return "", fmt.Errorf("%w, order type= %s", order.ErrTypeIsInvalid, arg.OrderType)
}
resp := struct {
OrderID string `json:"orderId"`
}{}
return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresPlaceOrderEPL, http.MethodPost, kucoinFuturesOrder, &arg, &resp)
}
// CancelFuturesOrder used to cancel single order previously placed including a stop order
func (ku *Kucoin) CancelFuturesOrder(ctx context.Context, orderID string) ([]string, error) {
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
}{}
if orderID == "" {
return resp.CancelledOrderIDs, errors.New("orderID can't be empty")
}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresCancelAnOrderEPL, http.MethodDelete, kucoinFuturesCancelOrder+orderID, nil, &resp)
}
// CancelAllFuturesOpenOrders used to cancel all futures order excluding stop orders
func (ku *Kucoin) CancelAllFuturesOpenOrders(ctx context.Context, symbol string) ([]string, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
}{}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresLimitOrderMassCancelationEPL, http.MethodDelete, common.EncodeURLValues(kucoinFuturesOrder, params), nil, &resp)
}
// CancelAllFuturesStopOrders used to cancel all untriggered stop orders
func (ku *Kucoin) CancelAllFuturesStopOrders(ctx context.Context, symbol string) ([]string, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
resp := struct {
CancelledOrderIDs []string `json:"cancelledOrderIds"`
}{}
return resp.CancelledOrderIDs, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodDelete, common.EncodeURLValues(kucoinFuturesStopOrder, params), nil, &resp)
}
// GetFuturesOrders gets the user current futures order list
func (ku *Kucoin) GetFuturesOrders(ctx context.Context, status, symbol, side, orderType string, startAt, endAt time.Time) (*FutureOrdersResponse, error) {
params := url.Values{}
if status != "" {
params.Set("status", status)
}
if symbol != "" {
params.Set("symbol", symbol)
}
if side != "" {
params.Set("side", side)
}
if orderType != "" {
params.Set("type", orderType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *FutureOrdersResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresRetrieveOrderListEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesOrder, params), nil, &resp)
}
// GetUntriggeredFuturesStopOrders gets the untriggered stop orders list
func (ku *Kucoin) GetUntriggeredFuturesStopOrders(ctx context.Context, symbol, side, orderType string, startAt, endAt time.Time) (*FutureOrdersResponse, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if side != "" {
params.Set("side", side)
}
if orderType != "" {
params.Set("type", orderType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *FutureOrdersResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesStopOrder, params), nil, &resp)
}
// GetFuturesRecentCompletedOrders gets list of recent 1000 orders in the last 24 hours
func (ku *Kucoin) GetFuturesRecentCompletedOrders(ctx context.Context) ([]FuturesOrder, error) {
var resp []FuturesOrder
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, kucoinFuturesRecentCompletedOrder, nil, &resp)
}
// GetFuturesOrderDetails gets single order details by order ID
func (ku *Kucoin) GetFuturesOrderDetails(ctx context.Context, orderID string) (*FuturesOrder, error) {
var resp *FuturesOrder
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, kucoinFuturesGetOrderDetails+orderID, nil, &resp)
}
// GetFuturesOrderDetailsByClientID gets single order details by client ID
func (ku *Kucoin) GetFuturesOrderDetailsByClientID(ctx context.Context, clientID string) (*FuturesOrder, error) {
if clientID == "" {
return nil, errors.New("clientID can't be empty")
}
params := url.Values{}
params.Set("clientOid", clientID)
var resp *FuturesOrder
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesGetOrderDetailsByClientID, params), nil, &resp)
}
// GetFuturesFills gets list of recent fills
func (ku *Kucoin) GetFuturesFills(ctx context.Context, orderID, symbol, side, orderType string, startAt, endAt time.Time) (*FutureFillsResponse, error) {
params := url.Values{}
if orderID != "" {
params.Set("orderId", orderID)
}
if symbol != "" {
params.Set("symbol", symbol)
}
if side != "" {
params.Set("side", side)
}
if orderType != "" {
params.Set("type", orderType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
var resp *FutureFillsResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresRetrieveFillsEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesFills, params), nil, &resp)
}
// GetFuturesRecentFills gets list of 1000 recent fills in the last 24 hrs
func (ku *Kucoin) GetFuturesRecentFills(ctx context.Context) ([]FuturesFill, error) {
var resp []FuturesFill
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresRecentFillsEPL, http.MethodGet, kucoinFuturesRecentFills, nil, &resp)
}
// GetFuturesOpenOrderStats gets the total number and value of the all your active orders
func (ku *Kucoin) GetFuturesOpenOrderStats(ctx context.Context, symbol string) (*FuturesOpenOrderStats, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
var resp *FuturesOpenOrderStats
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesOpenOrderStats, params), nil, &resp)
}
// GetFuturesPosition gets the position details of a specified position
func (ku *Kucoin) GetFuturesPosition(ctx context.Context, symbol string) (*FuturesPosition, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
var resp *FuturesPosition
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesPosition, params), nil, &resp)
}
// GetFuturesPositionList gets the list of position with details
func (ku *Kucoin) GetFuturesPositionList(ctx context.Context) ([]FuturesPosition, error) {
var resp []FuturesPosition
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresRetrievePositionListEPL, http.MethodGet, kucoinFuturesPositionList, nil, &resp)
}
// SetAutoDepositMargin enable/disable of auto-deposit margin
func (ku *Kucoin) SetAutoDepositMargin(ctx context.Context, symbol string, status bool) (bool, error) {
params := make(map[string]interface{})
if symbol == "" {
return false, errors.New("symbol can't be empty")
}
params["symbol"] = symbol
params["status"] = status
var resp bool
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodPost, kucoinFuturesSetAutoDeposit, params, &resp)
}
// AddMargin is used to add margin manually
func (ku *Kucoin) AddMargin(ctx context.Context, symbol, uniqueID string, margin float64) (*FuturesPosition, error) {
params := make(map[string]interface{})
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params["symbol"] = symbol
if uniqueID == "" {
return nil, errors.New("uniqueID can't be empty")
}
params["bizNo"] = uniqueID
if margin <= 0 {
return nil, errors.New("margin can't be zero or negative")
}
params["margin"] = strconv.FormatFloat(margin, 'f', -1, 64)
var resp *FuturesPosition
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodPost, kucoinFuturesAddMargin, params, &resp)
}
// GetFuturesRiskLimitLevel gets information about risk limit level of a specific contract
func (ku *Kucoin) GetFuturesRiskLimitLevel(ctx context.Context, symbol string) ([]FuturesRiskLimitLevel, error) {
var resp []FuturesRiskLimitLevel
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, kucoinFuturesRiskLimitLevel+symbol, nil, &resp)
}
// FuturesUpdateRiskLmitLevel is used to adjustment the risk limit level
func (ku *Kucoin) FuturesUpdateRiskLmitLevel(ctx context.Context, symbol string, level int64) (bool, error) {
params := make(map[string]interface{})
if symbol == "" {
return false, errors.New("symbol can't be empty")
}
params["symbol"] = symbol
params["level"] = strconv.FormatInt(level, 10)
var resp bool
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodPost, kucoinFuturesUpdateRiskLmitLevel, params, &resp)
}
// GetFuturesFundingHistory gets information about funding history
func (ku *Kucoin) GetFuturesFundingHistory(ctx context.Context, symbol string, offset, maxCount int64, reverse, forward bool, startAt, endAt time.Time) (*FuturesFundingHistoryResponse, error) {
if symbol == "" {
return nil, errors.New("symbol can't be empty")
}
params := url.Values{}
params.Set("symbol", symbol)
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
params.Set("reverse", strconv.FormatBool(reverse))
params.Set("forward", strconv.FormatBool(forward))
if offset != 0 {
params.Set("offset", strconv.FormatInt(offset, 10))
}
if maxCount != 0 {
params.Set("maxCount", strconv.FormatInt(maxCount, 10))
}
var resp *FuturesFundingHistoryResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresRetrieveFundingHistoryEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesFundingHistory, params), nil, &resp)
}
// GetFuturesAccountOverview gets future account overview
func (ku *Kucoin) GetFuturesAccountOverview(ctx context.Context, currency string) (FuturesAccount, error) {
params := url.Values{}
if currency != "" {
params.Set("currency", currency)
}
resp := FuturesAccount{}
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresRetrieveAccountOverviewEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesAccountOverview, params), nil, &resp)
}
// GetFuturesTransactionHistory gets future transaction history
func (ku *Kucoin) GetFuturesTransactionHistory(ctx context.Context, currency, txType string, offset, maxCount int64, forward bool, startAt, endAt time.Time) (*FuturesTransactionHistoryResponse, error) {
params := url.Values{}
if currency != "" {
params.Set("currency", currency)
}
if txType != "" {
params.Set("type", txType)
}
if !startAt.IsZero() {
params.Set("startAt", strconv.FormatInt(startAt.UnixMilli(), 10))
}
if !endAt.IsZero() {
params.Set("endAt", strconv.FormatInt(endAt.UnixMilli(), 10))
}
params.Set("forward", strconv.FormatBool(forward))
if offset != 0 {
params.Set("offset", strconv.FormatInt(offset, 10))
}
if maxCount != 0 {
params.Set("maxCount", strconv.FormatInt(maxCount, 10))
}
var resp *FuturesTransactionHistoryResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresRetrieveTransactionHistoryEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesTransactionHistory, params), nil, &resp)
}
// CreateFuturesSubAccountAPIKey is used to create Futures APIs for sub-accounts
func (ku *Kucoin) CreateFuturesSubAccountAPIKey(ctx context.Context, ipWhitelist, passphrase, permission, remark, subName string) (*APIKeyDetail, error) {
params := make(map[string]interface{})
if ipWhitelist != "" {
params["ipWhitelist"] = ipWhitelist
}
if passphrase == "" {
return nil, errors.New("passphrase can't be empty")
}
params["passphrase"] = passphrase
if permission != "" {
params["permission"] = permission
}
if remark == "" {
return nil, errors.New("remark can't be empty")
}
params["remark"] = remark
if subName == "" {
return nil, errors.New("subName can't be empty")
}
params["subName"] = subName
var resp *APIKeyDetail
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodPost, kucoinFuturesSubAccountAPI, params, &resp)
}
// GetFuturesDepositAddress gets deposit address for currency
func (ku *Kucoin) GetFuturesDepositAddress(ctx context.Context, currency string) (*DepositAddress, error) {
if currency == "" {
return nil, errors.New("currency can't be empty")
}
params := url.Values{}
params.Set("currency", currency)
var resp *DepositAddress
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesDepositAddress, params), nil, &resp)
}
// GetFuturesDepositsList gets deposits list
func (ku *Kucoin) GetFuturesDepositsList(ctx context.Context, currency, status string, startAt, endAt time.Time) (*FuturesDepositDetailsResponse, error) {
params := url.Values{}
if currency != "" {
params.Set("currency", currency)
}
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 *FuturesDepositDetailsResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesDepositsList, params), nil, &resp)
}
// GetFuturesWithdrawalLimit gets withdrawal limits for currency
func (ku *Kucoin) GetFuturesWithdrawalLimit(ctx context.Context, currency string) (*FuturesWithdrawalLimit, error) {
if currency == "" {
return nil, errors.New("currency can't be empty")
}
params := url.Values{}
params.Set("currency", currency)
var resp *FuturesWithdrawalLimit
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesWithdrawalLimit, params), nil, &resp)
}
// GetFuturesWithdrawalList gets withdrawal list
func (ku *Kucoin) GetFuturesWithdrawalList(ctx context.Context, currency, status string, startAt, endAt time.Time) (*FuturesWithdrawalsListResponse, error) {
params := url.Values{}
if currency != "" {
params.Set("currency", currency)
}
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 *FuturesWithdrawalsListResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesWithdrawalList, params), nil, &resp)
}
// CancelFuturesWithdrawal is used to cancel withdrawal request of only PROCESSING status
func (ku *Kucoin) CancelFuturesWithdrawal(ctx context.Context, withdrawalID string) (bool, error) {
var resp bool
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodDelete, kucoinFuturesCancelWithdrawal+withdrawalID, nil, &resp)
}
// TransferFuturesFundsToMainAccount helps in transferring funds from futures to main/trade account
func (ku *Kucoin) TransferFuturesFundsToMainAccount(ctx context.Context, amount float64, currency, recAccountType string) (*TransferRes, error) {
params := make(map[string]interface{})
if amount <= 0 {
return nil, errors.New("amount can't be zero or negative")
}
params["amount"] = amount
if currency == "" {
return nil, errors.New("currency can't be empty")
}
params["currency"] = currency
if recAccountType == "" {
return nil, errors.New("recAccountType can't be empty")
}
params["recAccountType"] = recAccountType
var resp *TransferRes
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodPost, kucoinFuturesTransferFundtoMainAccount, params, &resp)
}
// TransferFundsToFuturesAccount helps in transferring funds from payee account to futures account
func (ku *Kucoin) TransferFundsToFuturesAccount(ctx context.Context, amount float64, currency, payAccountType string) error {
params := make(map[string]interface{})
if amount <= 0 {
return errors.New("amount can't be zero or negative")
}
params["amount"] = amount
if currency == "" {
return errors.New("currency can't be empty")
}
params["currency"] = currency
if payAccountType == "" {
return errors.New("payAccountType can't be empty")
}
params["payAccountType"] = payAccountType
resp := struct {
Error
}{}
return ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodPost, kucoinFuturesTransferFundtoFuturesAccount, params, &resp)
}
// GetFuturesTransferOutList gets list of transfer out
func (ku *Kucoin) GetFuturesTransferOutList(ctx context.Context, currency, status string, startAt, endAt time.Time) (*TransferListsResponse, error) {
if currency == "" {
return nil, errors.New("currency can't be empty")
}
params := url.Values{}
params.Set("currency", currency)
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 *TransferListsResponse
return resp, ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodGet, common.EncodeURLValues(kucoinFuturesTransferOutList, params), nil, &resp)
}
// CancelFuturesTransferOut is used to cancel transfer out request of only PROCESSING status
func (ku *Kucoin) CancelFuturesTransferOut(ctx context.Context, applyID string) error {
if applyID == "" {
return errors.New("applyID can't be empty")
}
params := url.Values{}
params.Set("applyId", applyID)
resp := struct {
Error
}{}
return ku.SendAuthHTTPRequest(ctx, exchange.RestFutures, defaultFuturesEPL, http.MethodDelete, common.EncodeURLValues(kucoinFuturesCancelTransferOut, params), nil, &resp)
}
func processFuturesOB(ob [][2]float64) []orderbook.Item {
o := make([]orderbook.Item, len(ob))
for x := range ob {
o[x] = orderbook.Item{
Price: ob[x][0],
Amount: ob[x][1],
}
}
return o
}
func constructFuturesOrderbook(o *futuresOrderbookResponse) (*Orderbook, error) {
var (
s Orderbook
err error
)
s.Bids = processFuturesOB(o.Bids)
if err != nil {
return nil, err
}
s.Asks = processFuturesOB(o.Asks)
if err != nil {
return nil, err
}
s.Sequence = o.Sequence
s.Time = o.Time.Time()
return &s, err
}

View File

@@ -0,0 +1,451 @@
package kucoin
import (
"time"
"github.com/thrasher-corp/gocryptotrader/common/convert"
)
var (
validGranularity = []string{
"1", "5", "15", "30", "60", "120", "240", "480", "720", "1440", "10080",
}
)
// Contract store contract details
type Contract struct {
Symbol string `json:"symbol"`
RootSymbol string `json:"rootSymbol"`
ContractType string `json:"type"`
FirstOpenDate convert.ExchangeTime `json:"firstOpenDate"`
ExpireDate convert.ExchangeTime `json:"expireDate"`
SettleDate convert.ExchangeTime `json:"settleDate"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
SettleCurrency string `json:"settleCurrency"`
MaxOrderQty float64 `json:"maxOrderQty"`
MaxPrice float64 `json:"maxPrice"`
LotSize float64 `json:"lotSize"`
TickSize float64 `json:"tickSize"`
IndexPriceTickSize float64 `json:"indexPriceTickSize"`
Multiplier float64 `json:"multiplier"`
InitialMargin float64 `json:"initialMargin"`
MaintainMargin float64 `json:"maintainMargin"`
MaxRiskLimit float64 `json:"maxRiskLimit"`
MinRiskLimit float64 `json:"minRiskLimit"`
RiskStep float64 `json:"riskStep"`
MakerFeeRate float64 `json:"makerFeeRate"`
TakerFeeRate float64 `json:"takerFeeRate"`
TakerFixFee float64 `json:"takerFixFee"`
MakerFixFee float64 `json:"makerFixFee"`
SettlementFee float64 `json:"settlementFee"`
IsDeleverage bool `json:"isDeleverage"`
IsQuanto bool `json:"isQuanto"`
IsInverse bool `json:"isInverse"`
MarkMethod string `json:"markMethod"`
FairMethod string `json:"fairMethod"`
FundingBaseSymbol string `json:"fundingBaseSymbol"`
FundingQuoteSymbol string `json:"fundingQuoteSymbol"`
FundingRateSymbol string `json:"fundingRateSymbol"`
IndexSymbol string `json:"indexSymbol"`
SettlementSymbol string `json:"settlementSymbol"`
Status string `json:"status"`
FundingFeeRate float64 `json:"fundingFeeRate"`
PredictedFundingFeeRate float64 `json:"predictedFundingFeeRate"`
OpenInterest string `json:"openInterest"`
TurnoverOf24h float64 `json:"turnoverOf24h"`
VolumeOf24h float64 `json:"volumeOf24h"`
MarkPrice float64 `json:"markPrice"`
IndexPrice float64 `json:"indexPrice"`
LastTradePrice float64 `json:"lastTradePrice"`
NextFundingRateTime float64 `json:"nextFundingRateTime"`
MaxLeverage float64 `json:"maxLeverage"`
SourceExchanges []string `json:"sourceExchanges"`
PremiumsSymbol1M string `json:"premiumsSymbol1M"`
PremiumsSymbol8H string `json:"premiumsSymbol8H"`
FundingBaseSymbol1M string `json:"fundingBaseSymbol1M"`
FundingQuoteSymbol1M string `json:"fundingQuoteSymbol1M"`
LowPrice float64 `json:"lowPrice"`
HighPrice float64 `json:"highPrice"`
PriceChgPct float64 `json:"priceChgPct"`
PriceChg float64 `json:"priceChg"`
}
// FuturesTicker stores ticker data
type FuturesTicker struct {
Sequence int64 `json:"sequence"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Size float64 `json:"size"`
Price float64 `json:"price"`
BestBidSize float64 `json:"bestBidSize"`
BestBidPrice float64 `json:"bestBidPrice"`
BestAskSize float64 `json:"bestAskSize"`
BestAskPrice float64 `json:"bestAskPrice"`
TradeID string `json:"tradeId"`
FilledTime convert.ExchangeTime `json:"time"`
}
type futuresOrderbookResponse struct {
Asks [][2]float64 `json:"asks"`
Bids [][2]float64 `json:"bids"`
Time convert.ExchangeTime `json:"ts"`
Sequence int64 `json:"sequence"`
Symbol string `json:"symbol"`
}
// FuturesTrade stores trade data
type FuturesTrade struct {
Sequence int64 `json:"sequence"`
TradeID string `json:"tradeId"`
TakerOrderID string `json:"takerOrderId"`
MakerOrderID string `json:"makerOrderId"`
Price float64 `json:"price,string"`
Size float64 `json:"size"`
Side string `json:"side"`
FilledTime convert.ExchangeTime `json:"ts"`
}
// FuturesInterestRate stores interest rate data
type FuturesInterestRate struct {
Symbol string `json:"symbol"`
TimePoint convert.ExchangeTime `json:"timePoint"`
Value float64 `json:"value"`
Granularity int64 `json:"granularity"`
}
// Decomposition stores decomposition data
type Decomposition struct {
Exchange string `json:"exchange"`
Price float64 `json:"price"`
Weight float64 `json:"weight"`
}
// FuturesIndex stores index data
type FuturesIndex struct {
FuturesInterestRate
DecompositionList []Decomposition `json:"decompositionList"`
}
// FuturesMarkPrice stores mark price data
type FuturesMarkPrice struct {
FuturesInterestRate
IndexPrice float64 `json:"indexPrice"`
}
// FuturesFundingRate stores funding rate data
type FuturesFundingRate struct {
FuturesInterestRate
PredictedValue float64 `json:"predictedValue"`
}
// FuturesKline stores kline data
type FuturesKline struct {
StartTime time.Time
Open float64
Close float64
High float64
Low float64
Volume float64
}
// FutureOrdersResponse represents a future order response list detail.
type FutureOrdersResponse struct {
CurrentPage int64 `json:"currentPage"`
PageSize int64 `json:"pageSize"`
TotalNum int64 `json:"totalNum"`
TotalPage int64 `json:"totalPage"`
Items []FuturesOrder `json:"items"`
}
// FuturesOrder represents futures order information
type FuturesOrder struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
OrderType string `json:"type"`
Side string `json:"side"`
Price float64 `json:"price,string"`
Size float64 `json:"size"`
Value float64 `json:"value,string"`
DealValue float64 `json:"dealValue,string"`
DealSize float64 `json:"dealSize"`
Stp string `json:"stp"`
Stop string `json:"stop"`
StopPriceType string `json:"stopPriceType"`
StopTriggered bool `json:"stopTriggered"`
StopPrice float64 `json:"stopPrice,string"`
TimeInForce string `json:"timeInForce"`
PostOnly bool `json:"postOnly"`
Hidden bool `json:"hidden"`
Iceberg bool `json:"iceberg"`
Leverage float64 `json:"leverage,string"`
ForceHold bool `json:"forceHold"`
CloseOrder bool `json:"closeOrder"`
VisibleSize float64 `json:"visibleSize"`
ClientOid string `json:"clientOid"`
Remark string `json:"remark"`
Tags string `json:"tags"`
IsActive bool `json:"isActive"`
CancelExist bool `json:"cancelExist"`
CreatedAt convert.ExchangeTime `json:"createdAt"`
UpdatedAt convert.ExchangeTime `json:"updatedAt"`
EndAt convert.ExchangeTime `json:"endAt"`
OrderTime convert.ExchangeTime `json:"orderTime"`
SettleCurrency string `json:"settleCurrency"`
Status string `json:"status"`
FilledValue float64 `json:"filledValue,string"`
FilledSize float64 `json:"filledSize"`
ReduceOnly bool `json:"reduceOnly"`
}
// FutureFillsResponse represents a future fills list response detail.
type FutureFillsResponse struct {
CurrentPage int64 `json:"currentPage"`
PageSize int64 `json:"pageSize"`
TotalNum int64 `json:"totalNum"`
TotalPage int64 `json:"totalPage"`
Items []FuturesFill `json:"items"`
}
// FuturesFill represents list of recent fills for futures orders.
type FuturesFill struct {
Symbol string `json:"symbol"`
TradeID string `json:"tradeId"`
OrderID string `json:"orderId"`
Side string `json:"side"`
Liquidity string `json:"liquidity"`
ForceTaker bool `json:"forceTaker"`
Price float64 `json:"price,string"`
Size float64 `json:"size,string"`
Value float64 `json:"value,string"`
FeeRate float64 `json:"feeRate,string"`
FixFee float64 `json:"fixFee,string"`
FeeCurrency string `json:"feeCurrency"`
Stop string `json:"stop"`
Fee float64 `json:"fee,string"`
OrderType string `json:"orderType"`
TradeType string `json:"tradeType"`
CreatedAt convert.ExchangeTime `json:"createdAt"`
SettleCurrency string `json:"settleCurrency"`
TradeTime convert.ExchangeTime `json:"tradeTime"`
}
// FuturesOpenOrderStats represents futures open order summary stats information.
type FuturesOpenOrderStats struct {
OpenOrderBuySize int64 `json:"openOrderBuySize"`
OpenOrderSellSize int64 `json:"openOrderSellSize"`
OpenOrderBuyCost float64 `json:"openOrderBuyCost,string"`
OpenOrderSellCost float64 `json:"openOrderSellCost,string"`
SettleCurrency string `json:"settleCurrency"`
}
// FuturesPosition represents futures position detailed information.
type FuturesPosition struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
AutoDeposit bool `json:"autoDeposit"`
MaintMarginReq float64 `json:"maintMarginReq"`
RiskLimit int64 `json:"riskLimit"`
RealLeverage float64 `json:"realLeverage"`
CrossMode bool `json:"crossMode"`
ADLRankingPercentile float64 `json:"delevPercentage"`
OpeningTimestamp convert.ExchangeTime `json:"openingTimestamp"`
CurrentTimestamp convert.ExchangeTime `json:"currentTimestamp"`
CurrentQty int64 `json:"currentQty"`
CurrentCost float64 `json:"currentCost"` // Current position value
CurrentComm float64 `json:"currentComm"` // Current commission
UnrealisedCost float64 `json:"unrealisedCost"`
RealisedGrossCost float64 `json:"realisedGrossCost"`
RealisedCost float64 `json:"realisedCost"`
IsOpen bool `json:"isOpen"`
MarkPrice float64 `json:"markPrice"`
MarkValue float64 `json:"markValue"`
PosCost float64 `json:"posCost"` // Position value
PosCross float64 `json:"posCross"` // Added margin
PosInit float64 `json:"posInit"` // Leverage margin
PosComm float64 `json:"posComm"` // Bankruptcy cost
PosLoss float64 `json:"posLoss"` // Funding fees paid out
PosMargin float64 `json:"posMargin"` // Position margin
PosMaint float64 `json:"posMaint"` // Maintenance margin
MaintMargin float64 `json:"maintMargin"`
RealisedGrossPnl float64 `json:"realisedGrossPnl"`
RealisedPnl float64 `json:"realisedPnl"`
UnrealisedPnl float64 `json:"unrealisedPnl"`
UnrealisedPnlPcnt float64 `json:"unrealisedPnlPcnt"`
UnrealisedRoePcnt float64 `json:"unrealisedRoePcnt"`
AvgEntryPrice float64 `json:"avgEntryPrice"`
LiquidationPrice float64 `json:"liquidationPrice"`
BankruptPrice float64 `json:"bankruptPrice"`
SettleCurrency string `json:"settleCurrency"`
MaintainMargin float64 `json:"maintainMargin"`
RiskLimitLevel int64 `json:"riskLimitLevel"`
}
// FuturesRiskLimitLevel represents futures risk limit level information.
type FuturesRiskLimitLevel struct {
Symbol string `json:"symbol"`
Level int64 `json:"level"`
MaxRiskLimit float64 `json:"maxRiskLimit"`
MinRiskLimit float64 `json:"minRiskLimit"`
MaxLeverage float64 `json:"maxLeverage"`
InitialMargin float64 `json:"initialMargin"`
MaintainMargin float64 `json:"maintainMargin"`
}
// FuturesFundingHistory represents futures funding information.
type FuturesFundingHistory struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
Time convert.ExchangeTime `json:"timePoint"`
FundingRate float64 `json:"fundingRate"`
MarkPrice float64 `json:"markPrice"`
PositionQty float64 `json:"positionQty"`
PositionCost float64 `json:"positionCost"`
Funding float64 `json:"funding"`
SettleCurrency string `json:"settleCurrency"`
}
// FuturesAccount holds futures account detail information.
type FuturesAccount struct {
AccountEquity float64 `json:"accountEquity"` // marginBalance + Unrealised PNL
UnrealisedPNL float64 `json:"unrealisedPNL"` // unrealised profit and loss
MarginBalance float64 `json:"marginBalance"` // positionMargin + orderMargin + frozenFunds + availableBalance - unrealisedPNL
PositionMargin float64 `json:"positionMargin"`
OrderMargin float64 `json:"orderMargin"`
FrozenFunds float64 `json:"frozenFunds"` // frozen funds for withdrawal and out-transfer
AvailableBalance float64 `json:"availableBalance"`
Currency string `json:"currency"`
}
// FuturesTransactionHistory represents a transaction history
type FuturesTransactionHistory struct {
Time convert.ExchangeTime `json:"time"`
Type string `json:"type"`
Amount float64 `json:"amount"`
Fee float64 `json:"fee"`
AccountEquity float64 `json:"accountEquity"`
Status string `json:"status"`
Remark string `json:"remark"`
Offset int64 `json:"offset"`
Currency string `json:"currency"`
}
// APIKeyDetail represents the API key detail
type APIKeyDetail struct {
SubName string `json:"subName"`
Remark string `json:"remark"`
APIKey string `json:"apiKey"`
APISecret string `json:"apiSecret"`
Passphrase string `json:"passphrase"`
Permission string `json:"permission"`
IPWhitelist string `json:"ipWhitelist"`
CreateAt convert.ExchangeTime `json:"createdAt"`
}
// FuturesDepositDetailsResponse represents a futures deposits list detail response.
type FuturesDepositDetailsResponse struct {
CurrentPage int64 `json:"currentPage"`
PageSize int64 `json:"pageSize"`
TotalNum int64 `json:"totalNum"`
TotalPage int64 `json:"totalPage"`
Items []FuturesDepositDetail `json:"items"`
}
// FuturesDepositDetail represents futures deposit detail information.
type FuturesDepositDetail struct {
Currency string `json:"currency"`
Status string `json:"status"`
Address string `json:"address"`
IsInner bool `json:"isInner"`
Amount float64 `json:"amount"`
Fee float64 `json:"fee"`
WalletTxID string `json:"walletTxId"`
CreatedAt convert.ExchangeTime `json:"createdAt"`
}
// FuturesWithdrawalLimit represents withdrawal limit information.
type FuturesWithdrawalLimit struct {
Currency string `json:"currency"`
ChainID string `json:"chainId"`
LimitAmount float64 `json:"limitAmount"`
UsedAmount float64 `json:"usedAmount"`
RemainAmount float64 `json:"remainAmount"`
AvailableAmount float64 `json:"availableAmount"`
WithdrawMinFee float64 `json:"withdrawMinFee"`
InnerWithdrawMinFee float64 `json:"innerWithdrawMinFee"`
WithdrawMinSize float64 `json:"withdrawMinSize"`
IsWithdrawEnabled bool `json:"isWithdrawEnabled"`
Precision float64 `json:"precision"`
}
// FuturesWithdrawalsListResponse represents a list of futures Withdrawal history instance.
type FuturesWithdrawalsListResponse struct {
CurrentPage int64 `json:"currentPage"`
PageSize int64 `json:"pageSize"`
TotalNum int64 `json:"totalNum"`
TotalPage int64 `json:"totalPage"`
Items []FuturesWithdrawalHistory `json:"items"`
}
// FuturesWithdrawalHistory represents a list of Futures withdrawal history.
type FuturesWithdrawalHistory struct {
WithdrawalID string `json:"withdrawalId"`
Currency string `json:"currency"`
Status string `json:"status"`
Address string `json:"address"`
Memo string `json:"memo"`
IsInner bool `json:"isInner"`
Amount float64 `json:"amount"`
Fee float64 `json:"fee"`
WalletTxID string `json:"walletTxId"`
CreatedAt convert.ExchangeTime `json:"createdAt"`
Remark string `json:"remark"`
Reason string `json:"reason"`
}
// TransferBase represents transfer base information.
type TransferBase struct {
ApplyID string `json:"applyId"`
Currency string `json:"currency"`
RecRemark string `json:"recRemark"`
RecSystem string `json:"recSystem"`
Status string `json:"status"`
Amount float64 `json:"amount,string"`
Reason string `json:"reason"`
CreatedAt convert.ExchangeTime `json:"createdAt"`
Remark string `json:"remark"`
}
// TransferRes represents a transfer response
type TransferRes struct {
TransferBase
BizNo string `json:"bizNo"`
PayAccountType string `json:"payAccountType"`
PayTag string `json:"payTag"`
RecAccountType string `json:"recAccountType"`
RecTag string `json:"recTag"`
Fee float64 `json:"fee,string"`
Serial int64 `json:"sn"`
UpdatedAt convert.ExchangeTime `json:"updatedAt"`
}
// TransferListsResponse represents a transfer lists detail.
type TransferListsResponse struct {
CurrentPage int64 `json:"currentPage"`
PageSize int64 `json:"pageSize"`
TotalNum int64 `json:"totalNum"`
TotalPage int64 `json:"totalPage"`
Items []Transfer `json:"items"`
}
// Transfer represents a transfer detail.
type Transfer struct {
TransferBase
Offset int64 `json:"offset"`
}
// FuturesServiceStatus represents service status.
type FuturesServiceStatus struct {
Status string `json:"status"`
Message string `json:"msg"`
}

View File

@@ -0,0 +1,244 @@
package kucoin
import (
"context"
"errors"
"fmt"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"golang.org/x/time/rate"
)
const (
threeSecondsInterval = time.Second * 3
oneMinuteInterval = time.Minute
)
// RateLimit implements the request.Limiter interface
type RateLimit struct {
RetrieveAccountLedger *rate.Limiter
MasterSubUserTransfer *rate.Limiter
RetrieveDepositList *rate.Limiter
RetrieveV1HistoricalDepositList *rate.Limiter
RetrieveWithdrawalList *rate.Limiter
RetrieveV1HistoricalWithdrawalList *rate.Limiter
PlaceOrder *rate.Limiter
PlaceMarginOrders *rate.Limiter
PlaceBulkOrders *rate.Limiter
CancelOrder *rate.Limiter
CancelAllOrders *rate.Limiter
ListOrders *rate.Limiter
ListFills *rate.Limiter
RetrieveFullOrderbook *rate.Limiter
RetrieveMarginAccount *rate.Limiter
SpotRate *rate.Limiter
FuturesRate *rate.Limiter
FRetrieveAccountOverviewRate *rate.Limiter
FRetrieveTransactionHistoryRate *rate.Limiter
FPlaceOrderRate *rate.Limiter
FCancelAnOrderRate *rate.Limiter
FLimitOrderMassCancelationRate *rate.Limiter
FRetrieveOrderListRate *rate.Limiter
FRetrieveFillsRate *rate.Limiter
FRecentFillsRate *rate.Limiter
FRetrievePositionListRate *rate.Limiter
FRetrieveFundingHistoryRate *rate.Limiter
FRetrieveFullOrderbookLevel2Rate *rate.Limiter
}
// rate of request per interval
const (
retrieveAccountLedgerRate = 18
masterSubUserTransferRate = 3
retrieveDepositListRate = 6
retrieveV1HistoricalDepositListRate = 6
retrieveWithdrawalListRate = 6
retrieveV1HistoricalWithdrawalListRate = 6
placeOrderRate = 45
placeMarginOrdersRate = 45
placeBulkOrdersRate = 3
cancelOrderRate = 60
cancelAllOrdersRate = 3
listOrdersRate = 30
listFillsRate = 9
retrieveFullOrderbookRate = 30
retrieveMarginAccountRate = 1
futuresRetrieveAccountOverviewRate = 30
futuresRetrieveTransactionHistoryRate = 9
futuresPlaceOrderRate = 30
futuresCancelAnOrderRate = 40
futuresLimitOrderMassCancelationRate = 9
futuresRetrieveOrderListRate = 30
futuresRetrieveFillsRate = 9
futuresRecentFillsRate = 9
futuresRetrievePositionListRate = 9
futuresRetrieveFundingHistoryRate = 9
futuresRetrieveFullOrderbookLevel2Rate = 30
defaultSpotRate = 1200
defaultFuturesRate = 1200
)
const (
// for spot endpoints
retrieveAccountLedgerEPL request.EndpointLimit = iota
masterSubUserTransferEPL
retrieveDepositListEPL
retrieveV1HistoricalDepositListEPL
retrieveWithdrawalListEPL
retrieveV1HistoricalWithdrawalListEPL
placeOrderEPL
placeMarginOrdersEPL
placeBulkOrdersEPL
cancelOrderEPL
cancelAllOrdersEPL
listOrdersEPL
listFillsEPL
retrieveFullOrderbookEPL
retrieveMarginAccountEPL
defaultSpotEPL
// for futures endpoints
futuresRetrieveAccountOverviewEPL
futuresRetrieveTransactionHistoryEPL
futuresPlaceOrderEPL
futuresCancelAnOrderEPL
futuresLimitOrderMassCancelationEPL
futuresRetrieveOrderListEPL
futuresRetrieveFillsEPL
futuresRecentFillsEPL
futuresRetrievePositionListEPL
futuresRetrieveFundingHistoryEPL
futuresRetrieveFullOrderbookLevel2EPL
defaultFuturesEPL
)
// Limit executes rate limiting functionality for Kucoin
func (r *RateLimit) Limit(ctx context.Context, epl request.EndpointLimit) error {
var limiter *rate.Limiter
var tokens int
switch epl {
case retrieveAccountLedgerEPL:
return r.RetrieveAccountLedger.Wait(ctx)
case masterSubUserTransferEPL:
return r.MasterSubUserTransfer.Wait(ctx)
case retrieveDepositListEPL:
return r.RetrieveDepositList.Wait(ctx)
case retrieveV1HistoricalDepositListEPL:
return r.RetrieveV1HistoricalDepositList.Wait(ctx)
case retrieveWithdrawalListEPL:
return r.RetrieveWithdrawalList.Wait(ctx)
case retrieveV1HistoricalWithdrawalListEPL:
return r.RetrieveV1HistoricalWithdrawalList.Wait(ctx)
case placeOrderEPL:
return r.PlaceOrder.Wait(ctx)
case placeMarginOrdersEPL:
return r.PlaceMarginOrders.Wait(ctx)
case placeBulkOrdersEPL:
return r.PlaceBulkOrders.Wait(ctx)
case cancelOrderEPL:
return r.CancelOrder.Wait(ctx)
case cancelAllOrdersEPL:
return r.CancelAllOrders.Wait(ctx)
case listOrdersEPL:
return r.ListOrders.Wait(ctx)
case listFillsEPL:
return r.ListFills.Wait(ctx)
case retrieveFullOrderbookEPL:
return r.RetrieveFullOrderbook.Wait(ctx)
case retrieveMarginAccountEPL:
return r.RetrieveMarginAccount.Wait(ctx)
case futuresRetrieveAccountOverviewEPL:
return r.FRetrieveAccountOverviewRate.Wait(ctx)
case futuresRetrieveTransactionHistoryEPL:
return r.FRetrieveTransactionHistoryRate.Wait(ctx)
case futuresPlaceOrderEPL:
return r.FPlaceOrderRate.Wait(ctx)
case futuresCancelAnOrderEPL:
return r.FCancelAnOrderRate.Wait(ctx)
case futuresLimitOrderMassCancelationEPL:
return r.FLimitOrderMassCancelationRate.Wait(ctx)
case futuresRetrieveOrderListEPL:
return r.FRetrieveOrderListRate.Wait(ctx)
case futuresRetrieveFillsEPL:
return r.FRetrieveFillsRate.Wait(ctx)
case futuresRecentFillsEPL:
return r.FRecentFillsRate.Wait(ctx)
case futuresRetrievePositionListEPL:
return r.FRetrievePositionListRate.Wait(ctx)
case futuresRetrieveFundingHistoryEPL:
return r.FRetrieveFundingHistoryRate.Wait(ctx)
case futuresRetrieveFullOrderbookLevel2EPL:
return r.FRetrieveFullOrderbookLevel2Rate.Wait(ctx)
case defaultSpotEPL:
limiter, tokens = r.SpotRate, 1
case defaultFuturesEPL:
limiter, tokens = r.FuturesRate, 1
default:
return errors.New("endpoint rate limit functionality not found")
}
var finalDelay time.Duration
var reserves = make([]*rate.Reservation, tokens)
for i := 0; i < tokens; i++ {
// Consume tokens 1 at a time as this avoids needing burst capacity in the limiter,
// which would otherwise allow the rate limit to be exceeded over short periods
reserves[i] = limiter.Reserve()
finalDelay = reserves[i].Delay()
}
if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) {
// Cancel all potential reservations to free up rate limiter if deadline
// is exceeded.
for x := range reserves {
reserves[x].Cancel()
}
return fmt.Errorf("rate limit delay of %s will exceed deadline: %w",
finalDelay,
context.DeadlineExceeded)
}
time.Sleep(finalDelay)
return nil
}
// SetRateLimit returns a RateLimit instance, which implements the request.Limiter interface.
func SetRateLimit() *RateLimit {
return &RateLimit{
// spot specific rate limiters
RetrieveAccountLedger: request.NewRateLimit(threeSecondsInterval, retrieveAccountLedgerRate),
MasterSubUserTransfer: request.NewRateLimit(threeSecondsInterval, masterSubUserTransferRate),
RetrieveDepositList: request.NewRateLimit(threeSecondsInterval, retrieveDepositListRate),
RetrieveV1HistoricalDepositList: request.NewRateLimit(threeSecondsInterval, retrieveV1HistoricalDepositListRate),
RetrieveWithdrawalList: request.NewRateLimit(threeSecondsInterval, retrieveWithdrawalListRate),
RetrieveV1HistoricalWithdrawalList: request.NewRateLimit(threeSecondsInterval, retrieveV1HistoricalWithdrawalListRate),
PlaceOrder: request.NewRateLimit(threeSecondsInterval, placeOrderRate),
PlaceMarginOrders: request.NewRateLimit(threeSecondsInterval, placeMarginOrdersRate),
PlaceBulkOrders: request.NewRateLimit(threeSecondsInterval, placeBulkOrdersRate),
CancelOrder: request.NewRateLimit(threeSecondsInterval, cancelOrderRate),
CancelAllOrders: request.NewRateLimit(threeSecondsInterval, cancelAllOrdersRate),
ListOrders: request.NewRateLimit(threeSecondsInterval, listOrdersRate),
ListFills: request.NewRateLimit(threeSecondsInterval, listFillsRate),
RetrieveFullOrderbook: request.NewRateLimit(threeSecondsInterval, retrieveFullOrderbookRate),
RetrieveMarginAccount: request.NewRateLimit(threeSecondsInterval, retrieveMarginAccountRate),
// default spot and futures rates
SpotRate: request.NewRateLimit(oneMinuteInterval, defaultSpotRate),
FuturesRate: request.NewRateLimit(oneMinuteInterval, defaultFuturesRate),
// futures specific rate limiters
FRetrieveAccountOverviewRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveAccountOverviewRate),
FRetrieveTransactionHistoryRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveTransactionHistoryRate),
FPlaceOrderRate: request.NewRateLimit(threeSecondsInterval, futuresPlaceOrderRate),
FCancelAnOrderRate: request.NewRateLimit(threeSecondsInterval, futuresCancelAnOrderRate),
FLimitOrderMassCancelationRate: request.NewRateLimit(threeSecondsInterval, futuresLimitOrderMassCancelationRate),
FRetrieveOrderListRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveOrderListRate),
FRetrieveFillsRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveFillsRate),
FRecentFillsRate: request.NewRateLimit(threeSecondsInterval, futuresRecentFillsRate),
FRetrievePositionListRate: request.NewRateLimit(threeSecondsInterval, futuresRetrievePositionListRate),
FRetrieveFundingHistoryRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveFundingHistoryRate),
FRetrieveFullOrderbookLevel2Rate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveFullOrderbookLevel2Rate),
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,11 @@ type Submit struct {
TriggerPrice float64
ClientID string // TODO: Shift to credentials
ClientOrderID string
// The system will first borrow you funds at the optimal interest rate and then place an order for you.
// see kucoin_wrapper.go
AutoBorrow bool
// MarginType such as isolated or cross margin for when an exchange
// supports margin type definition when submitting an order eg okx
MarginType margin.Type
@@ -77,6 +82,9 @@ type Submit struct {
// RetrieveFeeDelay some exchanges take time to properly save order data
// and cannot retrieve fees data immediately
RetrieveFeeDelay time.Duration
// Hidden when enabled orders not displaying in order book.
Hidden bool
// TradeMode specifies the trading mode for margin and non-margin orders: see okcoin_wrapper.go
TradeMode string
}
@@ -109,6 +117,9 @@ type SubmitResponse struct {
Fee float64
FeeAsset currency.Code
Cost float64
BorrowSize float64
LoanApplyID string
MarginType margin.Type
}

View File

@@ -34,6 +34,7 @@ var Exchanges = []string{
"huobi",
"itbit",
"kraken",
"kucoin",
"lbank",
"okcoin",
"okx",

View File

@@ -80,6 +80,7 @@ _b in this context is an `IBotExchange` implemented struct_
| Huobi.Pro | Yes | Yes | No |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | No |
| Kucoin | Yes | No | Yes |
| Lbank | Yes | No | Yes |
| Okcoin | Yes | Yes | Yes |
| Okx | Yes | Yes | Yes |

File diff suppressed because one or more lines are too long

View File

@@ -17,6 +17,7 @@ hitbtc,
huobi,
itbit,
kraken,
kucoin,
lbank,
okcoin,
okx,
1 binanceus
17 huobi
18 itbit
19 kraken
20 kucoin
21 lbank
22 okcoin
23 okx