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.
- 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
|