intrinio-realtime 4.1.2 → 5.0.0

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/intrinio-realtime.rb +114 -55
  3. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53a8c5ee612a42b4a13773ac8fba1edc288747b925e196d8d286d1498088065c
4
- data.tar.gz: 60f158795dcf471b473145fd0c37ff403fa7b2f998711baad444678a3cbc34e0
3
+ metadata.gz: c3beedb23f93b49b061ba476daa8520cb3f09cfde888eaddbd08fec263a39de3
4
+ data.tar.gz: e81ce551b2ce00694e0d7ef238b6b63d60b5deb365e20dc3583c8b7819a9eb7a
5
5
  SHA512:
6
- metadata.gz: c29368d7cb0a9ab0d89b897b3128b972a7bb83d0e78a2395030dcbf91f66a01b8d46c80a1097d8b88c3f8bd59dfc2d5327c4143c2ef96af5817f68b36ffd80f8
7
- data.tar.gz: b5bad0ddec4388a482a6f769bf0e56b285d1b7760c2afe660bd47b78608efbd9c0c0d193dfeab661acb5d3ce52c93395707d169fe7358f1adb3d3a3487604d23
6
+ metadata.gz: c09c353f980bcd5bd4cd36687bcbe0cd87661b5f3b428c98299374f69d19e01c6408d107842e2b46c84d26f2dc429bd6278e04abc5b2890de444e33590361467
7
+ data.tar.gz: f7d2c9141ee70a4cff900669152d1e2cda68ffba54d5360c4f013e88866bea9a829a49e79f3ce0f82444bf2b3c49fb592c8285edf2da0bb3be88516345da1927
@@ -7,14 +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
- PROVIDERS = [REALTIME, MANUAL, DELAYED_SIP].freeze
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
21
+ PROVIDERS = [REALTIME, MANUAL, DELAYED_SIP, NASDAQ_BASIC].freeze
22
+ SUBPROVIDERS = [NO_SUBPROVIDER, CTA_A, CTA_B, UTP, OTC, NASDAQ_BASIC, IEX].freeze
16
23
  ASK = "Ask".freeze
17
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
18
29
 
19
30
  def self.connect(options, on_trade, on_quote)
20
31
  EM.run do
@@ -24,12 +35,15 @@ module Intrinio
24
35
  end
25
36
 
26
37
  class Trade
27
- def initialize(symbol, price, size, timestamp, total_volume)
38
+ def initialize(symbol, price, size, timestamp, total_volume, subprovider, market_center, condition)
28
39
  @symbol = symbol
29
40
  @price = price
30
41
  @size = size
31
42
  @timestamp = timestamp
32
43
  @total_volume = total_volume
44
+ @subprovider = subprovider
45
+ @market_center = market_center
46
+ @condition = condition
33
47
  end
34
48
 
35
49
  def symbol
@@ -52,18 +66,33 @@ module Intrinio
52
66
  @total_volume
53
67
  end
54
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
+
55
81
  def to_s
56
- [@symbol, @price, @size, @timestamp, @total_volume].join(",")
82
+ [@symbol, @price, @size, @timestamp, @total_volume, @subprovider, @market_center, @condition].join(",")
57
83
  end
58
84
  end
59
85
 
60
86
  class Quote
61
- def initialize(type, symbol, price, size, timestamp)
87
+ def initialize(type, symbol, price, size, timestamp, subprovider, market_center, condition)
62
88
  @type = type
63
89
  @symbol = symbol
64
90
  @price = price
65
91
  @size = size
66
92
  @timestamp = timestamp
93
+ @subprovider = subprovider
94
+ @market_center = market_center
95
+ @condition = condition
67
96
  end
68
97
 
69
98
  def type
@@ -86,8 +115,20 @@ module Intrinio
86
115
  @timestamp
87
116
  end
88
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
+
89
130
  def to_s
90
- [@symbol, @type, @price, @size, @timestamp].join(",")
131
+ [@symbol, @type, @price, @size, @timestamp, @subprovider, @market_center, @condition].join(",")
91
132
  end
92
133
  end
93
134
 
@@ -114,7 +155,7 @@ module Intrinio
114
155
  unless @provider
115
156
  @provider = REALTIME
116
157
  end
117
- 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)
118
159
 
119
160
  @ip_address = options[:ip_address]
120
161
  raise "Missing option ip_address while in MANUAL mode." if @provider == MANUAL and (@ip_address.nil? || @ip_address.empty?)
@@ -147,7 +188,6 @@ module Intrinio
147
188
 
148
189
  @ready = false
149
190
  @joined_channels = []
150
- @heartbeat_timer = nil
151
191
  @selfheal_timer = nil
152
192
  @selfheal_backoffs = Array.new(SELF_HEAL_BACKOFFS)
153
193
  @ws = nil
@@ -205,7 +245,6 @@ module Intrinio
205
245
  end
206
246
 
207
247
  def disconnect
208
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
209
248
  EM.cancel_timer(@selfheal_timer) if @selfheal_timer
210
249
  @ready = false
211
250
  @closing = true
@@ -256,38 +295,71 @@ module Intrinio
256
295
  data.map { |i| [sprintf('%02x',i)].pack('H2') }.join.unpack('e').first
