bitmex-api 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bitmex/client.rb CHANGED
@@ -8,91 +8,176 @@ module Bitmex
8
8
  include HTTParty
9
9
  # logger ::Logger.new(STDOUT), :debug, :curl
10
10
 
11
- ANNOUNCEMENT_ARGS = %w(urgent).freeze
12
- APIKEY_ARGS = %w().freeze
13
- CHAT_ARGS = %w(channels connected).freeze
14
- EXECUTION_ARGS = %w(tradehistory).freeze
15
- FUNDING_ARGS = %w().freeze
16
- GLOBALNOTIFICATION_ARGS = %w().freeze
17
- INSTRUMENT_ARGS = %w(active activeandindices activeintervals compositeindex indices).freeze
18
- INSURANCE_ARGS = %w().freeze
19
- LEADERBOARD_ARGS = %w().freeze
20
- LIQUIDATION_ARGS = %w().freeze
21
- ORDER_ARGS = %w().freeze
22
- ORDERBOOK_ARGS = %w(L2).freeze
23
- POSITION_ARGS = %w().freeze
24
- QUOTE_ARGS = %w(bucketed).freeze
25
- SCHEMA_ARGS = %w(websockethelp).freeze
26
- SETTLEMENT_ARGS = %w().freeze
27
- STATS_ARGS = %w(history historyusd).freeze
28
- TRADE_ARGS = %w(bucketed).freeze
29
-
30
- TESTNET_HOST = 'testnet.bitmex.com'.freeze
31
- MAINNET_HOST = 'www.bitmex.com'.freeze
32
-
33
11
  AUTHORIZATIONS = %w(apikey execution position globalnotification order leaderboard quote user userevent)
34
12
 
35
13
  attr_reader :host, :api_key, :api_secret
36
14
 
15
+ # Create new client instance
16
+ # @param testnet [Boolean] true for testnet network
17
+ # @param api_key [String] the api key
18
+ # @param api_secret [String] the api secret
37
19
  def initialize(testnet: false, api_key: nil, api_secret: nil)
38
20
  @host = testnet ? TESTNET_HOST : MAINNET_HOST
39
21
  @api_key = api_key
40
22
  @api_secret = api_secret
41
23
  end
42
24
 
43
- def orderbook(symbol)
44
- execute 'orderbook', 'L2', { symbol: symbol } do |response|
45
- response.to_a.map do |s|
46
- Bitmex::Mash.new s
47
- end
25
+ # Get site announcements
26
+ # @return [Array] the public announcements
27
+ def announcements
28
+ get base_path(:announcement) do |response|
29
+ response_handler response
48
30
  end
49
31
  end
50
32
 
51
- #
52
- # WebSocket API
53
- #
54
- # https://www.bitmex.com/app/wsAPI
55
- #
56
- def listen(options, &ablock)
57
- EM.run do
58
- ws = Faye::WebSocket::Client.new realtime_url
33
+ # Persistent API Keys for Developers
34
+ # @return [Bitmex::Apikey] the apikey instance
35
+ def apikey(api_key = nil)
36
+ Bitmex::Apikey.new self, api_key
37
+ end
59
38
 
60
- topics = options.map{ |key, value| "#{key}:#{value}"}
61
- subscription = { op: :subscribe, args: topics }
62
- # puts subscription
39
+ # Trollbox Data
40
+ # @return [Bitmex::Chat] the chat instance
41
+ def chat
42
+ Bitmex::Chat.new self
43
+ end
63
44
 
64
- ws.on :open do |event|
65
- ws.send subscription.to_json.to_s
66
- end
45
+ # Tradeable Contracts, Indices, and History
46
+ # @return [Bitmex::Instrument] the instrument model
47
+ def instrument
48
+ Bitmex::Instrument.new self
49
+ end
67
50
 
68
- ws.on :message do |event|
69
- json = JSON.parse event.data
70
- data = json['data']
51
+ # Get funding history
52
+ # @!macro bitmex.filters
53
+ # @return [Array] the history
54
+ def funding(filters = {})
55
+ get base_path(:funding), params: filters do |response|
56
+ response_handler response
57
+ end
58
+ end
71
59
 
