kraken_client 0.2.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +7 -0
  4. data/Gemfile.lock +61 -0
  5. data/LICENSE.md +24 -0
  6. data/Rakefile +1 -0
  7. data/fixtures/vcr_cassettes/add_order.yml +41 -0
  8. data/fixtures/vcr_cassettes/assets.yml +45 -0
  9. data/fixtures/vcr_cassettes/assets_pairs.yml +45 -0
  10. data/fixtures/vcr_cassettes/balance.yml +41 -0
  11. data/fixtures/vcr_cassettes/cancel_order.yml +41 -0
  12. data/fixtures/vcr_cassettes/closed_orders.yml +134 -0
  13. data/fixtures/vcr_cassettes/ledgers.yml +41 -0
  14. data/fixtures/vcr_cassettes/open_orders.yml +43 -0
  15. data/fixtures/vcr_cassettes/order_book.yml +43 -0
  16. data/fixtures/vcr_cassettes/query_ledgers.yml +41 -0
  17. data/fixtures/vcr_cassettes/query_orders.yml +43 -0
  18. data/fixtures/vcr_cassettes/query_trades.yml +41 -0
  19. data/fixtures/vcr_cassettes/server_time.yml +44 -0
  20. data/fixtures/vcr_cassettes/spread.yml +43 -0
  21. data/fixtures/vcr_cassettes/ticker.yml +43 -0
  22. data/fixtures/vcr_cassettes/trade_balance.yml +41 -0
  23. data/fixtures/vcr_cassettes/trade_volume.yml +41 -0
  24. data/fixtures/vcr_cassettes/trades.yml +43 -0
  25. data/fixtures/vcr_cassettes/trades_history.yml +41 -0
  26. data/kraken_rb.gemspec +33 -0
  27. data/lib/configurable.rb +13 -0
  28. data/lib/configuration.rb +19 -0
  29. data/lib/exceptions.rb +6 -0
  30. data/lib/kraken_client/application.rb +45 -0
  31. data/lib/kraken_client/endpoints/base.rb +43 -0
  32. data/lib/kraken_client/endpoints/private.rb +45 -0
  33. data/lib/kraken_client/endpoints/public.rb +33 -0
  34. data/lib/kraken_client/requests/base.rb +35 -0
  35. data/lib/kraken_client/requests/content/body.rb +18 -0
  36. data/lib/kraken_client/requests/content/header.rb +61 -0
  37. data/lib/kraken_client/requests/get.rb +13 -0
  38. data/lib/kraken_client/requests/limiter.rb +78 -0
  39. data/lib/kraken_client/requests/post.rb +38 -0
  40. data/lib/kraken_client.rb +15 -0
  41. data/lib/version.rb +3 -0
  42. data/readme.md +280 -0
  43. data/spec/requests/private_spec.rb +104 -0
  44. data/spec/requests/public_spec.rb +57 -0
  45. metadata +229 -0
