intrinio-realtime 2.1.1 → 3.1.2

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 +5 -5
  2. data/lib/intrinio-realtime.rb +275 -132
  3. metadata +33 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d2f407c4a6d0106dd796298be25bc1b3ead868b5
4
- data.tar.gz: f56b5baffae68de9c2a4f4b9d400f7d8d40c0186
2
+ SHA256:
3
+ metadata.gz: 230f6b829df617dbc89057d3b413e8e72ab9f1ce54cbfd40962beba8649b7fc8
4
+ data.tar.gz: f4f9ddf1279d94b2533ff37badd573c99323db4ad03b513251a3c688ab196bee
5
5
  SHA512:
6
- metadata.gz: 8d565d209e22ee9528429f1f2c56797463dfaec55e1e2a386c50cbc3858b2bd02075fde2206e5c81ec083d3672a551a3f8c7f8b46b1fd8a34998eaed07517185
7
- data.tar.gz: 205ad51869711a39efa5c0234cc6a61258f8153a4a074f249afbbfc73d1c71d9589f2e12f71ccc6c3b84fd26ec9f5ec5fa1d8a7247dc8d4034eea57221f88e0d
6
+ metadata.gz: b07b66332b3e1ed14c506db140e7d3bd87d239c9e8868663a53442b76ec34178ee57add42bc13549e085cfbfad2cdf86927aa00f92b619a532feda517fdebb49
7
+ data.tar.gz: e1696ccdc4a6e4c037c84dd7f9e0ac5def100d974cdf286dad45a098a6cdf0007eed911237ae698b975f37941eddffb2e59947d0bab698ac2fddd78fd230671d
@@ -1,6 +1,7 @@
1
1
  require 'logger'
2
2
  require 'uri'
3
- require 'http'
3
+ #require 'http'
4
+ require 'net/http'
4
5
  require 'eventmachine'
5
6
  require 'websocket-client-simple'
6
7
 
@@ -8,27 +9,100 @@ module Intrinio
8
9
  module Realtime
9
10
  HEARTBEAT_TIME = 3
10
11
  SELF_HEAL_BACKOFFS = [0, 100, 500, 1000, 2000, 5000].freeze
11
- IEX = "iex".freeze
12
- QUODD = "quodd".freeze
13
- CRYPTOQUOTE = "cryptoquote".freeze
14
- PROVIDERS = [IEX, QUODD, CRYPTOQUOTE].freeze
12
+ REALTIME = "REALTIME".freeze
13
+ MANUAL = "MANUAL".freeze
14
+ PROVIDERS = [REALTIME, MANUAL].freeze
15
+ ASK = "Ask".freeze
16
+ BID = "Bid".freeze
15
17
 
16
- def self.connect(options, &b)
18
+ def self.connect(options, on_trade, on_quote)
17
19
  EM.run do
18
- client = ::Intrinio::Realtime::Client.new(options)
19
- client.on_quote(&b)
20
+ client = ::Intrinio::Realtime::Client.new(options, on_trade, on_quote)
20
21
  client.connect()
21
22
  end
22
23
  end
23
24
 
25
+ class Trade
26
+ def initialize(symbol, price, size, timestamp, total_volume)
27
+ @symbol = symbol
28
+ @price = price
29
+ @size = size
30
+ @timestamp = timestamp
31
+ @total_volume = total_volume
32
+ end
33
+
34
+ def symbol
35
+ @symbol
36
+ end
37
+
38
+ def price
39
+ @price
40
+ end
41
+
42
+ def size
43
+ @size
44
+ end
45
+
46
+ def timestamp
47
+ @timestamp
48
+ end
49
+
50
+ def total_volume
51
+ @total_volume
52
+ end
53
+
54
+ def to_s
55
+ [@symbol, @price, @size, @timestamp, @total_volume].join(",")
56
+ end
57
+ end
58
+
59
+ class Quote
60
+ def initialize(type, symbol, price, size, timestamp)
61
+ @type = type
62
+ @symbol = symbol
63
+ @price = price
64
+ @size = size
65
+ @timestamp = timestamp
66
+ end
67
+
68
+ def type
69
+ @type
70
+ end
71
+
72
+ def symbol
73
+ @symbol
74
+ end
75
+
76
+ def price
77
+ @price
78
+ end
79
+
80
+ def size
81
+ @size
82
+ end
83
+
84
+ def timestamp
85
+ @timestamp
86
+ end
87
+
88
+ def to_s
89
+ [@symbol, @type, @price, @size, @timestamp].join(",")
90
+ end
91
+ end
92
+
24
93
  class Client
