kucoin-api 0.2.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +7 -0
  5. data/.travis.yml +7 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +551 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/kucoin-api.gemspec +42 -0
  14. data/lib/kucoin/api.rb +34 -0
  15. data/lib/kucoin/api/endpoints.rb +117 -0
  16. data/lib/kucoin/api/endpoints/base.rb +58 -0
  17. data/lib/kucoin/api/endpoints/markets.rb +17 -0
  18. data/lib/kucoin/api/endpoints/markets/currencies.rb +24 -0
  19. data/lib/kucoin/api/endpoints/markets/histories.rb +18 -0
  20. data/lib/kucoin/api/endpoints/markets/order_book.rb +30 -0
  21. data/lib/kucoin/api/endpoints/markets/symbols.rb +15 -0
  22. data/lib/kucoin/api/endpoints/markets/tickers.rb +19 -0
  23. data/lib/kucoin/api/endpoints/other.rb +12 -0
  24. data/lib/kucoin/api/endpoints/trade.rb +8 -0
  25. data/lib/kucoin/api/endpoints/trade/fills.rb +20 -0
  26. data/lib/kucoin/api/endpoints/trade/orders.rb +44 -0
  27. data/lib/kucoin/api/endpoints/user.rb +8 -0
  28. data/lib/kucoin/api/endpoints/user/accounts.rb +40 -0
  29. data/lib/kucoin/api/endpoints/user/deposits.rb +26 -0
  30. data/lib/kucoin/api/endpoints/user/withdrawals.rb +30 -0
  31. data/lib/kucoin/api/endpoints/websocket.rb +27 -0
  32. data/lib/kucoin/api/error.rb +11 -0
  33. data/lib/kucoin/api/middleware/auth_request.rb +45 -0
  34. data/lib/kucoin/api/middleware/nonce_request.rb +13 -0
  35. data/lib/kucoin/api/rest.rb +46 -0
  36. data/lib/kucoin/api/rest/connection.rb +50 -0
  37. data/lib/kucoin/api/version.rb +5 -0
  38. data/lib/kucoin/api/websocket.rb +150 -0
  39. data/log/.keep +0 -0
  40. metadata +193 -0
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Endpoints
5
+ class Trade
6
+ class Orders < Trade
7
+ def create client_oid, side, symbol, options={}
8
+ options = { clientOid: client_oid, side: side, symbol: symbol }.merge(options)
9
+ assert_required_param options, :side, side_types
10
+ assert_param_is_one_of options, :type, order_types if options.has_key?(:type)
11
+ auth.ku_request :post, :index, **options
12
+ end
13
+ alias place create
14
+
15
+ def index options={}
16
+ auth.ku_request :get, :index, **options
17
+ end
18
+ alias all index
19
+ alias list index
20
+
21
+ def delete_all options={}
22
+ auth.ku_request :delete, :index, **options
23
+ end
24
+ alias cancel_all delete_all
25
+
26
+ def recent
27
+ auth.ku_request :get, :recent
28
+ end
29
+
30
+ def show order_id
31
+ auth.ku_request :get, :show, order_id: order_id
32
+ end
33
+ alias get show
34
+ alias detail show
35
+
36
+ def delete order_id
37
+ auth.ku_request :delete, :show, order_id: order_id
38
+ end
39
+ alias cancel delete
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Endpoints
5
+ class User < Base; end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Endpoints
5
+ class User
6
+ class Accounts < User
7
+ def create currency, type
8
+ options = { currency: currency, type: type }
9
+ assert_param_is_one_of options, :type, account_types
10
+ auth.ku_request :post, :index, **options
11
+ end
12
+
13
+ def index options={}
14
+ auth.ku_request :get, :index, **options
15
+ end
16
+ alias all index
17
+ alias list index
18
+
19
+ def inner_transfer client_oid, pay_account_id, rec_account_id, amount
20
+ auth.ku_request :post, :inner_transfer, clientOid: client_oid, payAccountId: pay_account_id, recAccountId: rec_account_id, amount: amount
21
+ end
22
+
23
+ def show account_id
24
+ auth.ku_request :get, :show, account_id: account_id
25
+ end
26
+ alias get show
27
+ alias detail show
28
+
29
+ def ledgers account_id, options={}
30
+ auth.ku_request :get, :ledgers, account_id: account_id, **options
31
+ end
32
+
33
+ def holds account_id
34
+ auth.ku_request :get, :holds, account_id: account_id
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Endpoints
5
+ class User
6
+ class Deposits < User
7
+ def create currency
8
+ auth.ku_request :post, :create, currency: currency
9
+ end
10
+
11
+ def index options={}
12
+ auth.ku_request :get, :index, **options
13
+ end
14
+ alias all index
15
+ alias list index
16
+
17
+ def show currency
18
+ auth.ku_request :get, :show, currency: currency
19
+ end
20
+ alias get show
21
+ alias detail show
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Endpoints
5
+ class User
6
+ class Withdrawals < User
7
+ def create currency, address, amount, options={}
8
+ auth.ku_request :post, :index, currency: currency, address: address, amount: amount, **options
9
+ end
10
+ alias apply create
11
+
12
+ def index options={}
13
+ auth.ku_request :get, :index, **options
14
+ end
15
+ alias all index
16
+ alias list index
17
+
18
+ def quotas currency
19
+ auth.ku_request :get, :quotas, currency: currency
20
+ end
21
+
22
+ def delete withdrawal_id
23
+ auth.ku_request :delete, :delete, withdrawal_id: withdrawal_id
24
+ end
25
+ alias cancel delete
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Endpoints
5
+ class Websocket < Base
6
+ def public
7
+ Response.new(open.ku_request :post, :public)
8
+ end
9
+
10
+ def private
11
+ Response.new(auth.ku_request :post, :private)
12
+ end
13
+
14
+ private
15
+
16
+ class Response
17
+ attr_reader :token, :endpoint, :ping_interval
18
+ def initialize response
19
+ @token = response["token"]
20
+ @endpoint = response["instanceServers"][0]["endpoint"]
21
+ @ping_interval = response["instanceServers"][0]["pingInterval"]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module Kucoin
2
+ module Api
3
+ class Error < StandardError; end
4
+ class MissingApiKeyError < StandardError; end
5
+ class MissingApiSecretError < StandardError; end
6
+ class MissingApiPassphraseError < StandardError; end
7
+ class ClientError < StandardError; end
8
+ class MissingParamError < StandardError; end
9
+ class InvalidParamError < StandardError; end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Middleware
5
+ class AuthRequest < Faraday::Middleware
6
+ def initialize app, api_key, api_secret, api_passphrase
7
+ super(app)
8
+ @api_key = api_key.to_s
9
+ @api_secret = api_secret.to_s
10
+ @api_passphrase = api_passphrase.to_s
11
+ end
12
+
13
+ def call env
14
+ raise Kucoin::Api::MissingApiKeyError.new('API KEY not provided') if @api_key.empty?
15
+ raise Kucoin::Api::MissingApiSecretError.new('API SECRET not provided') if @api_secret.empty?
16
+ raise Kucoin::Api::MissingApiPassphraseError.new('API PASSPHRASE not provided') if @api_passphrase.empty?
17
+ env[:request_headers]['KC-API-KEY'] = @api_key
18
+ env[:request_headers]['KC-API-SIGN'] = signature(env)
19
+ env[:request_headers]['KC-API-PASSPHRASE'] = @api_passphrase
20
+ @app.call env
21
+ end
22
+
23
+ private
24
+
25
+ def signature env
26
+ Base64.strict_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), @api_secret, str_to_sign(env)))
27
+ end
28
+
29
+ def str_to_sign env
30
+ "#{env[:request_headers]['KC-API-TIMESTAMP']}#{env.method.upcase}#{env.url.request_uri}#{query_string(env)}"
31
+ end
32
+
33
+ def query_string env
34
+ return if [:get, :delete].include?(env.method.to_sym)
35
+ params = {}
36
+ begin
37
+ params.merge!(::JSON.parse(env.body.to_s))
38
+ rescue JSON::ParserError => e
39
+ end
40
+ params.to_json
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ module Middleware
5
+ class NonceRequest < Faraday::Middleware
6
+ def call env
7
+ env[:request_headers]["KC-API-TIMESTAMP"] = DateTime.now.strftime('%Q')
8
+ @app.call(env)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ class REST
5
+ BASE_URL = 'https://openapi-v2.kucoin.com'.freeze
6
+ SANDBOX_BASE_URL = 'https://openapi-sandbox.kucoin.com'.freeze
7
+
8
+ extend Kucoin::Api::Endpoints
9
+ generate_endpoint_methods
10
+
11
+ attr_reader :api_key, :api_secret, :api_passphrase
12
+ attr_reader :adapter
13
+
14
+ def initialize api_key: Kucoin::Api.default_key, api_secret: Kucoin::Api.default_secret, api_passphrase: Kucoin::Api.default_passphrase, adapter: Faraday.default_adapter, sandbox: false
15
+ @api_key = api_key
16
+ @api_secret = api_secret
17
+ @api_passphrase = api_passphrase
18
+ @adapter = adapter
19
+ @sandbox = sandbox
20
+ end
21
+ def sandbox?; @sandbox == true end
22
+
23
+ def base_url
24
+ sandbox? ? SANDBOX_BASE_URL : BASE_URL
25
+ end
26
+
27
+ def open endpoint
28
+ Connection.new(endpoint, url: base_url) do |conn|
29
+ conn.request :json
30
+ conn.response :json, content_type: 'application/json'
31
+ conn.adapter adapter
32
+ end
33
+ end
34
+
35
+ def auth endpoint
36
+ Connection.new(endpoint, url: base_url) do |conn|
37
+ conn.request :json
38
+ conn.response :json, content_type: 'application/json'
39
+ conn.use Kucoin::Api::Middleware::NonceRequest
40
+ conn.use Kucoin::Api::Middleware::AuthRequest, api_key, api_secret, api_passphrase
41
+ conn.adapter adapter
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ module Kucoin
3
+ module Api
4
+ class REST
5
+ class Connection
6
+ attr_reader :endpoint, :client
7
+ def initialize endpoint, options={}, &block
8
+ @endpoint = endpoint
9
+ @client = ::Faraday.new(options, &block)
10
+ end
11
+
12
+ def ku_request method, subpath, options = {}
13
+ response = client.public_send(method) do |req|
14
+
15
+ # substitute path parameters and remove from options hash
16
+ endpoint_url = endpoint.url(subpath).dup
17
+ options.each do |option, value|
18
+ path_param = /:#{option}/
19
+ if endpoint_url.match? path_param
20
+ options.delete(option)
21
+ endpoint_url.gsub!(path_param, value.to_s)
22
+ end
23
+ end
24
+
25
+ req.url endpoint_url
26
+
27
+ # parameters go into request body, not headers on POSTs
28
+ if method == :post
29
+ req.body = options
30
+ else
31
+ req.params.merge!(options)
32
+ end
33
+ end
34
+ success_or_error response
35
+ end
36
+
37
+ private
38
+
39
+ def success_or_error response
40
+ body = response.body.is_a?(Hash) ? response.body : JSON.parse(response.body)
41
+ return body['data'] if body['code'].to_i == 200000
42
+ return body if response.status == 200
43
+ raise Kucoin::Api::ClientError.new("#{body["code"] || body["status"]} - #{body["msg"] || body["message"]}")
44
+ rescue => e
45
+ raise Kucoin::Api::ClientError.new("#{e.message}\n#{response.body}")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ module Kucoin
2
+ module Api
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kucoin
4
+ module Api
5
+ class Websocket
6
+ class << self
7
+ def request_id; rand(Time.now.to_i) end
8
+
9
+ def subscribe channel:, params: {}
10
+ stream = {id: request_id, privateChannel: false, response: true}.merge(params)
11
+ params = {
12
+ id: stream[:id], type: 'subscribe', topic: stream[:topic],
13
+ privateChannel: stream[:privateChannel], response: stream[:response],
14
+ tunnelId: stream[:tunnelId]
15
+ }.select { |k,v| !v.nil? }
16
+ channel.send(params.to_json)
17
+ end
18
+
19
+ def open_tunnel channel:, params: {}
20
+ stream = {id: request_id, response: true}.merge(params)
21
+ params = {
22
+ id: stream[:id], type: 'openTunnel', newTunnelId: stream[:newTunnelId], response: stream[:response]
23
+ }
24
+ channel.send(params.to_json)
25
+ end
26
+ end
27
+
28
+ attr_reader :rest_client
29
+
30
+ def initialize rest_client: Kucoin::Api::REST.new
31
+ @rest_client = rest_client
32
+ end
33
+
34
+ def connect private: false, params: {}, methods:
35
+ create_stream(private ? auth_client(params: params) : open_client(params: params), methods: methods)
36
+ end
37
+
38
+ # Public: Create a WebSocket stream
39
+ #
40
+ # :stream - The Hash used to define the stream
41
+ # :id - Unique string to mark the request
42
+ # :topic - The topic you want to subscribe to
43
+ # :privateChannel - The user will only receive messages related himself on the topic(default is false)
44
+ # :response - To return the ack messages after the subscriptions succeed(default is true)
45
+ #
46
+ # :methods - The Hash which contains the event handler methods to pass to
47
+ # the WebSocket client
48
+ # :open - The Proc called when a stream is opened (optional)
49
+ # :message - The Proc called when a stream receives a message
50
+ # :error - The Proc called when a stream receives an error (optional)
51
+ # :close - The Proc called when a stream is closed (optional)
52
+ def start stream:, methods:
53
+ channel = connect(private: !!stream[:privateChannel], methods: methods)
54
+ self.class.subscribe channel: channel, params: stream
55
+ channel
56
+ end
57
+
58
+ # Public: Create a WebSocket stream for multiplex tunnels
59
+ #
60
+ # :stream - The Hash used to define the stream
61
+ # :id - Unique string to mark the request
62
+ # :newTunnelId - Required
63
+ # :privateChannel - The user will only receive messages related himself on the topic(default is false)
64
+ #
65
+ def multiplex stream:, methods:
66
+ channel = connect(private: !!stream[:privateChannel], methods: methods)
67
+ self.class.open_tunnel channel: channel, params: stream
68
+ channel
69
+ end
70
+
71
+ # PUBLIC
72
+ def ticker symbols:, methods:
73
+ start stream: { topic: topic_path('/market/ticker', symbols) }, methods: methods
74
+ end
75
+
76
+ def all_ticker methods:
77
+ ticker symbols: :all, methods: methods
78
+ end
79
+
80
+ def snapshot symbol:, methods:
81
+ start stream: { topic: "/market/snapshot:#{symbol}" }, methods: methods
82
+ end
83
+ alias symbol_snapshot snapshot
84
+ alias market_snapshot snapshot
85
+
86
+ def level_2_market_data symbols:, methods:
87
+ start stream: { topic: topic_path('/market/level2', symbols) }, methods: methods
88
+ end
89
+
90
+ def match_execution_data symbols:, methods:, private_channel: false
91
+ start stream: { topic: topic_path('/market/match', symbols), privateChannel: private_channel }, methods: methods
92
+ end
93
+
94
+ def full_match_engine_data symbols:, methods:, private_channel: false
95
+ start stream: { topic: topic_path('/market/level3', symbols), privateChannel: private_channel }, methods: methods
96
+ end
97
+
98
+ # PRIVATE
99
+ def stop_order_received_event symbols: , methods:
100
+ full_match_engine_data symbols: symbols, methods: methods, private_channel: true
101
+ end
102
+ alias stop_order_activate_event stop_order_received_event
103
+
104
+ def balance methods:
105
+ start stream: { topic: '/account/balance', privateChannel: true }, methods: methods
106
+ end
107
+
108
+ private
109
+
110
+ def open_client params: {}
111
+ get_client(rest_client.websocket.public, params: params)
112
+ end
113
+
114
+ def auth_client params: {}
115
+ get_client(rest_client.websocket.private, params: params)
116
+ end
117
+
118
+ def get_client response, params: {}
119
+ url = ["#{response.endpoint}?token=#{response.token}", URI.encode_www_form(params)].join('&')
120
+ Faye::WebSocket::Client.new(url, [], ping: response.ping_interval)
121
+ end
122
+
123
+ def create_stream websocket, methods:
124
+ attach_methods websocket, methods
125
+ websocket
126
+ end
127
+
128
+ # Internal: Iterate through methods passed and add them to the WebSocket
129
+ #
130
+ # websocket - The Faye::WebSocket::Client to apply methods to
131
+ #
132
+ # methods - The Hash which contains the event handler methods to pass to
133
+ # the WebSocket client
134
+ # :open - The Proc called when a stream is opened (optional)
135
+ # :message - The Proc called when a stream receives a message
136
+ # :error - The Proc called when a stream receives an error (optional)
137
+ # :close - The Proc called when a stream is closed (optional)
138
+ def attach_methods websocket, methods
139
+ methods.each_pair do |key, method|
140
+ websocket.on(key) { |event| method.call(event) }
141
+ end
142
+ end
143
+
144
+ def topic_path path, values=[]
145
+ values = [values] unless values.is_a?(Array)
146
+ "#{path}:#{values.compact.join(',')}"
147
+ end
148
+ end
149
+ end
150
+ end