cotcube-bardata 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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