cotcube-bardata 0.1.2 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,16 +2,16 @@
2
2
 
3
3
  module Cotcube
4
4
  module Bardata
5
-
6
5
  SYMBOL_EXAMPLES = [
7
- { id: "13874U", symbol: "ET", ticksize: 0.25, power: 1.25, months: "HMUZ", bcf: 1.0, reports: "LF", name: "S&P 500 MICRO" },
8
- { id: "209747", symbol: "NM", ticksize: 0.25, power: 0.5, monhts: "HMUZ", bcf: 1.0, reports: "LF", name: "NASDAQ 100 MICRO" }
6
+ { id: '13874U', symbol: 'ET', ticksize: 0.25, power: 1.25, months: 'HMUZ', bcf: 1.0, reports: 'LF',
7
+ name: 'S&P 500 MICRO' },
8
+ { id: '209747', symbol: 'NM', ticksize: 0.25, power: 0.5, months: 'HMUZ', bcf: 1.0, reports: 'LF',
9
+ name: 'NASDAQ 100 MICRO' }
9
10
  ].freeze
10
11
 
11
- MONTH_COLOURS = { 'F' => :cyan, 'G' => :green, 'H' => :light_green,
12
- 'J' => :blue, 'K' => :yellow, 'M' => :light_yellow,
13
- 'N' => :cyan, 'Q' => :magenta, 'U' => :light_magenta,
14
- 'V' => :blue, 'X' => :red, 'Z' => :light_red }.freeze
15
-
12
+ MONTH_COLOURS = { 'F' => :cyan, 'G' => :green, 'H' => :light_green,
13
+ 'J' => :blue, 'K' => :yellow, 'M' => :light_yellow,
14
+ 'N' => :cyan, 'Q' => :magenta, 'U' => :light_magenta,
15
+ 'V' => :blue, 'X' => :red, 'Z' => :light_red }.freeze
16
16
  end
17
17
  end
@@ -1,92 +1,154 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cotcube
4
+ # Missing top level documentation comment
4
5
  module Bardata
5
-
6
6
  # just reads bardata/daily/<id>/<contract>.csv
7
- def provide_daily(symbol: nil, id: nil, contract:, timezone: Time.find_zone('America/Chicago'), config: init)
7
+ def provide_daily(contract:, # rubocop:disable Metrics/ParameterLists
8
+ symbol: nil, id: nil,
9
+ range: nil,
10
+ timezone: Time.find_zone('America/Chicago'),
11
+ config: init)
8
12
  contract = contract.to_s.upcase
9
- raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'" unless contract.is_a? String and [3,5].include? contract.size
10
- if contract.to_s.size == 5
11
- symbol = contract[0..1]
12
- contract = contract[2..4]
13
+ unless contract.is_a?(String) && [3, 5].include?(contract.size)
14
+ raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
15
+ end
16
+ unless range.nil? ||
17
+ (range.is_a?(Range) &&
18
+ [Date, DateTime, ActiveSupport::TimeWithZone].map do |cl|
19
+ (range.begin.nil? || range.begin.is_a?(cl)) &&
20
+ (range.end.nil? || range.end.is_a?(cl))
21
+ end.reduce(:|))
22
+
23
+ raise ArgumentError, 'Range, if given, must be either (Integer..Integer) or (Timelike..Timelike)'
13
24
  end
14
- unless symbol.nil?
15
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
16
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
17
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
18
- id = symbol_id
25
+
26
+ unless range.nil?
27
+ range_begin = range.begin.nil? ? nil : timezone.parse(range.begin.to_s)
28
+ range_end = range.end.nil? ? nil : timezone.parse(range.end.to_s)
29
+ range = (range_begin..range_end)
19
30
  end
20
- raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
31
+
32
+ sym = get_id_set(symbol: symbol, id: id, contract: contract)
33
+ contract = contract[2..4] if contract.to_s.size == 5
34
+ id = sym[:id]
21
35
  id_path = "#{config[:data_path]}/daily/#{id}"
22
36
  data_file = "#{id_path}/#{contract}.csv"
23
- raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
24
- raise RuntimeError, "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
25
- data = CSV.read(data_file, headers: %i[contract date open high low close volume oi] ).map do |row|
37
+ raise "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
38
+
39
+ raise "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
40
+
41
+ data = CSV.read(data_file, headers: %i[contract date open high low close volume oi]).map do |row|
26
42
  row = row.to_h
