bitfinex-rb 1.0.0 → 1.0.1

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 (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