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.
- checksums.yaml +4 -4
- data/.irbrc.rb +3 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile +3 -2
- data/README.md +61 -28
- data/Rakefile +5 -3
- data/VERSION +1 -1
- data/cotcube-bardata.gemspec +10 -11
- data/lib/cotcube-bardata.rb +44 -18
- data/lib/cotcube-bardata/cached.rb +80 -0
- data/lib/cotcube-bardata/constants.rb +8 -8
- data/lib/cotcube-bardata/daily.rb +94 -59
- data/lib/cotcube-bardata/eods.rb +78 -55
- data/lib/cotcube-bardata/helpers.rb +104 -0
- data/lib/cotcube-bardata/init.rb +23 -27
- data/lib/cotcube-bardata/provide.rb +38 -12
- data/lib/cotcube-bardata/quarters.rb +29 -65
- data/lib/cotcube-bardata/range_matrix.rb +117 -0
- data/lib/cotcube-bardata/trade_dates.rb +19 -6
- data/lib/cotcube-bardata/trading_hours.rb +45 -0
- metadata +43 -39
data/lib/cotcube-bardata/eods.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
raise ArgumentError, "
|
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,
|
67
|
+
raise ArgumentError, ':filter must be in [:volume_part, :oi_part]' unless %i[volume_part oi_part].include? filter
|
59
68
|
|
60
|
-
|
61
|
-
|
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
|
73
|
+
id_path_get = ->(local_id) { "#{config[:data_path]}/eods/#{local_id}" }
|
64
74
|
|
65
|
-
process_date_for_id = lambda do |d,i|
|
66
|
-
|
67
|
-
|
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
|
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
|
-
|
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]
|
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 [
|
79
|
-
row[k] = row[k].to_i if [
|
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
|
86
|
-
|
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{|
|
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,
|
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
|
data/lib/cotcube-bardata/init.rb
CHANGED
@@ -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
|
13
|
-
.map
|
14
|
-
.map{|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
|
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
|
28
|
+
raise 'unknown architecture'
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
def config_path
|
33
|
-
config_prefix
|
33
|
+
"#{config_prefix}/etc/cotcube"
|
34
34
|
end
|
35
35
|
|
36
|
-
def init(config_file_name: 'bardata.yml'
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
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
|
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
|
-
[''
|
71
|
-
dir = "#{config[:data_path]}#{path == '' ? '' : '/'}#{path
|
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(
|
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 :
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
7
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
33
|
-
|
34
|
-
|
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|
|
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
|
-
|
42
|
-
|
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
|