27
- row.each do |k, _|
28
- row[k] = row[k].to_f if [:open, :high, :low, :close].include? k
29
- row[k] = row[k].to_i if [:volume, :oi].include? k
43
+ row.each do |k, _|
44
+ row[k] = row[k].to_f if %i[open high low close].include? k
45
+ row[k] = row[k].to_i if %i[volume oi].include? k
30
46
  end
31
47
  row[:datetime] = timezone.parse(row[:date])
48
+ row[:type] = :daily
32
49
  row
33
50
  end
34
- data
51
+ data.pop if data.last[:high].zero?
52
+ if range.nil?
53
+ data
54
+ else
55
+ data.select do |x|
56
+ (range.begin.nil? ? true : x[:datetime] >= range.begin) and
57
+ (range.end.nil? ? true : x[:datetime] <= range.end)
58
+ end
59
+ end
35
60
  end
36
-
37
- # reads all files in bardata/daily/<id> and aggregates by date (what is a pre-stage of a continuous based on daily bars)
61
+
62
+ # reads all files in bardata/daily/<id> and aggregates by date
63
+ # (what is a pre-stage of a continuous based on daily bars)
38
64
  def continuous(symbol: nil, id: nil, config: init, date: nil)
39
- unless symbol.nil?
40
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
41
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
42
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
43
- id = symbol_id
44
- end
45
- raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
46
- id_path = "#{config[:data_path]}/daily/#{id}"
47
- available_contracts = Dir[id_path + '/*.csv'].map{|x| x.split('/').last.split('.').first}.sort_by{|x| x[-7]}.sort_by{|x| x[-6..-5]}
48
- data = []
49
- available_contracts.each do |c|
50
- provide_daily(id: id, config: config, contract: c).each do |x|
65
+ sym = get_id_set(symbol: symbol, id: id)
66
+ id = sym[:id]
67
+ id_path = "#{config[:data_path]}/daily/#{id}"
68
+ available_contracts = Dir["#{id_path}/*.csv"].map { |x| x.split('/').last.split('.').first }
69
+ available_contracts.sort_by! { |x| x[-7] }.sort_by! { |x| x[-6..-5] }
70
+ data = []
71
+ available_contracts.each do |c|
72
+ provide_daily(id: id, config: config, contract: c).each do |x|
51
73
  data << x
52
74
  end
53
75
  end
54
- result = []
55
- data.sort_by{|x| x[:date]}.group_by{|x| x[:date]}.map{|k,v|
56
- v.map{|x| x.delete(:date)}
76
+ result = []
77
+ data.sort_by { |x| x[:date] }.group_by { |x| x[:date] }.map do |k, v|
78
+ v.map { |x| x.delete(:date) }
57
79
  result << {
58
80
  date: k,
59
- volume: v.map{|x| x[:volume]}.reduce(:+),
60
- oi: v.map{|x| x[:oi ]}.reduce(:+)
81
+ volume: v.map { |x| x[:volume] }.reduce(:+),
82
+ oi: v.map { |x| x[:oi] }.reduce(:+)
61
83
  }
62
84
  result.last[:contracts] = v
63
- }
64
- date.nil? ? result : result.select{|x| x[:date] == date}.first
85
+ end
86
+ date.nil? ? result : result.select { |x| x[:date] == date }.first
65
87
  end
66
88
 
67
- # based on .continuous, this methods sorts the prepared dailies continuous for each date on either :volume (default) or :oi
89
+ def continuous_ml(symbol: nil, id: nil, base: nil)
90
+ (base.nil? ? Cotcube::Bardata.continuous(symbol: symbol, id: id) : base).map do |x|
91
+ x[:ml] = x[:contracts].max_by { |z| z[:volume] }[:contract]
92
+ { date: x[:date], ml: x[:ml] }
93
+ end
94
+ end
95
+
96
+ # the method above delivers the most_liquid as it is found at the end of the day. D
97
+ # during trading, the work is done with data
98
+ # that is already one day old. This is is fixed here:
99
+ def continuous_actual_ml(symbol: nil, id: nil)
100
+ continuous = Cotcube::Bardata.continuous symbol: symbol, id: id
101
+ continuous_ml = Cotcube::Bardata.continuous_ml base: continuous
102
+ continuous_hash = continuous.to_h { |x| [x[:date], x[:contracts]] }
103
+ actual_ml = continuous_ml.pairwise { |a, b| { date: b[:date], ml: a[:ml] } }
104
+ actual_ml.map do |x|
105
+ r = continuous_hash[x[:date]].select { |z| x[:ml] == z[:contract] }.first
106
+ r = continuous_hash[x[:date]].min_by { |z| -z[:volume] } if r.nil?
107
+ r
108
+ end
109
+ end
110
+
111
+ # based on .continuous, this methods sorts the prepared dailies continuous for each date
112
+ # on either :volume (default) or :oi
68
113
  # with this job done, it can provide the period for which a past contract was the most liquid