25
94
 
26
- def initialize(options)
95
+ def initialize(options, on_trade, on_quote)
27
96
  raise "Options parameter is required" if options.nil? || !options.is_a?(Hash)
97
+ @stop = false
98
+ @messages = Queue.new
99
+ raise "Unable to create queue." if @messages.nil?
100
+ @on_trade = on_trade
101
+ @on_quote = on_quote
28
102
 
29
103
  @api_key = options[:api_key]
30
104
  raise "API Key was formatted invalidly." if @api_key && !valid_api_key?(@api_key)
31
-
105
+
32
106
  unless @api_key
33
107
  @username = options[:username]
34
108
  @password = options[:password]
@@ -36,7 +110,25 @@ module Intrinio
36
110
  end
37
111
 
38
112
  @provider = options[:provider]
39
- raise "Provider must be 'quodd' or 'iex'" unless PROVIDERS.include?(@provider)
113
+ unless @provider
114
+ @provider = REALTIME
115
+ end
116
+ raise "Provider must be 'REALTIME' or 'MANUAL'" unless PROVIDERS.include?(@provider)
117
+
118
+ @ip_address = options[:ip_address]
119
+ raise "Missing option ip_address while in MANUAL mode." if @provider == MANUAL and (@ip_address.nil? || @ip_address.empty?)
120
+
121
+ @trades_only = options[:trades_only]
122
+ if @trades_only.nil?
123
+ @trades_only = false
124
+ end
125
+
126
+ @thread_quantity = options[:threads]
127
+ unless @thread_quantity
128
+ @thread_quantity = 4
129
+ end
130
+
131
+ @threads = []
40
132
 
41
133
  @channels = []
42
134
  @channels = parse_channels(options[:channels]) if options[:channels]
@@ -52,7 +144,6 @@ module Intrinio
52
144
  @logger.level = Logger::INFO
53
145
  end
54
146
 
55
- @quotes = EventMachine::Channel.new
56
147
  @ready = false
57
148
  @joined_channels = []
58
149
  @heartbeat_timer = nil
@@ -93,11 +184,7 @@ module Intrinio
93
184
  debug "Leaving all channels"
94
185
  refresh_channels()
95
186
  end
96
-
97
- def on_quote(&b)
98
- @quotes.subscribe(&b)
99
- end
100
-
187
+
101
188
  def connect
102
189
  raise "Must be run from within an EventMachine run loop" unless EM.reactor_running?
103
190
  return warn("Already connected!") if @ready
@@ -124,22 +211,131 @@ module Intrinio
124
211
  @channels = []
125
212
  @joined_channels = []
126
213
  @ws.close() if @ws
214
+ @stop = true
215
+ sleep(2)
216
+ @threads.each { |thread|
217
+ if !thread.nil? && (!thread.pending_interrupt? || thread.status == "run" || thread.status == "Sleeping")
218
+ then thread.join(7)
219
+ elsif !thread.nil?
220
+ then thread.kill
221
+ end
222
+ }
223
+ @threads = []
224
+ @stop = false
127
225
  info "Connection closed"
128
226
  end
227
+
228
+ def on_trade(on_trade)
229
+ @on_trade = on_trade
230
+ end
231
+
232
+ def on_quote(on_quote)
233
+ @on_quote = on_quote
234
+ end
129
235
 
130
236
  private