@@ -0,0 +1,18 @@
1
+ module KrakenClient
2
+ module Requests
3
+ module Content
4
+ class Body
5
+
6
+ attr_reader :post_data
7
+
8
+ def initialize(post_data)
9
+ @post_data = post_data
10
+ end
11
+
12
+ def call
13
+ post_data
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,61 @@
1
+ module KrakenClient
2
+ module Requests
3
+ module Content
4
+ class Header
5
+
6
+ attr_accessor :config, :endpoint_name, :options, :url
7
+
8
+ def initialize(config, endpoint_name, options, url)
9
+ @config = config
10
+ @endpoint_name = endpoint_name
11
+ @url = url
12
+
13
+ @options = options
14
+ @options[:nonce] = nonce
15
+ end
16
+
17
+ def call
18
+ {
19
+ 'API-Key' => config.api_key,
20
+ 'API-Sign' => generate_signature,
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ ## Security
27
+
28
+ # Generate a 64-bit nonce where the 48 high bits come directly from the current
29
+ # timestamp and the low 16 bits are pseudorandom. We can't use a pure [P]RNG here
30
+ # because the Kraken API requires every request within a given session to use a
31
+ # monotonically increasing nonce value. This approach splits the difference.
32
+ def nonce
33
+ high_bits = (Time.now.to_f * 10000).to_i << 16
34
+ low_bits = SecureRandom.random_number(2 ** 16) & 0xffff
35
+ (high_bits | low_bits).to_s
36
+ end
37
+
38
+ def encoded_options
39
+ uri = Addressable::URI.new
40
+ uri.query_values = options
41
+ uri.query
42
+ end
43
+
44
+ def generate_signature
45
+ key = Base64.decode64(config.api_secret)
46
+ message = generate_message
47
+ generate_hmac(key, message)
48
+ end
49
+
50
+ def generate_message
51
+ digest = OpenSSL::Digest.new('sha256', options[:nonce] + encoded_options).digest
52
+ url.split('.com').last + digest
53
+ end
54
+
55
+ def generate_hmac(key, message)
56
+ Base64.strict_encode64(OpenSSL::HMAC.digest('sha512', key, message))
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ module KrakenClient
2
+ module Requests
3
+ class Get < Base
4
+
5
+ def call(url, params, *)
6
+ super
7
+
8
+ HTTParty.get(url, query: params)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,78 @@
1
+ module KrakenClient
2
+ module Requests
3
+ class Limiter
4
+
5
+ attr_reader :config, :previous_timestamps, :endpoint_name, :current_count
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ @previous_timestamps = Time.now
10
+ @current_count = counter_total
11
+ end
12
+
13
+ def update(endpoint_name)
14
+ return unless config.limiter
15
+ @endpoint_name = endpoint_name
16
+
17
+ decrement_current_count
18
+ end
19
+
20
+ private
21
+
22
+ # Adds the number of seconds depending of the current tier value
23
+ def refresh_current_count
24
+ @current_count += ((Time.now - previous_timestamps) / seconds_to_decrement).to_int
25
+ @current_count = counter_total if current_count > counter_total
26
+
27
+ current_count
28
+ end
29
+
30
+ def decrement_current_count
31
+ @current_count -= value_to_decrement
32
+
33
+ if current_count < 0
34
+ sleep value_to_decrement
35
+
36
+ @current_count = value_to_decrement
37
+
38
+ update(endpoint_name)
39
+ else
40
+ refresh_current_count
41
+
42
+ @previous_timestamps = Time.now
43
+ end
44
+ end
45
+
46
+ def value_to_decrement
47
+
48
+ case endpoint_name
49
+ when 'Ledger' then 2
50
+ when 'TradeHistory' then 2
51
+ when 'AddOrder' then 0
52
+ when 'CancelOrder' then 0
53
+ else 1
54
+ end
55
+ end
56
+
57
+ def counter_total
58
+ @counter ||= case config.tier
59
+ when 0 then 10
60
+ when 1 then 10
61
+ when 2 then 10
62
+ when 3 then 20
63
+ when 4 then 20
64
+ end
65
+ end
66
+
67
+ def seconds_to_decrement
68
+ @decrement ||= case config.tier
69
+ when 0 then 5
70
+ when 1 then 5
71
+ when 2 then 5
72
+ when 3 then 2
73
+ when 4 then 1
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,38 @@
1
+ module KrakenClient
2
+ module Requests
3
+ class Post < Base
4
+
5
+ def call(url, endpoint_name, options)
6
+ super
7
+
8
+ @url = url
9
+ @endpoint_name = endpoint_name
10
+
11
+ response = HTTParty.post(url, params(options)).parsed_response
12
+ response['error'].empty? ? response['result'] : response['error']
13
+ end
14
+
15
+ private
16
+
17
+ def params(options = {})
18
+ params = {}
19
+
20
+ header_content = content_manager::Header.new(config, endpoint_name, options, url)
21
+
22
+ params[:headers] = header_content.call
23
+ params[:body] = body_content(header_content).call
24
+
25
+ params
26
+ end
27
+
28
+ def content_manager
29
+ KrakenClient::Requests::Content
30
+ end
31
+
32
+ def body_content(header_content)
33
+ content_manager::Body.new(header_content.send(:encoded_options))
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ require 'find'
2
+ require 'active_support/inflector'
3
+
4
+ Find.find('./lib').select { |p| /.*\.rb$/ =~ p }.each do |path|
5
+ require_relative("../" + path)
6
+ end
7
+
8
+ module KrakenClient
9
+ extend KrakenClient::Configurable
10
+
11
+ def self.load(params = {})
12
+ KrakenClient::Application.new(params)
13
+ end
14
+
15
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module KrakenClient
2
+ VERSION = "0.2.0"
3
+ end
data/readme.md ADDED
@@ -0,0 +1,280 @@
1
+
2
+ # **KrakenClient**
3
+
4
+ `KrakenClient` is a **Ruby** wrapper of the Kraken API. Kraken is a market exchange site serving those trading with Crypto-Currencies, such as **Bitcoin**.
5
+
6
+ This gem tends to be a replacement for [kraken_ruby](https://github.com/leishman/kraken_ruby), another wrapper, from which it is originally forked from.
7
+
8
+ It has every features of the latter gem, with improved readability, usability, maintenability, and speced using the Awesome [Spectus gem](https://github.com/fixrb/spectus).
9
+
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'kraken_client'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install kraken_ruby
26
+
27
+ And require it in your application:
28
+
29
+ irb(main):001:0> require 'kraken_client'
30
+ => true
31
+
32
+
33
+ ## Usage
34
+
35
+ ### Configuration ###
36
+ You can pass multiple variables that will be used in the gem.
37
+
38
+ ```ruby
39
+ KrakenClient.configure do |config|
40
+ config.api_key = ENV['KRAKEN_API_KEY']
41
+ config.api_secret = ENV['KRAKEN_API_SECRET']
42
+ config.base_uri = 'https://api.kraken.com'
43
+ config.api_version = 0
44
+ config.limiter = true
45
+ config.tier = 2
46
+ end
47
+ ```
48
+
49
+ By default, the default values are the ones described in the above example.
50
+
51
+ You can also pass any of those options inline when loading an instance of KrakenClient.
52
+
53
+ ```ruby
54
+ KrakenClient.load({base_uri: 'https://api.kraken.com', tier: 3}).config.tier
55
+ ```
56
+
57
+
58
+ **/!\\ Important Note /!\\** If you wish to use the Private Endpoints, you need to specify an API Key, or an exception will be raised.
59
+
60
+ ### Call Rate Limiter ###
61
+
62
+ Kraken has implemented a security which limit API users to make too much requests to the server. Each user has a counter (which is bigger depending on your tier). Each call increments your counter, and if your counter reaches its limit, you are blocked for 15 minutes.
63
+
64
+ To prevent this, `KrakenClient` has a safeguard, which queue the request which should in theory be blocked and is executed two second later.
65
+
66
+ If you want to disable this option, pass the `limiter` variable in the configuration to false.
67
+
68
+ ```ruby
69
+ KrakenClient.load({limiter: false}).config.tier
70
+ ```
71
+
72
+ Also, this limiter is activated by default. You would like to specify your tier, and `KrakenClient` will automatically make the required adjustments. The default `tier` is 2.
73
+
74
+ ```ruby
75
+ KrakenClient.load({tier: 3}).config.tier
76
+ ```
77
+
78
+ For more information, please consult the [Kraken official documentation](https://support.kraken.com/hc/en-us/articles/206548367-What-is-the-API-call-rate-limit-).
79
+
80
+ ### Requests ###
81
+
82
+ In all our examples henceforward, we consider this variable to be a loaded instance of `KrakenClient`
83
+
84
+ ```ruby
85
+ client = KrakenClient.load
86
+ ```
87
+
88
+ If you ever need to see the full documentation for the possible parameters, please take a look at the official [Kraken API docs](https://www.kraken.com/help/api).
89
+
90
+ A `KrakenClient::MissingParameter` exception will be raised along with the missing parameters if a required parameter is not passed.
91
+
92
+ ### *Public Endpoints* ###
93
+
94
+
95
+ ##### Server Time
96
+
97
+ This functionality is provided by Kraken to to aid in approximating the skew time between the server and client.
98
+
99
+ ```ruby
100
+ time = client.public.server_time
101
+
102
+ time.unixtime #=> 1393056191
103
+ time.rfc1123 #=> "Sat, 22 Feb 2014 08:28:04 GMT"
104
+ ```
105
+
106
+ ##### Asset Info
107
+
108
+ Returns the assets that can be traded on the exchange. This method can be passed ```info```, ```aclass``` (asset class), and ```asset``` options. An example below is given for each:
109
+
110
+ ```ruby
111
+ assets = client.public.assets
112
+ ```
113
+
114
+ ##### Asset Pairs
115
+
116
+ ```ruby
117
+ pairs = client.public.asset_pairs
118
+ ```
119
+
120
+ ##### Ticker Information
121
+
122
+ ```ruby
123
+ ticker_data = client.public.ticker('XLTCXXDG, ZUSDXXVN')
124
+ ```
125
+
126
+ ##### Order Book
127
+
128
+ Get market depth information for given asset pairs
129
+
130
+ ```ruby
131
+ depth_data = client.public.order_book('LTCXRP')
132
+ ```
133
+
134
+ ##### Trades
135
+
136
+ Get recent trades
137
+
138
+ ```ruby
139
+ trades = client.public.trades('LTCXRP')
140
+ ```
141
+
142
+ ##### Spread
143
+
144
+ Get spread data for a given asset pair
145
+
146
+ ```ruby
147
+ spread = client.public.spread('LTCXRP')
148
+ ```
149
+
150
+
151
+ #### Private Endpoints ####
152
+
153
+ ##### Balance
154
+
155
+ Get account balance for each asset
156
+ Note: Rates used for the floating valuation is the midpoint of the best bid and ask prices
157
+
158
+ ```ruby
159
+ balance = client.private.balance
160
+ ```
161
+
162
+ ##### Trade Balance
163
+
164
+ Get account trade balance
165
+
166
+ ```ruby
167
+ trade_balance = client.private.trade_balance
168
+ ```
169
+
170
+ ##### Open Orders
171
+
172
+ ```ruby
173
+ open_orders = client.private.open_orders
174
+ ```
175
+
176
+ ##### Closed Orders
177
+
178
+ ```ruby
179
+ closed_orders = client.private.closed_orders
180
+ ```
181
+
182
+ ##### Query Orders
183
+
184
+ **Input:** Comma delimited list of transaction ids (txid)
185
+
186
+ See all orders
187
+
188
+ ```ruby
189
+ orders = client.private.query_orders(txid: ids)
190
+ ```
191
+
192
+ ##### Trades History
193
+
194
+ Get array of all trades
195
+
196
+ ```ruby
197
+ trades = client.private.trade_history
198
+ ```
199
+
200
+ ##### Query Trades
201
+
202
+ **Input:** Comma delimited list of transaction ids (txid)
203
+
204
+ See all orders
205
+
206
+ ```ruby
207
+ orders = client.private.query_orders(txid: ids)
208
+ ```
209
+
210
+ ##### Open Positions
211
+
212
+ **Input:** Comma delimited list of transaction (txid) ids
213
+
214
+ ```ruby
215
+ positions = client.private.open_positions(txid)
216
+ ```
217
+
218
+ ##### Ledgers Info
219
+
220
+ ```ruby
221
+ ledgers = client.private.ledgers
222
+ ```
223
+
224
+ ##### Query Ledgers
225
+
226
+ **Input:** Comma delimited list of ledger ids
227
+
228
+ ```ruby
229
+ ledgers = client.private.query_ledgers(id: ledger_ids)
230
+ ```
231
+
232
+ ##### Trade Volume
233
+
234
+ ```ruby
235
+ ledgers = client.private.trade_volume
236
+ ```
237
+
238
+ ##### Add Order
239
+
240
+ There are 4 required parameters for buying an order. The example below illustrates the most basic order. Please see the [Kraken documentation](https://www.kraken.com/help/api#add-standard-order) for the parameters required for more advanced order types.
241
+
242
+ ```ruby
243
+ # buying 0.01 XBT (bitcoin) for XRP (ripple) at market price
244
+ opts = {
245
+ pair: 'XBTXRP',
246
+ type: 'buy',
247
+ ordertype: 'market',
248
+ volume: 0.01
249
+ }
250
+
251
+ client.private.add_order(opts)
252
+ ```
253
+
254
+ ##### Cancel Order
255
+
256
+ ```ruby
257
+ client.private.cancel_order("UKIYSP-9VN27-AJWWYC")
258
+ ```
259
+
260
+
261
+ ## Credits
262
+
263
+ This gem has been made by [Sidney SISSAOUI (shideneyu)](https://github.com/shideneyu).
264
+
265
+ Special credits goes to [Alexander LEISHMAN](http://alexleishman.com/) and other [kraken_ruby](https://github.com/leishman/kraken_ruby) contributors for their gem, which helped me to have a nice base to begin **KrakenClient**. It would have been difficult for me to sign the requests if it wasn't thanks to their work.
266
+ If you want to be part of those credits, do not hesitate to contribute by doing some pull requests ;) !
267
+
268
+
269
+ ## Contributing
270
+
271
+ 1. Fork it ( https://github.com/[my-github-username]/swiffer/fork )
272
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
273
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
274
+ 4. Push to the branch (`git push origin my-new-feature`)
275
+ 5. Create a new Pull Request
276
+
277
+
278
+ ## License
279
+
280
+ See `LICENSE.md` file.
@@ -0,0 +1,104 @@
1
+ require_relative File.join '..', '..', 'lib', 'kraken_client'
2
+ require 'spectus'
3
+ require 'vcr'
4
+ require 'webmock'
5
+
6
+ include WebMock::API
7
+
8
+ VCR.configure do |config|
9
+ config.cassette_library_dir = "fixtures/vcr_cassettes"
10
+ config.hook_into :webmock # or :fakeweb
11
+ end
12
+
13
+ # Testing Private Endpoints
14
+ KrakenClient.configure do |config|
15
+ config.api_key = ENV['KRAKEN_API_KEY'] || 'COMPUTED'
16
+ config.api_secret = ENV['KRAKEN_API_SECRET'] ||'COMPUTED'
17
+ end
18
+
19
+ kraken = KrakenClient.load
20
+ client = kraken.private
21
+
22
+ # User Balance
23
+ VCR.use_cassette("balance") do
24
+ Spectus.this { client.balance.class }.MUST Equal: Hashie::Mash
25
+ end
26
+
27
+ # Trade Balance
28
+ VCR.use_cassette("trade_balance") do
29
+ Spectus.this { client.trade_balance.c }.MUST Eql: '0.0000'
30
+ end
31
+
32
+ # Open Orders
33
+ VCR.use_cassette("open_orders") do
34
+ Spectus.this { client.open_orders.open.class }.MUST Equal: Hashie::Mash
35
+ end
36
+
37
+ # Closed Orders
38
+ VCR.use_cassette("closed_orders") do
39
+ Spectus.this { client.closed_orders.closed.class }.MUST Equal: Hashie::Mash
40
+ end
41
+
42
+ # Query Orders
43
+ VCR.use_cassette("query_orders") do
44
+ Spectus.this do
45
+ order = client.query_orders({txid: 'OKRRJ6-MH3UH-DV6IKT'})
46
+ order['OKRRJ6-MH3UH-DV6IKT'].status
47
+ end.MUST Eql: 'canceled'
48
+ end
49
+
50
+ # Trades History
51
+ VCR.use_cassette("trades_history") do
52
+ Spectus.this { client.trades_history.trades.class }.MUST Equal: Hashie::Mash
53
+ end
54
+
55
+ # Query Trades
56
+ VCR.use_cassette("query_trades") do
57
+ Spectus.this do
58
+ order = client.query_trades({txid: 'THZPTW-BMF6X-VWMN5P'})
59
+ order['THZPTW-BMF6X-VWMN5P'].pair
60
+ end.MUST Eql: 'XETHZEUR'
61
+ end
62
+
63
+ # Open Positions
64
+ #CANT TEST, PARAMS DO NOT WORK
65
+
66
+ # Ledgers Info
67
+ VCR.use_cassette("ledgers") do
68
+ Spectus.this { client.ledgers.ledger.class }.MUST Equal: Hashie::Mash
69
+ end
70
+
71
+ # Query Ledgers
72
+ VCR.use_cassette("query_ledgers") do
73
+ Spectus.this do
74
+ ledger = client.query_ledgers(id: 'LRSNYS-DICDD-3QM34P')
75
+ ledger['LRSNYS-DICDD-3QM34P'].class
76
+ end.MUST Equal: Hashie::Mash
77
+ end
78
+
79
+ # Trade Volume
80
+ VCR.use_cassette("trade_volume") do
81
+ Spectus.this { client.trade_volume(pair: 'XETHZEUR').count }.MUST Equal: 4
82
+ end
83
+
84
+ # Add Order
85
+ VCR.use_cassette("add_order") do
86
+ Spectus.this do
87
+
88
+ opts = {
89
+ pair: 'ETHEUR',
90
+ type: 'buy',
91
+ ordertype: 'market',
92
+ volume: 0.01
93
+ }
94
+
95
+ client.add_order(opts).txid
96
+ end.MUST Eql: ['OEDIZV-VDAW3-RHLJVB']
97
+ end
98
+
99
+ # Cancel Order
100
+ VCR.use_cassette("cancel_order") do
101
+ Spectus.this do
102
+ client.cancel_order(txid: 'ODEC3J-QAMVD-NSF7XD').count
103
+ end.MUST Eql: 1
104
+ end
@@ -0,0 +1,57 @@
1
+ require_relative File.join '..', '..', 'lib', 'kraken_client'
2
+ require 'spectus'
3
+ require 'vcr'
4
+ require 'webmock'
5
+
6
+ include WebMock::API
7
+
8
+ VCR.configure do |config|
9
+ config.cassette_library_dir = "fixtures/vcr_cassettes"
10
+ config.hook_into :webmock # or :fakeweb
11
+ end
12
+
13
+ # Testing Public Endpoints
14
+ kraken = KrakenClient.load
15
+ client = kraken.public
16
+
17
+ # Server Time
18
+ VCR.use_cassette("server_time") do
19
+ kraken_time = DateTime.parse(client.server_time.rfc1123)
20
+ utc_time = Time.now.getutc
21
+ Spectus.this { kraken_time.day.class }.MUST Equal: Fixnum
22
+ Spectus.this { kraken_time.hour.class }.MUST Equal: Fixnum
23
+ end
24
+
25
+ # Assets
26
+ VCR.use_cassette("assets") do
27
+ Spectus.this { client.assets.XETH.aclass }.MUST Eql: 'currency'
28
+ end
29
+
30
+ # Assets Pairs
31
+ VCR.use_cassette("assets_pairs") do
32
+ Spectus.this { client.asset_pairs.XETHXXBT.altname }.MUST Eql: 'ETHXBT'
33
+ end
34
+
35
+ # Ticker
36
+ VCR.use_cassette("ticker") do
37
+ result = client.ticker(pair: 'XXBTZEUR, XXBTZGBP')
38
+ Spectus.this { result.XXBTZGBP.a.class }.MUST Equal: Array
39
+ end
40
+
41
+ # Order Book
42
+ VCR.use_cassette("order_book") do
43
+ order_book = client.order_book(pair: 'XXBTZEUR')
44
+ Spectus.this { order_book.XXBTZEUR.asks.class }.MUST Equal: Array
45
+ end
46
+
47
+ # Trades
48
+ VCR.use_cassette("trades") do
49
+ trades = client.trades(pair: 'XXBTZEUR')
50
+ Spectus.this { trades.XXBTZEUR.class }.MUST Equal: Array
51
+ end
52
+
53
+ # Spread
54
+ VCR.use_cassette("spread") do
55
+ spread = client.spread(pair: 'XXBTZEUR')
56
+ Spectus.this { spread.XXBTZEUR.class }.MUST Equal: Array
57
+ end