cotcube-bardata 0.1.1 → 0.1.6

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,87 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cotcube
4
+ # Missing top level documentation comment
4
5
  module Bardata
6
+ def provide(contract:, # rubocop:disable Metrics/ParameterLists
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
+ interval: :days,
13
+ # supported filters are :raw, :_24x7_, :full and :rth (and custom named, if provided as file)
14
+ filter: :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)
5
18
 
6
- def provide(symbol: nil, id: nil, contract:, config: init)
7
- raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'" unless contract.is_a? String and [3,5].include? contract.size
8
- if contract.to_s.size == 5
9
- symbol = contract[0..1]
10
- contract = contract[2..4]
11
- end
12
- unless symbol.nil?
13
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
14
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
15
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
16
- id = symbol_id
17
- end
18
- raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
19
- id_path = "#{config[:data_path]}/daily/#{id}"
20
- data_file = "#{id_path}/#{contract}.csv"
21
- raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
22
- raise RuntimeError, "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
23
- data = CSV.read(data_file, headers: %i[contract date open high low close volume oi] ).map do |row|
24
- row = row.to_h
25
- row.each do |k, _|
26
- row[k] = row[k].to_f if [:open, :high, :low, :close].include? k
27
- row[k] = row[k].to_i if [:volume, :oi].include? k
28
- end
29
- row
30
- end
31
- data
32
- end
19
+ sym = get_id_set(symbol: symbol, id: id, contract: contract, config: config)
33
20
 
34
- def continuous(symbol: nil, id: nil, config: init)
35
- unless symbol.nil?
36
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
37
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
38
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
39
- id = symbol_id
40
- end
41
- raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
42
- id_path = "#{config[:data_path]}/daily/#{id}"
43
- available_contracts = Dir[id_path + '/*.csv'].map{|x| x.split('/').last.split('.').first}.sort_by{|x| x[-7]}.sort_by{|x| x[-6..-5]}
44
- data = []
45
- available_contracts.each do |c|
46
- provide(id: id, config: config, contract: c).each do |x|
47
- data << x
21
+ case interval
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], filter: filter)
26
+
27
+ base = base.select_within(ranges: requested_set, attr: :datetime) { |x| x.to_datetime.to_sssm }
28
+ return base if %i[quarters quarter].include? interval
29
+
30
+ Cotcube::Helpers.reduce(bars: base, to: :hours) do |c, b|
31
+ c[:day] == b[:day] and c[:datetime].hour == b[:datetime].hour
48
32
  end
49
- end
50
- result = []
51
- data.sort_by{|x| x[:date]}.group_by{|x| x[:date]}.map{|k,v|
52
- v.map{|x| x.delete(:date)}
53
- result << {
54
- date: k,
55
- volume: v.map{|x| x[:volume]}.reduce(:+),
56
- oi: v.map{|x| x[:oi ]}.reduce(:+)
57
- }
58
- result.last[:contracts] = v
59
- }
60
- result
61
- end
62
33
 