131
-
237
+
238
+ def queue_message(message)
239
+ @messages.enq(message)
240
+ end
241
+
242
+ def parse_uint64(data)
243
+ data.map { |i| [sprintf('%02x',i)].pack('H2') }.join.unpack('Q<').first
244
+ end
245
+
246
+ def parse_int32(data)
247
+ data.map { |i| [sprintf('%02x',i)].pack('H2') }.join.unpack('l<').first
248
+ end
249
+
250
+ def parse_uint32(data)
251
+ data.map { |i| [sprintf('%02x',i)].pack('H2') }.join.unpack('V').first
252
+ end
253
+
254
+ def parse_trade(data, start_index, symbol_length)
255
+ symbol = data[start_index + 2, symbol_length].map!{|c| c.chr}.join
256
+ price = parse_int32(data[start_index + 2 + symbol_length, 4]).to_f / 10000.0
257
+ size = parse_uint32(data[start_index + 6 + symbol_length, 4])
258
+ timestamp = parse_uint64(data[start_index + 10 + symbol_length, 8])
259
+ total_volume = parse_uint32(data[start_index + 18 + symbol_length, 4])
260
+ return Trade.new(symbol, price, size, timestamp, total_volume)
261
+ end
262
+
263
+ def parse_quote(data, start_index, symbol_length, msg_type)
264
+ type = case when msg_type == 1 then ASK when msg_type == 2 then BID end
265
+ symbol = data[start_index + 2, symbol_length].map!{|c| c.chr}.join
266
+ price = parse_int32(data[start_index + 2 + symbol_length, 4]).to_f / 10000.0
267
+ size = parse_uint32(data[start_index + 6 + symbol_length, 4])
268
+ timestamp = parse_uint64(data[start_index + 10 + symbol_length, 8])
269
+ return Quote.new(type, symbol, price, size, timestamp)
270
+ end
271
+
272
+ def handle_message(data, start_index)
273
+ msg_type = data[start_index]
274
+ symbol_length = data[start_index + 1]
275
+ case msg_type
276
+ when 0 then
277
+ trade = parse_trade(data, start_index, symbol_length)
278
+ @on_trade.call(trade)
279
+ return start_index + 22 + symbol_length
280
+ when 1 || 2 then
281
+ quote = parse_quote(data, start_index, symbol_length, msg_type)
282
+ @on_quote.call(quote)
283
+ return start_index + 18 + symbol_length
284
+ end
285
+ return start_index
286
+ end
287
+
288
+ def handle_data
289
+ Thread.current.priority -= 1
290
+ me = self
291
+ pop = nil
292
+ until @stop do
293
+ begin
294
+ pop = nil
295
+ data = nil
296
+ pop = @messages.deq
297
+ unless pop.nil?
298
+ begin
299
+ data = pop.unpack('C*')
300
+ rescue StandardError => ex
301
+ me.send :error, "Error unpacking data from queue: #{ex} #{pop}"
302
+ next
303
+ end
304
+ if !data then me.send :error, "Cannot process data. Data is nil. #{pop}" end
305
+ start_index = 1
306
+ count = data[0]
307
+ # These are grouped (many) messages.
308
+ # The first byte tells us how many there are.
309
+ # From there, check the type and symbol length at index 0 of each chunk to know how many bytes each message has.
310
+ count.times {start_index = handle_message(data, start_index)}
311
+ end
312
+ if pop.nil? then sleep(0.1) end
313
+ rescue StandardError => e
314
+ me.send :error, "Error handling message from queue: #{e} #{pop} : #{data} ; count: #{count} ; start index: #{start_index}"
315
+ rescue Exception => e
316
+ #me.send :error, "General error handling message from queue: #{e} #{pop} : #{data} ; count: #{count} ; start index: #{start_index}"
317
+ end
318
+ end
319
+ end
320
+
132
321
  def refresh_token
133
322
  @token = nil
134
323
 
