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/TODOs.org
CHANGED
@@ -5,7 +5,8 @@
|
|
5
5
|
CLOSED: [2019-01-14] SCHEDULED: <2019-01-14 Fri>
|
6
6
|
** DONE auth for REST-API endpoints
|
7
7
|
CLOSED: [2019-01-16 Wed] SCHEDULED: <2019-01-15 Tue> DEADLINE: <2019-01-16 Wed>
|
8
|
-
**
|
8
|
+
** DONE auth for private subscription topics
|
9
|
+
CLOSED: [2019-02-01 Fri] SCHEDULED: <2019-02-01 Fri>
|
9
10
|
** move from rspec to minitest
|
10
11
|
** configure autorun
|
11
12
|
** hearbeat, ping/pong
|
@@ -18,8 +19,15 @@
|
|
18
19
|
** DONE orderbook, order resource
|
19
20
|
CLOSED: [2019-01-29 Tue] SCHEDULED: <2019-01-29 Tue>
|
20
21
|
** refactoring: use class methods when working with multiple entities
|
21
|
-
**
|
22
|
+
** bug: {"error":{"message":"Signature not valid.","name":"HTTPError"}} in user#executions
|
22
23
|
** DONE liquidation, leaderboard, insurance, instrument, funding, execution, chat, announcement
|
23
24
|
CLOSED: [2019-01-30 Wed] SCHEDULED: <2019-01-30 Wed>
|
24
25
|
** DONE refactoring: extract websocket common logic
|
25
26
|
CLOSED: [2019-01-31 Thu] SCHEDULED: <2019-01-31 Thu>
|
27
|
+
** DONE refactoring: extract REST API implemenration
|
28
|
+
CLOSED: [2019-02-08 Fri] SCHEDULED: <2019-02-08 Fri>
|
29
|
+
** DONE add idiomatic websocket support for all resources
|
30
|
+
CLOSED: [2019-02-10 Sun 16:57] SCHEDULED: <2019-02-10 Sun>
|
31
|
+
** refactoring: make user model to extend base class as well
|
32
|
+
** DONE Release 1.0: API endpoints, examples in README
|
33
|
+
CLOSED: [2019-02-11 Mon] SCHEDULED: <2019-02-11 Mon>
|
data/bin/chat.rb
CHANGED
data/bitmex.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require 'bitmex/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = 'bitmex-api'
|
@@ -9,45 +8,45 @@ Gem::Specification.new do |spec|
|
|
9
8
|
spec.authors = ['Iulian Costan']
|
10
9
|
spec.email = ['iulian.costan@gmail.com']
|
11
10
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
11
|
+
spec.summary = 'Fully-featured, idiomatic Ruby library for BitMEX API'
|
12
|
+
spec.description = 'Fully-featured, idiomatic Ruby library for BitMEX API'
|
14
13
|
spec.homepage = 'https://github.com/icostan/bitmex-api-ruby'
|
15
14
|
|
16
15
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
16
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
17
|
if spec.respond_to?(:metadata)
|
19
|
-
spec.metadata[
|
18
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
20
19
|
|
21
|
-
spec.metadata[
|
22
|
-
spec.metadata[
|
23
|
-
spec.metadata[
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/icostan/bitmex-api-ruby.git'
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/icostan/bitmex-api-ruby/blob/master/CHANGELOG.md'
|
24
23
|
else
|
25
|
-
raise
|
26
|
-
|
24
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
25
|
+
'public gem pushes.'
|
27
26
|
end
|
28
27
|
|
29
28
|
# Specify which files should be added to the gem when it is released.
|
30
29
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
31
|
-
spec.files
|
30
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
32
31
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
33
32
|
end
|
34
|
-
spec.bindir =
|
33
|
+
spec.bindir = 'exe'
|
35
34
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
36
|
-
spec.require_paths = [
|
35
|
+
spec.require_paths = ['lib']
|
37
36
|
|
38
|
-
spec.add_dependency 'httparty'
|
39
|
-
spec.add_dependency 'hashie'
|
40
|
-
spec.add_dependency 'faye-websocket'
|
41
37
|
spec.add_dependency 'eventmachine'
|
38
|
+
spec.add_dependency 'faye-websocket'
|
39
|
+
spec.add_dependency 'hashie'
|
40
|
+
spec.add_dependency 'httparty'
|
42
41
|
|
43
|
-
spec.add_development_dependency 'bundler'
|
44
|
-
spec.add_development_dependency 'rake'
|
45
|
-
spec.add_development_dependency 'rspec'
|
46
42
|
spec.add_development_dependency 'bump'
|
43
|
+
spec.add_development_dependency 'bundler'
|
44
|
+
spec.add_development_dependency 'dotenv'
|
47
45
|
spec.add_development_dependency 'pry'
|
48
46
|
spec.add_development_dependency 'pry-doc'
|
49
|
-
spec.add_development_dependency '
|
50
|
-
spec.add_development_dependency 'dotenv'
|
51
|
-
spec.add_development_dependency 'rubocop'
|
47
|
+
spec.add_development_dependency 'rake'
|
52
48
|
spec.add_development_dependency 'reek'
|
49
|
+
spec.add_development_dependency 'rspec'
|
50
|
+
spec.add_development_dependency 'rubocop'
|
51
|
+
spec.add_development_dependency 'simplecov'
|
53
52
|
end
|
data/lib/bitmex.rb
CHANGED
@@ -16,10 +16,14 @@ require 'bitmex/instrument'
|
|
16
16
|
require 'bitmex/user'
|
17
17
|
require 'bitmex/apikey'
|
18
18
|
require 'bitmex/websocket'
|
19
|
+
require 'bitmex/rest'
|
19
20
|
|
20
21
|
# Bitmex module
|
21
22
|
module Bitmex
|
23
|
+
# Bitmex standard error
|
22
24
|
class Error < StandardError; end
|
25
|
+
# 403 Forbidden
|
26
|
+
class ForbiddenError < Error; end
|
23
27
|
|
24
28
|
TESTNET_HOST = 'testnet.bitmex.com'.freeze
|
25
29
|
MAINNET_HOST = 'www.bitmex.com'.freeze
|
@@ -31,4 +35,15 @@ module Bitmex
|
|
31
35
|
data = verb + path + expires.to_s + params
|
32
36
|
OpenSSL::HMAC.hexdigest 'SHA256', api_secret, data
|
33
37
|
end
|
38
|
+
|
39
|
+
def self.headers(api_key, api_secret, verb, path, body)
|
40
|
+
return {} unless api_key || api_secret
|
41
|
+
|
42
|
+
expires = Time.now.utc.to_i + 60
|
43
|
+
{
|
44
|
+
'api-expires' => expires.to_s,
|
45
|
+
'api-key' => api_key,
|
46
|
+
'api-signature' => Bitmex.signature(api_secret, verb, path, expires, body)
|
47
|
+
}
|
48
|
+
end
|
34
49
|
end
|
data/lib/bitmex/apikey.rb
CHANGED
@@ -5,17 +5,17 @@ module Bitmex
|
|
5
5
|
attr_reader :api_key
|
6
6
|
|
7
7
|
# Create new Apikey
|
8
|
-
# @param
|
8
|
+
# @param rest [Bitmex::Rest] the rest client
|
9
9
|
# @param api_key [String] public apikey
|
10
|
-
def initialize(
|
11
|
-
super
|
10
|
+
def initialize(rest, api_key = nil)
|
11
|
+
super rest
|
12
12
|
@api_key = api_key
|
13
13
|
end
|
14
14
|
|
15
15
|
# Get your API Keys
|
16
16
|
# @return [Array] the api keys
|
17
17
|
def all
|
18
|
-
|
18
|
+
rest.get apikey_path, auth: true do |response|
|
19
19
|
response_handler response
|
20
20
|
end
|
21
21
|
end
|
@@ -23,7 +23,7 @@ module Bitmex
|
|
23
23
|
# NOT SUPPORTED
|
24
24
|
# #return 403 Access Denied
|
25
25
|
def enable
|
26
|
-
|
26
|
+
rest.post apikey_path(:enable) do |response|
|
27
27
|
response_handler response
|
28
28
|
end
|
29
29
|
end
|
@@ -31,7 +31,7 @@ module Bitmex
|
|
31
31
|
# NOT SUPPORTED
|
32
32
|
# @return 403 Access Denied
|
33
33
|
def disable
|
34
|
-
|
34
|
+
rest.post apikey_path(:disable) do |response|
|
35
35
|
response_handler response
|
36
36
|
end
|
37
37
|
end
|
data/lib/bitmex/base.rb
CHANGED
@@ -2,25 +2,31 @@ module Bitmex
|
|
2
2
|
# Base class for all Bitmex models
|
3
3
|
# @author Iulian Costan
|
4
4
|
class Base
|
5
|
-
attr_reader :
|
5
|
+
attr_reader :rest, :websocket
|
6
6
|
|
7
|
-
# @param
|
8
|
-
|
9
|
-
|
7
|
+
# @param rest [Bitmex::Rest] the rest implementation
|
8
|
+
# @param websocket [Bitmex::Websocket] the websocket implementation
|
9
|
+
def initialize(rest, websocket = nil)
|
10
|
+
@rest = rest
|
11
|
+
@websocket = websocket
|
10
12
|
end
|
11
13
|
|
12
14
|
protected
|
13
15
|
|
16
|
+
def check_binsize(bin_size)
|
17
|
+
raise ArgumentError, 'invalid bin_size' unless %w[1m 5m 1h 1d].include? bin_size.to_s
|
18
|
+
end
|
19
|
+
|
14
20
|
def response_handler(response)
|
15
|
-
|
21
|
+
rest.response_handler response
|
16
22
|
end
|
17
23
|
|
18
|
-
def requires
|
24
|
+
def requires(arg, args)
|
19
25
|
raise ArgumentError, "argument '#{arg}' is required" unless args.include? arg
|
20
26
|
end
|
21
27
|
|
22
28
|
def base_path(resource, action)
|
23
|
-
|
29
|
+
rest.base_path resource, action
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
data/lib/bitmex/chat.rb
CHANGED
@@ -4,48 +4,46 @@ module Bitmex
|
|
4
4
|
class Chat < Base
|
5
5
|
# Get chat messages
|
6
6
|
# @example Get last 10 messages for channel 1
|
7
|
-
# messages = client.chat.messages
|
7
|
+
# messages = client.chat.messages channelID: 1, count: 10, reverse: true
|
8
8
|
# @param options [Hash] options to filter by
|
9
9
|
# @option options [Integer] :count (100) number of results to fetch.
|
10
10
|
# @option options [Integer] :start starting ID for results
|
11
11
|
# @option options [Boolean] :reverse If true, will sort results newest first
|
12
12
|
# @option options [Integer] :channelID Channel id. GET /chat/channels for ids. Leave blank for all.
|
13
13
|
# @return [Array] the messages
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
response_handler response
|
14
|
+
# @yield [Hash] the message
|
15
|
+
def messages(options = { count: 100, reverse: true }, &ablock)
|
16
|
+
if block_given?
|
17
|
+
websocket.listen chat: options[:channelID], &ablock
|
18
|
+
else
|
19
|
+
rest.get chat_path, params: options
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
23
|
# Get available channels
|
25
24
|
# @return [Array] the available channels
|
26
25
|
def channels
|
27
|
-
|
28
|
-
response_handler response
|
29
|
-
end
|
26
|
+
rest.get chat_path(:channels)
|
30
27
|
end
|
31
28
|
|
32
29
|
# Get connected users
|
33
30
|
# @return [Bitmex::Mash] an array with browser users in the first position and API users (bots) in the second position.
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
# @yield [Hash] the stats
|
32
|
+
def stats(&ablock)
|
33
|
+
if block_given?
|
34
|
+
websocket.listen connected: nil, &ablock
|
35
|
+
else
|
36
|
+
rest.get chat_path(:connected)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
40
|
# Send a chat message
|
41
41
|
# @param message [String] the message to send
|
42
42
|
# @param options [Hash] filter options
|
43
|
-
# @option options [Integer] :
|
44
|
-
def send(message, options = {
|
45
|
-
params = { message: message, channelID: options[:
|
46
|
-
|
47
|
-
response_handler response
|
48
|
-
end
|
43
|
+
# @option options [Integer] :channelID (1) channel to post to
|
44
|
+
def send(message, options = { channelID: 1 })
|
45
|
+
params = { message: message, channelID: options[:channelID] }
|
46
|
+
rest.post chat_path, params: params
|
49
47
|
end
|
50
48
|
|
51
49
|
private
|
data/lib/bitmex/client.rb
CHANGED
@@ -1,15 +1,6 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'faye/websocket'
|
3
|
-
require 'eventmachine'
|
4
|
-
require 'logger'
|
5
|
-
|
6
1
|
module Bitmex
|
2
|
+
# Main client interface for Bitmex API.
|
7
3
|
class Client
|
8
|
-
include HTTParty
|
9
|
-
# logger ::Logger.new(STDOUT), :debug, :curl
|
10
|
-
|
11
|
-
AUTHORIZATIONS = %w(apikey execution position globalnotification order leaderboard quote user userevent)
|
12
|
-
|
13
4
|
attr_reader :host, :api_key, :api_secret
|
14
5
|
|
15
6
|
# Create new client instance
|
@@ -24,45 +15,53 @@ module Bitmex
|
|
24
15
|
|
25
16
|
# Get site announcements
|
26
17
|
# @return [Array] the public announcements
|
27
|
-
def announcements
|
28
|
-
|
29
|
-
|
18
|
+
def announcements(&ablock)
|
19
|
+
if block_given?
|
20
|
+
websocket.listen announcement: nil, &ablock
|
21
|
+
else
|
22
|
+
rest.get :announcement
|
30
23
|
end
|
31
24
|
end
|
32
25
|
|
33
26
|
# Persistent API Keys for Developers
|
34
27
|
# @return [Bitmex::Apikey] the apikey instance
|
35
28
|
def apikey(api_key = nil)
|
36
|
-
Bitmex::Apikey.new
|
29
|
+
Bitmex::Apikey.new rest, api_key
|
37
30
|
end
|
38
31
|
|
39
32
|
# Trollbox Data
|
40
33
|
# @return [Bitmex::Chat] the chat instance
|
41
34
|
def chat
|
42
|
-
Bitmex::Chat.new
|
35
|
+
Bitmex::Chat.new rest, websocket
|
43
36
|
end
|
44
37
|
|
45
38
|
# Tradeable Contracts, Indices, and History
|
46
39
|
# @return [Bitmex::Instrument] the instrument model
|
47
40
|
def instrument
|
48
|
-
Bitmex::Instrument.new
|
41
|
+
Bitmex::Instrument.new rest, websocket
|
49
42
|
end
|
50
43
|
|
51
44
|
# Get funding history
|
52
45
|
# @!macro bitmex.filters
|
53
46
|
# @return [Array] the history
|
54
|
-
|
55
|
-
|
56
|
-
|
47
|
+
# @yield [Hash] the funding data
|
48
|
+
def funding(filters = {}, &ablock)
|
49
|
+
if block_given?
|
50
|
+
websocket.listen funding: nil, &ablock
|
51
|
+
else
|
52
|
+
rest.get :funding, params: filters
|
57
53
|
end
|
58
54
|
end
|
59
55
|
|
60
56
|
# Get insurance fund history
|
61
57
|
# @!macro bitmex.filters
|
62
58
|
# @return [Array] the history
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
# @yield [Hash] the insurance data
|
60
|
+
def insurance(filters = {}, &ablock)
|
61
|
+
if block_given?
|
62
|
+
websocket.listen insurance: nil, &ablock
|
63
|
+
else
|
64
|
+
rest.get :insurance, params: filters
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
@@ -70,17 +69,24 @@ module Bitmex
|
|
70
69
|
# @param ranking [notional ROE] the ranking type
|
71
70
|
# @return [Array] current leaders
|
72
71
|
def leaderboard(ranking = 'notional')
|
73
|
-
get
|
74
|
-
response_handler response
|
75
|
-
end
|
72
|
+
rest.get :leaderboard, params: { method: ranking }
|
76
73
|
end
|
77
74
|
|
78
75
|
# Get liquidation orders
|
76
|
+
# @example Get liquidations orders
|
77
|
+
# liquidations = client.liquidations symbol: 'XBTUSD'
|
78
|
+
# @example Listen for liquidation orders
|
79
|
+
# client.liquidations symbol: 'XBTUSD' do |liquidation|
|
80
|
+
# puts liquidation.inspect
|
81
|
+
# end
|
79
82
|
# @!macro bitmex.filters
|
80
83
|
# @return [Array] the liquidations
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
+
# @yield [Hash] the liquidation data
|
85
|
+
def liquidations(filters = {}, &ablock)
|
86
|
+
if block_given?
|
87
|
+
websocket.listen liquidation: filters[:symbol], &ablock
|
88
|
+
else
|
89
|
+
rest.get :liquidation, params: filters
|
84
90
|
end
|
85
91
|
end
|
86
92
|
|
@@ -88,7 +94,7 @@ module Bitmex
|
|
88
94
|
# @return [Bitmex::Order] the order model
|
89
95
|
def orders
|
90
96
|
# TODO: use class method
|
91
|
-
Bitmex::Order.new
|
97
|
+
Bitmex::Order.new rest, websocket
|
92
98
|
end
|
93
99
|
|
94
100
|
# Get an order by id
|
@@ -98,17 +104,27 @@ module Bitmex
|
|
98
104
|
def order(orderID: nil, clOrdID: nil)
|
99
105
|
raise ArgumentError, 'either orderID or clOrdID is required' if orderID.nil? && clOrdID.nil?
|
100
106
|
|
101
|
-
Bitmex::Order.new
|
107
|
+
Bitmex::Order.new rest, websocket, orderID, clOrdID
|
102
108
|
end
|
103
109
|
|
104
|
-
# Get current orderbook in vertical format
|
110
|
+
# Get current Level 2 orderbook in vertical format
|
111
|
+
# @example Get the first level
|
112
|
+
# orderbook = client.orderbook 'XBTUSD', depth: 1
|
113
|
+
# @example Listen to orderbook changes
|
114
|
+
# client.orderbook 'XBTUSD' do |orderbook|
|
115
|
+
# puts orderbook.inspect
|
116
|
+
# end
|
105
117
|
# @param symbol [String] instrument symbol, send a series (e.g. XBT) to get data for the nearest contract in that series
|
106
118
|
# @param depth [Integer] orderbook depth per side. send 0 for full depth.
|
107
119
|
# @return [Array] the orderbook
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
120
|
+
# @yield [Hash] the orderbook data
|
121
|
+
def orderbook(symbol, depth: 25, &ablock)
|
122
|
+
raise ArgumentError, 'symbol is required' unless symbol
|
123
|
+
|
124
|
+
if block_given?
|
125
|
+
websocket.listen orderBookL2: symbol, &ablock
|
126
|
+
else
|
127
|
+
rest.get 'orderbook/L2', params: { symbol: symbol, depth: depth }
|
112
128
|
end
|
113
129
|
end
|
114
130
|
|
@@ -116,209 +132,64 @@ module Bitmex
|
|
116
132
|
# @return [Array] the list of positions
|
117
133
|
def positions
|
118
134
|
# TODO: use class method
|
119
|
-
Bitmex::Position.new
|
135
|
+
Bitmex::Position.new rest, websocket
|
120
136
|
end
|
121
137
|
|
122
138
|
# Get an open position
|
123
139
|
# @param symbol [String] symbol of position
|
124
140
|
# @return [Bitmex::Position] open position
|
125
141
|
def position(symbol)
|
126
|
-
Bitmex::Position.new
|
142
|
+
Bitmex::Position.new rest, websocket, symbol
|
127
143
|
end
|
128
144
|
|
129
145
|
# Best Bid/Offer Snapshots & Historical Bins
|
130
146
|
# @return [Bitmex::Quote] the quote model
|
131
147
|
def quotes
|
132
148
|
# TODO: use class method
|
133
|
-
Bitmex::Quote.new
|
149
|
+
Bitmex::Quote.new rest, websocket
|
134
150
|
end
|
135
151
|
|
136
152
|
# Get model schemata for data objects returned by this AP
|
137
153
|
# @return [Hash] the schema
|
138
154
|
def schema
|
139
|
-
get
|
140
|
-
response_handler response
|
141
|
-
end
|
155
|
+
rest.get :schema
|
142
156
|
end
|
143
157
|
|
144
158
|
# Get settlement history
|
145
159
|
# @return [Array] the settlement history
|
146
|
-
|
147
|
-
|
148
|
-
|
160
|
+
# @yield [Hash] the settlement data
|
161
|
+
def settlements(&ablock)
|
162
|
+
if block_given?
|
163
|
+
websocket.listen settlement: nil, &ablock
|
164
|
+
else
|
165
|
+
rest.get :settlement
|
149
166
|
end
|
150
167
|
end
|
151
168
|
|
152
169
|
# Exchange statistics
|
153
170
|
# @return [Bitmex::Stats] the stats model
|
154
171
|
def stats
|
155
|
-
Bitmex::Stats.new
|
172
|
+
Bitmex::Stats.new rest
|
156
173
|
end
|
157
174
|
|
158
175
|
# Individual and bucketed trades
|
159
176
|
# @return [Bitmex::Trade] the trade model
|
160
177
|
def trades
|
161
|
-
Bitmex::Trade.new
|
178
|
+
Bitmex::Trade.new rest, websocket
|
162
179
|
end
|
163
180
|
|
164
181
|
# Account operations
|
165
182
|
# @return [Bitmex::User] the user model
|
166
183
|
def user
|
167
|
-
Bitmex::User.new
|
168
|
-
end
|
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
|
-
|
181
|
-
#
|
182
|
-
# Stop websocket listener
|
183
|
-
#
|
184
|
-
def stop
|
185
|
-
EM.stop_event_loop
|
184
|
+
Bitmex::User.new rest, websocket
|
186
185
|
end
|
187
186
|
|
188
187
|
def websocket
|
189
|
-
@websocket ||= Websocket.new
|
190
|
-
end
|
191
|
-
|
192
|
-
# TODO: move these methods into rest client
|
193
|
-
def get(path, params: {}, auth: false, &ablock)
|
194
|
-
options = {}
|
195
|
-
options[:query] = params unless params.empty?
|
196
|
-
options[:headers] = headers 'GET', path, '' if auth
|
197
|
-
|
198
|
-
response = self.class.get "#{domain_url}#{path}", options
|
199
|
-
yield response
|
200
|
-
end
|
201
|
-
|
202
|
-
def put(path, params: {}, auth: true, json: true)
|
203
|
-
body = json ? params.to_json.to_s : URI.encode_www_form(params)
|
204
|
-
|
205
|
-
options = {}
|
206
|
-
options[:body] = body
|
207
|
-
options[:headers] = headers 'PUT', path, body, json: json if auth
|
208
|
-
|
209
|
-
response = self.class.put "#{domain_url}#{path}", options
|
210
|
-
yield response
|
211
|
-
end
|
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
|
-
|
249
|
-
private
|
250
|
-
|
251
|
-
def method_missing(m, *args, &ablock)
|
252
|
-
name = m.to_s.gsub '_', ''
|
253
|
-
params = args.first || {}
|
254
|
-
type = params&.delete :type
|
255
|
-
types = self.class.const_get "#{name.upcase}_ARGS"
|
256
|
-
check! type, types
|
257
|
-
|
258
|
-
params[:auth] = auth_required? name
|
259
|
-
|
260
|
-
execute name, type, params do |response|
|
261
|
-
# p response.body
|
262
|
-
if response.parsed_response.is_a? Array
|
263
|
-
response.to_a.map do |s|
|
264
|
-
Bitmex::Mash.new s
|
265
|
-
end
|
266
|
-
else
|
267
|
-
Bitmex::Mash.new response
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
def auth_required?(action)
|
273
|
-
AUTHORIZATIONS.include? action
|
274
|
-
end
|
275
|
-
|
276
|
-
def execute(endpoint, type, params, &ablock)
|
277
|
-
url = "#{rest_url}/#{endpoint}/#{type}"
|
278
|
-
path = "/api/v1/#{endpoint}/#{type}"
|
279
|
-
auth = params&.delete(:auth)
|
280
|
-
params = nil if params&.empty?
|
281
|
-
|
282
|
-
options = { query: params }
|
283
|
-
options[:headers] = headers 'GET', path, '' if auth
|
284
|
-
|
285
|
-
response = self.class.get url, options
|
286
|
-
fail response.body unless response.success?
|
287
|
-
yield response
|
288
|
-
end
|
289
|
-
|
290
|
-
def headers(verb, path, body, json: true)
|
291
|
-
raise 'api_key and api_secret are required' unless api_key || api_secret
|
292
|
-
|
293
|
-
expires = Time.now.utc.to_i + 60
|
294
|
-
headers = {
|
295
|
-
'api-expires' => expires.to_s,
|
296
|
-
'api-key' => api_key,
|
297
|
-
'api-signature' => Bitmex.signature(api_secret, verb, path, expires, body)
|
298
|
-
}
|
299
|
-
if json
|
300
|
-
headers['Content-Type'] = 'application/json'
|
301
|
-
else
|
302
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
303
|
-
end
|
304
|
-
headers
|
305
|
-
end
|
306
|
-
|
307
|
-
def check!(type, types)
|
308
|
-
return true if type.nil? or type == ''
|
309
|
-
raise ArgumentError, "invalid argument #{type}, only #{types} are supported" if !types.include?(type.to_s)
|
310
|
-
end
|
311
|
-
|
312
|
-
def rest_url
|
313
|
-
"https://#{host}/api/v1"
|
314
|
-
end
|
315
|
-
|
316
|
-
def domain_url
|
317
|
-
"https://#{host}"
|
188
|
+
@websocket ||= Websocket.new host, api_key: api_key, api_secret: api_secret
|
318
189
|
end
|
319
190
|
|
320
|
-
def
|
321
|
-
|
191
|
+
def rest
|
192
|
+
@rest ||= Rest.new host, api_key: api_key, api_secret: api_secret
|
322
193
|
end
|
323
194
|
end
|
324
195
|
end
|