bitmex-api 0.0.3 → 0.1.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.
@@ -5,43 +5,38 @@ module Bitmex
5
5
  # Get all instruments
6
6
  # @!macro bitmex.filters
7
7
  # @return [Array] all instruments
8
- def all(filters = {})
9
- client.get instrument_path, params: filters do |response|
10
- response_handler response
8
+ # @yield [Hash] the instrument
9
+ def all(filters = {}, &ablock)
10
+ if block_given?
11
+ websocket.listen instrument: filters[:symbol], &ablock
12
+ else
13
+ rest.get instrument_path, params: filters
11
14
  end
12
15
  end
13
16
 
14
17
  # Get all active instruments and instruments that have expired in <24hrs.
15
18
  # @return [Array] active instruments
16
19
  def active
17
- client.get instrument_path('active') do |response|
18
- response_handler response
19
- end
20
+ rest.get instrument_path('active')
20
21
  end
21
22
 
22
23
  # Return all active contract series and interval pairs
23
24
  # @return [Bitmex::Mash] active intervals and symbols
24
25
  def intervals
25
- client.get instrument_path('activeIntervals') do |response|
26
- response_handler response
27
- end
26
+ rest.get instrument_path('activeIntervals')
28
27
  end
29
28
 
30
29
  # Show constituent parts of an index.
31
30
  # @!macro bitmex.filters
32
31
  # @return [Array] the parts of an index
33
32
  def composite_index(filters = { symbol: '.XBT' })
34
- client.get instrument_path('compositeIndex'), params: filters do |response|
35
- response_handler response
36
- end
33
+ rest.get instrument_path('compositeIndex'), params: filters
37
34
  end
38
35
 
39
36
  # Get all price indices
40
37
  # @return [Array] all indices
41
38
  def indices
42
- client.get instrument_path('indices') do |response|
43
- response_handler response
44
- end
39
+ rest.get instrument_path('indices')
45
40
  end
46
41
 
47
42
  private
@@ -4,8 +4,8 @@ module Bitmex
4
4
  class Order < Base
5
5
  attr_reader :orderID, :clOrdID
6
6
 
7
- def initialize(client, orderID = nil, clOrdID = nil)
8
- super client
7
+ def initialize(rest, websocket = nil, orderID = nil, clOrdID = nil)
8
+ super rest, websocket
9
9
  @orderID = orderID
10
10
  @clOrdID = clOrdID
11
11
  end
@@ -13,9 +13,12 @@ module Bitmex
13
13
  # Get your orders
14
14
  # @!macro bitmex.filters
15
15
  # @return [Array] the orders
16
- def all(filters = {})
17
- client.get order_path, params: filters, auth: true do |response|
18
- response_handler response
16
+ # @yield [Hash] the order
17
+ def all(filters = {}, &ablock)
18
+ if block_given?
19
+ websocket.listen order: filters[:symbol], &ablock
20
+ else
21
+ rest.get order_path, params: filters, auth: true
19
22
  end
20
23
  end
21
24
 
@@ -30,9 +33,7 @@ module Bitmex
30
33
  # @return [Bitmex::Mash] the updated order
31
34
  def update(attributes)
32
35
  params = attributes.merge orderID: orderID, origClOrdID: clOrdID
33
- client.put order_path, params: params do |response|
34
- response_handler response
35
- end
36
+ rest.put order_path, params: params
36
37
  end
37
38
 
38
39
  # Place new order
@@ -53,9 +54,7 @@ module Bitmex
53
54
  # @return [Bitmex::Mash] the created order
54
55
  def create(symbol, attributes)
55
56
  params = attributes.merge symbol: symbol
56
- client.post order_path, params: params do |response|
57
- response_handler response
58
- end
57
+ rest.post order_path, params: params
59
58
  end
60
59
 
61
60
  # Cancel an order
@@ -63,7 +62,7 @@ module Bitmex
63
62
  # @return [Bitmex::Mash] the canceled order
64
63
  def cancel(text = nil)
65
64
  params = { orderID: orderID, clOrdID: clOrdID, text: text }
66
- client.delete order_path, params: params do |response|
65
+ rest.delete order_path, params: params do |response|
67
66
  # a single order only
68
67
  response_handler(response).first
69
68
  end
@@ -72,7 +71,7 @@ module Bitmex
72
71
  private
73
72
 
74
73
  def order_path(action = '')
