cotcube-bardata 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,29 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cotcube
4
+ # Missing top level documentation
4
5
  module Bardata
5
-
6
- def most_liquid_for(symbol: nil, id: nil, date: last_trade_date, config: init, quiet: false)
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?
6
+ def most_liquid_for(symbol: nil, id: nil, date: last_trade_date, config: init)
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
- def provide_most_liquids_by_eod(config: init, date: last_trade_date, filter: :volume_part, age: 1.hour)
18
- eods = provide_eods(config: config, dates: date, filter: filter)
19
- result = []
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 or id
19
+ eods = provide_eods(id: sym.nil? ? nil : sym[:id], config: config, dates: date, filter: filter)
20
+ result = []
20
21
  eods.map do |eod|
21
22
  symbol = eod[0..1]
22
- contract = eod[2..4]
23
- sym = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first
23
+ contract = eod[2..4]
24
+ sym = symbols.select { |s| s[:symbol] == symbol.to_s.upcase }.first
24
25
  quarter = "#{config[:data_path]}/quarters/#{sym[:id]}/#{contract}.csv"
25
26
  if File.exist?(quarter)
26
- puts "#{quarter}: #{ Time.now } - #{File.mtime(quarter)} > #{age} : #{Time.now - File.mtime(quarter) > age}"
27
+ # puts "#{quarter}: #{ Time.now } - #{File.mtime(quarter)} > #{age} : #{Time.now - File.mtime(quarter) > age}"
27
28
  result << eod if Time.now - File.mtime(quarter) > age
28
29
  else
29
30
  result << eod
@@ -32,67 +33,89 @@ module Cotcube
32
33
  result
33
34
  end
34
35
 
35
- def provide_eods(symbol: nil,
36
- id: nil,
37
- contract: nil,
38
- config: init,
39
- dates: last_trade_date, # should accept either a date or datelike or date string OR a range of 2 datelike
36
+ # provide a list of all eods for id/symbol or all symbols (default) for an
37
+ # array of dates (default: [last_trade_date])
38
+ #
39
+ # filter by :threshold*100% share on entire volume(default) or oi
40
+ #
41
+ # return full data or just the contract name (default)
42
+ def provide_eods(symbol: nil, # rubocop:disable Metrics/ParameterLists
43
+ id: nil,
44
+ contract: nil,
45
+ config: init,
46
+ # should accept either a date or date_alike or date string OR a range of 2 dates alike
40
47
  # if omitted returns the eods of last trading date
41
- threshold: 0.1, # set threshold to 0 to disable filtering at all. otherwise only contracts with partial of >= threshold are returned
42
- filter: :volume_part, # filter can be set to volume_part and oi_part. determines, which property is used for filtering.
43
- contracts_only: true # set to false to return the complete row instead of just the contracts matching filter and threshold
44
- )
45
- 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)
46
- if contract.to_s.size == 5
47
- symbol = contract[0..1]
48
- contract = contract[2..4]
49
- end
50
- unless symbol.nil?
51
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
52
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
53
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
54
- id = symbol_id
48
+ dates: last_trade_date,
49
+ # set threshold to 0 to disable filtering at all.
50
+ # otherwise only contracts with partial of >= threshold are returned
51
+ threshold: 0.05,
52
+ # filter can be set to volume_part and oi_part.
53
+ # determines, which property is used for filtering.
54
+ filter: :volume_part,
55
+ # set to false to return the complete row instead
56
+ # of just the contracts matching filter and threshold
57
+ contracts_only: true,
58
+ quiet: false)
59
+ unless contract.nil? || (contract.is_a?(String) && [3, 5].include?(contract.size))
60
+ raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
55
61
  end
62
+
63
+ symbol = contract[0..1] if contract.to_s.size == 5
64
+ sym = get_id_set(symbol: symbol, id: id, config: config) if symbol || id
56
65
  # if no id can be clarified from given arguments, return all matching contracts from all available symbols
57
66
  # raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
58
- raise ArgumentError, ":filter must be in [:volume_part, :oi_part]" unless [:volume_part, :oi_part].include? filter
67
+ raise ArgumentError, ':filter must be in [:volume_part, :oi_part]' unless %i[volume_part oi_part].include? filter
59
68
 
