cotcube-bardata 0.1.13 → 0.1.15.5
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/CHANGELOG.md +30 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/cotcube-bardata.gemspec +2 -2
- data/lib/cotcube-bardata/cached.rb +8 -4
- data/lib/cotcube-bardata/daily.rb +120 -27
- data/lib/cotcube-bardata/eods.rb +3 -3
- data/lib/cotcube-bardata/helpers.rb +0 -36
- data/lib/cotcube-bardata/provide.rb +10 -3
- data/lib/cotcube-bardata/quarters.rb +2 -1
- data/lib/cotcube-bardata/range_matrix.rb +1 -1
- data/lib/cotcube-bardata/suggest.rb +28 -0
- data/lib/cotcube-bardata/trade_dates.rb +38 -12
- data/lib/cotcube-bardata/trading_hours.rb +11 -5
- data/lib/cotcube-bardata.rb +6 -4
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1ab9094e7e7e6c04a90f1f1a640b902de5a0c688403a5d79c53a7b3f11c9434
|
4
|
+
data.tar.gz: 16175de316f036ec9a930b43d700fc241999afaffca728bf2ba436470255c957
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f549b77c54222c0424d0f96c20a43634ab18af6001efffbc27ef3a4d21ee56e4fe67e727399dbbce47335b82d7e3733350b7b5469a22acee970c682eefef1d0c
|
7
|
+
data.tar.gz: 5153e8ad6cfae659c0f538e42cf635b53a3e543bb41a0decc8daf9169f62bce92dd0f7cd8b2d7a71a983a953b5be57b1e2fff7e6679f14662d6369524b486ca0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
## 0.1.15.5 (November 08, 2021)
|
2
|
+
- decommissioned .get_id_set in favor of Cotcube::Helpers.get_id_set
|
3
|
+
|
4
|
+
## 0.1.15.4 (October 26, 2021)
|
5
|
+
- daily: including the sourcing factor (:bcf) into provide_daily and continuous
|
6
|
+
|
7
|
+
## 0.1.15.3 (August 04, 2021)
|
8
|
+
- daily.rb: Hotfixing
|
9
|
+
|
10
|
+
## 0.1.15.2 (August 04, 2021)
|
11
|
+
- fixed leftover debug setting
|
12
|
+
|
13
|
+
## 0.1.15.1 (August 04, 2021)
|
14
|
+
- fixed license mismatch
|
15
|
+
|
16
|
+
## 0.1.15 (August 04, 2021)
|
17
|
+
- daily.rb: added support to add eod data on incomplete dailies
|
18
|
+
- adding dep: cotcube-indicators
|
19
|
+
- provide: new method :determine_significant_volume
|
20
|
+
- suggest: adding silence
|
21
|
+
- cached: Adding :dist
|
22
|
+
- daily: new method :determine_significant_volume
|
23
|
+
- added :dist generically to quarters
|
24
|
+
- trading_hours: added param to return headers only
|
25
|
+
- last_trade_date: Enabled caching instead of fetching each time
|
26
|
+
|
27
|
+
## 0.1.14 (May 07, 2021)
|
28
|
+
- few changes in provide / cached / daily for a more straigtforward forcing of cache renewal
|
29
|
+
- suggest: new method to suggest a contract for given symbol and date
|
30
|
+
|
1
31
|
## 0.1.13 (April 07, 2021)
|
2
32
|
- daily: fixed const_caching in continuous
|
3
33
|
- trade_dates: FIXING call with HTTParty must send Agent Header
|
data/README.md
CHANGED
@@ -90,5 +90,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/donkey
|
|
90
90
|
|
91
91
|
## License
|
92
92
|
|
93
|
-
The gem is available as open source under the terms of the [
|
93
|
+
The gem is available as open source under the terms of the [BSD-3-Clause-License](https://opensource.org/licenses/BSD-3-Clause).
|
94
94
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.15.5
|
data/cotcube-bardata.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = 'Functions to provide bardata; and some simple time series aggregations '
|
11
11
|
|
12
12
|
spec.homepage = "https://github.com/donkeybridge/#{spec.name}"
|
13
|
-
spec.license = 'BSD-
|
13
|
+
spec.license = 'BSD-3-Clause'
|
14
14
|
spec.required_ruby_version = Gem::Requirement.new('~> 2.7')
|
15
15
|
|
16
16
|
spec.metadata['homepage_uri'] = spec.homepage
|
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
|
29
29
|
spec.add_dependency 'activesupport', '~> 6'
|
30
30
|
spec.add_dependency 'colorize', '~> 0.8'
|
31
|
-
spec.add_dependency 'cotcube-helpers', '~> 0.
|
31
|
+
spec.add_dependency 'cotcube-helpers', '~> 0.2'
|
32
32
|
spec.add_dependency 'cotcube-indicators', '~> 0.1'
|
33
33
|
spec.add_dependency 'httparty', '~> 0.18'
|
34
34
|
spec.add_dependency 'parallel', '~> 1'
|
@@ -12,7 +12,7 @@ module Cotcube
|
|
12
12
|
timezone: Time.find_zone('America/Chicago'),
|
13
13
|
filter: :full, # most probably either :full or :rth
|
14
14
|
force_update: false, # force reloading via provide_quarters
|
15
|
-
force_recent: false) #
|
15
|
+
force_recent: false) # provide base.last even if dayswitch hasn't happen yet
|
16
16
|
|
17
17
|
unless range.nil? ||
|
18
18
|
range.is_a?(Range) &&
|
@@ -30,7 +30,7 @@ module Cotcube
|
|
30
30
|
end
|
31
31
|
|
32
32
|
headers = %i[contract datetime open high low close volume]
|
33
|
-
sym = get_id_set(symbol: symbol, id: id, contract: contract)
|
33
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id, contract: contract)
|
34
34
|
contract = contract[-3..]
|
35
35
|
dir = "#{config[:data_path]}/cached/#{sym[:id]}_#{filter.to_s.downcase}"
|
36
36
|
symlink = "#{config[:data_path]}/cached/#{sym[:symbol]}_#{filter.to_s.downcase}"
|
@@ -39,17 +39,20 @@ module Cotcube
|
|
39
39
|
file = "#{dir}/#{contract}.csv"
|
40
40
|
quarters_file = "#{config[:data_path]}/quarters/#{sym[:id]}/#{contract[-3..]}.csv"
|
41
41
|
if File.exist?(file) && (not force_update)
|
42
|
+
puts "Working with existing #{file}, no update was forced" if debug
|
43
|
+
puts " Using quarters from #{quarters_file}" if debug
|
42
44
|
base = CSV.read(file, headers: headers).map do |x|
|
43
45
|
x = x.to_h
|
44
46
|
x[:datetime] = timezone.parse(x[:datetime])
|
45
47
|
%i[open high low close].each { |z| x[z] = x[z].to_f.round(9) }
|
46
48
|
x[:volume] = x[:volume].to_i
|
49
|
+
x[:dist] = ((x[:high] - x[:low]) / sym[:ticksize] ).to_i
|
47
50
|
x[:type] = "#{filter.to_s.downcase}_day".to_sym
|
48
51
|
x
|
49
52
|
end
|
50
53
|
if base.last[:high].zero?
|
51
54
|
# contract exists but is closed (has the CLOSED marker)
|
52
|
-
base.pop
|
55
|
+
base.pop
|
53
56
|
# rubocop:disable Metrics/BlockNesting
|
54
57
|
result = if range.nil?
|
55
58
|
base
|
@@ -96,8 +99,9 @@ module Cotcube
|
|
96
99
|
end
|
97
100
|
|
98
101
|
base = Cotcube::Helpers.reduce(bars: data, to: :days)
|
102
|
+
puts "Reduced base ends at #{bast.last[:datetime].strftime('%Y-%m-%d')}" if debug
|
99
103
|
|
100
|
-
# remove last day of result
|
104
|
+
# remove last day of result if suspecting incomplete last base
|
101
105
|
base.pop if base.last[:datetime].to_date == timezone.now.to_date and not force_recent
|
102
106
|
|
103
107
|
base.map do |x|
|
@@ -9,8 +9,11 @@ module Cotcube
|
|
9
9
|
range: nil,
|
10
10
|
timezone: Time.find_zone('America/Chicago'),
|
11
11
|
keep_last: false,
|
12
|
+
add_eods: true,
|
13
|
+
indicators: {},
|
12
14
|
config: init)
|
13
15
|
contract = contract.to_s.upcase
|
16
|
+
rounding = 8
|
14
17
|
unless contract.is_a?(String) && [3, 5].include?(contract.size)
|
15
18
|
raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
|
16
19
|
end
|
@@ -30,7 +33,7 @@ module Cotcube
|
|
30
33
|
range = (range_begin..range_end)
|
31
34
|
end
|
32
35
|
|
33
|
-
sym = get_id_set(symbol: symbol, id: id, contract: contract)
|
36
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id, contract: contract)
|
34
37
|
contract = contract[2..4] if contract.to_s.size == 5
|
35
38
|
id = sym[:id]
|
36
39
|
id_path = "#{config[:data_path]}/daily/#{id}"
|
@@ -42,14 +45,46 @@ module Cotcube
|
|
42
45
|
data = CSV.read(data_file, headers: %i[contract date open high low close volume oi]).map do |row|
|
43
46
|
row = row.to_h
|
44
47
|
row.each do |k, _|
|
45
|
-
|
48
|
+
if %i[open high low close].include? k
|
49
|
+
row[k] = row[k].to_f
|
50
|
+
row[k] = (row[k] * sym[:bcf]).round(8) unless sym[:bcf] == 1.0
|
51
|
+
end
|
46
52
|
row[k] = row[k].to_i if %i[volume oi].include? k
|
47
53
|
end
|
48
54
|
row[:datetime] = timezone.parse(row[:date])
|
55
|
+
row[:dist] = ((row[:high] - row[:low]) / sym[:ticksize] ).to_i
|
49
56
|
row[:type] = :daily
|
50
57
|
row
|
51
58
|
end
|
52
|
-
|
59
|
+
contract_expired = data.last[:high].zero?
|
60
|
+
data.pop if contract_expired and not keep_last
|
61
|
+
if not contract_expired and add_eods
|
62
|
+
today = Date.today
|
63
|
+
eods = [ ]
|
64
|
+
while today.strftime('%Y-%m-%d') > data.last[:date]
|
65
|
+
eods << provide_eods(symbol: sym[:symbol], dates: today, contracts_only: false, quiet: true)
|
66
|
+
today -= 1
|
67
|
+
end
|
68
|
+
eods.flatten!.map!{|x| x.tap {|y| %i[ volume_part oi_part ].map{|z| y.delete(z)} } }
|
69
|
+
eods.select!{|x| x[:contract] == "#{sym[:symbol]}#{contract}" }
|
70
|
+
eods.map!{|x| x.tap{|y|
|
71
|
+
if sym[:bcf] != 1.0
|
72
|
+
%i[open high low close].map{|k|
|
73
|
+
y[k] = (y[k] * sym[:bcf]).round(8)
|
74
|
+
}
|
75
|
+
end
|
76
|
+
y[:datetime] = timezone.parse(y[:date])
|
77
|
+
y[:dist] = ((y[:high] - y[:low]) / sym[:ticksize] ).to_i
|
78
|
+
y[:type] = :eod
|
79
|
+
} }
|
80
|
+
data += eods.reverse
|
81
|
+
end
|
82
|
+
data.map do |bar|
|
83
|
+
indicators.each do |k,v|
|
84
|
+
tmp = v.call(bar)
|
85
|
+
bar[k] = tmp.respond_to?(:round) ? tmp.round(rounding) : tmp
|
86
|
+
end
|
87
|
+
end unless indicators.empty?
|
53
88
|
if range.nil?
|
54
89
|
data
|
55
90
|
else
|
@@ -62,52 +97,105 @@ module Cotcube
|
|
62
97
|
|
63
98
|
# reads all files in bardata/daily/<id> and aggregates by date
|
64
99
|
# (what is a pre-stage of a continuous based on daily bars)
|
65
|
-
def continuous(symbol: nil, id: nil, config: init, date: nil, measure: nil, force_rewrite: false)
|
100
|
+
def continuous(symbol: nil, id: nil, config: init, date: nil, measure: nil, force_rewrite: false, selector: nil, debug: false, add_eods: true, indicators: nil)
|
66
101
|
raise ArgumentError, ':measure, if given, must be a Time object (e.g. Time.now)' unless [NilClass, Time].include? measure.class
|
67
102
|
measuring = lambda {|c| puts "[continuous] Time measured until '#{c}': #{(Time.now.to_f - measure.to_f).round(2)}sec" unless measure.nil? }
|
68
103
|
|
69
104
|
measuring.call("Starting")
|
70
|
-
sym = get_id_set(symbol: symbol, id: id)
|
105
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id)
|
71
106
|
id = sym[:id]
|
72
107
|
symbol = sym[:symbol]
|
108
|
+
ticksize = sym[:ticksize]
|
109
|
+
effective_selector = selector || :volume
|
110
|
+
raise ArgumentError, 'selector must be in %i[ nil :volume ;oi].' unless [ nil, :volume, :oi ].include? selector
|
73
111
|
id_path = "#{config[:data_path]}/daily/#{id}"
|
74
112
|
c_file = "#{id_path}/continuous.csv"
|
113
|
+
puts "Using file #{c_file}" if debug
|
75
114
|
|
76
115
|
# instead of using the provide_daily methods above, for this bulk operation a 'continuous.csv' is created
|
77
116
|
# this boosts from 4.5sec to 0.3sec
|
78
|
-
rewriting = force_rewrite or not(File.exist?(c_file)) or (Time.now - File.mtime(c_file) > 8.days)
|
117
|
+
rewriting = (force_rewrite or not(File.exist?(c_file)) or (Time.now - File.mtime(c_file) > 8.days))
|
79
118
|
if rewriting
|
80
119
|
puts "In daily+continuous: Rewriting #{c_file} #{force_rewrite ? "forcibly" : "due to fileage"}.".light_yellow
|
81
|
-
`rm #{c_file}; find #{id_path} | xargs cat 2>/dev/null | grep -v '
|
120
|
+
`rm #{c_file}; find #{id_path} | xargs cat 2>/dev/null | grep -v ',0,' | grep -v ',0$'| sort -t, -k2 | cut -d, -f1-8 | grep ',.*,' | uniq > #{c_file}`
|
82
121
|
end
|
83
122
|
loading = lambda do
|
84
123
|
data = CSV.read(c_file).map do |row|
|
85
124
|
r = { contract: row[0],
|
86
125
|
date: row[1],
|
87
|
-
|
88
|
-
|
126
|
+
open: row[2],
|
127
|
+
high: row[3],
|
128
|
+
low: row[4],
|
129
|
+
close: row[5],
|
130
|
+
volume: row[6].to_i,
|
131
|
+
oi: row[7].to_i,
|
132
|
+
type: :cont
|
89
133
|
}
|
90
134
|
end
|
135
|
+
if add_eods
|
136
|
+
today = Date.today
|
137
|
+
eods = [ ]
|
138
|
+
while today.strftime('%Y-%m-%d') > data.last[:date]
|
139
|
+
eods << provide_eods(symbol: symbol, dates: today, contracts_only: false, quiet: true)
|
140
|
+
today -= 1
|
141
|
+
end
|
142
|
+
eods.flatten!.map!{|x| x.tap {|y| %i[ volume_part oi_part ].map{|z| y.delete(z)} } }
|
143
|
+
eods.delete_if { |elem| elem.flatten.empty? }
|
144
|
+
data += eods.reverse
|
145
|
+
end
|
91
146
|
|
92
147
|
measuring.call("Finished retrieving dailies.")
|
93
148
|
result = []
|
149
|
+
rounding = 8 # sym[:format].split('.').last.to_i rescue 6
|
150
|
+
indicators ||= {
|
151
|
+
typical: Cotcube::Indicators.calc(a: :high, b: :low, c: :close) {|high, low, close| (high + low + close) / 3 },
|
152
|
+
sma250_high: Cotcube::Indicators.sma(key: :high, length: 250),
|
153
|
+
sma250_low: Cotcube::Indicators.sma(key: :low, length: 250),
|
154
|
+
sma250_typ: Cotcube::Indicators.sma(key: :typical, length: 250),
|
155
|
+
dist: Cotcube::Indicators.calc(a: :high, b: :low, finalize: :to_i) {|high, low| ((high-low) / ticksize) },
|
156
|
+
}
|
157
|
+
|
158
|
+
|
159
|
+
|
94
160
|
data.group_by { |x| x[:date] }.map do |k, v|
|
95
161
|
v.map { |x| x.delete(:date) }
|
96
|
-
|
162
|
+
avg_bar = {
|
97
163
|
date: k,
|
98
|
-
|
99
|
-
|
164
|
+
contract: v.max_by{|x| x[:oi] }[:contract],
|
165
|
+
open: nil, high: nil, low: nil, close: nil,
|
166
|
+
volume: v.map { |x| x[:volume] }.reduce(:+),
|
167
|
+
oi: v.map { |x| x[:oi] }.reduce(:+),
|
168
|
+
type: :cont_eod
|
169
|
+
}
|
170
|
+
|
171
|
+
%i[ open high low close ].each do |ohlc|
|
172
|
+
avg_bar[ohlc] = (v.map{|x| x[ohlc].to_f * x[effective_selector] }.reduce(:+) / avg_bar[effective_selector]).round(rounding)
|
173
|
+
avg_bar[ohlc] = (avg_bar[ohlc] * sym[:bcf]).round(8) unless sym[:bcf] == 1.0
|
174
|
+
|
175
|
+
end
|
176
|
+
p avg_bar if debug
|
177
|
+
indicators.each do |k,v|
|
178
|
+
print format('%12s: ', k.to_s) if debug
|
179
|
+
tmp = v.call(avg_bar)
|
180
|
+
avg_bar[k] = tmp.respond_to?(:round) ? tmp.round(rounding) : tmp
|
181
|
+
puts avg_bar[k] if debug
|
182
|
+
end
|
183
|
+
%i[tr atr5].each { |ind|
|
184
|
+
avg_bar[ind] = (avg_bar[ind] / sym[:ticksize]).round.to_i unless avg_bar[ind].nil?
|
100
185
|
}
|
186
|
+
result << avg_bar
|
101
187
|
result.last[:contracts] = v
|
102
188
|
end
|
103
189
|
result
|
104
190
|
end
|
105
|
-
constname = "CONTINUOUS_#{symbol}".to_sym
|
191
|
+
constname = "CONTINUOUS_#{symbol}#{selector.nil? ? '' : ('_' + selector.to_s)}".to_sym
|
106
192
|
if rewriting or not Cotcube::Bardata.const_defined?( constname)
|
193
|
+
old = $VERBOSE; $VERBOSE = nil
|
107
194
|
Cotcube::Bardata.const_set constname, loading.call
|
195
|
+
$VERBOSE = old
|
108
196
|
end
|
109
197
|
measuring.call("Finished processing")
|
110
|
-
date.nil? ? Cotcube::Bardata.const_get(constname) : Cotcube::Bardata.const_get(constname).find { |x| x[:date] == date }
|
198
|
+
date.nil? ? Cotcube::Bardata.const_get(constname).map{|z| z.dup } : Cotcube::Bardata.const_get(constname).find { |x| x[:date] == date }
|
111
199
|
end
|
112
200
|
|
113
201
|
def continuous_ml(symbol: nil, id: nil, base: nil)
|
@@ -150,7 +238,7 @@ module Cotcube
|
|
150
238
|
%i[volume oi].include?(selector)
|
151
239
|
|
152
240
|
measuring.call("Starting")
|
153
|
-
sym = get_id_set(symbol: symbol, id: id)
|
241
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id)
|
154
242
|
id = sym[:id]
|
155
243
|
# noinspection RubyNilAnalysis
|
156
244
|
data = continuous(id: id, config: config, measure: measure).map do |x|
|
@@ -197,6 +285,7 @@ module Cotcube
|
|
197
285
|
filter: nil,
|
198
286
|
date: Date.today,
|
199
287
|
short: true,
|
288
|
+
silent: false,
|
200
289
|
measure: nil,
|
201
290
|
debuglevel: 1,
|
202
291
|
debug: false)
|
@@ -204,6 +293,7 @@ module Cotcube
|
|
204
293
|
debuglevel = debug
|
205
294
|
debug = debuglevel > 0 ? true : false
|
206
295
|
end
|
296
|
+
silent = false if debug
|
207
297
|
|
208
298
|
raise ArgumentError, ':measure, if given, must be a Time object (e.g. Time.now)' unless [NilClass, Time].include? measure.class
|
209
299
|
measuring = lambda {|c| puts "[continuous_table] Time measured until '#{c}': #{(Time.now.to_f - measure.to_f).round(2)}sec" unless measure.nil? }
|
@@ -212,7 +302,7 @@ module Cotcube
|
|
212
302
|
%i[volume oi].include?(selector)
|
213
303
|
|
214
304
|
measuring.call("Entering function")
|
215
|
-
sym = get_id_set(symbol: symbol, id: id)
|
305
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id)
|
216
306
|
if %w[R6 BJ GE].include? sym[:symbol]
|
217
307
|
puts "Rejecting to process symbol '#{sym[:symbol]}'.".light_red
|
218
308
|
return []
|
@@ -228,17 +318,18 @@ module Cotcube
|
|
228
318
|
|
229
319
|
ytoday = date.yday
|
230
320
|
data = continuous_overview(id: id, selector: selector, filter: filter, human: false, config: init, measure: measure)
|
231
|
-
.reject { |k, _| k[-2..].to_i
|
321
|
+
.reject { |k, _| k[-2..].to_i >= date.year % 2000 }
|
232
322
|
.group_by { |k, _| k[2] }
|
233
323
|
measuring.call("Retrieved continous_overview")
|
234
324
|
output_sent = []
|
235
325
|
early_year=nil
|
236
326
|
long_output = []
|
237
327
|
data.keys.sort.each do |month|
|
238
|
-
current_long = { month: month }
|
239
328
|
puts "Processing #{sym[:symbol]}#{month}" if debuglevel > 1
|
240
329
|
v0 = data[month]
|
330
|
+
# ldays is the list of 'last days'
|
241
331
|
ldays = v0.map { |_, v1| Date.parse(v1.last[:date]).yday }
|
332
|
+
# fdays is the list of 'first days'
|
242
333
|
fdays = v0.map { |_, v1| Date.parse(v1.first[:date]).yday }.sort
|
243
334
|
# if the last ml day nears the end of the year, we must fix
|
244
335
|
ldays.map! { |x| x > 350 ? x - 366 : x } if ldays.min < 50
|
@@ -275,31 +366,33 @@ module Cotcube
|
|
275
366
|
}\tto #{format '%5d', ldays.max
|
276
367
|
}: #{dfm.call(ldays.max)}".colorize(color)
|
277
368
|
if debug || (color != :white)
|
278
|
-
puts output
|
279
|
-
output_sent << "#{sym[:symbol]}#{month}" unless color == :white
|
369
|
+
puts output unless silent
|
280
370
|
end
|
371
|
+
output_sent << "#{sym[:symbol]}#{month}" unless color == :white
|
281
372
|
early_year ||= output
|
282
|
-
next
|
373
|
+
next if silent or not (debug and debuglevel >= 2)
|
283
374
|
|
284
375
|
v0.each do |contract, v1|
|
285
376
|
puts "\t#{contract
|
286
377
|
}\t#{v1.first[:date]
|
287
378
|
} (#{format '%3d', Date.parse(v1.first[:date]).yday
|
288
379
|
})\t#{Date.parse(v1.last[:date]).strftime('%a, %Y-%m-%d')
|
289
|
-
} (#{Date.parse(v1.last[:date]).yday})"
|
380
|
+
} (#{Date.parse(v1.last[:date]).yday})" unless silent
|
290
381
|
# rubocop:enable Layout/ClosingParenthesisIndentation
|
291
382
|
end
|
292
|
-
|
383
|
+
end
|
293
384
|
case output_sent.size
|
294
385
|
when 0
|
295
|
-
|
296
|
-
|
297
|
-
|
386
|
+
unless silent
|
387
|
+
puts "WARNING: No output was sent for symbol '#{sym[:symbol]}'.".colorize(:light_yellow)
|
388
|
+
puts " Assuming late-year-processing.".light_yellow
|
389
|
+
puts early_year.light_green
|
390
|
+
end
|
298
391
|
when 1
|
299
392
|
# all ok
|
300
393
|
true
|
301
394
|
else
|
302
|
-
puts "Continuous table show #{output_sent.size} active contracts ( #{output_sent} ) for #{sym[:symbol]} ---------------"
|
395
|
+
puts "Continuous table show #{output_sent.size} active contracts ( #{output_sent} ) for #{sym[:symbol]} ---------------" unless silent
|
303
396
|
end
|
304
397
|
measuring.call("Finished processing")
|
305
398
|
short ? output_sent : long_output
|
data/lib/cotcube-bardata/eods.rb
CHANGED
@@ -4,7 +4,7 @@ module Cotcube
|
|
4
4
|
# Missing top level documentation
|
5
5
|
module Bardata
|
6
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]
|
7
|
+
id = Cotcube::Helpers.get_id_set(symbol: symbol, id: id, config: config)[:id]
|
8
8
|
provide_eods(id: id, dates: date, contracts_only: true).first
|
9
9
|
end
|
10
10
|
|
@@ -15,7 +15,7 @@ module Cotcube
|
|
15
15
|
date: last_trade_date,
|
16
16
|
filter: :volume_part,
|
17
17
|
age: 1.hour)
|
18
|
-
sym = get_id_set(symbol: symbol, id: id) if symbol || id
|
18
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id) if symbol || id
|
19
19
|
# noinspection RubyScope
|
20
20
|
eods = provide_eods(id: sym.nil? ? nil : sym[:id], config: config, dates: date, filter: filter)
|
21
21
|
result = []
|
@@ -62,7 +62,7 @@ module Cotcube
|
|
62
62
|
end
|
63
63
|
|
64
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
|
65
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id, config: config) if symbol || id
|
66
66
|
# if no id can be clarified from given arguments, return all matching contracts from all available symbols
|
67
67
|
# raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
|
68
68
|
raise ArgumentError, ':filter must be in [:volume_part, :oi_part]' unless %i[volume_part oi_part].include? filter
|
@@ -45,42 +45,6 @@ module Cotcube
|
|
45
45
|
result
|
46
46
|
end
|
47
47
|
|
48
|
-
def get_id_set(symbol: nil, id: nil, contract: nil, config: init)
|
49
|
-
contract = contract.to_s.upcase if contract.is_a? Symbol
|
50
|
-
id = id.to_s.upcase if id.is_a? Symbol
|
51
|
-
symbol = symbol.to_s.upcase if symbol.is_a? Symbol
|
52
|
-
|
53
|
-
if contract.is_a?(String) && (contract.length == 5)
|
54
|
-
c_symbol = contract[0..1]
|
55
|
-
if (not symbol.nil?) && (symbol != c_symbol)
|
56
|
-
raise ArgumentError,
|
57
|
-
"Mismatch between given symbol #{symbol} and contract #{contract}"
|
58
|
-
end
|
59
|
-
|
60
|
-
symbol = c_symbol
|
61
|
-
end
|
62
|
-
|
63
|
-
unless symbol.nil?
|
64
|
-
sym = symbols.select { |s| s[:symbol] == symbol.to_s.upcase }.first
|
65
|
-
if sym.nil? || sym[:id].nil?
|
66
|
-
raise ArgumentError,
|
67
|
-
"Could not find match in #{config[:symbols_file]} for given symbol #{symbol}"
|
68
|
-
end
|
69
|
-
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if (not id.nil?) && (sym[:id] != id)
|
70
|
-
|
71
|
-
return sym
|
72
|
-
end
|
73
|
-
unless id.nil?
|
74
|
-
sym = symbols.select { |s| s[:id] == id.to_s }.first
|
75
|
-
if sym.nil? || sym[:id].nil?
|
76
|
-
raise ArgumentError,
|
77
|
-
"Could not find match in #{config[:symbols_file]} for given id #{id}"
|
78
|
-
end
|
79
|
-
return sym
|
80
|
-
end
|
81
|
-
raise ArgumentError, 'Need :id, :symbol or valid :contract '
|
82
|
-
end
|
83
|
-
|
84
48
|
def compare(contract:, format: '%5.2f')
|
85
49
|
format = "%#{format}" unless format[0] == '%'
|
86
50
|
daily = provide(contract: contract, interval: :daily)
|
@@ -14,9 +14,10 @@ module Cotcube
|
|
14
14
|
filter: :full,
|
15
15
|
# TODO: for future compatibility and suggestion: planning to include a function to update
|
16
16
|
# with live data from broker
|
17
|
+
force_update: false,
|
17
18
|
force_recent: false)
|
18
19
|
|
19
|
-
sym = get_id_set(symbol: symbol, id: id, contract: contract, config: config)
|
20
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id, contract: contract, config: config)
|
20
21
|
|
21
22
|
case interval
|
22
23
|
when :quarters, :hours, :quarter, :hour
|
@@ -33,7 +34,7 @@ module Cotcube
|
|
33
34
|
|
34
35
|
when :days, :weeks, :months
|
35
36
|
base = provide_cached contract: contract, symbol: symbol, id: id, config: config, filter: filter,
|
36
|
-
range: range, force_recent: force_recent
|
37
|
+
range: range, force_recent: force_recent, force_update: force_update
|
37
38
|
return base if %i[day days].include? interval
|
38
39
|
|
39
40
|
# TODO: Missing implementation to reduce cached days to weeks or months
|
@@ -42,7 +43,7 @@ module Cotcube
|
|
42
43
|
provide_daily contract: contract, symbol: symbol, id: id, config: config, range: range
|
43
44
|
when :synth, :synthetic, :synthetic_days
|
44
45
|
days = provide_cached contract: contract, symbol: symbol, id: id, config: config, filter: filter,
|
45
|
-
range: range, force_recent: force_recent
|
46
|
+
range: range, force_recent: force_recent, force_update: force_update
|
46
47
|
dailies = provide_daily contract: contract, symbol: symbol, id: id, config: config, range: range
|
47
48
|
if ((days.last[:datetime] > dailies.last[:datetime]) rescue false)
|
48
49
|
dailies[..-2] + days.select { |d| d[:datetime] > dailies[-2][:datetime] }
|
@@ -53,5 +54,11 @@ module Cotcube
|
|
53
54
|
raise ArgumentError, "Unsupported or unknown interval '#{interval}' in Bardata.provide"
|
54
55
|
end
|
55
56
|
end
|
57
|
+
|
58
|
+
def determine_significant_volume(base: , contract: )
|
59
|
+
set = Cotcube::Bardata.trading_hours(symbol: contract[0..1], filter: :rth)
|
60
|
+
prod = base - base.select_within(ranges: set ,attr: :datetime) {|x| x.to_datetime.to_sssm }
|
61
|
+
prod.group_by{|x| x[:volume] / 500 }
|
62
|
+
end
|
56
63
|
end
|
57
64
|
end
|
@@ -16,7 +16,7 @@ module Cotcube
|
|
16
16
|
raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
|
17
17
|
end
|
18
18
|
|
19
|
-
sym = get_id_set(symbol: symbol, id: id, contract: contract)
|
19
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id, contract: contract)
|
20
20
|
|
21
21
|
contract = contract[2..4] if contract.to_s.size == 5
|
22
22
|
id = sym[:id]
|
@@ -33,6 +33,7 @@ module Cotcube
|
|
33
33
|
%i[open high low close].map { |x| row[x] = row[x].to_f }
|
34
34
|
%i[volume day].map { |x| row[x] = row[x].to_i }
|
35
35
|
row[:datetime] = timezone.parse(row[:datetime])
|
36
|
+
row[:dist] = ((row[:high] - row[:low]) / sym[:ticksize] ).to_i
|
36
37
|
row[:type] = :quarter
|
37
38
|
row
|
38
39
|
end
|
@@ -24,7 +24,7 @@ module Cotcube
|
|
24
24
|
def range_matrix(symbol: nil, id: nil, base: nil, print: false, dim: 0.05, days_only: false, last_n: 60, &block)
|
25
25
|
# rubocop:disable Style/MultilineBlockChain
|
26
26
|
symbol ||= base.last[:contract][0..1] if id.nil?
|
27
|
-
sym = get_id_set(symbol: symbol, id: id)
|
27
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id)
|
28
28
|
source = {}
|
29
29
|
target = {}
|
30
30
|
if base.nil?
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
# missing top level documentation
|
5
|
+
module Bardata
|
6
|
+
# based on day(of year) and symbol, suggest best fitting contract
|
7
|
+
def suggest_contract_for symbol:, date: Date.today, warnings: true
|
8
|
+
ml = Cotcube::Bardata.continuous_table symbol: symbol, date: date, silent: true
|
9
|
+
if ml.size != 1
|
10
|
+
puts "WARNING: No or no unique most liquid found for #{date}. please give :contract parameter".light_yellow if warnings
|
11
|
+
if ml.size > 1
|
12
|
+
puts "\tUsing #{ml.last}. Consider breaking here, if that is not acceptable.".light_yellow if warnings
|
13
|
+
sleep 1
|
14
|
+
else
|
15
|
+
puts "\tERROR: No suggestible contract found for #{symbol} and #{date}.".light_red
|
16
|
+
return
|
17
|
+
end
|
18
|
+
end
|
19
|
+
year = date.year % 100
|
20
|
+
if ml.last[2] < "K" and date.month > 9
|
21
|
+
"#{ml.last}#{year + 1}"
|
22
|
+
else
|
23
|
+
"#{ml.last}#{year}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -5,19 +5,45 @@ module Cotcube
|
|
5
5
|
module Bardata
|
6
6
|
# fetching official trade dates from CME
|
7
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
"
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
8
|
+
def last_trade_date(force_update: false)
|
9
|
+
const_LTD = :LAST_TRADE_DATE
|
10
|
+
const_LTDU = :LAST_TRADE_DATE_UPDATE
|
11
|
+
if force_update or not Object.const_defined?(const_LTD) or Object.const_get(const_LTD).nil? or Time.now - Object.const_get(const_LTDU) > 2.hours
|
12
|
+
result = nil
|
13
|
+
uri = 'https://www.cmegroup.com/CmeWS/mvc/Volume/TradeDates?exchange=CME'
|
14
|
+
headers = { "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
15
|
+
"Accept-Encoding" => "gzip, deflate, br",
|
16
|
+
"Accept-Language" => "en-US,en;q=0.9",
|
17
|
+
"Cache-Control" => "max-age=0",
|
18
|
+
"Connection" => "keep-alive",
|
19
|
+
# Cookie: ak_bmsc=602078F6DE40954BAA8C7E7D3815102CACE82AAFD237000084B5A460F4FBCA68~pluz010T49Xag3sXquUZtVJmFX701dzEgt5v6Ht1EZSLKE4HL+bgg1L9ePnL5I0mm7QWXe1qaLhUbX1IPrL/f20trRMMRlkC3UWXk27DY/EBCP4mRno8QQygLCwgs2B2AQHJyb63WwRihCko8UYUiIhb89ArPZM5OPraoKy3JU9oE9e+iERdARNZHLHqRiB1GnmbKUvQqos3sXaEe3GpoiTszzk8sHZs4ZKuoO/rvFHko=",
|
20
|
+
"Host" => "www.cmegroup.com",
|
21
|
+
"sec-ch-ua" => %q[" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"],
|
22
|
+
"sec-ch-ua-mobile" => "?0",
|
23
|
+
"Sec-Fetch-Dest" => "document",
|
24
|
+
"Sec-Fetch-Mode" => "navigate",
|
25
|
+
"Sec-Fetch-Site" => "none",
|
26
|
+
"Sec-Fetch-User" => "?1",
|
27
|
+
"Upgrade-Insecure-Requests" => "1",
|
28
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36" }
|
29
|
+
begin
|
30
|
+
# HTTParty.get(uri, headers: { "User-Agent" => "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"})
|
31
|
+
result = HTTParty.get(uri, headers: headers)
|
32
|
+
.parsed_response
|
33
|
+
.map do |x|
|
34
|
+
a = x['tradeDate'].chars.each_slice(2).map(&:join)
|
35
|
+
"#{a[0]}#{a[1]}-#{a[2]}-#{a[3]}"
|
36
|
+
end
|
37
|
+
.first
|
38
|
+
rescue StandardError
|
39
|
+
result = nil
|
40
|
+
end
|
41
|
+
oldverbose = $VERBOSE; $VERBOSE = nil
|
42
|
+
Object.const_set(const_LTD, result)
|
43
|
+
Object.const_set(const_LTDU, Time.now) unless result.nil?
|
44
|
+
$VERBOSE = oldverbose
|
20
45
|
end
|
46
|
+
Object.const_get(const_LTD)
|
21
47
|
end
|
22
48
|
|
23
49
|
def holidays(config: init)
|
@@ -12,17 +12,23 @@ module Cotcube
|
|
12
12
|
filter: ,
|
13
13
|
force_filter: false, # with force_filter one would avoid falling back
|
14
14
|
# to the contract_type based range set
|
15
|
+
headers_only: false, # return only headers instead of ranges
|
15
16
|
config: init, debug: false)
|
16
17
|
return (0...24 * 7 * 3600) if filter.to_s =~ /24x7/
|
17
18
|
|
18
19
|
prepare = lambda do |f|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
if headers_only
|
21
|
+
CSV.read(f)
|
22
|
+
.first
|
23
|
+
else
|
24
|
+
CSV.read(f, converters: :numeric)
|
25
|
+
.map(&:to_a)
|
26
|
+
.tap { |x| x.shift unless x.first.first.is_a?(Numeric) }
|
27
|
+
.map { |x| (x.first...x.last) }
|
28
|
+
end
|
23
29
|
end
|
24
30
|
|
25
|
-
sym = get_id_set(symbol: symbol, id: id)
|
31
|
+
sym = Cotcube::Helpers.get_id_set(symbol: symbol, id: id)
|
26
32
|
|
27
33
|
file = "#{config[:data_path]}/trading_hours/#{sym[:symbol]}_#{filter}.csv"
|
28
34
|
puts "Trying to use #{file} for #{symbol} + #{filter}" if debug
|
data/lib/cotcube-bardata.rb
CHANGED
@@ -11,6 +11,7 @@ require 'date' unless defined?(DateTime)
|
|
11
11
|
require 'csv' unless defined?(CSV)
|
12
12
|
require 'yaml' unless defined?(YAML)
|
13
13
|
require 'cotcube-helpers'
|
14
|
+
require 'cotcube-indicators'
|
14
15
|
require 'parallel'
|
15
16
|
|
16
17
|
require_relative 'cotcube-bardata/constants'
|
@@ -22,6 +23,7 @@ require_relative 'cotcube-bardata/quarters'
|
|
22
23
|
require_relative 'cotcube-bardata/eods'
|
23
24
|
require_relative 'cotcube-bardata/cached'
|
24
25
|
require_relative 'cotcube-bardata/provide'
|
26
|
+
require_relative 'cotcube-bardata/suggest'
|
25
27
|
require_relative 'cotcube-bardata/range_matrix'
|
26
28
|
require_relative 'cotcube-bardata/trading_hours'
|
27
29
|
|
@@ -60,14 +62,14 @@ module Cotcube
|
|
60
62
|
:range_matrix,
|
61
63
|
# create an array of ranges based on specified source data
|
62
64
|
:trading_hours,
|
63
|
-
#
|
64
|
-
:get_id_set,
|
65
|
+
#
|
65
66
|
:select_specific_date,
|
66
67
|
:extended_select_for_range,
|
67
68
|
:provide_cached,
|
69
|
+
:suggest_contract_for,
|
70
|
+
#
|
68
71
|
:compare,
|
69
|
-
:holidays
|
70
|
-
:symbols # reads and provides the symbols file
|
72
|
+
:holidays
|
71
73
|
|
72
74
|
# please note that module_functions of source provided in private files must be published there
|
73
75
|
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.15.5
|
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: 2021-
|
11
|
+
date: 2021-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0.
|
47
|
+
version: '0.2'
|
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.2'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: cotcube-indicators
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,11 +175,12 @@ files:
|
|
175
175
|
- lib/cotcube-bardata/provide.rb
|
176
176
|
- lib/cotcube-bardata/quarters.rb
|
177
177
|
- lib/cotcube-bardata/range_matrix.rb
|
178
|
+
- lib/cotcube-bardata/suggest.rb
|
178
179
|
- lib/cotcube-bardata/trade_dates.rb
|
179
180
|
- lib/cotcube-bardata/trading_hours.rb
|
180
181
|
homepage: https://github.com/donkeybridge/cotcube-bardata
|
181
182
|
licenses:
|
182
|
-
- BSD-
|
183
|
+
- BSD-3-Clause
|
183
184
|
metadata:
|
184
185
|
homepage_uri: https://github.com/donkeybridge/cotcube-bardata
|
185
186
|
source_code_uri: https://github.com/donkeybridge/cotcube-bardata
|
@@ -199,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
200
|
- !ruby/object:Gem::Version
|
200
201
|
version: '0'
|
201
202
|
requirements: []
|
202
|
-
rubygems_version: 3.1.
|
203
|
+
rubygems_version: 3.1.6
|
203
204
|
signing_key:
|
204
205
|
specification_version: 4
|
205
206
|
summary: Functions to provide bardata; and some simple time series aggregations
|