bitmex-api 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|