60
- ids = id.nil? ? symbols.map{|x| x[:id]} : [ id ]
61
- dates = [ dates ] unless dates.is_a? Array or dates.nil?
69
+ # noinspection RubyScope
70
+ ids = sym.nil? ? symbols.map { |x| x[:id] } : [sym[:id]]
71
+ dates = [dates] unless dates.is_a?(Array) || dates.nil?
62
72
 
63
- id_path_get = lambda {|_id| "#{config[:data_path]}/eods/#{_id}" }
73
+ id_path_get = ->(local_id) { "#{config[:data_path]}/eods/#{local_id}" }
64
74
 
65
- process_date_for_id = lambda do |d,i|
66
- sym = symbols.select{|s| s[:id] == i}.first
67
- symbol = sym[:symbol]
75
+ process_date_for_id = lambda do |d, i|
76
+ # l_sym = symbols.select { |s| s[:id] == i }.first
77
+ # l_symbol = l_sym[:symbol]
68
78
  id_path = id_path_get.call(i)
69
79
  data_file = "#{id_path}/#{d}.csv"
70
- raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
80
+ raise "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
81
+
71
82
  unless File.exist?(data_file)
72
- puts "WARNING: No data found for requested id/symbol #{id}/#{symbol} in #{id_path}.".light_yellow unless quiet
83
+ unless quiet
84
+ puts 'WARNING: No data found for requested id/symbol'\
85
+ " #{id}/#{symbol} in #{id_path} for #{d}.".colorize(:light_yellow)
86
+ end
73
87
  return []
74
88
  end
75
- data = CSV.read(data_file, headers: %i[contract date open high low close volume oi] ).map do |row|
89
+ data = CSV.read(data_file, headers: %i[contract date open high low close volume oi]).map do |row|
76
90
  row = row.to_h
77
- row.each do |k, _|
78
- row[k] = row[k].to_f if [:open, :high, :low, :close].include? k
79
- row[k] = row[k].to_i if [:volume, :oi].include? k
91
+ row.each do |k, _|
92
+ row[k] = row[k].to_f if %i[open high low close].include? k
93
+ row[k] = row[k].to_i if %i[volume oi].include? k
80
94
  end
81
95
  row
82
96
  end
83
- all_volume = data.map{|x| x[:volume] }.reduce(:+)
84
- all_oi = data.map{|x| x[:oi] }.reduce(:+)
85
- 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) }
86
- data.select{|x| x[filter] >= threshold}.sort_by{|x| -x[filter]}.tap{|x| x.map!{|y| y[:contract]} if contracts_only}
97
+ all_volume = data.map { |x| x[:volume] }.reduce(:+)
98
+ all_oi = data.map { |x| x[:oi] }.reduce(:+)
99
+ data.map do |x|
100
+ x[:volume_part] = (x[:volume] / all_volume.to_f).round(4)
101
+ x[:oi_part] = (x[:oi] / all_oi.to_f).round(4)
102
+ end
103
+ data.select { |x| x[filter] >= threshold }.sort_by { |x| -x[filter] }.tap do |x|
104
+ if contracts_only
105
+ x.map! do |y|
106
+ y[:contract]
107
+ end
108
+ end
109
+ end
87
110
  end
88
111
  if dates
89
- dates.map do |date|
90
- ids.map{|id| process_date_for_id.call(date, id) }
112
+ dates.map do |date|
113
+ ids.map { |local_id| process_date_for_id.call(date, local_id) }
91
114
  end.flatten
92
115
  else
93
- raise ArgumentError, "Sorry, support for unlimited dates is not implemented yet. Please send array of dates or single date"
116
+ raise ArgumentError,
117
+ 'Sorry, support for unlimited dates is not implemented yet. Please send array of dates or single date'
94
118
  end
95
119
  end
96
-
97
120
  end
98
121
  end
