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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b1f66885f510fbe8be92a8e543f7206220db76940825862115c60be6892d5d4
4
- data.tar.gz: a12e509a14c41cc17e31d95fca73d09ab09c342b029e973baf30377ebe8f1d6a
3
+ metadata.gz: e1ab9094e7e7e6c04a90f1f1a640b902de5a0c688403a5d79c53a7b3f11c9434
4
+ data.tar.gz: 16175de316f036ec9a930b43d700fc241999afaffca728bf2ba436470255c957
5
5
  SHA512:
6
- metadata.gz: dc53284daf85e21387ba1d7b24b3e2518be8dae3d49f6b2893e6aa6442e082516be12f76f23001ad3f04aee3e3ae5601ae7e747fd8451d5d9a437c7ac2b45976
7
- data.tar.gz: 3b496b4e75e73feee1dbc08fd355572471ba78eb1b692c91e81dd3b1f515eab510cab0d22bc712cabab84cfe621a536e91a81e0925ce897e8d48c05770b0b015
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 [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.13
1
+ 0.1.15.5
@@ -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
@@ -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.1'
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 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|
@@ -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
- row[k] = row[k].to_f if %i[open high low close].include? k
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
- data.pop if data.last[:high].zero? and not keep_last
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 '0,0,0,0' | sort -t, -k2 | cut -d, -f1,2,7,8 > #{c_file}`
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
- volume: row[2].to_i,
88
- oi: row[3].to_i
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
- result << {
162
+ avg_bar = {
97
163
  date: k,
98
- volume: v.map { |x| x[:volume] }.reduce(:+),
99
- oi: v.map { |x| x[:oi] }.reduce(:+)
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 == date.year % 2000 }
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 unless (debug and debuglevel >= 2)
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
- end
383
+ end
293
384
  case output_sent.size
294
385
  when 0
295
- puts "WARNING: No output was sent for symbol '#{sym[:symbol]}'.".colorize(:light_yellow)
296
- puts " Assuming late-year-processing.".light_yellow
297
- puts early_year.light_green
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
@@ -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
- uri = 'https://www.cmegroup.com/CmeWS/mvc/Volume/TradeDates?exchange=CME'
10
- begin
11
- HTTParty.get(uri, headers: { "User-Agent" => "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"})
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,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
- 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
- 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
@@ -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
- # receive id / symbol information on an uncertain set of parameters
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.13
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-04-07 00:00:00.000000000 Z
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.1'
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.1'
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-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
@@ -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.2
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