intrinio-realtime 4.2.0 → 5.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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/intrinio-realtime.rb +115 -55
  3. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5999d98e1df1a97e8d4a6f217a645b2a4cc83173df769f831b92a0e2fdaa03f0
4
- data.tar.gz: 5c49201fd0ea6140c1c8e538a6d9ac1b9ddcb53ba980493c3e1bffd0b3739866
3
+ metadata.gz: aa4f4dd35aeb0249722b61a5781edc9c7f621b0edfffb32b0ee7be771a3e3d2d
4
+ data.tar.gz: dcea71654614604c02f3194009386b09857637c6faa463aca3f6c507df625553
5
5
  SHA512:
6
- metadata.gz: 114ca0120fe8b58520217f933679519a79160014facb39b9a1668bc05682c9bd69aaab1cd4d8b53360152430b85cbb52057f724a83b78cbc3bd0ed85ea38655e
7
- data.tar.gz: 3d37a004b8603e5ed8ba2346f26520781098504fd265bb90ccd0cda7d638dec6114b9c114283950d1716170ce27952c7ce7e51b7b0aedf174cf5abf74fbcea3a
6
+ metadata.gz: 5a4180e823e6bd20357105dc3e40ad2753d81a109b5c661c76ac36d989f9de1b266c64188f6796ba5e345d33985bb503aab98a0798e7af3e31093477b4dbd3f2
7
+ data.tar.gz: e35bacfcda00abbbf89f369f1a9c8d100f658b29955987d76fadc47ac150a02776b68de2f9b0628378a30b3cb916072ddebce45169516842f1b8f8d2619bfe24
@@ -7,15 +7,25 @@ require 'websocket-client-simple'
7
7
 
8
8
  module Intrinio
9
9
  module Realtime
10
- HEARTBEAT_TIME = 3
11
10
  SELF_HEAL_BACKOFFS = [0, 100, 500, 1000, 2000, 5000].freeze
12
11
  REALTIME = "REALTIME".freeze
13
12
  MANUAL = "MANUAL".freeze
14
13
  DELAYED_SIP = "DELAYED_SIP".freeze
15
14
  NASDAQ_BASIC = "NASDAQ_BASIC".freeze
15
+ NO_SUBPROVIDER = "NONE".freeze
16
+ CTA_A = "CTA_A".freeze
17
+ CTA_B = "CTA_B".freeze
18
+ UTP = "UTP".freeze
19
+ OTC = "OTC".freeze
20
+ IEX = "IEX".freeze
16
21
  PROVIDERS = [REALTIME, MANUAL, DELAYED_SIP, NASDAQ_BASIC].freeze
22
+ SUBPROVIDERS = [NO_SUBPROVIDER, CTA_A, CTA_B, UTP, OTC, NASDAQ_BASIC, IEX].freeze
17
23
  ASK = "Ask".freeze
18
24
  BID = "Bid".freeze
25
+ CLIENT_INFO_HEADER_KEY = "Client-Information".freeze
26
+ CLIENT_INFO_HEADER_VALUE = "IntrinioRealtimeRubySDKv5.0".freeze
27
+ MESSAGE_VERSION_HEADER_KEY = "UseNewEquitiesFormat".freeze
28
+ MESSAGE_VERSION_HEADER_VALUE = "v2".freeze
19
29
 
20
30
  def self.connect(options, on_trade, on_quote)
21
31
  EM.run do
@@ -25,12 +35,15 @@ module Intrinio
25
35
  end
26
36
 
27
37
  class Trade
28
- def initialize(symbol, price, size, timestamp, total_volume)
38
+ def initialize(symbol, price, size, timestamp, total_volume, subprovider, market_center, condition)
29
39
  @symbol = symbol
30
40
  @price = price
31
41
  @size = size
32
42
  @timestamp = timestamp
33
43
  @total_volume = total_volume
44
+ @subprovider = subprovider
45
+ @market_center = market_center
46
+ @condition = condition
34
47
  end
35
48
 
36
49
  def symbol
@@ -53,18 +66,37 @@ module Intrinio
53
66
  @total_volume
54
67
  end
55
68
 
69
+ def subprovider
70
+ @subprovider
71
+ end
72
+
73
+ def market_center
74
+ @market_center
75
+ end
76
+
77
+ def condition
78
+ @condition
79
+ end
80
+
81
+ def is_darkpool
82
+ @market_center == nil || @market_center == 'D' || @market_center == 'E' || @market_center == "\0" || @market_center.empty?
83
+ end
84
+
56
85
  def to_s
