kucoin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +21 -0
- data/README.md +62 -0
- data/Rakefile +6 -0
- data/bin/console +31 -0
- data/bin/setup +8 -0
- data/credentials.yml.example +7 -0
- data/kucoin.gemspec +34 -0
- data/lib/kucoin.rb +77 -0
- data/lib/kucoin/configuration.rb +24 -0
- data/lib/kucoin/constants.rb +34 -0
- data/lib/kucoin/errors.rb +11 -0
- data/lib/kucoin/extensions/hash.rb +27 -0
- data/lib/kucoin/extensions/string.rb +13 -0
- data/lib/kucoin/models/balance.rb +13 -0
- data/lib/kucoin/models/base.rb +26 -0
- data/lib/kucoin/models/coin.rb +20 -0
- data/lib/kucoin/models/coin_address.rb +17 -0
- data/lib/kucoin/models/deal.rb +20 -0
- data/lib/kucoin/models/ohlcv.rb +35 -0
- data/lib/kucoin/models/order_book.rb +53 -0
- data/lib/kucoin/models/ticker.rb +24 -0
- data/lib/kucoin/models/trade.rb +38 -0
- data/lib/kucoin/models/user.rb +31 -0
- data/lib/kucoin/rest/authentication.rb +38 -0
- data/lib/kucoin/rest/client.rb +99 -0
- data/lib/kucoin/rest/errors.rb +30 -0
- data/lib/kucoin/rest/private/balances.rb +34 -0
- data/lib/kucoin/rest/private/invitations.rb +24 -0
- data/lib/kucoin/rest/private/languages.rb +14 -0
- data/lib/kucoin/rest/private/trading.rb +113 -0
- data/lib/kucoin/rest/private/transfers.rb +25 -0
- data/lib/kucoin/rest/private/user.rb +15 -0
- data/lib/kucoin/rest/public/currencies.rb +13 -0
- data/lib/kucoin/rest/public/klines.rb +85 -0
- data/lib/kucoin/rest/public/languages.rb +13 -0
- data/lib/kucoin/rest/public/markets.rb +33 -0
- data/lib/kucoin/rest/public/orders.rb +39 -0
- data/lib/kucoin/rest/public/ticker.rb +14 -0
- data/lib/kucoin/rest/public/trades.rb +22 -0
- data/lib/kucoin/utilities/encoding.rb +15 -0
- data/lib/kucoin/utilities/parsing.rb +70 -0
- data/lib/kucoin/version.rb +3 -0
- metadata +218 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Constants
|
3
|
+
|
4
|
+
IN_SECONDS = {
|
5
|
+
minute: 60,
|
6
|
+
hour: 3600,
|
7
|
+
day: 86400,
|
8
|
+
week: 604800
|
9
|
+
}
|
10
|
+
|
11
|
+
STANDARD_KLINE_RESOLUTIONS = {
|
12
|
+
one_minute: "1min",
|
13
|
+
five_minutes: "5min",
|
14
|
+
fifteen_minutes: "15min",
|
15
|
+
thirty_minutes: "30min",
|
16
|
+
one_hour: "1hour",
|
17
|
+
eight_hours: "8hour",
|
18
|
+
one_day: "1day",
|
19
|
+
one_week: "1week"
|
20
|
+
}
|
21
|
+
|
22
|
+
TRADING_VIEW_KLINE_RESOLUTIONS = {
|
23
|
+
one_minute: "1",
|
24
|
+
five_minutes: "5",
|
25
|
+
fifteen_minutes: "15",
|
26
|
+
thirty_minutes: "30",
|
27
|
+
one_hour: "60",
|
28
|
+
eight_hours: "480",
|
29
|
+
one_day: "D",
|
30
|
+
one_week: "W"
|
31
|
+
}
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Errors
|
3
|
+
class Error < StandardError; end;
|
4
|
+
class ResponseError < Kucoin::Errors::Error; end;
|
5
|
+
|
6
|
+
MAPPING = {
|
7
|
+
404 => -> { raise ::Kucoin::Errors::ResponseError.new("Endpoint couldn't be found. Make sure the endpoint you're calling is a supported KuCoin API Endpoint.") },
|
8
|
+
}
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def symbolize_keys
|
4
|
+
transform_keys{ |key| key.to_sym rescue key }
|
5
|
+
end
|
6
|
+
|
7
|
+
def transform_keys
|
8
|
+
result = {}
|
9
|
+
each_key do |key|
|
10
|
+
result[yield(key)] = self[key]
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
|
15
|
+
def deep_symbolize_keys
|
16
|
+
deep_transform_keys{ |key| key.to_sym rescue key }
|
17
|
+
end
|
18
|
+
|
19
|
+
def deep_transform_keys(&block)
|
20
|
+
result = {}
|
21
|
+
each do |key, value|
|
22
|
+
result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
|
23
|
+
end
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def initialize(hash)
|
6
|
+
self.class::MAPPING.keys.each { |key| self.class.send(:attr_accessor, key) }
|
7
|
+
|
8
|
+
hash.each do |key, value|
|
9
|
+
key = key.to_s.underscore.to_sym
|
10
|
+
type = self.class::MAPPING.fetch(key, nil)
|
11
|
+
value = value && type ? ::Kucoin::Utilities::Parsing.convert_value(value, type, use_ms_for_time: true) : value
|
12
|
+
self.send("#{key}=", value) if self.respond_to?(key)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def attributes
|
17
|
+
Hash[instance_variables.map { |name| [name.to_s.gsub(/^@/, "").to_sym, instance_variable_get(name)] }]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse(data)
|
21
|
+
data&.collect { |item| self.new(item) }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class Coin < Base
|
4
|
+
MAPPING = {
|
5
|
+
name: :string,
|
6
|
+
coin: :string,
|
7
|
+
info_url: :string,
|
8
|
+
trade_precision: :integer,
|
9
|
+
deposit_remart: :string,
|
10
|
+
confirmation_count: :integer,
|
11
|
+
withdraw_min_fee: :float,
|
12
|
+
withdraw_min_amount: :float,
|
13
|
+
withdraw_fee_rate: :float,
|
14
|
+
withdraw_remart: :string,
|
15
|
+
enable_withdraw: :boolean,
|
16
|
+
enable_deposit: :boolean
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class CoinAddress < Base
|
4
|
+
MAPPING = {
|
5
|
+
oid: :string,
|
6
|
+
address: :string,
|
7
|
+
context: :string,
|
8
|
+
user_oid: :string,
|
9
|
+
coin_type: :string,
|
10
|
+
created_at: :time,
|
11
|
+
deleted_at: :time,
|
12
|
+
updated_at: :time,
|
13
|
+
last_received_at: :time
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class Deal < Base
|
4
|
+
MAPPING = {
|
5
|
+
oid: :string,
|
6
|
+
order_oid: :string,
|
7
|
+
coin_type: :string,
|
8
|
+
coin_type_pair: :string,
|
9
|
+
deal_direction: :string,
|
10
|
+
direction: :string,
|
11
|
+
deal_price: :float,
|
12
|
+
amount: :float,
|
13
|
+
deal_value: :float,
|
14
|
+
fee: :float,
|
15
|
+
fee_rate: :float,
|
16
|
+
created_at: :time
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class OHLCV < Base
|
4
|
+
MAPPING = {
|
5
|
+
timestamp: :time,
|
6
|
+
open: :float,
|
7
|
+
high: :float,
|
8
|
+
low: :float,
|
9
|
+
close: :float,
|
10
|
+
volume: :float
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.parse(data)
|
14
|
+
ohlcvs = []
|
15
|
+
|
16
|
+
timestamps = data.fetch("t", [])
|
17
|
+
opens = data.fetch("o", [])
|
18
|
+
highs = data.fetch("h", [])
|
19
|
+
lows = data.fetch("l", [])
|
20
|
+
closes = data.fetch("c", [])
|
21
|
+
volumes = data.fetch("v", [])
|
22
|
+
|
23
|
+
if timestamps&.any? && opens&.any? && highs&.any? && lows&.any? && closes&.any? && volumes&.any?
|
24
|
+
timestamps.each_with_index do |timestamp, index|
|
25
|
+
item = {timestamp: timestamp, open: opens[index], high: highs[index], low: lows[index], close: closes[index], volume: volumes[index]}
|
26
|
+
ohlcvs << ::Kucoin::Models::OHLCV.new(item)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
return ohlcvs
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class OrderBook
|
4
|
+
attr_accessor :symbol, :timestamp, :asks, :bids
|
5
|
+
|
6
|
+
ARRAY_MAPPING = {
|
7
|
+
0 => :price,
|
8
|
+
1 => :amount,
|
9
|
+
2 => :volume
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(hash, type: :nil)
|
13
|
+
self.symbol = hash.fetch("symbol", nil)
|
14
|
+
self.timestamp = ::Kucoin::Utilities::Parsing.epoch_to_time(hash.fetch("timestamp", nil), ms: true) if hash.has_key?("timestamp") && !hash.fetch("timestamp", nil).to_s.empty?
|
15
|
+
|
16
|
+
self.bids = []
|
17
|
+
self.asks = []
|
18
|
+
|
19
|
+
process(hash.fetch("data", []), type: type)
|
20
|
+
end
|
21
|
+
|
22
|
+
def process(data, type: nil)
|
23
|
+
pp data
|
24
|
+
|
25
|
+
if data.is_a?(Hash)
|
26
|
+
["BUY", "SELL"].each do |type|
|
27
|
+
process_orders(data.fetch(type.to_s, []), type: type)
|
28
|
+
end
|
29
|
+
elsif data.is_a?(Array)
|
30
|
+
process_orders(data, type: type)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_orders(orders, type: "BUY")
|
35
|
+
orders.each do |item|
|
36
|
+
data = {}
|
37
|
+
|
38
|
+
item.each_with_index do |value, index|
|
39
|
+
data[ARRAY_MAPPING[index]] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
case type
|
43
|
+
when "BUY"
|
44
|
+
self.bids << data
|
45
|
+
when "SELL"
|
46
|
+
self.asks << data
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class Ticker < Base
|
4
|
+
MAPPING = {
|
5
|
+
coin_type: :string,
|
6
|
+
trading: :boolean,
|
7
|
+
symbol: :string,
|
8
|
+
last_deal_price: :float,
|
9
|
+
buy: :float,
|
10
|
+
sell: :float,
|
11
|
+
change: :float,
|
12
|
+
coin_type_pair: :string,
|
13
|
+
sort: :integer,
|
14
|
+
fee_rate: :float,
|
15
|
+
vol_value: :float,
|
16
|
+
high: :float,
|
17
|
+
datetime: :time,
|
18
|
+
vol: :float,
|
19
|
+
low: :float,
|
20
|
+
change_rate: :float
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class Trade < Base
|
4
|
+
ARRAY_MAPPING = {
|
5
|
+
0 => "timestamp",
|
6
|
+
1 => "order_type",
|
7
|
+
2 => "price",
|
8
|
+
3 => "amount",
|
9
|
+
4 => "volume"
|
10
|
+
}
|
11
|
+
|
12
|
+
MAPPING = {
|
13
|
+
timestamp: :time,
|
14
|
+
order_type: :string,
|
15
|
+
price: :float,
|
16
|
+
amount: :float,
|
17
|
+
volume: :float,
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.parse(response)
|
21
|
+
trades = []
|
22
|
+
|
23
|
+
response&.each do |item|
|
24
|
+
data = {}
|
25
|
+
|
26
|
+
item.each_with_index do |value, index|
|
27
|
+
data[ARRAY_MAPPING[index]] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
trades << ::Kucoin::Models::Trade.new(data)
|
31
|
+
end
|
32
|
+
|
33
|
+
return trades
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Models
|
3
|
+
class User < Base
|
4
|
+
MAPPING = {
|
5
|
+
oid: :string,
|
6
|
+
name: :string,
|
7
|
+
nickname: :string,
|
8
|
+
email: :string,
|
9
|
+
phone: :string,
|
10
|
+
referrer_code: :string,
|
11
|
+
language: :string,
|
12
|
+
csrf: :string,
|
13
|
+
login_record: :hash,
|
14
|
+
base_bonus_rate: :float,
|
15
|
+
base_fee_rate: :float,
|
16
|
+
credential_status: :string,
|
17
|
+
currency: :string,
|
18
|
+
photo_credential_validated: :boolean,
|
19
|
+
video_validated: :boolean,
|
20
|
+
has_credential: :boolean,
|
21
|
+
phone_validated: :boolean,
|
22
|
+
credential_validated: :boolean,
|
23
|
+
google_two_fa_binding: :boolean,
|
24
|
+
has_trade_password: :boolean,
|
25
|
+
email_validated: :boolean,
|
26
|
+
is_china_visitor: :boolean,
|
27
|
+
is_suspend: :boolean
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Rest
|
3
|
+
module Authentication
|
4
|
+
|
5
|
+
def authenticate!(path, params)
|
6
|
+
data = sign_message(path, params)
|
7
|
+
|
8
|
+
{
|
9
|
+
'KC-API-KEY' => self.configuration.key,
|
10
|
+
'KC-API-NONCE' => data[:nonce],
|
11
|
+
'KC-API-SIGNATURE' => data[:signature],
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def nonce
|
16
|
+
(Time.now.to_f * 1000).to_i.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def sign_message(path, params = nil)
|
20
|
+
path = signature_path(path)
|
21
|
+
nonced = nonce
|
22
|
+
params = compose_params(params)
|
23
|
+
message = "#{path}/#{nonced}/#{params}"
|
24
|
+
signature = ::Kucoin::Utilities::Encoding.sign(message, secret: self.configuration.secret)
|
25
|
+
|
26
|
+
return {nonce: nonced, signature: signature}
|
27
|
+
end
|
28
|
+
|
29
|
+
def compose_params(params)
|
30
|
+
return params unless params.is_a? Hash
|
31
|
+
uri = Addressable::URI.new
|
32
|
+
uri.query_values = params
|
33
|
+
uri.query
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Kucoin
|
2
|
+
module Rest
|
3
|
+
class Client
|
4
|
+
attr_accessor :url, :configuration
|
5
|
+
|
6
|
+
def initialize(configuration: ::Kucoin.configuration)
|
7
|
+
self.configuration = configuration
|
8
|
+
self.url = "#{self.configuration.api_url}/v#{self.configuration.api_version}"
|
9
|
+
end
|
10
|
+
|
11
|
+
include ::Kucoin::Rest::Errors
|
12
|
+
include ::Kucoin::Rest::Authentication
|
13
|
+
|
14
|
+
include ::Kucoin::Rest::Public::Currencies
|
15
|
+
include ::Kucoin::Rest::Public::Languages
|
16
|
+
include ::Kucoin::Rest::Public::Ticker
|
17
|
+
include ::Kucoin::Rest::Public::Orders
|
18
|
+
include ::Kucoin::Rest::Public::Trades
|
19
|
+
include ::Kucoin::Rest::Public::Markets
|
20
|
+
include ::Kucoin::Rest::Public::Klines
|
21
|
+
|
22
|
+
include ::Kucoin::Rest::Private::User
|
23
|
+
include ::Kucoin::Rest::Private::Languages
|
24
|
+
include ::Kucoin::Rest::Private::Invitations
|
25
|
+
include ::Kucoin::Rest::Private::Transfers
|
26
|
+
include ::Kucoin::Rest::Private::Balances
|
27
|
+
include ::Kucoin::Rest::Private::Trading
|
28
|
+
|
29
|
+
def configured?
|
30
|
+
!self.configuration.key.to_s.empty? && !self.configuration.secret.to_s.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_credentials!
|
34
|
+
unless configured?
|
35
|
+
raise ::Kucoin::Errors::MissingConfigError.new("Kucoin gem hasn't been properly configured.")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_uri(path)
|
40
|
+
"#{self.url}#{path}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def signature_path(path)
|
44
|
+
"/v#{self.configuration.api_version}#{path}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse(response)
|
48
|
+
error?(response)
|
49
|
+
response
|
50
|
+
end
|
51
|
+
|
52
|
+
def get(path, params: {}, options: {})
|
53
|
+
request path, method: :get, params: params, options: options
|
54
|
+
end
|
55
|
+
|
56
|
+
def post(path, params: {}, data: {}, options: {})
|
57
|
+
request path, method: :post, params: params, data: data, options: options
|
58
|
+
end
|
59
|
+
|
60
|
+
def request(path, method: :get, params: {}, data: {}, options: {})
|
61
|
+
should_auth = options.fetch(:authenticate, false)
|
62
|
+
user_agent = options.fetch(:user_agent, self.configuration.faraday.fetch(:user_agent, nil))
|
63
|
+
proxy = options.fetch(:proxy, nil)
|
64
|
+
|
65
|
+
connection = Faraday.new(url: to_uri(path)) do |builder|
|
66
|
+
builder.headers["User-Agent"] = user_agent if !user_agent.to_s.empty?
|
67
|
+
builder.headers["Accept-Language"] = "en_EN"
|
68
|
+
|
69
|
+
builder.headers.merge!(authenticate!(path, params)) if should_auth && method.eql?(:get)
|
70
|
+
builder.headers.merge!(authenticate!(path, data)) if should_auth && method.eql?(:post)
|
71
|
+
|
72
|
+
builder.request :url_encoded if method.eql?(:post)
|
73
|
+
builder.response :logger if self.configuration.verbose_faraday?
|
74
|
+
builder.response :json
|
75
|
+
|
76
|
+
if proxy
|
77
|
+
puts "[Kucoin::Rest::Client] - Will connect to Kucoin using proxy: #{proxy.inspect}" if self.configuration.verbose_faraday?
|
78
|
+
builder.proxy = proxy
|
79
|
+
end
|
80
|
+
|
81
|
+
builder.adapter self.configuration.faraday.fetch(:adapter, :net_http)
|
82
|
+
end
|
83
|
+
|
84
|
+
case method
|
85
|
+
when :get
|
86
|
+
connection.get do |request|
|
87
|
+
request.params = params if params && !params.empty?
|
88
|
+
end&.body
|
89
|
+
when :post
|
90
|
+
connection.post do |request|
|
91
|
+
request.body = data
|
92
|
+
request.params = params if params && !params.empty?
|
93
|
+
end&.body
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|