cotcube-bardata 0.1.3 → 0.1.4

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: e879208e6163ff7f56d5b4135e62e67e4054426e98d446fd13742fe34bbb6935
4
- data.tar.gz: 8bafe1a22673d497a55c201870b94b9561d3a01cb523606900e8592c0d0b1c6d
3
+ metadata.gz: ad166746ea75e103e71ce235710267f98fc6a1b13705b73dc978bd0001b282ed
4
+ data.tar.gz: 94b63c1eab7181655f99484ffcb27a29eadef2e2befa03e0301e1f82dc5e2b42
5
5
  SHA512:
6
- metadata.gz: e8bbe5746c9c07435a80b647d222ea6420b5057c1b26e4370f0e1dccedae71028fb54fb25f45ef47719501f0b297c928a6d589ca7a0dfb67a02e40836d2ff1d4
7
- data.tar.gz: 6cb37bdbc50a0c4fd2f9867dd983c6348df5a8f9372e0195e6c0f4dfd90463057e993b5a3c57a39b7f932166ad09f325dbff77311473513575cdd874d728a136
6
+ metadata.gz: 07a2d4ffc5291c1474f14c49ce1d9d42804b2a5bb5ce9fabfef048904a7d8455a106381e76ec6c8bbe05feeda50afb9db9b6f56087c572b704bbce2e7ab313c9
7
+ data.tar.gz: d3e83e577ad407550ab590dcd8b5c2880c05939197b80d67a07de117d2ffba918adc852f1de87e16b7262e22d71c53a1bd789d700b6ae3ef82be645adc532b52
data/.irbrc.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  def verbose_toggle
2
- irb_context.echo ? irb_context.echo = false : irb_context.echo = true
4
+ irb_context.echo = (irb_context.echo ? false : true)
3
5
  end
4
6
 
5
7
  alias vt verbose_toggle
@@ -1,3 +1,25 @@
1
+ ## 0.1.4 (January 02, 2021)
2
+ - two minor fixes (cached.rb, daily.rb)
3
+ - adding first (shy) specs ... to be continued
4
+ - cotcube-bardata.rb: added dependency parallel, added new module files and functions
5
+ - provide.rb: writing provide, the central accessor to actual bardata
6
+ - cached.rb: implementing 'provide_cached', which manages reduced and dimished subsets of 'quarters'
7
+ - helpers.rb: added get_id_set
8
+ - daily.rb: applied cops
9
+ - quarters.rb: applied cops, used new get_id_set, slimmed down content in favor of 'provide'
10
+ - eods.rb: renamed get_id_from to get_id_set
11
+ - added explanation to range_matrix.rb
12
+ - added 'holidays' to trade_dates.csv, depending on according CSV
13
+ - applied cops to init.rb
14
+ - changed name from get_range to trading_hours
15
+ - minor change in gemspec
16
+ - fixed typos in README
17
+ - added bounded versions to gemspec
18
+ - applied cops
19
+ - new file trading_hours.rb providing get_range(). Based on CSV data it provides a list of ranges depicting seconds since Sunday 0:00am, which in turn can be used with the helper Array.new.to_time_interval.
20
+ - new file and method 'range_matrix', investigating high-low ranges of entire daily
21
+ - Too bad, found copied README...fixing something quite embarassing.
22
+
1
23
  ## 0.1.3 (December 23, 2020)
2
24
  - added .provide_most_liquids_by_eod which supports :age, filtering out files that have been updated more recently
3
25
  - added 'type' to symbols to filter for e.g. currencies
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in bitangent.gemspec
4
6
  gemspec
5
-
data/README.md CHANGED
@@ -1,36 +1,19 @@
1
- # Bitangent
1
+ # cotcube-bardata
2
2
 
3
- This gem provides a class (and will maybe later provide a commandline tool) to process
4
- time series data into bitangents.
3
+ This gem is a versatile provider of bardata. It relies on a directory structure most probably saved to `/var/cotcube/bardata/`. The following directories and files contain data others might expect to be delivered by a database:
5
4
 
