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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc417f9b8b33204bbc1b345ec28e2c5604b2a5f59c183a6a0a1ad15a6cf223b1
4
- data.tar.gz: f631c5507dff588e4f2e19209aca5d135514e450ddc9b311e407b9733f539191
3
+ metadata.gz: 46f85dd103fd911ba475b347f14002f1402eda630b79ded6a161969a1df487db
4
+ data.tar.gz: 72f66b63600041d28131df575036d0295f8ab206e132fcd69c2ea84109194505
5
5
  SHA512:
6
- metadata.gz: f5276bd185a561658226f65b92ce696774f1da7442bf0c10704925e4afd295566844d02bf866921575bf60cea6944555c7dd6abc28214ed5852ca8228553d2f3
7
- data.tar.gz: c73f923b1bf4399bf6a9cea56e4adf2acc3aaeab770d4b56cc1daae7d5a4f7c4617f30baff58b03f9ced45a9987a4bf8106c709f12a1cfb17f0021cfe36a6d71
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 [MIT License](https://opensource.org/licenses/MIT).
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.11
1
+ 0.1.15.2
@@ -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-4-Clause'
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
@@ -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 unless marked
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
- data.pop if data.last[:high].zero?
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 '0,0,0,0' | sort -t, -k2 | cut -d, -f1,2,7,8 > #{c_file}`
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
- volume: row[2].to_i,
86
- oi: row[3].to_i
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
- result << {
174
+ avg_bar = {
95
175
  date: k,
96
- volume: v.map { |x| x[:volume] }.reduce(:+),
97
- oi: v.map { |x| x[:oi] }.reduce(:+)
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
- ydays = v0.map { |_, v1| Date.parse(v1.last[:date]).yday }
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
- ydays.map! { |x| x > 350 ? x - 366 : x } if ydays.min < 50
342
+ ldays.map! { |x| x > 350 ? x - 366 : x } if ldays.min < 50
239
343
  fday = fdays[fdays.size / 2]
240
- yavg = ydays.reduce(:+) / ydays.size
241
- # a contract is proposed to use after fday - 1, but before ydays.min (green)
242
- # it is warned to user after fday - 1 but before yavg - 1 (red)
243
- # it is warned red >= yavg - 1 and <= yavg + 1
244
- color = if (ytoday >= yavg - 1) && (ytoday <= yavg + 1)
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 > ydays.min) && (ytoday < yavg - 1)
350
+ elsif (ytoday > ldays.min) && (ytoday < lavg - 1)
247
351
  :light_yellow
248
- elsif (ytoday >= (fday > yavg ? 0 : fday - 5)) && (ytoday <= ydays.min)
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
- }\t#{ytoday
258
- }/#{fday
259
- }\t#{format '%5d', ydays.min
260
- }: #{dfm.call(ydays.min)
261
- }\t#{format '%5d', yavg
262
- }: #{dfm.call(yavg)
263
- }\t#{format '%5d', ydays.max
264
- }: #{dfm.call(ydays.max)}".colorize(color)
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 unless (debug and debuglevel >= 2)
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
- }\t#{Date.parse(v1.last[:date]).strftime('%a')
276
- }, #{v1.last[:date]
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
- puts "WARNING: No output was sent for symbol '#{sym[:symbol]}'.".colorize(:light_yellow)
284
- puts " Assuming late-year-processing.".light_yellow
285
- puts early_year.light_green
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 "---- #{output_sent} for #{sym[:symbol]} ---------------"
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)
@@ -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
- source[:days] = Cotcube::Bardata.continuous_actual_ml symbol: symbol
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
- -13..-2
89
+ -(last_n/15)..-2
84
90
  when :weeks
85
- -53..-2
91
+ -(last_n/4)..-2
86
92
  when :days
87
- -200..-1
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
- uri = 'https://www.cmegroup.com/CmeWS/mvc/Volume/TradeDates?exchange=CME'
10
- begin
11
- HTTParty.get(uri)
12
- .parsed_response
13
- .map do |x|
14
- a = x['tradeDate'].chars.each_slice(2).map(&:join)
15
- "#{a[0]}#{a[1]}-#{a[2]}-#{a[3]}"
16
- end
17
- .first
18
- rescue StandardError
19
- nil
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
- CSV.read(f, converters: :numeric)
20
- .map(&:to_a)
21
- .tap { |x| x.shift unless x.first.first.is_a?(Numeric) }
22
- .map { |x| (x.first...x.last) }
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.11
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-03-07 00:00:00.000000000 Z
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-4-Clause
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