57
- [@symbol, @price, @size, @timestamp, @total_volume].join(",")
86
+ [@symbol, @price, @size, @timestamp, @total_volume, @subprovider, @market_center, @condition].join(",")
58
87
  end
59
88
  end
60
89
 
61
90
  class Quote
62
- def initialize(type, symbol, price, size, timestamp)
91
+ def initialize(type, symbol, price, size, timestamp, subprovider, market_center, condition)
63
92
  @type = type
64
93
  @symbol = symbol
65
94
  @price = price
66
95
  @size = size
67
96
  @timestamp = timestamp
97
+ @subprovider = subprovider
98
+ @market_center = market_center
99
+ @condition = condition
68
100
  end
69
101
 
70
102
  def type
@@ -87,8 +119,20 @@ module Intrinio
87
119
  @timestamp
88
120
  end
89
121
 
122
+ def subprovider
123
+ @subprovider
124
+ end
125
+
126
+ def market_center
127
+ @market_center
128
+ end
129
+
130
+ def condition
131
+ @condition
132
+ end
133
+
90
134
  def to_s
91
- [@symbol, @type, @price, @size, @timestamp].join(",")
135
+ [@symbol, @type, @price, @size, @timestamp, @subprovider, @market_center, @condition].join(",")
92
136
  end
93
137
  end
94
138
 
@@ -115,7 +159,7 @@ module Intrinio
115
159
  unless @provider
116
160
  @provider = REALTIME
117
161
  end
118
- raise "Provider must be 'REALTIME' or 'MANUAL'" unless PROVIDERS.include?(@provider)
162
+ raise "Provider must be 'REALTIME', 'DELAYED_SIP', 'NASDAQ_BASIC', or 'MANUAL'" unless PROVIDERS.include?(@provider)
119
163
 
120
164
  @ip_address = options[:ip_address]
121
165
  raise "Missing option ip_address while in MANUAL mode." if @provider == MANUAL and (@ip_address.nil? || @ip_address.empty?)
@@ -148,7 +192,6 @@ module Intrinio
148
192
 
149
193
  @ready = false
150
194
  @joined_channels = []
151
- @heartbeat_timer = nil
152
195
  @selfheal_timer = nil
153
196
  @selfheal_backoffs = Array.new(SELF_HEAL_BACKOFFS)
154
197
  @ws = nil
@@ -206,7 +249,6 @@ module Intrinio
206
249
  end
207
250
 
208
251
  def disconnect
209
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
210
252
  EM.cancel_timer(@selfheal_timer) if @selfheal_timer
211
253
  @ready = false
212
254
  @closing = true
@@ -257,38 +299,71 @@ module Intrinio
257
299
  data.map { |i| [sprintf('%02x',i)].pack('H2') }.join.unpack('e').first
258
300
  end
259
301
 
260
- def parse_trade(data, start_index, symbol_length)
261
- symbol = data[start_index + 2, symbol_length].map!{|c| c.chr}.join
262
- price = parse_float32(data[start_index + 2 + symbol_length, 4])
263
- size = parse_uint32(data[start_index + 6 + symbol_length, 4])
264
- timestamp = parse_uint64(data[start_index + 10 + symbol_length, 8])
265
- total_volume = parse_uint32(data[start_index + 18 + symbol_length, 4])
266
- return Trade.new(symbol, price, size, timestamp, total_volume)
302
+ def parse_subprovider(byte)
303
+ case byte
304
+ when 0
305
+ NO_SUBPROVIDER
306
+ when 1
307
+ CTA_A
308
+ when 2
309
+ CTA_B
310
+ when 3
311
+ UTP
312
+ when 4
313
+ OTC
314
+ when 5
315
+ NASDAQ_BASIC
316
+ when 6
317
+ IEX
318
+ else
319
+ IEX
320
+ end
267
321
  end
268
322
 
