intrinio-realtime 2.1.1 → 3.1.2

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 +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: []