cotcube-bardata 0.1.3 → 0.1.4

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.
@@ -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