69
114
  #
70
- def continuous_overview(symbol: nil, id: nil, config: init, selector: :volume, human: false, filter: nil)
71
- raise ArgumentError, "Selector must be either :volume or :oi" unless selector.is_a? Symbol and [:volume, :oi].include? selector
72
-
73
- unless symbol.nil?
74
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
75
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
76
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
77
- id = symbol_id
78
- end
79
- raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
80
- data = continuous(id: id, config: config).map{|x|
115
+ def continuous_overview(symbol: nil, id: nil, # rubocop:disable Metrics/ParameterLists
116
+ config: init,
117
+ selector: :volume,
118
+ human: false,
119
+ filter: nil)
120
+ raise ArgumentError, 'Selector must be either :volume or :oi' unless selector.is_a?(Symbol) &&
121
+ %i[volume oi].include?(selector)
122
+
123
+ sym = get_id_set(symbol: symbol, id: id)
124
+ id = sym[:id]
125
+ # noinspection RubyNilAnalysis
126
+ data = continuous(id: id, config: config).map do |x|
81
127
  {
82
- date: x[:date],
83
- volume: x[:contracts].sort_by{|x| - x[:volume]}[0..4].compact.select{|x| not x[:volume].zero?},
84
- oi: x[:contracts].sort_by{|x| - x[:oi]}[0..4].compact.select{|x| not x[:oi].zero?}
128
+ date: x[:date],
129
+ volume: x[:contracts].sort_by { |z| - z[:volume] }[0..4].compact.reject { |z| z[:volume].zero? },
130
+ oi: x[:contracts].sort_by { |z| - z[:oi] }[0..4].compact.reject { |z| z[:oi].zero? }
85
131
  }
86
- }.select{|x| not x[selector].empty? }
87
- result = data.group_by{|x| x[selector].first[:contract]}
132
+ end
133
+ data.reject! { |x| x[selector].empty? }
134
+ result = data.group_by { |x| x[selector].first[:contract] }
88
135
  if human
89
- result.each {|k,v| puts "#{k}\t#{v.first[:date]}\t#{v.last[:date]}" if filter.nil? or v.first[selector].first[:contract][2..4]=~ /#{filter}/ }
136
+ result.each do |k, v|
137
+ next unless filter.nil? || v.first[selector].first[:contract][2..4] =~ (/#{filter}/)
138
+
139
+ # rubocop:disable Layout/ClosingParenthesisIndentation
140
+ puts "#{k
141
+ }\t#{v.first[:date]
142
+ }\t#{v.last[:date]
143
+ }\t#{format('%4d', (Date.parse(v.last[:date]) - Date.parse(v.first[:date])))
144
+ }\t#{result[k].map do |x|
145
+ x[:volume].select do
146
+ x[:contract] == k
147
+ end
148
+ end.size
149
+ }"
150
+ # rubocop:enable Layout/ClosingParenthesisIndentation
151
+ end
90
152
  end
91
153
  result
92
154
  end
@@ -1,79 +1,122 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cotcube
4
+ # Missing top level documentation
4
5
  module Bardata
5
-
6
6
  def most_liquid_for(symbol: nil, id: nil, date: last_trade_date, config: init)
7
- unless symbol.nil?
8
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
9
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
10
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
11
- id = symbol_id
12
- end
13
- raise ArgumentError, "Need :id or :symbol." if id.nil?
7
+ id = get_id_set(symbol: symbol, id: id, config: config)[:id]
14
8
  provide_eods(id: id, dates: date, contracts_only: true).first
