intrinio-realtime 4.2.0 → 5.0.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 +111 -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: c3beedb23f93b49b061ba476daa8520cb3f09cfde888eaddbd08fec263a39de3
4
+ data.tar.gz: e81ce551b2ce00694e0d7ef238b6b63d60b5deb365e20dc3583c8b7819a9eb7a
5
5
  SHA512:
6
- metadata.gz: 114ca0120fe8b58520217f933679519a79160014facb39b9a1668bc05682c9bd69aaab1cd4d8b53360152430b85cbb52057f724a83b78cbc3bd0ed85ea38655e
7
- data.tar.gz: 3d37a004b8603e5ed8ba2346f26520781098504fd265bb90ccd0cda7d638dec6114b9c114283950d1716170ce27952c7ce7e51b7b0aedf174cf5abf74fbcea3a
6
+ metadata.gz: c09c353f980bcd5bd4cd36687bcbe0cd87661b5f3b428c98299374f69d19e01c6408d107842e2b46c84d26f2dc429bd6278e04abc5b2890de444e33590361467
7
+ data.tar.gz: f7d2c9141ee70a4cff900669152d1e2cda68ffba54d5360c4f013e88866bea9a829a49e79f3ce0f82444bf2b3c49fb592c8285edf2da0bb3be88516345da1927
@@ -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,33 @@ 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
+
56
81
  def to_s
57
- [@symbol, @price, @size, @timestamp, @total_volume].join(",")
82
+ [@symbol, @price, @size, @timestamp, @total_volume, @subprovider, @market_center, @condition].join(",")
58
83
  end
59
84
  end
60
85
 
61
86
  class Quote
62
- def initialize(type, symbol, price, size, timestamp)
87
+ def initialize(type, symbol, price, size, timestamp, subprovider, market_center, condition)
63
88
  @type = type
64
89
  @symbol = symbol
65
90
  @price = price
66
91
  @size = size
67
92
  @timestamp = timestamp
93
+ @subprovider = subprovider
94
+ @market_center = market_center
95
+ @condition = condition
68
96
  end
69
97
 
70
98
  def type
@@ -87,8 +115,20 @@ module Intrinio
87
115
  @timestamp
88
116
  end
89
117
 
118
+ def subprovider
119
+ @subprovider
120
+ end
121
+
122
+ def market_center
123
+ @market_center
124
+ end
125
+
126
+ def condition
127
+ @condition
128
+ end
129
+
90
130
  def to_s
91
- [@symbol, @type, @price, @size, @timestamp].join(",")
131
+ [@symbol, @type, @price, @size, @timestamp, @subprovider, @market_center, @condition].join(",")
92
132
  end
93
133
  end
94
134
 
@@ -115,7 +155,7 @@ module Intrinio
115
155
  unless @provider
116
156
  @provider = REALTIME
117
157
  end
118
- raise "Provider must be 'REALTIME' or 'MANUAL'" unless PROVIDERS.include?(@provider)
158
+ raise "Provider must be 'REALTIME', 'DELAYED_SIP', 'NASDAQ_BASIC', or 'MANUAL'" unless PROVIDERS.include?(@provider)
119
159
 
120
160
  @ip_address = options[:ip_address]
121
161
  raise "Missing option ip_address while in MANUAL mode." if @provider == MANUAL and (@ip_address.nil? || @ip_address.empty?)
@@ -148,7 +188,6 @@ module Intrinio
148
188
 
149
189
  @ready = false
150
190
  @joined_channels = []
151
- @heartbeat_timer = nil
152
191
  @selfheal_timer = nil
153
192
  @selfheal_backoffs = Array.new(SELF_HEAL_BACKOFFS)
154
193
  @ws = nil
@@ -206,7 +245,6 @@ module Intrinio
206
245
  end
207
246
 
208
247
  def disconnect
209
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
210
248
  EM.cancel_timer(@selfheal_timer) if @selfheal_timer
211
249
  @ready = false
212
250
  @closing = true
@@ -257,38 +295,71 @@ module Intrinio
257
295
  data.map { |i| [sprintf('%02x',i)].pack('H2') }.join.unpack('e').first
258
296
  end
259
297
 
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)
298
+ def parse_subprovider(byte)
299
+ case byte
300
+ when 0
301
+ NO_SUBPROVIDER
302
+ when 1
303
+ CTA_A
304
+ when 2
305
+ CTA_B
306
+ when 3
307
+ UTP
308
+ when 4
309
+ OTC
310
+ when 5
311
+ NASDAQ_BASIC
312
+ when 6
313
+ IEX
314
+ else
315
+ IEX
316
+ end
267
317
  end
268
318
 
