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