cotcube-bardata 0.1.2 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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