63
- def continuous_overview(symbol: nil, id: nil, config: init, selector: :volume, human: false, filter: nil)
64
- raise ArgumentError, "Selector must be either :volume or :oi" unless selector.is_a? Symbol and [:volume, :oi].include? selector
65
-
66
- unless symbol.nil?
67
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
68
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
69
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
70
- id = symbol_id
71
- end
72
- raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
73
- data = continuous(id: id, config: config).map{|x|
74
- {
75
- date: x[:date],
76
- volume: x[:contracts].sort_by{|x| - x[:volume]}[0..4].compact.select{|x| not x[:volume].zero?},
77
- oi: x[:contracts].sort_by{|x| - x[:oi]}[0..4].compact.select{|x| not x[:oi].zero?}
78
- }
79
- }.select{|x| not x[selector].empty? }
80
- result = data.group_by{|x| x[selector].first[:contract]}
81
- if human
82
- result.each {|k,v| puts "#{k}\t#{v.first[:date]}\t#{v.last[:date]}" if filter.nil? or v.first[selector].first[:contract][2..4]=~ /#{filter}/ }
34
+ when :days, :weeks, :months
35
+ base = provide_cached contract: contract, symbol: symbol, id: id, config: config, filter: filter,
36
+ force_recent: force_recent
37
+ base = extended_select_for_range(range: range, base: base) if range
38
+ return base if %i[day days].include? interval
39
+
40
+ # TODO: Missing implementation to reduce cached days to weeks or months
41
+ raise 'Missing implementation to reduce cached days to weeks or months'
42
+ when :dailies, :daily
43
+ base = provide_daily contract: contract, symbol: symbol, id: id, config: config
44
+ base = extended_select_for_range(range: range, base: base) if range
45
+ base
46
+ else
47
+ raise ArgumentError, "Unsupported or unknown interval '#{interval}' in Bardata.provide"
83
48
  end
84
- result
85
49
  end
86
50
  end
87
51
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cotcube
4
+ # Missing top level documentation
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)
14
+
15
+ unless contract.is_a?(String) && [3, 5].include?(contract.size)
16
+ raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
17
+ end
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
+
25
+ id_path = "#{config[:data_path]}/quarters/#{id}"
26
+ data_file = "#{id_path}/#{contract}.csv"
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|
32
+ row = row.to_h
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 }
35
+ row[:datetime] = timezone.parse(row[:datetime])
36
+ row[:type] = :quarter
37
+ row
38
+ end
39
+ data.pop if data.last[:high].zero? && (not keep_marker)
40
+ data
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cotcube
4
+ # Missing top level documentation
5
+ module Bardata
6
+ # this is an analysis tool to investigate actual ranges of an underlying symbol
7
+ # it is in particular no true range or average true range, as a 'true range' can only be applied to
8
+ # a steady series, what changing contracts definitely aren't
9
+ #
10
+ # The result printed / returned is a table, containing a matrix of rows:
11
+ # 1. size: the amount of values evaluated
12
+ # 2. avg:
13
+ # 3. lower: like median, but not at 1/2 but 1/4
14
+ # 4. median:
15
+ # 5. upper: like median, bot not at 1/2 but 3/4
16
+ # 6. max:
17
+ # and columns:
18
+ # 1.a) all days os the series
19
+ # 1.b) all days of the series, diminished by 2* :dim*100% extreme values (i.e. at both ends)
20
+ # 1.c) the last 200 days
21
+ # 2.a-c) same with days reduced to weeks (c: 52 weeks)
22
+ # 3.a-c) same with days reduced to months (c: 12 months)
23
+ def range_matrix(symbol: nil, id: nil, print: false, dim: 0.05)
24
+ # rubocop:disable Style/MultilineBlockChain
25
+ sym = get_id_set(symbol: symbol, id: id)
26
+ source = {}
27
+ target = {}
28
+ source[:days] = Cotcube::Bardata.continuous_actual_ml symbol: symbol
29
+ source[:weeks] = Cotcube::Helpers.reduce bars: source[:days], to: :weeks
30
+ source[:months] = Cotcube::Helpers.reduce bars: source[:days], to: :months
31
+
32
+ %i[days weeks months].each do |period|
33
+ source[period].map! do |x|
34
+ x[:range] = ((x[:high] - x[:low]) / sym[:ticksize]).round
35
+ x
36
+ end
37
+ target[period] = {}
38
+ target[period][:all_size] = source[period].size
39
+ target[period][:all_avg] = (source[period].map { |x| x[:range] }.reduce(:+) / source[period].size).round
40
+ target[period][:all_lower] = source[period].sort_by do |x|
41
+ x[:range]
42
+ end.map { |x| x[:range] }[ (source[period].size * 1 / 4).round ]
43
+ target[period][:all_median] = source[period].sort_by do |x|
44
+ x[:range]
45
+ end.map { |x| x[:range] }[ (source[period].size * 2 / 4).round ]
46
+ target[period][:all_upper] = source[period].sort_by do |x|
47
+ x[:range]
48
+ end.map { |x| x[:range] }[ (source[period].size * 3 / 4).round ]
49
+ target[period][:all_max] = source[period].map { |x| x[:range] }.max
50
+ target[period][:all_records] = source[period].sort_by do |x|
51
+ -x[:range]
52
+ end.map { |x| { contract: x[:contract], range: x[:range] } }.take(5)
53
+
54
+ tenth = (source[period].size * dim).round
55
+ custom = source[period].sort_by { |x| x[:range] }[tenth..source[period].size - tenth]
56
+ target[period][:dim_size] = custom.size
57
+ target[period][:dim_avg] = (custom.map { |x| x[:range] }.reduce(:+) / custom.size).round
58
+ target[period][:dim_lower] = custom.sort_by do |x|
59
+ x[:range]
60
+ end.map { |x| x[:range] }[ (custom.size * 1 / 4).round ]
61
+ target[period][:dim_median] = custom.sort_by do |x|
62
+ x[:range]
63
+ end.map { |x| x[:range] }[ (custom.size * 2 / 4).round ]
64
+ target[period][:dim_upper] = custom.sort_by do |x|
65
+ x[:range]
66
+ end.map { |x| x[:range] }[ (custom.size * 3 / 4).round ]
67
+ target[period][:dim_max] = custom.map { |x| x[:range] }.max
68
+ target[period][:dim_records] = custom.sort_by do |x|
69
+ -x[:range]
70
+ end.map { |x| { contract: x[:contract], range: x[:range] } }.take(5)
71
+
72
+ range = case period
73
+ when :months
74
+ -13..-2
75
+ when :weeks
76
+ -53..-2
77
+ when :days
78
+ -200..-1
79
+ else
80
+ raise ArgumentError, "Unsupported period: '#{period}'"
81
+ end
82
+ custom = source[period][range]
83
+ target[period][:rec_size] = custom.size
84
+ target[period][:rec_avg] = (custom.map { |x| x[:range] }.reduce(:+) / custom.size).round
85
+ target[period][:rec_lower] = custom.sort_by do |x|
86
+ x[:range]
87
+ end.map { |x| x[:range] }[ (custom.size * 1 / 4).round ]
88
+ target[period][:rec_median] = custom.sort_by do |x|
89
+ x[:range]
90
+ end.map { |x| x[:range] }[ (custom.size * 2 / 4).round ]
91
+ target[period][:rec_upper] = custom.sort_by do |x|
92
+ x[:range]
93
+ end.map { |x| x[:range] }[ (custom.size * 3 / 4).round ]
94
+ target[period][:rec_max] = custom.map { |x| x[:range] }.max
95
+ target[period][:rec_records] = custom.sort_by do |x|
96
+ -x[:range]
97
+ end.map { |x| { contract: x[:contract], range: x[:range] } }.take(5)
98
+ end
99
+
100
+ if print
101
+ %w[size avg lower median upper max].each do |a|
102
+ print "#{'%10s' % a} | " # rubocop:disable Style/FormatString
103
+ %i[days weeks months].each do |b|
104
+ %w[all dim rec].each do |c|
105
+ print ('%8d' % target[b]["#{c}_#{a}".to_sym]).to_s # rubocop:disable Style/FormatString
106
+ end
107
+ print ' | '
108
+ end
109
+ puts ''
110
+ end
111
+ end
112
+
113
+ target
114
+ # rubocop:enable Style/MultilineBlockChain
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cotcube
4
+ # missing top level documentation
5
+ module Bardata
6
+ # fetching official trade dates from CME
7
+ # it returns the current trade date or, if today isn't a trading day, the last trade date.
8
+ def last_trade_date
9
+ uri = 'https://www.cmegroup.com/CmeWS/mvc/Volume/TradeDates?exchange=CME'
10
+ begin
11
+ HTTParty.get(uri)
12
+ .parsed_response
13
+ .map do |x|
14
+ a = x['tradeDate'].chars.each_slice(2).map(&:join)
15
+ "#{a[0]}#{a[1]}-#{a[2]}-#{a[3]}"
16
+ end
17
+ .first
18
+ rescue StandardError
19
+ nil
20
+ end
21
+ end
22
+
23
+ def holidays(config: init)
24
+ CSV.read("#{config[:data_path]}/holidays.csv").map{|x| DateTime.parse(x[0])}
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cotcube
4
+ # Missing top level comment
5
+ module Bardata
6
+ # returns an Array of ranges containing a week of trading hours, specified by seconds since monday morning
7
+ # (as sunday is wday:0)
8
+ # according files are located in config[:data_path]/trading_hours and picked either
9
+ # by the symbol itself or by the assigned type
10
+ # commonly there are two filter for each symbol: :full and :rth, exceptions are e.g. meats
11
+ def trading_hours(symbol: nil, id: nil, # rubocop:disable Metrics/ParameterLists
12
+ filter: ,
13
+ force_filter: false, # with force_filter one would avoid falling back
14
+ # to the contract_type based range set
15
+ config: init, debug: false)
16
+ return (0...24 * 7 * 3600) if filter.to_s =~ /24x7/
17
+
18
+ prepare = lambda do |f|
19
+ CSV.read(f, converters: :numeric)
20
+ .map(&:to_a)
21
+ .tap { |x| x.shift unless x.first.first.is_a?(Numeric) }
22
+ .map { |x| (x.first...x.last) }
23
+ end
24
+
25
+ sym = get_id_set(symbol: symbol, id: id)
26
+
27
+ file = "#{config[:data_path]}/trading_hours/#{sym[:symbol]}_#{filter}.csv"
28
+ puts "Trying to use #{file} for #{symbol} + #{filter}" if debug
29
+ return prepare.call(file) if File.exist? file
30
+
31
+ file = "#{config[:data_path]}/trading_hours/#{sym[:symbol]}_full.csv"
32
+ puts "Failed. Trying to use #{file} now" if debug
33
+ return prepare.call(file) if File.exist?(file) && (not force_filter)
34
+
35
+ file = "#{config[:data_path]}/trading_hours/#{sym[:type]}_#{filter}.csv"
36
+ puts "Failed. Trying to use #{file} now." if debug
37
+ return prepare.call(file) if File.exist? file
38
+
39
+ file = "#{config[:data_path]}/trading_hours/#{sym[:type]}_full.csv"
40
+ puts "Failed. Trying to use #{file} now." if debug
41
+ return prepare.call(file) if File.exist?(file) && (not force_filter)
42
+
43
+ puts "Finally failed to find range filter for #{symbol} + #{filter}, returning 24x7".colorize(:light_yellow)
44
+ (0...24 * 7 * 3600)
45
+ end
46
+ end
47
+ end
metadata CHANGED
@@ -1,113 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cotcube-bardata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin L. Tischendorf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-16 00:00:00.000000000 Z
11
+ date: 2021-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: cotcube-indicators
14
+ name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '6'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '6'
27
27
  - !ruby/object:Gem::Dependency