72
- data&.each do |payload|
73
- if block_given?
74
- yield Bitmex::Mash.new(payload.merge topic: json['table'])
75
- else
76
- p value
77
- end
78
- end
79
- end
60
+ # Get insurance fund history
61
+ # @!macro bitmex.filters
62
+ # @return [Array] the history
63
+ def insurance(filters = {})
64
+ get base_path(:insurance), params: filters do |response|
65
+ response_handler response
66
+ end
67
+ end
80
68
 
81
- ws.on :error do |event|
82
- raise [:error, event.data]
83
- end
69
+ # Get current leaderboard
70
+ # @param ranking [notional ROE] the ranking type
71
+ # @return [Array] current leaders
72
+ def leaderboard(ranking = 'notional')
73
+ get base_path(:leaderboard), params: { method: ranking } do |response|
74
+ response_handler response
75
+ end
76
+ end
84
77
 
85
- ws.on :close do |event|
86
- # p [:close, event.reason]
87
- ws = nil
88
- end
78
+ # Get liquidation orders
79
+ # @!macro bitmex.filters
80
+ # @return [Array] the liquidations
81
+ def liquidations(filters = {})
82
+ get base_path(:liquidation), params: filters do |response|
83
+ response_handler response
84
+ end
85
+ end
86
+
87
+ # Order Placement, Cancellation, Amending, and History
88
+ # @return [Bitmex::Order] the order model
89
+ def orders
90
+ # TODO: use class method
91
+ Bitmex::Order.new(self)
92
+ end
93
+
94
+ # Get an order by id
95
+ # @param orderID [String] the order #
96
+ # @param clOrdID [String] the client order #
97
+ # @return [Bitmex::Order] the order model
98
+ def order(orderID: nil, clOrdID: nil)
99
+ raise ArgumentError, 'either orderID or clOrdID is required' if orderID.nil? && clOrdID.nil?
100
+
101
+ Bitmex::Order.new(self, orderID, clOrdID)
102
+ end
103
+
104
+ # Get current orderbook in vertical format
105
+ # @param symbol [String] instrument symbol, send a series (e.g. XBT) to get data for the nearest contract in that series
106
+ # @param depth [Integer] orderbook depth per side. send 0 for full depth.
107
+ # @return [Array] the orderbook
108
+ def orderbook(symbol, depth: 25)
109
+ params = { symbol: symbol, depth: depth }
110
+ get base_path('orderbook/L2'), params: params do |response|
111
+ response_handler response
89
112
  end
90
113
  end
91
114
 
115
+ # Summary of Open and Closed Positions
116
+ # @return [Array] the list of positions
117
+ def positions
118
+ # TODO: use class method
119
+ Bitmex::Position.new(self).all
120
+ end
121
+
122
+ # Get an open position
123
+ # @param symbol [String] symbol of position
124
+ # @return [Bitmex::Position] open position
125
+ def position(symbol)
126
+ Bitmex::Position.new(self, symbol)
127
+ end
128
+
129
+ # Best Bid/Offer Snapshots & Historical Bins
130
+ # @return [Bitmex::Quote] the quote model
131
+ def quotes
132
+ # TODO: use class method
133
+ Bitmex::Quote.new self
134
+ end
135
+
136
+ # Get model schemata for data objects returned by this AP
137
+ # @return [Hash] the schema
138
+ def schema
139
+ get base_path(:schema) do |response|
140
+ response_handler response
141
+ end
142
+ end
143
+
144
+ # Get settlement history
145
+ # @return [Array] the settlement history
146
+ def settlement
147
+ get base_path(:settlement) do |response|
148
+ response_handler response
149
+ end
150
+ end
151
+
152
+ # Exchange statistics
153
+ # @return [Bitmex::Stats] the stats model
154
+ def stats
155
+ Bitmex::Stats.new self
156
+ end
157
+
158
+ # Individual and bucketed trades
159
+ # @return [Bitmex::Trade] the trade model
160
+ def trades
161
+ Bitmex::Trade.new self
162
+ end
163
+
164
+ # Account operations
165
+ # @return [Bitmex::User] the user model
92
166
  def user
