deribit 1.3.2
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 +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +55 -0
- data/LICENSE +22 -0
- data/README.md +261 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/deribit.gemspec +26 -0
- data/lib/deribit/api.rb +206 -0
- data/lib/deribit/error.rb +24 -0
- data/lib/deribit/immutable_header_key.rb +34 -0
- data/lib/deribit/request.rb +91 -0
- data/lib/deribit/version.rb +3 -0
- data/lib/deribit/ws/handler.rb +56 -0
- data/lib/deribit/ws.rb +285 -0
- data/lib/deribit.rb +18 -0
- metadata +120 -0
data/lib/deribit/api.rb
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
module Deribit
|
|
2
|
+
class API
|
|
3
|
+
attr_reader :request
|
|
4
|
+
|
|
5
|
+
def initialize(key, secret, test_server: nil)
|
|
6
|
+
@request = Request.new(key, secret, test_server: test_server)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def orderbook(instrument)
|
|
10
|
+
request.send(path: "/api/v1/public/getorderbook", params: {instrument: instrument})
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def instruments(expired: false, only_active: true)
|
|
14
|
+
response = request.send(path: '/api/v1/public/getinstruments', params: {expired: expired})
|
|
15
|
+
if response.is_a?(Array) and only_active
|
|
16
|
+
response = response.select {|i| i[:isActive] == true}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
response
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def index
|
|
23
|
+
request.send(path: '/api/v1/public/index', params: {})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test
|
|
27
|
+
request.send(path: '/api/v1/public/test', params: {})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def currencies
|
|
31
|
+
request.send(path: '/api/v1/public/getcurrencies', params: {})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def last_trades(instrument, count: nil, since: nil)
|
|
35
|
+
params = {instrument: instrument}
|
|
36
|
+
params[:count] = count if count
|
|
37
|
+
params[:since] = since if since
|
|
38
|
+
|
|
39
|
+
request.send(path: '/api/v1/public/getlasttrades', params: params)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def summary(instrument = 'all')
|
|
43
|
+
params = {}
|
|
44
|
+
params[:instrument] = instrument if instrument
|
|
45
|
+
|
|
46
|
+
request.send(path: '/api/v1/public/getsummary', params: params)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def margins(instrument, quantity: 1, price: 0.01)
|
|
50
|
+
params = {
|
|
51
|
+
instrument: instrument,
|
|
52
|
+
quantity: quantity,
|
|
53
|
+
price: price
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
request.send(path: '/api/v1/private/getmargins', params: params)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def account(full: false)
|
|
60
|
+
params = {
|
|
61
|
+
ext: full
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
request.send(path: '/api/v1/private/account', params: params)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# | Name | Type | Decription |
|
|
69
|
+
# |--------------|------------|-----------------------------------------------------------------------------------|
|
|
70
|
+
# | `instrument` | `string` | Required, instrument name |
|
|
71
|
+
# | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
|
|
72
|
+
# | `price` | `float` | Required, USD for futures, BTC for options |
|
|
73
|
+
# | `type` | `string` | Required, "limit", "market" or for futures only: "stop_limit" |
|
|
74
|
+
# | `stopPx` | `string` | Required, needed for stop_limit order, defines stop price |
|
|
75
|
+
# | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
|
|
76
|
+
# | `label` | `string` | Optional, user defined maximum 32-char label for the order |
|
|
77
|
+
# | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
|
|
78
|
+
# | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
|
|
79
|
+
|
|
80
|
+
def buy(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "index_price", post_only: nil, reduce_only: nil, label: nil, max_show: nil, adv: nil)
|
|
81
|
+
params = {
|
|
82
|
+
instrument: instrument,
|
|
83
|
+
quantity: quantity,
|
|
84
|
+
price: price
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
%i(type stopPx post_only reduce_only label max_show adv execInst).each do |var|
|
|
88
|
+
variable = eval(var.to_s)
|
|
89
|
+
params[var] = variable if variable
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
request.send(path: '/api/v1/private/buy', params: params)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# | Name | Type | Decription |
|
|
97
|
+
# |--------------|------------|-----------------------------------------------------------------------------------|
|
|
98
|
+
# | `instrument` | `string` | Required, instrument name |
|
|
99
|
+
# | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
|
|
100
|
+
# | `price` | `float` | Required, USD for futures, BTC for options |
|
|
101
|
+
# | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
|
|
102
|
+
# | `label` | `string` | Optional, user defined maximum 32-char label for the order |
|
|
103
|
+
# | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
|
|
104
|
+
# | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
|
|
105
|
+
#
|
|
106
|
+
|
|
107
|
+
def sell(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "index_price", post_only: nil, reduce_only: nil, label: nil, max_show: nil, adv: nil)
|
|
108
|
+
params = {
|
|
109
|
+
instrument: instrument,
|
|
110
|
+
quantity: quantity,
|
|
111
|
+
price: price
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
%i(type stopPx post_only reduce_only label max_show adv execInst).each do |var|
|
|
115
|
+
variable = eval(var.to_s)
|
|
116
|
+
params[var] = variable if variable
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
request.send(path: '/api/v1/private/sell', params: params)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
#
|
|
123
|
+
# | Name | Type | Decription |
|
|
124
|
+
# |--------------|------------|-----------------------------------------------------------------------------------|
|
|
125
|
+
# | `order_id` | `integer` | Required, ID of the order returned by "sell" or "buy" request |
|
|
126
|
+
# | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
|
|
127
|
+
# | `price` | `float` | Required, USD for futures, BTC for options |
|
|
128
|
+
# | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
|
|
129
|
+
# | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
|
|
130
|
+
|
|
131
|
+
def edit(order_id, quantity, price, post_only: nil, adv: nil, stopPx: nil)
|
|
132
|
+
params = {
|
|
133
|
+
orderId: order_id,
|
|
134
|
+
quantity: quantity,
|
|
135
|
+
price: price
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
%i(post_only adv stopPx).each do |var|
|
|
139
|
+
variable = eval(var.to_s)
|
|
140
|
+
params[var] = variable if variable
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
request.send(path: '/api/v1/private/edit', params: params)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def cancel(order_id)
|
|
147
|
+
params = {
|
|
148
|
+
"orderId": order_id
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
request.send(path: '/api/v1/private/cancel', params: params)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def cancel_all(type = "all")
|
|
155
|
+
params = {
|
|
156
|
+
"type": type
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
request.send(path: '/api/v1/private/cancelall', params: params)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def open_orders(instrument: nil, order_id: nil, type: nil)
|
|
163
|
+
params = {}
|
|
164
|
+
params[:instrument] = instrument if instrument
|
|
165
|
+
params[:orderId] = order_id if order_id
|
|
166
|
+
params[:type] = type if type
|
|
167
|
+
|
|
168
|
+
request.send(path: '/api/v1/private/getopenorders', params: params)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def positions
|
|
172
|
+
request.send(path: '/api/v1/private/positions', params: {})
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def order_state(order_id)
|
|
176
|
+
params = {
|
|
177
|
+
"orderId": order_id
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
request.send(path: '/api/v1/private/orderstate', params: params)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def order_history(instrument: nil, count: nil, offset: nil)
|
|
184
|
+
params = {}
|
|
185
|
+
params[:instrument] = instrument if instrument
|
|
186
|
+
params[:count] = count if count
|
|
187
|
+
params[:offset] = offset if offset
|
|
188
|
+
|
|
189
|
+
request.send(path: '/api/v1/private/orderhistory', params: params)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def trade_history(instrument: nil, count: nil, start_trade_id: nil)
|
|
193
|
+
params = {}
|
|
194
|
+
|
|
195
|
+
%i(count instrument).each do |var|
|
|
196
|
+
variable = eval(var.to_s)
|
|
197
|
+
params[var] = variable if variable
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
params[:startTradeId] = start_trade_id if start_trade_id
|
|
201
|
+
|
|
202
|
+
request.send(path: '/api/v1/private/tradehistory', params: params)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Deribit
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
attr_reader :code, :msg
|
|
4
|
+
|
|
5
|
+
def initialize(code: nil, json: {}, message: nil)
|
|
6
|
+
@code = code || json[:code]
|
|
7
|
+
@msg = message || json[:msg]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def inspect
|
|
11
|
+
message = ""
|
|
12
|
+
message += "(#{code}) " unless code.nil?
|
|
13
|
+
message += "#{msg}" unless msg.nil?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def message
|
|
17
|
+
inspect
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_s
|
|
21
|
+
inspect
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class Deribit::ImmutableHeaderKey
|
|
2
|
+
attr_reader :key
|
|
3
|
+
|
|
4
|
+
def initialize(key)
|
|
5
|
+
@key = key
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def downcase
|
|
9
|
+
self
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def capitalize
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def split(*)
|
|
17
|
+
[self]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def hash
|
|
21
|
+
key.hash
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def eql?(other)
|
|
25
|
+
key.eql? other.key.eql?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
def self.to_s
|
|
30
|
+
key
|
|
31
|
+
end
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
|
|
4
|
+
module Deribit
|
|
5
|
+
class Request
|
|
6
|
+
attr_accessor :key, :secret, :base_uri
|
|
7
|
+
|
|
8
|
+
def initialize(key, secret, test_server: nil)
|
|
9
|
+
test_server = ENV['DERIBIT_TEST_SERVER'] if test_server == nil
|
|
10
|
+
@key = key
|
|
11
|
+
@secret = secret
|
|
12
|
+
@base_uri = test_server ? URI(TEST_URL) : URI(SERVER_URL)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def send(path: DEFAULT_REQUEST_PATH, params: {})
|
|
16
|
+
uri = base_uri + path
|
|
17
|
+
|
|
18
|
+
if path.start_with?(PRIVATE_PATH)
|
|
19
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
20
|
+
request.body = URI.encode_www_form(params)
|
|
21
|
+
request.add_field 'x-deribit-sig', generate_signature(path, params)
|
|
22
|
+
|
|
23
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
24
|
+
http.request(request)
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
uri.query = URI.encode_www_form(params)
|
|
28
|
+
response = Net::HTTP.get_response(uri)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if is_error_response?(response)
|
|
32
|
+
raise Error.new(code: response.code)
|
|
33
|
+
else
|
|
34
|
+
process(response)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def process(response)
|
|
39
|
+
json = JSON.parse(response.body, symbolize_names: true)
|
|
40
|
+
|
|
41
|
+
raise Error.new(message: "Failed for #{key}: " + json[:message]) unless json[:success]
|
|
42
|
+
|
|
43
|
+
if json.include?(:result)
|
|
44
|
+
json[:result]
|
|
45
|
+
elsif json.include?(:message)
|
|
46
|
+
json[:message]
|
|
47
|
+
else
|
|
48
|
+
"ok"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def generate_signature(path, params = {})
|
|
53
|
+
timestamp = Time.now.utc.to_i + 1000
|
|
54
|
+
|
|
55
|
+
signature_data = {
|
|
56
|
+
_: timestamp,
|
|
57
|
+
_ackey: key,
|
|
58
|
+
_acsec: secret,
|
|
59
|
+
_action: path
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
signature_data.update(params)
|
|
63
|
+
sorted_signature_data = signature_data.sort
|
|
64
|
+
|
|
65
|
+
converter = ->(data){
|
|
66
|
+
key = data[0]
|
|
67
|
+
value = data[1]
|
|
68
|
+
if value.is_a?(Array)
|
|
69
|
+
[key.to_s, value.join].join('=')
|
|
70
|
+
else
|
|
71
|
+
[key.to_s, value.to_s].join('=')
|
|
72
|
+
end
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
items = sorted_signature_data.map(&converter)
|
|
76
|
+
signature_string = items.join('&')
|
|
77
|
+
|
|
78
|
+
sha256 = OpenSSL::Digest::SHA256.new
|
|
79
|
+
sha256_signature = sha256.digest(signature_string.encode('utf-8'))
|
|
80
|
+
|
|
81
|
+
base64_signature = Base64.encode64(sha256_signature).encode('utf-8')
|
|
82
|
+
|
|
83
|
+
[key, timestamp, base64_signature].join('.').strip
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def is_error_response?(response)
|
|
87
|
+
code = response.code.to_i
|
|
88
|
+
code == 0 || code >= 400
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Deribit
|
|
2
|
+
class WS
|
|
3
|
+
|
|
4
|
+
class Handler
|
|
5
|
+
attr_accessor :timestamp
|
|
6
|
+
|
|
7
|
+
AVAILABLE_METHODS = [
|
|
8
|
+
:account,
|
|
9
|
+
:getcurrencies,
|
|
10
|
+
:subscribe,
|
|
11
|
+
:subscribed,
|
|
12
|
+
:unsubscribe,
|
|
13
|
+
:buy,
|
|
14
|
+
:sell,
|
|
15
|
+
:trade,
|
|
16
|
+
:trade_event,
|
|
17
|
+
:order_book_event,
|
|
18
|
+
:user_order_event,
|
|
19
|
+
:user_orders_event,
|
|
20
|
+
:announcements,
|
|
21
|
+
:index,
|
|
22
|
+
:heartbeat,
|
|
23
|
+
:order,
|
|
24
|
+
:pong
|
|
25
|
+
]
|
|
26
|
+
SILENT = [:setheartbeat, :subscribed, :heartbeat, :"public API test"]
|
|
27
|
+
|
|
28
|
+
def initialize
|
|
29
|
+
@timestamp = Time.now.to_i
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def method_missing(m, *json, &block)
|
|
33
|
+
return false if SILENT.include?(m.to_sym)
|
|
34
|
+
|
|
35
|
+
puts "Delegating #{m}"
|
|
36
|
+
if AVAILABLE_METHODS.include?(m.to_sym)
|
|
37
|
+
notice(json)
|
|
38
|
+
else
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def notice(json)
|
|
44
|
+
return json.each { |e| notice(e) } if json.is_a?(Array)
|
|
45
|
+
|
|
46
|
+
msg = json.is_a?(String) ? json : json[:message]
|
|
47
|
+
puts "Notice: #{msg}" if msg && !SILENT.include?(msg.to_sym)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def handle_error(json, error)
|
|
51
|
+
puts "Alert! #{error.class} on message: '#{json.try(:fetch, :message)}', #{json.inspect}. Message: #{error.full_message}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/deribit/ws.rb
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
require 'websocket-client-simple'
|
|
2
|
+
|
|
3
|
+
module Deribit
|
|
4
|
+
class WS
|
|
5
|
+
AVAILABLE_EVENTS = [:order_book, :trade, :my_trade, :user_order, :index, :portfolio, :announcement]
|
|
6
|
+
|
|
7
|
+
attr_reader :socket, :response, :ids_stack, :handler, :subscribed_instruments
|
|
8
|
+
|
|
9
|
+
def initialize(key, secret, handler: Handler, test_server: nil)
|
|
10
|
+
test_server = ENV["DERIBIT_TEST_SERVER"] if test_server == nil
|
|
11
|
+
@request = Request.new(key, secret, test_server: test_server)
|
|
12
|
+
@socket = connect(test_server ? WS_TEST_URL : WS_SERVER_URL)
|
|
13
|
+
@handler = handler.instance_of?(Class) ? handler.new : handler
|
|
14
|
+
@ids_stack = []
|
|
15
|
+
|
|
16
|
+
# the structure of subscribed_instruments: {'event_name' => ['instrument1', 'instrument2']]}
|
|
17
|
+
@subscribed_instruments = {}
|
|
18
|
+
|
|
19
|
+
start_handle
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_subscribed_instruments(instruments: , events: )
|
|
23
|
+
instruments = [instruments] unless instruments.is_a?(Array)
|
|
24
|
+
|
|
25
|
+
events.each do |event|
|
|
26
|
+
_event = event.to_sym
|
|
27
|
+
@subscribed_instruments[_event] = if sub_instr = @subscribed_instruments[_event]
|
|
28
|
+
(sub_instr + instruments).uniq
|
|
29
|
+
else
|
|
30
|
+
instruments.uniq
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def connect(url)
|
|
36
|
+
puts "Connecting to #{url}"
|
|
37
|
+
WebSocket::Client::Simple.connect(url)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reconnect!
|
|
41
|
+
@socket = connect
|
|
42
|
+
start_handle
|
|
43
|
+
sleep 3
|
|
44
|
+
resubscribe!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def resubscribe!
|
|
48
|
+
if subscribed_instruments.any?
|
|
49
|
+
subscribed_instruments.each do |event, instruments|
|
|
50
|
+
p "Reconnecting to event: #{event} at instrument: #{instruments}"
|
|
51
|
+
subscribe(instruments, events: event.to_s)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def close
|
|
57
|
+
@socket.close
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ping
|
|
61
|
+
message = {action: "/api/v1/public/ping"}
|
|
62
|
+
@socket.send(message.to_json)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test
|
|
66
|
+
message = {action: "/api/v1/public/test"}
|
|
67
|
+
@socket.send(message.to_json)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# events to be reported, possible events:
|
|
71
|
+
# "order_book" -- order book change
|
|
72
|
+
# "trade" -- trade notification
|
|
73
|
+
# "announcements" -- announcements (list of new announcements titles is send)
|
|
74
|
+
# "user_order" -- change of user orders (openning, cancelling, filling)
|
|
75
|
+
# "my_trade" -- filtered trade notification, only trades of the
|
|
76
|
+
# subscribed user are reported with trade direction "buy"/"sell" from the
|
|
77
|
+
# subscribed user point of view ("I sell ...", "I buy ..."), see below.
|
|
78
|
+
# Note, for "index" - events are ignored and can be []
|
|
79
|
+
def subscribe(instruments = ['BTC-PERPETUAL'] , events: ["user_order"], arguments: {})
|
|
80
|
+
instruments = [instruments] unless instruments.is_a?(Array)
|
|
81
|
+
events = [events] unless events.is_a?(Array)
|
|
82
|
+
|
|
83
|
+
raise "Events must include only #{AVAILABLE_EVENTS.join(", ")} actions" if events.map{|e| AVAILABLE_EVENTS.include?(e.to_sym)}.index(false) or events.empty?
|
|
84
|
+
raise "instruments are required" if instruments.empty?
|
|
85
|
+
|
|
86
|
+
arguments = arguments.merge(instrument: instruments, event: events)
|
|
87
|
+
send(path: '/api/v1/private/subscribe', arguments: arguments)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#unsubscribe for all notifications if instruments is empty
|
|
91
|
+
def unsubscribe(instruments=[])
|
|
92
|
+
instruments = [instruments] unless instruments.is_a?(Array)
|
|
93
|
+
send(path: '/api/v1/private/unsubscribe')
|
|
94
|
+
sleep(0.2)
|
|
95
|
+
if instruments.any?
|
|
96
|
+
@subscribed_instruments.each do |event, _instruments|
|
|
97
|
+
@subscribed_instruments[event] = _instruments - instruments
|
|
98
|
+
subscribe(@subscribed_instruments[event], events: [event])
|
|
99
|
+
sleep(0.2)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# | Name | Type | Decription |
|
|
105
|
+
# |--------------|------------|-----------------------------------------------------------------------------------|
|
|
106
|
+
# | `instrument` | `string` | Required, instrument name |
|
|
107
|
+
# | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
|
|
108
|
+
# | `price` | `float` | Required, USD for futures, BTC for options |
|
|
109
|
+
# | `type` | `string` | Required, "limit", "market" or for futures only: "stop_limit" |
|
|
110
|
+
# | `stopPx` | `string` | Required, needed for stop_limit order, defines stop price |
|
|
111
|
+
# | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
|
|
112
|
+
# | `label` | `string` | Optional, user defined maximum 32-char label for the order |
|
|
113
|
+
# | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
|
|
114
|
+
# | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
|
|
115
|
+
|
|
116
|
+
def buy(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "mark_price", post_only: nil, label: nil, max_show: nil, adv: nil)
|
|
117
|
+
params = {
|
|
118
|
+
instrument: instrument,
|
|
119
|
+
quantity: quantity
|
|
120
|
+
}
|
|
121
|
+
params[:price] = price if price
|
|
122
|
+
|
|
123
|
+
%i(type stopPx post_only label max_show adv execInst).each do |var|
|
|
124
|
+
variable = eval(var.to_s)
|
|
125
|
+
params[var] = variable if variable
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
send(path: '/api/v1/private/buy', arguments: params)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# | Name | Type | Decription |
|
|
132
|
+
# |--------------|------------|-----------------------------------------------------------------------------------|
|
|
133
|
+
# | `instrument` | `string` | Required, instrument name |
|
|
134
|
+
# | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
|
|
135
|
+
# | `price` | `float` | Required, USD for futures, BTC for options |
|
|
136
|
+
# | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
|
|
137
|
+
# | `label` | `string` | Optional, user defined maximum 32-char label for the order |
|
|
138
|
+
# | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
|
|
139
|
+
# | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
|
|
140
|
+
#
|
|
141
|
+
|
|
142
|
+
def sell(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "mark_price", post_only: nil, label: nil, max_show: nil, adv: nil)
|
|
143
|
+
params = {
|
|
144
|
+
instrument: instrument,
|
|
145
|
+
quantity: quantity
|
|
146
|
+
}
|
|
147
|
+
params[:price] = price if price
|
|
148
|
+
|
|
149
|
+
%i(type stopPx post_only label max_show adv execInst).each do |var|
|
|
150
|
+
variable = eval(var.to_s)
|
|
151
|
+
params[var] = variable if variable
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
send(path: '/api/v1/private/sell', arguments: params)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def account
|
|
158
|
+
send(path: '/api/v1/private/account')
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def instruments(expired: false)
|
|
162
|
+
send(path: '/api/v1/public/getinstruments', arguments: {expired: expired})
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def currencies
|
|
166
|
+
send(path: '/api/v1/public/getcurrencies')
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def summary(instrument = 'BTC-PERPETUAL')
|
|
170
|
+
send(path: '/api/v1/public/getsummary', arguments: { instrument: instrument })
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def openorders(instrument: "BTC-PERPETUAL", order_id: nil, type: nil)
|
|
174
|
+
params = {}
|
|
175
|
+
params[:instrument] = instrument if instrument
|
|
176
|
+
params[:orderId] = order_id if order_id
|
|
177
|
+
params[:type] = type if type
|
|
178
|
+
|
|
179
|
+
send(path: '/api/v1/private/getopenorders', arguments: params)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def cancel(order_id)
|
|
183
|
+
params = {
|
|
184
|
+
"orderId": order_id
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
send(path: '/api/v1/private/cancel', arguments: params)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def cancel_all(type = "all")
|
|
191
|
+
params = {
|
|
192
|
+
"type": type
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
send(path: '/api/v1/private/cancelall', arguments: params)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def set_heartbeat(interval = "60")
|
|
199
|
+
params = {
|
|
200
|
+
"interval": interval
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
send(path: '/api/v1/public/setheartbeat', arguments: params)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def handle_notifications(notifications)
|
|
207
|
+
return if notifications.empty?
|
|
208
|
+
notification, *tail = notifications
|
|
209
|
+
handler.send(notification[:message], notification[:result])
|
|
210
|
+
|
|
211
|
+
handle_notifications(tail)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private
|
|
215
|
+
|
|
216
|
+
def start_handle
|
|
217
|
+
instance = self
|
|
218
|
+
@socket.on :message do |msg|
|
|
219
|
+
# puts "msg = #{msg.inspect}"
|
|
220
|
+
begin
|
|
221
|
+
if msg.type == :text
|
|
222
|
+
json = JSON.parse(msg.data, symbolize_names: true)
|
|
223
|
+
puts "Subscribed!" if json[:message] == "subscribed"
|
|
224
|
+
|
|
225
|
+
if json[:message] == "test_request"
|
|
226
|
+
# puts "Got test request: #{json.inspect}" # DEBUG
|
|
227
|
+
instance.test
|
|
228
|
+
elsif json[:id] and stack_id = instance.ids_stack.find{|i| i[json[:id]]}
|
|
229
|
+
method = stack_id[json[:id]][0]
|
|
230
|
+
#pass the method to handler
|
|
231
|
+
params = instance.ids_stack.delete(stack_id)
|
|
232
|
+
|
|
233
|
+
#save subscribed_instruments for resubscribe in unsubscribe action
|
|
234
|
+
if method == 'subscribe'
|
|
235
|
+
params = params[json[:id]][1][:arguments]
|
|
236
|
+
instance.add_subscribed_instruments(instruments: params[:instrument], events: params[:event])
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
instance.handler.send(method, json)
|
|
240
|
+
elsif json[:notifications]
|
|
241
|
+
instance.handle_notifications(json[:notifications])
|
|
242
|
+
else
|
|
243
|
+
instance.handler.send(:notice, json)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
instance.handler.timestamp = Time.now.to_i
|
|
247
|
+
elsif msg.type == :close
|
|
248
|
+
puts "trying to reconnect = got close event, msg: #{msg.inspect}"
|
|
249
|
+
instance.reconnect!
|
|
250
|
+
end
|
|
251
|
+
rescue StandardError => e
|
|
252
|
+
instance.handler.handle_error(json, e)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
@socket.on :error do |e|
|
|
257
|
+
puts e
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def send(path: , arguments: {})
|
|
262
|
+
return unless path
|
|
263
|
+
params = {action: path, arguments: arguments}
|
|
264
|
+
sig = @request.generate_signature(path, arguments)
|
|
265
|
+
params[:sig] = sig
|
|
266
|
+
params[:id] = Time.now.to_i
|
|
267
|
+
|
|
268
|
+
action = path[/\/api.*\/([^\/]+)$/, 1]
|
|
269
|
+
put_id(params[:id], [action, params])
|
|
270
|
+
|
|
271
|
+
p params
|
|
272
|
+
@socket.send(params.to_json)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def put_id(id, action)
|
|
277
|
+
@ids_stack << {id => action}
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def pop_id(id)
|
|
281
|
+
@ids_stack.delete(id)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
end
|