dtn 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +21 -5
- data/.env.example +5 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +7 -0
- data/CHANGELOG.md +5 -1
- data/Gemfile +3 -1
- data/README.md +288 -9
- data/Rakefile +2 -0
- data/docker-compose.yml +34 -0
- data/dtn.gemspec +1 -1
- data/lib/dtn.rb +18 -1
- data/lib/dtn/concerns/id.rb +36 -0
- data/lib/dtn/concerns/validation.rb +71 -0
- data/lib/dtn/helpers/catalog.rb +32 -0
- data/lib/dtn/lookups/catalog/listed_markets.rb +12 -0
- data/lib/dtn/lookups/catalog/naic_codes.rb +12 -0
- data/lib/dtn/lookups/catalog/security_types.rb +12 -0
- data/lib/dtn/lookups/catalog/sic_codes.rb +12 -0
- data/lib/dtn/lookups/catalog/trade_conditions.rb +12 -0
- data/lib/dtn/lookups/historical/base.rb +43 -0
- data/lib/dtn/lookups/historical/daily_datapoint.rb +13 -0
- data/lib/dtn/lookups/historical/daily_timeframe.rb +34 -0
- data/lib/dtn/lookups/historical/datapoint.rb +27 -0
- data/lib/dtn/lookups/historical/interval.rb +14 -0
- data/lib/dtn/lookups/historical/interval_datapoint.rb +31 -0
- data/lib/dtn/lookups/historical/interval_day.rb +32 -0
- data/lib/dtn/lookups/historical/interval_timeframe.rb +37 -0
- data/lib/dtn/lookups/historical/monthly_datapoint.rb +13 -0
- data/lib/dtn/lookups/historical/tick.rb +14 -0
- data/lib/dtn/lookups/historical/tick_datapoint.rb +24 -0
- data/lib/dtn/lookups/historical/tick_day.rb +34 -0
- data/lib/dtn/lookups/historical/tick_timeframe.rb +31 -0
- data/lib/dtn/lookups/historical/weekly_datapoint.rb +13 -0
- data/lib/dtn/lookups/news/base.rb +85 -0
- data/lib/dtn/lookups/news/config.rb +25 -0
- data/lib/dtn/lookups/news/headline.rb +40 -0
- data/lib/dtn/lookups/news/story.rb +40 -0
- data/lib/dtn/lookups/news/story_count.rb +36 -0
- data/lib/dtn/lookups/request.rb +92 -0
- data/lib/dtn/lookups/symbol/base.rb +11 -0
- data/lib/dtn/lookups/symbol/by_filter.rb +58 -0
- data/lib/dtn/lookups/symbol/by_naic.rb +26 -0
- data/lib/dtn/lookups/symbol/by_sic.rb +26 -0
- data/lib/dtn/message.rb +29 -0
- data/lib/dtn/messages/bar/base.rb +30 -0
- data/lib/dtn/messages/bar/current_bar.rb +11 -0
- data/lib/dtn/messages/bar/historical_bar.rb +11 -0
- data/lib/dtn/messages/bar/update_bar.rb +11 -0
- data/lib/dtn/messages/catalog/code.rb +22 -0
- data/lib/dtn/messages/catalog/listed_markets.rb +20 -0
- data/lib/dtn/messages/catalog/naic_codes.rb +10 -0
- data/lib/dtn/messages/catalog/security_types.rb +20 -0
- data/lib/dtn/messages/catalog/sic_codes.rb +10 -0
- data/lib/dtn/messages/catalog/trade_conditions.rb +20 -0
- data/lib/dtn/messages/historical/daily_weekly_monthly.rb +25 -0
- data/lib/dtn/messages/historical/interval.rb +28 -0
- data/lib/dtn/messages/historical/tick.rb +31 -0
- data/lib/dtn/messages/level2/level2_update.rb +38 -0
- data/lib/dtn/messages/level2/market_maker_name.rb +20 -0
- data/lib/dtn/messages/message_with_simple_parser.rb +38 -0
- data/lib/dtn/messages/news/base.rb +34 -0
- data/lib/dtn/messages/news/config.rb +24 -0
- data/lib/dtn/messages/news/headline.rb +25 -0
- data/lib/dtn/messages/news/story.rb +20 -0
- data/lib/dtn/messages/news/story_count.rb +21 -0
- data/lib/dtn/messages/quote/level1.rb +150 -0
- data/lib/dtn/messages/quote/level1_fundamental.rb +17 -0
- data/lib/dtn/messages/quote/level1_news.rb +27 -0
- data/lib/dtn/messages/quote/level1_regional.rb +31 -0
- data/lib/dtn/messages/quote/level1_summary.rb +19 -0
- data/lib/dtn/messages/quote/level1_update.rb +21 -0
- data/lib/dtn/messages/symbol/base.rb +35 -0
- data/lib/dtn/messages/symbol/by_filter.rb +11 -0
- data/lib/dtn/messages/symbol/by_naic.rb +22 -0
- data/lib/dtn/messages/symbol/by_sic.rb +22 -0
- data/lib/dtn/messages/system/client_stats.rb +46 -0
- data/lib/dtn/messages/system/customer_info.rb +30 -0
- data/lib/dtn/messages/system/end_of_message_characters.rb +22 -0
- data/lib/dtn/messages/system/error.rb +20 -0
- data/lib/dtn/messages/system/generic.rb +98 -0
- data/lib/dtn/messages/system/no_data_characters.rb +22 -0
- data/lib/dtn/messages/system/stats.rb +38 -0
- data/lib/dtn/messages/system/symbol_not_found.rb +19 -0
- data/lib/dtn/messages/system/timestamp.rb +16 -0
- data/lib/dtn/messages/unknown.rb +15 -0
- data/lib/dtn/registry.rb +57 -0
- data/lib/dtn/streaming/client.rb +105 -0
- data/lib/dtn/streaming/clients/admin.rb +20 -0
- data/lib/dtn/streaming/clients/bar.rb +49 -0
- data/lib/dtn/streaming/clients/level2.rb +25 -0
- data/lib/dtn/streaming/clients/quote.rb +57 -0
- data/lib/dtn/streaming/messages_recorder_observer.rb +26 -0
- data/lib/dtn/streaming/request.rb +27 -0
- data/lib/dtn/streaming/request_builder.rb +57 -0
- data/lib/dtn/streaming/requests/admin/register_client_app.rb +24 -0
- data/lib/dtn/streaming/requests/admin/remove_client_app.rb +22 -0
- data/lib/dtn/streaming/requests/admin/save_login_info.rb +18 -0
- data/lib/dtn/streaming/requests/admin/set_autoconnect.rb +18 -0
- data/lib/dtn/streaming/requests/admin/set_client_stats.rb +20 -0
- data/lib/dtn/streaming/requests/admin/set_loginid.rb +21 -0
- data/lib/dtn/streaming/requests/admin/set_password.rb +21 -0
- data/lib/dtn/streaming/requests/bar/unwatch.rb +18 -0
- data/lib/dtn/streaming/requests/bar/unwatch_all.rb +16 -0
- data/lib/dtn/streaming/requests/bar/watch.rb +81 -0
- data/lib/dtn/streaming/requests/bar/watches.rb +21 -0
- data/lib/dtn/streaming/requests/level2/connect.rb +16 -0
- data/lib/dtn/streaming/requests/level2/disconnect.rb +16 -0
- data/lib/dtn/streaming/requests/level2/market_maker_by_id.rb +18 -0
- data/lib/dtn/streaming/requests/level2/unwatch.rb +18 -0
- data/lib/dtn/streaming/requests/level2/watch.rb +18 -0
- data/lib/dtn/streaming/requests/quote/all_update_fieldnames.rb +16 -0
- data/lib/dtn/streaming/requests/quote/connect.rb +16 -0
- data/lib/dtn/streaming/requests/quote/current_update_fieldnames.rb +16 -0
- data/lib/dtn/streaming/requests/quote/fundamental_fieldnames.rb +16 -0
- data/lib/dtn/streaming/requests/quote/news_switch.rb +18 -0
- data/lib/dtn/streaming/requests/quote/refresh.rb +29 -0
- data/lib/dtn/streaming/requests/quote/regional_switch.rb +26 -0
- data/lib/dtn/streaming/requests/quote/set_client_name.rb +16 -0
- data/lib/dtn/streaming/requests/quote/set_protocol.rb +16 -0
- data/lib/dtn/streaming/requests/quote/timestamp.rb +21 -0
- data/lib/dtn/streaming/requests/quote/timestamp_switch.rb +18 -0
- data/lib/dtn/streaming/requests/quote/trades.rb +21 -0
- data/lib/dtn/streaming/requests/quote/unwatch.rb +22 -0
- data/lib/dtn/streaming/requests/quote/unwatch_all.rb +16 -0
- data/lib/dtn/streaming/requests/quote/update_fields.rb +40 -0
- data/lib/dtn/streaming/requests/quote/watch.rb +22 -0
- data/lib/dtn/streaming/requests/quote/watches.rb +21 -0
- data/lib/dtn/version.rb +1 -1
- data/lib/ext/business_day.rb +15 -0
- data/lib/tasks/spec_date.rake +13 -0
- metadata +126 -6
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module System
|
6
|
+
# Empty response abstraction
|
7
|
+
class NoDataCharacters < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
def fields
|
10
|
+
@fields ||= {
|
11
|
+
request_id: :to_i
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def termination?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module System
|
6
|
+
# All stats
|
7
|
+
class Stats < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
# rubocop:disable Metrics/MethodLength
|
10
|
+
def fields
|
11
|
+
@fields ||= {
|
12
|
+
server_ip: :to_s,
|
13
|
+
server_port: :to_i,
|
14
|
+
max_symbols: :to_i,
|
15
|
+
number_of_symbols: :to_i,
|
16
|
+
clients_connected: :to_i,
|
17
|
+
seconds_since_last_update: :to_i,
|
18
|
+
reconnections: :to_i,
|
19
|
+
attemptedReconnections: :to_i,
|
20
|
+
start_time: :to_datetime,
|
21
|
+
market_time: :to_datetime,
|
22
|
+
status: :to_s,
|
23
|
+
iq_feed_version: :to_s,
|
24
|
+
loginId: :to_s,
|
25
|
+
totalKBsRecv: :to_f,
|
26
|
+
kbsPerSecRecv: :to_f,
|
27
|
+
avgKBsPerSecRecv: :to_f,
|
28
|
+
totalKBsSent: :to_f,
|
29
|
+
kbsPerSecSent: :to_f,
|
30
|
+
avgKBsPerSecSent: :to_f
|
31
|
+
}
|
32
|
+
end
|
33
|
+
# rubocop:enable Metrics/MethodLength
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module System
|
6
|
+
# Not found symbol for streaming
|
7
|
+
class SymbolNotFound < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
def fields
|
10
|
+
@fields ||= {
|
11
|
+
_skip: :nil,
|
12
|
+
symbol: :to_s
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
# Just in case we got something unexpected.
|
6
|
+
# in the best world should be never executed.
|
7
|
+
class Unknown < Message
|
8
|
+
class << self
|
9
|
+
def parse(line:, **)
|
10
|
+
new(line: line)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/dtn/registry.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
# Abstract thread safe registry
|
5
|
+
class Registry
|
6
|
+
include Enumerable
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
delegate delete: :@items,
|
10
|
+
size: :@items,
|
11
|
+
clear: :@items
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
def initialize(name:)
|
16
|
+
@name = name
|
17
|
+
@items = Concurrent::Map.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear
|
21
|
+
@items.clear
|
22
|
+
end
|
23
|
+
|
24
|
+
def each(&block)
|
25
|
+
@items.values.uniq.each(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find(item_name)
|
29
|
+
@items.fetch(item_name)
|
30
|
+
rescue KeyError => e
|
31
|
+
raise key_error_with_custom_message(e, item_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
alias [] find
|
35
|
+
|
36
|
+
def register(name, item)
|
37
|
+
return unless name
|
38
|
+
|
39
|
+
@items[name] = item
|
40
|
+
end
|
41
|
+
|
42
|
+
alias []= register
|
43
|
+
|
44
|
+
def registered?(name)
|
45
|
+
@items.key?(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def key_error_with_custom_message(key_error, item_name)
|
51
|
+
message = key_error.message.sub("key not found", %(#{@name} not registered: "#{item_name}"))
|
52
|
+
error = KeyError.new(message)
|
53
|
+
error.set_backtrace(key_error.backtrace)
|
54
|
+
error
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Streaming
|
5
|
+
# Top level client abstraction. Different streams are available on different
|
6
|
+
# ports, so we can use it and follow the same pattern
|
7
|
+
class Client
|
8
|
+
# Status helper methods
|
9
|
+
module Status
|
10
|
+
STATUSES = { run: :running, stop: :stopped, initializing: :initialized }.freeze
|
11
|
+
|
12
|
+
STATUSES.each do |k, v|
|
13
|
+
define_method(k) { self.status = v }
|
14
|
+
define_method("#{v}?") { status == v }
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :status
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
attr_writer :status
|
22
|
+
end
|
23
|
+
|
24
|
+
include Status
|
25
|
+
|
26
|
+
PROTOCOL_VERSION = "6.1"
|
27
|
+
|
28
|
+
CLIENT_TERMINATION_SIGNALS = %w[TERM INT].freeze
|
29
|
+
|
30
|
+
COMMON_SUPPORTED_MESSAGES = {
|
31
|
+
"S" => Messages::System::Generic,
|
32
|
+
"T" => Messages::System::Timestamp,
|
33
|
+
"n" => Messages::System::SymbolNotFound,
|
34
|
+
"E" => Messages::System::Error
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
# @params name Specify name for this client
|
38
|
+
# @params start_engine auto start engine, which is processing messages
|
39
|
+
def initialize(name: nil, start_engine: true)
|
40
|
+
@name = name || SecureRandom.alphanumeric(10)
|
41
|
+
|
42
|
+
initializing
|
43
|
+
|
44
|
+
init_connection
|
45
|
+
setup_signals
|
46
|
+
engine if start_engine
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :name
|
50
|
+
|
51
|
+
# We are able to filer the incoming data with custom fields
|
52
|
+
# using
|
53
|
+
attr_accessor :quote_update_fields
|
54
|
+
|
55
|
+
def request
|
56
|
+
RequestBuilder.new(client: self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def observers
|
60
|
+
@observers ||= Set.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
"Client name: #{name}, status: #{status}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def socket
|
68
|
+
@socket ||= TCPSocket.open(Dtn.host, self.class::PORT)
|
69
|
+
end
|
70
|
+
|
71
|
+
def engine
|
72
|
+
@engine ||= Thread.new do
|
73
|
+
run
|
74
|
+
while running? && (line = socket.gets)
|
75
|
+
process_line(line: line)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def process_line(line:)
|
83
|
+
message_class(line: line).parse(line: line, client: self).tap do |message|
|
84
|
+
observers.each do |obs|
|
85
|
+
obs.public_send(message.callback_name, message: message) if obs.respond_to?(message.callback_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def message_class(line:)
|
91
|
+
self.class::SUPPORTED_MESSAGES[line[0]] || Messages::Unknown
|
92
|
+
end
|
93
|
+
|
94
|
+
def init_connection
|
95
|
+
socket
|
96
|
+
end
|
97
|
+
|
98
|
+
def setup_signals
|
99
|
+
CLIENT_TERMINATION_SIGNALS.each do |signal|
|
100
|
+
Signal.trap(signal) { stop }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Streaming
|
5
|
+
module Clients
|
6
|
+
# Provides a connection to IQFeed's Administrative socket.
|
7
|
+
#
|
8
|
+
# Is used to find out the health of the feed, figure out the
|
9
|
+
# status of each connection made to IQFeed, and also set various parameters
|
10
|
+
# to the feed.
|
11
|
+
#
|
12
|
+
# See www.iqfeed.net/dev/api/docs/AdminviaTCPIP.cfm
|
13
|
+
class Admin < Client
|
14
|
+
PORT = 9300
|
15
|
+
|
16
|
+
SUPPORTED_MESSAGES = COMMON_SUPPORTED_MESSAGES
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Streaming
|
5
|
+
module Clients
|
6
|
+
# Let's you get live data as interval bar data.
|
7
|
+
#
|
8
|
+
# If you are using interval bars for trading, use this class if you want
|
9
|
+
# IQFeed to calculate the interval bars for you and send you interval bars
|
10
|
+
# instead of (or in addition to) receiving every tick. For example, you may
|
11
|
+
# want to get open, high, low, close data for each minute or every 50 trades.
|
12
|
+
#
|
13
|
+
# The length of the interval can be in time units, number of trades units
|
14
|
+
# or volume traded units as for bars from HistoryConn.
|
15
|
+
# If you want historical bars, use HistoryConn instead. This class
|
16
|
+
# allows you to get some history, for example if you want the past 5
|
17
|
+
# days's bars to fill in a data structure before you start getting live
|
18
|
+
# data updates. But if you want historical data for back-testing or some
|
19
|
+
# such, you are better off using HistoryConn instead.
|
20
|
+
#
|
21
|
+
# Since most historical data that IQFeed gives you is bar data, if you are
|
22
|
+
# just getting started, it may be a good idea to save some live tick-data and
|
23
|
+
# bar-data and compare them so you understand exactly how IQFeed is
|
24
|
+
# filtering ticks and generating it's bars. Different data providers tend to
|
25
|
+
# do this differently, dome better than others and the documentation usually
|
26
|
+
# doesn't get updated when things are changed.
|
27
|
+
#
|
28
|
+
# For more info, see:
|
29
|
+
# www.iqfeed.net/dev/api/docs/Derivatives_Overview.cfm
|
30
|
+
# and
|
31
|
+
# www.iqfeed.net/dev/api/docs/Derivatives_StreamingIntervalBars_TCPIP.cfm
|
32
|
+
class Bar < Client
|
33
|
+
PORT = 9400
|
34
|
+
|
35
|
+
SUPPORTED_MESSAGES = COMMON_SUPPORTED_MESSAGES.merge(
|
36
|
+
"BH" => Messages::Bar::HistoricalBar,
|
37
|
+
"BC" => Messages::Bar::CurrentBar,
|
38
|
+
"BU" => Messages::Bar::UpdateBar
|
39
|
+
).freeze
|
40
|
+
|
41
|
+
def message_class(line:)
|
42
|
+
self.class::SUPPORTED_MESSAGES[line[0]] ||
|
43
|
+
((line =~ /\d+,(\w+),.+/) && self.class::SUPPORTED_MESSAGES[Regexp.last_match(1)]) ||
|
44
|
+
Messages::Unknown
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Streaming
|
5
|
+
module Clients
|
6
|
+
# Provides a connection to IQFeed's Level2 socket.
|
7
|
+
class Level2 < Client
|
8
|
+
PORT = 9200
|
9
|
+
|
10
|
+
SUPPORTED_MESSAGES = COMMON_SUPPORTED_MESSAGES.merge(
|
11
|
+
"Z" => Messages::Level2::Level2Update, # Summary message, but it's actually the same
|
12
|
+
"2" => Messages::Level2::Level2Update,
|
13
|
+
"U" => Messages::Level2::Level2Update,
|
14
|
+
"M" => Messages::Level2::MarketMakerName # A Market Maker name OR order book level query response message.
|
15
|
+
).freeze
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def init_connection
|
20
|
+
request.level2.connect
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Streaming
|
5
|
+
module Clients
|
6
|
+
# Quote provides real-time Level 1 data and real-time news.
|
7
|
+
#
|
8
|
+
# Quote provides access to top-of-book quotes, regional quotes
|
9
|
+
# (quotes from a single exchange), fundamentals (which includes
|
10
|
+
# reference) data, streaming real-time news.
|
11
|
+
#
|
12
|
+
# Quotes, Regional, Trades and Fundamental data is provided as a messages
|
13
|
+
# since you are likely going to do something fancy with it.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# READ THIS CAREFULLY: For quote updates (provided when the top of book
|
17
|
+
# quote changes or a trade happens) IQFeed.exe can send dynamic fieldsets.
|
18
|
+
# This means that you can ask for any fields (subset of the set available)
|
19
|
+
# you want. This map to `Messages::Quote::Level1::ALL_FIELDS` which lists
|
20
|
+
# all available fields which will be used as a field name by DTN.
|
21
|
+
# The values corresponding to each
|
22
|
+
# key a tuple of (FieldName used by DTN, FieldName used in Structured Array,
|
23
|
+
# numpy scalar type used for that field).
|
24
|
+
#
|
25
|
+
# We start with a default set of fields (same as default in the IQFeed
|
26
|
+
# docs. It will be fetch once client starts. If you want a different
|
27
|
+
# set of fields, call
|
28
|
+
# `client.request.quote.update_fields list: %i[Bid Ask]` with the
|
29
|
+
# fieldnames you want. If you want a different set of fieldnames for options
|
30
|
+
# and stocks, create two clients. Use one for all stock subscriptions and
|
31
|
+
# one for all options subscriptions. They can both use the same observer if
|
32
|
+
# that is what you want.
|
33
|
+
#
|
34
|
+
# If you don't understand the above two paragraphs, look at the code, look
|
35
|
+
# at the examples, run the examples and then read the above again.
|
36
|
+
class Quote < Client
|
37
|
+
PORT = 5009
|
38
|
+
|
39
|
+
SUPPORTED_MESSAGES = COMMON_SUPPORTED_MESSAGES.merge(
|
40
|
+
"F" => Messages::Quote::Level1Fundamental,
|
41
|
+
"P" => Messages::Quote::Level1Summary,
|
42
|
+
"Q" => Messages::Quote::Level1Update,
|
43
|
+
"R" => Messages::Quote::Level1Regional,
|
44
|
+
"N" => Messages::Quote::Level1News
|
45
|
+
).freeze
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def init_connection
|
50
|
+
request.quote.set_client_name(name: name)
|
51
|
+
request.quote.current_update_fieldnames
|
52
|
+
request.quote.set_protocol
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Streaming
|
5
|
+
# A sample class, which will record all messages, which were
|
6
|
+
# invoked by the client.
|
7
|
+
# It will spy and record all invocations for simpler further analyses
|
8
|
+
#
|
9
|
+
# It's costly for production use, but very convenient for dev &
|
10
|
+
# testing purposes to quickly understand what you will get from the
|
11
|
+
# API
|
12
|
+
class MessagesRecorderObserver
|
13
|
+
def invoked_methods
|
14
|
+
@invoked_methods ||= Hash.new { |h, k| h[k] = [] }
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method_name, **opts)
|
18
|
+
invoked_methods[method_name] << opts[:message]
|
19
|
+
end
|
20
|
+
|
21
|
+
def respond_to_missing?(*)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|