75
- client.base_path 'order', action
74
+ rest.base_path 'order', action
76
75
  end
77
76
  end
78
77
  end
@@ -5,18 +5,27 @@ module Bitmex
5
5
  attr_reader :symbol
6
6
 
7
7
  # A new instance of Position
8
- # @param client [Bitmex::Client] the HTTP client
8
+ # @param rest [Bitmex::Rest] the HTTP rest
9
9
  # @param symbol [String] the symbol of the underlying position
10
- def initialize(client, symbol = 'XBTUSD')
11
- super client
10
+ def initialize(rest, websocket, symbol = 'XBTUSD')
11
+ super rest, websocket
12
12
  @symbol = symbol
13
13
  end
14
14
 
15
15
  # Get your positions
16
+ # @example Get all positions
17
+ # positions = client.positions.all
18
+ # @example Listen for positions changes
19
+ # client.positions.all do |position|
20
+ # puts position.inspect
21
+ # end
16
22
  # @return [Array] the list of positions
17
- def all
18
- client.get position_path, auth: true do |response|
19
- response_handler response
23
+ # @yield [Hash] the position
24
+ def all(&ablock)
25
+ if block_given?
26
+ websocket.listen position: nil, &ablock
27
+ else
28
+ rest.get position_path, auth: true
20
29
  end
21
30
  end
22
31
 
@@ -26,7 +35,7 @@ module Bitmex
26
35
  def isolate(enabled: true)
27
36
  path = position_path(:isolate)
28
37
  params = { symbol: symbol, enabled: enabled }
29
- client.post path, params: params do |response|
38
+ rest.post path, params: params do |response|
30
39
  response_handler response
31
40
  end
32
41
  end
@@ -39,7 +48,7 @@ module Bitmex
39
48
 
40
49
  path = position_path(:leverage)
41
50
  params = { symbol: symbol, leverage: leverage }
42
- client.post path, params: params do |response|
51
+ rest.post path, params: params do |response|
43
52
  response_handler response
44
53
  end
45
54
  end
@@ -50,7 +59,7 @@ module Bitmex
50
59
  def risk_limit(risk_limit)
51
60
  path = position_path(:riskLimit)
52
61
  params = { symbol: symbol, riskLimit: risk_limit }
53
- client.post path, params: params do |response|
62
+ rest.post path, params: params do |response|
54
63
  response_handler response
55
64
  end
56
65
  end
@@ -63,7 +72,7 @@ module Bitmex
63
72
  def transfer_margin(amount)
64
73
  path = position_path(:transferMargin)
65
74
  params = { symbol: symbol, amount: amount }
66
- client.post path, params: params do |response|
75
+ rest.post path, params: params do |response|
67
76
  response_handler response
68
77
  end
69
78
  end
@@ -71,7 +80,7 @@ module Bitmex
71
80
  private
72
81
 
73
82
  def position_path(action = '')
74
- client.base_path :position, action
83
+ rest.base_path :position, action
75
84
  end
76
85
  end
77
86
  end
@@ -1,32 +1,41 @@
1
1
  module Bitmex
2
2
  # Best Bid/Offer Snapshots & Historical Bins
3
- # TODO: looks like all quotes related methods are forbidden
3
+ # Looks like all REST API methods return '403 Forbidden' but Web socket API works just fine.
4
4
  # @author Iulian Costan
5
5
  class Quote < Base
6
6
  # Get all quotes
7
7
  # @!macro bitmex.filters
8
8
  # @return [Array] the quotes
9
- def all(filters = {})
10
- client.get quotes_path, params: filters do |response|
11
- response_handler response
9
+ # @yield [Hash] the quote data
10
+ def all(filters = {}, &ablock)
11
+ if block_given?
12
+ websocket.listen quote: filters[:symbol], &ablock
13
+ else
14
+ rest.get quotes_path, params: filters
12
15
  end
13
16
  end
14
17
 
15
18
  # Get previous quotes in time buckets
16
- # @param binSize ['1m','5m','1h','1d'] the interval to bucket by
19
+ # @param bin_size ['1m','5m','1h','1d'] the interval to bucket by
17
20
  # @!macro bitmex.filters
18
21
  # @return [Array] the quotes by bucket
