Skip to content

Commit 2178e8c

Browse files
authored
Add Huobi Spot exchange (#38)
1 parent 1fe1088 commit 2178e8c

File tree

17 files changed

+1051
-1
lines changed

17 files changed

+1051
-1
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.20.1"
3+
version = "0.21.0"
44

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

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ Then, to install CryptoAPIs, simply use the Julia package manager:
9696
<td><a href="src/Gateio/Futures">CryptoAPIs.Gateio.Futures</a></td>
9797
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Gateio/#Futures">Futures</a></td>
9898
</tr>
99+
<tr>
100+
<td><img src="docs/src/assets/huobi.png" alt="Huobi Logo" width="20" height="20"></td>
101+
<td><a href="https://www.htx.com/">Huobi</a></td>
102+
<td><a href="https://www.htx.com/en-us/opend/newApiPages">Spot</a></td>
103+
<td><a href="src/Huobi/Futures">CryptoAPIs.Huobi.Spot</a></td>
104+
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Huobi/#Spot">Spot</a></td>
105+
</tr>
99106
<tr>
100107
<td><img src="docs/src/assets/kraken.png" alt="Kraken Logo" width="20" height="20"></td>
101108
<td><a href="https://www.kraken.com/">Kraken</a></td>

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ makedocs(;
2222
"pages/Coinbase.md",
2323
"pages/Cryptocom.md",
2424
"pages/Gateio.md",
25+
"pages/Huobi.md",
2526
"pages/Kraken.md",
2627
"pages/Kucoin.md",
2728
"pages/Okex.md",

docs/src/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ Then, to install CryptoAPIs, simply use the Julia package manager:
9090
<td><a href="src/Gateio/Futures">CryptoAPIs.Gateio.Futures</a></td>
9191
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Gateio/#Futures">Futures</a></td>
9292
</tr>
93+
<tr>
94+
<td><img src="assets/huobi.png" alt="Huobi Logo" width="20" height="20"></td>
95+
<td><a href="https://www.htx.com/">Huobi</a></td>
96+
<td><a href="https://www.htx.com/en-us/opend/newApiPages">Spot</a></td>
97+
<td><a href="src/Huobi/Futures">CryptoAPIs.Huobi.Spot</a></td>
98+
<td><a href="https://bhftbootcamp.github.io/CryptoAPIs.jl/stable/pages/Huobi/#Spot">Spot</a></td>
99+
</tr>
93100
<tr>
94101
<td><img src="assets/kraken.png" alt="Kraken Logo" width="20" height="20"></td>
95102
<td><a href="https://www.kraken.com/">Kraken</a></td>

docs/src/pages/Huobi.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Huobi
2+
3+
```@docs
4+
CryptoAPIs.Huobi.HuobiClient
5+
CryptoAPIs.Huobi.HuobiAPIError
6+
CryptoAPIs.Huobi.Data
7+
CryptoAPIs.Huobi.DataTick
8+
```
9+
10+
## Spot
11+
12+
```@docs
13+
CryptoAPIs.Huobi.Spot.public_client
14+
```
15+
16+
```@docs
17+
CryptoAPIs.Huobi.Spot.candle
18+
CryptoAPIs.Huobi.Spot.currency
19+
CryptoAPIs.Huobi.Spot.deposit_withdrawal
20+
CryptoAPIs.Huobi.Spot.order_book
21+
CryptoAPIs.Huobi.Spot.order_log
22+
CryptoAPIs.Huobi.Spot.ticker
23+
```

examples/Huobi/Spot.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Huobi/Spot
2+
# https://huobiapi.github.io/docs/spot/v1/en
3+
4+
using Dates
5+
using CryptoAPIs
6+
using CryptoAPIs.Huobi
7+
8+
CryptoAPIs.Huobi.Spot.candle(; symbol = "btcusdt", period = CryptoAPIs.Huobi.Spot.Candle.m1)
9+
10+
CryptoAPIs.Huobi.Spot.common_symbol()
11+
12+
CryptoAPIs.Huobi.Spot.order_book(; symbol = "btcusdt")
13+
14+
CryptoAPIs.Huobi.Spot.ticker()
15+
16+
huobi_client = CryptoAPIs.Huobi.HuobiClient(;
17+
base_url = "https://api.huobi.pro",
18+
public_key = ENV["HUOBI_PUBLIC_KEY"],
19+
secret_key = ENV["HUOBI_SECRET_KEY"],
20+
)
21+
22+
CryptoAPIs.Huobi.Spot.currency(huobi_client; currency = "btc", authorizedUser = true)
23+
24+
CryptoAPIs.Huobi.Spot.deposit_withdrawal(
25+
huobi_client;
26+
currency = "btc",
27+
direct = CryptoAPIs.Huobi.Spot.DepositWithdrawal.prev,
28+
from = "1",
29+
size = 500,
30+
type = "deposit",
31+
)
32+
33+
CryptoAPIs.Huobi.Spot.order_log(
34+
huobi_client;
35+
start_time = now(UTC) - Day(1),
36+
end_time = now(UTC) - Hour(1),
37+
size = 1000,
38+
)

src/CryptoAPIs.jl

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

src/Huobi/Errors.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Huobi/Errors
2+
# https://huobiapi.github.io/docs/spot/v1/en/
3+
4+
import ..CryptoAPIs: APIsResult, APIsUndefError
5+
import ..CryptoAPIs: isretriable, retry_maxcount, retry_timeout
6+
7+
# UNDEF
8+
isretriable(e::APIsResult{HuobiAPIError}) = true
9+
10+
# GATEWAY_INTERNAL_ERROR
11+
isretriable(e::APIsResult{HuobiAPIError{Symbol("gateway-internal-error")}}) = true
12+
retry_maxcount(e::APIsResult{HuobiAPIError{Symbol("gateway-internal-error")}}) = 20
13+
14+
# BAD_REQUEST
15+
isretriable(e::APIsResult{HuobiAPIError{Symbol("bad-request")}}) = false

src/Huobi/Huobi.jl

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
module Huobi
2+
3+
export HuobiCommonQuery,
4+
HuobiPublicQuery,
5+
HuobiAccessQuery,
6+
HuobiPrivateQuery,
7+
HuobiAPIError,
8+
HuobiClient,
9+
HuobiData
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 HuobiData <: AbstractAPIsData end
18+
abstract type HuobiCommonQuery <: AbstractAPIsQuery end
19+
abstract type HuobiPublicQuery <: HuobiCommonQuery end
20+
abstract type HuobiAccessQuery <: HuobiCommonQuery end
21+
abstract type HuobiPrivateQuery <: HuobiCommonQuery end
22+
23+
@enum Status begin
24+
ok
25+
error
26+
end
27+
28+
"""
29+
Data{D} <: AbstractAPIsData
30+
31+
## Required fields
32+
- `data::D`: The body data in response.
33+
34+
## Optional fields
35+
- `status::Status`: Status of API response.
36+
- `ch::String`: The data stream. It may be empty as some API doesn't have data stream.
37+
- `ts::NanoDate`: The UTC timestamp when API respond, the unit is millisecond.
38+
- `code::Int64`: Response code.
39+
"""
40+
struct Data{D} <: AbstractAPIsData
41+
status::Maybe{Status}
42+
ch::Maybe{String}
43+
ts::Maybe{NanoDate}
44+
code::Maybe{Int64}
45+
data::D
46+
end
47+
48+
"""
49+
DataTick{D} <: AbstractAPIsData
50+
51+
## Required fields
52+
- `tick::D`: The body tick in response.
53+
- `status::Status`: Status of API response.
54+
- `ch::String`: The data stream. It may be empty as some API doesn't have data stream.
55+
- `ts::NanoDate`: The UTC timestamp when API respond.
56+
"""
57+
struct DataTick{D} <: AbstractAPIsData
58+
status::Status
59+
ch::String
60+
ts::NanoDate
61+
tick::D
62+
end
63+
64+
"""
65+
HuobiClient <: AbstractAPIsClient
66+
67+
Client info.
68+
69+
## Required fields
70+
- `base_url::String`: Base URL for the client.
71+
72+
## Optional fields
73+
- `public_key::String`: Public key for authentication.
74+
- `secret_key::String`: Secret key for authentication.
75+
- `interface::String`: Interface for the client.
76+
- `proxy::String`: Proxy information for the client.
77+
- `account_name::String`: Account name associated with the client.
78+
- `description::String`: Description of the client.
79+
"""
80+
Base.@kwdef struct HuobiClient <: AbstractAPIsClient
81+
base_url::String
82+
public_key::Maybe{String} = nothing
83+
secret_key::Maybe{String} = nothing
84+
interface::Maybe{String} = nothing
85+
proxy::Maybe{String} = nothing
86+
account_name::Maybe{String} = nothing
87+
description::Maybe{String} = nothing
88+
end
89+
90+
"""
91+
HuobiAPIError{T} <: AbstractAPIsError
92+
93+
Exception thrown when an API method fails with code `T`.
94+
95+
## Required fields
96+
- `err_code::String`: Error code.
97+
- `err_msg::String`: Error message.
98+
99+
## Optional fields
100+
- `ts::NanoDate`: he UTC timestamp when API respond.
101+
- `status::String`: Status of API response.
102+
- `code::Int64`: Response code.
103+
"""
104+
struct HuobiAPIError{T} <: AbstractAPIsError
105+
err_code::String
106+
err_msg::String
107+
ts::Maybe{NanoDate}
108+
status::Maybe{String}
109+
data::Nothing
110+
code::Maybe{Int64}
111+
112+
function HuobiAPIError(err_code::String, err_msg::String, x...)
113+
return new{Symbol(err_code)}(err_code, err_msg, x...)
114+
end
115+
end
116+
117+
function Base.show(io::IO, e::HuobiAPIError)
118+
return print(io, "code = ", "\"", e.err_code, "\"", ", ", "msg = ", "\"", e.err_msg, "\"")
119+
end
120+
121+
CryptoAPIs.error_type(::HuobiClient) = HuobiAPIError
122+
123+
function CryptoAPIs.request_sign!(::HuobiClient, query::Q, ::String)::Q where {Q<:HuobiPublicQuery}
124+
return query
125+
end
126+
127+
function CryptoAPIs.request_sign!(client::HuobiClient, query::Q, endpoint::String)::Nothing where {Q<:HuobiPrivateQuery}
128+
query.AccessKeyId = client.public_key
129+
query.Timestamp = now(UTC)
130+
query.SignatureMethod = "HmacSHA256"
131+
query.SignatureVersion = "2"
132+
query.Signature = nothing
133+
body::String = Serde.to_query(query)
134+
endpoint = string("/", endpoint)
135+
host = last(split(client.base_url, "//"))
136+
salt = join(["GET", host, endpoint, body], "\n")
137+
query.Signature = Base64.base64encode(digest("sha256", client.secret_key, salt))
138+
return nothing
139+
end
140+
141+
function CryptoAPIs.request_body(::Q)::String where {Q<:HuobiPublicQuery}
142+
return ""
143+
end
144+
145+
function CryptoAPIs.request_body(query::Q)::String where {Q<:HuobiPrivateQuery}
146+
return Serde.to_query(query)
147+
end
148+
149+
function CryptoAPIs.request_query(query::Q)::String where {Q<:HuobiPublicQuery}
150+
return Serde.to_query(query)
151+
end
152+
153+
function CryptoAPIs.request_query(::Q)::String where {Q<:HuobiPrivateQuery}
154+
return ""
155+
end
156+
157+
function CryptoAPIs.request_headers(client::HuobiClient, ::HuobiCommonQuery)::Vector{Pair{String,String}}
158+
return Pair{String,String}[
159+
"Content-Type" => "application/json",
160+
]
161+
end
162+
163+
include("Utils.jl")
164+
include("Errors.jl")
165+
166+
include("Spot/Spot.jl")
167+
using .Spot
168+
169+
end

0 commit comments

Comments
 (0)