bitfinex-rb 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ws/ws2.rb +225 -23
  3. metadata +81 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8606ee4dc2dfdab159222376cdd3819df830f8d1
4
- data.tar.gz: 8ccf0f428e9f06143427ad4f1d3c3fd16be6ee57
3
+ metadata.gz: e989c9158e8a4d3d1a733cefe023574dd3301ad4
4
+ data.tar.gz: 53f80b89a84bc0c758ffb5d41510201ca24b6175
5
5
  SHA512:
6
- metadata.gz: 28e9e659341d71b2e1592bbda88555ee800d6a16aac810e968567a7e1fef2724af81aa3b590e2e11f65f813ab202e9777fc2f716a191231758dbd67e744ead79
7
- data.tar.gz: 1acf7d6bc3363c637d1ff6bbdd3ef25531f5ade7ced8acfc7a6ca75a88778681ae9d1900d8f0ce2d66b3cd56053217b131e13301e54fbf812d1244e63f152dae
6
+ metadata.gz: c36be632f126916c680fb2abdeafb0f5c629efbc559ff16521786f511899195150efb5eccc2f9390c6e825c379d8eddef52a3dce3330b90249407c28a7af91b4
7
+ data.tar.gz: 6e3cd81ba239543b784edc77c459f7b26799a7655b4352c691c058641e0e517b635d41fccd3fe78d6eb6b21e430cfc9cd909cc9154eeb47c1b17d9e493f92e15
@@ -2,6 +2,7 @@ require 'faye/websocket'
2
2
  require 'eventmachine'
3
3
  require 'logger'
4
4
  require 'emittr'
5
+
5
6
  require_relative '../models/alert'
6
7
  require_relative '../models/balance_info'
7
8
  require_relative '../models/candle'
@@ -26,6 +27,13 @@ require_relative '../models/user_info'
26
27
  require_relative '../models/wallet'
27
28
 
28
29
  module Bitfinex
30
+ ###
31
+ # Implements version 2 of the Bitfinex WebSocket API, taking an evented
32
+ # approach. Incoming packets trigger event broadcasts with names relevant to
33
+ # the individual packets. Provides order manipulation methods that support
34
+ # callback blocks, which are called when the relevant confirmation
35
+ # notifications are received
36
+ ###
29
37
  class WSv2
30
38
  include Emittr::Events
31
39
 
@@ -39,6 +47,18 @@ module Bitfinex
39
47
  FLAG_SEQ_ALL = 65536, # enable sequencing
40
48
  FLAG_CHECKSUM = 131072 # enable OB checksums, top 25 levels per side
41
49
 
50
+ ###
51
+ # Creates a new instance of the class
52
+ #
53
+ # @param [Hash] params
54
+ # @param [string] params.url - connection URL
55
+ # @param [string] params.api_key
56
+ # @param [string] params.api_secret
57
+ # @param [boolean] params.manage_order_books - if true, order books are persisted internally, allowing for automatic checksum verification
58
+ # @param [boolean] params.transform - if true, full models are returned in place of array data
59
+ # @param [boolean] params.seq_audit - enables automatic seq number verification
60
+ # @param [boolean] params.checksum_audit - enables automatic OB checksum verification (requires manage_order_books)
61
+ ###
42
62
  def initialize (params = {})
43
63
  @l = Logger.new(STDOUT)
44
64
  @l.progname = 'ws2'
@@ -56,11 +76,12 @@ module Bitfinex
56
76
  @is_authenticated = false
57
77
  @channel_map = {}
58
78
  @order_books = {}
79
+ @pending_blocks = {}
59
80
  @last_pub_seq = nil
60
81
  @last_auth_seq = nil
61
82
  end
62
83
 
63
- def on_open (e)
84
+ def on_open (e) # :nodoc:
64
85
  @l.info 'client open'
65
86
  @is_open = true
66
87
  emit(:open)
@@ -69,7 +90,7 @@ module Bitfinex
69
90
  enable_ob_checksums if @checksum_audit
70
91
  end
71
92
 
72
- def on_message (e)
93
+ def on_message (e) # :nodoc:
73
94
  @l.info "recv #{e.data}"
74
95
 
75
96
  msg = JSON.parse(e.data)