135
- if @api_key
136
- response = HTTP.get(auth_url)
137
- else
138
- response = HTTP.basic_auth(:user => @username, :pass => @password).get(auth_url)
324
+ uri = URI.parse(auth_url)
325
+ http = Net::HTTP.new(uri.host, uri.port)
326
+ http.use_ssl = true if (auth_url.include?("https"))
327
+ http.start
328
+ request = Net::HTTP::Get.new(uri.request_uri)
329
+ request.add_field("Client-Information", "IntrinioRealtimeRubySDKv3.1")
330
+
331
+ unless @api_key
332
+ request.basic_auth(@username, @password)
139
333
  end
140
334
 
141
- return fatal("Unable to authorize") if response.status == 401
142
- return fatal("Could not get auth token") if response.status != 200
335
+ response = http.request(request)
336
+
337
+ return fatal("Unable to authorize") if response.code == "401"
338
+ return fatal("Could not get auth token") if response.code != "200"
143
339
 
144
340
  @token = response.body
145
341
  debug "Token refreshed"
@@ -149,9 +345,8 @@ module Intrinio
149
345
  url = ""
150
346
 
151
347
  case @provider
152
- when IEX then url = "https://realtime.intrinio.com/auth"
153
- when QUODD then url = "https://api.intrinio.com/token?type=QUODD"
154
- when CRYPTOQUOTE then url = "https://crypto.intrinio.com/auth"
348
+ when REALTIME then url = "https://realtime-mx.intrinio.com/auth"
349
+ when MANUAL then url = "http://" + @ip_address + "/auth"
155
350
  end
156
351
 
157
352
  url = api_auth_url(url) if @api_key
@@ -160,7 +355,7 @@ module Intrinio
160
355
  end
161
356
 
162
357
  def api_auth_url(url)
163
- if @api_key.include? "?"
358
+ if url.include? "?"
164
359
  url = "#{url}&"
165
360
  else
166
361
  url = "#{url}?"
@@ -170,10 +365,9 @@ module Intrinio
170
365
  end
171
366
 
172
367
  def socket_url
173
- case @provider
174
- when IEX then URI.escape("wss://realtime.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}")
175
- when QUODD then URI.escape("wss://www5.quodd.com/websocket/webStreamer/intrinio/#{@token}")
176
- when CRYPTOQUOTE then URI.escape("wss://crypto.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}")
368
+ case @provider
369
+ when REALTIME then URI.escape("wss://realtime-mx.intrinio.com/socket/websocket?vsn=1.0.0&token=#{@token}")
370
+ when MANUAL then URI.escape("ws://" + @ip_address + "/socket/websocket?vsn=1.0.0&token=#{@token}")
177
371
  end
178
372
  end
179
373
 
@@ -183,6 +377,19 @@ module Intrinio
183
377
  @ws.close() unless @ws.nil?
184
378
  @ready = false
185
379
  @joined_channels = []
380
+
381
+ @stop = true
382
+ sleep(2)
383
+ @threads.each { |thread|
384
+ if !thread.nil? && (!thread.pending_interrupt? || thread.status == "run" || thread.status == "Sleeping")
385
+ then thread.join(7)
386
+ elsif !thread.nil?
387
+ then thread.kill
388
+ end
389
+ }
390
+ @threads = []
391
+ @stop = false
392
+ @thread_quantity.times {@threads << Thread.new{handle_data}}
186
393
 
187
394
  @ws = ws = WebSocket::Client::Simple.connect(socket_url)
188
395
  me.send :info, "Connection opening"
@@ -190,7 +397,7 @@ module Intrinio
190
397
  ws.on :open do
191
398
  me.send :info, "Connection established"
192
399
  me.send :ready, true
193
- if me.send(:provider) == IEX || me.send(:provider) == CRYPTOQUOTE
400
+ if [REALTIME, MANUAL].include?(me.send(:provider))
194
401
  me.send :refresh_channels
195
402
  end
196
403
  me.send :start_heartbeat
@@ -198,39 +405,21 @@ module Intrinio
198
405
  end
199
406
 
200
407
  ws.on :message do |frame|
201
- message = frame.data
202
- me.send :debug, "Message: #{message}"
203
-
408
+ data_message = frame.data
409
+ #me.send :debug, "Message: #{data_message}"
204
410
  begin