6
- The underlying algorithm starts with the entire range of data and shears it (starting at
7
- 90 or -90 degrees) along the x axis until a bitangent is found parallel to the x axis
8
- (i.e. __y == 0__ resp. __y == y.last__), resulting in an angle and a group of at least
9
- 2 measurements. Please note here:
10
- - Measurements can be clustered to 1 finding in case there is no significant distance to
11
- the bitangent in regard to the :fuzziness, which is at least 1 'tick'.
12
- - Ocassionally a bitangent becomes an N-tangent, when multiple findings or clusters are
13
- in one line resp. the fuzzied ranged on the sheared graph.
14
- - Shearing is limited by reaching 0 degrees, so everything below the horizont (or above resp.)
15
- is discarded.
16
-
17
- After identifiying the angle of Z degress delivering N findings( actually N - 1, as the
18
- last finding always is the last member of the series), the entire time series then is split
19
- into N subranges, where each subrange is processed again until it reaches minimum size
20
- (which defaults to 3 items).
21
-
22
- Except for the very first range the challenge is to trim away the beginning of each sub
23
- range.
24
-
25
- The result of such a search is a tree, where it might be considerable to walk and change
26
- this tree by adding elements to the time series instead of recalculating it completely.
5
+ 1. `eods`: within *eods/<id or symbol>/<date>.csv*, for each date a list of contracts is located, to be applied with the list of headers `%i[ contract date open high low close volume oi ]`.
6
+ 2. `daily`: within *daily/<id or symbol>/<contract>.csv*, for each contract all eods are provided. The list of headers is expected as `%i[ contract date open high low close volume oi ]`. Please note that it is not obvious whether `close` contains settlement or actual closing price, depending on the exchange and the broker providing the source data.
7
+ 3. `quarters`: within *quarters/<id or symbol>/<contract>.csv*, for each contract a list of quarters (15 minute intervals) is provided, depending on the first occurrence of the contract within the topN volume segment. Note the different headers here: `%i[ contract date_alike day open high low close volume ]`.
8
+ 4. `trading_hours`: within *trading_hours/<symbol or type>_<set>.csv* a list of intervals is provided, with the headers `%i[ interval_start interval_end ]` for each interval described by seconds since Sunday 0:00p.m. (as defaulted by Ruby's *DateTime.new.wday)*.
9
+ 5. `trade_dates.csv`: A growing list of trade\_dates as provided by the CME.
27
10
 
28
11
  ## Installation
29
12
 
30
13
  Add this line to your application's Gemfile:
31
14
 
32
15
  ```ruby
33
- gem 'bitangent'
16
+ gem 'cotcube-bardata'
34
17
  ```
35
18
 
36
19
  And then execute:
@@ -39,11 +22,60 @@ And then execute:
39
22
 
40
23
  Or install it yourself as:
41
24
 
42
- $ gem install bitangent
25
+ $ gem install cotcube-bardata
43
26
 
44
27
  ## Usage
45
28
 
46
- TODO: Write usage instructions here
29
+ ### Configuration
30
+
31
+ The gem expects a configfile 'bardata.yml', located in '/etc/cotcube/' on linux platform and '/usr/local/etc/cotcube/' on FreeBSD. The location of the configfile can be overwritten by passing the according parameter to `init`.
32
+
33
+ ### daily.rb
34
+
35
+ Provides
36
+
37
+ * `provide_daily(symbol: nil, id: nil, contract:, timezone: Time.find_zone('America/Chicago'), config: init)`
38
+ * `continuous(symbol: nil, id: nil, config: init, date: nil)`: Loads all dailies for given *id* and groups by date, hence providing a list of eods.
39
+ * `continuous_ml(symbol: nil, id: nil, base: nil)`: Provides a list of contracts, containing a list of most liquid by volume contracts as `{ date: , ml: }`
40
+ * `continuous_actual_ml(symbol: nil, id: nil)`: Same as above, but providing the succeeding trading day (as the signaled 'ML' is yet one day old before it can be used).
41
+ * `continuous_overview(symbol: nil, id: nil, config: init, selector: :volume, human: false, filter: nil)`: Several purposes, but most noticeable providing the range of first and last occurrence within the top N% by volume within eods.
42
+
43
+ ### eods.rb
44
+
45
+ Provides
46
+
47
+ * `most_liquid_for(symbol: nil, id: nil, date: last_trade_date, config: init, quiet: false)`
48
+ * `provide_most_liquids_by_eod(config: init, date: last_trade_date, filter: :volume_part, age: 1.hour)`
49
+ * `provide_eods(**args)`
50
+ * `provide_quarters(**args)`
51
+
52
+ ### quarters.rb
53
+
54
+ Provides `provide_quarters(**args)`.
55
+
56
+ ### provide.rb
57
+
58
+ Provides `provide(**args)`.
59
+
60
+ ### range\_matrix.rb
61
+
62
+ Provides `range_matrix`, a simple method processing data based on `Bardata.continuous_actual_ml` to return a statistical overview of daily high-low ranges (not True Ranges). It contains sets for
63
+
64
+ * all available data,
65
+ * a data subset containing the recent 12 months
66
+ * data diminished by `:dim` (top and bottom) based on all available data
67
+
68
+ and contains *max*, *avg*, *lower*, *median*, *upper* and *max* (where 'upper' and 'lower' are like the median but at the 25percentile and 75percentile resp.).
69
+
70
+ As a third dimension (sorry!) all of the above is applied to days, weeks as well as months.
71
+
72
+ ### trade\_dates.rb
73
+
74
+ Provides `last_trade_date`, simple fetches the current 5 trade dates from CME and returns the very last.
75
+
76
+ ### trading\_hours.rb
77
+
78
+ Provides `get_range(symbol: nil, set: :full, force_set: false, config: init, debug: false)`, loading a set of intervals. The sets are defaulting to :full when the requested set is not found--unless :force\_set is enabled. Furthermore, if symbol is not found, the type-based version is returned. Eventually, if neither could be returned, the 24x7 interval is returned.
47
79
 
48
80
  ## Development
49
81
 
@@ -53,9 +85,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
53
85
 
54
86
  ## Contributing
55
87
 
56
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bitangent.
88
+ Bug reports and pull requests are welcome on GitHub at https://github.com/donkeybridge/bitangent.
57
89
 
58
90
 
59
91
  ## License
60
92
 
61
93
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
94
+
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.1.4
@@ -9,13 +9,13 @@ Gem::Specification.new do |spec|
9
9
  spec.summary = 'Functions to provide bardata; and some simple time series aggregations'
10
10
  spec.description = 'Functions to provide bardata; and some simple time series aggregations '
11
11
 
12
- spec.homepage = 'https://github.com/donkeybridge/'+ spec.name
12
+ spec.homepage = "https://github.com/donkeybridge/#{spec.name}"
13
13
  spec.license = 'BSD-4-Clause'
14
14
  spec.required_ruby_version = Gem::Requirement.new('~> 2.7')
15
15
 
16
16
  spec.metadata['homepage_uri'] = spec.homepage
17
17
  spec.metadata['source_code_uri'] = spec.homepage
18
- spec.metadata['changelog_uri'] = spec.homepage + '/CHANGELOG.md'
18
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/CHANGELOG.md"
19
19
 
20
20
  # Specify which files should be added to the gem when it is released.
21
21
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -26,16 +26,15 @@ Gem::Specification.new do |spec|
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ['lib']
28
28
 
29
- spec.add_dependency 'cotcube-indicators'
30
- spec.add_dependency 'cotcube-helpers'
31
- spec.add_dependency 'yaml'
32
- spec.add_dependency 'activesupport'
33
- spec.add_dependency 'colorize'
34
- spec.add_dependency 'httparty'
35
- spec.add_dependency 'rubyzip'
29
+ spec.add_dependency 'activesupport', '~> 6'
30
+ spec.add_dependency 'colorize', '~> 0.8'
31
+ spec.add_dependency 'cotcube-helpers', '~> 0.1'
32
+ spec.add_dependency 'cotcube-indicators', '~> 0.1'
33
+ spec.add_dependency 'httparty', '~> 0.18'
34
+ spec.add_dependency 'parallel', '~> 1'
35
+ spec.add_dependency 'yaml', '~> 0.1'
36
36
 
37
-
38
- spec.add_development_dependency 'rake'
37
+ spec.add_development_dependency 'rake', '>= 12'
39
38
  spec.add_development_dependency 'rspec', '~>3.6'
40
39
  spec.add_development_dependency 'yard', '~>0.9'
41
40
  end
@@ -1,3 +1,5 @@
1
+ # rubocop:disable Naming/FileName
2
+ # rubocop:enable Naming/FileName
1
3
  # frozen_string_literal: true
2
4
 
3
5
  require 'active_support'
@@ -9,35 +11,59 @@ require 'date' unless defined?(DateTime)
9
11
  require 'csv' unless defined?(CSV)
10
12
  require 'yaml' unless defined?(YAML)
11
13
  require 'cotcube-helpers'
12
-
13
-
14
+ require 'parallel'
14
15
 
15
16
  require_relative 'cotcube-bardata/constants'
17
+ require_relative 'cotcube-bardata/helpers'
16
18
  require_relative 'cotcube-bardata/init'
17
19
  require_relative 'cotcube-bardata/trade_dates'
18
20
  require_relative 'cotcube-bardata/daily'
19
21
  require_relative 'cotcube-bardata/quarters'
20
22
  require_relative 'cotcube-bardata/eods'
23
+ require_relative 'cotcube-bardata/cached'
21
24
  require_relative 'cotcube-bardata/provide'
25
+ require_relative 'cotcube-bardata/range_matrix'
26
+ require_relative 'cotcube-bardata/trading_hours'
22
27
 
23
28
  module Cotcube
24
29
  module Bardata
25
-
26
30
  module_function :config_path, # provides the path of configuration directory
27
- :config_prefix, # provides the prefix of the configuration directory according to OS-specific FSH
28
- :init, # checks whether environment is prepared and returns the config hash
29
- :last_trade_date, # Provides the most recent trade date (today or maybe last friday before weekend)
30
- :provide, #
31
- :most_liquid_for, # the most_liquid contract for a given symbol or id, based on date or last_trade_date
32
- :provide_eods, # provides the list of eods, either with data or just the contracts, filtered for liquidity threshold
33
- :provide_most_liquids_by_eod,
34
- :provide_daily, # provides the list of dailies for a given symbol, which include OI. Note that the close is settlement price.
35
- :continuous, # for a given date or range, provide all contracts that exceed a given threshold of volume share
36
- :continuous_overview, # based on continuous, create list of when which contract was most liquid
37
- :provide_quarters, # provide the list of quarters, possibly as hours or days.
38
- :symbols # reads and provides the symbols file
39
-
40
- # please not that module_functions of source provided in private files must be published there
31
+ # provides the prefix of the configuration directory according to OS-specific FSH
32
+ :config_prefix,
33
+ # checks whether environment is prepared and returns the config hash
34
+ :init,
35
+ # Provides the most recent trade date (today or maybe last friday before weekend)
36
+ :last_trade_date,
37
+ :provide,
38
+ # the most_liquid contract for a given symbol or id, based on date or last_trade_date
39
+ :most_liquid_for,
40
+ # provides the list of eods, either with data or just the contracts,
41
+ # filtered for liquidity threshold
42
+ :provide_eods,
43
+ :provide_most_liquids_by_eod,
44
+ # provides the list of dailies for a given symbol, which include OI.
45
+ # Note that the close is most probably settlement price.
46
+ :provide_daily,
47
+ # for a given date or range, provide all contracts that exceed a given threshold of volume share
48
+ :continuous,
49
+ # the list of most liquid contracts (by each days volume share)
50
+ :continuous_ml,
51
+ # same list but riped one day each
52
+ :continuous_actual_ml,
53
+ # based on continuous, create list of when which contract was most liquid
54
+ :continuous_overview,
55
+ # provide the list of quarters, possibly as hours or days.
56
+ :provide_quarters,
57
+ # some statistics to estimate daily volatility of specific contract
58
+ :range_matrix,
59
+ # create an array of ranges based on specified source data
60
+ :trading_hours,
61
+ # receive id / symbol information on an uncertain set of parameters
62
+ :get_id_set,
63
+ :compare,
64
+ :holidays,
65
+ :symbols # reads and provides the symbols file
66
+
67
+ # please note that module_functions of source provided in private files must be published there
41
68
  end
42
69
  end
43
-
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cotcube
4
+ # missing top level documentation
5
+ module Bardata
6
+
7
+ # send pre-created days based on quarters
8
+ def provide_cached(contract:,
9
+ symbol: nil, id: nil,
10
+ config: init,
11
+ timezone: Time.find_zone('America/Chicago'),
12
+ set: :full, # most probably either :full or :rth
13
+ force_update: false, # force reloading via provide_quarters
14
+ force_recent: false) #
15
+
16
+ headers = %i[contract datetime open high low close volume]
17
+ sym = get_id_set(symbol: symbol, id: id, contract: contract)
18
+ contract = contract[-3..-1]
19
+ dir = "#{config[:data_path]}/cached/#{sym[:id]}_#{set.to_s.downcase}"
20
+ symlink = "#{config[:data_path]}/cached/#{sym[:symbol]}_#{set.to_s.downcase}"
21
+ `mkdir -p #{dir}` unless Dir.exist? dir
22
+ `ln -s #{dir} #{symlink}` unless File.exist? symlink
23
+ file = "#{dir}/#{contract}.csv"
24
+ quarters_file = "#{config[:data_path]}/quarters/#{sym[:id]}/#{contract[-3..-1]}.csv"
25
+ if File.exist?(file) and not force_update
26
+ base = CSV.read(file, headers: headers).map{|x|
27
+ x = x.to_h
28
+ x[:datetime] = timezone.parse(x[:datetime])
29
+ %i[open high low close].each{|z| x[z] = x[z].to_f.round(9)}
30
+ x[:volume] = x[:volume].to_i
31
+ x[:type] = "#{set.to_s.downcase}_day".to_sym
32
+ x
33
+ }
34
+ if base.last[:high].zero?
35
+ # contract exists but is closed (has the CLOSED marker)
36
+ base.pop
37
+ return base
38
+ elsif File.mtime(file) < File.mtime(quarters_file)
39
+ return base
40
+ else
41
+ puts "File #{file} exists, but is neither closed nor current. Running update.".colorize(:light_green)
42
+ end
43
+ end
44
+ begin
45
+ data = provide_quarters(contract: contract, id: sym[:id], keep_marker: true)
46
+ rescue
47
+ puts "Cannot provide quarters for requested contract #{sym[:symbol]}:#{contract}, returning '[ ]'".colorize(:light_red)
48
+ return []
49
+ end
50
+
51
+ # removing marker if existing
52
+ contract_is_marked = data.last[:high].zero?
53
+ data.pop if contract_is_marked
54
+ unless set == :full or data.size < 3
55
+ requested_set = trading_hours(symbol: sym[:symbol], set: set)
56
+ data = data.select_within(ranges: requested_set, attr: :datetime) {|x| x.to_datetime.to_sssm }
57
+ end
58
+
59
+ base = Cotcube::Helpers.reduce(bars: data, to: :days)
60
+
61
+ # remove last day of result if not marked
62
+ base.pop unless contract_is_marked or force_recent
63
+
64
+ base.map do |x|
65
+ x[:datetime] = x[:datetime].to_date
66
+ x[:type]= "#{set}_day".to_sym
67
+ x.delete(:day)
68
+ end
69
+ CSV.open(file, 'w') do |csv|
70
+ base.each{|b| csv << b.values_at(*headers) }
71
+ if contract_is_marked
72
+ marker = [ "#{sym[:symbol]}#{contract}",base.last[:datetime]+1.day,0,0,0,0,0 ]
73
+ csv << marker
74
+ end
75
+ end
76
+ base
77
+ end
78
+
79
+ end
80
+ end
@@ -2,16 +2,16 @@
2
2
 
3
3
  module Cotcube
4
4
  module Bardata
5
-
6
5
  SYMBOL_EXAMPLES = [
7
- { id: "13874U", symbol: "ET", ticksize: 0.25, power: 1.25, months: "HMUZ", bcf: 1.0, reports: "LF", name: "S&P 500 MICRO" },
8
- { id: "209747", symbol: "NM", ticksize: 0.25, power: 0.5, monhts: "HMUZ", bcf: 1.0, reports: "LF", name: "NASDAQ 100 MICRO" }
6
+ { id: '13874U', symbol: 'ET', ticksize: 0.25, power: 1.25, months: 'HMUZ', bcf: 1.0, reports: 'LF',
7
+ name: 'S&P 500 MICRO' },
8
+ { id: '209747', symbol: 'NM', ticksize: 0.25, power: 0.5, months: 'HMUZ', bcf: 1.0, reports: 'LF',
9
+ name: 'NASDAQ 100 MICRO' }
9
10
  ].freeze
10
11
 
11
- MONTH_COLOURS = { 'F' => :cyan, 'G' => :green, 'H' => :light_green,
12
- 'J' => :blue, 'K' => :yellow, 'M' => :light_yellow,
13
- 'N' => :cyan, 'Q' => :magenta, 'U' => :light_magenta,
14
- 'V' => :blue, 'X' => :red, 'Z' => :light_red }.freeze
15
-
12
+ MONTH_COLOURS = { 'F' => :cyan, 'G' => :green, 'H' => :light_green,
13
+ 'J' => :blue, 'K' => :yellow, 'M' => :light_yellow,
14
+ 'N' => :cyan, 'Q' => :magenta, 'U' => :light_magenta,
15
+ 'V' => :blue, 'X' => :red, 'Z' => :light_red }.freeze
16
16
  end
17
17
  end
@@ -1,92 +1,127 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cotcube
4
+ # Missing top level documentation comment
4
5
  module Bardata
5
-
6
6
  # just reads bardata/daily/<id>/<contract>.csv
7
- def provide_daily(symbol: nil, id: nil, contract:, timezone: Time.find_zone('America/Chicago'), config: init)
7
+ def provide_daily(contract:, symbol: nil, id: nil, timezone: Time.find_zone('America/Chicago'), config: init)
8
8
  contract = contract.to_s.upcase
9
- raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'" unless contract.is_a? String and [3,5].include? contract.size
10
- if contract.to_s.size == 5
11
- symbol = contract[0..1]
12
- contract = contract[2..4]
13
- end
14
- unless symbol.nil?
15
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
16
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
17
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
18
- id = symbol_id
9
+ unless contract.is_a?(String) && [3, 5].include?(contract.size)
10
+ raise ArgumentError, "Contract '#{contract}' is bogus, should be like 'M21' or 'ESM21'"
19
11
  end
20
- raise ArgumentError, "Could not guess :id or :symbol from 'contract: #{contract}', please clarify." if id.nil?
12
+
13
+ sym = get_id_set(symbol: symbol, id: id, contract: contract)
14
+ contract = contract[2..4] if contract.to_s.size == 5
15
+ id = sym[:id]
21
16
  id_path = "#{config[:data_path]}/daily/#{id}"
22
17
  data_file = "#{id_path}/#{contract}.csv"
23
- raise RuntimeError, "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
24
- raise RuntimeError, "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
25
- data = CSV.read(data_file, headers: %i[contract date open high low close volume oi] ).map do |row|
18
+ raise "No data found for requested :id (#{id_path} does not exist)" unless Dir.exist?(id_path)
19
+
20
+ raise "No data found for requested contract #{symbol}:#{contract} in #{id_path}." unless File.exist?(data_file)
21
+
22
+ data = CSV.read(data_file, headers: %i[contract date open high low close volume oi]).map do |row|
26
23
  row = row.to_h
27
- row.each do |k, _|
28
- row[k] = row[k].to_f if [:open, :high, :low, :close].include? k
29
- row[k] = row[k].to_i if [:volume, :oi].include? k
24
+ row.each do |k, _|
25
+ row[k] = row[k].to_f if %i[open high low close].include? k
26
+ row[k] = row[k].to_i if %i[volume oi].include? k
30
27
  end
31
- row[:datetime] = timezone.parse(row[:date])
28
+ row[:datetime] = timezone.parse(row[:date]).to_date
29
+ row[:type] = :daily
32
30
  row
33
31
  end
32
+ data.pop if data.last[:high].zero?
34
33
  data
35
34
  end
36
-
37
- # reads all files in bardata/daily/<id> and aggregates by date (what is a pre-stage of a continuous based on daily bars)
35
+
36
+ # reads all files in bardata/daily/<id> and aggregates by date
37
+ # (what is a pre-stage of a continuous based on daily bars)
38
38
  def continuous(symbol: nil, id: nil, config: init, date: nil)
39
- unless symbol.nil?
40
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
41
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
42
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
43
- id = symbol_id
44
- end
45
- raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
46
- id_path = "#{config[:data_path]}/daily/#{id}"
47
- available_contracts = Dir[id_path + '/*.csv'].map{|x| x.split('/').last.split('.').first}.sort_by{|x| x[-7]}.sort_by{|x| x[-6..-5]}
48
- data = []
49
- available_contracts.each do |c|
50
- provide_daily(id: id, config: config, contract: c).each do |x|
39
+ sym = get_id_set(symbol: symbol, id: id)
40
+ id = sym[:id]
41
+ id_path = "#{config[:data_path]}/daily/#{id}"
42
+ available_contracts = Dir["#{id_path}/*.csv"].map { |x| x.split('/').last.split('.').first }
43
+ available_contracts.sort_by! { |x| x[-7] }.sort_by! { |x| x[-6..-5] }
44
+ data = []
45
+ available_contracts.each do |c|
46
+ provide_daily(id: id, config: config, contract: c).each do |x|
51
47
  data << x
52
48
  end
53
49
  end
54
- result = []
55
- data.sort_by{|x| x[:date]}.group_by{|x| x[:date]}.map{|k,v|
56
- v.map{|x| x.delete(:date)}
50
+ result = []
51
+ data.sort_by { |x| x[:date] }.group_by { |x| x[:date] }.map do |k, v|
52
+ v.map { |x| x.delete(:date) }
57
53
  result << {
58
54
  date: k,
59
- volume: v.map{|x| x[:volume]}.reduce(:+),
60
- oi: v.map{|x| x[:oi ]}.reduce(:+)
55
+ volume: v.map { |x| x[:volume] }.reduce(:+),
56
+ oi: v.map { |x| x[:oi] }.reduce(:+)
61
57
  }
62
58
  result.last[:contracts] = v
63
- }
64
- date.nil? ? result : result.select{|x| x[:date] == date}.first
59
+ end
60
+ date.nil? ? result : result.select { |x| x[:date] == date }.first
65
61
  end
66
62
 
67
- # based on .continuous, this methods sorts the prepared dailies continuous for each date on either :volume (default) or :oi
63
+ def continuous_ml(symbol: nil, id: nil, base: nil)
64
+ (base.nil? ? Cotcube::Bardata.continuous(symbol: symbol, id: id) : base).map do |x|
65
+ x[:ml] = x[:contracts].max_by { |z| z[:volume] }[:contract]
66
+ { date: x[:date], ml: x[:ml] }
67
+ end
68
+ end
69
+
70
+ # the method above delivers the most_liquid as it is found at the end of the day. D
71
+ # during trading, the work is done with data
72
+ # that is already one day old. This is is fixed here:
73
+ def continuous_actual_ml(symbol: nil, id: nil)
74
+ continuous = Cotcube::Bardata.continuous symbol: symbol, id: id
75
+ continuous_ml = Cotcube::Bardata.continuous_ml base: continuous
76
+ continuous_hash = continuous.to_h { |x| [x[:date], x[:contracts]] }
77
+ actual_ml = continuous_ml.pairwise { |a, b| { date: b[:date], ml: a[:ml] } }
78
+ actual_ml.map do |x|
79
+ r = continuous_hash[x[:date]].select { |z| x[:ml] == z[:contract] }.first
80
+ r = continuous_hash[x[:date]].min_by { |z| -z[:volume] } if r.nil?
81
+ r
82
+ end
83
+ end
84
+
85
+ # based on .continuous, this methods sorts the prepared dailies continuous for each date
86
+ # on either :volume (default) or :oi
68
87
  # with this job done, it can provide the period for which a past contract was the most liquid
69
88
  #
70
- def continuous_overview(symbol: nil, id: nil, config: init, selector: :volume, human: false, filter: nil)
71
- raise ArgumentError, "Selector must be either :volume or :oi" unless selector.is_a? Symbol and [:volume, :oi].include? selector
72
-
73
- unless symbol.nil?
74
- symbol_id = symbols.select{|s| s[:symbol] == symbol.to_s.upcase}.first[:id]
75
- raise ArgumentError, "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}" if symbol_id.nil?
76
- raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if not id.nil? and symbol_id != id
77
- id = symbol_id
78
- end
79
- raise ArgumentError, "Could not guess :id or :symbol, please clarify." if id.nil?
80
- data = continuous(id: id, config: config).map{|x|
89
+ def continuous_overview(symbol: nil, id: nil, # rubocop:disable Metrics/ParameterLists
90
+ config: init,
91
+ selector: :volume,
92
+ human: false,
93
+ filter: nil)
94
+ raise ArgumentError, 'Selector must be either :volume or :oi' unless selector.is_a?(Symbol) &&
95
+ %i[volume oi].include?(selector)
96
+
97
+ sym = get_id_set(symbol: symbol, id: id)
98
+ id = sym[:id]
99
+ data = continuous(id: id, config: config).map do |x|
81
100
  {
82
- date: x[:date],
83
- volume: x[:contracts].sort_by{|x| - x[:volume]}[0..4].compact.select{|x| not x[:volume].zero?},
84
- oi: x[:contracts].sort_by{|x| - x[:oi]}[0..4].compact.select{|x| not x[:oi].zero?}
101
+ date: x[:date],
102
+ volume: x[:contracts].sort_by { |z| - z[:volume] }[0..4].compact.reject { |z| z[:volume].zero? },
103
+ oi: x[:contracts].sort_by { |z| - z[:oi] }[0..4].compact.reject { |z| z[:oi].zero? }
85
104
  }
86
- }.select{|x| not x[selector].empty? }
87
- result = data.group_by{|x| x[selector].first[:contract]}
105
+ end
106
+ data.reject! { |x| x[selector].empty? }
107
+ result = data.group_by { |x| x[selector].first[:contract] }
88
108
  if human
89
- result.each {|k,v| puts "#{k}\t#{v.first[:date]}\t#{v.last[:date]}" if filter.nil? or v.first[selector].first[:contract][2..4]=~ /#{filter}/ }
109
+ result.each do |k, v|
110
+ next unless filter.nil? || v.first[selector].first[:contract][2..4] =~ (/#{filter}/)
111
+
112
+ # rubocop:disable Layout/ClosingParenthesisIndentation
113
+ puts "#{k
114
+ }\t#{v.first[:date]
115
+ }\t#{v.last[:date]
116
+ }\t#{format('%4d', (Date.parse(v.last[:date]) - Date.parse(v.first[:date])))
117
+ }\t#{result[k].map do |x|
118
+ x[:volume].select do
119
+ x[:contract] == k
120
+ end
121
+ end.size
122
+ }"
123
+ # rubocop:enable Layout/ClosingParenthesisIndentation
124
+ end
90
125
  end
91
126
  result
92
127
  end