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