kucoin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +59 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +62 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +31 -0
  12. data/bin/setup +8 -0
  13. data/credentials.yml.example +7 -0
  14. data/kucoin.gemspec +34 -0
  15. data/lib/kucoin.rb +77 -0
  16. data/lib/kucoin/configuration.rb +24 -0
  17. data/lib/kucoin/constants.rb +34 -0
  18. data/lib/kucoin/errors.rb +11 -0
  19. data/lib/kucoin/extensions/hash.rb +27 -0
  20. data/lib/kucoin/extensions/string.rb +13 -0
  21. data/lib/kucoin/models/balance.rb +13 -0
  22. data/lib/kucoin/models/base.rb +26 -0
  23. data/lib/kucoin/models/coin.rb +20 -0
  24. data/lib/kucoin/models/coin_address.rb +17 -0
  25. data/lib/kucoin/models/deal.rb +20 -0
  26. data/lib/kucoin/models/ohlcv.rb +35 -0
  27. data/lib/kucoin/models/order_book.rb +53 -0
  28. data/lib/kucoin/models/ticker.rb +24 -0
  29. data/lib/kucoin/models/trade.rb +38 -0
  30. data/lib/kucoin/models/user.rb +31 -0
  31. data/lib/kucoin/rest/authentication.rb +38 -0
  32. data/lib/kucoin/rest/client.rb +99 -0
  33. data/lib/kucoin/rest/errors.rb +30 -0
  34. data/lib/kucoin/rest/private/balances.rb +34 -0
  35. data/lib/kucoin/rest/private/invitations.rb +24 -0
  36. data/lib/kucoin/rest/private/languages.rb +14 -0
  37. data/lib/kucoin/rest/private/trading.rb +113 -0
  38. data/lib/kucoin/rest/private/transfers.rb +25 -0
  39. data/lib/kucoin/rest/private/user.rb +15 -0
  40. data/lib/kucoin/rest/public/currencies.rb +13 -0
  41. data/lib/kucoin/rest/public/klines.rb +85 -0
  42. data/lib/kucoin/rest/public/languages.rb +13 -0
  43. data/lib/kucoin/rest/public/markets.rb +33 -0
  44. data/lib/kucoin/rest/public/orders.rb +39 -0
  45. data/lib/kucoin/rest/public/ticker.rb +14 -0
  46. data/lib/kucoin/rest/public/trades.rb +22 -0
  47. data/lib/kucoin/utilities/encoding.rb +15 -0
  48. data/lib/kucoin/utilities/parsing.rb +70 -0
  49. data/lib/kucoin/version.rb +3 -0
  50. 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,13 @@
1
+ class String
2
+
3
+ def underscore
4
+ word = self.dup
5
+ word.gsub!(/::/, '/')
6
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
7
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
8
+ word.tr!("-", "_")
9
+ word.downcase!
10
+ word
11
+ end
12
+
13
+ end
@@ -0,0 +1,13 @@
1
+ module Kucoin
2
+ module Models
3
+ class Balance < Base
4
+ MAPPING = {
5
+ coin_type: :string,
6
+ balance: :float,
7
+ balance_str: :string,
8
+ freeze_balance: :float,
9
+ freeze_balance_str: :string
10
+ }
11
+ end
12
+ end
13
+ 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