├── .gitignore
├── go.mod
├── signer_test.go
├── time.go
├── helper.go
├── service_status_test.go
├── service_status.go
├── time_test.go
├── helper_test.go
├── pagination.go
├── LICENSE
├── fill_test.go
├── fill.go
├── go.sum
├── hf_account_test.go
├── signer.go
├── hf_account.go
├── currency_test.go
├── websocket_test.go
├── deposit_test.go
├── deposit.go
├── withdrawal_test.go
├── withdrawal.go
├── examples
└── main.go
├── currency.go
├── earn_test.go
├── api.go
├── http.go
├── earn.go
├── symbol_test.go
├── symbol.go
├── websocket.go
├── hf_order_types.go
├── order_test.go
├── account_test.go
├── hf_order.go
├── README.md
├── order.go
└── hf_order_test.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor
3 | .github
4 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Kucoin/kucoin-go-sdk
2 |
3 | require (
4 | github.com/google/go-querystring v1.1.0 // indirect
5 | github.com/gorilla/websocket v1.4.2
6 | github.com/pkg/errors v0.8.1
7 | github.com/sirupsen/logrus v1.4.1
8 | )
9 |
10 | go 1.13
11 |
--------------------------------------------------------------------------------
/signer_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import "testing"
4 |
5 | func TestKcSigner_Sign(t *testing.T) {
6 | s := NewKcSigner("abc", "efg", "kcs")
7 | b := []byte("GET/api/v1/orders")
8 | if string(s.Sign(b)) != "iOdkcc7K6cyY8Cdr3yMcTgXCof4vhHCaDyrDSG7Qf3w=" {
9 | t.Error("Invalid sign")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/time.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | type ServerTimeModel int64
9 |
10 | // ServerTime returns the API server time.
11 | func (as *ApiService) ServerTime(ctx context.Context) (*ApiResponse, error) {
12 | req := NewRequest(http.MethodGet, "/api/v1/timestamp", nil)
13 | return as.Call(ctx, req)
14 | }
15 |
--------------------------------------------------------------------------------
/helper.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "encoding/json"
5 | "strconv"
6 | )
7 |
8 | // IntToString converts int64 to string.
9 | func IntToString(i int64) string {
10 | return strconv.FormatInt(i, 10)
11 | }
12 |
13 | // ToJsonString converts any value to JSON string.
14 | func ToJsonString(v interface{}) string {
15 | b, err := json.Marshal(v)
16 | if err != nil {
17 | return ""
18 | }
19 | return string(b)
20 | }
21 |
--------------------------------------------------------------------------------
/service_status_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestApiService_ServiceStatus(t *testing.T) {
9 | s := NewApiServiceFromEnv()
10 |
11 | rsp, err := s.ServiceStatus(context.Background())
12 | if err != nil {
13 | t.Fatal(err)
14 | }
15 | var ss ServiceStatusModel
16 | if err := rsp.ReadData(&ss); err != nil {
17 | t.Fatal(err)
18 | }
19 | if ss.Status == "" {
20 | t.Fatal("empty status")
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/service_status.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | // A ServiceStatusModel represents the structure of service status.
9 | type ServiceStatusModel struct {
10 | Status string `json:"status"`
11 | Msg string `json:"msg"`
12 | }
13 |
14 | // ServiceStatus returns the service status.
15 | func (as *ApiService) ServiceStatus(ctx context.Context) (*ApiResponse, error) {
16 | req := NewRequest(http.MethodGet, "/api/v1/status", nil)
17 | return as.Call(ctx, req)
18 | }
19 |
--------------------------------------------------------------------------------
/time_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "math"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestApiService_ServerTime(t *testing.T) {
11 | s := NewApiServiceFromEnv()
12 | rsp, err := s.ServerTime(context.Background())
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 | var ts ServerTimeModel
17 | if err := rsp.ReadData(&ts); err != nil {
18 | t.Fatal(err)
19 | }
20 | t.Log(ts)
21 | now := time.Now().UnixNano() / 1000 / 1000
22 | if math.Abs(float64(int64(ts)-now)) > 10000 {
23 | t.Error("Invalid timestamp")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/helper_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import "testing"
4 |
5 | func TestIntToString(t *testing.T) {
6 | var i int64 = 5200
7 | if IntToString(i) != "5200" {
8 | t.Error("Invalid string")
9 | }
10 | }
11 |
12 | func TestToJsonString(t *testing.T) {
13 | type test struct {
14 | M1 string `json:"m1"`
15 | M2 int64 `json:"m2"`
16 | M3 bool `json:"m3"`
17 | M4 map[string]string `json:"m4"`
18 | m5 chan string
19 | }
20 | var s = test{
21 | M1: "KuCoin",
22 | M2: 5200,
23 | M3: false,
24 | M4: map[string]string{"KCS": "$300"},
25 | m5: make(chan string, 10),
26 | }
27 | var a = `{"m1":"KuCoin","m2":5200,"m3":false,"m4":{"KCS":"$300"}}`
28 | if ToJsonString(s) != a {
29 | t.Error("Invalid string")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pagination.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import "encoding/json"
4 |
5 | // A PaginationParam represents the pagination parameters `currentPage` `pageSize` in a request .
6 | type PaginationParam struct {
7 | CurrentPage int64
8 | PageSize int64
9 | }
10 |
11 | // ReadParam read pagination parameters into params.
12 | func (p *PaginationParam) ReadParam(params map[string]string) {
13 | params["currentPage"], params["pageSize"] = IntToString(p.CurrentPage), IntToString(p.PageSize)
14 | }
15 |
16 | // A PaginationModel represents the pagination in a response.
17 | type PaginationModel struct {
18 | CurrentPage int64 `json:"currentPage"`
19 | PageSize int64 `json:"pageSize"`
20 | TotalNum int64 `json:"totalNum"`
21 | TotalPage int64 `json:"totalPage"`
22 | RawItems json.RawMessage `json:"items"` // delay parsing
23 | }
24 |
25 | // ReadItems read the `items` into v.
26 | func (p *PaginationModel) ReadItems(v interface{}) error {
27 | return json.Unmarshal(p.RawItems, v)
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 KuCoin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/fill_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestApiService_Fills(t *testing.T) {
9 | s := NewApiServiceFromEnv()
10 | p := &PaginationParam{CurrentPage: 1, PageSize: 10}
11 | rsp, err := s.Fills(context.Background(), map[string]string{}, p)
12 | if err != nil {
13 | t.Fatal(err)
14 | }
15 |
16 | fs := FillsModel{}
17 | if _, err := rsp.ReadPaginationData(&fs); err != nil {
18 | t.Fatal(err)
19 | }
20 | for _, f := range fs {
21 | t.Log(ToJsonString(f))
22 | switch {
23 | case f.Symbol == "":
24 | t.Error("Empty key 'symbol'")
25 | case f.TradeId == "":
26 | t.Error("Empty key 'tradeId'")
27 | case f.OrderId == "":
28 | t.Error("Empty key 'orderId'")
29 | case f.Type == "":
30 | t.Error("Empty key 'type'")
31 | case f.Side == "":
32 | t.Error("Empty key 'side'")
33 | }
34 | }
35 | }
36 |
37 | func TestApiService_RecentFills(t *testing.T) {
38 | s := NewApiServiceFromEnv()
39 | rsp, err := s.RecentFills(context.Background())
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 |
44 | fs := FillsModel{}
45 | if err := rsp.ReadData(&fs); err != nil {
46 | t.Fatal(err)
47 | }
48 | for _, f := range fs {
49 | t.Log(ToJsonString(f))
50 | switch {
51 | case f.Symbol == "":
52 | t.Error("Empty key 'symbol'")
53 | case f.TradeId == "":
54 | t.Error("Empty key 'tradeId'")
55 | case f.OrderId == "":
56 | t.Error("Empty key 'orderId'")
57 | case f.Type == "":
58 | t.Error("Empty key 'type'")
59 | case f.Side == "":
60 | t.Error("Empty key 'side'")
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/fill.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | // A FillModel represents the structure of fill.
9 | type FillModel struct {
10 | Symbol string `json:"symbol"`
11 | TradeId string `json:"tradeId"`
12 | OrderId string `json:"orderId"`
13 | CounterOrderId string `json:"counterOrderId"`
14 | Side string `json:"side"`
15 | Liquidity string `json:"liquidity"`
16 | ForceTaker bool `json:"forceTaker"`
17 | Price string `json:"price"`
18 | Size string `json:"size"`
19 | Funds string `json:"funds"`
20 | Fee string `json:"fee"`
21 | FeeRate string `json:"feeRate"`
22 | FeeCurrency string `json:"feeCurrency"`
23 | Stop string `json:"stop"`
24 | Type string `json:"type"`
25 | CreatedAt int64 `json:"createdAt"`
26 | TradeType string `json:"tradeType"`
27 | }
28 |
29 | // A FillsModel is the set of *FillModel.
30 | type FillsModel []*FillModel
31 |
32 | // Fills returns a list of recent fills.
33 | func (as *ApiService) Fills(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
34 | pagination.ReadParam(params)
35 | req := NewRequest(http.MethodGet, "/api/v1/fills", params)
36 | return as.Call(ctx, req)
37 | }
38 |
39 | // RecentFills returns the recent fills of the latest transactions within 24 hours.
40 | func (as *ApiService) RecentFills(ctx context.Context) (*ApiResponse, error) {
41 | req := NewRequest(http.MethodGet, "/api/v1/limit/fills", nil)
42 | return as.Call(ctx, req)
43 | }
44 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
4 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
5 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
6 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
7 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
8 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
9 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
10 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
11 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14 | github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
15 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
16 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
17 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
18 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
19 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
20 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
21 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
22 |
--------------------------------------------------------------------------------
/hf_account_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestApiService_HfAccountInnerTransfer(t *testing.T) {
10 | s := NewApiServiceFromEnv()
11 | clientOid := IntToString(time.Now().Unix())
12 | p := map[string]string{
13 | "clientOid": clientOid,
14 | "currency": "USDT",
15 | "from": "trade",
16 | "to": "margin_v2",
17 | "amount": "1",
18 | }
19 | rsp, err := s.HfAccountInnerTransfer(context.Background(), p)
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 | v := &InnerTransferResultModel{}
24 | if err := rsp.ReadData(v); err != nil {
25 | t.Fatal(err)
26 | }
27 | t.Log(ToJsonString(v))
28 | if v.OrderId == "" {
29 | t.Error("Empty key 'orderId'")
30 | }
31 | }
32 |
33 | func TestApiService_HfAccounts(t *testing.T) {
34 | s := NewApiServiceFromEnv()
35 |
36 | rsp, err := s.HfAccounts(context.Background(), "", "trade_hf")
37 | if err != nil {
38 | t.Fatal(err)
39 | }
40 | v := &HfAccountsModel{}
41 | if err := rsp.ReadData(v); err != nil {
42 | t.Fatal(err)
43 | }
44 | t.Log(ToJsonString(v))
45 | }
46 |
47 | func TestApiService_HfAccount(t *testing.T) {
48 | s := NewApiServiceFromEnv()
49 | rsp, err := s.HfAccount(context.Background(), "2969860516868")
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 | v := &HfAccountModel{}
54 | if err := rsp.ReadData(v); err != nil {
55 | t.Fatal(err)
56 | }
57 | t.Log(ToJsonString(v))
58 | }
59 |
60 | func TestApiService_HfAccountTransferable(t *testing.T) {
61 | s := NewApiServiceFromEnv()
62 | rsp, err := s.HfAccountTransferable(context.Background(), "USDT")
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 | a := &HfAccountTransferableModel{}
67 | if err := rsp.ReadData(a); err != nil {
68 | t.Fatal(err)
69 | }
70 | t.Log(ToJsonString(a))
71 | }
72 |
73 | func TestApiService_HfAccountLedgers(t *testing.T) {
74 | s := NewApiServiceFromEnv()
75 | p := map[string]string{}
76 | rsp, err := s.HfAccountLedgers(context.Background(), p)
77 | if err != nil {
78 | t.Fatal(err)
79 | }
80 | a := &HfAccountLedgersModel{}
81 | if err := rsp.ReadData(a); err != nil {
82 | t.Fatal(err)
83 | }
84 | t.Log(ToJsonString(a))
85 | }
86 |
--------------------------------------------------------------------------------
/signer.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "crypto/hmac"
5 | "crypto/sha256"
6 | "encoding/base64"
7 | "time"
8 | )
9 |
10 | // Signer interface contains Sign() method.
11 | type Signer interface {
12 | Sign(plain []byte) []byte
13 | }
14 |
15 | // Sha256Signer is the sha256 Signer.
16 | type Sha256Signer struct {
17 | key []byte
18 | }
19 |
20 | // Sign makes a signature by sha256.
21 | func (ss *Sha256Signer) Sign(plain []byte) []byte {
22 | hm := hmac.New(sha256.New, ss.key)
23 | hm.Write(plain)
24 | return hm.Sum(nil)
25 | }
26 |
27 | // KcSigner is the implement of Signer for KuCoin.
28 | type KcSigner struct {
29 | Sha256Signer
30 | apiKey string
31 | apiSecret string
32 | apiPassPhrase string
33 | apiKeyVersion string
34 | }
35 |
36 | // Sign makes a signature by sha256 with `apiKey` `apiSecret` `apiPassPhrase`.
37 | func (ks *KcSigner) Sign(plain []byte) []byte {
38 | s := ks.Sha256Signer.Sign(plain)
39 | return []byte(base64.StdEncoding.EncodeToString(s))
40 | }
41 |
42 | // Headers returns a map of signature header.
43 | func (ks *KcSigner) Headers(plain string) map[string]string {
44 | t := IntToString(time.Now().UnixNano() / 1000000)
45 | p := []byte(t + plain)
46 | s := string(ks.Sign(p))
47 | ksHeaders := map[string]string{
48 | "KC-API-KEY": ks.apiKey,
49 | "KC-API-PASSPHRASE": ks.apiPassPhrase,
50 | "KC-API-TIMESTAMP": t,
51 | "KC-API-SIGN": s,
52 | }
53 |
54 | if ks.apiKeyVersion != "" && ks.apiKeyVersion != ApiKeyVersionV1 {
55 | ksHeaders["KC-API-KEY-VERSION"] = ks.apiKeyVersion
56 | }
57 |
58 | return ksHeaders
59 | }
60 |
61 | // NewKcSigner creates a instance of KcSigner.
62 | func NewKcSigner(key, secret, passPhrase string) *KcSigner {
63 | ks := &KcSigner{
64 | apiKey: key,
65 | apiSecret: secret,
66 | apiPassPhrase: passPhrase,
67 | apiKeyVersion: ApiKeyVersionV1,
68 | }
69 | ks.key = []byte(secret)
70 | return ks
71 | }
72 |
73 | // NewKcSignerV2 creates a instance of KcSigner.
74 | func NewKcSignerV2(key, secret, passPhrase string) *KcSigner {
75 | ks := &KcSigner{
76 | apiKey: key,
77 | apiSecret: secret,
78 | apiPassPhrase: passPhraseEncrypt([]byte(secret), []byte(passPhrase)),
79 | apiKeyVersion: ApiKeyVersionV2,
80 | }
81 | ks.key = []byte(secret)
82 | return ks
83 | }
84 |
85 | // passPhraseEncrypt, encrypt passPhrase
86 | func passPhraseEncrypt(key, plain []byte) string {
87 | hm := hmac.New(sha256.New, key)
88 | hm.Write(plain)
89 | return base64.StdEncoding.EncodeToString(hm.Sum(nil))
90 | }
91 |
--------------------------------------------------------------------------------
/hf_account.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | // HfAccountInnerTransfer Users can transfer funds between their main account,
9 | // trading account, and high-frequency trading account free of charge.
10 | func (as *ApiService) HfAccountInnerTransfer(ctx context.Context, params map[string]string) (*ApiResponse, error) {
11 | req := NewRequest(http.MethodPost, "/api/v2/accounts/inner-transfer", params)
12 | return as.Call(ctx, req)
13 | }
14 |
15 | type HfAccountInnerTransferRes struct {
16 | OrderId string `json:"orderId"`
17 | }
18 |
19 | // HfAccounts Get a list of high-frequency trading accounts.
20 | func (as *ApiService) HfAccounts(ctx context.Context, currency, accountType string) (*ApiResponse, error) {
21 | p := map[string]string{
22 | "currency": currency,
23 | "type": accountType,
24 | }
25 | req := NewRequest(http.MethodGet, "/api/v1/accounts", p)
26 | return as.Call(ctx, req)
27 | }
28 |
29 | type HfAccountsModel []HfAccountModel
30 |
31 | // HfAccount Get the details of the high-frequency trading account
32 | func (as *ApiService) HfAccount(ctx context.Context, accountId string) (*ApiResponse, error) {
33 | req := NewRequest(http.MethodGet, "/api/v1/accounts/"+accountId, nil)
34 | return as.Call(ctx, req)
35 | }
36 |
37 | type HfAccountModel struct {
38 | Balance string `json:"balance"`
39 | Available string `json:"available"`
40 | Currency string `json:"currency"`
41 | Holds string `json:"holds"`
42 | Type string `json:"type"`
43 | Id string `json:"id"`
44 | }
45 |
46 | // HfAccountTransferable This API can be used to obtain the amount of transferrable funds
47 | // in high-frequency trading accounts.
48 | func (as *ApiService) HfAccountTransferable(ctx context.Context, currency string) (*ApiResponse, error) {
49 | p := map[string]string{
50 | "currency": currency,
51 | "type": "TRADE_HF",
52 | }
53 | req := NewRequest(http.MethodGet, "/api/v1/accounts/transferable", p)
54 | return as.Call(ctx, req)
55 | }
56 |
57 | type HfAccountTransferableModel struct {
58 | Balance string `json:"balance"`
59 | Available string `json:"available"`
60 | Currency string `json:"currency"`
61 | Holds string `json:"holds"`
62 | Transferable string `json:"transferable"`
63 | }
64 |
65 | // HfAccountLedgers returns all transfer (in and out) records in high-frequency trading account
66 | // and supports multi-coin queries. The query results are sorted in descending order by createdAt and id.
67 | func (as *ApiService) HfAccountLedgers(ctx context.Context, params map[string]string) (*ApiResponse, error) {
68 | req := NewRequest(http.MethodGet, "/api/v1/hf/accounts/ledgers", params)
69 | return as.Call(ctx, req)
70 | }
71 |
72 | type HfAccountLedgersModel []*HfAccountLedgerModel
73 |
74 | type HfAccountLedgerModel struct {
75 | Id string `json:"id"`
76 | Currency string `json:"currency"`
77 | Amount string `json:"amount"`
78 | Fee string `json:"fee"`
79 | Balance string `json:"balance"`
80 | AccountType string `json:"accountType"`
81 | BizType string `json:"bizType"`
82 | Direction string `json:"direction"`
83 | CreatedAt string `json:"createdAt"`
84 | Context string `json:"context"`
85 | }
86 |
--------------------------------------------------------------------------------
/currency_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestApiService_Currencies(t *testing.T) {
9 | s := NewApiServiceFromEnv()
10 | rsp, err := s.Currencies(context.Background())
11 | if err != nil {
12 | t.Fatal(err)
13 | }
14 | cl := CurrenciesModel{}
15 | if err := rsp.ReadData(&cl); err != nil {
16 | t.Fatal(err)
17 | }
18 | for _, c := range cl {
19 | t.Log(ToJsonString(c))
20 | switch {
21 | case c.Name == "":
22 | t.Error("Empty key 'name'")
23 | case c.Currency == "":
24 | t.Error("Empty key 'currency'")
25 | case c.FullName == "":
26 | t.Error("Empty key 'fullName'")
27 | case c.WithdrawalMinSize == "":
28 | t.Error("Empty key 'withdrawalMinSize'")
29 | case c.WithdrawalMinFee == "":
30 | t.Error("Empty key 'withdrawalMinFee'")
31 | }
32 | }
33 | }
34 |
35 | func TestApiService_Currency(t *testing.T) {
36 | s := NewApiServiceFromEnv()
37 | rsp, err := s.Currency(context.Background(), "BTC", "")
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 | c := &CurrencyModel{}
42 | if err := rsp.ReadData(c); err != nil {
43 | t.Fatal(err)
44 | }
45 | t.Log(ToJsonString(c))
46 | switch {
47 | case c.Name == "":
48 | t.Error("Empty key 'name'")
49 | case c.Currency == "":
50 | t.Error("Empty key 'currency'")
51 | case c.FullName == "":
52 | t.Error("Empty key 'fullName'")
53 | case c.WithdrawalMinSize == "":
54 | t.Error("Empty key 'withdrawalMinSize'")
55 | case c.WithdrawalMinFee == "":
56 | t.Error("Empty key 'withdrawalMinFee'")
57 | }
58 | }
59 |
60 | func TestApiService_Currency_V2(t *testing.T) {
61 | s := NewApiServiceFromEnv()
62 | rsp, err := s.CurrencyV2(context.Background(), "BTC", "")
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 | c := &CurrencyV2Model{}
67 | if err := rsp.ReadData(c); err != nil {
68 | t.Fatal(err)
69 | }
70 | t.Log(ToJsonString(c))
71 | switch {
72 | case c.Name == "":
73 | t.Error("Empty key 'name'")
74 | case c.Currency == "":
75 | t.Error("Empty key 'currency'")
76 | case c.FullName == "":
77 | t.Error("Empty key 'fullName'")
78 | }
79 | }
80 |
81 | func TestApiService_Prices(t *testing.T) {
82 | s := NewApiServiceFromEnv()
83 | rsp, err := s.Prices(context.Background(), "USD", "BTC,KCS")
84 | if err != nil {
85 | t.Fatal(err)
86 | }
87 | p := PricesModel{}
88 | if err := rsp.ReadData(&p); err != nil {
89 | t.Fatal(err)
90 | }
91 | if len(p) == 0 {
92 | t.Error("Empty prices")
93 | }
94 | t.Log(ToJsonString(p))
95 | }
96 |
97 | func TestApiServiceCurrenciesV3(t *testing.T) {
98 | s := NewApiServiceFromEnv()
99 | rsp, err := s.CurrenciesV3(context.Background())
100 | if err != nil {
101 | t.Fatal(err)
102 | }
103 | p := CurrenciesV3Model{}
104 | if err := rsp.ReadData(&p); err != nil {
105 | t.Fatal(err)
106 | }
107 | t.Log(ToJsonString(p))
108 | }
109 |
110 | func TestApiService_CurrencyInfoV3(t *testing.T) {
111 | s := NewApiServiceFromEnv()
112 | rsp, err := s.CurrencyInfoV3(context.Background(), "BTC")
113 | if err != nil {
114 | t.Fatal(err)
115 | }
116 | p := CurrencyV3Model{}
117 | if err := rsp.ReadData(&p); err != nil {
118 | t.Fatal(err)
119 | }
120 | t.Log(ToJsonString(p))
121 | }
122 |
--------------------------------------------------------------------------------
/websocket_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestApiService_WebSocketPublicToken(t *testing.T) {
9 | s := NewApiServiceFromEnv()
10 | rsp, err := s.WebSocketPublicToken(context.Background())
11 | if err != nil {
12 | t.Fatal(err)
13 | }
14 | pt := &WebSocketTokenModel{}
15 | if err := rsp.ReadData(pt); err != nil {
16 | t.Fatal(err)
17 | }
18 | t.Log(pt.Token)
19 | switch {
20 | case pt.Token == "":
21 | t.Error("Empty key 'token'")
22 | case len(pt.Servers) == 0:
23 | t.Fatal("Empty key 'instanceServers'")
24 | }
25 | for _, s := range pt.Servers {
26 | t.Log(ToJsonString(s))
27 | switch {
28 | case s.Endpoint == "":
29 | t.Error("Empty key 'endpoint'")
30 | case s.Protocol == "":
31 | t.Fatal("Empty key 'protocol'")
32 | }
33 | }
34 | }
35 |
36 | func TestApiService_WebSocketPrivateToken(t *testing.T) {
37 | s := NewApiServiceFromEnv()
38 | rsp, err := s.WebSocketPrivateToken(context.Background())
39 | if err != nil {
40 | t.Fatal(err)
41 | }
42 | pt := &WebSocketTokenModel{}
43 | if err := rsp.ReadData(pt); err != nil {
44 | t.Fatal(err)
45 | }
46 | t.Log(pt.Token)
47 | switch {
48 | case pt.Token == "":
49 | t.Error("Empty key 'token'")
50 | case len(pt.Servers) == 0:
51 | t.Fatal("Empty key 'instanceServers'")
52 | }
53 | for _, s := range pt.Servers {
54 | t.Log(ToJsonString(s))
55 | switch {
56 | case s.Endpoint == "":
57 | t.Error("Empty key 'endpoint'")
58 | case s.Protocol == "":
59 | t.Fatal("Empty key 'protocol'")
60 | }
61 | }
62 | }
63 |
64 | func TestWebSocketClient_Connect(t *testing.T) {
65 | s := NewApiServiceFromEnv()
66 |
67 | rsp, err := s.WebSocketPublicToken(context.Background())
68 | if err != nil {
69 | t.Fatal(err)
70 | }
71 |
72 | tk := &WebSocketTokenModel{}
73 | if err := rsp.ReadData(tk); err != nil {
74 | t.Fatal(err)
75 | }
76 |
77 | c := s.NewWebSocketClient(tk)
78 |
79 | _, _, err = c.Connect()
80 | if err != nil {
81 | t.Fatal(err)
82 | }
83 | }
84 | func TestWebSocketClient_Subscribe(t *testing.T) {
85 | t.SkipNow()
86 |
87 | s := NewApiServiceFromEnv()
88 |
89 | rsp, err := s.WebSocketPublicToken(context.Background())
90 | if err != nil {
91 | t.Fatal(err)
92 | }
93 |
94 | tk := &WebSocketTokenModel{}
95 | if err := rsp.ReadData(tk); err != nil {
96 | t.Fatal(err)
97 | }
98 |
99 | c := s.NewWebSocketClient(tk)
100 |
101 | mc, ec, err := c.Connect()
102 | if err != nil {
103 | t.Fatal(err)
104 | }
105 |
106 | ch1 := NewSubscribeMessage("/market/ticker:KCS-BTC", false)
107 | ch2 := NewSubscribeMessage("/market/ticker:ETH-BTC", false)
108 | uch := NewUnsubscribeMessage("/market/ticker:ETH-BTC", false)
109 |
110 | if err := c.Subscribe(ch1, ch2); err != nil {
111 | t.Fatal(err)
112 | }
113 |
114 | var i = 0
115 | for {
116 | select {
117 | case err := <-ec:
118 | c.Stop() // Stop subscribing the WebSocket feed
119 | t.Fatal(err)
120 | case msg := <-mc:
121 | t.Log(ToJsonString(msg))
122 | i++
123 | if i == 5 {
124 | t.Log("Unsubscribe ETH-BTC")
125 | if err = c.Unsubscribe(uch); err != nil {
126 | t.Fatal(err)
127 | }
128 | }
129 | if i == 10 {
130 | t.Log("Subscribe ETH-BTC")
131 | if err = c.Subscribe(ch2); err != nil {
132 | t.Fatal(err)
133 | }
134 | }
135 | if i == 15 {
136 | t.Log("Exit subscribing")
137 | c.Stop() // Stop subscribing the WebSocket feed
138 | return
139 | }
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/deposit_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestApiService_CreateDepositAddress(t *testing.T) {
9 | t.SkipNow()
10 |
11 | s := NewApiServiceFromEnv()
12 | rsp, err := s.CreateDepositAddress(context.Background(), "KCS", "")
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 | a := &DepositAddressModel{}
17 | if err := rsp.ReadData(a); err != nil {
18 | t.Fatal(err)
19 | }
20 |
21 | t.Log(ToJsonString(a))
22 |
23 | switch {
24 | case a.Address == "":
25 | t.Error("Empty key 'address'")
26 | case a.Memo == "":
27 | t.Error("Empty key 'memo'")
28 | }
29 | }
30 |
31 | func TestApiService_DepositAddresses(t *testing.T) {
32 | t.SkipNow()
33 |
34 | s := NewApiServiceFromEnv()
35 | rsp, err := s.DepositAddresses(context.Background(), "KCS", "")
36 | if err != nil {
37 | t.Fatal(err)
38 | }
39 | if rsp.Code == "260200" {
40 | // Ignore deposit.disabled
41 | return
42 | }
43 | as := DepositAddressesModel{}
44 | if err := rsp.ReadData(&as); err != nil {
45 | t.Fatal(err)
46 | }
47 | t.Log(ToJsonString(as))
48 | if as.Address == "" {
49 | t.Error("Empty key 'address'")
50 | }
51 | if as.Memo == "" {
52 | t.Error("Empty key 'memo'")
53 | }
54 | }
55 |
56 | func TestApiService_DepositAddressesV2(t *testing.T) {
57 | s := NewApiServiceFromEnv()
58 | rsp, err := s.DepositAddressesV2(context.Background(), "USDT")
59 | if err != nil {
60 | t.Fatal(err)
61 | }
62 | as := DepositAddressesV2Model{}
63 | if err := rsp.ReadData(&as); err != nil {
64 | t.Fatal(err)
65 | }
66 | t.Log(ToJsonString(as))
67 | }
68 |
69 | func TestApiService_Deposits(t *testing.T) {
70 | s := NewApiServiceFromEnv()
71 | p := map[string]string{}
72 | pp := &PaginationParam{CurrentPage: 1, PageSize: 10}
73 | rsp, err := s.Deposits(context.Background(), p, pp)
74 | if err != nil {
75 | t.Fatal(err)
76 | }
77 | ds := DepositsModel{}
78 | if _, err := rsp.ReadPaginationData(&ds); err != nil {
79 | t.Fatal(err)
80 | }
81 |
82 | for _, d := range ds {
83 | t.Log(ToJsonString(d))
84 | switch {
85 | case d.Address == "":
86 | t.Error("Empty key 'address'")
87 | case d.Amount == "":
88 | t.Error("Empty key 'amount'")
89 | case d.Fee == "":
90 | t.Error("Empty key 'fee'")
91 | case d.Currency == "":
92 | t.Error("Empty key 'currency'")
93 | case d.WalletTxId == "":
94 | t.Error("Empty key 'walletTxId'")
95 | case d.Status == "":
96 | t.Error("Empty key 'status'")
97 | case d.CreatedAt == 0:
98 | t.Error("Empty key 'createdAt'")
99 | case d.UpdatedAt == 0:
100 | t.Error("Empty key 'updatedAt'")
101 | }
102 | }
103 | }
104 |
105 | func TestApiService_V1Deposits(t *testing.T) {
106 | t.SkipNow()
107 | s := NewApiServiceFromEnv()
108 | p := map[string]string{}
109 | pp := &PaginationParam{CurrentPage: 1, PageSize: 10}
110 | rsp, err := s.V1Deposits(context.Background(), p, pp)
111 | if err != nil {
112 | t.Fatal(err)
113 | }
114 | ds := V1DepositsModel{}
115 | if _, err := rsp.ReadPaginationData(&ds); err != nil {
116 | t.Fatal(err)
117 | }
118 |
119 | for _, d := range ds {
120 | t.Log(ToJsonString(d))
121 | switch {
122 | case d.Amount == "":
123 | t.Error("Empty key 'amount'")
124 | case d.Currency == "":
125 | t.Error("Empty key 'currency'")
126 | case d.WalletTxId == "":
127 | t.Error("Empty key 'walletTxId'")
128 | case d.Status == "":
129 | t.Error("Empty key 'status'")
130 | case d.CreateAt == 0:
131 | t.Error("Empty key 'createAt'")
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/deposit.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | // A DepositAddressModel represents a deposit address of currency for deposit.
9 | type DepositAddressModel struct {
10 | Address string `json:"address"`
11 | Memo string `json:"memo"`
12 | Chain string `json:"chain"`
13 | }
14 |
15 | // A DepositAddressesModel is the set of *DepositAddressModel.
16 | type DepositAddressesModel DepositAddressModel
17 |
18 | // A DepositModel represents a deposit record.
19 | type DepositModel struct {
20 | Chain string `json:"chain"`
21 | Address string `json:"address"`
22 | Memo string `json:"memo"`
23 | Amount string `json:"amount"`
24 | Fee string `json:"fee"`
25 | Currency string `json:"currency"`
26 | IsInner bool `json:"isInner"`
27 | WalletTxId string `json:"walletTxId"`
28 | Status string `json:"status"`
29 | Remark string `json:"remark"`
30 | CreatedAt int64 `json:"createdAt"`
31 | UpdatedAt int64 `json:"updatedAt"`
32 | }
33 |
34 | // A DepositsModel is the set of *DepositModel.
35 | type DepositsModel []*DepositModel
36 |
37 | // CreateDepositAddress creates a deposit address.
38 | func (as *ApiService) CreateDepositAddress(ctx context.Context, currency, chain string) (*ApiResponse, error) {
39 | params := map[string]string{"currency": currency}
40 | if chain != "" {
41 | params["chain"] = chain
42 | }
43 | req := NewRequest(http.MethodPost, "/api/v1/deposit-addresses", params)
44 | return as.Call(ctx, req)
45 | }
46 |
47 | // DepositAddresses returns the deposit address of currency for deposit.
48 | // If return data is empty, you may need create a deposit address first.
49 | func (as *ApiService) DepositAddresses(ctx context.Context, currency, chain string) (*ApiResponse, error) {
50 | params := map[string]string{"currency": currency}
51 | if chain != "" {
52 | params["chain"] = chain
53 | }
54 | req := NewRequest(http.MethodGet, "/api/v1/deposit-addresses", params)
55 | return as.Call(ctx, req)
56 | }
57 |
58 | type depositAddressV2Model struct {
59 | Address string `json:"address"`
60 | Memo string `json:"memo"`
61 | Chain string `json:"chain"`
62 | ContractAddress string `json:"contract_address"`
63 | }
64 | type DepositAddressesV2Model []*depositAddressV2Model
65 |
66 | // DepositAddressesV2 Get all deposit addresses for the currency you intend to deposit.
67 | // If the returned data is empty, you may need to create a deposit address first.
68 | func (as *ApiService) DepositAddressesV2(ctx context.Context, currency string) (*ApiResponse, error) {
69 | params := map[string]string{"currency": currency}
70 | req := NewRequest(http.MethodGet, "/api/v2/deposit-addresses", params)
71 | return as.Call(ctx, req)
72 | }
73 |
74 | // Deposits returns a list of deposit.
75 | func (as *ApiService) Deposits(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
76 | pagination.ReadParam(params)
77 | req := NewRequest(http.MethodGet, "/api/v1/deposits", params)
78 | return as.Call(ctx, req)
79 | }
80 |
81 | // A V1DepositModel represents a v1 deposit record.
82 | type V1DepositModel struct {
83 | Amount string `json:"amount"`
84 | Currency string `json:"currency"`
85 | IsInner bool `json:"isInner"`
86 | WalletTxId string `json:"walletTxId"`
87 | Status string `json:"status"`
88 | CreateAt int64 `json:"createAt"`
89 | }
90 |
91 | // A V1DepositsModel is the set of *V1DepositModel.
92 | type V1DepositsModel []*V1DepositModel
93 |
94 | // V1Deposits returns a list of v1 historical deposits.
95 | func (as *ApiService) V1Deposits(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
96 | pagination.ReadParam(params)
97 | req := NewRequest(http.MethodGet, "/api/v1/hist-deposits", params)
98 | return as.Call(ctx, req)
99 | }
100 |
--------------------------------------------------------------------------------
/withdrawal_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestApiService_Withdrawals(t *testing.T) {
9 | s := NewApiServiceFromEnv()
10 | p := map[string]string{}
11 | pp := &PaginationParam{CurrentPage: 1, PageSize: 10}
12 | rsp, err := s.Withdrawals(context.Background(), p, pp)
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 | ws := WithdrawalsModel{}
17 | if _, err := rsp.ReadPaginationData(&ws); err != nil {
18 | t.Fatal(err)
19 | }
20 |
21 | for _, w := range ws {
22 | t.Log(ToJsonString(w))
23 | switch {
24 | case w.Id == "":
25 | t.Error("Empty key 'id'")
26 | case w.Address == "":
27 | t.Error("Empty key 'address'")
28 | case w.Currency == "":
29 | t.Error("Empty key 'currency'")
30 | case w.Amount == "":
31 | t.Error("Empty key 'amount'")
32 | case w.Fee == "":
33 | t.Error("Empty key 'fee'")
34 | case w.Status == "":
35 | t.Error("Empty key 'status'")
36 | case w.CreatedAt == 0:
37 | t.Error("Empty key 'createdAt'")
38 | case w.UpdatedAt == 0:
39 | t.Error("Empty key 'updatedAt'")
40 | }
41 | }
42 | }
43 |
44 | func TestApiService_V1Withdrawals(t *testing.T) {
45 | t.SkipNow()
46 | s := NewApiServiceFromEnv()
47 | p := map[string]string{}
48 | pp := &PaginationParam{CurrentPage: 1, PageSize: 10}
49 | rsp, err := s.V1Withdrawals(context.Background(), p, pp)
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 | ws := V1WithdrawalsModel{}
54 | if _, err := rsp.ReadPaginationData(&ws); err != nil {
55 | t.Fatal(err)
56 | }
57 |
58 | for _, w := range ws {
59 | t.Log(ToJsonString(w))
60 | switch {
61 | case w.Address == "":
62 | t.Error("Empty key 'address'")
63 | case w.Currency == "":
64 | t.Error("Empty key 'currency'")
65 | case w.Amount == "":
66 | t.Error("Empty key 'amount'")
67 | case w.Status == "":
68 | t.Error("Empty key 'status'")
69 | case w.WalletTxId == "":
70 | t.Error("Empty key 'walletTxId'")
71 | case w.CreateAt == 0:
72 | t.Error("Empty key 'createAt'")
73 | }
74 | }
75 | }
76 |
77 | func TestApiService_WithdrawalQuotas(t *testing.T) {
78 | s := NewApiServiceFromEnv()
79 | rsp, err := s.WithdrawalQuotas(context.Background(), "BTC", "")
80 | if err != nil {
81 | t.Fatal(err)
82 | }
83 | wq := &WithdrawalQuotasModel{}
84 | if err := rsp.ReadData(wq); err != nil {
85 | t.Fatal(err)
86 | }
87 | t.Log(ToJsonString(wq))
88 | switch {
89 | case wq.Currency == "":
90 | t.Error("Empty key 'currency'")
91 | case wq.AvailableAmount == "":
92 | t.Error("Empty key 'availableAmount'")
93 | case wq.RemainAmount == "":
94 | t.Error("Empty key 'remainAmount'")
95 | case wq.WithdrawMinSize == "":
96 | t.Error("Empty key 'withdrawMinSize'")
97 | case wq.LimitBTCAmount == "":
98 | t.Error("Empty key 'limitBTCAmount'")
99 | case wq.InnerWithdrawMinFee == "":
100 | t.Error("Empty key 'innerWithdrawMinFee'")
101 | case wq.WithdrawMinFee == "":
102 | t.Error("Empty key 'withdrawMinFee'")
103 | case wq.Precision == 0:
104 | t.Error("Empty key 'precision'")
105 | }
106 | }
107 |
108 | func TestApiService_ApplyWithdrawal(t *testing.T) {
109 | t.SkipNow()
110 |
111 | s := NewApiServiceFromEnv()
112 | rsp, err := s.ApplyWithdrawal(context.Background(), "BTC", "xx", "0.01", map[string]string{})
113 | if err != nil {
114 | t.Fatal(err)
115 | }
116 | w := &ApplyWithdrawalResultModel{}
117 | if err := rsp.ReadData(w); err != nil {
118 | t.Fatal(err)
119 | }
120 | t.Log(ToJsonString(w))
121 | switch {
122 | case w.WithdrawalId == "":
123 | t.Error("Empty key 'withdrawalId'")
124 | }
125 | }
126 |
127 | func TestApiService_CancelWithdrawal(t *testing.T) {
128 | t.SkipNow()
129 |
130 | s := NewApiServiceFromEnv()
131 | rsp, err := s.CancelWithdrawal(context.Background(), "xxx")
132 | if err != nil {
133 | t.Fatal(err)
134 | }
135 | w := &CancelWithdrawalResultModel{}
136 | if err := rsp.ReadData(w); err != nil {
137 | t.Fatal(err)
138 | }
139 | t.Log(ToJsonString(w))
140 | switch {
141 | case len(w.CancelledWithdrawIds) == 0:
142 | t.Error("Empty key 'cancelledWithdrawIds'")
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/withdrawal.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | // A WithdrawalModel represents a withdrawal.
9 | type WithdrawalModel struct {
10 | Chain string `json:"chain"`
11 | Id string `json:"id"`
12 | Address string `json:"address"`
13 | Memo string `json:"memo"`
14 | Currency string `json:"currency"`
15 | Amount string `json:"amount"`
16 | Fee string `json:"fee"`
17 | WalletTxId string `json:"walletTxId"`
18 | IsInner bool `json:"isInner"`
19 | Status string `json:"status"`
20 | Remark string `json:"remark"`
21 | CreatedAt int64 `json:"createdAt"`
22 | UpdatedAt int64 `json:"updatedAt"`
23 | }
24 |
25 | // A WithdrawalsModel is the set of *WithdrawalModel.
26 | type WithdrawalsModel []*WithdrawalModel
27 |
28 | // Withdrawals returns a list of withdrawals.
29 | func (as *ApiService) Withdrawals(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
30 | pagination.ReadParam(params)
31 | req := NewRequest(http.MethodGet, "/api/v1/withdrawals", params)
32 | return as.Call(ctx, req)
33 | }
34 |
35 | // A V1WithdrawalModel represents a v1 historical withdrawal.
36 | type V1WithdrawalModel struct {
37 | Address string `json:"address"`
38 | Amount string `json:"amount"`
39 | Currency string `json:"currency"`
40 | IsInner bool `json:"isInner"`
41 | WalletTxId string `json:"walletTxId"`
42 | Status string `json:"status"`
43 | CreateAt int64 `json:"createAt"`
44 | }
45 |
46 | // A V1WithdrawalsModel is the set of *V1WithdrawalModel.
47 | type V1WithdrawalsModel []*V1WithdrawalModel
48 |
49 | // V1Withdrawals returns a list of v1 historical withdrawals.
50 | func (as *ApiService) V1Withdrawals(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
51 | pagination.ReadParam(params)
52 | req := NewRequest(http.MethodGet, "/api/v1/hist-withdrawals", params)
53 | return as.Call(ctx, req)
54 | }
55 |
56 | // A WithdrawalQuotasModel represents the quotas for a currency.
57 | type WithdrawalQuotasModel struct {
58 | Currency string `json:"currency"`
59 | AvailableAmount string `json:"availableAmount"`
60 | RemainAmount string `json:"remainAmount"`
61 | WithdrawMinSize string `json:"withdrawMinSize"`
62 | LimitBTCAmount string `json:"limitBTCAmount"`
63 | InnerWithdrawMinFee string `json:"innerWithdrawMinFee"`
64 | UsedBTCAmount string `json:"usedBTCAmount"`
65 | IsWithdrawEnabled bool `json:"isWithdrawEnabled"`
66 | WithdrawMinFee string `json:"withdrawMinFee"`
67 | Precision uint8 `json:"precision"`
68 | Chain string `json:"chain"`
69 | }
70 |
71 | // WithdrawalQuotas returns the quotas of withdrawal.
72 | func (as *ApiService) WithdrawalQuotas(ctx context.Context, currency, chain string) (*ApiResponse, error) {
73 | params := map[string]string{"currency": currency}
74 | if chain != "" {
75 | params["chain"] = chain
76 | }
77 | req := NewRequest(http.MethodGet, "/api/v1/withdrawals/quotas", params)
78 | return as.Call(ctx, req)
79 | }
80 |
81 | // ApplyWithdrawalResultModel represents the result of ApplyWithdrawal().
82 | type ApplyWithdrawalResultModel struct {
83 | WithdrawalId string `json:"withdrawalId"`
84 | }
85 |
86 | // ApplyWithdrawal applies a withdrawal.
87 | func (as *ApiService) ApplyWithdrawal(ctx context.Context, currency, address, amount string, options map[string]string) (*ApiResponse, error) {
88 | p := map[string]string{
89 | "currency": currency,
90 | "address": address,
91 | "amount": amount,
92 | }
93 | for k, v := range options {
94 | p[k] = v
95 | }
96 | req := NewRequest(http.MethodPost, "/api/v1/withdrawals", p)
97 | return as.Call(ctx, req)
98 | }
99 |
100 | // CancelWithdrawalResultModel represents the result of CancelWithdrawal().
101 | type CancelWithdrawalResultModel struct {
102 | CancelledWithdrawIds []string `json:"cancelledWithdrawIds"`
103 | }
104 |
105 | // CancelWithdrawal cancels a withdrawal by withdrawalId.
106 | func (as *ApiService) CancelWithdrawal(ctx context.Context, withdrawalId string) (*ApiResponse, error) {
107 | req := NewRequest(http.MethodDelete, "/api/v1/withdrawals/"+withdrawalId, nil)
108 | return as.Call(ctx, req)
109 | }
110 |
--------------------------------------------------------------------------------
/examples/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | "github.com/Kucoin/kucoin-go-sdk"
8 | )
9 |
10 | func main() {
11 | //s := kucoin.NewApiServiceFromEnv()
12 | s := kucoin.NewApiService(
13 | kucoin.ApiKeyOption("key"),
14 | kucoin.ApiSecretOption("secret"),
15 | kucoin.ApiPassPhraseOption("passphrase"),
16 | )
17 |
18 | ctx := context.Background()
19 |
20 | serverTime(ctx, s)
21 | accounts(ctx, s)
22 | orders(ctx, s)
23 | publicWebsocket(ctx, s)
24 | privateWebsocket(ctx, s)
25 | }
26 |
27 | func serverTime(ctx context.Context, s *kucoin.ApiService) {
28 | rsp, err := s.ServerTime(ctx)
29 | if err != nil {
30 | log.Printf("Error: %s", err.Error())
31 | // Handle error
32 | return
33 | }
34 |
35 | var ts int64
36 | if err := rsp.ReadData(&ts); err != nil {
37 | // Handle error
38 | return
39 | }
40 | log.Printf("The server time: %d", ts)
41 | }
42 |
43 | func accounts(ctx context.Context, s *kucoin.ApiService) {
44 | rsp, err := s.Accounts(ctx, "", "")
45 | if err != nil {
46 | // Handle error
47 | return
48 | }
49 |
50 | as := kucoin.AccountsModel{}
51 | if err := rsp.ReadData(&as); err != nil {
52 | // Handle error
53 | return
54 | }
55 |
56 | for _, a := range as {
57 | log.Printf("Available balance: %s %s => %s", a.Type, a.Currency, a.Available)
58 | }
59 | }
60 |
61 | func orders(ctx context.Context, s *kucoin.ApiService) {
62 | rsp, err := s.Orders(ctx, map[string]string{}, &kucoin.PaginationParam{CurrentPage: 1, PageSize: 10})
63 | if err != nil {
64 | // Handle error
65 | return
66 | }
67 |
68 | os := kucoin.OrdersModel{}
69 | pa, err := rsp.ReadPaginationData(&os)
70 | if err != nil {
71 | // Handle error
72 | return
73 | }
74 | log.Printf("Total num: %d, total page: %d", pa.TotalNum, pa.TotalPage)
75 | for _, o := range os {
76 | log.Printf("Order: %s, %s, %s", o.Id, o.Type, o.Price)
77 | }
78 | }
79 | func publicWebsocket(ctx context.Context, s *kucoin.ApiService) {
80 | rsp, err := s.WebSocketPublicToken(ctx)
81 | if err != nil {
82 | // Handle error
83 | return
84 | }
85 |
86 | tk := &kucoin.WebSocketTokenModel{}
87 | if err := rsp.ReadData(tk); err != nil {
88 | // Handle error
89 | return
90 | }
91 |
92 | c := s.NewWebSocketClient(tk)
93 |
94 | mc, ec, err := c.Connect()
95 | if err != nil {
96 | // Handle error
97 | return
98 | }
99 |
100 | ch1 := kucoin.NewSubscribeMessage("/market/ticker:KCS-BTC", false)
101 | ch2 := kucoin.NewSubscribeMessage("/market/ticker:ETH-BTC", false)
102 | uch := kucoin.NewUnsubscribeMessage("/market/ticker:ETH-BTC", false)
103 |
104 | if err := c.Subscribe(ch1, ch2); err != nil {
105 | // Handle error
106 | return
107 | }
108 |
109 | var i = 0
110 | for {
111 | select {
112 | case err := <-ec:
113 | c.Stop() // Stop subscribing the WebSocket feed
114 | log.Printf("Error: %s", err.Error())
115 | // Handle error
116 | return
117 | case msg := <-mc:
118 | log.Printf("Received: %s", kucoin.ToJsonString(msg))
119 | i++
120 | if i == 5 {
121 | log.Println("Unsubscribe ETH-BTC")
122 | if err = c.Unsubscribe(uch); err != nil {
123 | log.Printf("Error: %s", err.Error())
124 | // Handle error
125 | return
126 | }
127 | }
128 | if i == 10 {
129 | log.Println("Subscribe ETH-BTC")
130 | if err = c.Subscribe(ch2); err != nil {
131 | log.Printf("Error: %s", err.Error())
132 | // Handle error
133 | return
134 | }
135 | }
136 | if i == 15 {
137 | log.Println("Exit subscription")
138 | c.Stop() // Stop subscribing the WebSocket feed
139 | return
140 | }
141 | }
142 | }
143 | }
144 |
145 | func privateWebsocket(ctx context.Context, s *kucoin.ApiService) {
146 | rsp, err := s.WebSocketPrivateToken(ctx)
147 | if err != nil {
148 | // Handle error
149 | return
150 | }
151 |
152 | tk := &kucoin.WebSocketTokenModel{}
153 | //tk.AcceptUserMessage = true
154 | if err := rsp.ReadData(tk); err != nil {
155 | // Handle error
156 | return
157 | }
158 |
159 | c := s.NewWebSocketClient(tk)
160 |
161 | mc, ec, err := c.Connect()
162 | if err != nil {
163 | // Handle error
164 | return
165 | }
166 |
167 | ch1 := kucoin.NewSubscribeMessage("/market/level3:BTC-USDT", false)
168 | ch2 := kucoin.NewSubscribeMessage("/account/balance", false)
169 |
170 | if err := c.Subscribe(ch1, ch2); err != nil {
171 | // Handle error
172 | return
173 | }
174 |
175 | var i = 0
176 | for {
177 | select {
178 | case err := <-ec:
179 | c.Stop() // Stop subscribing the WebSocket feed
180 | log.Printf("Error: %s", err.Error())
181 | // Handle error
182 | return
183 | case msg := <-mc:
184 | log.Printf("Received: %s", kucoin.ToJsonString(msg))
185 | i++
186 | if i == 10 {
187 | log.Println("Subscribe ETH-BTC")
188 | if err = c.Subscribe(ch2); err != nil {
189 | log.Printf("Error: %s", err.Error())
190 | // Handle error
191 | return
192 | }
193 | }
194 | if i == 15 {
195 | log.Println("Exit subscription")
196 | c.Stop() // Stop subscribing the WebSocket feed
197 | return
198 | }
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/currency.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "net/http"
7 | )
8 |
9 | // A CurrencyModel represents a model of known currency.
10 | type CurrencyModel struct {
11 | Name string `json:"name"`
12 | Currency string `json:"currency"`
13 | FullName string `json:"fullName"`
14 | Precision uint8 `json:"precision"`
15 | Confirms int64 `json:"confirms"`
16 | ContractAddress string `json:"contractAddress"`
17 | WithdrawalMinSize string `json:"withdrawalMinSize"`
18 | WithdrawalMinFee string `json:"withdrawalMinFee"`
19 | IsWithdrawEnabled bool `json:"isWithdrawEnabled"`
20 | IsDepositEnabled bool `json:"isDepositEnabled"`
21 | IsMarginEnabled bool `json:"isMarginEnabled"`
22 | IsDebitEnabled bool `json:"isDebitEnabled"`
23 | }
24 |
25 | // A CurrenciesModel is the set of *CurrencyModel.
26 | type CurrenciesModel []*CurrencyModel
27 |
28 | // Currencies returns a list of known currencies.
29 | func (as *ApiService) Currencies(ctx context.Context) (*ApiResponse, error) {
30 | req := NewRequest(http.MethodGet, "/api/v1/currencies", nil)
31 | return as.Call(ctx, req)
32 | }
33 |
34 | // Currency returns the details of the currency.
35 | // Deprecated: Use CurrencyV2 instead.
36 | func (as *ApiService) Currency(ctx context.Context, currency string, chain string) (*ApiResponse, error) {
37 | params := map[string]string{}
38 | if chain != "" {
39 | params["chain"] = chain
40 | }
41 | req := NewRequest(http.MethodGet, "/api/v1/currencies/"+currency, params)
42 | return as.Call(ctx, req)
43 | }
44 |
45 | // ChainsModel Chains Model
46 | type ChainsModel struct {
47 | ChainName string `json:"chainName"`
48 | WithdrawalMinSize string `json:"withdrawalMinSize"`
49 | WithdrawalMinFee string `json:"withdrawalMinFee"`
50 | IsWithdrawEnabled bool `json:"isWithdrawEnabled"`
51 | IsDepositEnabled bool `json:"isDepositEnabled"`
52 | Confirms int64 `json:"confirms"`
53 | ContractAddress string `json:"contractAddress"`
54 | ChainId string `json:"chainId"`
55 | }
56 |
57 | // CurrencyV2Model CurrencyV2 Model
58 | type CurrencyV2Model struct {
59 | Name string `json:"name"`
60 | Currency string `json:"currency"`
61 | FullName string `json:"fullName"`
62 | Precision uint8 `json:"precision"`
63 | Confirms int64 `json:"confirms"`
64 | ContractAddress string `json:"contractAddress"`
65 | IsMarginEnabled bool `json:"isMarginEnabled"`
66 | IsDebitEnabled bool `json:"isDebitEnabled"`
67 | Chains []*ChainsModel `json:"chains"`
68 | }
69 |
70 | // CurrencyV2 returns the details of the currency.
71 | func (as *ApiService) CurrencyV2(ctx context.Context, currency string, chain string) (*ApiResponse, error) {
72 | params := map[string]string{}
73 | if chain != "" {
74 | params["chain"] = chain
75 | }
76 | req := NewRequest(http.MethodGet, "/api/v2/currencies/"+currency, params)
77 | return as.Call(ctx, req)
78 | }
79 |
80 | type PricesModel map[string]string
81 |
82 | // Prices returns the fiat prices for currency.
83 | func (as *ApiService) Prices(ctx context.Context, base, currencies string) (*ApiResponse, error) {
84 | params := map[string]string{}
85 | if base != "" {
86 | params["base"] = base
87 | }
88 | if currencies != "" {
89 | params["currencies"] = currencies
90 | }
91 | req := NewRequest(http.MethodGet, "/api/v1/prices", params)
92 | return as.Call(ctx, req)
93 | }
94 |
95 | type CurrenciesV3Model []*CurrencyV3Model
96 |
97 | type CurrencyV3Model struct {
98 | Currency string `json:"currency"`
99 | Name string `json:"name"`
100 | FullName string `json:"fullName"`
101 | Precision int32 `json:"precision"`
102 | Confirms int32 `json:"confirms"`
103 | ContractAddress string `json:"contractAddress"`
104 | IsMarginEnabled bool `json:"isMarginEnabled"`
105 | IsDebitEnabled bool `json:"isDebitEnabled"`
106 | Chains []struct {
107 | ChainName string `json:"chainName"`
108 | WithdrawalMinFee json.Number `json:"withdrawalMinFee"`
109 | WithdrawalMinSize json.Number `json:"withdrawalMinSize"`
110 | WithdrawFeeRate json.Number `json:"withdrawFeeRate"`
111 | DepositMinSize json.Number `json:"depositMinSize"`
112 | IsWithdrawEnabled bool `json:"isWithdrawEnabled"`
113 | IsDepositEnabled bool `json:"isDepositEnabled"`
114 | PreConfirms int32 `json:"preConfirms"`
115 | ContractAddress string `json:"contractAddress"`
116 | ChainId string `json:"chainId"`
117 | Confirms int32 `json:"confirms"`
118 | } `json:"chains"`
119 | }
120 |
121 | func (as *ApiService) CurrenciesV3(ctx context.Context) (*ApiResponse, error) {
122 | req := NewRequest(http.MethodGet, "/api/v3/currencies/", nil)
123 | return as.Call(ctx, req)
124 | }
125 |
126 | // CurrencyInfoV3 Request via this endpoint to get the currency details of a specified currency
127 | func (as *ApiService) CurrencyInfoV3(ctx context.Context, currency string) (*ApiResponse, error) {
128 | req := NewRequest(http.MethodGet, "/api/v3/currencies/"+currency, nil)
129 | return as.Call(ctx, req)
130 | }
131 |
--------------------------------------------------------------------------------
/earn_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestApiService_CreateEarnOrder(t *testing.T) {
9 |
10 | s := NewApiServiceFromEnv()
11 | p := &CreateEarnOrderReq{
12 | ProductId: "2212",
13 | Amount: "10",
14 | AccountType: "TRADE",
15 | }
16 | rsp, err := s.CreateEarnOrder(context.Background(), p)
17 | if err != nil {
18 | t.Fatal(err)
19 | }
20 | o := &CreateEarnOrderRes{}
21 | if err := rsp.ReadData(o); err != nil {
22 | t.Fatal(err)
23 | }
24 | t.Log(ToJsonString(o))
25 | }
26 |
27 | func TestApiService_DeleteEarnOrder(t *testing.T) {
28 |
29 | s := NewApiServiceFromEnv()
30 | rsp, err := s.DeleteEarnOrder(context.Background(), "2596986", "10", "TRADE", "1")
31 | if err != nil {
32 | t.Fatal(err)
33 | }
34 | o := &DeleteEarnOrderRes{}
35 | if err := rsp.ReadData(o); err != nil {
36 | t.Fatal(err)
37 | }
38 | t.Log(ToJsonString(o))
39 | }
40 |
41 | func TestApiService_QueryOTCLoanInfo(t *testing.T) {
42 |
43 | s := NewApiServiceFromEnv()
44 | rsp, err := s.QueryOTCLoanInfo(context.Background())
45 | if err != nil {
46 | t.Fatal(err)
47 | }
48 | o := &OTCLoanModel{}
49 | if err := rsp.ReadData(o); err != nil {
50 | t.Fatal(err)
51 | }
52 | for _, order := range o.Orders {
53 | t.Log(ToJsonString(order))
54 | }
55 |
56 | t.Log(ToJsonString(o.Ltv))
57 | t.Log(ToJsonString(o.ParentUid))
58 | t.Log(ToJsonString(o.TotalMarginAmount))
59 | t.Log(ToJsonString(o.TransferMarginAmount))
60 | for _, margin := range o.Margins {
61 | t.Log(ToJsonString(margin))
62 | }
63 | }
64 |
65 | func TestApiService_RedeemPreview(t *testing.T) {
66 |
67 | s := NewApiServiceFromEnv()
68 | rsp, err := s.RedeemPreview(context.Background(), "2596986", "TRADE")
69 | if err != nil {
70 | t.Fatal(err)
71 | }
72 |
73 | o := &RedeemPreviewModel{}
74 | if err := rsp.ReadData(&o); err != nil {
75 | t.Fatal(err)
76 | }
77 | t.Log(ToJsonString(o))
78 | }
79 |
80 | func TestApiService_QuerySavingProducts(t *testing.T) {
81 | s := NewApiServiceFromEnv()
82 | rsp, err := s.QuerySavingProducts(context.Background(), "")
83 | if err != nil {
84 | t.Fatal(err)
85 | }
86 |
87 | os := EarnProductsRes{}
88 | if err := rsp.ReadData(&os); err != nil {
89 | t.Fatal(err)
90 | }
91 | for _, o := range os {
92 | t.Log(ToJsonString(o))
93 | switch {
94 | case o.Id == "":
95 | t.Error("Empty key 'id'")
96 | case o.Currency == "":
97 | t.Error("Empty key 'Currency'")
98 | case o.Category == "":
99 | t.Error("Empty key 'Category'")
100 | case o.Type == "":
101 | t.Error("Empty key 'Type'")
102 | case o.Status == "":
103 | t.Error("Empty key 'Status'")
104 | }
105 | }
106 | }
107 |
108 | func TestApiService_QueryPromotionProducts(t *testing.T) {
109 | s := NewApiServiceFromEnv()
110 | rsp, err := s.QueryPromotionProducts(context.Background(), "USDT")
111 | if err != nil {
112 | t.Fatal(err)
113 | }
114 |
115 | os := EarnProductsRes{}
116 | if err := rsp.ReadData(&os); err != nil {
117 | t.Fatal(err)
118 | }
119 | for _, o := range os {
120 | t.Log(ToJsonString(o))
121 | switch {
122 | case o.Id == "":
123 | t.Error("Empty key 'id'")
124 | case o.Currency == "":
125 | t.Error("Empty key 'Currency'")
126 | case o.Category == "":
127 | t.Error("Empty key 'Category'")
128 | case o.Type == "":
129 | t.Error("Empty key 'Type'")
130 | case o.Status == "":
131 | t.Error("Empty key 'Status'")
132 | }
133 | }
134 | }
135 |
136 | func TestApiService_QueryKCSStakingProducts(t *testing.T) {
137 | s := NewApiServiceFromEnv()
138 | rsp, err := s.QueryKCSStakingProducts(context.Background(), "KCS")
139 | if err != nil {
140 | t.Fatal(err)
141 | }
142 |
143 | os := EarnProductsRes{}
144 | if err := rsp.ReadData(&os); err != nil {
145 | t.Fatal(err)
146 | }
147 | for _, o := range os {
148 | t.Log(ToJsonString(o))
149 | switch {
150 | case o.Id == "":
151 | t.Error("Empty key 'id'")
152 | case o.Currency == "":
153 | t.Error("Empty key 'Currency'")
154 | case o.Category == "":
155 | t.Error("Empty key 'Category'")
156 | case o.Type == "":
157 | t.Error("Empty key 'Type'")
158 | case o.Status == "":
159 | t.Error("Empty key 'Status'")
160 | }
161 | }
162 | }
163 |
164 | func TestApiService_QueryStakingProducts(t *testing.T) {
165 | s := NewApiServiceFromEnv()
166 | rsp, err := s.QueryStakingProducts(context.Background(), "")
167 | if err != nil {
168 | t.Fatal(err)
169 | }
170 |
171 | os := EarnProductsRes{}
172 | if err := rsp.ReadData(&os); err != nil {
173 | t.Fatal(err)
174 | }
175 | for _, o := range os {
176 | t.Log(ToJsonString(o))
177 | switch {
178 | case o.Id == "":
179 | t.Error("Empty key 'id'")
180 | case o.Currency == "":
181 | t.Error("Empty key 'Currency'")
182 | case o.Category == "":
183 | t.Error("Empty key 'Category'")
184 | case o.Type == "":
185 | t.Error("Empty key 'Type'")
186 | case o.Status == "":
187 | t.Error("Empty key 'Status'")
188 | }
189 | }
190 | }
191 |
192 | func TestApiService_QueryETHProducts(t *testing.T) {
193 | s := NewApiServiceFromEnv()
194 | rsp, err := s.QueryETHProducts(context.Background(), "")
195 | if err != nil {
196 | t.Fatal(err)
197 | }
198 |
199 | os := EarnProductsRes{}
200 | if err := rsp.ReadData(&os); err != nil {
201 | t.Fatal(err)
202 | }
203 | for _, o := range os {
204 | t.Log(ToJsonString(o))
205 | switch {
206 | case o.Id == "":
207 | t.Error("Empty key 'id'")
208 | case o.Currency == "":
209 | t.Error("Empty key 'Currency'")
210 | case o.Category == "":
211 | t.Error("Empty key 'Category'")
212 | case o.Type == "":
213 | t.Error("Empty key 'Type'")
214 | case o.Status == "":
215 | t.Error("Empty key 'Status'")
216 | }
217 | }
218 | }
219 |
220 | func TestApiService_QueryHoldAssets(t *testing.T) {
221 | s := NewApiServiceFromEnv()
222 |
223 | p := &PaginationParam{CurrentPage: 1, PageSize: 10}
224 | rsp, err := s.QueryHoldAssets(context.Background(), "", "", "", p)
225 | if err != nil {
226 | t.Fatal(err)
227 | }
228 |
229 | os := HoldAssetsRes{}
230 | if _, err := rsp.ReadPaginationData(&os); err != nil {
231 | t.Fatal(err)
232 | }
233 | if len(os) == 0 {
234 | t.SkipNow()
235 | }
236 |
237 | for _, o := range os {
238 | t.Log(ToJsonString(o))
239 | }
240 | }
241 |
242 | func TestApiService_QueryOTCLoanAccountsInfo(t *testing.T) {
243 | s := NewApiServiceFromEnv()
244 |
245 | rsp, err := s.QueryOTCLoanAccountsInfo(context.Background())
246 | if err != nil {
247 | t.Fatal(err)
248 | }
249 |
250 | os := OTCAccountsModel{}
251 | if err := rsp.ReadData(&os); err != nil {
252 | t.Fatal(err)
253 | }
254 | if len(os) == 0 {
255 | t.SkipNow()
256 | }
257 |
258 | for _, o := range os {
259 | t.Log(ToJsonString(o))
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/api.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package kucoin provides two kinds of APIs: `RESTful API` and `WebSocket feed`.
3 | The official document: https://docs.kucoin.com
4 | */
5 | package kucoin
6 |
7 | import (
8 | "bytes"
9 | "context"
10 | "errors"
11 | "fmt"
12 | "log"
13 | "os"
14 | "runtime"
15 | "time"
16 |
17 | "github.com/sirupsen/logrus"
18 | )
19 |
20 | var (
21 | // Version is SDK version.
22 | Version = "1.2.10"
23 | // DebugMode will record the logs of API and WebSocket to files in the directory "kucoin.LogDirectory" according to the minimum log level "kucoin.LogLevel".
24 | DebugMode = os.Getenv("API_DEBUG_MODE") == "1"
25 | )
26 |
27 | func init() {
28 | // Initialize the logging component by default
29 | logrus.SetLevel(logrus.DebugLevel)
30 | if runtime.GOOS == "windows" {
31 | SetLoggerDirectory("tmp")
32 | } else {
33 | SetLoggerDirectory("/tmp")
34 | }
35 | }
36 |
37 | // SetLoggerDirectory sets the directory for logrus output.
38 | func SetLoggerDirectory(directory string) {
39 | var logFile string
40 | if !DebugMode {
41 | logFile = os.DevNull
42 | } else {
43 | logFile = fmt.Sprintf("%s/kucoin-sdk-%s.log", directory, time.Now().Format("2006-01-02"))
44 | }
45 | logWriter, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
46 | if err != nil {
47 | log.Panicf("Open file failed: %s", err.Error())
48 | }
49 | logrus.SetOutput(logWriter)
50 | }
51 |
52 | // An ApiService provides a HTTP client and a signer to make a HTTP request with the signature to KuCoin API.
53 | type ApiService struct {
54 | apiBaseURI string
55 | apiKey string
56 | apiSecret string
57 | apiPassphrase string
58 | apiSkipVerifyTls bool
59 | requester Requester
60 | signer Signer
61 | apiKeyVersion string
62 | }
63 |
64 | // ProductionApiBaseURI is api base uri for production.
65 | const ProductionApiBaseURI = "https://api.kucoin.com"
66 |
67 | /**
68 | Note: about api key version
69 | To reinforce the security of the API, KuCoin upgraded the API key to version 2.0, the validation logic has also been changed. It is recommended to create(https://www.kucoin.com/account/api) and update your API key to version 2.0. The API key of version 1.0 will be still valid until May 1, 2021.
70 | */
71 |
72 | // ApiKeyVersionV1 is v1 api key version
73 | const ApiKeyVersionV1 = "1"
74 |
75 | // ApiKeyVersionV2 is v2 api key version
76 | const ApiKeyVersionV2 = "2"
77 |
78 | // An ApiServiceOption is a option parameter to create the instance of ApiService.
79 | type ApiServiceOption func(service *ApiService)
80 |
81 | // ApiBaseURIOption creates a instance of ApiServiceOption about apiBaseURI.
82 | func ApiBaseURIOption(uri string) ApiServiceOption {
83 | return func(service *ApiService) {
84 | service.apiBaseURI = uri
85 | }
86 | }
87 |
88 | // ApiKeyOption creates a instance of ApiServiceOption about apiKey.
89 | func ApiKeyOption(key string) ApiServiceOption {
90 | return func(service *ApiService) {
91 | service.apiKey = key
92 | }
93 | }
94 |
95 | // ApiSecretOption creates a instance of ApiServiceOption about apiSecret.
96 | func ApiSecretOption(secret string) ApiServiceOption {
97 | return func(service *ApiService) {
98 | service.apiSecret = secret
99 | }
100 | }
101 |
102 | // ApiPassPhraseOption creates a instance of ApiServiceOption about apiPassPhrase.
103 | func ApiPassPhraseOption(passPhrase string) ApiServiceOption {
104 | return func(service *ApiService) {
105 | service.apiPassphrase = passPhrase
106 | }
107 | }
108 |
109 | // ApiSkipVerifyTlsOption creates a instance of ApiServiceOption about apiSkipVerifyTls.
110 | func ApiSkipVerifyTlsOption(skipVerifyTls bool) ApiServiceOption {
111 | return func(service *ApiService) {
112 | service.apiSkipVerifyTls = skipVerifyTls
113 | }
114 | }
115 |
116 | // ApiRequesterOption creates a instance of ApiServiceOption about requester.
117 | func ApiRequesterOption(requester Requester) ApiServiceOption {
118 | return func(service *ApiService) {
119 | service.requester = requester
120 | }
121 | }
122 |
123 | // ApiKeyVersionOption creates a instance of ApiServiceOption about apiKeyVersion.
124 | func ApiKeyVersionOption(apiKeyVersion string) ApiServiceOption {
125 | return func(service *ApiService) {
126 | service.apiKeyVersion = apiKeyVersion
127 | }
128 | }
129 |
130 | // NewApiService creates a instance of ApiService by passing ApiServiceOptions, then you can call methods.
131 | func NewApiService(opts ...ApiServiceOption) *ApiService {
132 | as := &ApiService{requester: &BasicRequester{}}
133 | for _, opt := range opts {
134 | opt(as)
135 | }
136 | if as.apiBaseURI == "" {
137 | as.apiBaseURI = ProductionApiBaseURI
138 | }
139 |
140 | if as.apiKeyVersion == "" {
141 | as.apiKeyVersion = ApiKeyVersionV1
142 | }
143 |
144 | if as.apiKey != "" {
145 | if as.apiKeyVersion == ApiKeyVersionV1 {
146 | as.signer = NewKcSigner(as.apiKey, as.apiSecret, as.apiPassphrase)
147 | } else {
148 | as.signer = NewKcSignerV2(as.apiKey, as.apiSecret, as.apiPassphrase)
149 | }
150 | }
151 |
152 | return as
153 | }
154 |
155 | // NewApiServiceFromEnv creates a instance of ApiService by environmental variables such as `API_BASE_URI` `API_KEY` `API_SECRET` `API_PASSPHRASE`, then you can call the methods of ApiService.
156 | func NewApiServiceFromEnv() *ApiService {
157 | return NewApiService(
158 | ApiBaseURIOption(os.Getenv("API_BASE_URI")),
159 | ApiKeyOption(os.Getenv("API_KEY")),
160 | ApiSecretOption(os.Getenv("API_SECRET")),
161 | ApiPassPhraseOption(os.Getenv("API_PASSPHRASE")),
162 | ApiSkipVerifyTlsOption(os.Getenv("API_SKIP_VERIFY_TLS") == "1"),
163 | ApiKeyVersionOption(os.Getenv("API_KEY_VERSION")),
164 | )
165 | }
166 |
167 | // Call calls the API by passing *Request and returns *ApiResponse.
168 | func (as *ApiService) Call(ctx context.Context, request *Request) (*ApiResponse, error) {
169 | defer func() {
170 | if err := recover(); err != nil {
171 | log.Println("[[Recovery] panic recovered:", err)
172 | }
173 | }()
174 |
175 | request.BaseURI = as.apiBaseURI
176 | request.SkipVerifyTls = as.apiSkipVerifyTls
177 | request.Header.Set("Content-Type", "application/json")
178 | request.Header.Set("User-Agent", "KuCoin-Go-SDK/"+Version)
179 | if as.signer != nil {
180 | var b bytes.Buffer
181 | b.WriteString(request.Method)
182 | b.WriteString(request.RequestURI())
183 | b.Write(request.Body)
184 | h := as.signer.(*KcSigner).Headers(b.String())
185 | for k, v := range h {
186 | request.Header.Set(k, v)
187 | }
188 | }
189 |
190 | rsp, err := as.requester.Request(ctx, request, request.Timeout)
191 | if err != nil {
192 | return nil, err
193 | }
194 |
195 | ar := &ApiResponse{response: rsp}
196 | if err := rsp.ReadJsonBody(ar); err != nil {
197 | rb, _ := rsp.ReadBody()
198 | m := fmt.Sprintf("[Parse]Failure: parse JSON body failed because %s, %s %s with body=%s, respond code=%d body=%s",
199 | err.Error(),
200 | rsp.request.Method,
201 | rsp.request.RequestURI(),
202 | string(rsp.request.Body),
203 | rsp.StatusCode,
204 | string(rb),
205 | )
206 | return ar, errors.New(m)
207 | }
208 | return ar, nil
209 | }
210 |
--------------------------------------------------------------------------------
/http.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "crypto/tls"
7 | "encoding/json"
8 | "errors"
9 | "fmt"
10 | "io/ioutil"
11 | "log"
12 | "net/http"
13 | "net/http/httputil"
14 | "net/url"
15 | "strings"
16 | "time"
17 |
18 | "github.com/sirupsen/logrus"
19 | )
20 |
21 | // A Request represents a HTTP request.
22 | type Request struct {
23 | fullURL string
24 | requestURI string
25 | BaseURI string
26 | Method string
27 | Path string
28 | Query url.Values
29 | Body []byte
30 | Header http.Header
31 | Timeout time.Duration
32 | SkipVerifyTls bool
33 | }
34 |
35 | // NewRequest creates a instance of Request.
36 | func NewRequest(method, path string, params interface{}) *Request {
37 | r := &Request{
38 | Method: method,
39 | Path: path,
40 | Query: make(url.Values),
41 | Header: make(http.Header),
42 | Body: []byte{},
43 | Timeout: 30 * time.Second,
44 | }
45 | if r.Path == "" {
46 | r.Path = "/"
47 | }
48 | if r.Method == "" {
49 | r.Method = http.MethodGet
50 | }
51 | r.addParams(params)
52 | return r
53 | }
54 |
55 | func (r *Request) addParams(p interface{}) {
56 | if p == nil {
57 | return
58 | }
59 |
60 | switch r.Method {
61 | case http.MethodGet, http.MethodDelete:
62 | if v, ok := p.(url.Values); ok {
63 | r.Query = v
64 | return
65 | }
66 | for key, value := range p.(map[string]string) {
67 | r.Query.Add(key, value)
68 | }
69 | default:
70 | b, err := json.Marshal(p)
71 | if err != nil {
72 | log.Panic("Cannot marshal params to JSON string:", err.Error())
73 | }
74 | r.Body = b
75 | }
76 | }
77 |
78 | // RequestURI returns the request uri.
79 | func (r *Request) RequestURI() string {
80 | if r.requestURI != "" {
81 | return r.requestURI
82 | }
83 |
84 | fu := r.FullURL()
85 | u, err := url.Parse(fu)
86 | if err != nil {
87 | r.requestURI = "/"
88 | } else {
89 | r.requestURI = u.RequestURI()
90 | }
91 | return r.requestURI
92 | }
93 |
94 | // FullURL returns the full url.
95 | func (r *Request) FullURL() string {
96 | if r.fullURL != "" {
97 | return r.fullURL
98 | }
99 | r.fullURL = fmt.Sprintf("%s%s", r.BaseURI, r.Path)
100 | if len(r.Query) > 0 {
101 | if strings.Contains(r.fullURL, "?") {
102 | r.fullURL += "&" + r.Query.Encode()
103 | } else {
104 | r.fullURL += "?" + r.Query.Encode()
105 | }
106 | }
107 | return r.fullURL
108 | }
109 |
110 | // HttpRequest creates a instance of *http.Request.
111 | func (r *Request) HttpRequest() (*http.Request, error) {
112 | req, err := http.NewRequest(r.Method, r.FullURL(), bytes.NewBuffer(r.Body))
113 | if err != nil {
114 | return nil, err
115 | }
116 |
117 | for key, values := range r.Header {
118 | for _, value := range values {
119 | req.Header.Add(key, value)
120 | }
121 | }
122 |
123 | return req, nil
124 | }
125 |
126 | // Requester contains Request() method, can launch a http request.
127 | type Requester interface {
128 | Request(ctx context.Context, request *Request, timeout time.Duration) (*Response, error)
129 | }
130 |
131 | // A BasicRequester represents a basic implement of Requester by http.Client.
132 | type BasicRequester struct {
133 | }
134 |
135 | // Request makes a http request.
136 | func (br *BasicRequester) Request(ctx context.Context, request *Request, timeout time.Duration) (*Response, error) {
137 | tr := http.DefaultTransport
138 | tc := tr.(*http.Transport).TLSClientConfig
139 | if tc == nil {
140 | tc = &tls.Config{InsecureSkipVerify: request.SkipVerifyTls}
141 | } else {
142 | tc.InsecureSkipVerify = request.SkipVerifyTls
143 | }
144 |
145 | cli := http.DefaultClient
146 | cli.Transport, cli.Timeout = tr, timeout
147 |
148 | req, err := request.HttpRequest()
149 | if err != nil {
150 | return nil, err
151 | }
152 | // Prevent re-use of TCP connections
153 | // req.Close = true
154 |
155 | rid := time.Now().UnixNano()
156 |
157 | if DebugMode {
158 | dump, _ := httputil.DumpRequest(req, true)
159 | logrus.Debugf("Sent a HTTP request#%d: %s", rid, string(dump))
160 | }
161 |
162 | rsp, err := cli.Do(req.WithContext(ctx))
163 | if err != nil {
164 | return nil, err
165 | }
166 |
167 | if DebugMode {
168 | dump, _ := httputil.DumpResponse(rsp, true)
169 | logrus.Debugf("Received a HTTP response#%d: %s", rid, string(dump))
170 | }
171 |
172 | return NewResponse(
173 | request,
174 | rsp,
175 | nil,
176 | ), nil
177 | }
178 |
179 | // A Response represents a HTTP response.
180 | type Response struct {
181 | request *Request
182 | *http.Response
183 | body []byte
184 | }
185 |
186 | // NewResponse Creates a new Response
187 | func NewResponse(
188 | request *Request,
189 | response *http.Response,
190 | body []byte,
191 | ) *Response {
192 | return &Response{
193 | request: request,
194 | Response: response,
195 | body: body,
196 | }
197 | }
198 |
199 | // ReadBody read the response data, then return it.
200 | func (r *Response) ReadBody() ([]byte, error) {
201 | if r.body != nil {
202 | return r.body, nil
203 | }
204 |
205 | r.body = make([]byte, 0)
206 | defer r.Body.Close()
207 | b, err := ioutil.ReadAll(r.Body)
208 | if err != nil {
209 | return nil, err
210 | }
211 | r.body = b
212 | return r.body, nil
213 | }
214 |
215 | // ReadJsonBody read the response data as JSON into v.
216 | func (r *Response) ReadJsonBody(v interface{}) error {
217 | b, err := r.ReadBody()
218 | if err != nil {
219 | return err
220 | }
221 | return json.Unmarshal(b, v)
222 | }
223 |
224 | // The predefined API codes
225 | const (
226 | ApiSuccess = "200000"
227 | )
228 |
229 | // An ApiResponse represents a API response wrapped Response.
230 | type ApiResponse struct {
231 | response *Response
232 | Code string `json:"code"`
233 | RawData json.RawMessage `json:"data"` // delay parsing
234 | Message string `json:"msg"`
235 | }
236 |
237 | // HttpSuccessful judges the success of http.
238 | func (ar *ApiResponse) HttpSuccessful() bool {
239 | return ar.response.StatusCode == http.StatusOK
240 | }
241 |
242 | // ApiSuccessful judges the success of API.
243 | func (ar *ApiResponse) ApiSuccessful() bool {
244 | return ar.Code == ApiSuccess
245 | }
246 |
247 | // ReadData read the api response `data` as JSON into v.
248 | func (ar *ApiResponse) ReadData(v interface{}) error {
249 | if !ar.HttpSuccessful() {
250 | rsb, _ := ar.response.ReadBody()
251 | m := fmt.Sprintf("[HTTP]Failure: status code is NOT 200, %s %s with body=%s, respond code=%d body=%s",
252 | ar.response.request.Method,
253 | ar.response.request.RequestURI(),
254 | string(ar.response.request.Body),
255 | ar.response.StatusCode,
256 | string(rsb),
257 | )
258 | return errors.New(m)
259 | }
260 |
261 | if !ar.ApiSuccessful() {
262 | m := fmt.Sprintf("[API]Failure: api code is NOT %s, %s %s with body=%s, respond code=%s message=\"%s\" data=%s",
263 | ApiSuccess,
264 | ar.response.request.Method,
265 | ar.response.request.RequestURI(),
266 | string(ar.response.request.Body),
267 | ar.Code,
268 | ar.Message,
269 | string(ar.RawData),
270 | )
271 | return errors.New(m)
272 | }
273 | // when input parameter v is nil, read nothing and return nil
274 | if v == nil {
275 | return nil
276 | }
277 |
278 | if len(ar.RawData) == 0 {
279 | m := fmt.Sprintf("[API]Failure: try to read empty data, %s %s with body=%s, respond code=%s message=\"%s\" data=%s",
280 | ar.response.request.Method,
281 | ar.response.request.RequestURI(),
282 | string(ar.response.request.Body),
283 | ar.Code,
284 | ar.Message,
285 | string(ar.RawData),
286 | )
287 | return errors.New(m)
288 | }
289 |
290 | return json.Unmarshal(ar.RawData, v)
291 | }
292 |
293 | // ReadPaginationData read the data `items` as JSON into v, and returns *PaginationModel.
294 | func (ar *ApiResponse) ReadPaginationData(v interface{}) (*PaginationModel, error) {
295 | p := &PaginationModel{}
296 | if err := ar.ReadData(p); err != nil {
297 | return nil, err
298 | }
299 | if err := p.ReadItems(v); err != nil {
300 | return p, err
301 | }
302 | return p, nil
303 | }
304 |
--------------------------------------------------------------------------------
/earn.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "net/http"
7 | )
8 |
9 | type CreateEarnOrderReq struct {
10 | ProductId string `json:"productId"`
11 | Amount string `json:"amount"`
12 | AccountType string `json:"accountType"`
13 | }
14 |
15 | type CreateEarnOrderRes struct {
16 | OrderId string `json:"orderId"`
17 | OrderTxId string `json:"orderTxId"`
18 | }
19 |
20 | // CreateEarnOrder subscribing to fixed income products. If the subscription fails, it returns the corresponding error code.
21 | func (as *ApiService) CreateEarnOrder(ctx context.Context, createEarnOrderReq *CreateEarnOrderReq) (*ApiResponse, error) {
22 | req := NewRequest(http.MethodPost, "/api/v1/earn/orders", createEarnOrderReq)
23 | return as.Call(ctx, req)
24 | }
25 |
26 | type DeleteEarnOrderRes struct {
27 | OrderTxId string `json:"orderTxId"`
28 | DeliverTime int64 `json:"deliverTime"`
29 | Status string `json:"status"`
30 | Amount string `json:"amount"`
31 | }
32 |
33 | // DeleteEarnOrder initiating redemption by holding ID.
34 | func (as *ApiService) DeleteEarnOrder(ctx context.Context, orderId, amount, fromAccountType, confirmPunishRedeem string) (*ApiResponse, error) {
35 | p := map[string]string{
36 | "orderId": orderId,
37 | "fromAccountType": fromAccountType,
38 | "confirmPunishRedeem": confirmPunishRedeem,
39 | "amount": amount,
40 | }
41 | req := NewRequest(http.MethodDelete, "/api/v1/earn/orders", p)
42 | return as.Call(ctx, req)
43 | }
44 |
45 | type RedeemPreviewModel struct {
46 | Currency string `json:"currency"`
47 | RedeemAmount json.Number `json:"redeemAmount"`
48 | PenaltyInterestAmount json.Number `json:"penaltyInterestAmount"`
49 | RedeemPeriod int32 `json:"redeemPeriod"`
50 | DeliverTime int64 `json:"deliverTime"`
51 | ManualRedeemable bool `json:"manualRedeemable"`
52 | RedeemAll bool `json:"redeemAll"`
53 | }
54 |
55 | // RedeemPreview retrieves redemption preview information by holding ID
56 | func (as *ApiService) RedeemPreview(ctx context.Context, orderId, fromAccountType string) (*ApiResponse, error) {
57 | p := map[string]string{
58 | "orderId": orderId,
59 | "fromAccountType": fromAccountType,
60 | }
61 | req := NewRequest(http.MethodGet, "/api/v1/earn/redeem-preview", p)
62 | return as.Call(ctx, req)
63 | }
64 |
65 | // QuerySavingProducts retrieves savings products.
66 | func (as *ApiService) QuerySavingProducts(ctx context.Context, currency string) (*ApiResponse, error) {
67 | p := map[string]string{
68 | "currency": currency,
69 | }
70 | req := NewRequest(http.MethodGet, "/api/v1/earn/saving/products", p)
71 | return as.Call(ctx, req)
72 | }
73 |
74 | type HoldAssetsRes []*HoldAssetModel
75 |
76 | type HoldAssetModel struct {
77 | OrderId string `json:"orderId"`
78 | ProductId string `json:"productId"`
79 | ProductCategory string `json:"productCategory"`
80 | ProductType string `json:"productType"`
81 | Currency string `json:"currency"`
82 | IncomeCurrency string `json:"incomeCurrency"`
83 | ReturnRate json.Number `json:"returnRate"`
84 | HoldAmount json.Number `json:"holdAmount"`
85 | RedeemedAmount json.Number `json:"redeemedAmount"`
86 | RedeemingAmount json.Number `json:"redeemingAmount"`
87 | LockStartTime int64 `json:"lockStartTime"`
88 | LockEndTime int64 `json:"lockEndTime"`
89 | PurchaseTime int64 `json:"purchaseTime"`
90 | RedeemPeriod int32 `json:"redeemPeriod"`
91 | Status string `json:"status"`
92 | EarlyRedeemSupported int32 `json:"earlyRedeemSupported"`
93 | }
94 |
95 | // QueryHoldAssets retrieves current holding assets of fixed income products
96 | func (as *ApiService) QueryHoldAssets(ctx context.Context, productId, productCategory, currency string, pagination *PaginationParam) (*ApiResponse, error) {
97 | p := map[string]string{
98 | "productId": productId,
99 | "productCategory": productCategory,
100 | "currency": currency,
101 | }
102 | pagination.ReadParam(p)
103 | req := NewRequest(http.MethodGet, "/api/v1/earn/hold-assets", p)
104 | return as.Call(ctx, req)
105 | }
106 |
107 | type EarnProductsRes []*EarnProductModel
108 |
109 | type EarnProductModel struct {
110 | Id string `json:"id"`
111 | Currency string `json:"currency"`
112 | Category string `json:"category"`
113 | Type string `json:"type"`
114 | Precision int32 `json:"precision"`
115 | ProductUpperLimit string `json:"productUpperLimit"`
116 | UserUpperLimit string `json:"userUpperLimit"`
117 | UserLowerLimit string `json:"userLowerLimit"`
118 | RedeemPeriod int `json:"redeemPeriod"`
119 | LockStartTime int64 `json:"lockStartTime"`
120 | LockEndTime int64 `json:"lockEndTime"`
121 | ApplyStartTime int64 `json:"applyStartTime"`
122 | ApplyEndTime int64 `json:"applyEndTime"`
123 | ReturnRate json.Number `json:"returnRate"`
124 | IncomeCurrency string `json:"incomeCurrency"`
125 | EarlyRedeemSupported int32 `json:"earlyRedeemSupported"`
126 | ProductRemainAmount json.Number `json:"productRemainAmount"`
127 | Status string `json:"status"`
128 | RedeemType string `json:"redeemType"`
129 | IncomeReleaseType string `json:"incomeReleaseType"`
130 | InterestDate int64 `json:"interestDate"`
131 | Duration int32 `json:"duration"`
132 | NewUserOnly int32 `json:"newUserOnly"`
133 | }
134 |
135 | // QueryPromotionProducts retrieves limited-time promotion products
136 | func (as *ApiService) QueryPromotionProducts(ctx context.Context, currency string) (*ApiResponse, error) {
137 | p := map[string]string{
138 | "currency": currency,
139 | }
140 | req := NewRequest(http.MethodGet, "/api/v1/earn/promotion/products", p)
141 | return as.Call(ctx, req)
142 | }
143 |
144 | // QueryKCSStakingProducts retrieves KCS Staking products
145 | func (as *ApiService) QueryKCSStakingProducts(ctx context.Context, currency string) (*ApiResponse, error) {
146 | p := map[string]string{
147 | "currency": currency,
148 | }
149 | req := NewRequest(http.MethodGet, "/api/v1/earn/kcs-staking/products", p)
150 | return as.Call(ctx, req)
151 | }
152 |
153 | // QueryStakingProducts retrieves Staking products
154 | func (as *ApiService) QueryStakingProducts(ctx context.Context, currency string) (*ApiResponse, error) {
155 | p := map[string]string{
156 | "currency": currency,
157 | }
158 | req := NewRequest(http.MethodGet, "/api/v1/earn/staking/products", p)
159 | return as.Call(ctx, req)
160 | }
161 |
162 | // QueryETHProducts retrieves ETH Staking products
163 | func (as *ApiService) QueryETHProducts(ctx context.Context, currency string) (*ApiResponse, error) {
164 | p := map[string]string{
165 | "currency": currency,
166 | }
167 | req := NewRequest(http.MethodGet, "/api/v1/earn/eth-staking/products", p)
168 | return as.Call(ctx, req)
169 | }
170 |
171 | type OTCLoanModel struct {
172 | ParentUid string `json:"parentUid"`
173 | Orders []struct {
174 | OrderId string `json:"orderId"`
175 | Currency string `json:"currency"`
176 | Principal json.Number `json:"principal"`
177 | Interest json.Number `json:"interest"`
178 | } `json:"orders"`
179 | Ltv struct {
180 | TransferLtv json.Number `json:"transferLtv"`
181 | OnlyClosePosLtv json.Number `json:"onlyClosePosLtv"`
182 | DelayedLiquidationLtv json.Number `json:"delayedLiquidationLtv"`
183 | InstantLiquidationLtv json.Number `json:"instantLiquidationLtv"`
184 | CurrentLtv json.Number `json:"currentLtv"`
185 | } `json:"ltv"`
186 | TotalMarginAmount json.Number `json:"totalMarginAmount"`
187 | TransferMarginAmount json.Number `json:"transferMarginAmount"`
188 | Margins []struct {
189 | MarginCcy string `json:"marginCcy"`
190 | MarginQty json.Number `json:"marginQty"`
191 | MarginFactor json.Number `json:"marginFactor"`
192 | } `json:"margins"`
193 | }
194 |
195 | // QueryOTCLoanInfo querying accounts that are currently involved in loans.
196 | func (as *ApiService) QueryOTCLoanInfo(ctx context.Context) (*ApiResponse, error) {
197 | req := NewRequest(http.MethodGet, "/api/v1/otc-loan/loan", nil)
198 | return as.Call(ctx, req)
199 | }
200 |
201 | type OTCAccountsModel []*OTCAccountModel
202 |
203 | type OTCAccountModel struct {
204 | Uid string `json:"uid"`
205 | MarginCcy string `json:"marginCcy"`
206 | MarginQty string `json:"marginQty"`
207 | MarginFactor string `json:"marginFactor"`
208 | AccountType string `json:"accountType"`
209 | IsParent bool `json:"isParent"`
210 | }
211 |
212 | // QueryOTCLoanAccountsInfo querying accounts that are currently involved in off-exchange funding and loans.
213 | func (as *ApiService) QueryOTCLoanAccountsInfo(ctx context.Context) (*ApiResponse, error) {
214 | req := NewRequest(http.MethodGet, "/api/v1/otc-loan/accounts", nil)
215 | return as.Call(ctx, req)
216 | }
217 |
--------------------------------------------------------------------------------
/symbol_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestApiService_Symbols(t *testing.T) {
10 | s := NewApiServiceFromEnv()
11 | rsp, err := s.Symbols(context.Background(), "")
12 | if err != nil {
13 | t.Fatal(err)
14 | }
15 | l := SymbolsModel{}
16 | if err := rsp.ReadData(&l); err != nil {
17 | t.Fatal(err)
18 | }
19 | for _, c := range l {
20 | t.Log(ToJsonString(c))
21 | switch {
22 | case c.Name == "":
23 | t.Error("Empty key 'name'")
24 | case c.Symbol == "":
25 | t.Error("Empty key 'symbol'")
26 | case c.BaseCurrency == "":
27 | t.Error("Empty key 'baseCurrency'")
28 | case c.QuoteCurrency == "":
29 | t.Error("Empty key 'quoteCurrency'")
30 | case c.BaseMinSize == "":
31 | t.Error("Empty key 'baseMinSize'")
32 | case c.QuoteMinSize == "":
33 | t.Error("Empty key 'quoteMinSize'")
34 | case c.BaseMaxSize == "":
35 | t.Error("Empty key 'baseMaxSize'")
36 | case c.QuoteMaxSize == "":
37 | t.Error("Empty key 'quoteMaxSize'")
38 | case c.BaseIncrement == "":
39 | t.Error("Empty key 'baseIncrement'")
40 | case c.QuoteIncrement == "":
41 | t.Error("Empty key 'quoteIncrement'")
42 | case c.FeeCurrency == "":
43 | t.Error("Empty key 'feeCurrency'")
44 | case c.PriceIncrement == "":
45 | t.Error("Empty key 'priceIncrement'")
46 | }
47 | }
48 | }
49 |
50 | func TestApiService_TickerLevel1(t *testing.T) {
51 | s := NewApiServiceFromEnv()
52 | rsp, err := s.TickerLevel1(context.Background(), "ETH-BTC")
53 | if err != nil {
54 | t.Fatal(err)
55 | }
56 | tk := &TickerLevel1Model{}
57 | if err := rsp.ReadData(tk); err != nil {
58 | t.Fatal(err)
59 | }
60 | t.Log(ToJsonString(tk))
61 | switch {
62 | case tk.Sequence == "":
63 | t.Error("Empty key 'sequence'")
64 | case tk.Price == "":
65 | t.Error("Empty key 'price'")
66 | case tk.Size == "":
67 | t.Error("Empty key 'size'")
68 | case tk.BestBid == "":
69 | t.Error("Empty key 'bestBid'")
70 | case tk.BestBidSize == "":
71 | t.Error("Empty key 'bestBidSize'")
72 | case tk.BestAsk == "":
73 | t.Error("Empty key 'bestAsk'")
74 | case tk.BestAskSize == "":
75 | t.Error("Empty key 'bestAskSize'")
76 | }
77 | }
78 |
79 | func TestApiService_Tickers(t *testing.T) {
80 | s := NewApiServiceFromEnv()
81 | rsp, err := s.Tickers(context.Background())
82 | if err != nil {
83 | t.Fatal(err)
84 | }
85 | ts := &TickersResponseModel{}
86 | if err := rsp.ReadData(ts); err != nil {
87 | t.Fatal(err)
88 | }
89 | if ts.Time == 0 {
90 | t.Error("Empty key 'time'")
91 | }
92 | for _, tk := range ts.Tickers {
93 | switch {
94 | case tk.Symbol == "":
95 | t.Error("Empty key 'symbol'")
96 | case tk.Vol == "":
97 | t.Error("Empty key 'vol'")
98 | case tk.ChangeRate == "":
99 | t.Error("Empty key 'changeRate'")
100 | //case tk.Buy == "":
101 | // t.Error("Empty key 'buy'")
102 | //case tk.Sell == "":
103 | // t.Error("Empty key 'sell'")
104 | //case tk.Last == "":
105 | // t.Error("Empty key 'last'")
106 | }
107 | }
108 | }
109 |
110 | func TestApiService_Stats24hr(t *testing.T) {
111 | s := NewApiServiceFromEnv()
112 | rsp, err := s.Stats24hr(context.Background(), "ETH-BTC")
113 | if err != nil {
114 | t.Fatal(err)
115 | }
116 | st := &Stats24hrModel{}
117 | if err := rsp.ReadData(st); err != nil {
118 | t.Fatal(err)
119 | }
120 | t.Log(ToJsonString(st))
121 | switch {
122 | case st.Symbol == "":
123 | t.Error("Empty key 'symbol'")
124 | case st.ChangeRate == "":
125 | t.Error("Empty key 'changRate'")
126 | }
127 | }
128 |
129 | func TestApiService_Markets(t *testing.T) {
130 | s := NewApiServiceFromEnv()
131 | rsp, err := s.Markets(context.Background())
132 | if err != nil {
133 | t.Fatal(err)
134 | }
135 | ms := MarketsModel{}
136 |
137 | if err := rsp.ReadData(&ms); err != nil {
138 | t.Fatal(err)
139 | }
140 | t.Log(ToJsonString(ms))
141 | if len(ms) == 0 {
142 | t.Error("Empty markets")
143 | }
144 | }
145 |
146 | func TestApiService_AggregatedPartOrderBook(t *testing.T) {
147 | s := NewApiServiceFromEnv()
148 | rsp, err := s.AggregatedPartOrderBook(context.Background(), "ETH-BTC", 100)
149 | if err != nil {
150 | t.Fatal(err)
151 | }
152 | c := &PartOrderBookModel{}
153 | if err := rsp.ReadData(c); err != nil {
154 | t.Fatal(err)
155 | }
156 | t.Log(ToJsonString(c))
157 | switch {
158 | case c.Sequence == "":
159 | t.Error("Empty key 'sequence'")
160 | case len(c.Asks) == 0:
161 | t.Error("Empty key 'asks'")
162 | case len(c.Asks[0]) != 2:
163 | t.Error("Invalid ask length")
164 | case len(c.Bids) == 0:
165 | t.Error("Empty key 'bids'")
166 | case len(c.Bids[0]) != 2:
167 | t.Error("Invalid bid length")
168 | }
169 | }
170 |
171 | func TestApiService_AggregatedFullOrderBook(t *testing.T) {
172 | s := NewApiServiceFromEnv()
173 | rsp, err := s.AggregatedFullOrderBook(context.Background(), "ETH-BTC")
174 | if err != nil {
175 | t.Fatal(err)
176 | }
177 | c := &FullOrderBookModel{}
178 | if err := rsp.ReadData(c); err != nil {
179 | t.Fatal(err)
180 | }
181 | t.Log(ToJsonString(c))
182 | switch {
183 | case c.Sequence == "":
184 | t.Error("Empty key 'sequence'")
185 | case len(c.Asks) == 0:
186 | t.Error("Empty key 'asks'")
187 | case len(c.Asks[0]) != 2:
188 | t.Error("Invalid ask length")
189 | case len(c.Bids) == 0:
190 | t.Error("Empty key 'bids'")
191 | case len(c.Bids[0]) != 2:
192 | t.Error("Invalid bid length")
193 | }
194 | }
195 | func TestApiService_AggregatedFullOrderBookV3(t *testing.T) {
196 | s := NewApiServiceFromEnv()
197 | rsp, err := s.AggregatedFullOrderBookV3(context.Background(), "BTC-USDT")
198 | if err != nil {
199 | t.Fatal(err)
200 | }
201 | c := &FullOrderBookModel{}
202 | if err := rsp.ReadData(c); err != nil {
203 | t.Fatal(err)
204 | }
205 | t.Log(ToJsonString(c))
206 | switch {
207 | case c.Sequence == "":
208 | t.Error("Empty key 'sequence'")
209 | case len(c.Asks) == 0:
210 | t.Error("Empty key 'asks'")
211 | case len(c.Asks[0]) != 2:
212 | t.Error("Invalid ask length")
213 | case len(c.Bids) == 0:
214 | t.Error("Empty key 'bids'")
215 | case len(c.Bids[0]) != 2:
216 | t.Error("Invalid bid length")
217 | }
218 | }
219 |
220 | func TestApiService_AtomicFullOrderBook(t *testing.T) {
221 | s := NewApiServiceFromEnv()
222 | rsp, err := s.AtomicFullOrderBook(context.Background(), "ETH-BTC")
223 | if err != nil {
224 | t.Fatal(err)
225 | }
226 | c := &FullOrderBookModel{}
227 | if err := rsp.ReadData(c); err != nil {
228 | t.Fatal(err)
229 | }
230 | t.Log(ToJsonString(c))
231 | switch {
232 | case c.Sequence == "":
233 | t.Error("Empty key 'sequence'")
234 | case len(c.Asks) == 0:
235 | t.Error("Empty key 'asks'")
236 | case len(c.Asks[0]) != 4:
237 | t.Error("Invalid ask length")
238 | case len(c.Bids) == 0:
239 | t.Error("Empty key 'bids'")
240 | case len(c.Bids[0]) != 4:
241 | t.Error("Invalid bid length")
242 | }
243 | }
244 |
245 | func TestApiService_AtomicFullOrderBookV2(t *testing.T) {
246 | s := NewApiServiceFromEnv()
247 | rsp, err := s.AtomicFullOrderBookV2(context.Background(), "ETH-BTC")
248 | if err != nil {
249 | t.Fatal(err)
250 | }
251 | c := &FullOrderBookV2Model{}
252 | if err := rsp.ReadData(c); err != nil {
253 | t.Fatal(err)
254 | }
255 | t.Log(ToJsonString(c))
256 | switch {
257 | case c.Sequence == 0:
258 | t.Error("Empty key 'sequence'")
259 | case len(c.Asks) == 0:
260 | t.Error("Empty key 'asks'")
261 | case len(c.Asks[0]) != 4:
262 | t.Error("Invalid ask length")
263 | case len(c.Bids) == 0:
264 | t.Error("Empty key 'bids'")
265 | case len(c.Bids[0]) != 4:
266 | t.Error("Invalid bid length")
267 | }
268 | }
269 |
270 | func TestApiService_TradeHistories(t *testing.T) {
271 | s := NewApiServiceFromEnv()
272 | rsp, err := s.TradeHistories(context.Background(), "ETH-BTC")
273 | if err != nil {
274 | t.Fatal(err)
275 | }
276 | l := TradeHistoriesModel{}
277 | if err := rsp.ReadData(&l); err != nil {
278 | t.Fatal(err)
279 | }
280 | for _, c := range l {
281 | t.Log(ToJsonString(c))
282 | switch {
283 | case c.Sequence == "":
284 | t.Error("Empty key 'sequence'")
285 | case c.Price == "":
286 | t.Error("Empty key 'price'")
287 | case c.Size == "":
288 | t.Error("Empty key 'size'")
289 | case c.Side == "":
290 | t.Error("Empty key 'side'")
291 | case c.Time == 0:
292 | t.Error("Empty key 'time'")
293 | }
294 | }
295 | }
296 |
297 | func TestApiService_KLines(t *testing.T) {
298 | s := NewApiServiceFromEnv()
299 | rsp, err := s.KLines(context.Background(), "ETH-BTC", "30min", time.Now().Unix()-7*24*3600, time.Now().Unix())
300 | if err != nil {
301 | t.Fatal(err)
302 | }
303 | l := KLinesModel{}
304 | if err := rsp.ReadData(&l); err != nil {
305 | t.Fatal(err)
306 | }
307 | for _, c := range l {
308 | t.Log(ToJsonString(c))
309 | if len(*c) != 7 {
310 | t.Error("Invalid length of rate")
311 | }
312 | }
313 | }
314 |
315 | func TestApiService_SymbolsV2(t *testing.T) {
316 | s := NewApiServiceFromEnv()
317 | rsp, err := s.SymbolsV2(context.Background(), "ETF")
318 | if err != nil {
319 | t.Fatal(err)
320 | }
321 | l := SymbolsModelV2{}
322 | if err := rsp.ReadData(&l); err != nil {
323 | t.Fatal(err)
324 | }
325 | for _, c := range l {
326 | t.Log(ToJsonString(c))
327 | switch {
328 | case c.Name == "":
329 | t.Error("Empty key 'name'")
330 | case c.Symbol == "":
331 | t.Error("Empty key 'symbol'")
332 | case c.BaseCurrency == "":
333 | t.Error("Empty key 'baseCurrency'")
334 | case c.QuoteCurrency == "":
335 | t.Error("Empty key 'quoteCurrency'")
336 | case c.BaseMinSize == "":
337 | t.Error("Empty key 'baseMinSize'")
338 | case c.QuoteMinSize == "":
339 | t.Error("Empty key 'quoteMinSize'")
340 | case c.BaseMaxSize == "":
341 | t.Error("Empty key 'baseMaxSize'")
342 | case c.QuoteMaxSize == "":
343 | t.Error("Empty key 'quoteMaxSize'")
344 | case c.BaseIncrement == "":
345 | t.Error("Empty key 'baseIncrement'")
346 | case c.QuoteIncrement == "":
347 | t.Error("Empty key 'quoteIncrement'")
348 | case c.FeeCurrency == "":
349 | t.Error("Empty key 'feeCurrency'")
350 | case c.PriceIncrement == "":
351 | t.Error("Empty key 'priceIncrement'")
352 | case c.MinFunds == "":
353 | t.Error("Empty key 'feeCurrency'")
354 | }
355 |
356 | }
357 | }
358 |
359 | func TestApiService_SymbolsV2Detail(t *testing.T) {
360 | s := NewApiServiceFromEnv()
361 | rsp, err := s.SymbolsDetail(context.Background(), "BTC-USDT")
362 | if err != nil {
363 | t.Fatal(err)
364 | }
365 | c := SymbolModelV2{}
366 | if err := rsp.ReadData(&c); err != nil {
367 | t.Fatal(err)
368 | }
369 | t.Log(ToJsonString(c))
370 | }
371 |
--------------------------------------------------------------------------------
/symbol.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | // A SymbolModel represents an available currency pairs for trading.
10 | type SymbolModel struct {
11 | Symbol string `json:"symbol"`
12 | Name string `json:"name"`
13 | BaseCurrency string `json:"baseCurrency"`
14 | QuoteCurrency string `json:"quoteCurrency"`
15 | Market string `json:"market"`
16 | BaseMinSize string `json:"baseMinSize"`
17 | QuoteMinSize string `json:"quoteMinSize"`
18 | BaseMaxSize string `json:"baseMaxSize"`
19 | QuoteMaxSize string `json:"quoteMaxSize"`
20 | BaseIncrement string `json:"baseIncrement"`
21 | QuoteIncrement string `json:"quoteIncrement"`
22 | PriceIncrement string `json:"priceIncrement"`
23 | FeeCurrency string `json:"feeCurrency"`
24 | EnableTrading bool `json:"enableTrading"`
25 | IsMarginEnabled bool `json:"isMarginEnabled"`
26 | PriceLimitRate string `json:"priceLimitRate"`
27 | }
28 |
29 | // A SymbolsModel is the set of *SymbolModel.
30 | type SymbolsModel []*SymbolModel
31 |
32 | // Symbols returns a list of available currency pairs for trading.
33 | // Deprecated
34 | func (as *ApiService) Symbols(ctx context.Context, market string) (*ApiResponse, error) {
35 | p := map[string]string{}
36 | if market != "" {
37 | p["market"] = market
38 | }
39 | req := NewRequest(http.MethodGet, "/api/v1/symbols", p)
40 | return as.Call(ctx, req)
41 | }
42 |
43 | // A TickerLevel1Model represents ticker include only the inside (i.e. best) bid and ask data, last price and last trade size.
44 | type TickerLevel1Model struct {
45 | Sequence string `json:"sequence"`
46 | Price string `json:"price"`
47 | Size string `json:"size"`
48 | BestBid string `json:"bestBid"`
49 | BestBidSize string `json:"bestBidSize"`
50 | BestAsk string `json:"bestAsk"`
51 | BestAskSize string `json:"bestAskSize"`
52 | Time int64 `json:"time"`
53 | }
54 |
55 | // TickerLevel1 returns the ticker include only the inside (i.e. best) bid and ask data, last price and last trade size.
56 | func (as *ApiService) TickerLevel1(ctx context.Context, symbol string) (*ApiResponse, error) {
57 | req := NewRequest(http.MethodGet, "/api/v1/market/orderbook/level1", map[string]string{"symbol": symbol})
58 | return as.Call(ctx, req)
59 | }
60 |
61 | // A TickerModel represents a market ticker for all trading pairs in the market (including 24h volume).
62 | type TickerModel struct {
63 | Symbol string `json:"symbol"`
64 | SymbolName string `json:"symbolName"`
65 | Buy string `json:"buy"`
66 | Sell string `json:"sell"`
67 | ChangeRate string `json:"changeRate"`
68 | ChangePrice string `json:"changePrice"`
69 | High string `json:"high"`
70 | Low string `json:"low"`
71 | Vol string `json:"vol"`
72 | VolValue string `json:"volValue"`
73 | Last string `json:"last"`
74 | AveragePrice string `json:"averagePrice"`
75 | TakerFeeRate string `json:"takerFeeRate"`
76 | MakerFeeRate string `json:"makerFeeRate"`
77 | TakerCoefficient string `json:"takerCoefficient"`
78 | MakerCoefficient string `json:"makerCoefficient"`
79 | }
80 |
81 | // A TickersModel is the set of *MarketTickerModel.
82 | type TickersModel []*TickerModel
83 |
84 | // TickersResponseModel represents the response model of MarketTickers().
85 | type TickersResponseModel struct {
86 | Time int64 `json:"time"`
87 | Tickers TickersModel `json:"ticker"`
88 | }
89 |
90 | // Tickers returns all tickers as TickersResponseModel for all trading pairs in the market (including 24h volume).
91 | func (as *ApiService) Tickers(ctx context.Context) (*ApiResponse, error) {
92 | req := NewRequest(http.MethodGet, "/api/v1/market/allTickers", nil)
93 | return as.Call(ctx, req)
94 | }
95 |
96 | // A Stats24hrModel represents 24 hr stats for the symbol.
97 | // Volume is in base currency units.
98 | // Open, high, low are in quote currency units.
99 | type Stats24hrModel struct {
100 | Time int64 `json:"time"`
101 | Symbol string `json:"symbol"`
102 | Buy string `json:"buy"`
103 | Sell string `json:"sell"`
104 | ChangeRate string `json:"changeRate"`
105 | ChangePrice string `json:"changePrice"`
106 | High string `json:"high"`
107 | Low string `json:"low"`
108 | Vol string `json:"vol"`
109 | VolValue string `json:"volValue"`
110 | Last string `json:"last"`
111 | AveragePrice string `json:"averagePrice"`
112 | TakerFeeRate string `json:"takerFeeRate"`
113 | MakerFeeRate string `json:"makerFeeRate"`
114 | TakerCoefficient string `json:"takerCoefficient"`
115 | MakerCoefficient string `json:"makerCoefficient"`
116 | }
117 |
118 | // Stats24hr returns 24 hr stats for the symbol. volume is in base currency units. open, high, low are in quote currency units.
119 | func (as *ApiService) Stats24hr(ctx context.Context, symbol string) (*ApiResponse, error) {
120 | req := NewRequest(http.MethodGet, "/api/v1/market/stats", map[string]string{"symbol": symbol})
121 | return as.Call(ctx, req)
122 | }
123 |
124 | // MarketsModel returns Model of Markets API.
125 | type MarketsModel []string
126 |
127 | // Markets returns the transaction currencies for the entire trading market.
128 | func (as *ApiService) Markets(ctx context.Context) (*ApiResponse, error) {
129 | req := NewRequest(http.MethodGet, "/api/v1/markets", nil)
130 | return as.Call(ctx, req)
131 | }
132 |
133 | // A PartOrderBookModel represents a list of open orders for a symbol, a part of Order Book within 100 depth for each side(ask or bid).
134 | type PartOrderBookModel struct {
135 | Sequence string `json:"sequence"`
136 | Time int64 `json:"time"`
137 | Bids [][]string `json:"bids"`
138 | Asks [][]string `json:"asks"`
139 | }
140 |
141 | // AggregatedPartOrderBook returns a list of open orders(aggregated) for a symbol.
142 | func (as *ApiService) AggregatedPartOrderBook(ctx context.Context, symbol string, depth int64) (*ApiResponse, error) {
143 | req := NewRequest(http.MethodGet, "/api/v1/market/orderbook/level2_"+IntToString(depth), map[string]string{"symbol": symbol})
144 | return as.Call(ctx, req)
145 | }
146 |
147 | // A FullOrderBookModel represents a list of open orders for a symbol, with full depth.
148 | type FullOrderBookModel struct {
149 | Sequence string `json:"sequence"`
150 | Time int64 `json:"time"`
151 | Bids [][]string `json:"bids"`
152 | Asks [][]string `json:"asks"`
153 | }
154 |
155 | // AggregatedFullOrderBook returns a list of open orders(aggregated) for a symbol.
156 | // Deprecated: Use AggregatedFullOrderBookV3/WebSocket instead.
157 | func (as *ApiService) AggregatedFullOrderBook(ctx context.Context, symbol string) (*ApiResponse, error) {
158 | req := NewRequest(http.MethodGet, "/api/v2/market/orderbook/level2", map[string]string{"symbol": symbol})
159 | return as.Call(ctx, req)
160 | }
161 |
162 | // AggregatedFullOrderBookV3 returns a list of open orders(aggregated) for a symbol.
163 | func (as *ApiService) AggregatedFullOrderBookV3(ctx context.Context, symbol string) (*ApiResponse, error) {
164 | req := NewRequest(http.MethodGet, "/api/v3/market/orderbook/level2", map[string]string{"symbol": symbol})
165 | return as.Call(ctx, req)
166 | }
167 |
168 | // A FullOrderBookV2Model represents a list of open orders for a symbol, with full depth.
169 | type FullOrderBookV2Model struct {
170 | Sequence int64 `json:"sequence"`
171 | Time int64 `json:"time"`
172 | Bids [][]interface{} `json:"bids"`
173 | Asks [][]interface{} `json:"asks"`
174 | }
175 |
176 | // AtomicFullOrderBook returns a list of open orders for a symbol.
177 | // Level-3 order book includes all bids and asks (non-aggregated, each item in Level-3 means a single order).
178 | func (as *ApiService) AtomicFullOrderBook(ctx context.Context, symbol string) (*ApiResponse, error) {
179 | req := NewRequest(http.MethodGet, "/api/v1/market/orderbook/level3", map[string]string{"symbol": symbol})
180 | return as.Call(ctx, req)
181 | }
182 |
183 | // AtomicFullOrderBookV2 returns a list of open orders for a symbol.
184 | // Level-3 order book includes all bids and asks (non-aggregated, each item in Level-3 means a single order).
185 | func (as *ApiService) AtomicFullOrderBookV2(ctx context.Context, symbol string) (*ApiResponse, error) {
186 | req := NewRequest(http.MethodGet, "/api/v2/market/orderbook/level3", map[string]string{"symbol": symbol})
187 | return as.Call(ctx, req)
188 | }
189 |
190 | // A TradeHistoryModel represents the latest trades for a symbol.
191 | type TradeHistoryModel struct {
192 | Sequence string `json:"sequence"`
193 | Price string `json:"price"`
194 | Size string `json:"size"`
195 | Side string `json:"side"`
196 | Time int64 `json:"time"`
197 | }
198 |
199 | // A TradeHistoriesModel is the set of *TradeHistoryModel.
200 | type TradeHistoriesModel []*TradeHistoryModel
201 |
202 | // TradeHistories returns a list the latest trades for a symbol.
203 | func (as *ApiService) TradeHistories(ctx context.Context, symbol string) (*ApiResponse, error) {
204 | req := NewRequest(http.MethodGet, "/api/v1/market/histories", map[string]string{"symbol": symbol})
205 | return as.Call(ctx, req)
206 | }
207 |
208 | // KLineModel represents the k lines for a symbol.
209 | // Rates are returned in grouped buckets based on requested type.
210 | type KLineModel []string
211 |
212 | // A KLinesModel is the set of *KLineModel.
213 | type KLinesModel []*KLineModel
214 |
215 | // KLines returns the k lines for a symbol.
216 | // Data are returned in grouped buckets based on requested type.
217 | // Parameter #2 typo is the type of candlestick patterns.
218 | func (as *ApiService) KLines(ctx context.Context, symbol, typo string, startAt, endAt int64) (*ApiResponse, error) {
219 | req := NewRequest(http.MethodGet, "/api/v1/market/candles", map[string]string{
220 | "symbol": symbol,
221 | "type": typo,
222 | "startAt": IntToString(startAt),
223 | "endAt": IntToString(endAt),
224 | })
225 | return as.Call(ctx, req)
226 | }
227 |
228 | // SymbolsV2 returns a list of available currency pairs for trading.
229 | func (as *ApiService) SymbolsV2(ctx context.Context, market string) (*ApiResponse, error) {
230 | p := map[string]string{}
231 | if market != "" {
232 | p["market"] = market
233 | }
234 | req := NewRequest(http.MethodGet, "/api/v2/symbols", p)
235 | return as.Call(ctx, req)
236 | }
237 |
238 | // SymbolsDetail Request via this endpoint to get detail currency pairs for trading
239 | func (as *ApiService) SymbolsDetail(ctx context.Context, symbol string) (*ApiResponse, error) {
240 | req := NewRequest(http.MethodGet, fmt.Sprintf("/api/v2/symbols/%s", symbol), nil)
241 | return as.Call(ctx, req)
242 | }
243 |
244 | // A SymbolsModelV2 is the set of *SymbolsModelV2.
245 | type SymbolsModelV2 []*SymbolModelV2
246 |
247 | type SymbolModelV2 struct {
248 | Symbol string `json:"symbol"`
249 | Name string `json:"name"`
250 | BaseCurrency string `json:"baseCurrency"`
251 | QuoteCurrency string `json:"quoteCurrency"`
252 | Market string `json:"market"`
253 | BaseMinSize string `json:"baseMinSize"`
254 | QuoteMinSize string `json:"quoteMinSize"`
255 | BaseMaxSize string `json:"baseMaxSize"`
256 | QuoteMaxSize string `json:"quoteMaxSize"`
257 | BaseIncrement string `json:"baseIncrement"`
258 | QuoteIncrement string `json:"quoteIncrement"`
259 | PriceIncrement string `json:"priceIncrement"`
260 | FeeCurrency string `json:"feeCurrency"`
261 | EnableTrading bool `json:"enableTrading"`
262 | IsMarginEnabled bool `json:"isMarginEnabled"`
263 | PriceLimitRate string `json:"priceLimitRate"`
264 | MinFunds string `json:"minFunds"`
265 | }
266 |
--------------------------------------------------------------------------------
/websocket.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "crypto/tls"
6 | "encoding/json"
7 | "fmt"
8 | "math/rand"
9 | "net/http"
10 | "net/url"
11 | "sync"
12 | "time"
13 |
14 | "github.com/gorilla/websocket"
15 | "github.com/pkg/errors"
16 | "github.com/sirupsen/logrus"
17 | )
18 |
19 | // A WebSocketTokenModel contains a token and some servers for WebSocket feed.
20 | type WebSocketTokenModel struct {
21 | Token string `json:"token"`
22 | Servers WebSocketServersModel `json:"instanceServers"`
23 | AcceptUserMessage bool `json:"accept_user_message"`
24 | }
25 |
26 | // A WebSocketServerModel contains some servers for WebSocket feed.
27 | type WebSocketServerModel struct {
28 | PingInterval int64 `json:"pingInterval"`
29 | Endpoint string `json:"endpoint"`
30 | Protocol string `json:"protocol"`
31 | Encrypt bool `json:"encrypt"`
32 | PingTimeout int64 `json:"pingTimeout"`
33 | }
34 |
35 | // A WebSocketServersModel is the set of *WebSocketServerModel.
36 | type WebSocketServersModel []*WebSocketServerModel
37 |
38 | // RandomServer returns a server randomly.
39 | func (s WebSocketServersModel) RandomServer() (*WebSocketServerModel, error) {
40 | l := len(s)
41 | if l == 0 {
42 | return nil, errors.New("No available server ")
43 | }
44 | return s[rand.Intn(l)], nil
45 | }
46 |
47 | // WebSocketPublicToken returns the token for public channel.
48 | func (as *ApiService) WebSocketPublicToken(ctx context.Context) (*ApiResponse, error) {
49 | req := NewRequest(http.MethodPost, "/api/v1/bullet-public", map[string]string{})
50 | return as.Call(ctx, req)
51 | }
52 |
53 | // WebSocketPrivateToken returns the token for private channel.
54 | func (as *ApiService) WebSocketPrivateToken(ctx context.Context) (*ApiResponse, error) {
55 | req := NewRequest(http.MethodPost, "/api/v1/bullet-private", map[string]string{})
56 | return as.Call(ctx, req)
57 | }
58 |
59 | // All message types of WebSocket.
60 | const (
61 | WelcomeMessage = "welcome"
62 | PingMessage = "ping"
63 | PongMessage = "pong"
64 | SubscribeMessage = "subscribe"
65 | AckMessage = "ack"
66 | UnsubscribeMessage = "unsubscribe"
67 | ErrorMessage = "error"
68 | Message = "message"
69 | Notice = "notice"
70 | Command = "command"
71 | )
72 |
73 | // A WebSocketMessage represents a message between the WebSocket client and server.
74 | type WebSocketMessage struct {
75 | Id string `json:"id"`
76 | Type string `json:"type"`
77 | }
78 |
79 | // A WebSocketSubscribeMessage represents a message to subscribe the public/private channel.
80 | type WebSocketSubscribeMessage struct {
81 | *WebSocketMessage
82 | Topic string `json:"topic"`
83 | PrivateChannel bool `json:"privateChannel"`
84 | Response bool `json:"response"`
85 | }
86 |
87 | // NewPingMessage creates a ping message instance.
88 | func NewPingMessage() *WebSocketMessage {
89 | return &WebSocketMessage{
90 | Id: IntToString(time.Now().UnixNano()),
91 | Type: PingMessage,
92 | }
93 | }
94 |
95 | // NewSubscribeMessage creates a subscribe message instance.
96 | func NewSubscribeMessage(topic string, privateChannel bool) *WebSocketSubscribeMessage {
97 | return &WebSocketSubscribeMessage{
98 | WebSocketMessage: &WebSocketMessage{
99 | Id: IntToString(time.Now().UnixNano()),
100 | Type: SubscribeMessage,
101 | },
102 | Topic: topic,
103 | PrivateChannel: privateChannel,
104 | Response: true,
105 | }
106 | }
107 |
108 | // A WebSocketUnsubscribeMessage represents a message to unsubscribe the public/private channel.
109 | type WebSocketUnsubscribeMessage WebSocketSubscribeMessage
110 |
111 | // NewUnsubscribeMessage creates a unsubscribe message instance.
112 | func NewUnsubscribeMessage(topic string, privateChannel bool) *WebSocketUnsubscribeMessage {
113 | return &WebSocketUnsubscribeMessage{
114 | WebSocketMessage: &WebSocketMessage{
115 | Id: IntToString(time.Now().UnixNano()),
116 | Type: UnsubscribeMessage,
117 | },
118 | Topic: topic,
119 | PrivateChannel: privateChannel,
120 | Response: true,
121 | }
122 | }
123 |
124 | // A WebSocketDownstreamMessage represents a message from the WebSocket server to client.
125 | type WebSocketDownstreamMessage struct {
126 | *WebSocketMessage
127 | Sn int64 `json:"sn"`
128 | Topic string `json:"topic"`
129 | Subject string `json:"subject"`
130 | RawData json.RawMessage `json:"data"`
131 | }
132 |
133 | // ReadData read the data in channel.
134 | func (m *WebSocketDownstreamMessage) ReadData(v interface{}) error {
135 | return json.Unmarshal(m.RawData, v)
136 | }
137 |
138 | // A WebSocketClient represents a connection to WebSocket server.
139 | type WebSocketClient struct {
140 | // Wait all goroutines quit
141 | wg *sync.WaitGroup
142 | // Stop subscribing channel
143 | done chan struct{}
144 | // Pong channel to check pong message
145 | pongs chan string
146 | // ACK channel to check pong message
147 | acks chan string
148 | // Error channel
149 | errors chan error
150 | // Downstream message channel
151 | messages chan *WebSocketDownstreamMessage
152 | conn *websocket.Conn
153 | token *WebSocketTokenModel
154 | server *WebSocketServerModel
155 | enableHeartbeat bool
156 | skipVerifyTls bool
157 | timeout time.Duration
158 | }
159 |
160 | var defaultTimeout = time.Second * 5
161 |
162 | // WebSocketClientOpts defines the options for the client
163 | // during the websocket connection.
164 | type WebSocketClientOpts struct {
165 | Token *WebSocketTokenModel
166 | TLSSkipVerify bool
167 | Timeout time.Duration
168 | }
169 |
170 | // NewWebSocketClient creates an instance of WebSocketClient.
171 | func (as *ApiService) NewWebSocketClient(token *WebSocketTokenModel) *WebSocketClient {
172 | return as.NewWebSocketClientOpts(WebSocketClientOpts{
173 | Token: token,
174 | TLSSkipVerify: as.apiSkipVerifyTls,
175 | Timeout: defaultTimeout,
176 | })
177 | }
178 |
179 | // NewWebSocketClientOpts creates an instance of WebSocketClient with the parsed options.
180 | func (as *ApiService) NewWebSocketClientOpts(opts WebSocketClientOpts) *WebSocketClient {
181 | wc := &WebSocketClient{
182 | wg: &sync.WaitGroup{},
183 | done: make(chan struct{}),
184 | errors: make(chan error, 1),
185 | pongs: make(chan string, 1),
186 | acks: make(chan string, 1),
187 | token: opts.Token,
188 | messages: make(chan *WebSocketDownstreamMessage, 2048),
189 | skipVerifyTls: opts.TLSSkipVerify,
190 | timeout: opts.Timeout,
191 | }
192 | return wc
193 | }
194 |
195 | // Connect connects the WebSocket server.
196 | func (wc *WebSocketClient) Connect() (<-chan *WebSocketDownstreamMessage, <-chan error, error) {
197 | // Find out a server
198 | s, err := wc.token.Servers.RandomServer()
199 | if err != nil {
200 | return wc.messages, wc.errors, err
201 | }
202 | wc.server = s
203 |
204 | // Concat ws url
205 | q := url.Values{}
206 | q.Add("connectId", IntToString(time.Now().UnixNano()))
207 | q.Add("token", wc.token.Token)
208 | if wc.token.AcceptUserMessage == true {
209 | q.Add("acceptUserMessage", "true")
210 | }
211 | u := fmt.Sprintf("%s?%s", s.Endpoint, q.Encode())
212 |
213 | // Ignore verify tls
214 | websocket.DefaultDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: wc.skipVerifyTls}
215 |
216 | // Connect ws server
217 | websocket.DefaultDialer.ReadBufferSize = 2048000 //2000 kb
218 | wc.conn, _, err = websocket.DefaultDialer.Dial(u, nil)
219 | if err != nil {
220 | return wc.messages, wc.errors, err
221 | }
222 |
223 | // Must read the first welcome message
224 | for {
225 | m := &WebSocketDownstreamMessage{}
226 | if err := wc.conn.ReadJSON(m); err != nil {
227 | return wc.messages, wc.errors, err
228 | }
229 | if DebugMode {
230 | logrus.Debugf("Received a WebSocket message: %s", ToJsonString(m))
231 | }
232 | if m.Type == ErrorMessage {
233 | return wc.messages, wc.errors, errors.Errorf("Error message: %s", ToJsonString(m))
234 | }
235 | if m.Type == WelcomeMessage {
236 | break
237 | }
238 | }
239 |
240 | wc.wg.Add(2)
241 | go wc.read()
242 | go wc.keepHeartbeat()
243 |
244 | return wc.messages, wc.errors, nil
245 | }
246 |
247 | func (wc *WebSocketClient) read() {
248 | defer func() {
249 | close(wc.pongs)
250 | close(wc.messages)
251 | wc.wg.Done()
252 | }()
253 |
254 | for {
255 | select {
256 | case <-wc.done:
257 | return
258 | default:
259 | m := &WebSocketDownstreamMessage{}
260 | if err := wc.conn.ReadJSON(m); err != nil {
261 | wc.errors <- err
262 | return
263 | }
264 | if DebugMode {
265 | logrus.Debugf("Received a WebSocket message: %s", ToJsonString(m))
266 | }
267 | // log.Printf("ReadJSON: %s", ToJsonString(m))
268 | switch m.Type {
269 | case WelcomeMessage:
270 | case PongMessage:
271 | if wc.enableHeartbeat {
272 | wc.pongs <- m.Id
273 | }
274 | case AckMessage:
275 | // log.Printf("Subscribed: %s==%s? %s", channel.Id, m.Id, channel.Topic)
276 | wc.acks <- m.Id
277 | case ErrorMessage:
278 | wc.errors <- errors.Errorf("Error message: %s", ToJsonString(m))
279 | return
280 | case Message, Notice, Command:
281 | wc.messages <- m
282 | default:
283 | wc.errors <- errors.Errorf("Unknown message type: %s", m.Type)
284 | }
285 | }
286 | }
287 | }
288 |
289 | func (wc *WebSocketClient) keepHeartbeat() {
290 | wc.enableHeartbeat = true
291 | // New ticker to send ping message
292 | pt := time.NewTicker(time.Duration(wc.server.PingInterval)*time.Millisecond - time.Millisecond*200)
293 | defer wc.wg.Done()
294 | defer pt.Stop()
295 |
296 | for {
297 | select {
298 | case <-wc.done:
299 | return
300 | case <-pt.C:
301 | p := NewPingMessage()
302 | m := ToJsonString(p)
303 | if DebugMode {
304 | logrus.Debugf("Sent a WebSocket message: %s", m)
305 | }
306 | if err := wc.conn.WriteMessage(websocket.TextMessage, []byte(m)); err != nil {
307 | wc.errors <- err
308 | return
309 | }
310 |
311 | // log.Printf("Ping: %s", ToJsonString(p))
312 | // Waiting (with timeout) for the server to response pong message
313 | // If timeout, close this connection
314 | select {
315 | case pid := <-wc.pongs:
316 | if pid != p.Id {
317 | wc.errors <- errors.Errorf("Invalid pong id %s, expect %s", pid, p.Id)
318 | return
319 | }
320 | case <-time.After(time.Duration(wc.server.PingTimeout) * time.Millisecond):
321 | wc.errors <- errors.Errorf("Wait pong message timeout in %d ms", wc.server.PingTimeout)
322 | return
323 | }
324 | }
325 | }
326 | }
327 |
328 | // Subscribe subscribes the specified channel.
329 | func (wc *WebSocketClient) Subscribe(channels ...*WebSocketSubscribeMessage) error {
330 | for _, c := range channels {
331 | m := ToJsonString(c)
332 | if DebugMode {
333 | logrus.Debugf("Sent a WebSocket message: %s", m)
334 | }
335 | if err := wc.conn.WriteMessage(websocket.TextMessage, []byte(m)); err != nil {
336 | return err
337 | }
338 | //log.Printf("Subscribing: %s, %s", c.Id, c.Topic)
339 | select {
340 | case id := <-wc.acks:
341 | //log.Printf("ack: %s=>%s", id, c.Id)
342 | if id != c.Id {
343 | return errors.Errorf("Invalid ack id %s, expect %s", id, c.Id)
344 | }
345 | case err := <-wc.errors:
346 | return errors.Errorf("Subscribe failed, %s", err.Error())
347 | case <-time.After(wc.timeout):
348 | return errors.Errorf("Wait ack message timeout in %v", wc.timeout)
349 | }
350 | }
351 | return nil
352 | }
353 |
354 | // Unsubscribe unsubscribes the specified channel.
355 | func (wc *WebSocketClient) Unsubscribe(channels ...*WebSocketUnsubscribeMessage) error {
356 | for _, c := range channels {
357 | m := ToJsonString(c)
358 | if DebugMode {
359 | logrus.Debugf("Sent a WebSocket message: %s", m)
360 | }
361 | if err := wc.conn.WriteMessage(websocket.TextMessage, []byte(m)); err != nil {
362 | return err
363 | }
364 | //log.Printf("Unsubscribing: %s, %s", c.Id, c.Topic)
365 | select {
366 | case id := <-wc.acks:
367 | //log.Printf("ack: %s=>%s", id, c.Id)
368 | if id != c.Id {
369 | return errors.Errorf("Invalid ack id %s, expect %s", id, c.Id)
370 | }
371 | case <-time.After(wc.timeout):
372 | return errors.Errorf("Wait ack message timeout in %v", wc.timeout)
373 | }
374 | }
375 | return nil
376 | }
377 |
378 | // Stop stops subscribing the specified channel, all goroutines quit.
379 | func (wc *WebSocketClient) Stop() {
380 | close(wc.done)
381 | _ = wc.conn.Close()
382 | wc.wg.Wait()
383 | }
384 |
--------------------------------------------------------------------------------
/hf_order_types.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "encoding/json"
5 | "math/big"
6 | )
7 |
8 | type HfPlaceOrderRes struct {
9 | OrderId string `json:"orderId"`
10 | ClientOid string `json:"clientOid"`
11 | Success bool `json:"success"`
12 | }
13 |
14 | type HfSyncPlaceOrderRes struct {
15 | OrderId string `json:"orderId"`
16 | ClientOid string `json:"clientOid"`
17 | OrderTime json.Number `json:"orderTime"`
18 | OriginSize string `json:"originSize"`
19 | DealSize string `json:"dealSize"`
20 | RemainSize string `json:"remainSize"`
21 | CanceledSize string `json:"canceledSize"`
22 | Status string `json:"status"`
23 | MatchTime json.Number `json:"matchTime"`
24 | }
25 |
26 | type HFCreateMultiOrderModel struct {
27 | ClientOid string `json:"clientOid"`
28 | Symbol string `json:"symbol"`
29 | OrderType string `json:"type"`
30 | TimeInForce string `json:"timeInForce"`
31 | Stp string `json:"stp"`
32 | Side string `json:"side"`
33 | Price string `json:"price"`
34 | Size string `json:"size"`
35 | CancelAfter big.Int `json:"cancelAfter"`
36 | PostOnly bool `json:"postOnly"`
37 | Hidden bool `json:"hidden"`
38 | Iceberg bool `json:"iceberg"`
39 | VisibleSize string `json:"visibleSize"`
40 | Tags string `json:"tags"`
41 | Remark string `json:"remark"`
42 | }
43 |
44 | type HfPlaceMultiOrdersRes []*HfPlaceOrderRes
45 |
46 | type HfModifyOrderRes struct {
47 | NewOrderId string `json:"newOrderId"`
48 | ClientOid string `json:"clientOid"`
49 | }
50 |
51 | type HfSyncCancelOrderRes struct {
52 | OrderId string `json:"orderId"`
53 | OriginSize string `json:"originSize"`
54 | OriginFunds string `json:"originFunds"`
55 | DealSize string `json:"dealSize"`
56 | RemainSize string `json:"remainSize"`
57 | CanceledSize string `json:"canceledSize"`
58 | Status string `json:"status"`
59 | }
60 |
61 | type HfSyncCancelOrderWithSizeRes struct {
62 | OrderId string `json:"orderId"`
63 | CancelSize string `json:"cancelSize"`
64 | }
65 |
66 | type HfOrdersModel []*HfOrderModel
67 |
68 | type HfOrderModel struct {
69 | Id string `json:"id"`
70 | Symbol string `json:"symbol"`
71 | OpType string `json:"opType"`
72 | Type string `json:"type"`
73 | Side string `json:"side"`
74 | Price string `json:"price"`
75 | Size string `json:"size"`
76 | Funds string `json:"funds"`
77 | DealSize string `json:"dealSize"`
78 | DealFunds string `json:"dealFunds"`
79 | Fee string `json:"fee"`
80 | FeeCurrency string `json:"feeCurrency"`
81 | Stp string `json:"stp"`
82 | TimeInForce string `json:"timeInForce"`
83 | PostOnly bool `json:"postOnly"`
84 | Hidden bool `json:"hidden"`
85 | Iceberg bool `json:"iceberg"`
86 | VisibleSize string `json:"visibleSize"`
87 | CancelAfter int64 `json:"cancelAfter"`
88 | Channel string `json:"channel"`
89 | ClientOid string `json:"clientOid"`
90 | Remark string `json:"remark"`
91 | Tags string `json:"tags"`
92 | CancelExist bool `json:"cancelExist"`
93 | CreatedAt json.Number `json:"createdAt"`
94 | LastUpdatedAt json.Number `json:"lastUpdatedAt"`
95 | TradeType string `json:"tradeType"`
96 | InOrderBook bool `json:"inOrderBook"`
97 | Active bool `json:"active"`
98 | CancelledSize string `json:"cancelledSize"`
99 | CancelledFunds string `json:"cancelledFunds"`
100 | RemainSize string `json:"remainSize"`
101 | RemainFunds string `json:"remainFunds"`
102 | }
103 |
104 | type HfAutoCancelSettingRes struct {
105 | CurrentTime json.Number `json:"currentTime"`
106 | TriggerTime json.Number `json:"triggerTime"`
107 | }
108 |
109 | type AUtoCancelSettingModel struct {
110 | Timeout int64 `json:"timeout"`
111 | Symbols string `json:"symbols"`
112 | CurrentTime json.Number `json:"currentTime"`
113 | TriggerTime json.Number `json:"triggerTime"`
114 | }
115 |
116 | type HfOrderIdModel struct {
117 | OrderId string `json:"orderId"`
118 | }
119 |
120 | type HfClientOidModel struct {
121 | ClientOid string `json:"clientOid"`
122 | }
123 |
124 | type HfTransactionDetailsModel struct {
125 | LastId json.Number `json:"lastId"`
126 | Items []*HfTransactionDetailModel `json:"items"`
127 | }
128 |
129 | type HfTransactionDetailModel struct {
130 | Id json.Number `json:"id"`
131 | Symbol string `json:"symbol"`
132 | TradeId json.Number `json:"tradeId"`
133 | OrderId string `json:"orderId"`
134 | CounterOrderId string `json:"counterOrderId"`
135 | Side string `json:"side"`
136 | Liquidity string `json:"liquidity"`
137 | ForceTaker bool `json:"forceTaker"`
138 | Price string `json:"price"`
139 | Size string `json:"size"`
140 | Funds string `json:"funds"`
141 | Fee string `json:"fee"`
142 | FeeRate string `json:"feeRate"`
143 | FeeCurrency string `json:"feeCurrency"`
144 | OrderType string `json:"type"`
145 | Stop string `json:"stop"`
146 | CreatedAt json.Number `json:"createdAt"`
147 | TradeType string `json:"tradeType"`
148 | }
149 |
150 | type HfCancelOrdersResultModel struct {
151 | SucceedSymbols []string `json:"succeedSymbols"`
152 | FailedSymbols []*HfCancelOrdersFailedResultModel `json:"failedSymbols"`
153 | }
154 | type HfCancelOrdersFailedResultModel struct {
155 | Symbol string `json:"symbol"`
156 | Error string `json:"error"`
157 | }
158 |
159 | type HfPlaceOrderReq struct {
160 | ClientOid string `json:"clientOid"`
161 | Symbol string `json:"symbol"`
162 | Side string `json:"side"`
163 | Stp string `json:"stp"`
164 | Tags string `json:"tags"`
165 | Remark string `json:"remark"`
166 |
167 | Price string `json:"price,omitempty"`
168 | Size string `json:"size,omitempty"`
169 | TimeInForce string `json:"timeInForce,omitempty"`
170 | CancelAfter int64 `json:"cancelAfter,omitempty"`
171 | PostOnly bool `json:"postOnly,omitempty"`
172 | Hidden bool `json:"hidden,omitempty"`
173 | Iceberg bool `json:"iceberg,omitempty"`
174 | VisibleSize bool `json:"visibleSize,omitempty"`
175 |
176 | Funds string `json:"funds,omitempty"`
177 | }
178 |
179 | type HfMarginOrderV3Req struct {
180 | ClientOid string `json:"clientOid"`
181 | Symbol string `json:"symbol"`
182 | Side string `json:"side"`
183 | Type string `json:"type"`
184 | Stp string `json:"stp"`
185 | IsIsolated bool `json:"isIsolated"`
186 | AutoBorrow bool `json:"autoBorrow"`
187 | AutoRepay bool `json:"autoRepay"`
188 |
189 | Price string `json:"price,omitempty"`
190 | Size string `json:"size,omitempty"`
191 | TimeInForce string `json:"timeInForce,omitempty"`
192 | CancelAfter int64 `json:"cancelAfter,omitempty"`
193 | PostOnly bool `json:"postOnly,omitempty"`
194 | Hidden bool `json:"hidden,omitempty"`
195 | Iceberg bool `json:"iceberg,omitempty"`
196 | VisibleSize bool `json:"visibleSize,omitempty"`
197 |
198 | Funds string `json:"funds,omitempty"`
199 | }
200 |
201 | type HfMarginOrderV3Resp struct {
202 | OrderNo string `json:"orderNo"`
203 | BorrowSize string `json:"borrowSize"`
204 | LoanApplyId string `json:"loanApplyId"`
205 | }
206 |
207 | type HfCancelMarinOrderV3Req struct {
208 | OrderId string `json:"orderId"`
209 | Symbol string `json:"symbol"`
210 | }
211 |
212 | type HfCancelMarinOrderV3Resp struct {
213 | OrderId string `json:"orderId"`
214 | }
215 |
216 | type HfCancelClientMarinOrderV3Req struct {
217 | ClientOid string `json:"clientOid"`
218 | Symbol string `json:"symbol"`
219 | }
220 |
221 | type HfCancelClientMarinOrderV3Resp struct {
222 | ClientOid string `json:"clientOid"`
223 | }
224 |
225 | type HfCancelAllMarginOrdersV3Req struct {
226 | TradeType string `json:"tradeType" url:"tradeType"`
227 | Symbol string `json:"symbol" url:"symbol"`
228 | }
229 |
230 | type HfCancelAllMarginOrdersV3Resp string
231 |
232 | type HFMarginActiveSymbolsModel struct {
233 | SymbolSize int32 `json:"symbolSize"`
234 | Symbols []string `json:"symbols"`
235 | }
236 |
237 | type HfMarinActiveOrdersV3Req struct {
238 | TradeType string `url:"tradeType"`
239 | Symbol string `url:"symbol"`
240 | }
241 |
242 | type MarginOrderV3Model struct {
243 | Id string `json:"id"`
244 | Symbol string `json:"symbol"`
245 | OpType string `json:"opType"`
246 | Type string `json:"type"`
247 | Side string `json:"side"`
248 | Price string `json:"price"`
249 | Size string `json:"size"`
250 | Funds string `json:"funds"`
251 | DealFunds string `json:"dealFunds"`
252 | DealSize string `json:"dealSize"`
253 | Fee string `json:"fee"`
254 | FeeCurrency string `json:"feeCurrency"`
255 | Stp string `json:"stp"`
256 | TimeInForce string `json:"timeInForce"`
257 | PostOnly bool `json:"postOnly"`
258 | Hidden bool `json:"hidden"`
259 | Iceberg bool `json:"iceberg"`
260 | VisibleSize string `json:"visibleSize"`
261 | CancelAfter int `json:"cancelAfter"`
262 | Channel string `json:"channel"`
263 | ClientOid string `json:"clientOid"`
264 | Remark string `json:"remark"`
265 | Tags string `json:"tags"`
266 | Active bool `json:"active"`
267 | InOrderBook bool `json:"inOrderBook"`
268 | CancelExist bool `json:"cancelExist"`
269 | CreatedAt int64 `json:"createdAt"`
270 | LastUpdatedAt int64 `json:"lastUpdatedAt"`
271 | TradeType string `json:"tradeType"`
272 | }
273 |
274 | type HfMarinActiveOrdersV3Resp []*MarginOrderV3Model
275 |
276 | type HfMarinDoneOrdersV3Req struct {
277 | TradeType string `url:"tradeType"`
278 | Symbol string `url:"symbol"`
279 | Side string `url:"side,omitempty"`
280 | Type string `url:"type,omitempty"`
281 | StartAt int64 `url:"startAt,omitempty"`
282 | EndAt int64 `url:"endAt,omitempty"`
283 | LastId int64 `url:"lastId,omitempty"`
284 | Limit int `url:"limit,omitempty"`
285 | }
286 |
287 | type HfMarinDoneOrdersV3Resp struct {
288 | Items []*MarginFillModel `json:"items"`
289 | LastId int64 `json:"lastId"`
290 | }
291 |
292 | type MarginFillModel struct {
293 | ID int64 `json:"id"`
294 | Symbol string `json:"symbol"`
295 | OpType string `json:"opType"`
296 | Type string `json:"type"`
297 | Side string `json:"side"`
298 | Price string `json:"price"`
299 | Size string `json:"size"`
300 | Funds string `json:"funds"`
301 | DealFunds string `json:"dealFunds"`
302 | DealSize string `json:"dealSize"`
303 | Fee string `json:"fee"`
304 | FeeCurrency string `json:"feeCurrency"`
305 | STP string `json:"stp"`
306 | TimeInForce string `json:"timeInForce"`
307 | PostOnly bool `json:"postOnly"`
308 | Hidden bool `json:"hidden"`
309 | Iceberg bool `json:"iceberg"`
310 | VisibleSize string `json:"visibleSize"`
311 | CancelAfter int64 `json:"cancelAfter"`
312 | Channel string `json:"channel"`
313 | ClientOid string `json:"clientOid"`
314 | Remark string `json:"remark"`
315 | Tags string `json:"tags"`
316 | Active bool `json:"active"`
317 | InOrderBook bool `json:"inOrderBook"`
318 | CancelExist bool `json:"cancelExist"`
319 | CreatedAt int64 `json:"createdAt"`
320 | LastUpdatedAt int64 `json:"lastUpdatedAt"`
321 | TradeType string `json:"tradeType"`
322 | }
323 |
324 | type HfMarinOrderV3Req struct {
325 | OrderId string
326 | Symbol string
327 | }
328 |
329 | type HfMarinOrderV3Resp struct {
330 | MarginFillModel
331 | }
332 |
333 | type HfMarinClientOrderV3Req struct {
334 | ClientOid string
335 | Symbol string
336 | }
337 |
338 | type HfMarinClientOrderV3Resp struct {
339 | MarginFillModel
340 | }
341 |
342 | type HfMarinFillsV3Req struct {
343 | Symbol string `url:"symbol"`
344 | TradeType string `url:"tradeType"`
345 | OrderId string `url:"orderId,omitempty"`
346 | Side string `url:"side,omitempty"`
347 | Type string `url:"type,omitempty"`
348 | StartAt int64 `url:"startAt,omitempty"`
349 | EndAt int64 `url:"endAt,omitempty"`
350 | LastId int64 `url:"lastId,omitempty"`
351 | Limit int `url:"limit,omitempty"`
352 | }
353 |
354 | type HfMarinFillsV3Resp struct {
355 | Items []*MarginFillModel `json:"items"`
356 | LastId int64 `json:"lastId"`
357 | }
358 |
--------------------------------------------------------------------------------
/order_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "strconv"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestApiService_CreateOrder(t *testing.T) {
11 | t.SkipNow()
12 |
13 | s := NewApiServiceFromEnv()
14 | p := &CreateOrderModel{
15 | ClientOid: IntToString(time.Now().UnixNano()),
16 | Side: "buy",
17 | Symbol: "KCS-ETH",
18 | Price: "0.0036",
19 | Size: "1",
20 | }
21 | rsp, err := s.CreateOrder(context.Background(), p)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 | o := &CreateOrderResultModel{}
26 | if err := rsp.ReadData(o); err != nil {
27 | t.Fatal(err)
28 | }
29 | t.Log(ToJsonString(o))
30 | switch {
31 | case o.OrderId == "":
32 | t.Error("Empty key 'OrderId'")
33 | }
34 | }
35 |
36 | func TestApiService_CreateMultiOrder(t *testing.T) {
37 | t.SkipNow()
38 |
39 | s := NewApiServiceFromEnv()
40 |
41 | orders := make([]*CreateOrderModel, 0, 5)
42 | for i := 0; i < 5; i++ {
43 | p := &CreateOrderModel{
44 | ClientOid: IntToString(time.Now().UnixNano() + int64(i)),
45 | Side: "buy",
46 | Price: "0.0036",
47 | Size: "1",
48 | Remark: "Multi " + strconv.Itoa(i),
49 | }
50 | orders = append(orders, p)
51 | }
52 | rsp, err := s.CreateMultiOrder(context.Background(), "KCS-ETH", orders)
53 | if err != nil {
54 | t.Fatal(err)
55 | }
56 | r := &CreateMultiOrderResultModel{}
57 | if err := rsp.ReadData(r); err != nil {
58 | t.Fatal(err)
59 | }
60 | t.Log(ToJsonString(r))
61 | }
62 |
63 | func TestApiService_CancelOrder(t *testing.T) {
64 | t.SkipNow()
65 |
66 | s := NewApiServiceFromEnv()
67 | rsp, err := s.CancelOrder(context.Background(), "order id")
68 | if err != nil {
69 | t.Fatal(err)
70 | }
71 | o := &CancelOrderResultModel{}
72 | if err := rsp.ReadData(o); err != nil {
73 | t.Fatal(err)
74 | }
75 | t.Log(ToJsonString(o))
76 | switch {
77 | case len(o.CancelledOrderIds) == 0:
78 | t.Error("Empty key 'cancelledOrderIds'")
79 | }
80 | }
81 |
82 | func TestApiService_CancelOrderByClient(t *testing.T) {
83 | t.SkipNow()
84 |
85 | s := NewApiServiceFromEnv()
86 | rsp, err := s.CancelOrderByClient(context.Background(), "client id")
87 | if err != nil {
88 | t.Fatal(err)
89 | }
90 | o := &CancelOrderByClientResultModel{}
91 | if err := rsp.ReadData(o); err != nil {
92 | t.Fatal(err)
93 | }
94 | t.Log(ToJsonString(o))
95 | switch {
96 | case len(o.CancelledOrderId) == 0:
97 | t.Error("Empty key 'cancelledOrderId'")
98 | case len(o.ClientOid) == 0:
99 | t.Error("Empty key 'clientOid'")
100 | }
101 | }
102 |
103 | func TestApiService_CancelOrders(t *testing.T) {
104 | t.SkipNow()
105 |
106 | s := NewApiServiceFromEnv()
107 | rsp, err := s.CancelOrders(context.Background(), map[string]string{
108 | "symbol": "ETH-BTC",
109 | "tradeType": "TRADE",
110 | })
111 | if err != nil {
112 | t.Fatal(err)
113 | }
114 | o := &CancelOrderResultModel{}
115 | if err := rsp.ReadData(o); err != nil {
116 | t.Fatal(err)
117 | }
118 | t.Log(ToJsonString(o))
119 | switch {
120 | case len(o.CancelledOrderIds) == 0:
121 | t.Error("Empty key 'cancelledOrderIds'")
122 | }
123 | }
124 |
125 | func TestApiService_Orders(t *testing.T) {
126 | s := NewApiServiceFromEnv()
127 | p := &PaginationParam{CurrentPage: 1, PageSize: 10}
128 | rsp, err := s.Orders(context.Background(), map[string]string{}, p)
129 | if err != nil {
130 | t.Fatal(err)
131 | }
132 |
133 | os := OrdersModel{}
134 | if _, err := rsp.ReadPaginationData(&os); err != nil {
135 | t.Fatal(err)
136 | }
137 | for _, o := range os {
138 | t.Log(ToJsonString(o))
139 | switch {
140 | case o.Id == "":
141 | t.Error("Empty key 'id'")
142 | case o.Symbol == "":
143 | t.Error("Empty key 'symbol'")
144 | case o.OpType == "":
145 | t.Error("Empty key 'opType'")
146 | case o.Type == "":
147 | t.Error("Empty key 'type'")
148 | case o.Side == "":
149 | t.Error("Empty key 'side'")
150 | }
151 | }
152 | }
153 |
154 | func TestApiService_V1Orders(t *testing.T) {
155 | t.SkipNow()
156 | s := NewApiServiceFromEnv()
157 | p := &PaginationParam{CurrentPage: 1, PageSize: 10}
158 | rsp, err := s.V1Orders(context.Background(), map[string]string{}, p)
159 | if err != nil {
160 | t.Fatal(err)
161 | }
162 |
163 | os := V1OrdersModel{}
164 | if _, err := rsp.ReadPaginationData(&os); err != nil {
165 | t.Fatal(err)
166 | }
167 | for _, o := range os {
168 | t.Log(ToJsonString(o))
169 | switch {
170 | case o.Symbol == "":
171 | t.Error("Empty key 'symbol'")
172 | case o.DealPrice == "":
173 | t.Error("Empty key 'dealPrice'")
174 | case o.DealValue == "":
175 | t.Error("Empty key 'dealValue'")
176 | case o.Amount == "":
177 | t.Error("Empty key 'amount'")
178 | case o.Fee == "":
179 | t.Error("Empty key 'fee'")
180 | case o.Side == "":
181 | t.Error("Empty key 'side'")
182 | }
183 | }
184 | }
185 |
186 | func TestApiService_Order(t *testing.T) {
187 | s := NewApiServiceFromEnv()
188 |
189 | p := &PaginationParam{CurrentPage: 1, PageSize: 10}
190 | rsp, err := s.Orders(context.Background(), map[string]string{}, p)
191 | if err != nil {
192 | t.Fatal(err)
193 | }
194 |
195 | os := OrdersModel{}
196 | if _, err := rsp.ReadPaginationData(&os); err != nil {
197 | t.Fatal(err)
198 | }
199 | if len(os) == 0 {
200 | t.SkipNow()
201 | }
202 |
203 | rsp, err = s.Order(context.Background(), os[0].Id)
204 | if err != nil {
205 | t.Fatal(err)
206 | }
207 |
208 | o := &OrderModel{}
209 | if err := rsp.ReadData(&o); err != nil {
210 | t.Fatal(err)
211 | }
212 | t.Log(ToJsonString(o))
213 | switch {
214 | case o.Id == "":
215 | t.Error("Empty key 'id'")
216 | case o.Symbol == "":
217 | t.Error("Empty key 'symbol'")
218 | case o.OpType == "":
219 | t.Error("Empty key 'opType'")
220 | case o.Type == "":
221 | t.Error("Empty key 'type'")
222 | case o.Side == "":
223 | t.Error("Empty key 'side'")
224 | }
225 | }
226 |
227 | func TestApiService_RecentOrders(t *testing.T) {
228 | s := NewApiServiceFromEnv()
229 | rsp, err := s.RecentOrders(context.Background())
230 | if err != nil {
231 | t.Fatal(err)
232 | }
233 |
234 | os := OrdersModel{}
235 | if err := rsp.ReadData(&os); err != nil {
236 | t.Fatal(err)
237 | }
238 | for _, o := range os {
239 | t.Log(ToJsonString(o))
240 | switch {
241 | case o.Id == "":
242 | t.Error("Empty key 'id'")
243 | case o.Symbol == "":
244 | t.Error("Empty key 'symbol'")
245 | case o.OpType == "":
246 | t.Error("Empty key 'opType'")
247 | case o.Type == "":
248 | t.Error("Empty key 'type'")
249 | case o.Side == "":
250 | t.Error("Empty key 'side'")
251 | }
252 | }
253 | }
254 |
255 | func TestApiService_OrderByClient(t *testing.T) {
256 | t.SkipNow()
257 |
258 | s := NewApiServiceFromEnv()
259 | rsp, err := s.OrderByClient(context.Background(), "client id")
260 | if err != nil {
261 | t.Fatal(err)
262 | }
263 |
264 | o := &OrderModel{}
265 | if err := rsp.ReadData(&o); err != nil {
266 | t.Fatal(err)
267 | }
268 | t.Log(ToJsonString(o))
269 | switch {
270 | case o.Id == "":
271 | t.Error("Empty key 'id'")
272 | case o.Symbol == "":
273 | t.Error("Empty key 'symbol'")
274 | case o.OpType == "":
275 | t.Error("Empty key 'opType'")
276 | case o.Type == "":
277 | t.Error("Empty key 'type'")
278 | case o.Side == "":
279 | t.Error("Empty key 'side'")
280 | }
281 | }
282 |
283 | func TestApiService_CreatMarginOrder(t *testing.T) {
284 | t.SkipNow()
285 |
286 | s := NewApiServiceFromEnv()
287 | p := &CreateOrderModel{
288 | ClientOid: IntToString(time.Now().UnixNano()),
289 | Side: "buy",
290 | Symbol: "BTC-USDT",
291 | Price: "1",
292 | Size: "1",
293 | }
294 | rsp, err := s.CreateMarginOrder(context.Background(), p)
295 | if err != nil {
296 | t.Fatal(err)
297 | }
298 | o := &CreateOrderResultModel{}
299 | if err := rsp.ReadData(o); err != nil {
300 | t.Fatal(err)
301 | }
302 | t.Log(ToJsonString(o))
303 | switch {
304 | case o.OrderId == "":
305 | t.Error("Empty key 'OrderId'")
306 | }
307 | }
308 |
309 | func TestApiService_CreateStopOrder(t *testing.T) {
310 | t.SkipNow()
311 |
312 | s := NewApiServiceFromEnv()
313 | p := &CreateOrderModel{
314 | ClientOid: IntToString(time.Now().UnixNano()),
315 | Side: "buy",
316 | Symbol: "BTC-USDT",
317 | Price: "1",
318 | Size: "1",
319 | StopPrice: "10.0",
320 | }
321 | rsp, err := s.CreateStopOrder(context.Background(), p)
322 | if err != nil {
323 | t.Fatal(err)
324 | }
325 | o := &CreateOrderResultModel{}
326 | if err := rsp.ReadData(o); err != nil {
327 | t.Fatal(err)
328 | }
329 | t.Log(ToJsonString(o))
330 | switch {
331 | case o.OrderId == "":
332 | t.Error("Empty key 'OrderId'")
333 | }
334 | }
335 | func TestApiService_CancelStopOrder(t *testing.T) {
336 | t.SkipNow()
337 |
338 | s := NewApiServiceFromEnv()
339 | rsp, err := s.CancelStopOrder(context.Background(), "xxxxx")
340 | if err != nil {
341 | t.Fatal(err)
342 | }
343 | o := &CancelOrderResultModel{}
344 | if err := rsp.ReadData(o); err != nil {
345 | t.Fatal(err)
346 | }
347 | t.Log(ToJsonString(o))
348 | }
349 |
350 | func TestApiService_CancelStopOrderBy(t *testing.T) {
351 | t.SkipNow()
352 |
353 | s := NewApiServiceFromEnv()
354 | rsp, err := s.CancelStopOrderBy(context.Background(), map[string]string{"orderId": "xxxx"})
355 | if err != nil {
356 | t.Fatal(err)
357 | }
358 | o := &CancelOrderResultModel{}
359 | if err := rsp.ReadData(o); err != nil {
360 | t.Fatal(err)
361 | }
362 | t.Log(ToJsonString(o))
363 | }
364 |
365 | func TestApiService_StopOrder(t *testing.T) {
366 | s := NewApiServiceFromEnv()
367 | rsp, err := s.StopOrder(context.Background(), "vs8hoo98rathe2ak003ag5t9")
368 | if err != nil {
369 | t.Fatal(err)
370 | }
371 | o := &StopOrderModel{}
372 | if err := rsp.ReadData(o); err != nil {
373 | t.Fatal(err)
374 | }
375 | t.Log(ToJsonString(o))
376 | }
377 |
378 | func TestApiService_StopOrderByClient(t *testing.T) {
379 | s := NewApiServiceFromEnv()
380 | rsp, err := s.StopOrderByClient(context.Background(), "1112", map[string]string{})
381 | if err != nil {
382 | t.Fatal(err)
383 | }
384 | o := &StopOrderListModel{}
385 | t.Log(ToJsonString(rsp))
386 | if err := rsp.ReadData(o); err != nil {
387 | t.Fatal(err)
388 | }
389 | t.Log(ToJsonString(o))
390 | }
391 |
392 | func TestApiService_CancelStopOrderByClient(t *testing.T) {
393 | s := NewApiServiceFromEnv()
394 | rsp, err := s.CancelStopOrderByClient(context.Background(), "1112", map[string]string{})
395 | if err != nil {
396 | t.Fatal(err)
397 | }
398 | o := &CancelStopOrderByClientModel{}
399 | t.Log(ToJsonString(rsp))
400 | if err := rsp.ReadData(o); err != nil {
401 | t.Fatal(err)
402 | }
403 | t.Log(ToJsonString(o))
404 | }
405 |
406 | func TestApiService_CreateOcoOrderModel(t *testing.T) {
407 |
408 | s := NewApiServiceFromEnv()
409 | p := &CreateOcoOrderModel{
410 | Side: "buy",
411 | Symbol: "BTC-USDT",
412 | Price: "1",
413 | Size: "1",
414 | StopPrice: "100000",
415 | LimitPrice: "100002",
416 | TradeType: "TRADE",
417 | ClientOid: IntToString(time.Now().UnixNano()),
418 | Remark: "xx",
419 | }
420 | rsp, err := s.CreateOcoOrder(context.Background(), p)
421 | if err != nil {
422 | t.Fatal(err)
423 | }
424 | o := &CreateOrderResultModel{}
425 | if err := rsp.ReadData(o); err != nil {
426 | t.Fatal(err)
427 | }
428 | t.Log(ToJsonString(o))
429 | switch {
430 | case o.OrderId == "":
431 | t.Error("Empty key 'OrderId'")
432 | }
433 | }
434 |
435 | func TestApiService_DeleteOcoOrder(t *testing.T) {
436 |
437 | s := NewApiServiceFromEnv()
438 | rsp, err := s.DeleteOcoOrder(context.Background(), "65d1c7042e6db70007e639b2")
439 | if err != nil {
440 | t.Fatal(err)
441 | }
442 | o := &CancelledOcoOrderResModel{}
443 | if err := rsp.ReadData(o); err != nil {
444 | t.Fatal(err)
445 | }
446 | t.Log(ToJsonString(o))
447 | switch {
448 | case len(o.CancelledOrderIds) == 0:
449 | t.Error("Empty key 'cancelledOrderIds'")
450 | }
451 | }
452 |
453 | func TestApiService_DeleteOcoOrderClientId(t *testing.T) {
454 |
455 | s := NewApiServiceFromEnv()
456 | rsp, err := s.DeleteOcoOrderClientId(context.Background(), "order client id")
457 | if err != nil {
458 | t.Fatal(err)
459 | }
460 | o := &CancelledOcoOrderResModel{}
461 | if err := rsp.ReadData(o); err != nil {
462 | t.Fatal(err)
463 | }
464 | t.Log(ToJsonString(o))
465 | switch {
466 | case len(o.CancelledOrderIds) == 0:
467 | t.Error("Empty key 'cancelledOrderIds'")
468 | }
469 | }
470 |
471 | func TestApiService_DeleteOcoOrders(t *testing.T) {
472 |
473 | s := NewApiServiceFromEnv()
474 | rsp, err := s.DeleteOcoOrders(context.Background(), "BTC-USDT", "")
475 | if err != nil {
476 | t.Fatal(err)
477 | }
478 | o := &CancelledOcoOrderResModel{}
479 | if err := rsp.ReadData(o); err != nil {
480 | t.Fatal(err)
481 | }
482 | t.Log(ToJsonString(o))
483 | switch {
484 | case len(o.CancelledOrderIds) == 0:
485 | t.Error("Empty key 'cancelledOrderIds'")
486 | }
487 | }
488 |
489 | func TestApiService_OcoOrderDetail(t *testing.T) {
490 |
491 | s := NewApiServiceFromEnv()
492 | rsp, err := s.OcoOrderDetail(context.Background(), "65d1c7042e6db70007e639b2")
493 | if err != nil {
494 | t.Fatal(err)
495 | }
496 |
497 | o := &OrderDetailModel{}
498 | if err := rsp.ReadData(&o); err != nil {
499 | t.Fatal(err)
500 | }
501 | t.Log(ToJsonString(o))
502 | }
503 |
504 | func TestApiService_OcoOrder(t *testing.T) {
505 |
506 | s := NewApiServiceFromEnv()
507 | rsp, err := s.OcoOrder(context.Background(), "65d1c7042e6db70007e639b2")
508 | if err != nil {
509 | t.Fatal(err)
510 | }
511 |
512 | o := &OcoOrderResModel{}
513 | if err := rsp.ReadData(&o); err != nil {
514 | t.Fatal(err)
515 | }
516 | t.Log(ToJsonString(o))
517 | }
518 |
519 | func TestApiService_OcoClientOrder(t *testing.T) {
520 |
521 | s := NewApiServiceFromEnv()
522 | rsp, err := s.OcoClientOrder(context.Background(), "1708246787246002000")
523 | if err != nil {
524 | t.Fatal(err)
525 | }
526 |
527 | o := &OcoOrderResModel{}
528 | if err := rsp.ReadData(&o); err != nil {
529 | t.Fatal(err)
530 | }
531 | t.Log(ToJsonString(o))
532 | }
533 |
534 | func TestApiService_OcoOrders(t *testing.T) {
535 |
536 | s := NewApiServiceFromEnv()
537 | p2 := &PaginationParam{CurrentPage: 1, PageSize: 10}
538 | p1 := map[string]string{
539 | "symbol": "BTC-USDT",
540 | }
541 | rsp, err := s.OcoOrders(context.Background(), p1, p2)
542 | if err != nil {
543 | t.Fatal(err)
544 | }
545 |
546 | o := &OcoOrdersModel{}
547 | if _, err := rsp.ReadPaginationData(&o); err != nil {
548 | t.Fatal(err)
549 | }
550 | t.Log(ToJsonString(o))
551 | }
552 |
--------------------------------------------------------------------------------
/account_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestApiService_Accounts(t *testing.T) {
11 | s := NewApiServiceFromEnv()
12 | rsp, err := s.Accounts(context.Background(), "", "")
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 | cl := AccountsModel{}
17 | if err := rsp.ReadData(&cl); err != nil {
18 | t.Fatal(err)
19 | }
20 | for _, c := range cl {
21 | t.Log(ToJsonString(c))
22 | switch {
23 | case c.Id == "":
24 | t.Error("Empty key 'id'")
25 | case c.Currency == "":
26 | t.Error("Empty key 'currency'")
27 | case c.Type == "":
28 | t.Error("Empty key 'type'")
29 | case c.Balance == "":
30 | t.Error("Empty key 'balance'")
31 | case c.Available == "":
32 | t.Error("Empty key 'available'")
33 | }
34 | }
35 | }
36 |
37 | func TestApiService_Account(t *testing.T) {
38 | s := NewApiServiceFromEnv()
39 | rsp, err := s.Accounts(context.Background(), "", "")
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | cl := AccountsModel{}
44 | if err := rsp.ReadData(&cl); err != nil {
45 | t.Fatal(err)
46 | }
47 | if len(cl) == 0 {
48 | return
49 | }
50 | rsp, err = s.Account(context.Background(), cl[0].Id)
51 | if err != nil {
52 | t.Fatal(err)
53 | }
54 | a := &AccountModel{}
55 | if err := rsp.ReadData(a); err != nil {
56 | t.Fatal(err)
57 | }
58 | t.Log(ToJsonString(a))
59 | switch {
60 | case a.Currency == "":
61 | t.Error("Empty key 'currency'")
62 | case a.Holds == "":
63 | t.Error("Empty key 'holds'")
64 | case a.Balance == "":
65 | t.Error("Empty key 'balance'")
66 | case a.Available == "":
67 | t.Error("Empty key 'available'")
68 | }
69 | }
70 |
71 | func TestApiService_SubAccountUsers(t *testing.T) {
72 | s := NewApiServiceFromEnv()
73 | rsp, err := s.SubAccountUsers(context.Background())
74 | if err != nil {
75 | t.Fatal(err)
76 | }
77 | cl := SubAccountUsersModel{}
78 | if err := rsp.ReadData(&cl); err != nil {
79 | t.Fatal(err)
80 | }
81 | if len(cl) == 0 {
82 | return
83 | }
84 | for _, c := range cl {
85 | t.Log(ToJsonString(c))
86 | switch {
87 | case c.UserId == "":
88 | t.Error("Empty key 'userId'")
89 | case c.SubName == "":
90 | t.Error("Empty key 'subName'")
91 | }
92 | }
93 | }
94 |
95 | func TestApiService_SubAccounts(t *testing.T) {
96 | s := NewApiServiceFromEnv()
97 | rsp, err := s.SubAccounts(context.Background())
98 | if err != nil {
99 | t.Fatal(err)
100 | }
101 | cl := SubAccountsModel{}
102 | if err := rsp.ReadData(&cl); err != nil {
103 | t.Fatal(err)
104 | }
105 | if len(cl) == 0 {
106 | return
107 | }
108 | for _, c := range cl {
109 | t.Log(ToJsonString(c))
110 | switch {
111 | case c.SubUserId == "":
112 | t.Error("Empty key 'subUserId'")
113 | case c.SubName == "":
114 | t.Error("Empty key 'subName'")
115 | }
116 | for _, b := range c.MainAccounts {
117 | switch {
118 | case b.Currency == "":
119 | t.Error("Empty key 'currency'")
120 | }
121 | }
122 | for _, b := range c.TradeAccounts {
123 | switch {
124 | case b.Currency == "":
125 | t.Error("Empty key 'currency'")
126 | }
127 | }
128 | }
129 | }
130 |
131 | func TestApiService_SubAccount(t *testing.T) {
132 | s := NewApiServiceFromEnv()
133 |
134 | ctx := context.Background()
135 |
136 | rsp, err := s.SubAccounts(context.Background())
137 | if err != nil {
138 | t.Fatal(err)
139 | }
140 | cl := SubAccountsModel{}
141 | if err := rsp.ReadData(&cl); err != nil {
142 | t.Fatal(err)
143 | }
144 | if len(cl) == 0 {
145 | return
146 | }
147 | rsp, err = s.SubAccount(ctx, cl[0].SubUserId)
148 | if err != nil {
149 | t.Fatal(err)
150 | }
151 | a := SubAccountModel{}
152 | if err := rsp.ReadData(&a); err != nil {
153 | t.Fatal(err)
154 | }
155 | t.Log(ToJsonString(a))
156 | switch {
157 | case a.SubUserId == "":
158 | t.Error("Empty key 'subUserId'")
159 | case a.SubName == "":
160 | t.Error("Empty key 'subName'")
161 | }
162 | for _, b := range a.MainAccounts {
163 | switch {
164 | case b.Currency == "":
165 | t.Error("Empty key 'currency'")
166 | }
167 | }
168 | for _, b := range a.TradeAccounts {
169 | switch {
170 | case b.Currency == "":
171 | t.Error("Empty key 'currency'")
172 | }
173 | }
174 | }
175 |
176 | func TestApiService_AccountsTransferable(t *testing.T) {
177 | s := NewApiServiceFromEnv()
178 | rsp, err := s.AccountsTransferable(context.Background(), "MATIC", "MAIN")
179 | if err != nil {
180 | t.Fatal(err)
181 | }
182 | a := &AccountsTransferableModel{}
183 | if err := rsp.ReadData(a); err != nil {
184 | t.Fatal(err)
185 | }
186 | t.Log(ToJsonString(a))
187 | }
188 |
189 | func TestApiService_CreateAccount(t *testing.T) {
190 | t.SkipNow()
191 |
192 | s := NewApiServiceFromEnv()
193 | rsp, err := s.CreateAccount(context.Background(), "trade", "BTC")
194 | if err != nil {
195 | t.Log(fmt.Sprintf("Create account failed: %s, %s", rsp.Code, rsp.Message))
196 | t.Fatal(err)
197 | }
198 | if rsp.Code == "230005" {
199 | t.Log(fmt.Sprintf("Account exits: %s, %s", rsp.Code, rsp.Message))
200 | return
201 | }
202 | a := &CreateAccountModel{}
203 | if err := rsp.ReadData(a); err != nil {
204 | t.Fatal(err)
205 | }
206 | t.Log(a.Id)
207 | switch {
208 | case a.Id == "":
209 | t.Error("Empty key 'id'")
210 | }
211 | }
212 |
213 | func TestApiService_AccountLedgers(t *testing.T) {
214 | s := NewApiServiceFromEnv()
215 |
216 | ctx := context.Background()
217 |
218 | rsp, err := s.Accounts(ctx, "", "")
219 | if err != nil {
220 | t.Fatal(err)
221 | }
222 | l := AccountsModel{}
223 | if err := rsp.ReadData(&l); err != nil {
224 | t.Fatal(err)
225 | }
226 | if len(l) == 0 {
227 | return
228 | }
229 | p := &PaginationParam{CurrentPage: 1, PageSize: 10}
230 | rsp, err = s.AccountLedgersV2(ctx, map[string]string{}, p)
231 | if err != nil {
232 | t.Fatal(err)
233 | }
234 | hs := AccountLedgersModel{}
235 | if _, err := rsp.ReadPaginationData(&hs); err != nil {
236 | t.Fatal(err)
237 | }
238 | for _, h := range hs {
239 | t.Log(ToJsonString(h))
240 | switch {
241 | case h.Currency == "":
242 | t.Error("Empty key 'currency'")
243 | case h.Amount == "":
244 | t.Error("Empty key 'amount'")
245 | case h.Fee == "":
246 | t.Error("Empty key 'fee'")
247 | case h.Balance == "":
248 | t.Error("Empty key 'balance'")
249 | case h.BizType == "":
250 | t.Error("Empty key 'bizType'")
251 | case h.Direction == "":
252 | t.Error("Empty key 'direction'")
253 | case h.CreatedAt == 0:
254 | t.Error("Empty key 'createdAt'")
255 | }
256 | }
257 | }
258 |
259 | func TestApiService_AccountHolds(t *testing.T) {
260 | s := NewApiServiceFromEnv()
261 |
262 | ctx := context.Background()
263 |
264 | rsp, err := s.Accounts(ctx, "", "")
265 | if err != nil {
266 | t.Fatal(err)
267 | }
268 | l := AccountsModel{}
269 | if err := rsp.ReadData(&l); err != nil {
270 | t.Fatal(err)
271 | }
272 | if len(l) == 0 {
273 | return
274 | }
275 | p := &PaginationParam{CurrentPage: 1, PageSize: 10}
276 | rsp, err = s.AccountHolds(ctx, l[0].Id, p)
277 | if err != nil {
278 | t.Fatal(err)
279 | }
280 | hs := AccountHoldsModel{}
281 | if _, err := rsp.ReadPaginationData(&hs); err != nil {
282 | t.Fatal(err)
283 | }
284 | for _, h := range hs {
285 | t.Log(ToJsonString(h))
286 | switch {
287 | case h.Currency == "":
288 | t.Error("Empty key 'currency'")
289 | case h.HoldAmount == "":
290 | t.Error("Empty key 'holdAmount'")
291 | case h.BizType == "":
292 | t.Error("Empty key 'bizType'")
293 | case h.OrderId == "":
294 | t.Error("Empty key 'orderId'")
295 | case h.CreatedAt == 0:
296 | t.Error("Empty key 'createdAt'")
297 | case h.UpdatedAt == 0:
298 | t.Error("Empty key 'updatedAt'")
299 | }
300 | }
301 | }
302 |
303 | func TestApiService_InnerTransferV2(t *testing.T) {
304 | t.SkipNow()
305 |
306 | s := NewApiServiceFromEnv()
307 | clientOid := IntToString(time.Now().Unix())
308 | rsp, err := s.InnerTransferV2(context.Background(), clientOid, "KCS", "main", "trade", "2")
309 | if err != nil {
310 | t.Fatal(err)
311 | }
312 | v := &InnerTransferResultModel{}
313 | if err := rsp.ReadData(v); err != nil {
314 | t.Fatal(err)
315 | }
316 | t.Log(ToJsonString(v))
317 | if v.OrderId == "" {
318 | t.Error("Empty key 'orderId'")
319 | }
320 | }
321 |
322 | func TestApiService_SubTransferV2(t *testing.T) {
323 | t.SkipNow()
324 |
325 | s := NewApiServiceFromEnv()
326 | clientOid := IntToString(time.Now().Unix())
327 | p := map[string]string{
328 | "clientOid": clientOid,
329 | "currency": "MATIC",
330 | "amount": "9",
331 | "direction": "OUT",
332 | "accountType": "MAIN",
333 | "subAccountType": "MAIN",
334 | "subUserId": "6482f1e32ba86200010eb03e",
335 | }
336 | rsp, err := s.SubTransferV2(context.Background(), p)
337 | if err != nil {
338 | t.Fatal(err)
339 | }
340 | v := &SubTransferResultModel{}
341 | if err := rsp.ReadData(v); err != nil {
342 | t.Fatal(err)
343 | }
344 | if v.OrderId == "" {
345 | t.Error("Empty key 'orderId'")
346 | }
347 | }
348 |
349 | func TestBaseFee(t *testing.T) {
350 | s := NewApiServiceFromEnv()
351 | rsp, err := s.BaseFee(context.Background(), "1")
352 | if err != nil {
353 | t.Fatal(err)
354 | }
355 |
356 | v := &BaseFeeModel{}
357 | if err := rsp.ReadData(v); err != nil {
358 | t.Fatal(err)
359 | }
360 |
361 | t.Log(v)
362 | t.Log(ToJsonString(v))
363 | }
364 |
365 | func TestActualFee(t *testing.T) {
366 | s := NewApiServiceFromEnv()
367 | rsp, err := s.ActualFee(context.Background(), "BTC-USDT")
368 | if err != nil {
369 | t.Fatal(err)
370 | }
371 |
372 | v := &TradeFeesResultModel{}
373 | if err := rsp.ReadData(v); err != nil {
374 | t.Fatal(err)
375 | }
376 |
377 | t.Log(v)
378 | t.Log(ToJsonString(v))
379 | }
380 |
381 | func TestApiService_SubAccountUsersV2(t *testing.T) {
382 | s := NewApiServiceFromEnv()
383 | pp := PaginationParam{
384 | CurrentPage: 1,
385 | PageSize: 2,
386 | }
387 | rsp, err := s.SubAccountUsersV2(context.Background(), &pp)
388 | if err != nil {
389 | t.Fatal(err)
390 | }
391 | cl := SubAccountUsersModelV2{}
392 | if _, err := rsp.ReadPaginationData(&cl); err != nil {
393 | t.Fatal(err)
394 | }
395 | if len(cl) == 0 {
396 | return
397 | }
398 | for _, c := range cl {
399 | t.Log(ToJsonString(c))
400 | switch {
401 | case c.UserId == "":
402 | t.Error("Empty key 'userId'")
403 | case c.SubName == "":
404 | t.Error("Empty key 'subName'")
405 | }
406 | }
407 | }
408 |
409 | func TestApiService_UserInfoV2(t *testing.T) {
410 | s := NewApiServiceFromEnv()
411 | rsp, err := s.UserSummaryInfoV2(context.Background())
412 | if err != nil {
413 | t.Fatal(err)
414 | }
415 | cl := UserSummaryInfoModelV2{}
416 | if err := rsp.ReadData(&cl); err != nil {
417 | t.Fatal(err)
418 | }
419 | t.Log(cl)
420 | t.Log(ToJsonString(cl))
421 | }
422 |
423 | func TestApiService_CreateSubAccountV2(t *testing.T) {
424 | subName := "marginFen1991"
425 | s := NewApiServiceFromEnv()
426 | rsp, err := s.CreateSubAccountV2(context.Background(), "Youaremine1314.", "", subName, "Margin")
427 | if err != nil {
428 | t.Fatal(err)
429 | }
430 | cl := CreateSubAccountV2Res{}
431 | if err := rsp.ReadData(&cl); err != nil {
432 | t.Fatal(err)
433 | }
434 | t.Log(cl)
435 | t.Log(ToJsonString(cl))
436 |
437 | if cl.SubName != subName {
438 | t.Error("Create sub account v2 fail")
439 | }
440 | }
441 |
442 | func TestApiService_SubApiKey(t *testing.T) {
443 | subName := "TestSubAccount1Fen"
444 | s := NewApiServiceFromEnv()
445 | rsp, err := s.SubApiKey(context.Background(), subName, "")
446 | if err != nil {
447 | t.Fatal(err)
448 | }
449 | cl := SubApiKeyRes{}
450 | if err := rsp.ReadData(&cl); err != nil {
451 | t.Fatal(err)
452 | }
453 | t.Log(cl)
454 | t.Log(ToJsonString(cl))
455 | }
456 |
457 | func TestApiService_CreateSubApiKey(t *testing.T) {
458 | t.SkipNow()
459 | s := NewApiServiceFromEnv()
460 | rsp, err := s.CreateSubApiKey(context.Background(), "TestSubAccount3Fen", "123abcABC", "3", "General", "", "")
461 | if err != nil {
462 | t.Fatal(err)
463 | }
464 | cl := CreateSubApiKeyRes{}
465 | if err := rsp.ReadData(&cl); err != nil {
466 | t.Fatal(err)
467 | }
468 | t.Log(cl)
469 | t.Log(ToJsonString(cl))
470 | }
471 |
472 | func TestApiService_UpdateSubApiKey(t *testing.T) {
473 | t.SkipNow()
474 | s := NewApiServiceFromEnv()
475 | rsp, err := s.UpdateSubApiKey(context.Background(), "TestSubAccount1Fen", "123abcABC", "648804c835848e0001690fb9", "Trade", "", "30")
476 | if err != nil {
477 | t.Fatal(err)
478 | }
479 | cl := UpdateSubApiKeyRes{}
480 | if err := rsp.ReadData(&cl); err != nil {
481 | t.Fatal(err)
482 | }
483 | t.Log(cl)
484 | t.Log(ToJsonString(cl))
485 | }
486 |
487 | func TestApiService_DeleteSubApiKey(t *testing.T) {
488 | t.SkipNow()
489 | s := NewApiServiceFromEnv()
490 | rsp, err := s.DeleteSubApiKey(context.Background(), "TestSubAccount3Fen", "123abcABC", "6497fc7c19a9ea0001d7ac46")
491 | if err != nil {
492 | t.Fatal(err)
493 | }
494 | cl := UpdateSubApiKeyRes{}
495 | if err := rsp.ReadData(&cl); err != nil {
496 | t.Fatal(err)
497 | }
498 | t.Log(cl)
499 | t.Log(ToJsonString(cl))
500 | }
501 |
502 | func TestApiService_SubAccountsV2(t *testing.T) {
503 | s := NewApiServiceFromEnv()
504 |
505 | pp := PaginationParam{
506 | CurrentPage: 1,
507 | PageSize: 10,
508 | }
509 | rsp, err := s.SubAccountsV2(context.Background(), &pp)
510 | if err != nil {
511 | t.Fatal(err)
512 | }
513 | cl := SubAccountsModel{}
514 | if _, err := rsp.ReadPaginationData(&cl); err != nil {
515 | t.Fatal(err)
516 | }
517 | if len(cl) == 0 {
518 | return
519 | }
520 | for _, c := range cl {
521 | t.Log(ToJsonString(c))
522 | switch {
523 | case c.SubUserId == "":
524 | t.Error("Empty key 'subUserId'")
525 | case c.SubName == "":
526 | t.Error("Empty key 'subName'")
527 | }
528 | for _, b := range c.MainAccounts {
529 | switch {
530 | case b.Currency == "":
531 | t.Error("Empty key 'currency'")
532 | }
533 | }
534 | for _, b := range c.TradeAccounts {
535 | switch {
536 | case b.Currency == "":
537 | t.Error("Empty key 'currency'")
538 | }
539 | }
540 | }
541 | }
542 |
543 | func TestApiService_UniversalTransfer(t *testing.T) {
544 | p := &UniversalTransferReq{
545 | ClientOid: IntToString(time.Now().Unix()),
546 | Type: "INTERNAL",
547 | Currency: "USDT",
548 | Amount: "5",
549 | FromAccountType: "TRADE",
550 | ToAccountType: "CONTRACT",
551 | }
552 | s := NewApiServiceFromEnv()
553 | rsp, err := s.UniversalTransfer(context.Background(), p)
554 | if err != nil {
555 | t.Fatal(err)
556 | }
557 | v := &UniversalTransferRes{}
558 | if err := rsp.ReadData(v); err != nil {
559 | t.Fatal(err)
560 | }
561 | t.Log(ToJsonString(v))
562 | if v.OrderId == "" {
563 | t.Error("Empty key 'orderId'")
564 | }
565 | }
566 |
--------------------------------------------------------------------------------
/hf_order.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "net/http"
8 |
9 | "github.com/google/go-querystring/query"
10 | )
11 |
12 | // HfPlaceOrder There are two types of orders:
13 | // (limit) order: set price and quantity for the transaction.
14 | // (market) order : set amount or quantity for the transaction.
15 | func (as *ApiService) HfPlaceOrder(ctx context.Context, params map[string]string) (*ApiResponse, error) {
16 | req := NewRequest(http.MethodPost, "/api/v1/hf/orders", params)
17 | return as.Call(ctx, req)
18 | }
19 |
20 | // HfSyncPlaceOrder The difference between this interface
21 | // and "Place hf order" is that this interface will synchronously
22 | // return the order information after the order matching is completed.
23 | // For higher latency requirements, please select the "Place hf order" interface.
24 | // If there is a requirement for returning data integrity, please select this interface
25 | func (as *ApiService) HfSyncPlaceOrder(ctx context.Context, params map[string]string) (*ApiResponse, error) {
26 | req := NewRequest(http.MethodPost, "/api/v1/hf/orders/sync", params)
27 | return as.Call(ctx, req)
28 | }
29 |
30 | // HfPlaceMultiOrders This endpoint supports sequential batch order placement from a single endpoint.
31 | // A maximum of 5orders can be placed simultaneously.
32 | // The order types must be limit orders of the same trading pair
33 | // (this endpoint currently only supports spot trading and does not support margin trading)
34 | func (as *ApiService) HfPlaceMultiOrders(ctx context.Context, orders []*HFCreateMultiOrderModel) (*ApiResponse, error) {
35 | p := map[string]interface{}{
36 | "orderList": orders,
37 | }
38 | req := NewRequest(http.MethodPost, "/api/v1/hf/orders/multi", p)
39 | return as.Call(ctx, req)
40 | }
41 |
42 | // HfSyncPlaceMultiOrders The request parameters of this interface
43 | // are the same as those of the "Sync place multiple hf orders" interface
44 | // The difference between this interface and "Sync place multiple hf orders" is that
45 | // this interface will synchronously return the order information after the order matching is completed.
46 | func (as *ApiService) HfSyncPlaceMultiOrders(ctx context.Context, orders []*HFCreateMultiOrderModel) (*ApiResponse, error) {
47 | p := map[string]interface{}{
48 | "orderList": orders,
49 | }
50 | req := NewRequest(http.MethodPost, "/api/v1/hf/orders/multi/sync", p)
51 | return as.Call(ctx, req)
52 | }
53 |
54 | type HfSyncPlaceMultiOrdersRes []*HfSyncPlaceOrderRes
55 |
56 | // HfModifyOrder
57 | // This interface can modify the price and quantity of the order according to orderId or clientOid.
58 | func (as *ApiService) HfModifyOrder(ctx context.Context, params map[string]string) (*ApiResponse, error) {
59 | req := NewRequest(http.MethodPost, "/api/v1/hf/orders/alter", params)
60 | return as.Call(ctx, req)
61 | }
62 |
63 | // HfCancelOrder This endpoint can be used to cancel a high-frequency order by orderId.
64 | func (as *ApiService) HfCancelOrder(ctx context.Context, orderId, symbol string) (*ApiResponse, error) {
65 | p := map[string]string{
66 | "symbol": symbol,
67 | }
68 | req := NewRequest(http.MethodDelete, "/api/v1/hf/orders/"+orderId, p)
69 | return as.Call(ctx, req)
70 | }
71 |
72 | // HfSyncCancelOrder The difference between this interface and "Cancel orders by orderId" is that
73 | // this interface will synchronously return the order information after the order canceling is completed.
74 | func (as *ApiService) HfSyncCancelOrder(ctx context.Context, orderId, symbol string) (*ApiResponse, error) {
75 | p := map[string]string{
76 | "symbol": symbol,
77 | }
78 | req := NewRequest(http.MethodDelete, "/api/v1/hf/orders/sync/"+orderId, p)
79 | return as.Call(ctx, req)
80 | }
81 |
82 | // HfCancelOrderByClientId This endpoint sends out a request to cancel a high-frequency order using clientOid.
83 | func (as *ApiService) HfCancelOrderByClientId(ctx context.Context, clientOid, symbol string) (*ApiResponse, error) {
84 | p := map[string]string{
85 | "symbol": symbol,
86 | }
87 | req := NewRequest(http.MethodDelete, "/api/v1/hf/orders/client-order/"+clientOid, p)
88 | return as.Call(ctx, req)
89 | }
90 |
91 | // HfSyncCancelOrderByClientId The difference between this interface and "Cancellation of order by clientOid"
92 | // is that this interface will synchronously return the order information after the order canceling is completed.
93 | func (as *ApiService) HfSyncCancelOrderByClientId(ctx context.Context, clientOid, symbol string) (*ApiResponse, error) {
94 | p := map[string]string{
95 | "symbol": symbol,
96 | }
97 | req := NewRequest(http.MethodDelete, "/api/v1/hf/orders/sync/client-order/"+clientOid, p)
98 | return as.Call(ctx, req)
99 | }
100 |
101 | // HfSyncCancelOrderWithSize This interface can cancel the specified quantity of the order according to the orderId.
102 | func (as *ApiService) HfSyncCancelOrderWithSize(ctx context.Context, orderId, symbol, cancelSize string) (*ApiResponse, error) {
103 | p := map[string]string{
104 | "symbol": symbol,
105 | "cancelSize": cancelSize,
106 | }
107 | req := NewRequest(http.MethodDelete, "/api/v1/hf/orders/cancel/"+orderId, p)
108 | return as.Call(ctx, req)
109 | }
110 |
111 | // HfSyncCancelAllOrders his endpoint allows cancellation of all orders related to a specific trading pair
112 | // with a status of open
113 | // (including all orders pertaining to high-frequency trading accounts and non-high-frequency trading accounts)
114 | func (as *ApiService) HfSyncCancelAllOrders(ctx context.Context, symbol string) (*ApiResponse, error) {
115 | p := map[string]string{
116 | "symbol": symbol,
117 | }
118 | req := NewRequest(http.MethodDelete, "/api/v1/hf/orders", p)
119 | return as.Call(ctx, req)
120 | }
121 |
122 | // HfObtainActiveOrders This endpoint obtains a list of all active HF orders.
123 | // The return data is sorted in descending order based on the latest update times.
124 | func (as *ApiService) HfObtainActiveOrders(ctx context.Context, symbol string) (*ApiResponse, error) {
125 | p := map[string]string{
126 | "symbol": symbol,
127 | }
128 | req := NewRequest(http.MethodGet, "/api/v1/hf/orders/active", p)
129 | return as.Call(ctx, req)
130 | }
131 |
132 | // HfObtainActiveSymbols This interface can query all trading pairs that the user has active orders
133 | func (as *ApiService) HfObtainActiveSymbols(ctx context.Context) (*ApiResponse, error) {
134 | req := NewRequest(http.MethodGet, "/api/v1/hf/orders/active/symbols", nil)
135 | return as.Call(ctx, req)
136 | }
137 |
138 | type HfSymbolsModel struct {
139 | Symbols []string `json:"symbols"`
140 | }
141 |
142 | // HfObtainFilledOrders This endpoint obtains a list of filled HF orders and returns paginated data.
143 | // The returned data is sorted in descending order based on the latest order update times.
144 | func (as *ApiService) HfObtainFilledOrders(ctx context.Context, p map[string]string) (*ApiResponse, error) {
145 | req := NewRequest(http.MethodGet, "/api/v1/hf/orders/done", p)
146 | return as.Call(ctx, req)
147 | }
148 |
149 | type HfFilledOrdersModel struct {
150 | LastId json.Number `json:"lastId"`
151 | Items []*HfOrderModel `json:"items"`
152 | }
153 |
154 | // HfOrderDetail This endpoint can be used to obtain information for a single HF order using the order id.
155 | func (as *ApiService) HfOrderDetail(ctx context.Context, orderId, symbol string) (*ApiResponse, error) {
156 | p := map[string]string{
157 | "symbol": symbol,
158 | }
159 | req := NewRequest(http.MethodGet, "/api/v1/hf/orders/"+orderId, p)
160 | return as.Call(ctx, req)
161 | }
162 |
163 | // HfOrderDetailByClientOid The endpoint can be used to obtain information about a single order using clientOid.
164 | // If the order does not exist, then there will be a prompt saying that the order does not exist.
165 | func (as *ApiService) HfOrderDetailByClientOid(ctx context.Context, clientOid, symbol string) (*ApiResponse, error) {
166 | p := map[string]string{
167 | "symbol": symbol,
168 | }
169 | req := NewRequest(http.MethodGet, "/api/v1/hf/orders/client-order/"+clientOid, p)
170 | return as.Call(ctx, req)
171 | }
172 |
173 | // HfAutoCancelSetting automatically cancel all orders of the set trading pair after the specified time.
174 | // If this interface is not called again for renewal or cancellation before the set time,
175 | // the system will help the user to cancel the order of the corresponding trading pair.
176 | // otherwise it will not.
177 | func (as *ApiService) HfAutoCancelSetting(ctx context.Context, timeout int64, symbol string) (*ApiResponse, error) {
178 | p := map[string]interface{}{
179 | "symbol": symbol,
180 | "timeout": timeout,
181 | }
182 | req := NewRequest(http.MethodPost, "/api/v1/hf/orders/dead-cancel-all", p)
183 | return as.Call(ctx, req)
184 | }
185 |
186 | // HfQueryAutoCancelSetting Through this interface, you can query the settings of automatic order cancellation
187 | func (as *ApiService) HfQueryAutoCancelSetting(ctx context.Context) (*ApiResponse, error) {
188 | req := NewRequest(http.MethodGet, "/api/v1/hf/orders/dead-cancel-all/query", nil)
189 | return as.Call(ctx, req)
190 | }
191 |
192 | // HfTransactionDetails This endpoint can be used to obtain a list of the latest HF transaction details.
193 | // The returned results are paginated. The data is sorted in descending order according to time.
194 | func (as *ApiService) HfTransactionDetails(ctx context.Context, p map[string]string) (*ApiResponse, error) {
195 | req := NewRequest(http.MethodGet, "/api/v1/hf/fills", p)
196 | return as.Call(ctx, req)
197 | }
198 |
199 | // HfCancelOrders This endpoint can be used to cancel all hf orders. return HfCancelOrdersResultModel
200 | func (as *ApiService) HfCancelOrders(ctx context.Context) (*ApiResponse, error) {
201 | req := NewRequest(http.MethodDelete, "/api/v1/hf/orders/cancelAll", nil)
202 | return as.Call(ctx, req)
203 | }
204 |
205 | func (as *ApiService) HfPlaceOrderTest(ctx context.Context, p *HfPlaceOrderReq) (*ApiResponse, error) {
206 | req := NewRequest(http.MethodPost, "/api/v1/hf/orders/test", p)
207 | return as.Call(ctx, req)
208 | }
209 |
210 | func (as *ApiService) HfMarginActiveSymbols(ctx context.Context, tradeType string) (*ApiResponse, error) {
211 | p := map[string]string{
212 | "tradeType": tradeType,
213 | }
214 | req := NewRequest(http.MethodGet, "/api/v3/hf/margin/order/active/symbols", p)
215 | return as.Call(ctx, req)
216 | }
217 |
218 | // HfCreateMarinOrderV3 This interface is used to place cross-margin or isolated-margin high-frequency margin trading
219 | func (as *ApiService) HfCreateMarinOrderV3(ctx context.Context, p *HfMarginOrderV3Req) (*ApiResponse, error) {
220 | req := NewRequest(http.MethodPost, "/api/v3/hf/margin/order", p)
221 | return as.Call(ctx, req)
222 | }
223 |
224 | // HfCreateMarinOrderTestV3 Order test endpoint, the request parameters and return parameters of this endpoint are exactly the same as the order endpoint,
225 | // and can be used to verify whether the signature is correct and other operations. After placing an order,
226 | // the order will not enter the matching system, and the order cannot be queried.
227 | func (as *ApiService) HfCreateMarinOrderTestV3(ctx context.Context, p *HfMarginOrderV3Req) (*ApiResponse, error) {
228 | req := NewRequest(http.MethodPost, "/api/v3/hf/margin/order/test", p)
229 | return as.Call(ctx, req)
230 | }
231 |
232 | // HfCancelMarinOrderV3 Cancel a single order by orderId. If the order cannot be canceled (sold or canceled),
233 | // an error message will be returned, and the reason can be obtained according to the returned msg.
234 | func (as *ApiService) HfCancelMarinOrderV3(ctx context.Context, p *HfCancelMarinOrderV3Req) (*ApiResponse, error) {
235 | req := NewRequest(http.MethodDelete, fmt.Sprintf("/api/v3/hf/margin/orders/%s?symbol=%s", p.OrderId, p.Symbol), nil)
236 | return as.Call(ctx, req)
237 | }
238 |
239 | // HfCancelClientMarinOrderV3 Cancel a single order by clientOid.
240 | func (as *ApiService) HfCancelClientMarinOrderV3(ctx context.Context, p *HfCancelClientMarinOrderV3Req) (*ApiResponse, error) {
241 | req := NewRequest(http.MethodDelete, fmt.Sprintf("/api/v3/hf/margin/orders/client-order/%s?symbol=%s", p.ClientOid, p.Symbol), nil)
242 | return as.Call(ctx, req)
243 | }
244 |
245 | // HfCancelAllMarginOrdersV3 This endpoint only sends cancellation requests.
246 | // The results of the requests must be obtained by checking the order detail or subscribing to websocket.
247 | func (as *ApiService) HfCancelAllMarginOrdersV3(ctx context.Context, p *HfCancelAllMarginOrdersV3Req) (*ApiResponse, error) {
248 | v, err := query.Values(p)
249 | if err != nil {
250 | return nil, err
251 | }
252 | req := NewRequest(http.MethodDelete, "/api/v3/hf/margin/orders", v)
253 | return as.Call(ctx, req)
254 | }
255 |
256 | // HfMarinActiveOrdersV3 This interface is to obtain all active hf margin order lists,
257 | // and the return value of the active order interface is the paged data of all uncompleted order lists.
258 | func (as *ApiService) HfMarinActiveOrdersV3(ctx context.Context, p *HfMarinActiveOrdersV3Req) (*ApiResponse, error) {
259 | v, err := query.Values(p)
260 | if err != nil {
261 | return nil, err
262 | }
263 | req := NewRequest(http.MethodGet, "/api/v3/hf/margin/orders/active", v)
264 | return as.Call(ctx, req)
265 | }
266 |
267 | // HfMarinDoneOrdersV3 This endpoint obtains a list of filled margin HF orders and returns paginated data.
268 | // The returned data is sorted in descending order based on the latest order update times.
269 | func (as *ApiService) HfMarinDoneOrdersV3(ctx context.Context, p *HfMarinDoneOrdersV3Req) (*ApiResponse, error) {
270 | v, err := query.Values(p)
271 | if err != nil {
272 | return nil, err
273 | }
274 | req := NewRequest(http.MethodGet, "/api/v3/hf/margin/orders/done", v)
275 | return as.Call(ctx, req)
276 | }
277 |
278 | // HfMarinOrderV3 This endpoint can be used to obtain information for a single margin HF order using the order id.
279 | func (as *ApiService) HfMarinOrderV3(ctx context.Context, p *HfMarinOrderV3Req) (*ApiResponse, error) {
280 | req := NewRequest(http.MethodGet, fmt.Sprintf("/api/v3/hf/margin/orders/%s?symbol=%s", p.OrderId, p.Symbol), nil)
281 | return as.Call(ctx, req)
282 | }
283 |
284 | // HfMarinClientOrderV3 This endpoint can be used to obtain information for a single margin HF order using the clientOid.
285 | func (as *ApiService) HfMarinClientOrderV3(ctx context.Context, p *HfMarinClientOrderV3Req) (*ApiResponse, error) {
286 | req := NewRequest(http.MethodGet, fmt.Sprintf("/api/v3/hf/margin/orders/client-order/%s?symbol=%s", p.ClientOid, p.Symbol), nil)
287 | return as.Call(ctx, req)
288 | }
289 |
290 | // HfMarinFillsV3 This endpoint can be used to obtain a list of the latest margin HF transaction details.
291 | // The returned results are paginated.
292 | // The data is sorted in descending order according to time.
293 | func (as *ApiService) HfMarinFillsV3(ctx context.Context, p *HfMarinFillsV3Req) (*ApiResponse, error) {
294 | v, err := query.Values(p)
295 | if err != nil {
296 | return nil, err
297 | }
298 | req := NewRequest(http.MethodGet, "/api/v3/hf/margin/fills", v)
299 | return as.Call(ctx, req)
300 | }
301 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notice: SDK Deprecation
2 |
3 | Thank you for your support and usage of this SDK. We want to inform you that **this project is no longer actively maintained or updated**.
4 |
5 | To ensure you have access to the latest features, improvements, and support, we recommend transitioning to our new SDK: [**KuCoin Universal SDK**](https://github.com/Kucoin/kucoin-universal-sdk).
6 |
7 | The KuCoin Universal SDK offers:
8 | - A unified architecture across multiple programming languages.
9 | - Enhanced performance and stability.
10 | - Continued support and updates.
11 |
12 | 👉 **New SDK Repository**: [https://github.com/Kucoin/kucoin-universal-sdk](https://github.com/Kucoin/kucoin-universal-sdk)
13 |
14 | We appreciate your understanding and encourage you to migrate to the new SDK for a better development experience. Should you have any questions or require assistance, feel free to reach out to us.
15 |
16 |
17 |
18 | # Go SDK for KuCoin API
19 | > The detailed document [https://docs.kucoin.com](https://docs.kucoin.com), in order to receive the latest API change notifications, please `Watch` this repository.
20 |
21 | [](https://github.com/Kucoin/kucoin-go-sdk/releases)
22 | [](https://godoc.org/github.com/Kucoin/kucoin-go-sdk)
23 | [](https://travis-ci.org/Kucoin/kucoin-go-sdk)
24 | [](https://goreportcard.com/report/github.com/Kucoin/kucoin-go-sdk)
25 | [](https://sourcegraph.com/github.com/Kucoin/kucoin-go-sdk?badge)
26 |
27 |
28 |
29 | ## Install
30 |
31 | ```bash
32 | go get github.com/Kucoin/kucoin-go-sdk
33 | ```
34 |
35 | ## Usage
36 |
37 | ### Choose environment
38 |
39 | | Environment | BaseUri |
40 | | -------- | -------- |
41 | | *Production* | `https://api.kucoin.com(DEFAULT)` `https://api.kucoin.cc` |
42 | | *Sandbox* | `https://openapi-sandbox.kucoin.com` |
43 |
44 | ### Create ApiService
45 |
46 | ###### **Note**
47 | To reinforce the security of the APIS, KuCoin upgraded the API key to version 2.0, the validation logic has also been changed. It is recommended to create(https://www.kucoin.com/account/api) and update your API key to version 2.0.
48 | The API key of version 1.0 will be still valid until May 1, 2021.
49 |
50 | ```go
51 | // API key version 2.0
52 | s := kucoin.NewApiService(
53 | // kucoin.ApiBaseURIOption("https://api.kucoin.com"),
54 | kucoin.ApiKeyOption("key"),
55 | kucoin.ApiSecretOption("secret"),
56 | kucoin.ApiPassPhraseOption("passphrase"),
57 | kucoin.ApiKeyVersionOption(ApiKeyVersionV2)
58 | )
59 |
60 | // API key version 1.0
61 | s := kucoin.NewApiService(
62 | // kucoin.ApiBaseURIOption("https://api.kucoin.com"),
63 | kucoin.ApiKeyOption("key"),
64 | kucoin.ApiSecretOption("secret"),
65 | kucoin.ApiPassPhraseOption("passphrase"),
66 | )
67 | // Or add these options into the environmental variable
68 | // Bash:
69 | // export API_BASE_URI=https://api.kucoin.com
70 | // export API_KEY=key
71 | // export API_SECRET=secret
72 | // export API_PASSPHRASE=passphrase
73 | // export API_KEY_VERSION=2
74 | // s := NewApiServiceFromEnv()
75 | ```
76 |
77 | ### Debug mode & logging
78 |
79 | ```go
80 | // Require package github.com/sirupsen/logrus
81 | // Debug mode will record the logs of API and WebSocket to files.
82 | // Default values: LogLevel=logrus.DebugLevel, LogDirectory="/tmp"
83 | kucoin.DebugMode = true
84 | // Or export API_DEBUG_MODE=1
85 |
86 | // Logging in your code
87 | // kucoin.SetLoggerDirectory("/tmp")
88 | // logrus.SetLevel(logrus.DebugLevel)
89 | logrus.Debugln("I'm a debug message")
90 | ```
91 |
92 | ### Examples
93 | > See the test case for more examples.
94 |
95 | #### Example of API `without` authentication
96 |
97 | ```go
98 | rsp, err := s.ServerTime()
99 | if err != nil {
100 | log.Printf("Error: %s", err.Error())
101 | // Handle error
102 | return
103 | }
104 |
105 | var ts int64
106 | if err := rsp.ReadData(&ts); err != nil {
107 | // Handle error
108 | return
109 | }
110 | log.Printf("The server time: %d", ts)
111 | ```
112 |
113 | #### Example of API `with` authentication
114 |
115 | ```go
116 | // Without pagination
117 | rsp, err := s.Accounts("", "")
118 | if err != nil {
119 | // Handle error
120 | return
121 | }
122 |
123 | as := kucoin.AccountsModel{}
124 | if err := rsp.ReadData(&as); err != nil {
125 | // Handle error
126 | return
127 | }
128 |
129 | for _, a := range as {
130 | log.Printf("Available balance: %s %s => %s", a.Type, a.Currency, a.Available)
131 | }
132 | ```
133 |
134 | ```go
135 | // Handle pagination
136 | rsp, err := s.Orders(map[string]string{}, &kucoin.PaginationParam{CurrentPage: 1, PageSize: 10})
137 | if err != nil {
138 | // Handle error
139 | return
140 | }
141 |
142 | os := kucoin.OrdersModel{}
143 | pa, err := rsp.ReadPaginationData(&os)
144 | if err != nil {
145 | // Handle error
146 | return
147 | }
148 | log.Printf("Total num: %d, total page: %d", pa.TotalNum, pa.TotalPage)
149 | for _, o := range os {
150 | log.Printf("Order: %s, %s, %s", o.Id, o.Type, o.Price)
151 | }
152 | ```
153 |
154 | #### Example of WebSocket feed
155 | > Require package [gorilla/websocket](https://github.com/gorilla/websocket)
156 |
157 | ```bash
158 | go get github.com/gorilla/websocket github.com/pkg/errors
159 | ```
160 |
161 | ```go
162 | rsp, err := s.WebSocketPublicToken()
163 | if err != nil {
164 | // Handle error
165 | return
166 | }
167 |
168 | tk := &kucoin.WebSocketTokenModel{}
169 | if err := rsp.ReadData(tk); err != nil {
170 | // Handle error
171 | return
172 | }
173 |
174 | c := s.NewWebSocketClient(tk)
175 |
176 | mc, ec, err := c.Connect()
177 | if err != nil {
178 | // Handle error
179 | return
180 | }
181 |
182 | ch1 := kucoin.NewSubscribeMessage("/market/ticker:KCS-BTC", false)
183 | ch2 := kucoin.NewSubscribeMessage("/market/ticker:ETH-BTC", false)
184 | uch := kucoin.NewUnsubscribeMessage("/market/ticker:ETH-BTC", false)
185 |
186 | if err := c.Subscribe(ch1, ch2); err != nil {
187 | // Handle error
188 | return
189 | }
190 |
191 | var i = 0
192 | for {
193 | select {
194 | case err := <-ec:
195 | c.Stop() // Stop subscribing the WebSocket feed
196 | log.Printf("Error: %s", err.Error())
197 | // Handle error
198 | return
199 | case msg := <-mc:
200 | // log.Printf("Received: %s", kucoin.ToJsonString(m))
201 | t := &kucoin.TickerLevel1Model{}
202 | if err := msg.ReadData(t); err != nil {
203 | log.Printf("Failure to read: %s", err.Error())
204 | return
205 | }
206 | log.Printf("Ticker: %s, %s, %s, %s", msg.Topic, t.Sequence, t.Price, t.Size)
207 | i++
208 | if i == 5 {
209 | log.Println("Unsubscribe ETH-BTC")
210 | if err = c.Unsubscribe(uch); err != nil {
211 | log.Printf("Error: %s", err.Error())
212 | // Handle error
213 | return
214 | }
215 | }
216 | if i == 10 {
217 | log.Println("Subscribe ETH-BTC")
218 | if err = c.Subscribe(ch2); err != nil {
219 | log.Printf("Error: %s", err.Error())
220 | // Handle error
221 | return
222 | }
223 | }
224 | if i == 15 {
225 | log.Println("Exit subscription")
226 | c.Stop() // Stop subscribing the WebSocket feed
227 | return
228 | }
229 | }
230 | }
231 | ```
232 |
233 | ### API list
234 |
235 | Trade Fee
236 |
237 | | API | Authentication | Description |
238 | | -------- | -------- | -------- |
239 | | ApiService.BaseFee() | YES | https://docs.kucoin.com/#basic-user-fee |
240 | | ApiService.ActualFee() | YES | https://docs.kucoin.com/#actual-fee-rate-of-the-trading-pair |
241 |
242 |
243 |
244 |
245 | Stop Order
246 |
247 | | API | Authentication | Description |
248 | | -------- | -------- | -------- |
249 | | ApiService.CreateStopOrder() | YES | https://docs.kucoin.com/#place-a-new-order-2 |
250 | | ApiService.CancelStopOrder() | YES | https://docs.kucoin.com/#cancel-an-order-2 |
251 | | ApiService.CancelStopOrderBy() | YES | https://docs.kucoin.com/#cancel-orders |
252 | | ApiService.StopOrder() | YES | https://docs.kucoin.com/#get-single-order-info |
253 | | ApiService.StopOrders() | YES | https://docs.kucoin.com/#list-stop-orders |
254 | | ApiService.StopOrderByClient() | YES | https://docs.kucoin.com/#get-single-order-by-clientoid |
255 | | ApiService.CancelStopOrderByClient() | YES | https://docs.kucoin.com/#cancel-single-order-by-clientoid-2 |
256 |
257 |
258 |
259 |
260 | Account
261 |
262 | | API | Authentication | Description |
263 | | -------- | -------- | -------- |
264 | | ApiService.CreateAccount() | YES | https://docs.kucoin.com/#create-an-account |
265 | | ApiService.Accounts() | YES | https://docs.kucoin.com/#list-accounts |
266 | | ApiService.Account() | YES | https://docs.kucoin.com/#get-an-account |
267 | | ApiService.SubAccountUsers() | YES | https://docs.kucoin.com/#get-user-info-of-all-sub-accounts |
268 | | ApiService.SubAccounts() | YES | https://docs.kucoin.com/#get-the-aggregated-balance-of-all-sub-accounts-of-the-current-user |
269 | | ApiService.SubAccount() | YES | https://docs.kucoin.com/#get-account-balance-of-a-sub-account |
270 | | ApiService.AccountLedgers() | YES | `DEPRECATED` https://docs.kucoin.com/#get-account-ledgers-deprecated |
271 | | ApiService.AccountHolds() | YES | https://docs.kucoin.com/#get-holds |
272 | | ApiService.InnerTransfer() | YES | `DEPRECATED` https://docs.kucoin.com/#inner-transfer |
273 | | ApiService.InnerTransferV2() | YES | https://docs.kucoin.com/#inner-transfer |
274 | | ApiService.SubTransfer() | YES | `DEPRECATED` |
275 | | ApiService.SubTransferV2() | YES | https://docs.kucoin.com/#transfer-between-master-user-and-sub-user |
276 | | ApiService.AccountLedgersV2() | YES | https://docs.kucoin.com/#get-account-ledgers |
277 |
278 |
279 |
280 |
281 | Deposit
282 |
283 | | API | Authentication | Description |
284 | | -------- | -------- | -------- |
285 | | ApiService.CreateDepositAddress() | YES | https://docs.kucoin.com/#create-deposit-address |
286 | | ApiService.DepositAddresses() | YES | https://docs.kucoin.com/#get-deposit-address |
287 | | ApiService.V1Deposits() | YES | https://docs.kucoin.com/#get-v1-historical-deposits-list |
288 | | ApiService.Deposits() | YES | https://docs.kucoin.com/#get-deposit-list |
289 |
290 |
291 |
292 |
293 | Fill
294 |
295 | | API | Authentication | Description |
296 | | -------- | -------- | -------- |
297 | | ApiService.Fills() | YES | https://docs.kucoin.com/#list-fills |
298 | | ApiService.RecentFills() | YES | https://docs.kucoin.com/#recent-fills |
299 |
300 |
301 |
302 |
303 | Order
304 |
305 | | API | Authentication | Description |
306 | | -------- | -------- | -------- |
307 | | ApiService.CreateOrder() | YES | https://docs.kucoin.com/#place-a-new-order |
308 | | ApiService.CreateMultiOrder() | YES | https://docs.kucoin.com/#place-bulk-orders |
309 | | ApiService.CancelOrder() | YES | https://docs.kucoin.com/#cancel-an-order |
310 | | ApiService.CancelOrders() | YES | https://docs.kucoin.com/#cancel-all-orders |
311 | | ApiService.V1Orders() | YES | https://docs.kucoin.com/#get-v1-historical-orders-list |
312 | | ApiService.Orders() | YES | https://docs.kucoin.com/#list-orders |
313 | | ApiService.Order() | YES | https://docs.kucoin.com/#get-an-order |
314 | | ApiService.RecentOrders() | YES | https://docs.kucoin.com/#recent-orders |
315 | | ApiService.CreateMarginOrder() | YES | https://docs.kucoin.com/#place-a-margin-order |
316 | | ApiService.CancelOrderByClient() | YES | https://docs.kucoin.com/#cancel-single-order-by-clientoid |
317 | | ApiService.OrderByClient() | YES | https://docs.kucoin.com/#get-single-active-order-by-clientoid|
318 |
319 |
320 |
321 |
322 | WebSocket Feed
323 |
324 | | API | Authentication | Description |
325 | | -------- | -------- | -------- |
326 | | ApiService.WebSocketPublicToken() | NO | https://docs.kucoin.com/#apply-connect-token |
327 | | ApiService.WebSocketPrivateToken() | YES | https://docs.kucoin.com/#apply-connect-token |
328 | | ApiService.NewWebSocketClient() | - | https://docs.kucoin.com/#websocket-feed |
329 |
330 |
331 |
332 |
333 | Withdrawal
334 |
335 | | API | Authentication | Description |
336 | | -------- | -------- | -------- |
337 | | ApiService.WithdrawalQuotas() | YES | https://docs.kucoin.com/#get-withdrawal-quotas |
338 | | ApiService.V1Withdrawals() | YES | https://docs.kucoin.com/#get-v1-historical-withdrawals-list |
339 | | ApiService.Withdrawals() | YES | https://docs.kucoin.com/#get-withdrawals-list |
340 | | ApiService.ApplyWithdrawal() | YES | https://docs.kucoin.com/#apply-withdraw |
341 | | ApiService.CancelWithdrawal() | YES | https://docs.kucoin.com/#cancel-withdrawal |
342 |
343 |
344 |
345 |
346 | Currency
347 |
348 | | API | Authentication | Description |
349 | | -------- | -------- | -------- |
350 | | ApiService.Currencies() | NO | https://docs.kucoin.com/#get-currencies |
351 | | ApiService.Currency() | NO | https://docs.kucoin.com/#get-currency-detail |
352 | | ApiService.Prices() | NO | https://docs.kucoin.com/#get-fiat-price |
353 |
354 |
355 |
356 |
357 | Symbol
358 |
359 | | API | Authentication | Description |
360 | | -------- | -------- | -------- |
361 | | ApiService.Symbols() | NO | https://docs.kucoin.com/#get-symbols-list |
362 | | ApiService.TickerLevel1() | NO | https://docs.kucoin.com/#get-ticker |
363 | | ApiService.Tickers() | NO | https://docs.kucoin.com/#get-all-tickers |
364 | | ApiService.AggregatedPartOrderBook() | NO | https://docs.kucoin.com/#get-part-order-book-aggregated |
365 | | ApiService.AggregatedFullOrderBook() | NO | https://docs.kucoin.com/#get-full-order-book-aggregated |
366 | | ApiService.AtomicFullOrderBook() | NO | https://docs.kucoin.com/#get-full-order-book-atomic |
367 | | ApiService.TradeHistories() | NO | https://docs.kucoin.com/#get-trade-histories |
368 | | ApiService.KLines() | NO | https://docs.kucoin.com/#get-klines |
369 | | ApiService.Stats24hr() | NO | https://docs.kucoin.com/#get-24hr-stats |
370 | | ApiService.Markets() | NO | https://docs.kucoin.com/#get-market-list |
371 |
372 |
373 |
374 |
375 | Time
376 |
377 | | API | Authentication | Description |
378 | | -------- | -------- | -------- |
379 | | ApiService.ServerTime() | NO | https://docs.kucoin.com/#server-time |
380 |
381 |
382 |
383 |
384 | Service Status
385 |
386 | | API | Authentication | Description |
387 | | -------- | -------- | -------- |
388 | | ApiService.ServiceStatus() | NO | https://docs.kucoin.com/#service-status |
389 |
390 |
391 |
392 | ## Run tests
393 |
394 | ```shell
395 | # Add your API configuration items into the environmental variable first
396 | export API_BASE_URI=https://api.kucoin.com
397 | export API_KEY=key
398 | export API_SECRET=secret
399 | export API_PASSPHRASE=passphrase
400 | export API_KEY_VERSION=2
401 |
402 | # Run tests
403 | go test -v
404 | ```
405 |
406 | ## License
407 |
408 | [MIT](LICENSE)
409 |
--------------------------------------------------------------------------------
/order.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | // A CreateOrderModel is the input parameter of CreateOrder().
9 | type CreateOrderModel struct {
10 | // BASE PARAMETERS
11 | ClientOid string `json:"clientOid"`
12 | Side string `json:"side"`
13 | Symbol string `json:"symbol,omitempty"`
14 | Type string `json:"type,omitempty"`
15 | Remark string `json:"remark,omitempty"`
16 | Stop string `json:"stop,omitempty"`
17 | StopPrice string `json:"stopPrice,omitempty"`
18 | STP string `json:"stp,omitempty"`
19 | TradeType string `json:"tradeType,omitempty"`
20 |
21 | // LIMIT ORDER PARAMETERS
22 | Price string `json:"price,omitempty"`
23 | Size string `json:"size,omitempty"`
24 | TimeInForce string `json:"timeInForce,omitempty"`
25 | CancelAfter int64 `json:"cancelAfter,omitempty"`
26 | PostOnly bool `json:"postOnly,omitempty"`
27 | Hidden bool `json:"hidden,omitempty"`
28 | IceBerg bool `json:"iceberg,omitempty"`
29 | VisibleSize string `json:"visibleSize,omitempty"`
30 |
31 | // MARKET ORDER PARAMETERS
32 | // Size string `json:"size"`
33 | Funds string `json:"funds,omitempty"`
34 |
35 | // MARGIN ORDER PARAMETERS
36 | MarginMode string `json:"marginMode,omitempty"`
37 | AutoBorrow bool `json:"autoBorrow,omitempty"`
38 | AutoRepay bool `json:"autoRepay,omitempty"`
39 | }
40 |
41 | // A CreateOrderResultModel represents the result of CreateOrder().
42 | type CreateOrderResultModel struct {
43 | OrderId string `json:"orderId"`
44 | }
45 |
46 | // CreateOrder places a new order.
47 | func (as *ApiService) CreateOrder(ctx context.Context, o *CreateOrderModel) (*ApiResponse, error) {
48 | req := NewRequest(http.MethodPost, "/api/v1/orders", o)
49 | return as.Call(ctx, req)
50 | }
51 |
52 | // CreateOrderTest places a new order test.
53 | func (as *ApiService) CreateOrderTest(ctx context.Context, o *CreateOrderModel) (*ApiResponse, error) {
54 | req := NewRequest(http.MethodPost, "/api/v1/orders/test", o)
55 | return as.Call(ctx, req)
56 | }
57 |
58 | // A CreateMultiOrderResultModel represents the result of CreateMultiOrder().
59 | type CreateMultiOrderResultModel struct {
60 | Data OrdersModel `json:"data"`
61 | }
62 |
63 | // CreateMultiOrder places bulk orders.
64 | func (as *ApiService) CreateMultiOrder(ctx context.Context, symbol string, orders []*CreateOrderModel) (*ApiResponse, error) {
65 | params := map[string]interface{}{
66 | "symbol": symbol,
67 | "orderList": orders,
68 | }
69 | req := NewRequest(http.MethodPost, "/api/v1/orders/multi", params)
70 | return as.Call(ctx, req)
71 | }
72 |
73 | // A CancelOrderResultModel represents the result of CancelOrder().
74 | type CancelOrderResultModel struct {
75 | CancelledOrderIds []string `json:"cancelledOrderIds"`
76 | }
77 |
78 | // CancelOrder cancels a previously placed order.
79 | func (as *ApiService) CancelOrder(ctx context.Context, orderId string) (*ApiResponse, error) {
80 | req := NewRequest(http.MethodDelete, "/api/v1/orders/"+orderId, nil)
81 | return as.Call(ctx, req)
82 | }
83 |
84 | // A CancelOrderByClientResultModel represents the result of CancelOrderByClient().
85 | type CancelOrderByClientResultModel struct {
86 | CancelledOrderId string `json:"cancelledOrderId"`
87 | ClientOid string `json:"clientOid"`
88 | }
89 |
90 | // CancelOrderByClient cancels a previously placed order by client ID.
91 | func (as *ApiService) CancelOrderByClient(ctx context.Context, clientOid string) (*ApiResponse, error) {
92 | req := NewRequest(http.MethodDelete, "/api/v1/order/client-order/"+clientOid, nil)
93 | return as.Call(ctx, req)
94 | }
95 |
96 | // CancelOrders cancels all orders of the symbol.
97 | // With best effort, cancel all open orders. The response is a list of ids of the canceled orders.
98 | func (as *ApiService) CancelOrders(ctx context.Context, p map[string]string) (*ApiResponse, error) {
99 | req := NewRequest(http.MethodDelete, "/api/v1/orders", p)
100 | return as.Call(ctx, req)
101 | }
102 |
103 | // An OrderModel represents an order.
104 | type OrderModel struct {
105 | Id string `json:"id"`
106 | Symbol string `json:"symbol"`
107 | OpType string `json:"opType"`
108 | Type string `json:"type"`
109 | Side string `json:"side"`
110 | Price string `json:"price"`
111 | Size string `json:"size"`
112 | Funds string `json:"funds"`
113 | DealFunds string `json:"dealFunds"`
114 | DealSize string `json:"dealSize"`
115 | Fee string `json:"fee"`
116 | FeeCurrency string `json:"feeCurrency"`
117 | Stp string `json:"stp"`
118 | Stop string `json:"stop"`
119 | StopTriggered bool `json:"stopTriggered"`
120 | StopPrice string `json:"stopPrice"`
121 | TimeInForce string `json:"timeInForce"`
122 | PostOnly bool `json:"postOnly"`
123 | Hidden bool `json:"hidden"`
124 | IceBerg bool `json:"iceberg"`
125 | VisibleSize string `json:"visibleSize"`
126 | CancelAfter int64 `json:"cancelAfter"`
127 | Channel string `json:"channel"`
128 | ClientOid string `json:"clientOid"`
129 | Remark string `json:"remark"`
130 | Tags string `json:"tags"`
131 | IsActive bool `json:"isActive"`
132 | CancelExist bool `json:"cancelExist"`
133 | CreatedAt int64 `json:"createdAt"`
134 | TradeType string `json:"tradeType"`
135 | }
136 |
137 | // A OrdersModel is the set of *OrderModel.
138 | type OrdersModel []*OrderModel
139 |
140 | // Orders returns a list your current orders.
141 | func (as *ApiService) Orders(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
142 | pagination.ReadParam(params)
143 | req := NewRequest(http.MethodGet, "/api/v1/orders", params)
144 | return as.Call(ctx, req)
145 | }
146 |
147 | // A V1OrderModel represents a v1 order.
148 | type V1OrderModel struct {
149 | Symbol string `json:"symbol"`
150 | DealPrice string `json:"dealPrice"`
151 | DealValue string `json:"dealValue"`
152 | Amount string `json:"amount"`
153 | Fee string `json:"fee"`
154 | Side string `json:"side"`
155 | CreatedAt int64 `json:"createdAt"`
156 | }
157 |
158 | // A V1OrdersModel is the set of *V1OrderModel.
159 | type V1OrdersModel []*V1OrderModel
160 |
161 | // V1Orders returns a list of v1 historical orders.
162 | // Deprecated
163 | func (as *ApiService) V1Orders(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
164 | pagination.ReadParam(params)
165 | req := NewRequest(http.MethodGet, "/api/v1/hist-orders", params)
166 | return as.Call(ctx, req)
167 | }
168 |
169 | // Order returns a single order by order id.
170 | func (as *ApiService) Order(ctx context.Context, orderId string) (*ApiResponse, error) {
171 | req := NewRequest(http.MethodGet, "/api/v1/orders/"+orderId, nil)
172 | return as.Call(ctx, req)
173 | }
174 |
175 | // OrderByClient returns a single order by client id.
176 | func (as *ApiService) OrderByClient(ctx context.Context, clientOid string) (*ApiResponse, error) {
177 | req := NewRequest(http.MethodGet, "/api/v1/order/client-order/"+clientOid, nil)
178 | return as.Call(ctx, req)
179 | }
180 |
181 | // RecentOrders returns the recent orders of the latest transactions within 24 hours.
182 | func (as *ApiService) RecentOrders(ctx context.Context) (*ApiResponse, error) {
183 | req := NewRequest(http.MethodGet, "/api/v1/limit/orders", nil)
184 | return as.Call(ctx, req)
185 | }
186 |
187 | // CreateStopOrder places a new stop-order.
188 | func (as *ApiService) CreateStopOrder(ctx context.Context, o *CreateOrderModel) (*ApiResponse, error) {
189 | req := NewRequest(http.MethodPost, "/api/v1/stop-order", o)
190 | return as.Call(ctx, req)
191 | }
192 |
193 | // CancelStopOrder cancels a previously placed stop-order.
194 | func (as *ApiService) CancelStopOrder(ctx context.Context, orderId string) (*ApiResponse, error) {
195 | req := NewRequest(http.MethodDelete, "/api/v1/stop-order/"+orderId, nil)
196 | return as.Call(ctx, req)
197 | }
198 |
199 | // CancelStopOrderByClientModel returns Model of CancelStopOrderByClient API
200 | type CancelStopOrderByClientModel struct {
201 | CancelledOrderId string `json:"cancelledOrderId"`
202 | ClientOid string `json:"clientOid"`
203 | }
204 |
205 | // CancelStopOrderByClient cancels a previously placed stop-order by client ID.
206 | func (as *ApiService) CancelStopOrderByClient(ctx context.Context, clientOid string, p map[string]string) (*ApiResponse, error) {
207 | p["clientOid"] = clientOid
208 |
209 | req := NewRequest(http.MethodDelete, "/api/v1/stop-order/cancelOrderByClientOid", p)
210 | return as.Call(ctx, req)
211 | }
212 |
213 | // StopOrderModel RESPONSES of StopOrder
214 | type StopOrderModel struct {
215 | Id string `json:"id"`
216 | Symbol string `json:"symbol"`
217 | UserId string `json:"userId"`
218 | Status string `json:"status"`
219 | Type string `json:"type"`
220 | Side string `json:"side"`
221 | Price string `json:"price"`
222 | Size string `json:"size"`
223 | Funds string `json:"funds"`
224 | Stp string `json:"stp"`
225 | TimeInForce string `json:"timeInForce"`
226 | CancelAfter int64 `json:"cancelAfter"`
227 | PostOnly bool `json:"postOnly"`
228 | Hidden bool `json:"hidden"`
229 | IceBerg bool `json:"iceberg"`
230 | VisibleSize string `json:"visibleSize"`
231 | Channel string `json:"channel"`
232 | ClientOid string `json:"clientOid"`
233 | Remark string `json:"remark"`
234 | Tags string `json:"tags"`
235 | OrderTime int64 `json:"orderTime"`
236 | DomainId string `json:"domainId"`
237 | TradeSource string `json:"tradeSource"`
238 | TradeType string `json:"tradeType"`
239 | FeeCurrency string `json:"feeCurrency"`
240 | TakerFeeRate string `json:"takerFeeRate"`
241 | MakerFeeRate string `json:"makerFeeRate"`
242 | CreatedAt int64 `json:"createdAt"`
243 | Stop string `json:"stop"`
244 | StopTriggerTime string `json:"stopTriggerTime"`
245 | StopPrice string `json:"stopPrice"`
246 | }
247 |
248 | // StopOrder returns a single order by stop-order id.
249 | func (as *ApiService) StopOrder(ctx context.Context, orderId string) (*ApiResponse, error) {
250 | req := NewRequest(http.MethodGet, "/api/v1/stop-order/"+orderId, nil)
251 | return as.Call(ctx, req)
252 | }
253 |
254 | // StopOrderListModel StopOrderByClient model
255 | type StopOrderListModel []*StopOrderModel
256 |
257 | // StopOrderByClient returns a single stop-order by client id.
258 | func (as *ApiService) StopOrderByClient(ctx context.Context, clientOid string, p map[string]string) (*ApiResponse, error) {
259 | p["clientOid"] = clientOid
260 |
261 | req := NewRequest(http.MethodGet, "/api/v1/stop-order/queryOrderByClientOid", p)
262 | return as.Call(ctx, req)
263 | }
264 |
265 | // StopOrders returns a list your current orders.
266 | func (as *ApiService) StopOrders(ctx context.Context, params map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
267 | pagination.ReadParam(params)
268 | req := NewRequest(http.MethodGet, "/api/v1/stop-order", params)
269 | return as.Call(ctx, req)
270 | }
271 |
272 | // CancelStopOrderBy returns a list your current orders.
273 | func (as *ApiService) CancelStopOrderBy(ctx context.Context, params map[string]string) (*ApiResponse, error) {
274 | req := NewRequest(http.MethodDelete, "/api/v1/stop-order/cancel", params)
275 | return as.Call(ctx, req)
276 | }
277 |
278 | // CreateMarginOrder places a new margin order.
279 | func (as *ApiService) CreateMarginOrder(ctx context.Context, o *CreateOrderModel) (*ApiResponse, error) {
280 | req := NewRequest(http.MethodPost, "/api/v1/margin/order", o)
281 | return as.Call(ctx, req)
282 | }
283 |
284 | // CreateMarginOrderTest places a new margin test order.
285 | func (as *ApiService) CreateMarginOrderTest(ctx context.Context, o *CreateOrderModel) (*ApiResponse, error) {
286 | req := NewRequest(http.MethodPost, "/api/v1/margin/order/test", o)
287 | return as.Call(ctx, req)
288 | }
289 |
290 | // A CreateOcoOrderModel is the input parameter of CreatOcoOrder().
291 | type CreateOcoOrderModel struct {
292 | Side string `json:"side"`
293 | Symbol string `json:"symbol,omitempty"`
294 | Price string `json:"price,omitempty"`
295 | Size string `json:"size,omitempty"`
296 | StopPrice string `json:"stopPrice,omitempty"`
297 | LimitPrice string `json:"limitPrice,omitempty"`
298 | TradeType string `json:"tradeType"`
299 | ClientOid string `json:"clientOid,omitempty"`
300 | Remark string `json:"remark"`
301 | }
302 |
303 | // CreateOcoOrder places a new margin order.
304 | func (as *ApiService) CreateOcoOrder(ctx context.Context, o *CreateOcoOrderModel) (*ApiResponse, error) {
305 | req := NewRequest(http.MethodPost, "/api/v3/oco/order", o)
306 | return as.Call(ctx, req)
307 | }
308 |
309 | type CancelledOcoOrderResModel struct {
310 | CancelledOrderIds []string `json:"cancelledOrderIds"`
311 | }
312 |
313 | // DeleteOcoOrder cancel a oco order. return CancelledOcoOrderResModel
314 | func (as *ApiService) DeleteOcoOrder(ctx context.Context, orderId string) (*ApiResponse, error) {
315 | req := NewRequest(http.MethodDelete, "/api/v3/oco/order/"+orderId, nil)
316 | return as.Call(ctx, req)
317 | }
318 |
319 | // DeleteOcoOrderClientId cancel a oco order with clientOrderId. return CancelledOcoOrderResModel
320 | func (as *ApiService) DeleteOcoOrderClientId(ctx context.Context, clientOrderId string) (*ApiResponse, error) {
321 | req := NewRequest(http.MethodDelete, "/api/v3/oco/client-order/"+clientOrderId, nil)
322 | return as.Call(ctx, req)
323 | }
324 |
325 | // DeleteOcoOrders cancel all oco order. return CancelledOcoOrderResModel
326 | func (as *ApiService) DeleteOcoOrders(ctx context.Context, symbol, orderIds string) (*ApiResponse, error) {
327 | params := map[string]interface{}{
328 | "symbol": symbol,
329 | "orderIds": orderIds,
330 | }
331 | req := NewRequest(http.MethodDelete, "/api/v3/oco/orders", params)
332 | return as.Call(ctx, req)
333 | }
334 |
335 | type OcoOrderResModel struct {
336 | OrderId string `json:"order_id"`
337 | Symbol string `json:"symbol"`
338 | ClientOid string `json:"clientOid"`
339 | OrderTime int64 `json:"orderTime"`
340 | Status string `json:"status"`
341 | }
342 |
343 | // OcoOrder returns a oco order by order id. return OcoOrderResModel
344 | func (as *ApiService) OcoOrder(ctx context.Context, orderId string) (*ApiResponse, error) {
345 | req := NewRequest(http.MethodGet, "/api/v3/oco/order/"+orderId, nil)
346 | return as.Call(ctx, req)
347 | }
348 |
349 | // OcoClientOrder returns a oco order by order id. return OcoOrderResModel
350 | func (as *ApiService) OcoClientOrder(ctx context.Context, clientOrderId string) (*ApiResponse, error) {
351 | req := NewRequest(http.MethodGet, "/api/v3/oco/client-order/"+clientOrderId, nil)
352 | return as.Call(ctx, req)
353 | }
354 |
355 | type OcoOrdersRes []*OcoOrderResModel
356 |
357 | // OcoOrders returns a oco order by order id. return OcoOrdersRes
358 | func (as *ApiService) OcoOrders(ctx context.Context, p map[string]string, pagination *PaginationParam) (*ApiResponse, error) {
359 | pagination.ReadParam(p)
360 | req := NewRequest(http.MethodGet, "/api/v3/oco/orders", p)
361 | return as.Call(ctx, req)
362 | }
363 |
364 | type OcoOrdersModel []*OrderDetailModel
365 |
366 | type OrderDetailModel struct {
367 | OrderId string `json:"order_id"`
368 | Symbol string `json:"symbol"`
369 | ClientOid string `json:"clientOid"`
370 | OrderTime int64 `json:"orderTime"`
371 | Status string `json:"status"`
372 | Orders []*OcoSubOrderModel `json:"orders"`
373 | }
374 | type OcoSubOrderModel struct {
375 | Id string `json:"id"`
376 | Symbol string `json:"symbol"`
377 | Side string `json:"side"`
378 | Price string `json:"price"`
379 | StopPrice string `json:"stopPrice"`
380 | Size string `json:"size"`
381 | Status string `json:"status"`
382 | }
383 |
384 | // OcoOrderDetail returns a oco order detail by order id. return OrderDetailModel
385 | func (as *ApiService) OcoOrderDetail(ctx context.Context, orderId string) (*ApiResponse, error) {
386 | req := NewRequest(http.MethodGet, "/api/v3/oco/order/details/"+orderId, nil)
387 | return as.Call(ctx, req)
388 | }
389 |
--------------------------------------------------------------------------------
/hf_order_test.go:
--------------------------------------------------------------------------------
1 | package kucoin
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestApiService_HfPlaceOrder(t *testing.T) {
10 | s := NewApiServiceFromEnv()
11 | clientOid := IntToString(time.Now().Unix())
12 | p := map[string]string{
13 | "clientOid": clientOid,
14 | "symbol": "KCS-USDT",
15 | "type": "limit",
16 | "side": "buy",
17 | "stp": "CN",
18 | "size": "1",
19 | "price": "0.1",
20 | }
21 | rsp, err := s.HfPlaceOrder(context.Background(), p)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 | v := &HfPlaceOrderRes{}
26 | if err := rsp.ReadData(v); err != nil {
27 | t.Fatal(err)
28 | }
29 | t.Log(ToJsonString(v))
30 | if v.OrderId == "" {
31 | t.Error("Empty key 'orderId'")
32 | }
33 | }
34 |
35 | func TestApiService_HfSyncPlaceOrder(t *testing.T) {
36 | t.SkipNow()
37 | s := NewApiServiceFromEnv()
38 | clientOid := IntToString(time.Now().Unix())
39 | p := map[string]string{
40 | "clientOid": clientOid,
41 | "symbol": "MATIC-USDT",
42 | "type": "market",
43 | "side": "sell",
44 | "stp": "CN",
45 | "tags": "t",
46 | "remark": "r",
47 | "size": "0.1",
48 | }
49 | rsp, err := s.HfSyncPlaceOrder(context.Background(), p)
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 | v := &HfSyncPlaceOrderRes{}
54 | if err := rsp.ReadData(v); err != nil {
55 | t.Fatal(err)
56 | }
57 | t.Log(ToJsonString(v))
58 | if v.OrderId == "" {
59 | t.Error("Empty key 'orderId'")
60 | }
61 | }
62 |
63 | func TestApiService_HfPlaceMultiOrders(t *testing.T) {
64 | t.SkipNow()
65 | s := NewApiServiceFromEnv()
66 | clientOid := IntToString(time.Now().Unix())
67 | p := make([]*HFCreateMultiOrderModel, 0)
68 | p = append(p, &HFCreateMultiOrderModel{
69 | ClientOid: clientOid,
70 | Symbol: "MATIC-USDT",
71 | OrderType: "market",
72 | Side: "sell",
73 | Size: "0.1",
74 | })
75 |
76 | clientOid2 := IntToString(time.Now().Unix())
77 | p = append(p, &HFCreateMultiOrderModel{
78 | ClientOid: clientOid2,
79 | Symbol: "MATIC-USDT",
80 | OrderType: "market",
81 | Side: "sell",
82 | Size: "0.1",
83 | })
84 |
85 | rsp, err := s.HfPlaceMultiOrders(context.Background(), p)
86 | if err != nil {
87 | t.Fatal(err)
88 | }
89 | v := &HfPlaceMultiOrdersRes{}
90 | if err := rsp.ReadData(v); err != nil {
91 | t.Fatal(err)
92 | }
93 | t.Log(ToJsonString(v))
94 | }
95 |
96 | func TestApiService_HfSyncPlaceMultiOrders(t *testing.T) {
97 | t.SkipNow()
98 | s := NewApiServiceFromEnv()
99 | clientOid := IntToString(time.Now().Unix())
100 | p := make([]*HFCreateMultiOrderModel, 0)
101 | p = append(p, &HFCreateMultiOrderModel{
102 | ClientOid: clientOid,
103 | Symbol: "MATIC-USDT",
104 | OrderType: "market",
105 | Side: "buy",
106 | Size: "0.1",
107 | })
108 |
109 | clientOid2 := IntToString(time.Now().Unix())
110 | p = append(p, &HFCreateMultiOrderModel{
111 | ClientOid: clientOid2,
112 | Symbol: "MATIC-USDT",
113 | OrderType: "market",
114 | Side: "buy",
115 | Size: "0.2",
116 | })
117 |
118 | rsp, err := s.HfSyncPlaceMultiOrders(context.Background(), p)
119 | if err != nil {
120 | t.Fatal(err)
121 | }
122 | v := &HfSyncPlaceMultiOrdersRes{}
123 | if err := rsp.ReadData(v); err != nil {
124 | t.Fatal(err)
125 | }
126 | t.Log(ToJsonString(v))
127 | }
128 |
129 | func TestApiService_HfObtainFilledOrders(t *testing.T) {
130 | s := NewApiServiceFromEnv()
131 | p := map[string]string{
132 | "symbol": "MATIC-USDT",
133 | }
134 | rsp, err := s.HfObtainFilledOrders(context.Background(), p)
135 | if err != nil {
136 | t.Fatal(err)
137 | }
138 | v := &HfFilledOrdersModel{}
139 | if err := rsp.ReadData(v); err != nil {
140 | t.Fatal(err)
141 | }
142 | if v == nil {
143 | return
144 | }
145 | for _, o := range v.Items {
146 | t.Log(ToJsonString(o))
147 | switch {
148 | case o.Id == "":
149 | t.Error("Empty key 'id'")
150 | case o.Symbol == "":
151 | t.Error("Empty key 'symbol'")
152 | case o.OpType == "":
153 | t.Error("Empty key 'opType'")
154 | case o.Type == "":
155 | t.Error("Empty key 'type'")
156 | case o.Side == "":
157 | t.Error("Empty key 'side'")
158 | }
159 | }
160 | }
161 |
162 | func TestApiService_HfObtainActiveOrders(t *testing.T) {
163 | s := NewApiServiceFromEnv()
164 | rsp, err := s.HfObtainActiveOrders(context.Background(), "MATIC-USDT")
165 | if err != nil {
166 | t.Fatal(err)
167 | }
168 | v := HfOrdersModel{}
169 | if err := rsp.ReadData(&v); err != nil {
170 | t.Fatal(err)
171 | }
172 | for _, o := range v {
173 | t.Log(ToJsonString(o))
174 | switch {
175 | case o.Id == "":
176 | t.Error("Empty key 'id'")
177 | case o.Symbol == "":
178 | t.Error("Empty key 'symbol'")
179 | case o.OpType == "":
180 | t.Error("Empty key 'opType'")
181 | case o.Type == "":
182 | t.Error("Empty key 'type'")
183 | case o.Side == "":
184 | t.Error("Empty key 'side'")
185 | }
186 | }
187 | }
188 |
189 | func TestApiService_HfObtainActiveSymbols(t *testing.T) {
190 | s := NewApiServiceFromEnv()
191 | rsp, err := s.HfObtainActiveSymbols(context.Background())
192 | if err != nil {
193 | t.Fatal(err)
194 | }
195 | v := HfSymbolsModel{}
196 | if err := rsp.ReadData(&v); err != nil {
197 | t.Fatal(err)
198 | }
199 | t.Log(ToJsonString(v))
200 | }
201 |
202 | func TestApiService_HfOrderDetail(t *testing.T) {
203 | s := NewApiServiceFromEnv()
204 | rsp, err := s.HfOrderDetail(context.Background(), "649a45d576174800019e44b4", "MATIC-USDT")
205 | if err != nil {
206 | t.Fatal(err)
207 | }
208 | v := &HfOrderModel{}
209 | if err := rsp.ReadData(v); err != nil {
210 | t.Fatal(err)
211 | }
212 | t.Log(ToJsonString(v))
213 | }
214 |
215 | func TestApiService_HfOrderDetailByClientOid(t *testing.T) {
216 | s := NewApiServiceFromEnv()
217 | rsp, err := s.HfOrderDetailByClientOid(context.Background(), "1687832021", "MATIC-USDT")
218 | if err != nil {
219 | t.Fatal(err)
220 | }
221 | v := &HfOrderModel{}
222 | if err := rsp.ReadData(v); err != nil {
223 | t.Fatal(err)
224 | }
225 | t.Log(ToJsonString(v))
226 | }
227 |
228 | // 649a45d576174800019e44b4
229 |
230 | func TestApiService_HfModifyOrder(t *testing.T) {
231 | t.SkipNow()
232 | s := NewApiServiceFromEnv()
233 | p := map[string]string{
234 | "symbol": "MATIC-USDT",
235 | "orderId": "649a45d576174800019e44b4",
236 | "newPrice": "2.0",
237 | }
238 | rsp, err := s.HfModifyOrder(context.Background(), p)
239 | if err != nil {
240 | t.Fatal(err)
241 | }
242 | v := &HfModifyOrderRes{}
243 | if err := rsp.ReadData(v); err != nil {
244 | t.Fatal(err)
245 | }
246 | t.Log(ToJsonString(v))
247 | }
248 |
249 | func TestApiService_HfQueryAutoCancelSetting(t *testing.T) {
250 | s := NewApiServiceFromEnv()
251 | rsp, err := s.HfQueryAutoCancelSetting(context.Background())
252 | if err != nil {
253 | t.Fatal(err)
254 | }
255 | v := &AUtoCancelSettingModel{}
256 | if err := rsp.ReadData(v); err != nil {
257 | t.Fatal(err)
258 | }
259 | t.Log(ToJsonString(v))
260 | }
261 |
262 | func TestApiService_HfAutoCancelSetting(t *testing.T) {
263 | t.SkipNow()
264 | s := NewApiServiceFromEnv()
265 | rsp, err := s.HfAutoCancelSetting(context.Background(), 10000, "MATIC-USDT")
266 | if err != nil {
267 | t.Fatal(err)
268 | }
269 | v := &HfAutoCancelSettingRes{}
270 | if err := rsp.ReadData(v); err != nil {
271 | t.Fatal(err)
272 | }
273 | t.Log(ToJsonString(v))
274 | }
275 |
276 | func TestApiService_HfCancelOrder(t *testing.T) {
277 | t.SkipNow()
278 | s := NewApiServiceFromEnv()
279 | rsp, err := s.HfCancelOrder(context.Background(), "649a49201a39390001adcce8", "MATIC-USDT")
280 | if err != nil {
281 | t.Fatal(err)
282 | }
283 | v := &HfOrderIdModel{}
284 | if err := rsp.ReadData(v); err != nil {
285 | t.Fatal(err)
286 | }
287 | t.Log(ToJsonString(v))
288 | }
289 |
290 | func TestApiService_HfSyncCancelOrder(t *testing.T) {
291 | t.SkipNow()
292 | s := NewApiServiceFromEnv()
293 | rsp, err := s.HfSyncCancelOrder(context.Background(), "649a49201a39390001adcce8", "MATIC-USDT")
294 | if err != nil {
295 | t.Fatal(err)
296 | }
297 | v := &HfSyncCancelOrderRes{}
298 | if err := rsp.ReadData(v); err != nil {
299 | t.Fatal(err)
300 | }
301 | t.Log(ToJsonString(v))
302 | }
303 |
304 | func TestApiService_HfCancelOrderByClientId(t *testing.T) {
305 | t.SkipNow()
306 | s := NewApiServiceFromEnv()
307 | rsp, err := s.HfCancelOrderByClientId(context.Background(), "649a49201a39390001adcce8", "MATIC-USDT")
308 | if err != nil {
309 | t.Fatal(err)
310 | }
311 | v := &HfClientOidModel{}
312 | if err := rsp.ReadData(v); err != nil {
313 | t.Fatal(err)
314 | }
315 | t.Log(ToJsonString(v))
316 | }
317 |
318 | func TestApiService_HfSyncCancelOrderByClientId(t *testing.T) {
319 | t.SkipNow()
320 | s := NewApiServiceFromEnv()
321 | rsp, err := s.HfSyncCancelOrderByClientId(context.Background(), "649a49201a39390001adcce8", "MATIC-USDT")
322 | if err != nil {
323 | t.Fatal(err)
324 | }
325 | v := &HfSyncCancelOrderRes{}
326 | if err := rsp.ReadData(v); err != nil {
327 | t.Fatal(err)
328 | }
329 | t.Log(ToJsonString(v))
330 | }
331 |
332 | func TestApiService_HfSyncCancelOrderWithSize(t *testing.T) {
333 | t.SkipNow()
334 | s := NewApiServiceFromEnv()
335 | rsp, err := s.HfSyncCancelOrderWithSize(context.Background(), "649a49201a39390001adcce8", "MATIC-USDT", "0.3")
336 | if err != nil {
337 | t.Fatal(err)
338 | }
339 | v := &HfSyncCancelOrderWithSizeRes{}
340 | if err := rsp.ReadData(v); err != nil {
341 | t.Fatal(err)
342 | }
343 | t.Log(ToJsonString(v))
344 | }
345 | func TestApiService_HfSyncCancelAllOrders(t *testing.T) {
346 | t.SkipNow()
347 | s := NewApiServiceFromEnv()
348 | rsp, err := s.HfSyncCancelAllOrders(context.Background(), "MATIC-USDT")
349 | if err != nil {
350 | t.Fatal(err)
351 | }
352 | data := new(string)
353 | if err := rsp.ReadData(data); err != nil {
354 | t.Fatal(err)
355 | }
356 | t.Log(ToJsonString(data))
357 | }
358 |
359 | func TestApiService_HfTransactionDetails(t *testing.T) {
360 | s := NewApiServiceFromEnv()
361 | p := map[string]string{
362 | "symbol": "MATIC-USDT",
363 | }
364 | rsp, err := s.HfTransactionDetails(context.Background(), p)
365 | if err != nil {
366 | t.Fatal(err)
367 | }
368 | v := &HfTransactionDetailsModel{}
369 | if err := rsp.ReadData(v); err != nil {
370 | t.Fatal(err)
371 | }
372 | if v == nil {
373 | return
374 | }
375 | for _, item := range v.Items {
376 | t.Log(ToJsonString(item))
377 | }
378 | }
379 | func TestApiService_HfCancelOrders(t *testing.T) {
380 |
381 | s := NewApiServiceFromEnv()
382 | rsp, err := s.HfCancelOrders(context.Background())
383 | if err != nil {
384 | t.Fatal(err)
385 | }
386 | o := &HfCancelOrdersResultModel{}
387 | if err := rsp.ReadData(o); err != nil {
388 | t.Fatal(err)
389 | }
390 | t.Log(ToJsonString(o))
391 |
392 | }
393 |
394 | func TestApiService_HfMarginActiveSymbols(t *testing.T) {
395 |
396 | s := NewApiServiceFromEnv()
397 | rsp, err := s.HfMarginActiveSymbols(context.Background(), "MARGIN_ISOLATED_TRADE")
398 | if err != nil {
399 | t.Fatal(err)
400 | }
401 | o := &HFMarginActiveSymbolsModel{}
402 | if err := rsp.ReadData(o); err != nil {
403 | t.Fatal(err)
404 | }
405 | t.Log(ToJsonString(o))
406 |
407 | }
408 |
409 | func TestApiService_HfMarginOrderV3(t *testing.T) {
410 | s := NewApiServiceFromEnv()
411 |
412 | // market order
413 | req := &HfMarginOrderV3Req{
414 | ClientOid: IntToString(time.Now().Unix()),
415 | Side: "buy",
416 | Symbol: "PEPE-USDT",
417 | Type: "market",
418 | Stp: "CN",
419 | IsIsolated: false,
420 | AutoBorrow: true,
421 | AutoRepay: true,
422 | Funds: "8",
423 | }
424 |
425 | rsp, err := s.HfCreateMarinOrderV3(context.Background(), req)
426 | if err != nil {
427 | t.Fatal(err)
428 | }
429 | o := &HfMarginOrderV3Resp{}
430 | if err := rsp.ReadData(o); err != nil {
431 | t.Fatal(err)
432 | }
433 | t.Log(ToJsonString(o))
434 |
435 | reqSell := &HfMarginOrderV3Req{
436 | ClientOid: IntToString(time.Now().Unix()),
437 | Side: "sell",
438 | Symbol: "PEPE-USDT",
439 | Type: "market",
440 | Stp: "CN",
441 | IsIsolated: false,
442 | AutoBorrow: true,
443 | AutoRepay: true,
444 | Funds: "100000",
445 | }
446 |
447 | rsp, err = s.HfCreateMarinOrderV3(context.Background(), reqSell)
448 | if err != nil {
449 | t.Fatal(err)
450 | }
451 | o = &HfMarginOrderV3Resp{}
452 | if err := rsp.ReadData(o); err != nil {
453 | t.Fatal(err)
454 | }
455 | t.Log(ToJsonString(o))
456 |
457 | // limit order
458 | reqLimit := &HfMarginOrderV3Req{
459 | ClientOid: IntToString(time.Now().Unix()),
460 | Side: "buy",
461 | Symbol: "SHIB-USDT",
462 | Type: "limit",
463 | Stp: "CN",
464 | IsIsolated: false,
465 | AutoBorrow: true,
466 | AutoRepay: true,
467 | Price: "0.000001",
468 | Size: "1000000",
469 | }
470 |
471 | rspObj, err := s.HfCreateMarinOrderV3(context.Background(), reqLimit)
472 | if err != nil {
473 | t.Fatal(err)
474 | }
475 | o = &HfMarginOrderV3Resp{}
476 | if err := rspObj.ReadData(o); err != nil {
477 | t.Fatal(err)
478 | }
479 | t.Log(ToJsonString(o))
480 | }
481 |
482 | func TestApiService_HfMarginOrderTestV3(t *testing.T) {
483 | s := NewApiServiceFromEnv()
484 |
485 | req := &HfMarginOrderV3Req{
486 | ClientOid: IntToString(time.Now().Unix()),
487 | Side: "buy",
488 | Symbol: "PEPE-USDT",
489 | Type: "market",
490 | Stp: "CN",
491 | IsIsolated: false,
492 | AutoBorrow: true,
493 | AutoRepay: true,
494 | Funds: "8",
495 | }
496 |
497 | rsp, err := s.HfCreateMarinOrderTestV3(context.Background(), req)
498 | if err != nil {
499 | t.Fatal(err)
500 | }
501 | o := &HfMarginOrderV3Resp{}
502 | if err := rsp.ReadData(o); err != nil {
503 | t.Fatal(err)
504 | }
505 | t.Log(ToJsonString(o))
506 |
507 | }
508 |
509 | func TestApiService_HfCancelMarinOrderV3(t *testing.T) {
510 | s := NewApiServiceFromEnv()
511 |
512 | req := &HfCancelMarinOrderV3Req{
513 | OrderId: "66ab62c1693a4f000753b464",
514 | Symbol: "SHIB-USDT",
515 | }
516 | rsp, err := s.HfCancelMarinOrderV3(context.Background(), req)
517 | if err != nil {
518 | t.Fatal(err)
519 | }
520 | o := &HfCancelMarinOrderV3Resp{}
521 | if err := rsp.ReadData(o); err != nil {
522 | t.Fatal(err)
523 | }
524 | t.Log(ToJsonString(o))
525 | }
526 |
527 | func TestApiService_HfCancelClientMarinOrderV3(t *testing.T) {
528 | s := NewApiServiceFromEnv()
529 |
530 | req := &HfCancelClientMarinOrderV3Req{
531 | ClientOid: "1722508074",
532 | Symbol: "SHIB-USDT",
533 | }
534 |
535 | rsp, err := s.HfCancelClientMarinOrderV3(context.Background(), req)
536 | if err != nil {
537 | t.Fatal(err)
538 | }
539 | o := &HfCancelClientMarinOrderV3Resp{}
540 | if err := rsp.ReadData(o); err != nil {
541 | t.Fatal(err)
542 | }
543 | t.Log(ToJsonString(o))
544 |
545 | }
546 |
547 | func TestApiService_HfCancelAllMarginOrdersV3(t *testing.T) {
548 | s := NewApiServiceFromEnv()
549 |
550 | req := &HfCancelAllMarginOrdersV3Req{
551 | TradeType: "MARGIN_TRADE",
552 | Symbol: "SHIB-USDT",
553 | }
554 |
555 | rsp, err := s.HfCancelAllMarginOrdersV3(context.Background(), req)
556 | if err != nil {
557 | t.Fatal(err)
558 | }
559 | var o HfCancelAllMarginOrdersV3Resp
560 | if err := rsp.ReadData(&o); err != nil {
561 | t.Fatal(err)
562 | }
563 | t.Log(ToJsonString(o))
564 |
565 | }
566 |
567 | func TestApiService_HfMarinActiveOrdersV3(t *testing.T) {
568 | s := NewApiServiceFromEnv()
569 |
570 | req := &HfMarinActiveOrdersV3Req{
571 | TradeType: "MARGIN_TRADE",
572 | Symbol: "SHIB-USDT",
573 | }
574 |
575 | rsp, err := s.HfMarinActiveOrdersV3(context.Background(), req)
576 | if err != nil {
577 | t.Fatal(err)
578 | }
579 | o := &HfMarinActiveOrdersV3Resp{}
580 | if err := rsp.ReadData(o); err != nil {
581 | t.Fatal(err)
582 | }
583 | t.Log(ToJsonString(o))
584 |
585 | }
586 |
587 | func TestApiService_HfMarinDoneOrdersV3(t *testing.T) {
588 | s := NewApiServiceFromEnv()
589 |
590 | {
591 | req := &HfMarinDoneOrdersV3Req{
592 | TradeType: "MARGIN_TRADE",
593 | Symbol: "PEPE-USDT",
594 | }
595 |
596 | rsp, err := s.HfMarinDoneOrdersV3(context.Background(), req)
597 | if err != nil {
598 | t.Fatal(err)
599 | }
600 | o := &HfMarinDoneOrdersV3Resp{}
601 | if err := rsp.ReadData(o); err != nil {
602 | t.Fatal(err)
603 | }
604 | t.Log(ToJsonString(o))
605 | }
606 | {
607 | req := &HfMarinDoneOrdersV3Req{
608 | TradeType: "MARGIN_TRADE",
609 | Symbol: "PEPE-USDT",
610 | Side: "buy",
611 | Type: "market",
612 | StartAt: 1722482940355,
613 | }
614 |
615 | rsp, err := s.HfMarinDoneOrdersV3(context.Background(), req)
616 | if err != nil {
617 | t.Fatal(err)
618 | }
619 | o := &HfMarinDoneOrdersV3Resp{}
620 | if err := rsp.ReadData(o); err != nil {
621 | t.Fatal(err)
622 | }
623 | t.Log(ToJsonString(o))
624 | }
625 |
626 | }
627 |
628 | func TestApiService_HfMarinOrderV3(t *testing.T) {
629 | s := NewApiServiceFromEnv()
630 |
631 | req := &HfMarinOrderV3Req{
632 | OrderId: "66ab00fc693a4f0007ac03db",
633 | Symbol: "PEPE-USDT",
634 | }
635 |
636 | rsp, err := s.HfMarinOrderV3(context.Background(), req)
637 | if err != nil {
638 | t.Fatal(err)
639 | }
640 | o := &HfMarinOrderV3Resp{}
641 | if err := rsp.ReadData(o); err != nil {
642 | t.Fatal(err)
643 | }
644 | t.Log(ToJsonString(o))
645 |
646 | }
647 |
648 | func TestApiService_HfMarinClientOrderV3(t *testing.T) {
649 | s := NewApiServiceFromEnv()
650 |
651 | req := &HfMarinClientOrderV3Req{
652 | ClientOid: "1722482939",
653 | Symbol: "PEPE-USDT",
654 | }
655 |
656 | rsp, err := s.HfMarinClientOrderV3(context.Background(), req)
657 | if err != nil {
658 | t.Fatal(err)
659 | }
660 | o := &HfMarinClientOrderV3Resp{}
661 | if err := rsp.ReadData(o); err != nil {
662 | t.Fatal(err)
663 | }
664 | t.Log(ToJsonString(o))
665 | }
666 |
667 | func TestApiService_HfMarinFillsV3(t *testing.T) {
668 | s := NewApiServiceFromEnv()
669 |
670 | {
671 | req := &HfMarinFillsV3Req{
672 | Symbol: "PEPE-USDT",
673 | TradeType: "MARGIN_TRADE",
674 | Side: "buy",
675 | }
676 |
677 | rsp, err := s.HfMarinFillsV3(context.Background(), req)
678 | if err != nil {
679 | t.Fatal(err)
680 | }
681 | o := &HfMarinFillsV3Resp{}
682 | if err := rsp.ReadData(o); err != nil {
683 | t.Fatal(err)
684 | }
685 | t.Log(ToJsonString(o))
686 | }
687 |
688 | {
689 | req := &HfMarinFillsV3Req{
690 | Symbol: "PEPE-USDT",
691 | TradeType: "MARGIN_TRADE",
692 | Side: "buy",
693 | OrderId: "66ab00fc693a4f0007ac03db",
694 | }
695 |
696 | rsp, err := s.HfMarinFillsV3(context.Background(), req)
697 | if err != nil {
698 | t.Fatal(err)
699 | }
700 | o := &HfMarinFillsV3Resp{}
701 | if err := rsp.ReadData(o); err != nil {
702 | t.Fatal(err)
703 | }
704 | t.Log(ToJsonString(o))
705 | }
706 | }
707 |
--------------------------------------------------------------------------------