@@ -78,12 +99,15 @@ module Bitfinex
78
99
  emit(:message, msg)
79
100
  end
80
101
 
81
- def on_close (e)
102
+ def on_close (e) # :nodoc:
82
103
  @l.info 'client closed'
83
104
  @is_open = false
84
105
  emit(:close)
85
106
  end
86
107
 
108
+ ###
109
+ # Opens the websocket client inside an eventmachine run block
110
+ ###
87
111
  def open!
88
112
  if @is_open
89
113
  raise Exception, 'already open'
@@ -106,11 +130,14 @@ module Bitfinex
106
130
  }
107
131
  end
108
132
 
133
+ ###
134
+ # Closes the websocket client
135
+ ###
109
136
  def close!
110
137
  @ws.close
111
138
  end
112
139
 
113
- def process_message (msg)
140
+ def process_message (msg) # :nodoc:
114
141
  if @seq_audit
115
142
  validate_message_seq(msg)
116
143
  end
@@ -122,7 +149,7 @@ module Bitfinex
122
149
  end
123
150
  end
124
151
 
125
- def validate_message_seq (msg)
152
+ def validate_message_seq (msg) # :nodoc:
126
153
  return unless @seq_audit
127
154
  return unless msg.kind_of?(Array)
128
155
  return unless msg.size > 2
@@ -169,7 +196,7 @@ module Bitfinex
169
196
  @last_auth_seq = auth_seq
170
197
  end
171
198
 
172
- def process_channel_message (msg)
199
+ def process_channel_message (msg) # :nodoc:
173
200
  if !@channel_map.include?(msg[0])
174
201
  @l.error "recv message on unknown channel: #{msg[0]}"
175
202
  return
@@ -200,7 +227,7 @@ module Bitfinex
200
227
  end
201
228
  end
202
229
 
203
- def handle_ticker_message (msg, chan)
230
+ def handle_ticker_message (msg, chan) # :nodoc:
204
231
  payload = msg[1]
205
232
 
206
233
  if chan['symbol'][0] === 't'
@@ -210,7 +237,7 @@ module Bitfinex
210
237
  end
211
238
  end
212
239
 
213
- def handle_trades_message (msg, chan)
240
+ def handle_trades_message (msg, chan) # :nodoc:
214
241
  if msg[1].kind_of?(Array)
215
242
  payload = msg[1]
216
243
  emit(:public_trades, chan['symbol'], @transform ? payload.map { |t| Models::PublicTrade.new(t) } : payload)
@@ -228,7 +255,7 @@ module Bitfinex
228
255
  end
229
256
  end
230
257
 
231
- def handle_candles_message (msg, chan)
258
+ def handle_candles_message (msg, chan) # :nodoc:
232
259
  payload = msg[1]
233
260
 
234
261
  if payload[0].kind_of?(Array)
@@ -238,7 +265,7 @@ module Bitfinex
238
265
  end
239
266
  end
240
267
 
241
- def handle_order_book_checksum_message (msg, chan)
268
+ def handle_order_book_checksum_message (msg, chan) # :nodoc:
242
269
  key = "#{chan['symbol']}:#{chan['prec']}:#{chan['len']}"
243
270
  emit(:checksum, chan['symbol'], msg)
244
271
 
@@ -257,7 +284,7 @@ module Bitfinex
257
284
  end
258
285
  end
259
286
 
260
- def handle_order_book_message (msg, chan)
287
+ def handle_order_book_message (msg, chan) # :nodoc:
261
288
  ob = msg[1]
262
289
 
263
290
  if @manage_obs
@@ -279,7 +306,57 @@ module Bitfinex
279
306
  emit(:order_book, chan['symbol'], data)
280
307
  end
281
308
 
