Skip to content

Commit 7952cb0

Browse files
authored
Add Kucoin Spot exchange (#34)
1 parent 40c5ef8 commit 7952cb0

File tree

14 files changed

+559
-4
lines changed

14 files changed

+559
-4
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "CryptoAPIs"
22
uuid = "5e3d4798-c815-4641-85e1-deed530626d3"
3-
version = "0.16.2"
3+
version = "0.17.0"
44

55
[deps]
66
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,14 @@ Then, to install CryptoAPIs, simply use the Julia package manager:
8888
<td><a href="https://www.gate.io/docs/developers/apiv4/">Futures</a></td>
8989
<td><a href="src/Gateio/Futures">CryptoAPIs.Gateio.Futures</a></td>
9090
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Gateio/#Futures">Futures</a></td>
91-
</tr>
91+
</tr>
92+
<tr>
93+
<td><img src="docs/src/assets/kucoin.png" alt="Kucoin Logo" width="20" height="20"></td>
94+
<td><a href="https://www.kucoin.com/">Kucoin</a></td>
95+
<td><a href="https://www.kucoin.com/docs/beginners/introduction">Spot</a></td>
96+
<td><a href="src/Kucoin/Spot">CryptoAPIs.Kucoin.Spot</a></td>
97+
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Kucoin/#Spot">Spot</a></td>
98+
</tr>
9299
<tr>
93100
<td><img src="docs/src/assets/okex.png" alt="Okex Logo" width="20" height="20"></td>
94101
<td><a href="https://www.okx.com/">Okex</a></td>

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ makedocs(;
2121
"pages/Coinbase.md",
2222
"pages/Cryptocom.md",
2323
"pages/Gateio.md",
24+
"pages/Kucoin.md",
2425
"pages/Okex.md",
2526
"pages/Upbit.md",
2627
"For Developers" => [

docs/src/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ Then, to install CryptoAPIs, simply use the Julia package manager:
8383
<td><a href="src/Gateio/Futures">CryptoAPIs.Gateio.Futures</a></td>
8484
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Gateio/#Futures">Futures</a></td>
8585
</tr>
86+
<tr>
87+
<td><img src="assets/kucoin.png" alt="Kucoin Logo" width="20" height="20"></td>
88+
<td><a href="https://www.kucoin.com/">Kucoin</a></td>
89+
<td><a href="https://www.kucoin.com/docs/beginners/introduction">Spot</a></td>
90+
<td><a href="src/Kucoin/Spot">CryptoAPIs.Kucoin.Spot</a></td>
91+
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Kucoin/#Spot">Spot</a></td>
92+
</tr>
8693
<tr>
8794
<td><img src="assets/okex.png" alt="Okex Logo" width="20" height="20"></td>
8895
<td><a href="https://www.okx.com/">Okex</a></td>

docs/src/pages/Kucoin.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Kucoin
2+
3+
```@docs
4+
CryptoAPIs.Kucoin.KucoinClient
5+
CryptoAPIs.Kucoin.KucoinAPIError
6+
CryptoAPIs.Kucoin.Data
7+
CryptoAPIs.Kucoin.Page
8+
```
9+
10+
## Spot
11+
12+
```@docs
13+
CryptoAPIs.Kucoin.Spot.public_client
14+
```
15+
16+
```@docs
17+
CryptoAPIs.Kucoin.Spot.candle
18+
CryptoAPIs.Kucoin.Spot.deposit
19+
```

examples/Kucoin/Spot.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Kucoin/Spot
2+
# https://docs.kucoin.com/
3+
4+
using Dates
5+
using CryptoAPIs
6+
using CryptoAPIs.Kucoin
7+
8+
CryptoAPIs.Kucoin.Spot.candle(;
9+
symbol = "BTC-USDT",
10+
type = CryptoAPIs.Kucoin.Spot.Candle.m1,
11+
)
12+
13+
kucoin_client = KucoinClient(;
14+
base_url = "https://api.kucoin.com",
15+
public_key = ENV["KUCOIN_PUBLIC_KEY"],
16+
secret_key = ENV["KUCOIN_SECRET_KEY"],
17+
passphrase = ENV["KUCOIN_PASSPHRASE"],
18+
)
19+
20+
CryptoAPIs.Kucoin.Spot.deposit(
21+
kucoin_client;
22+
currency = "BTC",
23+
)

src/Bybit/Utils.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# Bybit/Utils
22

3-
function Serde.deser(::Type{<:AbstractAPIsData}, ::Type{<:Maybe{NanoDate}}, x::Int64)::NanoDate
3+
function Serde.deser(::Type{<:BybitData}, ::Type{<:Maybe{NanoDate}}, x::Int64)::NanoDate
44
return unixnanos2nanodate(x * 1e6)
55
end
66

7-
function Serde.deser(::Type{<:AbstractAPIsData}, ::Type{<:Maybe{NanoDate}}, x::String)::NanoDate
7+
function Serde.deser(::Type{<:Data}, ::Type{<:Maybe{NanoDate}}, x::Int64)::NanoDate
8+
return unixnanos2nanodate(x * 1e6)
9+
end
10+
11+
function Serde.deser(::Type{<:BybitData}, ::Type{<:Maybe{NanoDate}}, x::String)::NanoDate
812
return unixnanos2nanodate(parse(Int64, x) * 1e6)
913
end
1014

src/CryptoAPIs.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ include("Coinbase/Coinbase.jl")
166166
include("Cryptocom/Cryptocom.jl")
167167
include("Gateio/Gateio.jl")
168168
include("Okex/Okex.jl")
169+
include("Kucoin/Kucoin.jl")
169170
include("Upbit/Upbit.jl")
170171

171172
end

src/Kucoin/Errors.jl

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Errors
2+
# https://docs.kucoin.com/#request
3+
4+
import ..CryptoAPIs: APIsResult, APIsUndefError
5+
import ..CryptoAPIs: isretriable, retry_maxcount, retry_timeout
6+
7+
# UNDEF
8+
isretriable(e::APIsResult{KucoinAPIError}) = true
9+
10+
# Order creation for this pair suspended
11+
isretriable(e::APIsResult{KucoinAPIError{200001}}) = false
12+
13+
# Order cancel for this pair suspended
14+
isretriable(e::APIsResult{KucoinAPIError{200002}}) = false
15+
16+
# Number of orders breached the limit
17+
isretriable(e::APIsResult{KucoinAPIError{200003}}) = false
18+
19+
# Please complete the KYC verification before you trade XX
20+
isretriable(e::APIsResult{KucoinAPIError{200009}}) = false
21+
22+
# Balance insufficient
23+
isretriable(e::APIsResult{KucoinAPIError{200004}}) = false
24+
25+
# withdraw.disabled -- Currency/Chain withdraw is closed, or user is frozen to withdraw
26+
isretriable(e::APIsResult{KucoinAPIError{260210}}) = false
27+
28+
# Any of KC-API-KEY, KC-API-SIGN, KC-API-TIMESTAMP, KC-API-PASSPHRASE is missing in your request header
29+
isretriable(e::APIsResult{KucoinAPIError{400001}}) = false
30+
31+
# KC-API-TIMESTAMP Invalid
32+
isretriable(e::APIsResult{KucoinAPIError{400002}}) = false
33+
34+
# KC-API-KEY not exists
35+
isretriable(e::APIsResult{KucoinAPIError{400003}}) = false
36+
37+
# KC-API-PASSPHRASE error
38+
isretriable(e::APIsResult{KucoinAPIError{400004}}) = false
39+
40+
# Signature error
41+
isretriable(e::APIsResult{KucoinAPIError{400005}}) = false
42+
43+
# The requested ip address is not in the api whitelist
44+
isretriable(e::APIsResult{KucoinAPIError{400006}}) = false
45+
46+
# Access Denied
47+
isretriable(e::APIsResult{KucoinAPIError{400007}}) = false
48+
49+
# Url Not Found
50+
isretriable(e::APIsResult{KucoinAPIError{404000}}) = false
51+
52+
# Parameter Error
53+
isretriable(e::APIsResult{KucoinAPIError{400100}}) = false
54+
55+
# Forbidden to place an order
56+
isretriable(e::APIsResult{KucoinAPIError{400200}}) = false
57+
58+
# Your located country/region is currently not supported for the trading of this token
59+
isretriable(e::APIsResult{KucoinAPIError{400500}}) = false
60+
61+
# validation.createOrder.symbolNotAvailable -- The trading pair has not yet started trading
62+
isretriable(e::APIsResult{KucoinAPIError{400600}}) = false
63+
64+
# Transaction restricted, there's a risk problem in your account
65+
isretriable(e::APIsResult{KucoinAPIError{400700}}) = false
66+
67+
# Leverage order failed
68+
isretriable(e::APIsResult{KucoinAPIError{400800}}) = false
69+
70+
# User are frozen
71+
isretriable(e::APIsResult{KucoinAPIError{411100}}) = false
72+
73+
# Unsupported Media Type -- The Content-Type of the request header needs to be set to application/json
74+
isretriable(e::APIsResult{KucoinAPIError{415000}}) = false
75+
76+
# Too many request
77+
isretriable(e::APIsResult{KucoinAPIError{429000}}) = true
78+
retry_timeout(e::APIsResult{KucoinAPIError{429000}}) = 5
79+
retry_maxcount(e::APIsResult{KucoinAPIError{429000}}) = 50
80+
81+
# Internal Server Error
82+
isretriable(e::APIsResult{KucoinAPIError{500000}}) = true
83+
84+
# symbol not exists
85+
isretriable(e::APIsResult{KucoinAPIError{900001}}) = false

src/Kucoin/Kucoin.jl

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
module Kucoin
2+
3+
export KucoinCommonQuery,
4+
KucoinPublicQuery,
5+
KucoinAccessQuery,
6+
KucoinPrivateQuery,
7+
KucoinAPIError,
8+
KucoinClient,
9+
KucoinData
10+
11+
using Serde
12+
using Dates, NanoDates, TimeZones, Base64, Nettle
13+
14+
using ..CryptoAPIs
15+
import ..CryptoAPIs: Maybe, AbstractAPIsError, AbstractAPIsData, AbstractAPIsQuery, AbstractAPIsClient
16+
17+
abstract type KucoinData <: AbstractAPIsData end
18+
abstract type KucoinCommonQuery <: AbstractAPIsQuery end
19+
abstract type KucoinPublicQuery <: KucoinCommonQuery end
20+
abstract type KucoinAccessQuery <: KucoinCommonQuery end
21+
abstract type KucoinPrivateQuery <: KucoinCommonQuery end
22+
23+
"""
24+
Data{D} <: AbstractAPIsData
25+
26+
## Required fields
27+
- `code::Int64`: Result code.
28+
- `data::D`: Result data.
29+
"""
30+
struct Data{D<:Union{A,Vector{A}} where {A<:AbstractAPIsData}} <: AbstractAPIsData
31+
code::Int64
32+
data::D
33+
end
34+
35+
"""
36+
Page{D} <: AbstractAPIsData
37+
38+
## Required fields
39+
- `pageSize::Int64`: Number of results per request.
40+
- `totalNum::Int64`: Total number of results.
41+
- `currentPage::Int64`: Current request page.
42+
- `totalPage::Int64`: Total request page.
43+
- `items::Vector{D}`: Result data.
44+
"""
45+
struct Page{D<:AbstractAPIsData} <:AbstractAPIsData
46+
pageSize::Int64
47+
totalNum::Int64
48+
currentPage::Int64
49+
totalPage::Int64
50+
items::Vector{D}
51+
end
52+
53+
"""
54+
KucoinClient <: AbstractAPIsClient
55+
56+
Client info.
57+
58+
## Required fields
59+
- `base_url::String`: Base URL for the client.
60+
61+
## Optional fields
62+
- `public_key::String`: Public key for authentication.
63+
- `secret_key::String`: Secret key for authentication.
64+
- `interface::String`: Interface for the client.
65+
- `proxy::String`: Proxy information for the client.
66+
- `account_name::String`: Account name associated with the client.
67+
- `description::String`: Description of the client.
68+
"""
69+
Base.@kwdef struct KucoinClient <: AbstractAPIsClient
70+
base_url::String
71+
public_key::Maybe{String} = nothing
72+
secret_key::Maybe{String} = nothing
73+
passphrase::Maybe{String} = nothing
74+
interface::Maybe{String} = nothing
75+
proxy::Maybe{String} = nothing
76+
account_name::Maybe{String} = nothing
77+
description::Maybe{String} = nothing
78+
end
79+
80+
"""
81+
KucoinAPIError{T} <: AbstractAPIsError
82+
83+
Exception thrown when an API method fails with code `T`.
84+
85+
## Required fields
86+
- `code::Int64`: Error code.
87+
- `msg::String`: Error message.
88+
"""
89+
struct KucoinAPIError{T} <: AbstractAPIsError
90+
code::Int64
91+
msg::String
92+
93+
function KucoinAPIError(code::Int64, x...)
94+
return new{code}(code, x...)
95+
end
96+
end
97+
98+
CryptoAPIs.error_type(::KucoinClient) = KucoinAPIError
99+
100+
function Base.show(io::IO, e::KucoinAPIError)
101+
return print(io, "code = ", "\"", e.code, "\"", ", ", "msg = ", "\"", e.msg, "\"")
102+
end
103+
104+
function CryptoAPIs.request_sign!(::KucoinClient, query::Q, ::String)::Q where {Q<:KucoinPublicQuery}
105+
return query
106+
end
107+
108+
function CryptoAPIs.request_sign!(client::KucoinClient, query::Q, endpoint::String)::Q where {Q <: KucoinPrivateQuery}
109+
query.timestamp = Dates.now(UTC)
110+
query.signature = nothing
111+
salt = join([string(round(Int64, 1000 * datetime2unix(query.timestamp))), "GET/$endpoint?", Serde.to_query(query)])
112+
query.passphrase = Base64.base64encode(digest("sha256", client.secret_key, client.passphrase))
113+
query.signature = Base64.base64encode(digest("sha256", client.secret_key, salt))
114+
return query
115+
end
116+
117+
function CryptoAPIs.request_body(::Q)::String where {Q<:KucoinCommonQuery}
118+
return ""
119+
end
120+
121+
function CryptoAPIs.request_query(query::Q)::String where {Q<:KucoinCommonQuery}
122+
return Serde.to_query(query)
123+
end
124+
125+
function CryptoAPIs.request_headers(client::KucoinClient, ::KucoinPublicQuery)::Vector{Pair{String,String}}
126+
return Pair{String,String}[
127+
"Content-Type" => "application/json"
128+
]
129+
end
130+
131+
function CryptoAPIs.request_headers(client::KucoinClient, query::KucoinPrivateQuery)::Vector{Pair{String,String}}
132+
return Pair{String,String}[
133+
"KC-API-SIGN" => query.signature,
134+
"KC-API-TIMESTAMP" => string(round(Int64, 1000 * datetime2unix(query.timestamp))),
135+
"KC-API-KEY" => client.public_key,
136+
"KC-API-PASSPHRASE" => query.passphrase,
137+
"Content-Type" => "application/json",
138+
"KC-API-KEY-VERSION" => "2",
139+
]
140+
end
141+
142+
include("Utils.jl")
143+
include("Errors.jl")
144+
145+
include("Spot/Spot.jl")
146+
using .Spot
147+
148+
end

0 commit comments

Comments
 (0)