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,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Lookups
|
5
|
+
# Lookups are used to get historical data from IQFeed's lookup socket.
|
6
|
+
# This class returns historical data as the return value from the function
|
7
|
+
# used to request the data.
|
8
|
+
#
|
9
|
+
# Works synchronously
|
10
|
+
#
|
11
|
+
# For more details see:
|
12
|
+
# www.iqfeed.net/dev/api/docs/HistoricalviaTCPIP.cfm
|
13
|
+
class Request
|
14
|
+
END_OF_MESSAGE_CHARACTERS = /!ENDMSG!/.freeze
|
15
|
+
NO_DATA_CHARACTERS = /!NO_DATA!/.freeze
|
16
|
+
SYNTAX_ERROR_CHARACTERS = /!SYNTAX_ERROR!/.freeze
|
17
|
+
|
18
|
+
PORT = 9100
|
19
|
+
|
20
|
+
include Dtn::Concerns::Id
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def call(*args, **opts, &blk)
|
24
|
+
new.call(*args, **opts, &blk)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :combined_options
|
29
|
+
|
30
|
+
# Initialize the request to api, should be used in children classes only
|
31
|
+
#
|
32
|
+
# @returns nil or request_id (Integer)
|
33
|
+
def call(*, &blk)
|
34
|
+
socket.print "#{format(self.class.const_get(:TEMPLATE), combined_options)}\r\n"
|
35
|
+
|
36
|
+
pull_socket(&blk)
|
37
|
+
|
38
|
+
return result_accumulator unless block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def pull_socket(&blk)
|
44
|
+
catch(:pull_termination) do
|
45
|
+
while (line = socket.gets)
|
46
|
+
process_line(line: line, &blk)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def process_line(line:)
|
52
|
+
message = engine_klass_picker(line).parse(line: line, request: self)
|
53
|
+
throw(:pull_termination) if message.termination?
|
54
|
+
|
55
|
+
block_given? ? yield(message) : result_accumulator << message
|
56
|
+
end
|
57
|
+
|
58
|
+
def result_accumulator
|
59
|
+
@result_accumulator ||= []
|
60
|
+
end
|
61
|
+
|
62
|
+
def engine_klass_picker(line)
|
63
|
+
/^(\d+,)?(.+)/ =~ line
|
64
|
+
payload = Regexp.last_match(2)
|
65
|
+
case payload
|
66
|
+
when END_OF_MESSAGE_CHARACTERS then Messages::System::EndOfMessageCharacters
|
67
|
+
when NO_DATA_CHARACTERS then Messages::System::NoDataCharacters
|
68
|
+
when /^E,/, SYNTAX_ERROR_CHARACTERS then Messages::System::Error
|
69
|
+
else expected_messages_class
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# This should contain expected class of the returning message.
|
74
|
+
# Might be overwritten in child class
|
75
|
+
#
|
76
|
+
# @returns Class
|
77
|
+
def expected_messages_class
|
78
|
+
self.class.name.sub("Lookups", "Messages").constantize
|
79
|
+
end
|
80
|
+
|
81
|
+
def socket
|
82
|
+
@socket ||= TCPSocket.open(Dtn.host, PORT)
|
83
|
+
end
|
84
|
+
|
85
|
+
def defaults(**options)
|
86
|
+
{
|
87
|
+
id: id
|
88
|
+
}.merge(options)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Lookups
|
5
|
+
module Symbol
|
6
|
+
# Symbol lookup by custom filter
|
7
|
+
class ByFilter < Base
|
8
|
+
TEMPLATE = "SBF,%<field_to_search>s,%<search_line>s,%<filter_type>s,%<filter_value>s,%<id>d"
|
9
|
+
|
10
|
+
# Symbols lookup by user filter.
|
11
|
+
#
|
12
|
+
# @params field_to_search String, can be 's' for symbols or 'd' for description. Default nil
|
13
|
+
# @params filter_type String, can be 'e' for listed_markets or 't' security_types. Default nil
|
14
|
+
# @params filter_value Array of Strings or ' ' separated String
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# Example messages
|
18
|
+
#
|
19
|
+
# SBF,[Field To Search],[Search String],[Filter Type],[Filter Value],[RequestID]<CR><LF>
|
20
|
+
def call(search_line:, **options)
|
21
|
+
self.combined_options = defaults(**options).merge(
|
22
|
+
{
|
23
|
+
search_line: search_line,
|
24
|
+
field_to_search: validate_field_to_search(options[:field_to_search]),
|
25
|
+
filter_type: validate_filter_type(options[:filter_type]),
|
26
|
+
filter_value: validate_filter_value(options[:filter_value], options[:filter_type])
|
27
|
+
}
|
28
|
+
)
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def validate_field_to_search(value)
|
35
|
+
return value if ["", "s", "d"].include?(value.to_s)
|
36
|
+
|
37
|
+
raise ValidationError, "Got #{value}, but field_to_search can be only 's' to search " \
|
38
|
+
"be symbols or 'd' to search by descriptions."
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate_filter_type(value)
|
42
|
+
return value if ["", "e", "t"].include?(value.to_s)
|
43
|
+
|
44
|
+
raise ValidationError, "Got #{value}, but filter_type can be only 'e' for listed_markets "\
|
45
|
+
"or 't' for security_types."
|
46
|
+
end
|
47
|
+
|
48
|
+
# it assume that filter type was already validated
|
49
|
+
def validate_filter_value(value, filter_type)
|
50
|
+
catalog = filter_type.to_s == "e" ? Dtn.listed_markets_catalog : Dtn.security_types_catalog
|
51
|
+
v = value.is_a?(Array) ? value : value.to_s.split.map(&:to_i)
|
52
|
+
v.each { |el| catalog.registered?(el) || raise("Missing #{el} in #{catalog.name} catalog") }
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Lookups
|
5
|
+
module Symbol
|
6
|
+
# Symbol lookup by NAIC
|
7
|
+
class ByNaic < Base
|
8
|
+
TEMPLATE = "SBN,%<search_line>s,%<id>d"
|
9
|
+
|
10
|
+
# Return symbols in a specific NAIC sector.
|
11
|
+
#
|
12
|
+
# Example messages
|
13
|
+
#
|
14
|
+
# SBN,[Search String],[RequestID]<CR><LF>
|
15
|
+
def call(search_line:, **options)
|
16
|
+
self.combined_options = defaults(**options).merge(
|
17
|
+
{
|
18
|
+
search_line: search_line
|
19
|
+
}
|
20
|
+
)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Lookups
|
5
|
+
module Symbol
|
6
|
+
# Symbol lookup by SIC
|
7
|
+
class BySic < Base
|
8
|
+
TEMPLATE = "SBS,%<search_line>s,%<id>d"
|
9
|
+
|
10
|
+
# Return symbols in a specific SIC sector.
|
11
|
+
#
|
12
|
+
# Example messages
|
13
|
+
#
|
14
|
+
# SBS,[Search String],[RequestID]<CR><LF>
|
15
|
+
def call(search_line:, **options)
|
16
|
+
self.combined_options = defaults(**options).merge(
|
17
|
+
{
|
18
|
+
search_line: search_line
|
19
|
+
}
|
20
|
+
)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/dtn/message.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
# Message abstraction
|
5
|
+
class Message < OpenStruct
|
6
|
+
class << self
|
7
|
+
def parse(**options)
|
8
|
+
new(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def callback_name
|
12
|
+
@callback_name ||= name.demodulize.underscore
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def callback_name
|
17
|
+
self.class.callback_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# For messages which return a collection of comma separated values.
|
22
|
+
class ParseListFromMessage < Message
|
23
|
+
class << self
|
24
|
+
def parse(line:, **)
|
25
|
+
new list: line.chomp.split(",").uniq
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module Bar
|
6
|
+
# Streaming Bars data.
|
7
|
+
class Base < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
# rubocop:disable Metrics/MethodLength
|
10
|
+
def fields
|
11
|
+
@fields ||= {
|
12
|
+
request_id: :to_i,
|
13
|
+
_skip: nil,
|
14
|
+
symbol: :to_s,
|
15
|
+
timestamp: :to_datetime,
|
16
|
+
open: :to_f,
|
17
|
+
high: :to_f,
|
18
|
+
low: :to_f,
|
19
|
+
close: :to_f,
|
20
|
+
total_volume: :to_i,
|
21
|
+
volume: :to_i,
|
22
|
+
number_of_trades: :to_i
|
23
|
+
}
|
24
|
+
end
|
25
|
+
# rubocop:enable Metrics/MethodLength
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module Catalog
|
6
|
+
# Code
|
7
|
+
class Code < Message
|
8
|
+
class << self
|
9
|
+
# this Message does not respect commas
|
10
|
+
def parse(line:, **)
|
11
|
+
id, *description = line.split(",").reject(&:blank?)
|
12
|
+
|
13
|
+
new.tap do |n|
|
14
|
+
n.id = id.to_i
|
15
|
+
n.description = description.join(",").gsub('"', "")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module Catalog
|
6
|
+
# Listed Markets
|
7
|
+
class ListedMarkets < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
def fields
|
10
|
+
@fields ||= {
|
11
|
+
id: :to_i,
|
12
|
+
name: :to_s,
|
13
|
+
description: :to_s
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module Catalog
|
6
|
+
# Security Types
|
7
|
+
class SecurityTypes < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
def fields
|
10
|
+
@fields ||= {
|
11
|
+
id: :to_i,
|
12
|
+
name: :to_s,
|
13
|
+
description: :to_s
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module Catalog
|
6
|
+
# Trade conditions
|
7
|
+
class TradeConditions < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
def fields
|
10
|
+
@fields ||= {
|
11
|
+
id: :to_i,
|
12
|
+
name: :to_s,
|
13
|
+
description: :to_s
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dtn
|
4
|
+
module Messages
|
5
|
+
module Historical
|
6
|
+
# High timeframe Bar representation
|
7
|
+
class DailyWeeklyMonthly < MessageWithSimpleParser
|
8
|
+
class << self
|
9
|
+
def fields
|
10
|
+
@fields ||= {
|
11
|
+
request_id: :to_i,
|
12
|
+
timestamp: :to_datetime,
|
13
|
+
high: :to_f,
|
14
|
+
low: :to_f,
|
15
|
+
open: :to_f,
|
16
|
+
close: :to_f,
|
17
|
+
period_volume: :to_i,
|
18
|
+
open_interest: :to_i
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|