@@ -0,0 +1,104 @@
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, type: :daily)
83
+ full = provide(contract: contract, type: :days, set: :full)
84
+ rth = provide(contract: contract, type: :days, set: :rth)
85
+ rth_dates = rth.map{|x| x[:datetime] }
86
+ daily.select!{ |x| rth_dates.include? x[:datetime] }
87
+ full.select!{ |x| rth_dates.include? x[:datetime] }
88
+
89
+ printer = lambda {|z| "#{z[:datetime].strftime("%m-%d")
90
+ }\t#{format format, z[:open]
91
+ }\t#{format format, z[:high]
92
+ }\t#{format format, z[:low]
93
+ }\t#{format format, z[:close]
94
+ }\t#{format '%7d', z[:volume]}" }
95
+ daily.each_with_index do |x, i|
96
+ puts "DAILY #{printer.call daily[i]}"
97
+ puts "FULL #{printer.call full[i]}"
98
+ puts "RTH #{printer.call rth[i]}"
99
+ puts " "
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -1,81 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cotcube
4
+ # Missing top level documentation comment
4
5
  module Bardata
5
-
6
-
7
- def symbols(config: init, type: nil)
6
+ def symbols(config: init, type: nil, symbol: nil)
8
7
  if config[:symbols_file].nil?
9
8
  SYMBOL_EXAMPLES
10
9
  else
11
10
  CSV
12
- .read(config[:symbols_file], headers: %i{ id symbol ticksize power months type bcf reports name})
13
- .map{|row| row.to_h }
14
- .map{|row| [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f}; row }
15
- .reject{|row| row[:id].nil? }
16
- .tap{|all| all.select!{|x| x[:type] == type} unless type.nil? }
11
+ .read(config[:symbols_file], headers: %i[id symbol ticksize power months type bcf reports name])
12
+ .map(&:to_h)
13
+ .map { |row| %i[ticksize power bcf].each { |z| row[z] = row[z].to_f }; row } # rubocop:disable Style/Semicolon
14
+ .reject { |row| row[:id].nil? }
15
+ .tap { |all| all.select! { |x| x[:type] == type } unless type.nil? }
16
+ .tap { |all| all.select! { |x| x[:symbol] == symbol } unless symbol.nil? }
17
17
  end
18
18
  end
19
19
 
20
20
  def config_prefix
21
- os = Gem::Platform.local.os
21
+ os = Gem::Platform.local.os
22
22
  case os
23
23
  when 'linux'
24
24
  ''
25
25
  when 'freebsd'
26
26
  '/usr/local'
27
27
  else
28
- raise RuntimeError, 'unknown architecture'
28
+ raise 'unknown architecture'
29
29
  end
30
30
  end
31
31
 
32
32
  def config_path
33
- config_prefix + '/etc/cotcube'
33
+ "#{config_prefix}/etc/cotcube"
34
34
  end
35
35
 
36
- def init(config_file_name: 'bardata.yml', debug: false)
36
+ def init(config_file_name: 'bardata.yml')
37
37
  name = 'bardata'
38
38
  config_file = config_path + "/#{config_file_name}"
39
39
 
40
- if File.exist?(config_file)
41
- config = YAML.load(File.read config_file).transform_keys(&:to_sym)
42
- else
43
- config = {}
44
- end
40
+ config = if File.exist?(config_file)
41
+ YAML.safe_load(File.read(config_file)).transform_keys(&:to_sym)
42
+ else
43
+ {}
44
+ end
45
45
 
