intrinio-realtime 4.2.0 → 5.1.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 +115 -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: aa4f4dd35aeb0249722b61a5781edc9c7f621b0edfffb32b0ee7be771a3e3d2d
|
4
|
+
data.tar.gz: dcea71654614604c02f3194009386b09857637c6faa463aca3f6c507df625553
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a4180e823e6bd20357105dc3e40ad2753d81a109b5c661c76ac36d989f9de1b266c64188f6796ba5e345d33985bb503aab98a0798e7af3e31093477b4dbd3f2
|
7
|
+
data.tar.gz: e35bacfcda00abbbf89f369f1a9c8d100f658b29955987d76fadc47ac150a02776b68de2f9b0628378a30b3cb916072ddebce45169516842f1b8f8d2619bfe24
|
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,37 @@ 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
|
+
|
81
|
+
def is_darkpool
|
82
|
+
@market_center == nil || @market_center == 'D' || @market_center == 'E' || @market_center == "\0" || @market_center.empty?
|
83
|
+
end
|
84
|
+
|
56
85
|
def to_s
|
57
|
-
[@symbol, @price, @size, @timestamp, @total_volume].join(",")
|
86
|
+
[@symbol, @price, @size, @timestamp, @total_volume, @subprovider, @market_center, @condition].join(",")
|
58
87
|
end
|
59
88
|
end
|
60
89
|
|
61
90
|
class Quote
|
62
|
-
def initialize(type, symbol, price, size, timestamp)
|
91
|
+
def initialize(type, symbol, price, size, timestamp, subprovider, market_center, condition)
|
63
92
|
@type = type
|
64
93
|
@symbol = symbol
|
65
94
|
@price = price
|
66
95
|
@size = size
|
67
96
|
@timestamp = timestamp
|
97
|
+
@subprovider = subprovider
|
98
|
+
@market_center = market_center
|
99
|
+
@condition = condition
|
68
100
|
end
|
69
101
|
|
70
102
|
def type
|
@@ -87,8 +119,20 @@ module Intrinio
|
|
87
119
|
@timestamp
|
88
120
|
end
|
89
121
|
|
122
|
+
def subprovider
|
123
|
+
@subprovider
|
124
|
+
end
|
125
|
+
|
126
|
+
def market_center
|
127
|
+
@market_center
|
128
|
+
end
|
129
|
+
|
130
|
+
def condition
|
131
|
+
@condition
|
132
|
+
end
|
133
|
+
|
90
134
|
def to_s
|
91
|
-
[@symbol, @type, @price, @size, @timestamp].join(",")
|
135
|
+
[@symbol, @type, @price, @size, @timestamp, @subprovider, @market_center, @condition].join(",")
|
92
136
|
end
|
93
137
|
end
|
94
138
|
|
@@ -115,7 +159,7 @@ module Intrinio
|
|
115
159
|
unless @provider
|
116
160
|
@provider = REALTIME
|
117
161
|
end
|
118
|
-
raise "Provider must be 'REALTIME' or 'MANUAL'" unless PROVIDERS.include?(@provider)
|
162
|
+
raise "Provider must be 'REALTIME', 'DELAYED_SIP', 'NASDAQ_BASIC', or 'MANUAL'" unless PROVIDERS.include?(@provider)
|
119
163
|
|
120
164
|
@ip_address = options[:ip_address]
|
121
165
|
raise "Missing option ip_address while in MANUAL mode." if @provider == MANUAL and (@ip_address.nil? || @ip_address.empty?)
|
@@ -148,7 +192,6 @@ module Intrinio
|
|
148
192
|
|
149
193
|
@ready = false
|
150
194
|
@joined_channels = []
|
151
|
-
@heartbeat_timer = nil
|
152
195
|
@selfheal_timer = nil
|
153
196
|
@selfheal_backoffs = Array.new(SELF_HEAL_BACKOFFS)
|
154
197
|
@ws = nil
|
@@ -206,7 +249,6 @@ module Intrinio
|
|
206
249
|
end
|
207
250
|
|
208
251
|
def disconnect
|
209
|
-
EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
|
210
252
|
EM.cancel_timer(@selfheal_timer) if @selfheal_timer
|
211
253
|
@ready = false
|
212
254
|
@closing = true
|
@@ -257,38 +299,71 @@ module Intrinio
|
|
257
299
|
data.map { |i| [sprintf('%02x',i)].pack('H2') }.join.unpack('e').first
|
258
300
|
end
|
259
301
|
|
260
|
-
def
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
302
|
+
def parse_subprovider(byte)
|
303
|
+
case byte
|
304
|
+
when 0
|
305
|
+
NO_SUBPROVIDER
|
306
|
+
when 1
|
307
|
+
CTA_A
|
308
|
+
when 2
|
309
|
+
CTA_B
|
310
|
+
when 3
|
311
|
+
UTP
|
312
|
+
when 4
|
313
|
+
OTC
|
314
|
+
when 5
|
315
|
+
NASDAQ_BASIC
|
316
|
+
when 6
|
317
|
+
IEX
|
318
|
+
else
|
319
|
+
IEX
|
320
|
+
end
|
267
321
|
end
|
268
322
|
|
269
|
-
def
|
323
|
+
def parse_trade(data, start_index)
|
324
|
+
symbol_length = data[start_index + 2]
|
325
|
+
condition_length = data[start_index + 26 + symbol_length]
|
326
|
+
symbol = data[start_index + 3, symbol_length].map!{|c| c.chr}.join
|
327
|
+
price = parse_float32(data[start_index + 6 + symbol_length, 4])
|
328
|
+
size = parse_uint32(data[start_index + 10 + symbol_length, 4])
|
329
|
+
timestamp = parse_uint64(data[start_index + 14 + symbol_length, 8])
|
330
|
+
total_volume = parse_uint32(data[start_index + 22 + symbol_length, 4])
|
331
|
+
subprovider = parse_subprovider(data[start_index + 3 + symbol_length])
|
332
|
+
market_center = data[start_index + 4 + symbol_length, 2].pack("C*").encode!('UTF-8', 'UTF-16LE')
|
333
|
+
condition = if condition_length > 0 then data[start_index + 27 + symbol_length, condition_length].map!{|c| c.chr}.join else "" end
|
334
|
+
|
335
|
+
return Trade.new(symbol, price, size, timestamp, total_volume, subprovider, market_center, condition)
|
336
|
+
end
|
337
|
+
|
338
|
+
def parse_quote(data, start_index, msg_type)
|
339
|
+
symbol_length = data[start_index + 2]
|
340
|
+
condition_length = data[start_index + 22 + symbol_length]
|
270
341
|
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
|
-
|
342
|
+
symbol = data[start_index + 3, symbol_length].map!{|c| c.chr}.join
|
343
|
+
price = parse_float32(data[start_index + 6 + symbol_length, 4])
|
344
|
+
size = parse_uint32(data[start_index + 10 + symbol_length, 4])
|
345
|
+
timestamp = parse_uint64(data[start_index + 14 + symbol_length, 8])
|
346
|
+
subprovider = parse_subprovider(data[start_index + 3 + symbol_length])
|
347
|
+
market_center = data[start_index + 4 + symbol_length, 2].pack("C*").encode!('UTF-8', 'UTF-16LE')
|
348
|
+
condition = if condition_length > 0 then data[start_index + 23 + symbol_length, condition_length].map!{|c| c.chr}.join else "" end
|
349
|
+
|
350
|
+
return Quote.new(type, symbol, price, size, timestamp, subprovider, market_center, condition)
|
276
351
|
end
|
277
352
|
|
278
353
|
def handle_message(data, start_index)
|
279
354
|
msg_type = data[start_index]
|
280
|
-
|
355
|
+
msg_length = data[start_index + 1]
|
281
356
|
case msg_type
|
282
357
|
when 0 then
|
283
|
-
trade = parse_trade(data, start_index
|
358
|
+
trade = parse_trade(data, start_index)
|
284
359
|
@on_trade.call(trade)
|
285
|
-
return start_index +
|
360
|
+
return start_index + msg_length
|
286
361
|
when 1 || 2 then
|
287
|
-
quote = parse_quote(data, start_index,
|
362
|
+
quote = parse_quote(data, start_index, msg_type)
|
288
363
|
@on_quote.call(quote)
|
289
|
-
return start_index +
|
364
|
+
return start_index + msg_length
|
290
365
|
end
|
291
|
-
return start_index
|
366
|
+
return start_index + msg_length
|
292
367
|
end
|
293
368
|
|
294
369
|
def handle_data
|
@@ -332,7 +407,7 @@ module Intrinio
|
|
332
407
|
http.use_ssl = true if (auth_url.include?("https"))
|
333
408
|
http.start
|
334
409
|
request = Net::HTTP::Get.new(uri.request_uri)
|
335
|
-
request.add_field(
|
410
|
+
request.add_field(CLIENT_INFO_HEADER_KEY, CLIENT_INFO_HEADER_VALUE)
|
336
411
|
|
337
412
|
unless @api_key
|
338
413
|
request.basic_auth(@username, @password)
|
@@ -374,10 +449,10 @@ module Intrinio
|
|
374
449
|
|
375
450
|
def socket_url
|
376
451
|
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
|
-
|
452
|
+
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}"
|
453
|
+
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}"
|
454
|
+
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}"
|
455
|
+
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
456
|
end
|
382
457
|
end
|
383
458
|
|
@@ -400,17 +475,21 @@ module Intrinio
|
|
400
475
|
@threads = []
|
401
476
|
@stop = false
|
402
477
|
@thread_quantity.times {@threads << Thread.new{handle_data}}
|
403
|
-
|
404
|
-
|
478
|
+
|
479
|
+
headers = {}
|
480
|
+
headers[:headers] = {}
|
481
|
+
headers[CLIENT_INFO_HEADER_KEY] = CLIENT_INFO_HEADER_VALUE
|
482
|
+
headers[MESSAGE_VERSION_HEADER_KEY] = MESSAGE_VERSION_HEADER_VALUE
|
483
|
+
@ws = ws = WebSocket::Client::Simple.connect(socket_url, headers)
|
484
|
+
|
405
485
|
me.send :info, "Connection opening"
|
406
486
|
|
407
487
|
ws.on :open do
|
408
488
|
me.send :info, "Connection established"
|
409
489
|
me.send :ready, true
|
410
|
-
if
|
490
|
+
if PROVIDERS.include?(me.send(:provider))
|
411
491
|
me.send :refresh_channels
|
412
492
|
end
|
413
|
-
me.send :start_heartbeat
|
414
493
|
me.send :stop_self_heal
|
415
494
|
end
|
416
495
|
|
@@ -468,24 +547,6 @@ module Intrinio
|
|
468
547
|
debug "Current channels: #{@channels}"
|
469
548
|
end
|
470
549
|
|
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
550
|
def try_self_heal
|
490
551
|
return if @closing
|
491
552
|
debug "Attempting to self-heal"
|
@@ -493,7 +554,6 @@ module Intrinio
|
|
493
554
|
time = @selfheal_backoffs.first
|
494
555
|
@selfheal_backoffs.delete_at(0) if @selfheal_backoffs.count > 1
|
495
556
|
|
496
|
-
EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
|
497
557
|
EM.cancel_timer(@selfheal_timer) if @selfheal_timer
|
498
558
|
|
499
559
|
@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.1.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:
|
11
|
+
date: 2024-03-14 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.
|