282
- def handle_auth_message (msg, chan)
309
+ # Resolves/rejects any pending promise associated with the notification
310
+ def handle_notification_promises (n) # :nodoc:
311
+ type = n[1]
312
+ payload = n[4]
313
+ status = n[6]
314
+ msg = n[7]
315
+
316
+ return unless payload.kind_of?(Array) # expect order payload
317
+
318
+ case type
319
+ when 'on-req'
320
+ cid = payload[2]
321
+ k = "order-new-#{cid}"
322
+
323
+ return unless @pending_blocks.has_key?(k)
324
+
325
+ if status == 'SUCCESS'
326
+ @pending_blocks[k].call(@transform ? Models::Order.new(payload) : payload)
327
+ else
328
+ @pending_blocks[k].call(Exception.new("#{status}: #{msg}"))
329
+ end
330
+
331
+ @pending_blocks.delete(k)
332
+ when 'oc-req'
333
+ id = payload[0]
334
+ k = "order-cancel-#{id}"
335
+
336
+ return unless @pending_blocks.has_key?(k)
337
+
338
+ if status == 'SUCCESS'
339
+ @pending_blocks[k].call(payload)
340
+ else
341
+ @pending_blocks[k].call(Exception.new("#{status}: #{msg}"))
342
+ end
343
+
344
+ @pending_blocks.delete(k)
345
+ when 'ou-req'
346
+ id = payload[0]
347
+ k = "order-update-#{id}"
348
+
349
+ return unless @pending_blocks.has_key?(k)
350
+
351
+ if status == 'SUCCESS'
352
+ @pending_blocks[k].call(@transform ? Models::Order.new(payload) : payload)
353
+ else
354
+ @pending_blocks[k].call(Exception.new("#{status}: #{msg}"))
355
+ end
356
+ end
357
+ end
358
+
359
+ def handle_auth_message (msg, chan) # :nodoc:
283
360
  type = msg[1]
284
361
  return if type == 'hb'
285
362
  payload = msg[2]
@@ -287,6 +364,7 @@ module Bitfinex
287
364
  case type
288
365
  when 'n'
289
366
  emit(:notification, @transform ? Models::Notification.new(payload) : payload)
367
+ handle_notification_promises(payload)
290
368
  when 'te'
291
369
  emit(:trade_entry, @transform ? Models::Trade.new(payload) : payload)
292
370
  when 'tu'
@@ -348,6 +426,16 @@ module Bitfinex
348
426
  end
349
427
  end
350
428
 
429
+ ###
430
+ # Subscribes to the specified channel; params dictate the channel filter
431
+ #
432
+ # @param [string] channel - i.e. 'trades', 'candles', etc
433
+ # @param [Hash] params
434
+ # @param [string?] params.symbol
435
+ # @param [string?] params.prec - for order book channels
436
+ # @param [string?] params.len - for order book channels
437
+ # @param [string?] params.key - for candle channels
438
+ ###
351
439
  def subscribe (channel, params = {})
352
440
  @l.info 'subscribing to channel %s [%s]' % [channel, params]
353
441
  @ws.send(JSON.generate(params.merge({
@@ -356,18 +444,40 @@ module Bitfinex
356
444
  })))
357
445
  end
358
446
 
447
+ ###
448
+ # Subscribes to a ticker channel by symbol
449
+ #
450
+ # @param [string] sym - i.e. tBTCUSD
451
+ ###
359
452
  def subscribe_ticker (sym)
360
453
  subscribe('ticker', { :symbol => sym })
361
454
  end
362
455
 
456
+ ###
457
+ # Subscribes to a trades channel by symbol
458
+ #
459
+ # @param [string] sym - i.e. tBTCUSD
460
+ ###
363
461
  def subscribe_trades (sym)
364
462
  subscribe('trades', { :symbol => sym })
365
463
  end
366
464
 
465
+ ###
466
+ # Subscribes to a candle channel by key
467
+ #
468
+ # @param [string] key - i.e. trade:1m:tBTCUSD
469
+ ###
367
470
  def subscribe_candles (key)
368
471
  subscribe('candles', { :key => key })
369
472
  end
370
473
 
474
+ ###
475
+ # Subscribes to an order book channel
476
+ #
477
+ # @param [string] sym - i.e. tBTCUSD
478
+ # @param [string] prec - i.e. R0, P0, etc
479
+ # @param [string] len - i.e. 25, 100, etc
480
+ ###
371
481
  def subscribe_order_book (sym, prec, len)
372
482
  subscribe('book', {
373
483
  :symbol => sym,
@@ -376,7 +486,7 @@ module Bitfinex
376
486
  })
377
487
  end
378
488
 
379
- def process_event_message (msg)
489
+ def process_event_message (msg) # :nodoc:
380
490
  case msg['event']
381
491
  when 'auth'
382
492
  handle_auth_event(msg)