15
9
  end
16
10
 
17
-
18
-
19
- def provide_eods(symbol: nil,
20
- id: nil,
21
- contract: nil,
22
- config: init,
23
- dates: last_trade_date, # should accept either a date or datelike or date string OR a range of 2 datelike
24
- # if omitted returns the eods of last trading date
25
- threshold: 0.1, # set threshold to 0 to disable filtering at all. otherwise only contracts with partial of >= threshold are returned
26
- filter: :volume_part, # filter can be set to volume_part and oi_part. determines, which property is used for filtering.
27
- contracts_only: true # set to false to return the complete row instead of just the contracts matching filter and threshold
28
- )
29
- raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'" unless contract.nil? or (contract.is_a? String and [3,5].include? contract.size)
30
- if contract.to_s.size == 5
31
- symbol = contract[0..1]
32
- contract = contract[2..4]
11
+ # the following method seems to be garbage. It is not used anywhere. It seems it's purpose
12
+ # was to retrieve a list of quarters that have not been fetched recently (--> :age)
13
+ def provide_most_liquids_by_eod(symbol: nil, id: nil, # rubocop:disable Metrics/ParameterLists
14
+ config: init,
15
+ date: last_trade_date,
16
+ filter: :volume_part,
17
+ age: 1.hour)
18
+ sym = get_id_set(symbol: symbol, id: id) if symbol || id
19
+ # noinspection RubyScope
20
+ eods = provide_eods(id: sym.nil? ? nil : sym[:id], config: config, dates: date, filter: filter)
21
+ result = []
22
+ eods.map do |eod|
23
+ symbol = eod[0..1]
24
+ contract = eod[2..4]
25
+ sym = symbols.select { |s| s[:symbol] == symbol.to_s.upcase }.first
26
+ quarter = "#{config[:data_path]}/quarters/#{sym[:id]}/#{contract}.csv"
27
+ if File.exist?(quarter)
28
+ # puts "#{quarter}: #{ Time.now } - #{File.mtime(quarter)} > #{age} : #{Time.now - File.mtime(quarter) > age}"
29
+ result << eod if Time.now - File.mtime(quarter) > age
30
+ else
31
+ result << eod
32
+ end
33
33
  end
34
- unless symbol.nil?
35
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
36
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
37
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
38
- id = symbol_id
34
+ result
35
+ end
36
+
37
+ # provide a list of all eods for id/symbol or all symbols (default) for an
38
+ # array of dates (default: [last_trade_date])
39
+ #
40
+ # filter by :threshold*100% share on entire volume(default) or oi
41
+ #
42
+ # return full data or just the contract name (default)
43
+ def provide_eods(symbol: nil, # rubocop:disable Metrics/ParameterLists
44
+ id: nil,
45
+ contract: nil,
46
+ config: init,
47
+ # should accept either a date or date_alike or date string OR a range of 2 dates alike
48
+ # if omitted returns the eods of last trading date
49
+ dates: last_trade_date,
50
+ # set threshold to 0 to disable filtering at all.
51
+ # otherwise only contracts with partial of >= threshold are returned
52
+ threshold: 0.05,
53
+ # filter can be set to volume_part and oi_part.
54
+ # determines, which property is used for filtering.
55
+ filter: :volume_part,
56
+ # set to false to return the complete row instead
57
+ # of just the contracts matching filter and threshold
58
+ contracts_only: true,
59
+ quiet: false)
60
+ unless contract.nil? || (contract.is_a?(String) && [3, 5].include?(contract.size))
61
+ raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
39
62
  end
63
+
64
+ symbol = contract[0..1] if contract.to_s.size == 5
65
+ sym = get_id_set(symbol: symbol, id: id, config: config) if symbol || id
40
66
  # if no id can be clarified from given arguments, return all matching contracts from all available symbols
41
67
  # raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
42
- raise ArgumentError, ":filter must be in [:volume_part, :oi_part]" unless [:volume_part, :oi_part].include? filter
68
+ raise ArgumentError, ':filter must be in [:volume_part, :oi_part]' unless %i[volume_part oi_part].include? filter
43
69
 
