cotcube-bardata 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/VERSION +1 -1
- data/cotcube-bardata.gemspec +1 -0
- data/lib/cotcube-bardata.rb +17 -12
- data/lib/cotcube-bardata/daily.rb +94 -0
- data/lib/cotcube-bardata/eods.rb +79 -0
- data/lib/cotcube-bardata/provide.rb +14 -78
- data/lib/cotcube-bardata/quarters.rb +80 -0
- data/lib/cotcube-bardata/trade_dates.rb +15 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7cc8f5a19330f55ec26073d90b2c7da30547b61903daa93f8b2977c05ad308f8
|
4
|
+
data.tar.gz: 3dac9e49fb0143d8dd91a86f5a7779dcb25a5c8fca719de5b23a46a5e742380f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aafb4160d474f4f4d0694faee22821f1e8a65a510dcc2c84999acce0e0f3b7511dc52cd2d28cb21856162fbed9c5fa2e9b8e65b8065b7c1603128f8ef1cdea75
|
7
|
+
data.tar.gz: 51ba4678419a6daf9f4d2a18810432f2f3c93c7a6417e8d0e7beae822e76ad9214450bfb1bf4479fc30c4032b51e4504c923e707f6ea8feff295f16071040df3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 0.1.2 (December 22, 2020)
|
2
|
+
- created and added .provide_quarters
|
3
|
+
- added cotcube-helpers as new dependency to gempsec
|
4
|
+
- added timezone to dailies and :datetime to each row
|
5
|
+
- updated master file with new methods and sub-file
|
6
|
+
- added eods with 2 simple mathods .most_liquid_for and .provide_eods
|
7
|
+
- added a simple getter to access CME tradedates (to fetch last_trade_date)
|
8
|
+
- moved daily stuff into new file, prepare .provide to become a major accessor method
|
9
|
+
|
1
10
|
## 0.1.1 (December 16, 2020)
|
2
11
|
|
3
12
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
data/cotcube-bardata.gemspec
CHANGED
data/lib/cotcube-bardata.rb
CHANGED
@@ -1,34 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'active_support'
|
4
|
+
require 'active_support/core_ext/time'
|
5
|
+
require 'active_support/core_ext/numeric'
|
4
6
|
require 'colorize'
|
7
|
+
require 'httparty'
|
5
8
|
require 'date' unless defined?(DateTime)
|
6
9
|
require 'csv' unless defined?(CSV)
|
7
10
|
require 'yaml' unless defined?(YAML)
|
8
|
-
require '
|
9
|
-
|
11
|
+
require 'cotcube-helpers'
|
12
|
+
|
10
13
|
|
11
14
|
|
12
15
|
require_relative 'cotcube-bardata/constants'
|
13
16
|
require_relative 'cotcube-bardata/init'
|
17
|
+
require_relative 'cotcube-bardata/trade_dates'
|
18
|
+
require_relative 'cotcube-bardata/daily'
|
19
|
+
require_relative 'cotcube-bardata/quarters'
|
20
|
+
require_relative 'cotcube-bardata/eods'
|
14
21
|
require_relative 'cotcube-bardata/provide'
|
15
22
|
|
16
|
-
private_files = Dir[__dir__ + '/cotcube-bardata/private/*.rb']
|
17
|
-
private_files.each do |file|
|
18
|
-
# puts 'Loading private module extension ' + file.chomp
|
19
|
-
require file.chomp
|
20
|
-
end
|
21
|
-
|
22
23
|
module Cotcube
|
23
24
|
module Bardata
|
24
25
|
|
25
26
|
module_function :config_path, # provides the path of configuration directory
|
26
27
|
:config_prefix, # provides the prefix of the configuration directory according to OS-specific FSH
|
27
28
|
:init, # checks whether environment is prepared and returns the config hash
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
|
29
|
+
:last_trade_date, # Provides the most recent trade date (today or maybe last friday before weekend)
|
30
|
+
:provide, #
|
31
|
+
:most_liquid_for, # the most_liquid contract for a given symbol or id, based on date or last_trade_date
|
32
|
+
:provide_eods, # provides the list of eods, either with data or just the contracts, filtered for liquidity threshold
|
33
|
+
:provide_daily, # provides the list of dailies for a given symbol, which include OI. Note that the close is settlement price.
|
34
|
+
:continuous, # for a given date or range, provide all contracts that exceed a given threshold of volume share
|
35
|
+
:continuous_overview, # based on continuous, create list of when which contract was most liquid
|
36
|
+
:provide_quarters, # provide the list of quarters, possibly as hours or days.
|
32
37
|
:symbols # reads and provides the symbols file
|
33
38
|
|
34
39
|
# please not that module_functions of source provided in private files must be published there
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
module Bardata
|
5
|
+
|
6
|
+
# just reads bardata/daily/<id>/<contract>.csv
|
7
|
+
def provide_daily(symbol: nil, id: nil, contract:, timezone: Time.find_zone('America/Chicago'), config: init)
|
8
|
+
contract = contract.to_s.upcase
|
9
|
+
raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'" unless contract.is_a? String and [3,5].include? contract.size
|
10
|
+
if contract.to_s.size == 5
|
11
|
+
symbol = contract[0..1]
|
12
|
+
contract = contract[2..4]
|
13
|
+
end
|
14
|
+
unless symbol.nil?
|
15
|
+
symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
|
16
|
+
raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
|
17
|
+
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
|
18
|
+
id = symbol_id
|
19
|
+
end
|
20
|
+
raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
|
21
|
+
id_path = "#{config[:data_path]}/daily/#{id}"
|
22
|
+
data_file = "#{id_path}/#{contract}.csv"
|
23
|
+
raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
|
24
|
+
raise RuntimeError, "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
|
25
|
+
data = CSV.read(data_file, headers: %i[contract date open high low close volume oi] ).map do |row|
|
26
|
+
row = row.to_h
|
27
|
+
row.each do |k, _|
|
28
|
+
row[k] = row[k].to_f if [:open, :high, :low, :close].include? k
|
29
|
+
row[k] = row[k].to_i if [:volume, :oi].include? k
|
30
|
+
end
|
31
|
+
row[:datetime] = timezone.parse(row[:date])
|
32
|
+
row
|
33
|
+
end
|
34
|
+
data
|
35
|
+
end
|
36
|
+
|
37
|
+
# reads all files in bardata/daily/<id> and aggregates by date (what is a pre-stage of a continuous based on daily bars)
|
38
|
+
def continuous(symbol: nil, id: nil, config: init, date: nil)
|
39
|
+
unless symbol.nil?
|
40
|
+
symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
|
41
|
+
raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
|
42
|
+
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
|
43
|
+
id = symbol_id
|
44
|
+
end
|
45
|
+
raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
|
46
|
+
id_path = "#{config[:data_path]}/daily/#{id}"
|
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|
|
51
|
+
data << x
|
52
|
+
end
|
53
|
+
end
|
54
|
+
result = []
|
55
|
+
data.sort_by{|x| x[:date]}.group_by{|x| x[:date]}.map{|k,v|
|
56
|
+
v.map{|x| x.delete(:date)}
|
57
|
+
result << {
|
58
|
+
date: k,
|
59
|
+
volume: v.map{|x| x[:volume]}.reduce(:+),
|
60
|
+
oi: v.map{|x| x[:oi ]}.reduce(:+)
|
61
|
+
}
|
62
|
+
result.last[:contracts] = v
|
63
|
+
}
|
64
|
+
date.nil? ? result : result.select{|x| x[:date] == date}.first
|
65
|
+
end
|
66
|
+
|
67
|
+
# based on .continuous, this methods sorts the prepared dailies continuous for each date on either :volume (default) or :oi
|
68
|
+
# with this job done, it can provide the period for which a past contract was the most liquid
|
69
|
+
#
|
70
|
+
def continuous_overview(symbol: nil, id: nil, config: init, selector: :volume, human: false, filter: nil)
|
71
|
+
raise ArgumentError, "Selector must be either :volume or :oi" unless selector.is_a? Symbol and [:volume, :oi].include? selector
|
72
|
+
|
73
|
+
unless symbol.nil?
|
74
|
+
symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
|
75
|
+
raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
|
76
|
+
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
|
77
|
+
id = symbol_id
|
78
|
+
end
|
79
|
+
raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
|
80
|
+
data = continuous(id: id, config: config).map{|x|
|
81
|
+
{
|
82
|
+
date: x[:date],
|
83
|
+
volume: x[:contracts].sort_by{|x| - x[:volume]}[0..4].compact.select{|x| not x[:volume].zero?},
|
84
|
+
oi: x[:contracts].sort_by{|x| - x[:oi]}[0..4].compact.select{|x| not x[:oi].zero?}
|
85
|
+
}
|
86
|
+
}.select{|x| not x[selector].empty? }
|
87
|
+
result = data.group_by{|x| x[selector].first[:contract]}
|
88
|
+
if human
|
89
|
+
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}/ }
|
90
|
+
end
|
91
|
+
result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
module Bardata
|
5
|
+
|
6
|
+
def most_liquid_for(symbol: nil, id: nil, date: last_trade_date, config: init)
|
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?
|
14
|
+
provide_eods(id: id, dates: date, contracts_only: true).first
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
def provide_eods(symbol: nil,
|
20
|
+
id: nil,
|
21
|
+
contract: nil,
|
22
|
+
config: init,
|
23
|
+
dates: last_trade_date, # should accept either a date or datelike or date string OR a range of 2 datelike
|
24
|
+
# if omitted returns the eods of last trading date
|
25
|
+
threshold: 0.1, # set threshold to 0 to disable filtering at all. otherwise only contracts with partial of >= threshold are returned
|
26
|
+
filter: :volume_part, # filter can be set to volume_part and oi_part. determines, which property is used for filtering.
|
27
|
+
contracts_only: true # set to false to return the complete row instead of just the contracts matching filter and threshold
|
28
|
+
)
|
29
|
+
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)
|
30
|
+
if contract.to_s.size == 5
|
31
|
+
symbol = contract[0..1]
|
32
|
+
contract = contract[2..4]
|
33
|
+
end
|
34
|
+
unless symbol.nil?
|
35
|
+
symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
|
36
|
+
raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
|
37
|
+
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
|
38
|
+
id = symbol_id
|
39
|
+
end
|
40
|
+
# if no id can be clarified from given arguments, return all matching contracts from all available symbols
|
41
|
+
# raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
|
42
|
+
raise ArgumentError, ":filter must be in [:volume_part, :oi_part]" unless [:volume_part, :oi_part].include? filter
|
43
|
+
|
44
|
+
ids = id.nil? ? symbols.map{|x| x[:id]} : [ id ]
|
45
|
+
dates = [ dates ] unless dates.is_a? Array or dates.nil?
|
46
|
+
|
47
|
+
id_path_get = lambda {|_id| "#{config[:data_path]}/eods/#{_id}" }
|
48
|
+
|
49
|
+
process_date_for_id = lambda do |d,i|
|
50
|
+
sym = symbols.select{|s| s[:id] == i}.first
|
51
|
+
symbol = sym[:symbol]
|
52
|
+
id_path = id_path_get.call(i)
|
53
|
+
data_file = "#{id_path}/#{d}.csv"
|
54
|
+
raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
|
55
|
+
raise RuntimeError, "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
|
56
|
+
data = CSV.read(data_file, headers: %i[contract date open high low close volume oi] ).map do |row|
|
57
|
+
row = row.to_h
|
58
|
+
row.each do |k, _|
|
59
|
+
row[k] = row[k].to_f if [:open, :high, :low, :close].include? k
|
60
|
+
row[k] = row[k].to_i if [:volume, :oi].include? k
|
61
|
+
end
|
62
|
+
row
|
63
|
+
end
|
64
|
+
all_volume = data.map{|x| x[:volume] }.reduce(:+)
|
65
|
+
all_oi = data.map{|x| x[:oi] }.reduce(:+)
|
66
|
+
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) }
|
67
|
+
data.select{|x| x[filter] >= threshold}.sort_by{|x| -x[filter]}.tap{|x| x.map!{|y| y[:contract]} if contracts_only}
|
68
|
+
end
|
69
|
+
if dates
|
70
|
+
dates.map do |date|
|
71
|
+
ids.map{|id| process_date_for_id.call(date, id) }
|
72
|
+
end.flatten
|
73
|
+
else
|
74
|
+
raise ArgumentError, "Sorry, support for unlimited dates is not implemented yet. Please send array of dates or single date"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -3,85 +3,21 @@
|
|
3
3
|
module Cotcube
|
4
4
|
module Bardata
|
5
5
|
|
6
|
-
def provide(symbol: nil, id: nil, contract:, config: init)
|
7
|
-
|
8
|
-
|
9
|
-
symbol
|
10
|
-
|
6
|
+
def provide(symbol: nil, id: nil, contract:, config: init, date: Date.today - 1, type: nil, fill: :none)
|
7
|
+
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
|
16
|
+
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)
|
11
19
|
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
|
33
|
-
|
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
|
48
|
-
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
|
-
|
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}/ }
|
83
|
-
end
|
84
|
-
result
|
85
20
|
end
|
86
21
|
end
|
22
|
+
|
87
23
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
module Bardata
|
5
|
+
|
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]
|
21
|
+
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?
|
30
|
+
id_path = "#{config[:data_path]}/quarters/#{id}"
|
31
|
+
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|
|
35
|
+
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}
|
38
|
+
row[:datetime] = timezone.parse(row[:datetime])
|
39
|
+
row
|
40
|
+
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
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
module Bardata
|
5
|
+
|
6
|
+
# fetching official tradedates from CME
|
7
|
+
def last_trade_date
|
8
|
+
uri = "https://www.cmegroup.com/CmeWS/mvc/Volume/TradeDates?exchange=CME"
|
9
|
+
res = nil
|
10
|
+
res = HTTParty.get(uri).parsed_response
|
11
|
+
res.map{|x| a = x["tradeDate"].chars.each_slice(2).map(&:join); "#{a[0]}#{a[1]}-#{a[2]}-#{a[3]}"}.first
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cotcube-bardata
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
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-
|
11
|
+
date: 2020-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cotcube-indicators
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: cotcube-helpers
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: yaml
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -153,8 +167,12 @@ files:
|
|
153
167
|
- cotcube-bardata.gemspec
|
154
168
|
- lib/cotcube-bardata.rb
|
155
169
|
- lib/cotcube-bardata/constants.rb
|
170
|
+
- lib/cotcube-bardata/daily.rb
|
171
|
+
- lib/cotcube-bardata/eods.rb
|
156
172
|
- lib/cotcube-bardata/init.rb
|
157
173
|
- lib/cotcube-bardata/provide.rb
|
174
|
+
- lib/cotcube-bardata/quarters.rb
|
175
|
+
- lib/cotcube-bardata/trade_dates.rb
|
158
176
|
homepage: https://github.com/donkeybridge/cotcube-bardata
|
159
177
|
licenses:
|
160
178
|
- BSD-4-Clause
|