dtn 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -5
  3. data/.env.example +5 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +7 -0
  6. data/CHANGELOG.md +5 -1
  7. data/Gemfile +3 -1
  8. data/README.md +288 -9
  9. data/Rakefile +2 -0
  10. data/docker-compose.yml +34 -0
  11. data/dtn.gemspec +1 -1
  12. data/lib/dtn.rb +18 -1
  13. data/lib/dtn/concerns/id.rb +36 -0
  14. data/lib/dtn/concerns/validation.rb +71 -0
  15. data/lib/dtn/helpers/catalog.rb +32 -0
  16. data/lib/dtn/lookups/catalog/listed_markets.rb +12 -0
  17. data/lib/dtn/lookups/catalog/naic_codes.rb +12 -0
  18. data/lib/dtn/lookups/catalog/security_types.rb +12 -0
  19. data/lib/dtn/lookups/catalog/sic_codes.rb +12 -0
  20. data/lib/dtn/lookups/catalog/trade_conditions.rb +12 -0
  21. data/lib/dtn/lookups/historical/base.rb +43 -0
  22. data/lib/dtn/lookups/historical/daily_datapoint.rb +13 -0
  23. data/lib/dtn/lookups/historical/daily_timeframe.rb +34 -0
  24. data/lib/dtn/lookups/historical/datapoint.rb +27 -0
  25. data/lib/dtn/lookups/historical/interval.rb +14 -0
  26. data/lib/dtn/lookups/historical/interval_datapoint.rb +31 -0
  27. data/lib/dtn/lookups/historical/interval_day.rb +32 -0
  28. data/lib/dtn/lookups/historical/interval_timeframe.rb +37 -0
  29. data/lib/dtn/lookups/historical/monthly_datapoint.rb +13 -0
  30. data/lib/dtn/lookups/historical/tick.rb +14 -0
  31. data/lib/dtn/lookups/historical/tick_datapoint.rb +24 -0
  32. data/lib/dtn/lookups/historical/tick_day.rb +34 -0
  33. data/lib/dtn/lookups/historical/tick_timeframe.rb +31 -0
  34. data/lib/dtn/lookups/historical/weekly_datapoint.rb +13 -0
  35. data/lib/dtn/lookups/news/base.rb +85 -0
  36. data/lib/dtn/lookups/news/config.rb +25 -0
  37. data/lib/dtn/lookups/news/headline.rb +40 -0
  38. data/lib/dtn/lookups/news/story.rb +40 -0
  39. data/lib/dtn/lookups/news/story_count.rb +36 -0
  40. data/lib/dtn/lookups/request.rb +92 -0
  41. data/lib/dtn/lookups/symbol/base.rb +11 -0
  42. data/lib/dtn/lookups/symbol/by_filter.rb +58 -0
  43. data/lib/dtn/lookups/symbol/by_naic.rb +26 -0
  44. data/lib/dtn/lookups/symbol/by_sic.rb +26 -0
  45. data/lib/dtn/message.rb +29 -0
  46. data/lib/dtn/messages/bar/base.rb +30 -0
  47. data/lib/dtn/messages/bar/current_bar.rb +11 -0
  48. data/lib/dtn/messages/bar/historical_bar.rb +11 -0
  49. data/lib/dtn/messages/bar/update_bar.rb +11 -0
  50. data/lib/dtn/messages/catalog/code.rb +22 -0
  51. data/lib/dtn/messages/catalog/listed_markets.rb +20 -0
  52. data/lib/dtn/messages/catalog/naic_codes.rb +10 -0
  53. data/lib/dtn/messages/catalog/security_types.rb +20 -0
  54. data/lib/dtn/messages/catalog/sic_codes.rb +10 -0
  55. data/lib/dtn/messages/catalog/trade_conditions.rb +20 -0
  56. data/lib/dtn/messages/historical/daily_weekly_monthly.rb +25 -0
  57. data/lib/dtn/messages/historical/interval.rb +28 -0
  58. data/lib/dtn/messages/historical/tick.rb +31 -0
  59. data/lib/dtn/messages/level2/level2_update.rb +38 -0
  60. data/lib/dtn/messages/level2/market_maker_name.rb +20 -0
  61. data/lib/dtn/messages/message_with_simple_parser.rb +38 -0
  62. data/lib/dtn/messages/news/base.rb +34 -0
  63. data/lib/dtn/messages/news/config.rb +24 -0
  64. data/lib/dtn/messages/news/headline.rb +25 -0
  65. data/lib/dtn/messages/news/story.rb +20 -0
  66. data/lib/dtn/messages/news/story_count.rb +21 -0
  67. data/lib/dtn/messages/quote/level1.rb +150 -0
  68. data/lib/dtn/messages/quote/level1_fundamental.rb +17 -0
  69. data/lib/dtn/messages/quote/level1_news.rb +27 -0
  70. data/lib/dtn/messages/quote/level1_regional.rb +31 -0
  71. data/lib/dtn/messages/quote/level1_summary.rb +19 -0
  72. data/lib/dtn/messages/quote/level1_update.rb +21 -0
  73. data/lib/dtn/messages/symbol/base.rb +35 -0
  74. data/lib/dtn/messages/symbol/by_filter.rb +11 -0
  75. data/lib/dtn/messages/symbol/by_naic.rb +22 -0
  76. data/lib/dtn/messages/symbol/by_sic.rb +22 -0
  77. data/lib/dtn/messages/system/client_stats.rb +46 -0
  78. data/lib/dtn/messages/system/customer_info.rb +30 -0
  79. data/lib/dtn/messages/system/end_of_message_characters.rb +22 -0
  80. data/lib/dtn/messages/system/error.rb +20 -0
  81. data/lib/dtn/messages/system/generic.rb +98 -0
  82. data/lib/dtn/messages/system/no_data_characters.rb +22 -0
  83. data/lib/dtn/messages/system/stats.rb +38 -0
  84. data/lib/dtn/messages/system/symbol_not_found.rb +19 -0
  85. data/lib/dtn/messages/system/timestamp.rb +16 -0
  86. data/lib/dtn/messages/unknown.rb +15 -0
  87. data/lib/dtn/registry.rb +57 -0
  88. data/lib/dtn/streaming/client.rb +105 -0
  89. data/lib/dtn/streaming/clients/admin.rb +20 -0
  90. data/lib/dtn/streaming/clients/bar.rb +49 -0
  91. data/lib/dtn/streaming/clients/level2.rb +25 -0
  92. data/lib/dtn/streaming/clients/quote.rb +57 -0
  93. data/lib/dtn/streaming/messages_recorder_observer.rb +26 -0
  94. data/lib/dtn/streaming/request.rb +27 -0
  95. data/lib/dtn/streaming/request_builder.rb +57 -0
  96. data/lib/dtn/streaming/requests/admin/register_client_app.rb +24 -0
  97. data/lib/dtn/streaming/requests/admin/remove_client_app.rb +22 -0
  98. data/lib/dtn/streaming/requests/admin/save_login_info.rb +18 -0
  99. data/lib/dtn/streaming/requests/admin/set_autoconnect.rb +18 -0
  100. data/lib/dtn/streaming/requests/admin/set_client_stats.rb +20 -0
  101. data/lib/dtn/streaming/requests/admin/set_loginid.rb +21 -0
  102. data/lib/dtn/streaming/requests/admin/set_password.rb +21 -0
  103. data/lib/dtn/streaming/requests/bar/unwatch.rb +18 -0
  104. data/lib/dtn/streaming/requests/bar/unwatch_all.rb +16 -0
  105. data/lib/dtn/streaming/requests/bar/watch.rb +81 -0
  106. data/lib/dtn/streaming/requests/bar/watches.rb +21 -0
  107. data/lib/dtn/streaming/requests/level2/connect.rb +16 -0
  108. data/lib/dtn/streaming/requests/level2/disconnect.rb +16 -0
  109. data/lib/dtn/streaming/requests/level2/market_maker_by_id.rb +18 -0
  110. data/lib/dtn/streaming/requests/level2/unwatch.rb +18 -0
  111. data/lib/dtn/streaming/requests/level2/watch.rb +18 -0
  112. data/lib/dtn/streaming/requests/quote/all_update_fieldnames.rb +16 -0
  113. data/lib/dtn/streaming/requests/quote/connect.rb +16 -0
  114. data/lib/dtn/streaming/requests/quote/current_update_fieldnames.rb +16 -0
  115. data/lib/dtn/streaming/requests/quote/fundamental_fieldnames.rb +16 -0
  116. data/lib/dtn/streaming/requests/quote/news_switch.rb +18 -0
  117. data/lib/dtn/streaming/requests/quote/refresh.rb +29 -0
  118. data/lib/dtn/streaming/requests/quote/regional_switch.rb +26 -0
  119. data/lib/dtn/streaming/requests/quote/set_client_name.rb +16 -0
  120. data/lib/dtn/streaming/requests/quote/set_protocol.rb +16 -0
  121. data/lib/dtn/streaming/requests/quote/timestamp.rb +21 -0
  122. data/lib/dtn/streaming/requests/quote/timestamp_switch.rb +18 -0
  123. data/lib/dtn/streaming/requests/quote/trades.rb +21 -0
  124. data/lib/dtn/streaming/requests/quote/unwatch.rb +22 -0
  125. data/lib/dtn/streaming/requests/quote/unwatch_all.rb +16 -0
  126. data/lib/dtn/streaming/requests/quote/update_fields.rb +40 -0
  127. data/lib/dtn/streaming/requests/quote/watch.rb +22 -0
  128. data/lib/dtn/streaming/requests/quote/watches.rb +21 -0
  129. data/lib/dtn/version.rb +1 -1
  130. data/lib/ext/business_day.rb +15 -0
  131. data/lib/tasks/spec_date.rake +13 -0
  132. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dtn
4
+ module Lookups
5
+ module Symbol
6
+ # Symbol lookup
7
+ class Base < Request
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dtn
4
+ module Messages
5
+ module Bar
6
+ # Current (live) messages for bar request
7
+ class CurrentBar < Base
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dtn
4
+ module Messages
5
+ module Bar
6
+ # Historical messages for bar request
7
+ class HistoricalBar < Base
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dtn
4
+ module Messages
5
+ module Bar
6
+ # Update messages for bar request
7
+ class UpdateBar < Base
8
+ end
9
+ end
10
+ end
11
+ 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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dtn
4
+ module Messages
5
+ module Catalog
6
+ class NaicCodes < Code
7
+ end
8
+ end
9
+ end
10
+ 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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dtn
4
+ module Messages
5
+ module Catalog
6
+ class SicCodes < Code
7
+ end
8
+ end
9
+ end
10
+ 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