44
- ids = id.nil? ? symbols.map{|x| x[:id]} : [ id ]
45
- dates = [ dates ] unless dates.is_a? Array or dates.nil?
70
+ # noinspection RubyScope
71
+ ids = sym.nil? ? symbols.map { |x| x[:id] } : [sym[:id]]
72
+ dates = [dates] unless dates.is_a?(Array) || dates.nil?
46
73
 
47
- id_path_get = lambda {|_id| "#{config[:data_path]}/eods/#{_id}" }
74
+ id_path_get = ->(local_id) { "#{config[:data_path]}/eods/#{local_id}" }
48
75
 
49
- process_date_for_id = lambda do |d,i|
50
- sym = symbols.select{|s| s[:id] == i}.first
51
- symbol = sym[:symbol]
76
+ process_date_for_id = lambda do |d, i|
77
+ # l_sym = symbols.select { |s| s[:id] == i }.first
78
+ # l_symbol = l_sym[:symbol]
52
79
  id_path = id_path_get.call(i)
53
80
  data_file = "#{id_path}/#{d}.csv"
54
- raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
55
- raise RuntimeError, "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
56
- data = CSV.read(data_file, headers: %i[contract date open high low close volume oi] ).map do |row|
81
+ raise "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
82
+
83
+ unless File.exist?(data_file)
84
+ unless quiet
85
+ puts 'WARNING: No data found for requested id/symbol'\
86
+ " #{id}/#{symbol} in #{id_path} for #{d}.".colorize(:light_yellow)
87
+ end
88
+ return []
89
+ end
90
+ data = CSV.read(data_file, headers: %i[contract date open high low close volume oi]).map do |row|
57
91
  row = row.to_h
58
- row.each do |k, _|
59
- row[k] = row[k].to_f if [:open, :high, :low, :close].include? k
60
- row[k] = row[k].to_i if [:volume, :oi].include? k
92
+ row.each do |k, _|
93
+ row[k] = row[k].to_f if %i[open high low close].include? k
94
+ row[k] = row[k].to_i if %i[volume oi].include? k
61
95
  end
62
96
  row
63
97
  end
64
- all_volume = data.map{|x| x[:volume] }.reduce(:+)
65
- all_oi = data.map{|x| x[:oi] }.reduce(:+)
66
- data.map{|x| x[:volume_part] = (x[:volume] / all_volume.to_f).round(4); x[:oi_part] = (x[:oi] / all_oi.to_f).round(4) }
67
- data.select{|x| x[filter] >= threshold}.sort_by{|x| -x[filter]}.tap{|x| x.map!{|y| y[:contract]} if contracts_only}
98
+ all_volume = data.map { |x| x[:volume] }.reduce(:+)
99
+ all_oi = data.map { |x| x[:oi] }.reduce(:+)
100
+ data.map do |x|
101
+ x[:volume_part] = (x[:volume] / all_volume.to_f).round(4)
102
+ x[:oi_part] = (x[:oi] / all_oi.to_f).round(4)
103
+ end
104
+ data.select { |x| x[filter] >= threshold }.sort_by { |x| -x[filter] }.tap do |x|
105
+ if contracts_only
106
+ x.map! do |y|
107
+ y[:contract]
108
+ end
109
+ end
110
+ end
68
111
  end
69
112
  if dates
70
- dates.map do |date|
71
- ids.map{|id| process_date_for_id.call(date, id) }
113
+ dates.map do |date|
114
+ ids.map { |local_id| process_date_for_id.call(date, local_id) }
72
115
  end.flatten
73
116
  else
74
- raise ArgumentError, "Sorry, support for unlimited dates is not implemented yet. Please send array of dates or single date"
117
+ raise ArgumentError,
118
+ 'Sorry, support for unlimited dates is not implemented yet. Please send array of dates or single date'
75
119
  end
76
120
  end
77
-
78
121
  end
