bitmex-api 0.0.2 → 0.0.3

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