269
- def parse_quote(data, start_index, symbol_length, msg_type)
323
+ def parse_trade(data, start_index)
324
+ symbol_length = data[start_index + 2]
325
+ condition_length = data[start_index + 26 + symbol_length]
326
+ symbol = data[start_index + 3, symbol_length].map!{|c| c.chr}.join
327
+ price = parse_float32(data[start_index + 6 + symbol_length, 4])
328
+ size = parse_uint32(data[start_index + 10 + symbol_length, 4])
329
+ timestamp = parse_uint64(data[start_index + 14 + symbol_length, 8])
330
+ total_volume = parse_uint32(data[start_index + 22 + symbol_length, 4])
331
+ subprovider = parse_subprovider(data[start_index + 3 + symbol_length])
332
+ market_center = data[start_index + 4 + symbol_length, 2].pack("C*").encode!('UTF-8', 'UTF-16LE')
333
+ condition = if condition_length > 0 then data[start_index + 27 + symbol_length, condition_length].map!{|c| c.chr}.join else "" end
334
+
335
+ return Trade.new(symbol, price, size, timestamp, total_volume, subprovider, market_center, condition)
336
+ end
337
+
338
+ def parse_quote(data, start_index, msg_type)
339
+ symbol_length = data[start_index + 2]
340
+ condition_length = data[start_index + 22 + symbol_length]
270
341
  type = case when msg_type == 1 then ASK when msg_type == 2 then BID end
271
- symbol = data[start_index + 2, symbol_length].map!{|c| c.chr}.join
272
- price = parse_float32(data[start_index + 2 + symbol_length, 4])
273
- size = parse_uint32(data[start_index + 6 + symbol_length, 4])
274
- timestamp = parse_uint64(data[start_index + 10 + symbol_length, 8])
275
- return Quote.new(type, symbol, price, size, timestamp)
342
+ symbol = data[start_index + 3, symbol_length].map!{|c| c.chr}.join
343
+ price = parse_float32(data[start_index + 6 + symbol_length, 4])
344
+ size = parse_uint32(data[start_index + 10 + symbol_length, 4])
345
+ timestamp = parse_uint64(data[start_index + 14 + symbol_length, 8])
346
+ subprovider = parse_subprovider(data[start_index + 3 + symbol_length])
347
+ market_center = data[start_index + 4 + symbol_length, 2].pack("C*").encode!('UTF-8', 'UTF-16LE')
348
+ condition = if condition_length > 0 then data[start_index + 23 + symbol_length, condition_length].map!{|c| c.chr}.join else "" end
349
+
350
+ return Quote.new(type, symbol, price, size, timestamp, subprovider, market_center, condition)
276
351
  end
277
352
 
278
353
  def handle_message(data, start_index)
279
354
  msg_type = data[start_index]
280
- symbol_length = data[start_index + 1]
355
+ msg_length = data[start_index + 1]
281
356
  case msg_type
282
357
  when 0 then
283
- trade = parse_trade(data, start_index, symbol_length)
358
+ trade = parse_trade(data, start_index)
284
359
  @on_trade.call(trade)
285
- return start_index + 22 + symbol_length
360
+ return start_index + msg_length
286
361
  when 1 || 2 then
287
- quote = parse_quote(data, start_index, symbol_length, msg_type)
362
+ quote = parse_quote(data, start_index, msg_type)
288
363
  @on_quote.call(quote)
289
- return start_index + 18 + symbol_length
364
+ return start_index + msg_length
290
365
  end
291
- return start_index
366
+ return start_index + msg_length
292
367
  end
293
368
 
294
369
  def handle_data
@@ -332,7 +407,7 @@ module Intrinio
332
407
  http.use_ssl = true if (auth_url.include?("https"))
333
408
  http.start
334
409
  request = Net::HTTP::Get.new(uri.request_uri)
335
- request.add_field("Client-Information", "IntrinioRealtimeRubySDKv4.2")
410
+ request.add_field(CLIENT_INFO_HEADER_KEY, CLIENT_INFO_HEADER_VALUE)
336
411
 
337
412
  unless @api_key
338
413
  request.basic_auth(@username, @password)
@@ -374,10 +449,10 @@ module Intrinio
374
449
 
375
450
  def socket_url
376
451
  case @provider
