cotcube-bardata 0.1.2 → 0.1.7
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 +38 -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 +47 -17
- data/lib/cotcube-bardata/cached.rb +120 -0
- data/lib/cotcube-bardata/constants.rb +8 -8
- data/lib/cotcube-bardata/daily.rb +120 -58
- data/lib/cotcube-bardata/eods.rb +93 -50
- data/lib/cotcube-bardata/helpers.rb +107 -0
- data/lib/cotcube-bardata/init.rb +23 -26
- data/lib/cotcube-bardata/provide.rb +38 -13
- data/lib/cotcube-bardata/quarters.rb +28 -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 +47 -0
- metadata +43 -39
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
module Cotcube
|
4
4
|
module Bardata
|
5
|
-
|
6
5
|
SYMBOL_EXAMPLES = [
|
7
|
-
{ id:
|
8
|
-
|
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
|
12
|
-
|
13
|
-
|
14
|
-
|
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(
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
24
|
-
|
25
|
-
data
|
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 [
|
29
|
-
row[k] = row[k].to_i if [
|
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
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
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:
|
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
|
-
|
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,
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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:
|
83
|
-
volume: x[:contracts].sort_by{|
|
84
|
-
oi:
|
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
|
-
|
87
|
-
|
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
|
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
|
data/lib/cotcube-bardata/eods.rb
CHANGED
@@ -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
|
-
|
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
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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,
|
68
|
+
raise ArgumentError, ':filter must be in [:volume_part, :oi_part]' unless %i[volume_part oi_part].include? filter
|
43
69
|
|
44
|
-
|
45
|
-
|
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
|
74
|
+
id_path_get = ->(local_id) { "#{config[:data_path]}/eods/#{local_id}" }
|
48
75
|
|
49
|
-
process_date_for_id = lambda do |d,i|
|
50
|
-
|
51
|
-
|
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
|
55
|
-
|
56
|
-
|
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 [
|
60
|
-
row[k] = row[k].to_i if [
|
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
|
67
|
-
|
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{|
|
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,
|
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
|