cotcube-bardata 0.1.11 → 0.1.15.2
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 +1 -1
- data/lib/cotcube-bardata.rb +8 -2
- data/lib/cotcube-bardata/cached.rb +7 -3
- data/lib/cotcube-bardata/daily.rb +150 -39
- data/lib/cotcube-bardata/helpers.rb +4 -0
- data/lib/cotcube-bardata/init.rb +2 -2
- data/lib/cotcube-bardata/provide.rb +9 -2
- data/lib/cotcube-bardata/quarters.rb +1 -0
- data/lib/cotcube-bardata/range_matrix.rb +18 -7
- data/lib/cotcube-bardata/suggest.rb +28 -0
- data/lib/cotcube-bardata/trade_dates.rb +38 -12
- data/lib/cotcube-bardata/trading_hours.rb +10 -4
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46f85dd103fd911ba475b347f14002f1402eda630b79ded6a161969a1df487db
|
4
|
+
data.tar.gz: 72f66b63600041d28131df575036d0295f8ab206e132fcd69c2ea84109194505
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29271f6f4b5dba379bb19e81846d52f1d2995bd010f35b7e80bad3256ae415c841eeead6a84034ce72d29cd6b3746d4d62fb182347abca1fa6d78fd0c3bfb6a7
|
7
|
+
data.tar.gz: 5cb42e0fbd5ae2124edb7ede368047e72c8fe68b99ed928727d8e47859a5c94470bab989a032f0689e63058dd01b190936d02f22d8be7fca3cd61f3240a40a1c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
## 0.1.15.2 (August 04, 2021)
|
2
|
+
- fixed leftover debug setting
|
3
|
+
|
4
|
+
## 0.1.15.1 (August 04, 2021)
|
5
|
+
- fixed license mismatch
|
6
|
+
|
7
|
+
## 0.1.15 (August 04, 2021)
|
8
|
+
- daily.rb: added support to add eod data on incomplete dailies
|
9
|
+
- adding dep: cotcube-indicators
|
10
|
+
- provide: new method :determine_significant_volume
|
11
|
+
- suggest: adding silence
|
12
|
+
- cached: Adding :dist
|
13
|
+
- daily: new method :determine_significant_volume
|
14
|
+
- added :dist generically to quarters
|
15
|
+
- trading_hours: added param to return headers only
|
16
|
+
- last_trade_date: Enabled caching instead of fetching each time
|
17
|
+
|
18
|
+
## 0.1.14 (May 07, 2021)
|
19
|
+
- few changes in provide / cached / daily for a more straigtforward forcing of cache renewal
|
20
|
+
- suggest: new method to suggest a contract for given symbol and date
|
21
|
+
|
22
|
+
## 0.1.13 (April 07, 2021)
|
23
|
+
- daily: fixed const_caching in continuous
|
24
|
+
- trade_dates: FIXING call with HTTParty must send Agent Header
|
25
|
+
- helpers/get_id_set: added support for params given as Symbols (:NG instead of 'NG')
|
26
|
+
|
27
|
+
## 0.1.12 (March 13, 2021)
|
28
|
+
- range_matrix: adapted to accept block for range provision; added params :days_only and :last_n
|
29
|
+
- minor fix on previous patch
|
30
|
+
|
1
31
|
## 0.1.11 (March 07, 2021)
|
2
32
|
- daily.rb: added new technique 'caching in constants' to accelerate computation, also referring to continuous
|
3
33
|
- provide.rb: minor change, so disregarded contracts can be used in swapproximate
|
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.2
|
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
|
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
|
|
@@ -61,13 +63,17 @@ module Cotcube
|
|
61
63
|
# create an array of ranges based on specified source data
|
62
64
|
:trading_hours,
|
63
65
|
# receive id / symbol information on an uncertain set of parameters
|
66
|
+
# TODO: decommision sppearance of get_id_set and symbols in cotcube::bardata
|
64
67
|
:get_id_set,
|
68
|
+
:symbols,
|
69
|
+
#
|
65
70
|
:select_specific_date,
|
66
71
|
:extended_select_for_range,
|
67
72
|
:provide_cached,
|
73
|
+
:suggest_contract_for,
|
74
|
+
#
|
68
75
|
:compare,
|
69
|
-
:holidays
|
70
|
-
:symbols # reads and provides the symbols file
|
76
|
+
:holidays
|
71
77
|
|
72
78
|
# please note that module_functions of source provided in private files must be published there
|
73
79
|
end
|
@@ -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) &&
|
@@ -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|
|
@@ -8,6 +8,8 @@ module Cotcube
|
|
8
8
|
symbol: nil, id: nil,
|
9
9
|
range: nil,
|
10
10
|
timezone: Time.find_zone('America/Chicago'),
|
11
|
+
keep_last: false,
|
12
|
+
add_eods: true,
|
11
13
|
config: init)
|
12
14
|
contract = contract.to_s.upcase
|
13
15
|
unless contract.is_a?(String) && [3, 5].include?(contract.size)
|
@@ -45,10 +47,29 @@ module Cotcube
|
|
45
47
|
row[k] = row[k].to_i if %i[volume oi].include? k
|
46
48
|
end
|
47
49
|
row[:datetime] = timezone.parse(row[:date])
|
50
|
+
row[:dist] = ((row[:high] - row[:low]) / sym[:ticksize] ).to_i
|
48
51
|
row[:type] = :daily
|
49
52
|
row
|
50
53
|
end
|
51
|
-
|
54
|
+
contract_expired = data.last[:high].zero?
|
55
|
+
data.pop if contract_expired and not keep_last
|
56
|
+
if not contract_expired and add_eods
|
57
|
+
today = Date.today
|
58
|
+
eods = [ ]
|
59
|
+
while today.strftime('%Y-%m-%d') > data.last[:date]
|
60
|
+
eods << provide_eods(symbol: sym[:symbol], dates: today, contracts_only: false)
|
61
|
+
today -= 1
|
62
|
+
end
|
63
|
+
eods.flatten!.map!{|x| x.tap {|y| %i[ volume_part oi_part ].map{|z| y.delete(z)} } }
|
64
|
+
eods.select{|x| x[:contract] == contract }
|
65
|
+
eods.map!{|x| x.tap{|y|
|
66
|
+
y[:datetime] = timezone.parse(y[:date])
|
67
|
+
y[:dist] = ((y[:high] - y[:low]) / sym[:ticksize] ).to_i
|
68
|
+
y[:type] = :eod
|
69
|
+
} }
|
70
|
+
data += eods.reverse
|
71
|
+
|
72
|
+
end
|
52
73
|
if range.nil?
|
53
74
|
data
|
54
75
|
else
|
@@ -61,50 +82,128 @@ module Cotcube
|
|
61
82
|
|
62
83
|
# reads all files in bardata/daily/<id> and aggregates by date
|
63
84
|
# (what is a pre-stage of a continuous based on daily bars)
|
64
|
-
def continuous(symbol: nil, id: nil, config: init, date: nil, measure: nil, force_rewrite: false)
|
85
|
+
def continuous(symbol: nil, id: nil, config: init, date: nil, measure: nil, force_rewrite: false, selector: nil, debug: false, add_eods: true)
|
65
86
|
raise ArgumentError, ':measure, if given, must be a Time object (e.g. Time.now)' unless [NilClass, Time].include? measure.class
|
66
87
|
measuring = lambda {|c| puts "[continuous] Time measured until '#{c}': #{(Time.now.to_f - measure.to_f).round(2)}sec" unless measure.nil? }
|
67
88
|
|
68
89
|
measuring.call("Starting")
|
69
90
|
sym = get_id_set(symbol: symbol, id: id)
|
70
91
|
id = sym[:id]
|
92
|
+
symbol = sym[:symbol]
|
93
|
+
ticksize = sym[:ticksize]
|
94
|
+
effective_selector = selector || :volume
|
95
|
+
raise ArgumentError, 'selector must be in %i[ nil :volume ;oi].' unless [ nil, :volume, :oi ].include? selector
|
71
96
|
id_path = "#{config[:data_path]}/daily/#{id}"
|
72
97
|
c_file = "#{id_path}/continuous.csv"
|
98
|
+
puts "Using file #{c_file}" if debug
|
73
99
|
|
74
100
|
# instead of using the provide_daily methods above, for this bulk operation a 'continuous.csv' is created
|
75
101
|
# this boosts from 4.5sec to 0.3sec
|
76
102
|
rewriting = force_rewrite or not(File.exist?(c_file)) or (Time.now - File.mtime(c_file) > 8.days)
|
77
103
|
if rewriting
|
78
104
|
puts "In daily+continuous: Rewriting #{c_file} #{force_rewrite ? "forcibly" : "due to fileage"}.".light_yellow
|
79
|
-
`rm #{c_file}; find #{id_path} | xargs cat 2>/dev/null | grep -v '
|
105
|
+
`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}`
|
80
106
|
end
|
81
107
|
loading = lambda do
|
82
108
|
data = CSV.read(c_file).map do |row|
|
83
109
|
r = { contract: row[0],
|
84
110
|
date: row[1],
|
85
|
-
|
86
|
-
|
111
|
+
open: row[2],
|
112
|
+
high: row[3],
|
113
|
+
low: row[4],
|
114
|
+
close: row[5],
|
115
|
+
volume: row[6].to_i,
|
116
|
+
oi: row[7].to_i
|
87
117
|
}
|
88
118
|
end
|
119
|
+
if add_eods
|
120
|
+
today = Date.today
|
121
|
+
eods = [ ]
|
122
|
+
while today.strftime('%Y-%m-%d') > data.last[:date]
|
123
|
+
eods << provide_eods(symbol: symbol, dates: today, contracts_only: false)
|
124
|
+
today -= 1
|
125
|
+
end
|
126
|
+
eods.flatten!.map!{|x| x.tap {|y| %i[ volume_part oi_part ].map{|z| y.delete(z)} } }
|
127
|
+
eods.delete_if { |elem| elem.flatten.empty? }
|
128
|
+
data += eods.reverse
|
129
|
+
|
130
|
+
end
|
89
131
|
|
90
132
|
measuring.call("Finished retrieving dailies.")
|
91
133
|
result = []
|
134
|
+
rounding = 8 # sym[:format].split('.').last.to_i rescue 6
|
135
|
+
indicators ||= {
|
136
|
+
typical: Cotcube::Indicators.calc(a: :high, b: :low, c: :close) {|high, low, close| (high + low + close) / 3 },
|
137
|
+
sma250_high: Cotcube::Indicators.sma(key: :high, length: 250),
|
138
|
+
sma250_low: Cotcube::Indicators.sma(key: :low, length: 250),
|
139
|
+
sma250_typ: Cotcube::Indicators.sma(key: :typical, length: 250),
|
140
|
+
# sma60_typ: Cotcube::Indicators.sma(key: :typical, length: short),
|
141
|
+
# tr: Cotcube::Indicators.true_range,
|
142
|
+
# atr5: Cotcube::Indicators.sma(key: :tr, length: 5),
|
143
|
+
# dist_abs: Cotcube::Indicators.calc(a: :sma250_high, b: :sma250_low, c: :high, d: :low) do |sma_high, sma_low, high, low|
|
144
|
+
# if high > sma_high
|
145
|
+
# high - sma_high
|
146
|
+
# elsif sma_low > low
|
147
|
+
# low - sma_low
|
148
|
+
# else
|
149
|
+
# 0
|
150
|
+
# end
|
151
|
+
#end,
|
152
|
+
#dist_sma: Cotcube::Indicators.calc(a: :sma250_typ, b: :sma60_typ) do |sma250, sma60|
|
153
|
+
# sma60 - sma250
|
154
|
+
#end,
|
155
|
+
#dist_index: Cotcube::Indicators.index(key: :dist_abs, length: 60, abs: true),
|
156
|
+
#dev250_squared: Cotcube::Indicators.calc(a: :sma250_typ, b: :typical) {|sma, x| (sma - x) ** 2 },
|
157
|
+
#var250: Cotcube::Indicators.sma(key: :dev250_squared, length: long),
|
158
|
+
#sig250: Cotcube::Indicators.calc(a: :var250) {|var| Math.sqrt(var)},
|
159
|
+
#boll250_high: Cotcube::Indicators.calc(a: :sig250, b: :sma250_typ) {|sig, typ| (typ + sig * bollinger_factor) rescue nil},
|
160
|
+
#boll250_low: Cotcube::Indicators.calc(a: :sig250, b: :sma250_typ) {|sig, typ| (typ - sig * bollinger_factor) rescue nil},
|
161
|
+
#dev60_squared: Cotcube::Indicators.calc(a: :sma60_typ, b: :typical) {|sma, x| (sma - x) ** 2 },
|
162
|
+
#var60: Cotcube::Indicators.sma(key: :dev60_squared, length: short),
|
163
|
+
#sig60: Cotcube::Indicators.calc(a: :var60) {|var| Math.sqrt(var)},
|
164
|
+
#boll60_high: Cotcube::Indicators.calc(a: :sig60, b: :sma60_typ) {|sig, typ| (typ + sig * bollinger_factor) rescue nil},
|
165
|
+
#boll60_low: Cotcube::Indicators.calc(a: :sig60, b: :sma60_typ) {|sig, typ| (typ - sig * bollinger_factor) rescue nil}
|
166
|
+
dist: Cotcube::Indicators.calc(a: :high, b: :low, finalize: :to_i) {|high, low| ((high-low) / ticksize) }
|
167
|
+
|
168
|
+
}
|
169
|
+
|
170
|
+
|
171
|
+
|
92
172
|
data.group_by { |x| x[:date] }.map do |k, v|
|
93
173
|
v.map { |x| x.delete(:date) }
|
94
|
-
|
174
|
+
avg_bar = {
|
95
175
|
date: k,
|
96
|
-
|
97
|
-
|
176
|
+
contract: v.max_by{|x| x[:oi] }[:contract],
|
177
|
+
open: nil, high: nil, low: nil, close: nil,
|
178
|
+
volume: v.map { |x| x[:volume] }.reduce(:+),
|
179
|
+
oi: v.map { |x| x[:oi] }.reduce(:+),
|
98
180
|
}
|
181
|
+
|
182
|
+
%i[ open high low close ].each do |ohlc|
|
183
|
+
avg_bar[ohlc] = (v.map{|x| x[ohlc].to_f * x[effective_selector] }.reduce(:+) / avg_bar[effective_selector]).round(rounding)
|
184
|
+
end
|
185
|
+
p avg_bar if debug
|
186
|
+
indicators.each do |k,v|
|
187
|
+
print format('%12s: ', k.to_s) if debug
|
188
|
+
avg_bar[k] = v.call(avg_bar).round(rounding)
|
189
|
+
puts avg_bar[k] if debug
|
190
|
+
end
|
191
|
+
%i[tr atr5].each { |ind|
|
192
|
+
avg_bar[ind] = (avg_bar[ind] / sym[:ticksize]).round.to_i unless avg_bar[ind].nil?
|
193
|
+
}
|
194
|
+
result << avg_bar
|
99
195
|
result.last[:contracts] = v
|
100
196
|
end
|
197
|
+
result
|
101
198
|
end
|
102
|
-
constname = "CONTINUOUS_#{symbol}".to_sym
|
199
|
+
constname = "CONTINUOUS_#{symbol}#{selector.nil? ? '' : ('_' + selector.to_s)}".to_sym
|
103
200
|
if rewriting or not Cotcube::Bardata.const_defined?( constname)
|
201
|
+
old = $VERBOSE; $VERBOSE = nil
|
104
202
|
Cotcube::Bardata.const_set constname, loading.call
|
203
|
+
$VERBOSE = old
|
105
204
|
end
|
106
205
|
measuring.call("Finished processing")
|
107
|
-
date.nil? ? Cotcube::Bardata.const_get(constname) : Cotcube::Bardata.const_get(constname).find { |x| x[:date] == date }
|
206
|
+
date.nil? ? Cotcube::Bardata.const_get(constname).map{|z| z.dup } : Cotcube::Bardata.const_get(constname).find { |x| x[:date] == date }
|
108
207
|
end
|
109
208
|
|
110
209
|
def continuous_ml(symbol: nil, id: nil, base: nil)
|
@@ -157,7 +256,7 @@ module Cotcube
|
|
157
256
|
oi: x[:contracts].sort_by { |z| - z[:oi] }[0..4].compact.reject { |z| z[:oi].zero? }
|
158
257
|
}
|
159
258
|
end
|
160
|
-
measuring.call("Retrieved continuous")
|
259
|
+
measuring.call("Retrieved continuous for #{sym[:symbol]}")
|
161
260
|
data.reject! { |x| x[selector].empty? }
|
162
261
|
result = data.group_by { |x| x[selector].first[:contract] }
|
163
262
|
result.each_key do |key|
|
@@ -193,6 +292,8 @@ module Cotcube
|
|
193
292
|
selector: :volume,
|
194
293
|
filter: nil,
|
195
294
|
date: Date.today,
|
295
|
+
short: true,
|
296
|
+
silent: false,
|
196
297
|
measure: nil,
|
197
298
|
debuglevel: 1,
|
198
299
|
debug: false)
|
@@ -200,6 +301,7 @@ module Cotcube
|
|
200
301
|
debuglevel = debug
|
201
302
|
debug = debuglevel > 0 ? true : false
|
202
303
|
end
|
304
|
+
silent = false if debug
|
203
305
|
|
204
306
|
raise ArgumentError, ':measure, if given, must be a Time object (e.g. Time.now)' unless [NilClass, Time].include? measure.class
|
205
307
|
measuring = lambda {|c| puts "[continuous_table] Time measured until '#{c}': #{(Time.now.to_f - measure.to_f).round(2)}sec" unless measure.nil? }
|
@@ -229,69 +331,78 @@ module Cotcube
|
|
229
331
|
measuring.call("Retrieved continous_overview")
|
230
332
|
output_sent = []
|
231
333
|
early_year=nil
|
334
|
+
long_output = []
|
232
335
|
data.keys.sort.each do |month|
|
336
|
+
current_long = { month: month }
|
233
337
|
puts "Processing #{sym[:symbol]}#{month}" if debuglevel > 1
|
234
338
|
v0 = data[month]
|
235
|
-
|
339
|
+
ldays = v0.map { |_, v1| Date.parse(v1.last[:date]).yday }
|
236
340
|
fdays = v0.map { |_, v1| Date.parse(v1.first[:date]).yday }.sort
|
237
341
|
# if the last ml day nears the end of the year, we must fix
|
238
|
-
|
342
|
+
ldays.map! { |x| x > 350 ? x - 366 : x } if ldays.min < 50
|
239
343
|
fday = fdays[fdays.size / 2]
|
240
|
-
|
241
|
-
# a contract is proposed to use after fday - 1, but before
|
242
|
-
# it is warned to user after fday - 1 but before
|
243
|
-
# it is warned red >=
|
244
|
-
color = if (ytoday >=
|
344
|
+
lavg = ldays.reduce(:+) / ldays.size
|
345
|
+
# a contract is proposed to use after fday - 1, but before ldays.min (green)
|
346
|
+
# it is warned to user after fday - 1 but before lavg - 1 (red)
|
347
|
+
# it is warned red >= lavg - 1 and <= lavg + 1
|
348
|
+
color = if (ytoday >= lavg - 1) && (ytoday <= lavg + 1)
|
245
349
|
:light_red
|
246
|
-
elsif (ytoday >
|
350
|
+
elsif (ytoday > ldays.min) && (ytoday < lavg - 1)
|
247
351
|
:light_yellow
|
248
|
-
elsif (ytoday >= (fday >
|
352
|
+
elsif (ytoday >= (fday > lavg ? 0 : fday - 5)) && (ytoday <= ldays.min)
|
249
353
|
:light_green
|
250
354
|
else
|
251
355
|
:white
|
252
356
|
end
|
253
357
|
# rubocop:disable Layout/ClosingParenthesisIndentation
|
358
|
+
long_output << {
|
359
|
+
month: month,
|
360
|
+
first_ml: fday,
|
361
|
+
last_min: ldays.min,
|
362
|
+
last_avg: lavg,
|
363
|
+
last_max: ldays.max }
|
254
364
|
output = "#{sym[:symbol]
|
255
365
|
}#{month
|
256
366
|
}\t#{format '%12s', sym[:type]
|
257
|
-
}\
|
258
|
-
|
259
|
-
}\
|
260
|
-
}: #{dfm.call(
|
261
|
-
}\t#{format '%5d',
|
262
|
-
}: #{dfm.call(
|
263
|
-
}\
|
264
|
-
}: #{dfm.call(
|
367
|
+
}\ttoday is #{ytoday
|
368
|
+
} -- median of first is #{fday
|
369
|
+
}\tlast ranges from #{format '%5d', ldays.min
|
370
|
+
}: #{dfm.call(ldays.min)
|
371
|
+
}\t#{format '%5d', lavg
|
372
|
+
}: #{dfm.call(lavg)
|
373
|
+
}\tto #{format '%5d', ldays.max
|
374
|
+
}: #{dfm.call(ldays.max)}".colorize(color)
|
265
375
|
if debug || (color != :white)
|
266
|
-
puts output
|
376
|
+
puts output unless silent
|
267
377
|
output_sent << "#{sym[:symbol]}#{month}" unless color == :white
|
268
378
|
end
|
269
379
|
early_year ||= output
|
270
|
-
next
|
380
|
+
next if silent or not (debug and debuglevel >= 2)
|
271
381
|
|
272
382
|
v0.each do |contract, v1|
|
273
383
|
puts "\t#{contract
|
274
384
|
}\t#{v1.first[:date]
|
275
|
-
}
|
276
|
-
|
277
|
-
} (#{Date.parse(v1.last[:date]).yday}"
|
385
|
+
} (#{format '%3d', Date.parse(v1.first[:date]).yday
|
386
|
+
})\t#{Date.parse(v1.last[:date]).strftime('%a, %Y-%m-%d')
|
387
|
+
} (#{Date.parse(v1.last[:date]).yday})" unless silent
|
278
388
|
# rubocop:enable Layout/ClosingParenthesisIndentation
|
279
389
|
end
|
280
390
|
end
|
281
391
|
case output_sent.size
|
282
392
|
when 0
|
283
|
-
|
284
|
-
|
285
|
-
|
393
|
+
unless silent
|
394
|
+
puts "WARNING: No output was sent for symbol '#{sym[:symbol]}'.".colorize(:light_yellow)
|
395
|
+
puts " Assuming late-year-processing.".light_yellow
|
396
|
+
puts early_year.light_green
|
397
|
+
end
|
286
398
|
when 1
|
287
399
|
# all ok
|
288
400
|
true
|
289
401
|
else
|
290
|
-
puts "
|
291
|
-
|
402
|
+
puts "Continuous table show #{output_sent.size} active contracts ( #{output_sent} ) for #{sym[:symbol]} ---------------" unless silent
|
292
403
|
end
|
293
404
|
measuring.call("Finished processing")
|
294
|
-
output_sent
|
405
|
+
short ? output_sent : long_output
|
295
406
|
end
|
296
407
|
end
|
297
408
|
end
|
@@ -46,6 +46,10 @@ module Cotcube
|
|
46
46
|
end
|
47
47
|
|
48
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
|
+
|
49
53
|
if contract.is_a?(String) && (contract.length == 5)
|
50
54
|
c_symbol = contract[0..1]
|
51
55
|
if (not symbol.nil?) && (symbol != c_symbol)
|
data/lib/cotcube-bardata/init.rb
CHANGED
@@ -8,9 +8,9 @@ module Cotcube
|
|
8
8
|
SYMBOL_EXAMPLES
|
9
9
|
else
|
10
10
|
CSV
|
11
|
-
.read(config[:symbols_file], headers: %i[id symbol ticksize power months type bcf reports name])
|
11
|
+
.read(config[:symbols_file], headers: %i[id symbol ticksize power months type bcf reports format name])
|
12
12
|
.map(&:to_h)
|
13
|
-
.map { |row| %i[ticksize power bcf].each { |z| row[z] = row[z].to_f }; row } # rubocop:disable Style/Semicolon
|
13
|
+
.map { |row| %i[ticksize power bcf].each { |z| row[z] = row[z].to_f }; row[:format] = "%#{row[:format]}f"; row } # rubocop:disable Style/Semicolon
|
14
14
|
.reject { |row| row[:id].nil? }
|
15
15
|
.tap { |all| all.select! { |x| x[:type] == type } unless type.nil? }
|
16
16
|
.tap { |all| all.select! { |x| x[:symbol] == symbol } unless symbol.nil? }
|
@@ -14,6 +14,7 @@ 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
20
|
sym = get_id_set(symbol: symbol, id: id, contract: contract, config: config)
|
@@ -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
|
@@ -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
|
@@ -5,7 +5,6 @@ module Cotcube
|
|
5
5
|
module Bardata
|
6
6
|
# this is an analysis tool to investigate actual ranges of an underlying symbol
|
7
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
8
|
#
|
10
9
|
# The result printed / returned is a table, containing a matrix of rows:
|
11
10
|
# 1. size: the amount of values evaluated
|
@@ -22,18 +21,25 @@ module Cotcube
|
|
22
21
|
# 3.a-c) same with days reduced to months (c: 12 months)
|
23
22
|
#
|
24
23
|
# NOTE: there is now a new method Cotcube::Helpers.simple_series_stats, that should be used in favor.
|
25
|
-
def range_matrix(symbol: nil, id: nil, print: false, dim: 0.05)
|
24
|
+
def range_matrix(symbol: nil, id: nil, base: nil, print: false, dim: 0.05, days_only: false, last_n: 60, &block)
|
26
25
|
# rubocop:disable Style/MultilineBlockChain
|
26
|
+
symbol ||= base.last[:contract][0..1] if id.nil?
|
27
27
|
sym = get_id_set(symbol: symbol, id: id)
|
28
28
|
source = {}
|
29
29
|
target = {}
|
30
|
-
|
30
|
+
if base.nil?
|
31
|
+
ml = (Cotcube::Bardata.continuous_actual_ml symbol: symbol)&.last[:contract]
|
32
|
+
source[:days] = Cotcube::Bardata.provide contract: ml
|
33
|
+
else
|
34
|
+
source[:days] = base
|
35
|
+
end
|
31
36
|
source[:weeks] = Cotcube::Helpers.reduce bars: source[:days], to: :weeks
|
32
37
|
source[:months] = Cotcube::Helpers.reduce bars: source[:days], to: :months
|
33
38
|
|
34
39
|
%i[days weeks months].each do |period|
|
40
|
+
next if days_only and %i[weeks months].include? period
|
35
41
|
source[period].map! do |x|
|
36
|
-
x[:range] = ((x[:high] - x[:low]) / sym[:ticksize]).round
|
42
|
+
x[:range] = block_given? ? yield(x) : (((x[:high] - x[:low]) / sym[:ticksize]).round)
|
37
43
|
x
|
38
44
|
end
|
39
45
|
target[period] = {}
|
@@ -80,14 +86,18 @@ module Cotcube
|
|
80
86
|
|
81
87
|
range = case period
|
82
88
|
when :months
|
83
|
-
-
|
89
|
+
-(last_n/15)..-2
|
84
90
|
when :weeks
|
85
|
-
-
|
91
|
+
-(last_n/4)..-2
|
86
92
|
when :days
|
87
|
-
-
|
93
|
+
-last_n..-1
|
88
94
|
else
|
89
95
|
raise ArgumentError, "Unsupported period: '#{period}'"
|
90
96
|
end
|
97
|
+
if range.begin.abs > source[period].size
|
98
|
+
puts "WARNING: requested last_n = #{last_n} exceeds actual size (#{source[period].size}), adjusting...".light_yellow
|
99
|
+
range = (-source[period].size..range.end)
|
100
|
+
end
|
91
101
|
custom = source[period][range]
|
92
102
|
target[period][:rec_size] = custom.size
|
93
103
|
target[period][:rec_avg] = (custom.map { |x| x[:range] }.reduce(:+) / custom.size).round
|
@@ -110,6 +120,7 @@ module Cotcube
|
|
110
120
|
%w[size avg lower median upper max].each do |a|
|
111
121
|
print "#{'%10s' % a} | " # rubocop:disable Style/FormatString
|
112
122
|
%i[days weeks months].each do |b|
|
123
|
+
next if days_only and %i[weeks months].include? b
|
113
124
|
%w[all dim rec].each do |c|
|
114
125
|
print ('%8d' % target[b]["#{c}_#{a}".to_sym]).to_s # rubocop:disable Style/FormatString
|
115
126
|
end
|
@@ -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,14 +12,20 @@ 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
31
|
sym = get_id_set(symbol: symbol, id: id)
|
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.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: 2021-
|
11
|
+
date: 2021-08-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -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
|