257
296
  end
258
297
 
259
- def parse_trade(data, start_index, symbol_length)
260
- symbol = data[start_index + 2, symbol_length].map!{|c| c.chr}.join
261
- price = parse_float32(data[start_index + 2 + symbol_length, 4])
262
- size = parse_uint32(data[start_index + 6 + symbol_length, 4])
263
- timestamp = parse_uint64(data[start_index + 10 + symbol_length, 8])
264
- total_volume = parse_uint32(data[start_index + 18 + symbol_length, 4])
265
- 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
266
317
  end
267
318
 
268
- 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]
269
337
  type = case when msg_type == 1 then ASK when msg_type == 2 then BID end
270
- symbol = data[start_index + 2, symbol_length].map!{|c| c.chr}.join
271
- price = parse_float32(data[start_index + 2 + symbol_length, 4])
272
- size = parse_uint32(data[start_index + 6 + symbol_length, 4])
273
- timestamp = parse_uint64(data[start_index + 10 + symbol_length, 8])
274
- 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)
275
347
  end
276
348
 
277
349
  def handle_message(data, start_index)
278
350
  msg_type = data[start_index]
279
- symbol_length = data[start_index + 1]
351
+ msg_length = data[start_index + 1]
280
352
  case msg_type
281
353
  when 0 then
282
- trade = parse_trade(data, start_index, symbol_length)
354
+ trade = parse_trade(data, start_index)
283
355
  @on_trade.call(trade)
284
- return start_index + 22 + symbol_length
356
+ return start_index + msg_length
285
357
  when 1 || 2 then
286
- quote = parse_quote(data, start_index, symbol_length, msg_type)
358
+ quote = parse_quote(data, start_index, msg_type)
287
359
  @on_quote.call(quote)
288
- return start_index + 18 + symbol_length
360
+ return start_index + msg_length
289
361
  end
290
- return start_index
362
+ return start_index + msg_length
291
363
  end
292
364
 
293
365
  def handle_data
@@ -331,7 +403,7 @@ module Intrinio
331
403
  http.use_ssl = true if (auth_url.include?("https"))
332
404
  http.start
333
405
  request = Net::HTTP::Get.new(uri.request_uri)
334
- request.add_field("Client-Information", "IntrinioRealtimeRubySDKv4.1")
406
+ request.add_field(CLIENT_INFO_HEADER_KEY, CLIENT_INFO_HEADER_VALUE)
335
407
 
336
408
  unless @api_key
337
409
  request.basic_auth(@username, @password)
@@ -352,6 +424,7 @@ module Intrinio
352
424
  case @provider
353
425
  when REALTIME then url = "https://realtime-mx.intrinio.com/auth"
354
426
  when DELAYED_SIP then url = "https://realtime-delayed-sip.intrinio.com/auth"
427
+ when NASDAQ_BASIC then url = "https://realtime-nasdaq-basic.intrinio.com/auth"
355
428
  when MANUAL then url = "http://" + @ip_address + "/auth"
356
429
  end
357
430
 
@@ -372,9 +445,10 @@ module Intrinio
372
445
 
373
446
  def socket_url
374
447
  case @provider
375
- when REALTIME then "wss://realtime-mx.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}"
376
- when DELAYED_SIP then "wss://realtime-delayed-sip.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}"
377
- 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}"
378
452
  end
379
453
  end
380
454
 
@@ -397,17 +471,21 @@ module Intrinio
397
471
  @threads = []
398
472
  @stop = false
399
473
  @thread_quantity.times {@threads << Thread.new{handle_data}}
400
-
401
- @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
+
402
481
  me.send :info, "Connection opening"
403
482
 
404
483
  ws.on :open do
405
484
  me.send :info, "Connection established"
406
485
  me.send :ready, true
407
- if [REALTIME, MANUAL].include?(me.send(:provider))
486
+ if PROVIDERS.include?(me.send(:provider))
408
487
  me.send :refresh_channels
409
488
  end
410
- me.send :start_heartbeat
411
489
  me.send :stop_self_heal
412
490
  end
413
491
 
@@ -465,24 +543,6 @@ module Intrinio
465
543
  debug "Current channels: #{@channels}"
466
544
  end
467
545
 
468
- def start_heartbeat
469
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
470
- @heartbeat_timer = EM.add_periodic_timer(HEARTBEAT_TIME) do
471
- if msg = heartbeat_msg()
472
- @ws.send(msg)
473
- debug "Heartbeat #{msg}"
474
- end
475
- end
476
- end
477
-
478
- def heartbeat_msg
479
- ""
480
- end
481
-
482
- def stop_heartbeat
483
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
484
- end
485
-
486
546
  def try_self_heal
487
547
  return if @closing
488
548
  debug "Attempting to self-heal"
@@ -490,7 +550,6 @@ module Intrinio
490
550
  time = @selfheal_backoffs.first
491
551
  @selfheal_backoffs.delete_at(0) if @selfheal_backoffs.count > 1
492
552
 
493
- EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
494
553
  EM.cancel_timer(@selfheal_timer) if @selfheal_timer
495
554
 
496
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.1.2
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-01-24 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.