205
- json = JSON.parse(message)
206
-
207
- quote = case me.send(:provider)
208
- when IEX
209
- if json["event"] == "quote"
210
- json["payload"]
211
- end
212
- when QUODD
213
- if json["event"] == "info" && json["data"]["message"] == "Connected"
214
- me.send :refresh_channels
215
- elsif json["event"] == "quote" || json["event"] == "trade"
216
- json["data"]
217
- end
218
- when CRYPTOQUOTE
219
- if json["event"] == "book_update" || json["event"] == "ticker" || json["event"] == "trade"
220
- json["payload"]
221
- end
222
- end
223
-
224
- if quote && quote.is_a?(Hash)
225
- me.send :process_quote, quote
411
+ unless data_message.nil?
412
+ then me.send :queue_message, data_message
226
413
  end
227
414
  rescue StandardError => e
228
- me.send :error, "Could not parse message: #{message} #{e}"
415
+ me.send :error, "Error adding message to queue: #{data_message} #{e}"
229
416
  end
230
417
  end
231
418
 
232
419
  ws.on :close do |e|
233
- me.send :disconnect
420
+ me.send :ready, false
421
+ me.send :info, "Connection closing...: #{e}"
422
+ me.send :try_self_heal
234
423
  end
235
424
 
236
425
  ws.on :error do |e|
@@ -247,16 +436,20 @@ module Intrinio
247
436
  # Join new channels
248
437
  new_channels = @channels - @joined_channels
249
438
  new_channels.each do |channel|
250
- msg = join_message(channel)
251
- @ws.send(msg.to_json)
439
+ #msg = join_message(channel)
440
+ #@ws.send(msg.to_json)
441
+ msg = join_binary_message(channel)
442
+ @ws.send(msg)
252
443
  info "Joined #{channel}"
253
444
  end
254
445
 
255
446
  # Leave old channels
256
447
  old_channels = @joined_channels - @channels
257
448
  old_channels.each do |channel|
258
- msg = leave_message(channel)
259
- @ws.send(msg.to_json)
449
+ #msg = leave__message(channel)
450
+ #@ws.send(msg.to_json)
451
+ msg = leave_binary_message(channel)
452
+ @ws.send(msg)
260
453
  info "Left #{channel}"
261
454
  end
262
455
 
@@ -276,11 +469,7 @@ module Intrinio
276
469
  end
277
470
 
278
471
  def heartbeat_msg
279
- case @provider
280
- when IEX then {topic: 'phoenix', event: 'heartbeat', payload: {}, ref: nil}.to_json
281
- when QUODD then {event: 'heartbeat', data: {action: 'heartbeat', ticker: (Time.now.to_f * 1000).to_i}}.to_json
282
- when CRYPTOQUOTE then {topic: 'phoenix', event: 'heartbeat', payload: {}, ref: nil}.to_json
283
- end
472
+ ""
284
473
  end
285
474
 
286
475
  def stop_heartbeat
@@ -310,11 +499,7 @@ module Intrinio
310
499
  def ready(val)
311
500
  @ready = val
312
501
  end
313
-
314
- def process_quote(quote)
315
- @quotes.push(quote)
316
- end
317
-
502
+
318
503
  def debug(message)
319
504
  message = "IntrinioRealtime | #{message}"
320
505
  @logger.debug(message) rescue
@@ -348,68 +533,26 @@ module Intrinio
348
533
  channels
349
534
  end
350
535
 
351
- def parse_iex_topic(channel)
352
- case channel
353
- when "$lobby"
354
- "iex:lobby"
355
- when "$lobby_last_price"
356
- "iex:lobby:last_price"
536
+ def join_binary_message(channel)
537
+ if (channel == "lobby") && (@trades_only == false)
538
+ return [74, 0, 36, 70, 73, 82, 69, 72, 79, 83, 69].pack('C*') #74, not trades only, "$FIREHOSE"
539
+ elsif (channel == "lobby") && (@trades_only == true)
540
+ return [74, 1, 36, 70, 73, 82, 69, 72, 79, 83, 69].pack('C*') #74, trades only, "$FIREHOSE"
357
541
  else