@@ -393,7 +503,7 @@ module Bitfinex
393
503
  end
394
504
  end
395
505
 
396
- def handle_auth_event (msg)
506
+ def handle_auth_event (msg) # :nodoc:
397
507
  if msg['status'] != 'OK'
398
508
  @l.error "auth failed: #{msg['message']}"
399
509
  return
@@ -406,7 +516,7 @@ module Bitfinex
406
516
  @l.info 'authenticated'
407
517
  end
408
518
 
409
- def handle_info_event (msg)
519
+ def handle_info_event (msg) # :nodoc:
410
520
  if msg.include?('version')
411
521
  if msg['version'] != 2
412
522
  close!
@@ -435,11 +545,11 @@ module Bitfinex
435
545
  end
436
546
  end
437
547
 
438
- def handle_error_event (msg)
548
+ def handle_error_event (msg) # :nodoc:
439
549
  @l.error msg
440
550
  end
441
551
 
442
- def handle_config_event (msg)
552
+ def handle_config_event (msg) # :nodoc:
443
553
  if msg['status'] != 'OK'
444
554
  @l.error "config failed: #{msg['message']}"
445
555
  else
@@ -448,18 +558,23 @@ module Bitfinex
448
558
  end
449
559
  end
450
560
 
451
- def handle_subscribed_event (msg)
561
+ def handle_subscribed_event (msg) # :nodoc:
452
562
  @l.info "subscribed to #{msg['channel']} [#{msg['chanId']}]"
453
563
  @channel_map[msg['chanId']] = msg
454
564
  emit(:subscribed, msg['chanId'])
455
565
  end
456
566
 
457
- def handle_unsubscribed_event (msg)
567
+ def handle_unsubscribed_event (msg) # :nodoc:
458
568
  @l.info "unsubscribed from #{msg['chanId']}"
459
569
  @channel_map.delete(msg['chanId'])
460
570
  emit(:unsubscribed, msg['chanId'])
461
571
  end
462
572
 
573
+ ###
574
+ # Enable an individual flag (see FLAG_* constants)
575
+ #
576
+ # @param [number] flag
577
+ ###
463
578
  def enable_flag (flag)
464
579
  return unless @is_open
465
580
 
@@ -469,19 +584,43 @@ module Bitfinex
469
584
  }))
470
585
  end
471
586
 
587
+ ###
588
+ # Checks if an individual flag is enabled (see FLAG_* constants)
589
+ #
590
+ # @param [number] flag
591
+ # @return [boolean] enabled
592
+ ###
472
593
  def is_flag_enabled (flag)
473
594
  (@enabled_flags & flag) == flag
474
595
  end
475
596
 
597
+ ###
598
+ # Sets the flag to activate sequence numbers on incoming packets
599
+ #
600
+ # @param [boolean] audit - if true (default), incoming seq numbers will be checked for consistency
601
+ ###
476
602
  def enable_sequencing (audit = true)
477
603
  @seq_audit = audit
478
604
  enable_flag(FLAG_SEQ_ALL)
479
605
  end
480
606
 
481
- def enable_ob_checksums
607
+ ###
608
+ # Sets the flag to activate order book checksums. Managed order books are
609
+ # required for automatic checksum audits.
610
+ #
611
+ # @param [boolean] audit - if true (default), incoming checksums will be compared to local checksums
612
+ ###
613
+ def enable_ob_checksums (audit = true)
614
+ @checksum_audit = audit
482
615
  enable_flag(FLAG_CHECKSUM)
483
616
  end
484
617
 
618
+ ###
619
+ # Authenticates the socket connection
620
+ #
621
+ # @param [number] calc
622
+ # @param [number] dms - dead man switch, active 4
623
+ ###
485
624
  def auth! (calc = 0, dms = 0)
486
625
  if @is_authenticated
487
626
  raise Exception, 'already authenticated'
@@ -502,15 +641,74 @@ module Bitfinex
502
641
  }))
503
642
  end
504
643
 
505
- def new_nonce
644
+ def new_nonce # :nodoc:
506
645
  Time.now.to_i.to_s
507
646
  end
508
647
 
509
- def sign (payload)
648
+ def sign (payload) # :nodoc:
510
649
  OpenSSL::HMAC.hexdigest('sha384', @api_secret, payload)