93
167
  Bitmex::User.new self
94
168
  end
95
169
 
170
+ # Listen to generic topics
171
+ # @param topics [Hash] topics to listen to e.g. { trade: "XBTUSD" }
172
+ # @yield [data] data pushed via websocket
173
+ def listen(topics, &ablock)
174
+ EM.run do
175
+ topics.each do |topic, symbol|
176
+ websocket.subscribe topic, symbol, &ablock
177
+ end
178
+ end
179
+ end
180
+
96
181
  #
97
182
  # Stop websocket listener
98
183
  #
@@ -100,7 +185,12 @@ module Bitmex
100
185
  EM.stop_event_loop
101
186
  end
102
187
 
103
- def get(path, params: {}, auth: false)
188
+ def websocket
189
+ @websocket ||= Websocket.new realtime_url
190
+ end
191
+
192
+ # TODO: move these methods into rest client
193
+ def get(path, params: {}, auth: false, &ablock)
104
194
  options = {}
105
195
  options[:query] = params unless params.empty?
106
196
  options[:headers] = headers 'GET', path, '' if auth
@@ -117,10 +207,45 @@ module Bitmex
117
207
  options[:headers] = headers 'PUT', path, body, json: json if auth
118
208
 
119
209
  response = self.class.put "#{domain_url}#{path}", options
120
- puts response.body
121
210
  yield response
122
211
  end
123
212
 
213
+ def post(path, params: {}, auth: true, json: true)
214
+ body = json ? params.to_json.to_s : URI.encode_www_form(params)
215
+
216
+ options = {}
217
+ options[:body] = body
218
+ options[:headers] = headers 'POST', path, body, json: json if auth
219
+
220
+ response = self.class.post "#{domain_url}#{path}", options
221
+ yield response
222
+ end
223
+
224
+ def delete(path, params: {}, auth: true, json: true)
225
+ body = json ? params.to_json.to_s : URI.encode_www_form(params)
226
+
227
+ options = {}
228
+ options[:body] = body
229
+ options[:headers] = headers 'DELETE', path, body, json: json if auth
230
+
231
+ response = self.class.delete "#{domain_url}#{path}", options
232
+ yield response
233
+ end
234
+
235
+ def base_path(resource, action = '')
236
+ "/api/v1/#{resource}/#{action}"
237
+ end
238
+
239
+ def response_handler(response)
240
+ fail response.body unless response.success?
241
+
242
+ if response.parsed_response.is_a? Array
243
+ response.to_a.map { |s| Bitmex::Mash.new s }
244
+ else
245
+ Bitmex::Mash.new response
246
+ end
247
+ end
248
+
124
249
  private
125
250
 
126
251
  def method_missing(m, *args, &ablock)
