Skip to content

Commit

Permalink
Add Kucoin Spot exchange (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
yshadrin authored May 14, 2024
1 parent 40c5ef8 commit 7952cb0
Show file tree
Hide file tree
Showing 14 changed files with 559 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "CryptoAPIs"
uuid = "5e3d4798-c815-4641-85e1-deed530626d3"
version = "0.16.2"
version = "0.17.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ Then, to install CryptoAPIs, simply use the Julia package manager:
<td><a href="https://www.gate.io/docs/developers/apiv4/">Futures</a></td>
<td><a href="src/Gateio/Futures">CryptoAPIs.Gateio.Futures</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Gateio/#Futures">Futures</a></td>
</tr>
</tr>
<tr>
<td><img src="docs/src/assets/kucoin.png" alt="Kucoin Logo" width="20" height="20"></td>
<td><a href="https://www.kucoin.com/">Kucoin</a></td>
<td><a href="https://www.kucoin.com/docs/beginners/introduction">Spot</a></td>
<td><a href="src/Kucoin/Spot">CryptoAPIs.Kucoin.Spot</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Kucoin/#Spot">Spot</a></td>
</tr>
<tr>
<td><img src="docs/src/assets/okex.png" alt="Okex Logo" width="20" height="20"></td>
<td><a href="https://www.okx.com/">Okex</a></td>
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ makedocs(;
"pages/Coinbase.md",
"pages/Cryptocom.md",
"pages/Gateio.md",
"pages/Kucoin.md",
"pages/Okex.md",
"pages/Upbit.md",
"For Developers" => [
Expand Down
7 changes: 7 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ Then, to install CryptoAPIs, simply use the Julia package manager:
<td><a href="src/Gateio/Futures">CryptoAPIs.Gateio.Futures</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Gateio/#Futures">Futures</a></td>
</tr>
<tr>
<td><img src="assets/kucoin.png" alt="Kucoin Logo" width="20" height="20"></td>
<td><a href="https://www.kucoin.com/">Kucoin</a></td>
<td><a href="https://www.kucoin.com/docs/beginners/introduction">Spot</a></td>
<td><a href="src/Kucoin/Spot">CryptoAPIs.Kucoin.Spot</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Kucoin/#Spot">Spot</a></td>
</tr>
<tr>
<td><img src="assets/okex.png" alt="Okex Logo" width="20" height="20"></td>
<td><a href="https://www.okx.com/">Okex</a></td>
Expand Down
19 changes: 19 additions & 0 deletions docs/src/pages/Kucoin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Kucoin

```@docs
CryptoAPIs.Kucoin.KucoinClient
CryptoAPIs.Kucoin.KucoinAPIError
CryptoAPIs.Kucoin.Data
CryptoAPIs.Kucoin.Page
```

## Spot

```@docs
CryptoAPIs.Kucoin.Spot.public_client
```

```@docs
CryptoAPIs.Kucoin.Spot.candle
CryptoAPIs.Kucoin.Spot.deposit
```
23 changes: 23 additions & 0 deletions examples/Kucoin/Spot.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Kucoin/Spot
# https://docs.kucoin.com/

using Dates
using CryptoAPIs
using CryptoAPIs.Kucoin

CryptoAPIs.Kucoin.Spot.candle(;
symbol = "BTC-USDT",
type = CryptoAPIs.Kucoin.Spot.Candle.m1,
)

kucoin_client = KucoinClient(;
base_url = "https://api.kucoin.com",
public_key = ENV["KUCOIN_PUBLIC_KEY"],
secret_key = ENV["KUCOIN_SECRET_KEY"],
passphrase = ENV["KUCOIN_PASSPHRASE"],
)

CryptoAPIs.Kucoin.Spot.deposit(
kucoin_client;
currency = "BTC",
)
8 changes: 6 additions & 2 deletions src/Bybit/Utils.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Bybit/Utils

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

function Serde.deser(::Type{<:AbstractAPIsData}, ::Type{<:Maybe{NanoDate}}, x::String)::NanoDate
function Serde.deser(::Type{<:Data}, ::Type{<:Maybe{NanoDate}}, x::Int64)::NanoDate
return unixnanos2nanodate(x * 1e6)
end

function Serde.deser(::Type{<:BybitData}, ::Type{<:Maybe{NanoDate}}, x::String)::NanoDate
return unixnanos2nanodate(parse(Int64, x) * 1e6)
end

Expand Down
1 change: 1 addition & 0 deletions src/CryptoAPIs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ include("Coinbase/Coinbase.jl")
include("Cryptocom/Cryptocom.jl")
include("Gateio/Gateio.jl")
include("Okex/Okex.jl")
include("Kucoin/Kucoin.jl")
include("Upbit/Upbit.jl")

end
85 changes: 85 additions & 0 deletions src/Kucoin/Errors.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Errors
# https://docs.kucoin.com/#request

import ..CryptoAPIs: APIsResult, APIsUndefError
import ..CryptoAPIs: isretriable, retry_maxcount, retry_timeout

# UNDEF
isretriable(e::APIsResult{KucoinAPIError}) = true

# Order creation for this pair suspended
isretriable(e::APIsResult{KucoinAPIError{200001}}) = false

# Order cancel for this pair suspended
isretriable(e::APIsResult{KucoinAPIError{200002}}) = false

# Number of orders breached the limit
isretriable(e::APIsResult{KucoinAPIError{200003}}) = false

# Please complete the KYC verification before you trade XX
isretriable(e::APIsResult{KucoinAPIError{200009}}) = false

# Balance insufficient
isretriable(e::APIsResult{KucoinAPIError{200004}}) = false

# withdraw.disabled -- Currency/Chain withdraw is closed, or user is frozen to withdraw
isretriable(e::APIsResult{KucoinAPIError{260210}}) = false

# Any of KC-API-KEY, KC-API-SIGN, KC-API-TIMESTAMP, KC-API-PASSPHRASE is missing in your request header
isretriable(e::APIsResult{KucoinAPIError{400001}}) = false

# KC-API-TIMESTAMP Invalid
isretriable(e::APIsResult{KucoinAPIError{400002}}) = false

# KC-API-KEY not exists
isretriable(e::APIsResult{KucoinAPIError{400003}}) = false

# KC-API-PASSPHRASE error
isretriable(e::APIsResult{KucoinAPIError{400004}}) = false

# Signature error
isretriable(e::APIsResult{KucoinAPIError{400005}}) = false

# The requested ip address is not in the api whitelist
isretriable(e::APIsResult{KucoinAPIError{400006}}) = false

# Access Denied
isretriable(e::APIsResult{KucoinAPIError{400007}}) = false

# Url Not Found
isretriable(e::APIsResult{KucoinAPIError{404000}}) = false

# Parameter Error
isretriable(e::APIsResult{KucoinAPIError{400100}}) = false

# Forbidden to place an order
isretriable(e::APIsResult{KucoinAPIError{400200}}) = false

# Your located country/region is currently not supported for the trading of this token
isretriable(e::APIsResult{KucoinAPIError{400500}}) = false

# validation.createOrder.symbolNotAvailable -- The trading pair has not yet started trading
isretriable(e::APIsResult{KucoinAPIError{400600}}) = false

# Transaction restricted, there's a risk problem in your account
isretriable(e::APIsResult{KucoinAPIError{400700}}) = false

# Leverage order failed
isretriable(e::APIsResult{KucoinAPIError{400800}}) = false

# User are frozen
isretriable(e::APIsResult{KucoinAPIError{411100}}) = false

# Unsupported Media Type -- The Content-Type of the request header needs to be set to application/json
isretriable(e::APIsResult{KucoinAPIError{415000}}) = false

# Too many request
isretriable(e::APIsResult{KucoinAPIError{429000}}) = true
retry_timeout(e::APIsResult{KucoinAPIError{429000}}) = 5
retry_maxcount(e::APIsResult{KucoinAPIError{429000}}) = 50

# Internal Server Error
isretriable(e::APIsResult{KucoinAPIError{500000}}) = true

# symbol not exists
isretriable(e::APIsResult{KucoinAPIError{900001}}) = false
148 changes: 148 additions & 0 deletions src/Kucoin/Kucoin.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
module Kucoin

export KucoinCommonQuery,
KucoinPublicQuery,
KucoinAccessQuery,
KucoinPrivateQuery,
KucoinAPIError,
KucoinClient,
KucoinData

using Serde
using Dates, NanoDates, TimeZones, Base64, Nettle

using ..CryptoAPIs
import ..CryptoAPIs: Maybe, AbstractAPIsError, AbstractAPIsData, AbstractAPIsQuery, AbstractAPIsClient

abstract type KucoinData <: AbstractAPIsData end
abstract type KucoinCommonQuery <: AbstractAPIsQuery end
abstract type KucoinPublicQuery <: KucoinCommonQuery end
abstract type KucoinAccessQuery <: KucoinCommonQuery end
abstract type KucoinPrivateQuery <: KucoinCommonQuery end

"""
Data{D} <: AbstractAPIsData
## Required fields
- `code::Int64`: Result code.
- `data::D`: Result data.
"""
struct Data{D<:Union{A,Vector{A}} where {A<:AbstractAPIsData}} <: AbstractAPIsData
code::Int64
data::D
end

"""
Page{D} <: AbstractAPIsData
## Required fields
- `pageSize::Int64`: Number of results per request.
- `totalNum::Int64`: Total number of results.
- `currentPage::Int64`: Current request page.
- `totalPage::Int64`: Total request page.
- `items::Vector{D}`: Result data.
"""
struct Page{D<:AbstractAPIsData} <:AbstractAPIsData
pageSize::Int64
totalNum::Int64
currentPage::Int64
totalPage::Int64
items::Vector{D}
end

"""
KucoinClient <: AbstractAPIsClient
Client info.
## Required fields
- `base_url::String`: Base URL for the client.
## Optional fields
- `public_key::String`: Public key for authentication.
- `secret_key::String`: Secret key for authentication.
- `interface::String`: Interface for the client.
- `proxy::String`: Proxy information for the client.
- `account_name::String`: Account name associated with the client.
- `description::String`: Description of the client.
"""
Base.@kwdef struct KucoinClient <: AbstractAPIsClient
base_url::String
public_key::Maybe{String} = nothing
secret_key::Maybe{String} = nothing
passphrase::Maybe{String} = nothing
interface::Maybe{String} = nothing
proxy::Maybe{String} = nothing
account_name::Maybe{String} = nothing
description::Maybe{String} = nothing
end

"""
KucoinAPIError{T} <: AbstractAPIsError
Exception thrown when an API method fails with code `T`.
## Required fields
- `code::Int64`: Error code.
- `msg::String`: Error message.
"""
struct KucoinAPIError{T} <: AbstractAPIsError
code::Int64
msg::String

function KucoinAPIError(code::Int64, x...)
return new{code}(code, x...)
end
end

CryptoAPIs.error_type(::KucoinClient) = KucoinAPIError

function Base.show(io::IO, e::KucoinAPIError)
return print(io, "code = ", "\"", e.code, "\"", ", ", "msg = ", "\"", e.msg, "\"")
end

function CryptoAPIs.request_sign!(::KucoinClient, query::Q, ::String)::Q where {Q<:KucoinPublicQuery}
return query
end

function CryptoAPIs.request_sign!(client::KucoinClient, query::Q, endpoint::String)::Q where {Q <: KucoinPrivateQuery}
query.timestamp = Dates.now(UTC)
query.signature = nothing
salt = join([string(round(Int64, 1000 * datetime2unix(query.timestamp))), "GET/$endpoint?", Serde.to_query(query)])
query.passphrase = Base64.base64encode(digest("sha256", client.secret_key, client.passphrase))
query.signature = Base64.base64encode(digest("sha256", client.secret_key, salt))
return query
end

function CryptoAPIs.request_body(::Q)::String where {Q<:KucoinCommonQuery}
return ""
end

function CryptoAPIs.request_query(query::Q)::String where {Q<:KucoinCommonQuery}
return Serde.to_query(query)
end

function CryptoAPIs.request_headers(client::KucoinClient, ::KucoinPublicQuery)::Vector{Pair{String,String}}
return Pair{String,String}[
"Content-Type" => "application/json"
]
end

function CryptoAPIs.request_headers(client::KucoinClient, query::KucoinPrivateQuery)::Vector{Pair{String,String}}
return Pair{String,String}[
"KC-API-SIGN" => query.signature,
"KC-API-TIMESTAMP" => string(round(Int64, 1000 * datetime2unix(query.timestamp))),
"KC-API-KEY" => client.public_key,
"KC-API-PASSPHRASE" => query.passphrase,
"Content-Type" => "application/json",
"KC-API-KEY-VERSION" => "2",
]
end

include("Utils.jl")
include("Errors.jl")

include("Spot/Spot.jl")
using .Spot

end
Loading

0 comments on commit 7952cb0

Please sign in to comment.