19
- def bucketed(binSize = '1h', filters = {})
20
- params = filters.merge binSize: binSize
21
- client.get quotes_path(:bucketed), params: params do |response|
22
- response_handler response
22
+ # @yield [Hash] the quote data
23
+ def bucketed(bin_size = '1h', filters = {}, &ablock)
24
+ check_binsize bin_size
25
+
26
+ if block_given?
27
+ topic = { "quoteBin#{bin_size}": filters[:symbol] }
28
+ websocket.listen topic, &ablock
29
+ else
30
+ params = filters.merge binSize: bin_size
31
+ rest.get quotes_path(:bucketed), params: params
23
32
  end
24
33
  end
25
34
 
26
35
  private
27
36
 
28
37
  def quotes_path(action = '')
29
- client.base_path :quote, action
38
+ rest.base_path :quote, action
30
39
  end
31
40
  end
32
41
  end
@@ -0,0 +1,103 @@
1
+ module Bitmex
2
+ # REST API support
3
+ # https://www.bitmex.com/api/explorer/
4
+ class Rest
5
+ include HTTParty
6
+ # logger ::Logger.new(STDOUT), :debug, :curl
7
+
8
+ attr_reader :host, :api_key, :api_secret
9
+
10
+ # Create new rest instance
11
+ # @param host [String] the underlying host to connect to
12
+ # @param api_key [String] the api key
13
+ # @param api_secret [String] the api secret
14
+ # @return [Bitmex::Rest] the REST implementation
15
+ def initialize(host, api_key: nil, api_secret: nil)
16
+ @host = host
17
+ @api_key = api_key
18
+ @api_secret = api_secret
19
+ end
20
+
21
+ # Execute GET request
22
+ # @param path [String] either absolute or relative URI path
23
+ # @param params [Hash] extra parameters to pass to GET request
24
+ # @param auth [Boolean] if the request needs authentication
25
+ # @return [Hash, Array] response wrapped in either array or hash
26
+ # @yield [HTTParty::Response] the underlying response
27
+ def get(path, params: {}, auth: false, &ablock)
28
+ path = base_path(path) unless path.to_s.start_with?('/')
29
+
30
+ options = {}
31
+ options[:query] = params unless params.empty?
32
+ options[:headers] = rest_headers 'GET', path, '' if auth
33
+
34
+ response = self.class.get "#{domain_url}#{path}", options
35
+ block_given? ? yield(response) : response_handler(response)
36
+ end
37
+
38
+ def put(path, params: {}, auth: true, json: true)
39
+ body = json ? params.to_json.to_s : URI.encode_www_form(params)
40
+
41
+ options = {}
42
+ options[:body] = body
43
+ options[:headers] = rest_headers 'PUT', path, body, json: json if auth
44
+
45
+ response = self.class.put "#{domain_url}#{path}", options
46
+ block_given? ? yield(response) : response_handler(response)
47
+ end
48
+
49
+ def post(path, params: {}, auth: true, json: true)
50
+ body = json ? params.to_json.to_s : URI.encode_www_form(params)
51
+
52
+ options = {}
53
+ options[:body] = body
54
+ options[:headers] = rest_headers 'POST', path, body, json: json if auth
55
+
56
+ response = self.class.post "#{domain_url}#{path}", options
57
+ block_given? ? yield(response) : response_handler(response)
58
+ end
59
+
60
+ def delete(path, params: {}, auth: true, json: true)
61
+ body = json ? params.to_json.to_s : URI.encode_www_form(params)
62
+
63
+ options = {}
64
+ options[:body] = body
65
+ options[:headers] = rest_headers 'DELETE', path, body, json: json if auth
66
+
67
+ response = self.class.delete "#{domain_url}#{path}", options
68
+ block_given? ? yield(response) : response_handler(response)
69
+ end
70
+
71
+ def base_path(resource, action = '')
72
+ "/api/v1/#{resource}/#{action}"
73
+ end
74
+
75
+ def rest_headers(verb, path, body, json: true)
76
+ headers = headers verb, path, body
77
+ if json
78
+ headers['Content-Type'] = 'application/json'
79
+ else
80
+ headers['Content-Type'] = 'application/x-www-form-urlencoded'
81
+ end
82
+ headers
83
+ end
84
+
85
+ def headers(verb, path, body)
86
+ Bitmex.headers api_key, api_secret, verb, path, body
87
+ end
88
+
89
+ def response_handler(response)
90
+ raise Bitmex::ForbiddenError, response.body unless response.success?
91
+
92
+ if response.parsed_response.is_a? Array
93
+ response.to_a.map { |s| Bitmex::Mash.new s }
94
+ else
95
+ Bitmex::Mash.new response
96
+ end
97
+ end
98
+
99
+ def domain_url
100
+ "https://#{host}"
101
+ end
102
+ end
103
+ end
@@ -5,7 +5,7 @@ module Bitmex
5
5
  # Get exchange-wide and per-series turnover and volume statistics
