intrinio-realtime 4.2.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|