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.
- checksums.yaml +4 -4
- data/lib/intrinio-realtime.rb +111 -55
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3beedb23f93b49b061ba476daa8520cb3f09cfde888eaddbd08fec263a39de3
|
4
|
+
data.tar.gz: e81ce551b2ce00694e0d7ef238b6b63d60b5deb365e20dc3583c8b7819a9eb7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c09c353f980bcd5bd4cd36687bcbe0cd87661b5f3b428c98299374f69d19e01c6408d107842e2b46c84d26f2dc429bd6278e04abc5b2890de444e33590361467
|
7
|
+
data.tar.gz: f7d2c9141ee70a4cff900669152d1e2cda68ffba54d5360c4f013e88866bea9a829a49e79f3ce0f82444bf2b3c49fb592c8285edf2da0bb3be88516345da1927
|
data/lib/intrinio-realtime.rb
CHANGED
@@ -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
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
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 +
|
272
|
-
price = parse_float32(data[start_index +
|
273
|
-
size = parse_uint32(data[start_index +
|
274
|
-
timestamp = parse_uint64(data[start_index +
|
275
|
-
|
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
|
-
|
351
|
+
msg_length = data[start_index + 1]
|
281
352
|
case msg_type
|
282
353
|
when 0 then
|
283
|
-
trade = parse_trade(data, start_index
|
354
|
+
trade = parse_trade(data, start_index)
|
284
355
|
@on_trade.call(trade)
|
285
|
-
return start_index +
|
356
|
+
return start_index + msg_length
|
286
357
|
when 1 || 2 then
|
287
|
-
quote = parse_quote(data, start_index,
|
358
|
+
quote = parse_quote(data, start_index, msg_type)
|
288
359
|
@on_quote.call(quote)
|
289
|
-
return start_index +
|
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(
|
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
|
-
|
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
|
-
|
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
|
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
|
+
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-
|
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.
|