6
6
  # @return [Array] the statistics
7
7
  def current
8
- client.get stats_path do |response|
8
+ rest.get stats_path do |response|
9
9
  response_handler response
10
10
  end
11
11
  end
@@ -13,7 +13,7 @@ module Bitmex
13
13
  # Get historical exchange-wide and per-series turnover and volume statistics
14
14
  # @return [Array] the history in XBT
15
15
  def history
16
- client.get stats_path(:history) do |response|
16
+ rest.get stats_path(:history) do |response|
17
17
  response_handler response
18
18
  end
19
19
  end
@@ -21,7 +21,7 @@ module Bitmex
21
21
  # Get a summary of exchange statistics in USD
22
22
  # @return [Array] the history in USD
23
23
  def history_usd
24
- client.get stats_path(:historyUSD) do |response|
24
+ rest.get stats_path(:historyUSD) do |response|
25
25
  response_handler response
26
26
  end
27
27
  end
@@ -29,7 +29,7 @@ module Bitmex
29
29
  private
30
30
 
31
31
  def stats_path(action = '')
32
- base_path :stats, action
32
+ rest.base_path :stats, action
33
33
  end
34
34
  end
35
35
  end
@@ -5,37 +5,48 @@ module Bitmex
5
5
  # Get all trades
6
6
  # @example Get first 10 traders starting Jan 1st for XBTUSD
7
7
  # client.trades.all symbol: 'XBTUSD', startTime: '2019-01-01', count: 10
8
+ # @example Listen to all XBTUSD trades
9
+ # client.trades.all symbol: 'XBTUSD' do |trade|
10
+ # puts trade.inspect
11
+ # end
8
12
  # @!macro bitmex.filters
9
13
  # @return [Array] the trades
10
- # @yield [trade] the trade
11
- def all(filters = {}, &callback)
14
+ # @yield [Hash] the trade
15
+ def all(filters = {}, &ablock)
12
16
  if block_given?
13
- # TODO: investigate eventmachine + faye
14
- EM.run { client.websocket.subscribe :trade, filters[:symbol], &callback }
17
+ websocket.listen trade: filters[:symbol], &ablock
15
18
  else
16
- client.get trade_path, params: filters do |response|
17
- response_handler response
18
- end
19
+ rest.get trade_path, params: filters
19
20
  end
20
21
  end
21
22
 
22
23
  # Get previous trades in time buckets
23
24
  # @example Get last hour in 2018 and first hour in 2019 in reverse order
24
25
  # client.trades.bucketed '1h', symbol: 'XBTUSD', endTime: Date.new(2019, 1, 1), count: 2, reverse: true
25
- # @param binSize ['1m','5m','1h','1d'] the interval to bucket by
26
+ # @example Listen to bucketed trades
27
+ # client.trades.bucketed '1h', symbol: 'XBTUSD' do |bucket|
28
+ # puts bucket.inspect
29
+ # end
30
+ # @param bin_size ['1m','5m','1h','1d'] the interval to bucket by
26
31
  # @!macro bitmex.filters
27
32
  # @return [Array] the trades by bucket
28
- def bucketed(binSize = '1h', filters = {})
29
- params = filters.merge binSize: binSize
30
- client.get trade_path(:bucketed), params: params do |response|
31
- response_handler response
33
+ # @yield [trade] the bucketed trade
34
+ def bucketed(bin_size = '1h', filters = {}, &ablock)
35
+ check_binsize bin_size
36
+
37
+ if block_given?
38
+ topic = { "tradeBin#{bin_size}": filters[:symbol] }
39
+ websocket.listen topic, &ablock
40
+ else
41
+ params = filters.merge binSize: bin_size
42
+ rest.get trade_path(:bucketed), params: params
32
43
  end
33
44
  end
34
45
 
35
46
  private
36
47
 
37
48
  def trade_path(action = '')
38
- base_path :trade, action
49
+ rest.base_path :trade, action
39
50
  end
40
51
  end
41
52
  end