358
- "iex:securities:#{channel}"
359
- end
360
- end
361
-
362
- def join_message(channel)
363
- case @provider
364
- when IEX
365
- {
366
- topic: parse_iex_topic(channel),
367
- event: "phx_join",
368
- payload: {},
369
- ref: nil
370
- }
371
- when QUODD
372
- {
373
- event: "subscribe",
374
- data: {
375
- ticker: channel,
376
- action: "subscribe"
377
- }
378
- }
379
- when CRYPTOQUOTE
380
- {
381
- topic: channel,
382
- event: "phx_join",
383
- payload: {},
384
- ref: nil
385
- }
542
+ bytes = [74, 0]
543
+ if (@trades_only == true)
544
+ bytes[1] = 1
545
+ end
546
+ return bytes.concat(channel.bytes).pack('C*')
386
547
  end
387
548
  end
388
-
389
- def leave_message(channel)
390
- case @provider
391
- when IEX
392
- {
393
- topic: parse_iex_topic(channel),
394
- event: "phx_leave",
395
- payload: {},
396
- ref: nil
397
- }
398
- when QUODD
399
- {
400
- event: "unsubscribe",
401
- data: {
402
- ticker: channel,
403
- action: "unsubscribe"
404
- }
405
- }
406
- when CRYPTOQUOTE
407
- {
408
- topic: channel,
409
- event: "phx_leave",
410
- payload: {},
411
- ref: nil
412
- }
549
+
550
+ def leave_binary_message(channel)
551
+ if channel == "lobby"
552
+ return [76, 36, 70, 73, 82, 69, 72, 79, 83, 69].pack('C*') #74, not trades only, "$FIREHOSE"
553
+ else
554
+ bytes = [76]
555
+ return bytes.concat(channel.bytes).pack('C*')
413
556
  end
414
557
  end
415
558
 
metadata CHANGED
@@ -1,60 +1,74 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intrinio-realtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 3.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Intrinio
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-29 00:00:00.000000000 Z
11
+ date: 2022-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: http
14
+ name: eventmachine
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.2'
19
+ version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.2'
26
+ version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: eventmachine
28
+ name: websocket-client-simple
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.2'
33
+ version: '0.3'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.2'
40
+ version: '0.3'
41
41
  - !ruby/object:Gem::Dependency
42
- name: websocket-client-simple
42
+ name: thread
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.3'
47
+ version: 0.2.2
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.3'
55
- description: Intrinio Ruby SDK for Real-Time Stock & Crypto Prices
54
+ version: 0.2.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: bigdecimal
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.4.0
69
+ description: Intrinio Ruby SDK for Real-Time Stock Prices
56
70
  email:
57
- - asolo@intrinio.com
71
+ - ssnyder@intrinio.com
58
72
  executables: []
59
73
  extensions: []
60
74
  extra_rdoc_files: []
@@ -64,7 +78,7 @@ homepage: https://github.com/intrinio/intrinio-realtime-ruby-sdk
64
78
  licenses:
65
79
  - GPL-3.0
66
80
  metadata: {}
67
- post_install_message:
81
+ post_install_message:
68
82
  rdoc_options: []
69
83
  require_paths:
70
84
  - lib
@@ -79,10 +93,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
93
  - !ruby/object:Gem::Version
80
94
  version: '0'
81
95
  requirements: []
82
- rubyforge_project:
83
- rubygems_version: 2.5.2.2
84
- signing_key:
96
+ rubygems_version: 3.2.15
97
+ signing_key:
85
98
  specification_version: 4
86
- summary: Intrinio provides real-time stock & crypto prices from the IEX stock exchange,
87
- via a two-way WebSocket connection.
99
+ summary: Intrinio provides real-time stock prices from its Multi-Exchange feed, via
100
+ a two-way WebSocket connection.
88
101
  test_files: []