46
- defaults = {
47
- data_path: config_prefix + '/var/cotcube/' + name,
46
+ defaults = {
47
+ data_path: "#{config_prefix}/var/cotcube/#{name}"
48
48
  }
49
49
 
50
-
51
50
  config = defaults.merge(config)
52
51
 
53
-
54
52
  # part 2 of init process: Prepare directories
55
53
 
56
54
  save_create_directory = lambda do |directory_name|
57
55
  unless Dir.exist?(directory_name)
58
56
  begin
59
57
  `mkdir -p #{directory_name}`
60
- unless $?.exitstatus.zero?
58
+ unless $CHILD_STATUS.exitstatus.zero?
61
59
  puts "Missing permissions to create or access '#{directory_name}', please clarify manually"
62
60
  exit 1 unless defined?(IRB)
63
61
  end
64
- rescue
62
+ rescue StandardError
65
63
  puts "Missing permissions to create or access '#{directory_name}', please clarify manually"
66
64
  exit 1 unless defined?(IRB)
67
65
  end
68
66
  end
69
67
  end
70
- ['',:daily,:quarters].each do |path|
71
- dir = "#{config[:data_path]}#{path == '' ? '' : '/'}#{path.to_s}"
68
+ ['', :daily, :quarters, :eods, :trading_hours, :cached].each do |path|
69
+ dir = "#{config[:data_path]}#{path == '' ? '' : '/'}#{path}"
72
70
  save_create_directory.call(dir)
73
71
  end
74
72
 
75
73
  # eventually return config
76
74
  config
77
75
  end
78
-
79
76
  end
80
77
  end
81
-
@@ -3,21 +3,47 @@
3
3
  module Cotcube
4
4
  module Bardata
5
5
 
6
- def provide(symbol: nil, id: nil, contract:, config: init, date: Date.today - 1, type: nil, fill: :none)
6
+ def provide(contract:,
7
+ # Can be like ("2020-12-01 12:00"..."2020-12-14 11:00")
8
+ range: nil,
9
+ symbol: nil, id: nil,
10
+ config: init,
11
+ # supported types are :quarters, :hours, :days, :rth, :dailies, :weeks, :months
12
+ type: :days,
13
+ # supported fills are :raw, :_24x7_, :full and :rth (and custom named, if provided as file)
14
+ set: :full,
15
+ # TODO: for future compatibility and suggestion: planning to include a function to update
16
+ # with live data from broker
17
+ force_recent: false)
18
+
19
+ sym = get_id_set(symbol: symbol, id: id, contract: contract, config: config)
20
+
7
21
  case type
8
- when :eod, :eods
9
- provide_eods(symbol: symbol, id: id, contract: contract, date: date)
10
- when :quarters
11
- print :quarters
12
- when :hours
13
- print :hours
14
- when :daily, :dailies
15
- print :dailies
22
+ when :quarters, :hours, :quarter, :hour
23
+ base = provide_quarters(contract: contract, symbol: symbol, id: id, config: config)
24
+ base = extended_select_for_range(range: range, base: base) if range
25
+ requested_set = trading_hours(symbol: sym[:symbol], set: set)
26
+
27
+ base = base.select_within(ranges: requested_set, attr: :datetime) {|x| x.to_datetime.to_sssm }
28
+ return base if [:quarters, :quarter].include? type
29
+
30
+ base = Cotcube::Helpers.reduce(bars: base, to: :hours){|c,b|
31
+ c[:day] == b[:day] and c[:datetime].hour == b[:datetime].hour
32
+ }
33
+
34
+ when :days, :weeks, :months
35
+ base = provide_cached contract: contract, symbol: symbol, id: id, config: config, set: set, force_recent: force_recent
36
+ base = extended_select_for_range(range: range, base: base) if range
37
+ return base if [:day, :days].include? type
38
+ # TODO: Missing implemetation to reduce cached days to weeks or months
39
+ raise "Missing implementation to reduce cached days to weeks or months"
40
+ when :dailies, :daily
41
+ base = provide_daily contract: contract, symbol: symbol, id: id, config: config
42
+ base = extended_select_for_range(range: range, base: base) if range
43
+ return base
16
44
  else
17
- puts "WARNING: Using provide without :type is for legacy support pointing to .provide_daily".light_yellow
18
- provide_daily(symbol: symbol, id: id, contract: contract, config: config)
45
+ raise ArgumentError, "Unsupported or unknown type '#{type}' in Bardata.provide"
19
46
  end
20
47
  end
21
48
  end
22
-
23
49
  end
@@ -1,80 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cotcube
4
+ # Missing top level documentation
4
5
  module Bardata
6
+ # the following method loads the quarterly bars (15-min bars) from the directory tree
7
+ # also note that a former version of this method allowed to provide range or date parameters. this has been moved
8
+ # to #provide itself.
9
+ def provide_quarters(contract:, # rubocop:disable Metrics/ParameterLists
10
+ symbol: nil, id: nil,
11
+ timezone: Time.find_zone('America/Chicago'),
12
+ config: init,
13
+ keep_marker: false)
5
14
 
6
- def provide_quarters(
7
- symbol: nil, id: nil,
8
- contract:,
9
- as: :quarters,
10
- range: nil, date: nil,
11
- timezone: Time.find_zone('America/Chicago'),
12
- config: init,
13
- quiet: false
14
- )
15
- date = timezone.parse(date) if date.is_a? String
16
- raise ArgumentError, ":range and :date are mutually exclusive" if range and date
17
- raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'" unless contract.is_a? String and [3,5].include? contract.size
18
- if contract.to_s.size == 5
19
- symbol = contract[0..1]
20
- contract = contract[2..4]
15
+ unless contract.is_a?(String) && [3, 5].include?(contract.size)
16
+ raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
21
17
  end
22
- unless symbol.nil?
23
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
24
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
25
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
26
- id = symbol_id
27
- end
28
- raise ArgumentError, ":as can only be in [:quarters, :hours, :days]" unless %i[quarters hours days].include?(as)
29
- raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
18
+
19
+ sym = get_id_set(symbol: symbol, id: id, contract: contract)
20
+
21
+ contract = contract[2..4] if contract.to_s.size == 5
22
+ id = sym[:id]
23
+ symbol = sym[:symbol]
24
+
30
25
  id_path = "#{config[:data_path]}/quarters/#{id}"
31
26
  data_file = "#{id_path}/#{contract}.csv"
32
- raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
33
- raise RuntimeError, "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
34
- data = CSV.read(data_file, headers: %i[contract datetime day open high low close volume]).map do |row|
27
+ raise "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
28
+
29
+ raise "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
30
+
31
+ data = CSV.read(data_file, headers: %i[contract datetime day open high low close volume]).map do |row|
35
32
  row = row.to_h
36
- %i[open high low close].map{|x| row[x] = row[x].to_f}
37
- %i[volume day].map{|x| row[x] = row[x].to_i}
33
+ %i[open high low close].map { |x| row[x] = row[x].to_f }
34
+ %i[volume day].map { |x| row[x] = row[x].to_i }
38
35
  row[:datetime] = timezone.parse(row[:datetime])
36
+ row[:type] = :quarter
39
37
  row
40
38
  end
41
- select_specific_date = lambda do |specific_date|
42
- data.select{|d| d[:day] == specific_date.day and specific_date.year == d[:datetime].year and (
43
- if specific_date.day > 1
44
- specific_date.month == d[:datetime].month
45
- else
46
- ((specific_date.month == d[:datetime].month and d[:datetime].day == 1) or
47
- (specific_date.month == d[:datetime].month + 1 and d[:datetime].day > 25) )
48
- end
49
- )}
50
- end
51
- if range
52
- starting = range.begin
53
- starting = timezone.parse(starting) if starting.is_a? String
54
- ending = range.end
55
- ending = timezone.parse( ending) if ending.is_a? String
56
- if starting.hour == 0 and starting.min == 0 and ending.hour == 0 and ending.min == 0
57
- puts "WARNING: When sending midnight, full trading day is assumed (starting 5 pm yesterday, ending 4 pm today)".light_yellow unless quiet
58
- result = select_specific_date.call(starting)
59
- result += data.select{|d| d[:datetime] > starting and d[:datetime] < ending.to_date }
60
- result += select_specific_date.call(ending)
61
- result.uniq!
62
- else
63
- result = data.select{|x| x[:datetime] >= starting and x[:datetime] < ending }
64
- end
65
- elsif date
66
- result = select_specific_date.call(date)
67
- else
68
- result = data
69
- end
70
- return case as
71
- when :hours
72
- Cotcube::Helpers.reduce(bars: result, to: 1.hour){|c,b| c[:day] == b[:day] and c[:datetime].hour == b[:datetime].hour }
73
- when :days
74
- Cotcube::Helpers.reduce(bars: result, to: 1.day ){|c,b| c[:day] == b[:day] }
75
- else
76
- result
77
- end
39
+ data.pop if data.last[:high].zero? && (not keep_marker)
40
+ data
78
41
  end
42
+
79
43
  end
80
44
  end