Skip to content

Commit

Permalink
Add Cryptocom exchange (#26)
Browse files Browse the repository at this point in the history
Co-authored-by: Dmitrii Doronin <[email protected]>
Co-authored-by: Stas Gryumov <[email protected]>
  • Loading branch information
3 people authored Apr 18, 2024
1 parent d996638 commit 1905c14
Show file tree
Hide file tree
Showing 14 changed files with 585 additions and 1 deletion.
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.14.1"
version = "0.15.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ Then, to install CryptoAPIs, simply use the Julia package manager:
<td><a href="src/Upbit/Spot">CryptoAPIs.Upbit.Spot</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Upbit/#Spot">Spot</a></td>
</tr>
<tr>
<td><img src="docs/src/assets/cryptocom.png" alt="Cryptocom Logo" width="20" height="20"></td>
<td><a href="https://crypto.com/">Cryptocom</a></td>
<td><a href="https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#introduction">Spot</a></td>
<td><a href="src/Cryptocom/Spot">CryptoAPIs.Cryptocom.Spot</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Cryptocom/#Spot">Spot</a></td>
</tr>
</table>
</body>
</html>
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ makedocs(;
"pages/Binance.md",
"pages/Bybit.md",
"pages/Coinbase.md",
"pages/Cryptocom.md",
"pages/Gateio.md",
"pages/Okex.md",
"pages/Upbit.md",
Expand Down
7 changes: 7 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ CryptoAPIs is a library written in Julia that combines API wrappers from various
<td><a href="src/Upbit/Spot">CryptoAPIs.Upbit.Spot</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Upbit/#Spot">Spot</a></td>
</tr>
<tr>
<td><img src="assets/cryptocom.png" alt="Cryptocom Logo" width="20" height="20"></td>
<td><a href="https://crypto.com/">Cryptocom</a></td>
<td><a href="https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#introduction">Spot</a></td>
<td><a href="src/Cryptocom/Spot">CryptoAPIs.Cryptocom.Spot</a></td>
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Cryptocom/#Spot">Spot</a></td>
</tr>
</table>
</body>
</html>
Expand Down
19 changes: 19 additions & 0 deletions docs/src/pages/Cryptocom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Cryptocom

```@docs
CryptoAPIs.Cryptocom.CryptocomClient
CryptoAPIs.Cryptocom.CryptocomAPIError
CryptoAPIs.Cryptocom.Data
```

## Spot

```@docs
CryptoAPIs.Cryptocom.Spot.public_client
```

```@docs
CryptoAPIs.Cryptocom.Spot.candle
CryptoAPIs.Cryptocom.Spot.get_instruments
CryptoAPIs.Cryptocom.Spot.ticker
```
17 changes: 17 additions & 0 deletions examples/Cryptocom/Spot.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Cryptocom/Spot
# https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#introduction

using Dates
using CryptoAPIs
using CryptoAPIs.Cryptocom

CryptoAPIs.Cryptocom.Spot.candle(;
instrument_name = "BTC_USDT",
timeframe = Cryptocom.Spot.Candle.M1,
start_ts = Dates.now() - Dates.Day(1),
end_ts = Dates.now(),
)

CryptoAPIs.Cryptocom.Spot.get_instruments()

CryptoAPIs.Cryptocom.Spot.ticker(; instrument_name = "BTCUSD-PERP")
1 change: 1 addition & 0 deletions src/CryptoAPIs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ include("Interface.jl")
include("Binance/Binance.jl")
include("Bybit/Bybit.jl")
include("Coinbase/Coinbase.jl")
include("Cryptocom/Cryptocom.jl")
include("Gateio/Gateio.jl")
include("Okex/Okex.jl")
include("Upbit/Upbit.jl")
Expand Down
121 changes: 121 additions & 0 deletions src/Cryptocom/Cryptocom.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
module Cryptocom

export CryptocomCommonQuery,
CryptocomPublicQuery,
CryptocomAccessQuery,
CryptocomPrivateQuery,
CryptocomAPIError,
CryptocomClient,
CryptocomData

using Serde
using Dates, NanoDates, Base64, Nettle

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

abstract type CryptocomData <: AbstractAPIsData end
abstract type CryptocomCommonQuery <: AbstractAPIsQuery end
abstract type CryptocomPublicQuery <: CryptocomCommonQuery end
abstract type CryptocomAccessQuery <: CryptocomCommonQuery end
abstract type CryptocomPrivateQuery <: CryptocomCommonQuery end

"""
Data{D} <: AbstractAPIsData
## Required fields
- `id::Int64`: Return id.
- `method::String`: Return method endpoint.
- `code::String`: Return code.
- `result::D`: Request result data.
"""
struct Data{D<:AbstractAPIsData} <: AbstractAPIsData
id::Int64
method::String
code::String
result::D
end

"""
CryptocomClient <: 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 CryptocomClient <: AbstractAPIsClient
base_url::String
public_key::Maybe{String} = nothing
secret_key::Maybe{String} = nothing
interface::Maybe{String} = nothing
proxy::Maybe{String} = nothing
account_name::Maybe{String} = nothing
description::Maybe{String} = nothing
end

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

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

CryptoAPIs.error_type(::CryptocomClient) = CryptocomAPIError

function Base.show(io::IO, e::CryptocomAPIError)
return print(io, "code = ", "\"", e.code, "\"", ", ", "msg = ", "\"", e.message, "\"", ", ", "desc = ", "\"", e.description, "\"")
end

struct CryptocomUndefError <: AbstractAPIsError
e::Exception
msg::String
end

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

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

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

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

include("Utils.jl")

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

end
44 changes: 44 additions & 0 deletions src/Cryptocom/Errors.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Cryptocom/Errors
# https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#response-and-reason-codes

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

# UNDEF
isretriable(::APIsResult{CryptocomAPIError}) = true

# Success
isretriable(e::APIsResult{CryptocomAPIError{0}}) = false

# Server Timeout
isretriable(e::APIsResult{CryptocomAPIError{40801}}) = true
retry_timeout(e::APIsResult{CryptocomAPIError{40801}}) = 10
retry_maxcount(e::APIsResult{CryptocomAPIError{40801}}) = 50

# Requests have exceeded rate limits
isretriable(e::APIsResult{CryptocomAPIError{42901}}) = true
retry_timeout(e::APIsResult{CryptocomAPIError{42901}}) = 10
retry_maxcount(e::APIsResult{CryptocomAPIError{42901}}) = 50

#Instrument has expired
isretriable(e::APIsResult{CryptocomAPIError{206}}) = false

#Instrument is not tradable
isretriable(e::APIsResult{CryptocomAPIError{208}}) = false

#Market is not open
isretriable(e::APIsResult{CryptocomAPIError{309}}) = false

#Bad request
isretriable(e::APIsResult{CryptocomAPIError{40001}}) = false

#Required argument is blank or missing
isretriable(e::APIsResult{CryptocomAPIError{40004}}) = false

#Invalid date
isretriable(e::APIsResult{CryptocomAPIError{40005}}) = false

#IP address not whitelisted
isretriable(e::APIsResult{CryptocomAPIError{40103}}) = false

#Not found
isretriable(e::APIsResult{CryptocomAPIError{40401}}) = false
135 changes: 135 additions & 0 deletions src/Cryptocom/Spot/API/Candle.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
module Candle

export CandleQuery,
CandleData,
candle

using Serde
using Dates, NanoDates, TimeZones

using CryptoAPIs.Cryptocom
using CryptoAPIs.Cryptocom: Data
using CryptoAPIs: Maybe, APIsRequest

@enum TimeInterval m1 m5 m15 m30 h1 h2 h4 h12 d1 d7 d14 M1

Base.@kwdef struct CandleQuery <: CryptocomPublicQuery
instrument_name::String
timeframe::Maybe{TimeInterval} = nothing
count::Maybe{Int64} = nothing
start_ts::Maybe{DateTime} = nothing
end_ts::Maybe{DateTime} = nothing
end

function Serde.ser_type(::Type{<:CandleQuery}, x::TimeInterval)::String
x == m1 && return "1m"
x == m5 && return "5m"
x == m15 && return "15m"
x == m30 && return "30m"
x == h1 && return "1h"
x == h2 && return "2h"
x == h4 && return "4h"
x == h12 && return "12h"
x == d1 && return "1D"
x == d7 && return "7D"
x == d14 && return "14D"
x == M1 && return "1M"
end

struct CandleInfo <: CryptocomData
o::Maybe{Float64}
h::Maybe{Float64}
l::Maybe{Float64}
c::Maybe{Float64}
v::Maybe{Float64}
t::NanoDate
end

function Serde.isempty(::Type{CandleInfo}, x)::Bool
return x === ""
end

struct CandleData <: CryptocomData
interval::String
data::Vector{CandleInfo}
instrument_name::String
end

function Serde.isempty(::Type{CandleData}, x)::Bool
return x == []
end

"""
candle(client::CryptocomClient, query::CandleQuery)
candle(client::CryptocomClient = Cryptocom.Spot.public_client; kw...)
Retrieves candlesticks (k-line data history) over a given period for an instrument.
[`GET public/get-candlestick`](https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-candlestick)
## Parameters:
| Parameter | Type | Required | Description |
|:----------------|:-------------|:---------|:----------------------------------------|
| instrument_name | String | true | |
| timeframe | TimeInterval | false | m1 m5 m15 m30 h1 h2 h4 h12 d1 d7 d14 M1 |
| count | Int64 | false | |
| start_ts | Int64 | false | |
| end_ts | Int64 | false | |
## Code samples:
```julia
using Serde
using CryptoAPIs.Cryptocom
result = Cryptocom.Spot.candle(;
instrument_name = "BTC_USDT",
timeframe = Cryptocom.Spot.Candle.M1,
)
to_pretty_json(result.result)
```
## Result:
```json
{
"id":-1,
"method":"public/get-candlestick",
"code":"0",
"result":{
"interval":"1M",
"data":[
{
"o":19000.0,
"h":22982.05,
"l":15492.33,
"c":17170.28,
"v":150666.07457,
"t":"2022-11-01T00:00:00"
},
{
"o":17166.5,
"h":18372.46,
"l":16263.22,
"c":16539.69,
"v":51215.88412,
"t":"2022-12-01T00:00:00"
...
],
"instrument_name":"BTC_USDT"
}
}
```
"""
function candle(client::CryptocomClient, query::CandleQuery)
return APIsRequest{Data{CandleData}}("GET", "public/get-candlestick", query)(client)
end

function candle(client::CryptocomClient = Cryptocom.Spot.public_client; kw...)
return candle(client, CandleQuery(; kw...))
end

end

Loading

0 comments on commit 1905c14

Please sign in to comment.