@@ -0,0 +1,53 @@
1
+ module Bitmex
2
+ # Tradeable Contracts, Indices, and History
3
+ # @author Iulian Costan
4
+ class Instrument < Base
5
+ # Get all instruments
6
+ # @!macro bitmex.filters
7
+ # @return [Array] all instruments
8
+ def all(filters = {})
9
+ client.get instrument_path, params: filters do |response|
10
+ response_handler response
11
+ end
12
+ end
13
+
14
+ # Get all active instruments and instruments that have expired in <24hrs.
15
+ # @return [Array] active instruments
16
+ def active
17
+ client.get instrument_path('active') do |response|
18
+ response_handler response
19
+ end
20
+ end
21
+
22
+ # Return all active contract series and interval pairs
23
+ # @return [Bitmex::Mash] active intervals and symbols
24
+ def intervals
25
+ client.get instrument_path('activeIntervals') do |response|
26
+ response_handler response
27
+ end
28
+ end
29
+
30
+ # Show constituent parts of an index.
31
+ # @!macro bitmex.filters
32
+ # @return [Array] the parts of an index
33
+ def composite_index(filters = { symbol: '.XBT' })
34
+ client.get instrument_path('compositeIndex'), params: filters do |response|
35
+ response_handler response
36
+ end
37
+ end
38
+
39
+ # Get all price indices
40
+ # @return [Array] all indices
41
+ def indices
42
+ client.get instrument_path('indices') do |response|
43
+ response_handler response
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def instrument_path(action = '')
50
+ base_path :instrument, action
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
1
+ module Bitmex
2
+ # Order Placement, Cancellation, Amending, and History
3
+ # @author Iulian Costan
4
+ class Order < Base
5
+ attr_reader :orderID, :clOrdID
6
+
7
+ def initialize(client, orderID = nil, clOrdID = nil)
8
+ super client
9
+ @orderID = orderID
10
+ @clOrdID = clOrdID
11
+ end
12
+
13
+ # Get your orders
14
+ # @!macro bitmex.filters
15
+ # @return [Array] the orders
16
+ def all(filters = {})
17
+ client.get order_path, params: filters, auth: true do |response|
18
+ response_handler response
19
+ end
20
+ end
21
+
22
+ # Amend the quantity or price of an open order
23
+ # @param attributes [Hash] the fields to update
24
+ # @option attributes [Integer] :orderQty Optional order quantity in units of the instrument (i.e. contracts)
25
+ # @option attributes [Integer] :leavesQty Optional leaves quantity in units of the instrument (i.e. contracts). Useful for amending partially filled orders.
26
+ # @option attributes [Double] :price Optional limit price for 'Limit', 'StopLimit', and 'LimitIfTouched' orders.
27
+ # @option attributes [Double] :stopPx Optional trigger price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders. Use a price below the current price for stop-sell orders and buy-if-touched orders.
28
+ # @option attributes [Double] :pegOffsetValue Optional trailing offset from the current price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders; use a negative offset for stop-sell orders and buy-if-touched orders. Optional offset from the peg price for 'Pegged' orders.
29
+ # @option attributes [String] :text Optional amend annotation. e.g. 'Adjust skew'
30
+ # @return [Bitmex::Mash] the updated order
31
+ def update(attributes)
32
+ params = attributes.merge orderID: orderID, origClOrdID: clOrdID
33
+ client.put order_path, params: params do |response|
34
+ response_handler response
35
+ end
36
+ end
37
+
38
+ # Place new order
39
+ # @param symbol [String] instrument symbol
40
+ # @param attributes [Hash] order attributes
41
+ # @option attributes [Buy, Sell] :side Order side. Defaults to 'Buy' unless orderQty is negative
42
+ # @option attributes [Integer] :orderQty Order quantity in units of the instrument (i.e. contracts)
43
+ # @option attributes [Double] :price Optional limit price for 'Limit', 'StopLimit', and 'LimitIfTouched' orders
44
+ # @option attributes [Double] :displayQty Optional quantity to display in the book. Use 0 for a fully hidden order.
45
+ # @option attributes [Double] :stopPx Optional trigger price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders. Use a price below the current price for stop-sell orders and buy-if-touched orders. Use execInst of 'MarkPrice' or 'LastPrice' to define the current price used for triggering.
46
+ # @option attributes [String] :clOrdID Optional Client Order ID. This clOrdID will come back on the order and any related executions.
47
+ # @option attributes [Double] :pegOffsetValue Optional trailing offset from the current price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders; use a negative offset for stop-sell orders and buy-if-touched orders. Optional offset from the peg price for 'Pegged' orders.
48
+ # @option attributes [LastPeg, MidPricePeg, MarketPeg, PrimaryPeg, TrailingStopPeg] :pegPriceType Optional peg price type.
49
+ # @option attributes [Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched, MarketWithLeftOverAsLimit, Pegged] :ordType Order type. Defaults to 'Limit' when price is specified. Defaults to 'Stop' when stopPx is specified. Defaults to 'StopLimit' when price and stopPx are specified.
50
+ # @option attributes [Day, GoodTillCancel, ImmediateOrCancel, FillOrKill] :timeInForce Time in force. Defaults to 'GoodTillCancel' for 'Limit', 'StopLimit', 'LimitIfTouched', and 'MarketWithLeftOverAsLimit' orders.
51
+ # @option attributes [ParticipateDoNotInitiate, AllOrNone, MarkPrice, IndexPrice, LastPrice, Close, ReduceOnly, Fixed] :execInst Optional execution instructions. AllOrNone' instruction requires displayQty to be 0. 'MarkPrice', 'IndexPrice' or 'LastPrice' instruction valid for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders.
52
+ # @option attributes [String] :text Optional amend annotation. e.g. 'Take profit'
53
+ # @return [Bitmex::Mash] the created order
54
+ def create(symbol, attributes)
55
+ params = attributes.merge symbol: symbol
56
+ client.post order_path, params: params do |response|
57
+ response_handler response
58
+ end
59
+ end
60
+
61
+ # Cancel an order
62
+ # @param text [String] Optional cancellation annotation. e.g. 'Spread Exceeded'.
63
+ # @return [Bitmex::Mash] the canceled order
64
+ def cancel(text = nil)
65
+ params = { orderID: orderID, clOrdID: clOrdID, text: text }
66
+ client.delete order_path, params: params do |response|
67
+ # a single order only
68
+ response_handler(response).first
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def order_path(action = '')
75
+ client.base_path 'order', action
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,77 @@
1
+ module Bitmex
2
+ # Summary of Open and Closed Positions
3
+ # @author Iulian Costan
4
+ class Position < Base
5
+ attr_reader :symbol
6
+
7
+ # A new instance of Position
8
+ # @param client [Bitmex::Client] the HTTP client
9
+ # @param symbol [String] the symbol of the underlying position
10
+ def initialize(client, symbol = 'XBTUSD')
11
+ super client
12
+ @symbol = symbol
13
+ end
14
+
15
+ # Get your positions
16
+ # @return [Array] the list of positions
17
+ def all
18
+ client.get position_path, auth: true do |response|
19
+ response_handler response
20
+ end
21
+ end
22
+
23
+ # Enable isolated margin or cross margin per-position
24
+ # @param enabled [true, false] true for isolated margin, false cross margin
25
+ # @return [Hash] the updated position
26
+ def isolate(enabled: true)
27
+ path = position_path(:isolate)
28
+ params = { symbol: symbol, enabled: enabled }
29
+ client.post path, params: params do |response|
30
+ response_handler response
31
+ end
32
+ end
33
+
34
+ # Choose leverage for a position
35
+ # @param leverage [0-100] leverage value. send a number between 0.01 and 100 to enable isolated margin with a fixed leverage. send 0 to enable cross margin
36
+ # @return [Hash] the updated position
37
+ def leverage(leverage)
38
+ raise ArgumentError, "leverage #{leverage} is outside of [0..100] range" unless (0..100).include? leverage
39
+
40
+ path = position_path(:leverage)
41
+ params = { symbol: symbol, leverage: leverage }
42
+ client.post path, params: params do |response|
43
+ response_handler response
44
+ end
45
+ end
46
+
47
+ # Update your risk limit
48
+ # @param risk_limit [Double] new risk limit, in Satoshis.
49
+ # @return [Hash] the updated position
50
+ def risk_limit(risk_limit)
51
+ path = position_path(:riskLimit)
52
+ params = { symbol: symbol, riskLimit: risk_limit }
53
+ client.post path, params: params do |response|
54
+ response_handler response
55
+ end
56
+ end
57
+
58
+ # Transfer equity in or out of a position
59
+ # @example Transfer 1000 Satoshi
60
+ # position = client.position('XBTUSD').transfer_margin 1000
61
+ # @param amount [Double] amount to transfer, in Satoshis. may be negative.
62
+ # @return [Hash] the updated position
63
+ def transfer_margin(amount)
64
+ path = position_path(:transferMargin)
65
+ params = { symbol: symbol, amount: amount }
66
+ client.post path, params: params do |response|
67
+ response_handler response
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def position_path(action = '')
74
+ client.base_path :position, action
75
+ end
76
+ end
77
+ end