kucoin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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