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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +28 -13
- data/LICENSE +21 -0
- data/README.md +201 -24
- data/TODOs.org +10 -2
- data/bin/chat.rb +1 -1
- data/bitmex.gemspec +22 -23
- data/lib/bitmex.rb +15 -0
- data/lib/bitmex/apikey.rb +6 -6
- data/lib/bitmex/base.rb +13 -7
- data/lib/bitmex/chat.rb +18 -20
- data/lib/bitmex/client.rb +67 -196
- data/lib/bitmex/instrument.rb +10 -15
- data/lib/bitmex/order.rb +12 -13
- data/lib/bitmex/position.rb +20 -11
- data/lib/bitmex/quote.rb +19 -10
- data/lib/bitmex/rest.rb +103 -0
- data/lib/bitmex/stats.rb +4 -4
- data/lib/bitmex/trade.rb +24 -13
- data/lib/bitmex/user.rb +26 -48
- data/lib/bitmex/version.rb +1 -1
- data/lib/bitmex/websocket.rb +80 -30
- metadata +19 -17
data/lib/bitmex/instrument.rb
CHANGED
@@ -5,43 +5,38 @@ module Bitmex
|
|
5
5
|
# Get all instruments
|
6
6
|
# @!macro bitmex.filters
|
7
7
|
# @return [Array] all instruments
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
43
|
-
response_handler response
|
44
|
-
end
|
39
|
+
rest.get instrument_path('indices')
|
45
40
|
end
|
46
41
|
|
47
42
|
private
|
data/lib/bitmex/order.rb
CHANGED
@@ -4,8 +4,8 @@ module Bitmex
|
|
4
4
|
class Order < Base
|
5
5
|
attr_reader :orderID, :clOrdID
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
super
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
74
|
+
rest.base_path 'order', action
|
76
75
|
end
|
77
76
|
end
|
78
77
|
end
|
data/lib/bitmex/position.rb
CHANGED
@@ -5,18 +5,27 @@ module Bitmex
|
|
5
5
|
attr_reader :symbol
|
6
6
|
|
7
7
|
# A new instance of Position
|
8
|
-
# @param
|
8
|
+
# @param rest [Bitmex::Rest] the HTTP rest
|
9
9
|
# @param symbol [String] the symbol of the underlying position
|
10
|
-
def initialize(
|
11
|
-
super
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
83
|
+
rest.base_path :position, action
|
75
84
|
end
|
76
85
|
end
|
77
86
|
end
|
data/lib/bitmex/quote.rb
CHANGED
@@ -1,32 +1,41 @@
|
|
1
1
|
module Bitmex
|
2
2
|
# Best Bid/Offer Snapshots & Historical Bins
|
3
|
-
#
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
38
|
+
rest.base_path :quote, action
|
30
39
|
end
|
31
40
|
end
|
32
41
|
end
|
data/lib/bitmex/rest.rb
ADDED
@@ -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
|
data/lib/bitmex/stats.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/bitmex/trade.rb
CHANGED
@@ -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 [
|
11
|
-
def all(filters = {}, &
|
14
|
+
# @yield [Hash] the trade
|
15
|
+
def all(filters = {}, &ablock)
|
12
16
|
if block_given?
|
13
|
-
|
14
|
-
EM.run { client.websocket.subscribe :trade, filters[:symbol], &callback }
|
17
|
+
websocket.listen trade: filters[:symbol], &ablock
|
15
18
|
else
|
16
|
-
|
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
|
-
# @
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|