269
- def parse_quote(data, start_index, symbol_length, msg_type)
319
+ def parse_trade(data, start_index)
320
+ symbol_length = data[start_index + 2]
321
+ condition_length = data[start_index + 26 + symbol_length]
322
+ symbol = data[start_index + 3, symbol_length].map!{|c| c.chr}.join
323
+ price = parse_float32(data[start_index + 6 + symbol_length, 4])
324
+ size = parse_uint32(data[start_index + 10 + symbol_length, 4])
325
+ timestamp = parse_uint64(data[start_index + 14 + symbol_length, 8])
326
+ total_volume = parse_uint32(data[start_index + 22 + symbol_length, 4])
327
+ subprovider = parse_subprovider(data[start_index + 3 + symbol_length])
328
+ market_center = data[start_index + 4 + symbol_length, 2].pack("C*").encode!('UTF-8', 'UTF-16LE')
329
+ condition = if condition_length > 0 then data[start_index + 27 + symbol_length, condition_length].map!{|c| c.chr}.join else "" end
330
+
331
+ return Trade.new(symbol, price, size, timestamp, total_volume, subprovider, market_center, condition)
332
+ end
333
+
334
+ def parse_quote(data, start_index, msg_type)
335
+ symbol_length = data[start_index + 2]
336
+ condition_length = data[start_index + 22 + symbol_length]
270
337
  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)
338
+ symbol = data[start_index + 3, symbol_length].map!{|c| c.chr}.join
339
+ price = parse_float32(data[start_index + 6 + symbol_length, 4])
340
+ size = parse_uint32(data[start_index + 10 + symbol_length, 4])
341
+ timestamp = parse_uint64(data[start_index + 14 + symbol_length, 8])
342
+ subprovider = parse_subprovider(data[start_index + 3 + symbol_length])
343
+ market_center = data[start_index + 4 + symbol_length, 2].pack("C*").encode!('UTF-8', 'UTF-16LE')
344
+ condition = if condition_length > 0 then data[start_index + 23 + symbol_length, condition_length].map!{|c| c.chr}.join else "" end
345
+
346
+ return Quote.new(type, symbol, price, size, timestamp, subprovider, market_center, condition)
276
347
  end
277
348
 
278
349
  def handle_message(data, start_index)
279
350
  msg_type = data[start_index]
280
- symbol_length = data[start_index + 1]
351
+ msg_length = data[start_index + 1]
281
352
  case msg_type
282
353
  when 0 then
283
- trade = parse_trade(data, start_index, symbol_length)
354
+ trade = parse_trade(data, start_index)
284
355
  @on_trade.call(trade)
285
- return start_index + 22 + symbol_length
356
+ return start_index + msg_length
286
357
  when 1 || 2 then
287
- quote = parse_quote(data, start_index, symbol_length, msg_type)
358
+ quote = parse_quote(data, start_index, msg_type)
288
359
  @on_quote.call(quote)
289
- return start_index + 18 + symbol_length
360
+ return start_index + msg_length
290
361
  end
291
- return start_index
362
+ return start_index + msg_length
292
363
  end
293
364
 
294
365
  def handle_data
@@ -332,7 +403,7 @@ module Intrinio
332
403
  http.use_ssl = true if (auth_url.include?("https"))
333
404
  http.start
334
405
  request = Net::HTTP::Get.new(uri.request_uri)
335
- request.add_field("Client-Information", "IntrinioRealtimeRubySDKv4.2")
406
+ request.add_field(CLIENT_INFO_HEADER_KEY, CLIENT_INFO_HEADER_VALUE)
336
407
 
337
408
  unless @api_key
338
409
  request.basic_auth(@username, @password)
@@ -374,10 +445,10 @@ module Intrinio
374
445
 
375
446
  def socket_url
376
447
  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}"
448
+ 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}"
449
+ 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}"
450
+ 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}"
451
+ 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
452
  end
382
453
  end
383
454
 
@@ -400,17 +471,21 @@ module Intrinio
400
471
  @threads = []
401
472
  @stop = false
402
473
  @thread_quantity.times {@threads << Thread.new{handle_data}}
403
-
404
- @ws = ws = WebSocket::Client::Simple.connect(socket_url)
474
+
475
+ headers = {}
476
+ headers[:headers] = {}
477
+ headers[CLIENT_INFO_HEADER_KEY] = CLIENT_INFO_HEADER_VALUE
478
+ headers[MESSAGE_VERSION_HEADER_KEY] = MESSAGE_VERSION_HEADER_VALUE
479
+ @ws = ws = WebSocket::Client::Simple.connect(socket_url, headers)
480
+
405
481
  me.send :info, "Connection opening"
406
482
 
407
483
  ws.on :open do
408
484
  me.send :info, "Connection established"
409
485
  me.send :ready, true
410
- if [REALTIME, MANUAL].include?(me.send(:provider))
486
+ if PROVIDERS.include?(me.send(:provider))
411
487
  me.send :refresh_channels
412
488
  end
413
- me.send :start_heartbeat
414
489
  me.send :stop_self_heal
415
490
  end
416
491
 
@@ -468,24 +543,6 @@ module Intrinio
468
543
  debug "Current channels: #{@channels}"
469
544
  end
470
545
 
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
546
  def try_self_heal
490
547
  return if @closing
491
548
  debug "Attempting to self-heal"
@@ -493,7 +550,6 @@ module Intrinio
493
550
  time = @selfheal_backoffs.first
494
551
  @selfheal_backoffs.delete_at(0) if @selfheal_backoffs.count > 1
495
552
 
496
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
497
553
  EM.cancel_timer(@selfheal_timer) if @selfheal_timer
498
554
 
499
555
  @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.0.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: 2023-06-08 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.