28
- name: yaml
28
+ name: colorize
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '0.8'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '0.8'
41
41
  - !ruby/object:Gem::Dependency
42
- name: activesupport
42
+ name: cotcube-helpers
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '0.1'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '0.1'
55
55
  - !ruby/object:Gem::Dependency
56
- name: colorize
56
+ name: cotcube-indicators
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '0.1'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '0.1'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: httparty
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '0.18'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '0.18'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rubyzip
84
+ name: parallel
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '1'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yaml
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.1'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '0'
110
+ version: '0.1'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rake
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - ">="
102
116
  - !ruby/object:Gem::Version
103
- version: '0'
117
+ version: '12'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
- version: '0'
124
+ version: '12'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rspec
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -152,9 +166,17 @@ files:
152
166
  - VERSION
153
167
  - cotcube-bardata.gemspec
154
168
  - lib/cotcube-bardata.rb
169
+ - lib/cotcube-bardata/cached.rb
155
170
  - lib/cotcube-bardata/constants.rb
171
+ - lib/cotcube-bardata/daily.rb
172
+ - lib/cotcube-bardata/eods.rb
173
+ - lib/cotcube-bardata/helpers.rb
156
174
  - lib/cotcube-bardata/init.rb
157
175
  - lib/cotcube-bardata/provide.rb
176
+ - lib/cotcube-bardata/quarters.rb
177
+ - lib/cotcube-bardata/range_matrix.rb
178
+ - lib/cotcube-bardata/trade_dates.rb
179
+ - lib/cotcube-bardata/trading_hours.rb
158
180
  homepage: https://github.com/donkeybridge/cotcube-bardata
159
181
  licenses:
160
182
  - BSD-4-Clause