377
- when REALTIME then "wss://realtime-mx.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}"
378
- when DELAYED_SIP then "wss://realtime-delayed-sip.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}"
379
- when NASDAQ_BASIC then "wss://realtime-nasdaq-basic.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}"
380
- when MANUAL then "ws://" + @ip_address + "/socket/websocket?vsn=1.0.0&token=#{@token}"
452
+ when REALTIME then "wss://realtime-mx.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}&#{CLIENT_INFO_HEADER_KEY}=#{CLIENT_INFO_HEADER_VALUE}&#{MESSAGE_VERSION_HEADER_KEY}=#{MESSAGE_VERSION_HEADER_VALUE}"
453
+ when DELAYED_SIP then "wss://realtime-delayed-sip.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}&#{CLIENT_INFO_HEADER_KEY}=#{CLIENT_INFO_HEADER_VALUE}&#{MESSAGE_VERSION_HEADER_KEY}=#{MESSAGE_VERSION_HEADER_VALUE}"
454
+ when NASDAQ_BASIC then "wss://realtime-nasdaq-basic.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}&#{CLIENT_INFO_HEADER_KEY}=#{CLIENT_INFO_HEADER_VALUE}&#{MESSAGE_VERSION_HEADER_KEY}=#{MESSAGE_VERSION_HEADER_VALUE}"
455
+ when MANUAL then "ws://" + @ip_address + "/socket/websocket?vsn=1.0.0&token=#{@token}&#{CLIENT_INFO_HEADER_KEY}=#{CLIENT_INFO_HEADER_VALUE}&#{MESSAGE_VERSION_HEADER_KEY}=#{MESSAGE_VERSION_HEADER_VALUE}"
381
456
  end
382
457
  end
383
458
 
@@ -400,17 +475,21 @@ module Intrinio
400
475
  @threads = []
401
476
  @stop = false
402
477
  @thread_quantity.times {@threads << Thread.new{handle_data}}
403
-
404
- @ws = ws = WebSocket::Client::Simple.connect(socket_url)
478
+
479
+ headers = {}
480
+ headers[:headers] = {}
481
+ headers[CLIENT_INFO_HEADER_KEY] = CLIENT_INFO_HEADER_VALUE
482
+ headers[MESSAGE_VERSION_HEADER_KEY] = MESSAGE_VERSION_HEADER_VALUE
483
+ @ws = ws = WebSocket::Client::Simple.connect(socket_url, headers)
484
+
405
485
  me.send :info, "Connection opening"
406
486
 
407
487
  ws.on :open do
408
488
  me.send :info, "Connection established"
409
489
  me.send :ready, true
410
- if [REALTIME, MANUAL].include?(me.send(:provider))
490
+ if PROVIDERS.include?(me.send(:provider))
411
491
  me.send :refresh_channels
412
492
  end
413
- me.send :start_heartbeat
414
493
  me.send :stop_self_heal
415
494
  end
416
495
 
@@ -468,24 +547,6 @@ module Intrinio
468
547
  debug "Current channels: #{@channels}"
469
548
  end
470
549
 
471
- def start_heartbeat
472
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
473
- @heartbeat_timer = EM.add_periodic_timer(HEARTBEAT_TIME) do
474
- if msg = heartbeat_msg()
475
- @ws.send(msg)
476
- debug "Heartbeat #{msg}"
477
- end
478
- end
479
- end
480
-
481
- def heartbeat_msg
482
- ""
483
- end
484
-
485
- def stop_heartbeat
486
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
487
- end
488
-
489
550
  def try_self_heal
490
551
  return if @closing
491
552
  debug "Attempting to self-heal"
@@ -493,7 +554,6 @@ module Intrinio
493
554
  time = @selfheal_backoffs.first
494
555
  @selfheal_backoffs.delete_at(0) if @selfheal_backoffs.count > 1
495
556
 
496
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
497
557
  EM.cancel_timer(@selfheal_timer) if @selfheal_timer
498
558
 
499
559
  @selfheal_timer = EM.add_timer(time/1000) do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intrinio-realtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Intrinio
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-19 00:00:00.000000000 Z
11
+ date: 2024-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -78,7 +78,7 @@ homepage: https://github.com/intrinio/intrinio-realtime-ruby-sdk
78
78
  licenses:
79
79
  - GPL-3.0
80
80
  metadata: {}
81
- post_install_message:
81
+ post_install_message:
82
82
  rdoc_options: []
83
83
  require_paths:
84
84
  - lib
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  version: '0'
95
95
  requirements: []
96
96
  rubygems_version: 3.1.6
97
- signing_key:
97
+ signing_key:
98
98
  specification_version: 4
99
99
  summary: Intrinio provides real-time stock prices from its Multi-Exchange feed, via
100
100
  a two-way WebSocket connection.