511
650
  end
512
651
 
513
- def submit_order (order)
652
+ ###
653
+ # Requests a calculation to be performed
654
+ # @see https://docs.bitfinex.com/v2/reference#ws-input-calc
655
+ #
656
+ # @param [Array] prefixes - i.e. ['margin_base']
657
+ ###
658
+ def request_calc (prefixes)
659
+ @ws.send(JSON.generate([0, 'calc', nil, prefixes.map { |p| [p] }]))
660
+ end
661
+
662
+ ###
663
+ # Update an order with a changeset by ID
664
+ #
665
+ # @param [Hash] changes - must contain ID
666
+ # @param [Block] cb - triggered upon receipt of confirmation notification
667
+ ###
668
+ def update_order (changes, &cb)
669
+ id = changes[:id] || changes['id']
670
+ @ws.send(JSON.generate([0, 'ou', nil, changes]))
671
+
672
+ if !cb.nil?
673
+ @pending_blocks["order-update-#{id}"] = cb
674
+ end
675
+ end
676
+
677
+ ###
678
+ # Cancel an order by ID
679
+ #
680
+ # @param [Hash|Array|Order|number] order - must contain or be ID
681
+ # @param [Block] cb - triggered upon receipt of confirmation notification
682
+ ###
683
+ def cancel_order (order, &cb)
684
+ return if !@is_authenticated
685
+
686
+ if order.is_a?(Numeric)
687
+ id = order
688
+ elsif order.is_a?(Array)
689
+ id = order[0]
690
+ elsif order.instance_of?(Models::Order)
691
+ id = order.id
692
+ elsif order.kind_of?(Hash)
693
+ id = order[:id] || order['id']
694
+ else
695
+ raise Exception, 'tried to cancel order with invalid ID'
696
+ end
697
+
698
+ @ws.send(JSON.generate([0, 'oc', nil, { :id => id }]))
699
+
700
+ if !cb.nil?
701
+ @pending_blocks["order-cancel-#{id}"] = cb
702
+ end
703
+ end
704
+
705
+ ###
706
+ # Submit a new order
707
+ #
708
+ # @param [Hash|Array|Order] order
709
+ # @param [Block] cb - triggered upon receipt of confirmation notification
710
+ ###
711
+ def submit_order (order, &cb)
514
712
  return if !@is_authenticated
515
713
 
516
714
  if order.kind_of?(Array)
@@ -524,6 +722,10 @@ module Bitfinex
524
722
  end
525
723
 
526
724
  @ws.send(JSON.generate([0, 'on', nil, packet]))
725
+
726
+ if packet.has_key?(:cid) && !cb.nil?
727
+ @pending_blocks["order-new-#{packet[:cid]}"] = cb
728
+ end
527
729
  end
528
730
  end
529
731
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitfinex-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bitfinex
@@ -124,6 +124,86 @@ dependencies:
124
124
  - - ">="
125
125
  - !ruby/object:Gem::Version
126
126
  version: 0.12.2
127
+ - !ruby/object:Gem::Dependency
128
+ name: emittr
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: 0.1.0
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 0.1.0
137
+ type: :runtime
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: 0.1.0
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: 0.1.0
147
+ - !ruby/object:Gem::Dependency
148
+ name: dotenv
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: 2.5.0
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: 2.5.0
157
+ type: :runtime
158
+ prerelease: false
159
+ version_requirements: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "~>"
162
+ - !ruby/object:Gem::Version
163
+ version: 2.5.0
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: 2.5.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: faraday_adapter_socks
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.1.1
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: 0.1.1
177
+ type: :runtime
178
+ prerelease: false
179
+ version_requirements: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - "~>"
182
+ - !ruby/object:Gem::Version
183
+ version: 0.1.1
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: 0.1.1
187
+ - !ruby/object:Gem::Dependency
188
+ name: zlib
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: 1.0.0
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: 1.0.0
197
+ type: :runtime
198
+ prerelease: false
199
+ version_requirements: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - "~>"
202
+ - !ruby/object:Gem::Version
203
+ version: 1.0.0
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ version: 1.0.0
127
207
  description: Official Bitfinex API ruby wrapper
128
208
  email:
129
209
  - developers@bitfinex.com