kucoin-api 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.simplecov +7 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +551 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kucoin-api.gemspec +42 -0
- data/lib/kucoin/api.rb +34 -0
- data/lib/kucoin/api/endpoints.rb +117 -0
- data/lib/kucoin/api/endpoints/base.rb +58 -0
- data/lib/kucoin/api/endpoints/markets.rb +17 -0
- data/lib/kucoin/api/endpoints/markets/currencies.rb +24 -0
- data/lib/kucoin/api/endpoints/markets/histories.rb +18 -0
- data/lib/kucoin/api/endpoints/markets/order_book.rb +30 -0
- data/lib/kucoin/api/endpoints/markets/symbols.rb +15 -0
- data/lib/kucoin/api/endpoints/markets/tickers.rb +19 -0
- data/lib/kucoin/api/endpoints/other.rb +12 -0
- data/lib/kucoin/api/endpoints/trade.rb +8 -0
- data/lib/kucoin/api/endpoints/trade/fills.rb +20 -0
- data/lib/kucoin/api/endpoints/trade/orders.rb +44 -0
- data/lib/kucoin/api/endpoints/user.rb +8 -0
- data/lib/kucoin/api/endpoints/user/accounts.rb +40 -0
- data/lib/kucoin/api/endpoints/user/deposits.rb +26 -0
- data/lib/kucoin/api/endpoints/user/withdrawals.rb +30 -0
- data/lib/kucoin/api/endpoints/websocket.rb +27 -0
- data/lib/kucoin/api/error.rb +11 -0
- data/lib/kucoin/api/middleware/auth_request.rb +45 -0
- data/lib/kucoin/api/middleware/nonce_request.rb +13 -0
- data/lib/kucoin/api/rest.rb +46 -0
- data/lib/kucoin/api/rest/connection.rb +50 -0
- data/lib/kucoin/api/version.rb +5 -0
- data/lib/kucoin/api/websocket.rb +150 -0
- data/log/.keep +0 -0
- 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,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,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
|