79
122
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cotcube
4
+ # Missing top level documentation comment
5
+ module Bardata
6
+ # small helper to select a specific full trading day from quarters (or reduced)
7
+ # this special handling is needed, as full trading days start '5pm CT yesterday'
8
+ def select_specific_date(date:, base:)
9
+ base.select do |d|
10
+ d[:day] == date.day and date.year == d[:datetime].year and (
11
+ if date.day > 1
12
+ date.month == d[:datetime].month
13
+ else
14
+ ((date.month == d[:datetime].month and d[:datetime].day == 1) or
15
+ (date.month == d[:datetime].month + 1 and d[:datetime].day > 25))
16
+ end
17
+ )
18
+ end
19
+ end
20
+
21
+ # diminishes a given base of bars to fit into a given range (DO NOT CONFUSE with trading_hours)
22
+ # note that the last bar is simply required to _start_ within the given range, not to end withing
23
+ def extended_select_for_range(base:,
24
+ range: ('1900-01-01'...'2100-01-01'),
25
+ timezone: Time.find_zone('America/Chicago'),
26
+ quiet: false)
27
+
28
+ starting = range.begin
29
+ starting = timezone.parse(starting) if starting.is_a? String
30
+ ending = range.end
31
+ ending = timezone.parse(ending) if ending.is_a? String
32
+ puts "#{starting}\t#{ending}" unless quiet
33
+ if starting.hour.zero? && starting.min.zero? && ending.hour.zero? && ending.min.zero?
34
+ unless quiet
35
+ puts 'WARNING: When sending midnight, full trading day'\
36
+ ' is assumed (starting 5 pm CT yesterday, ending 4 pm CT today)'.colorize(:light_yellow)
37
+ end
38
+ result = select_specific_date(date: starting, base: base)
39
+ result += base.select { |d| d[:datetime] > starting and d[:datetime] < ending.to_date }
40
+ result += select_specific_date(date: ending, base: base)
41
+ result.uniq!
42
+ else
43
+ result = base.select { |x| x[:datetime] >= starting and x[:datetime] < ending }
44
+ end
45
+ result
46
+ end
47
+
48
+ def get_id_set(symbol: nil, id: nil, contract: nil, config: init)
49
+ if contract.is_a?(String) && (contract.length == 5)
50
+ c_symbol = contract[0..1]
51
+ if (not symbol.nil?) && (symbol != c_symbol)
52
+ raise ArgumentError,
53
+ "Mismatch between given symbol #{symbol} and contract #{contract}"
54
+ end
55
+
56
+ symbol = c_symbol
57
+ end
58
+
59
+ unless symbol.nil?
60
+ sym = symbols.select { |s| s[:symbol] == symbol.to_s.upcase }.first
61
+ if sym.nil? || sym[:id].nil?
62
+ raise ArgumentError,
63
+ "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}"
64
+ end
65
+ raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if (not id.nil?) && (sym[:id] != id)
66
+
67
+ return sym
68
+ end
69
+ unless id.nil?
70
+ sym = symbols.select { |s| s[:id] == id.to_s }.first
71
+ if sym.nil? || sym[:id].nil?
72
+ raise ArgumentError,
73
+ "Could not find match in #{config[:symbols_file]} for given id #{id}"
74
+ end
75
+ return sym
76
+ end
77
+ raise ArgumentError, 'Need :id, :symbol or valid :contract '
78
+ end
79
+
80
+ def compare(contract:, format: '%5.2f')
81
+ format = "%#{format}" unless format[0] == '%'
82
+ daily = provide(contract: contract, interval: :daily)
83
+ full = provide(contract: contract, interval: :days, filter: :full)
84
+ rth = provide(contract: contract, interval: :days, filter: :rth)
85
+ rth_dates = rth.map { |x| x[:datetime] }
86
+ daily.select! { |x| rth_dates.include? x[:datetime].to_datetime }
87
+ full.select! { |x| rth_dates.include? x[:datetime].to_datetime }
88
+
89
+ printer = lambda { |z|
90
+ # rubocop:disable Layout/ClosingParenthesisIndentation
91
+ "#{z[:datetime].strftime('%m-%d') # rubocop:disable Layout/IndentationWidth
92
+ }\t#{format format, z[:open]
93
+ }\t#{format format, z[:high]
94
+ }\t#{format format, z[:low]
95
+ }\t#{format format, z[:close]
96
+ }\t#{format '%7d', z[:volume]}"
97
+ # rubocop:enable Layout/ClosingParenthesisIndentation
98
+ }
99
+ daily.each_with_index do |_x, i|
100
+ puts "DAILY #{printer.call daily[i]}"
101
+ puts "FULL #{printer.call full[i]}"
102
+ puts "RTH #{printer.call rth[i]}"
103
+ puts ' '
104
+ end
105
+ end
106
+ end
107
+ end