kucoin-api 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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