cotcube-level 0.2.0 → 0.3.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: c1c9fc6eb075ccc486c29c13d9989e52b5e659330cba2b900bdd4f420d085bb8
4
- data.tar.gz: 6303babcc58fd8def53c747174c0feaa669c848787c0c32eaa811e0f76aad61f
3
+ metadata.gz: '04184473172dd9ffd09c49a16bf6c2a724eb5cbef3f0f3d5423c44dea55f530f'
4
+ data.tar.gz: 75d1363041e3767a2f79c277a0d945fd830c1521df481194d8162c36b36044f2
5
5
  SHA512:
6
- metadata.gz: a5bc9e406caecd100ad2aebaf5934f9c2b6f0b3c43dc51aa76b63575bb52bd77fec7b06f5b183801149f6b35fb367462261e93bb2c174ef8b10b29bb61ee2370
7
- data.tar.gz: b1de063e02b133b76aea5ffe01b357fad9b9ed99461225cf8780b7cc592ea00bd043afef6d957a3a2f78db02e75291bfac775aa2c56c3cd36886378fbd7f7767
6
+ metadata.gz: 70875d4dd83cb114b62456866b7c8ddaaa9753890442b06d71d770bb6408994cadcf3a5cd12704d658e049ed2aba0cd14bdbf79ce9d25e462ff9453c785a5142
7
+ data.tar.gz: 34aa4547dab7a3b13c896db860bcb23d36c432a0c3480cadf2e8a542351316d92cbe10a321ff8b20da236556312da27e8bb53ac6f65a1d885b48ccbe270c41ef
data/CHANGELOG.md CHANGED
@@ -1,3 +1,52 @@
1
+ ## 0.3.4 (October 06, 2021)
2
+ - intraday_stencil: major rework resp. rebuild ... now beta-ready
3
+ - helpers: preparing puts_swap and get_jsonl_name to work intraday
4
+ - puts_swap: changed output scheme to provide exceedance as NOTE
5
+
6
+ ## 0.3.3 (October 05, 2021)
7
+ - helpers::load_swaps: added :exceed to allow 1(sic) swap to be exceeded while loading
8
+ - tritangulate: added :manual for feature of manual swap creation with base of 2 members
9
+ - helpers::load_swap added :digest to filter for swaps starting with pattern
10
+ - helpers: minor readability improvements
11
+ - eod_stencil: minor readability improvements
12
+ - helpers: few optimizations
13
+
14
+ ## 0.3.2 (August 29, 2021)
15
+ - tritangulate: fixing 'finalize', as Integer zero won't comparte to Float zero
16
+ - cotcube-level.rb: added :check_exceedance
17
+ - helpers:member_to_human: added :daily param to distinguish stencils
18
+ - tritangulate: added 'min_ratio' as param with lambda
19
+ - eod_stencil: added #use to calculate current swap line value / dist;
20
+ - tritangulate: added interval to be saved with the swap information.
21
+ - helpers: moved puts_swaps to puts_swap, and added a :short switch for 1-liners per swap
22
+
23
+ ## 0.3.1.1 (August 25, 2021)
24
+ - trying to fix versioning mistake
25
+ - Bump version to 0.3.2.1.
26
+ - minor fixes correcting mistakes sneaked in during documentation rework
27
+ - minor fix
28
+
29
+ ## 0.3.2.1 (August 25, 2021)
30
+ - minor fixes correcting mistakes sneaked in during documentation rework
31
+ - minor fix
32
+
33
+ ## 0.3.1 (August 24, 2021)
34
+ - renaming triangulation to tritangulation
35
+ - minor fixes in README and gemspec
36
+
37
+ ## 0.3.0 (August 24, 2021)
38
+ - removed tests, moving to cotcube-jobs
39
+ - all: added documentation
40
+ - triangulate: added continuous support and documentation
41
+ - eod_stencil: added support for continuous futures
42
+ - adapted tests to recent changes
43
+ - cotcube-level: included intraday_stencil
44
+ - intraday_stencil: added to repo
45
+ - eod_stencil: fixing minor typo
46
+ - triangulate: fixed indention
47
+ - stencil: slight changes, incl rename to EODStencil
48
+ - detect_slope: now returns all members, regardless of amount (i.e. check whether it is a valid swap will be done later)
49
+
1
50
  ## 0.2.0 (August 17, 2021)
2
51
  - cotcube-level: added new module_functions, added restrictive constants for intervals and swaptypes
3
52
  - adding new features to so-called 'test suite'
data/README.md CHANGED
@@ -5,20 +5,20 @@ of the development process. All in the beginning was the wish to create an algor
5
5
  lines. So for like 2 year I was whirling my head on how to put an algorithmic ruler on a chart and rotate it until
6
6
  one or a set of trend lines are concise outpout.
7
7
 
8
- As most hard do accomplish thing turn out to be much easier when you put them up side down, same happend to me in this
8
+ As most hard to accomplish things turn out to be much easier when you put them up side down, same happened to me in this
9
9
  matter. I found rotating the ruler is too hard for, but instead transforming (shearing) the chart itself while keeping
10
10
  the ruler at its _level_ is much more eligible.
11
11
 
12
12
  The idea and the development of the algorithm had taken place within another Cotcube project named 'SwapSeeker'. At
13
- some point the SwapSeeker has become too complex so I decided to decouple the Level and the Stencil as independent
13
+ some point the SwapSeeker has become too complex so I decided to decouple the Level and the Stencils as separate
14
14
  functional unit, that can be used on arbitrary time series.
15
15
 
16
16
  ### The shear mapping
17
17
 
18
18
  There is really no magic in it. The timeseries (or basically an interval of a timeseries) needs to be prepared to locate
19
19
  in Cartesian Quadrant I with fitting x==0 to y==0, and then a binary search on shearing angles determines the resulting
20
- muiltitangent of which as the origin ('now') one point already is given. As limitation only shearing between 0 and 90
21
- deg is supported.
20
+ muiltitangent of which the origin ('now') as one point already is given. One limitation applies: Only shearing between
21
+ 0 and 90 degrees is supported.
22
22
 
23
23
  The result contains
24
24
 
@@ -28,17 +28,25 @@ to happen if _deg -> 0_.
28
28
  - the origin and two or more points residing on the same level--what is the desired result showing mathematical accuracy where
29
29
  human eye cannot detect it in time.
30
30
 
31
+ ### Tritangulation
32
+
33
+ The 'tri' is about that to find an artificially supported slope (i.e. a swap) at least 3 points
34
+ have to reside on it. It is leaned to triangulate, but instead of working with angles we are working with tangents here.
35
+
36
+ Shear mapping and slope detection might find a slope meeting the requirements. But in tritangulate, near misses are
37
+ considered and added as members.
38
+
31
39
  ### The stencil
32
40
 
33
41
  The shearing transformation is based on _x_ and _y_ values, where y obviously are the values of the series while _x_
34
42
  refers to the time. It turned out a much bigger challenge to create an expedient mapping from DateTime to Integer,
35
43
  where, as you foresee, 'now' should result in _x.zero?_.
36
44
 
37
- ## Usage
45
+ ## Usage (pure template text)
38
46
 
39
47
  TODO: Write usage instructions here
40
48
 
41
- ## Development
49
+ ## Development (pure template text)
42
50
 
43
51
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
44
52
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.4
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.email = ['donkeybridge@jtown.eu']
8
8
 
9
9
  spec.summary = 'A gem to shear a time series '
10
- spec.description = 'A gem to shear a time series, basically serving as a yet unssen class of indicators.'
10
+ spec.description = 'A gem to shear a time series, basically serving as a yet unseen class of indicators.'
11
11
 
12
12
  spec.homepage = 'https://github.com/donkeybridge/' + spec.name
13
13
  spec.license = 'BSD-3-Clause'
@@ -1,117 +1,98 @@
1
1
  module Cotcube
2
2
  module Level
3
- #
4
- # TODO: add support for slopes not only exactly matching but also allow 'dip' of n x ticksize
5
- def detect_slope(base:, max: 90, debug: false, format: '% 5.2f', calculus: false, ticksize: nil, max_dev: 200)
6
- raise ArgumentError, "'0 < max < 90, but got '#{max}'" unless max.is_a? Numeric and 0 < max and max <= 90
7
- #
8
- # aiming for a shearing angle, all but those in a line below the abscissa
9
- #
10
- # doing a binary search starting at part = 45 degrees
11
- # on each iteration,
12
- # part is halved and added or substracted based on current success
13
- # if more than the mandatory result is found, all negative results are removed and degrees are increased by part
14
- #
15
- raise ArgumentError, 'detect_slope needs param Array :base' unless base.is_a? Array
16
-
17
- # from given base, choose non-negative stencil containing values
18
- old_base = base.dup.select{|b| b[:x] >= 0 and not b[:y].nil? }
3
+ def detect_slope(base:, max: 90, debug: false, format: '% 5.2f', calculus: false, ticksize: nil, max_dev: 200)
4
+ raise ArgumentError, "'0 < max < 90, but got '#{max}'" unless max.is_a? Numeric and 0 < max and max <= 90
5
+ #
6
+ # this method processes a 'well prepared' stencil in a way, that :y values are sheared around stencil.zero,
7
+ # resulting to a temporary :yy value for each point. this process is iterated until no more :yy
8
+ # values are above the abscissa ( yy > 0 ) but at least one other values is on (yy == 0)
9
+ #
10
+ # the entire process initially aimed to find slopes that contain 3 or more members. the current version
11
+ # is confident with 2 members--or even one member, which results in an even slope.
12
+ #
13
+ # it works by running a binary search, whereon each iteration,
14
+ # - :part is halved and added or substracted based on current success
15
+ # - if more than the mandatory result is found, all negative results are removed and degrees are increased by part
16
+ # - if no results are found, the process is repeated with the same current base after degrees are decreased by part
17
+ #
18
+ raise ArgumentError, 'detect_slope needs param Array :base' unless base.is_a? Array
19
19
 
20
- # some debug output
21
- old_base.each {|x| p x} if old_base.size < 50 and debug
20
+ # from given base, choose non-negative stencil containing values
21
+ old_base = base.dup.select{|b| b[:x] >= 0 and not b[:y].nil? }
22
22
 
23
- # set initial shearing angle if not given as param
24
- deg ||= -max / 2.0
23
+ # set initial shearing angle if not given as param. This is a prepared functionality, that is not yet used.
24
+ # when implemented to use in tritangulate, it would speed up the binary search process, but initial part
25
+ # has to be set in a different way
26
+ deg ||= -max / 2.0
25
27
 
26
- # create first sheering. please note how selection working with d[:yy]
27
- new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 } #-ticksize }
28
+ # create first shearing. please note how selection works with d[:yy]
29
+ new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 }
28
30
 
29
- # debug output
30
- puts "Iterating slope:\t#{format '% 7.5f',deg
31
+ # debug output
32
+ puts "Iterating slope:\t#{format '% 7.5f',deg
31
33
  }\t\t#{new_base.size
32
34
  } || #{new_base.values_at(*[0]).map{|f| "'#{f[:x]
33
35
  } | #{format format,f[:y]
34
36
  } | #{format format,f[:yy]}'"}.join(" || ") }" if debug
35
- # set initial part to deg
36
- part = deg.abs
37
- #
38
- # the loop, that runs until either
39
- # - only two points are left on the slope
40
- # - the slope has even angle
41
- # - several points are on the slope in quite a good approximation ('round(7)')
42
- #
43
- until deg.round(PRECISION).zero? || part.round(PRECISION).zero? ||
44
- ((new_base.size >= 2) && (new_base.map { |f| f[:yy].round(PRECISION).zero? }.uniq.size == 1))
45
-
46
- part /= 2.0
47
- if new_base.size == 1
48
- # the graph was sheared too far, reuse old_base
49
- deg = deg + part
50
- else
51
- # the graph was sheared too short, continue with new base
52
- deg = deg - part
53
- old_base = new_base.dup unless deg.round(PRECISION).zero?
54
- end
37
+ # set initial part to deg
38
+ part = deg.abs
39
+ #
40
+ # the loop, that runs until either
41
+ # - only two points are left on the slope
42
+ # - the slope has even angle
43
+ # - several points are found on the slope in quite a good approximation ('round(PRECISION)')
44
+ #
45
+ until deg.round(PRECISION).zero? || part.round(PRECISION).zero? ||
46
+ ((new_base.size >= 2) && (new_base.map { |f| f[:yy].round(PRECISION / 2).zero? }.uniq.size == 1))
55
47
 
56
- # the actual sheering operation
57
- # note that this basically maps old_base with yy = y + (dx||x * tan(deg) )
58
- #
59
- new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 } #-ticksize }
60
- new_base.last[:dx] = 0.0
61
- if debug
62
- print " #{format '% 8.5f',deg}"
63
- puts "Iterating slope:\t#{format '% 8.5f',deg
64
- }\t#{new_base.size
65
- } || #{new_base.values_at(*[0]).map{|f| "'#{f[:x]
66
- } | #{format '%4.5f', part
67
- } | #{format format,f[:y]
68
- } | #{format format,f[:yy]}'"}.join(" || ") }"
69
- end
48
+ part /= 2.0
49
+ if new_base.size == 1
50
+ # the graph was sheared too far, reuse old_base
51
+ deg += part
52
+ else
53
+ # the graph was sheared too short, continue with new base
54
+ deg -= part
55
+ old_base = new_base.dup unless deg.round(PRECISION).zero?
70
56
  end
71
- puts ' done.' if debug
72
57
 
73
- ### Sheering ends here
58
+ # the actual shearing operation
59
+ # this basically maps old_base with yy = y + (dx||x * tan(deg) )
60
+ #
61
+ new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 }
62
+ new_base.last[:dx] = 0.0
74
63
 
75
- # define the approximited result as (also) 0.0
76
- new_base.each{|x| x[:yy] = 0.0}
77
- if debug
78
- puts "RESULT: #{deg} #{deg2rad(deg)}"
79
- new_base.each {|f| puts "\t#{f.inspect}" }
80
- end
81
- # there is speacial treatment for even slopes
82
- if deg.round(PRECISION).zero?
83
- #puts "found even slope"
84
- # this is intentionally voided as evenness is calculated somewhere else
85
- # even_base = base.dup.select{|b| b[:x] >= 0 and not b[:y].nil? }[-2..-1].map{|x| x.dup}
86
- # last_barrier is the last bar, that exceeds
87
- #binding.irb
88
- #last_barrier = even_base.select{|bar| (bar[:y] - even_base.last[:y]).abs > evenness * ticksize}.last
89
- #even_base.select!{|bar| (bar[:y] - even_base.last[:y]).abs <= evenness * ticksize}
90
- # could be, that no last barrier exists, when there is a top or bottom plateau
91
- #even_base.select!{|bar| bar[:x] < last_barrier[:x]} unless last_barrier.nil?
92
- # TODO
93
- return { deg: 0, slope: 0, members: [] } #, members: even_base.map { |x| xx = x.dup; %i[y yy].map { |z| xx[z]=nil }; xx } })
64
+ # debug output is reduced by appr. 70%
65
+ if debug and Random.rand < 0.3
66
+ print " #{format '% 18.15f',deg}\t"
67
+ puts "Iterating slope:\t#{format '% 18.10f',deg
68
+ }\t#{new_base.size
69
+ } || #{new_base.values_at(*[0]).map{|f| "'#{f[:x]
70
+ } | #{format '%4.5f', part
71
+ } | #{format format,f[:y]
72
+ } | #{format format,f[:yy]}'"}.join(" || ") }"
94
73
  end
74
+ end
75
+ ### Sheering ends here
95
76
 
77
+ # define the approximited result as (also) 0.0
78
+ new_base.each{|x| x[:yy] = 0.0}
96
79
 
97
- #####################################################################################
98
- # Calculate the slope bsaed on the angle that resulted above
99
- # y = m x + n -->
100
- # m = delta-y / delta-x
101
- # n = y0 - m * x0
102
- #
103
- slope = (new_base.first[:y] - new_base.last[:y]) / (
104
- (new_base.first[:dx].nil? ? new_base.first[:x] : new_base.first[:dx]).to_f -
105
- (new_base. last[:dx].nil? ? new_base. last[:x] : new_base. last[:dx]).to_f
106
- )
107
- # the result
108
- {
109
- deg: deg,
110
- slope: slope,
111
- members: new_base.map { |x| x.dup }
112
- }
113
- end
80
+ #####################################################################################
81
+ # Calculate the slope based on the angle that resulted above
82
+ # y = m x + n -->
83
+ # m = delta-y / delta-x
84
+ # n = y0 - m * x0
85
+ #
86
+ slope = deg.zero? ? 0 : (new_base.first[:y] - new_base.last[:y]) / (
87
+ (new_base.first[:dx].nil? ? new_base.first[:x] : new_base.first[:dx]).to_f -
88
+ (new_base. last[:dx].nil? ? new_base. last[:x] : new_base. last[:dx]).to_f
89
+ )
90
+ # the result
91
+ {
92
+ deg: deg,
93
+ slope: slope,
94
+ members: new_base.map { |x| x.dup }
95
+ }
96
+ end
114
97
  end
115
98
  end
116
-
117
-
@@ -3,7 +3,7 @@
3
3
  module Cotcube
4
4
  module Level
5
5
 
6
- class Stencil
6
+ class EOD_Stencil
7
7
  attr_accessor :base
8
8
  attr_reader :interval
9
9
 
@@ -43,21 +43,16 @@ module Cotcube
43
43
  range: nil, # used to shrink the stencil size, accepts String or Date
44
44
  interval:,
45
45
  swap_type:,
46
- ranges: nil, # currently not used, prepared to be used in connection intraday
47
- contract: nil,
48
46
  date: nil,
49
47
  debug: false,
50
48
  version: nil, # when referring to a specicic version of the stencil
51
- timezone: CHICAGO,
52
- stencil: nil, # instead of loading, use this data
53
- #config: init,
54
- warnings: true
49
+ timezone: Cotcube::Helpers::CHICAGO,
50
+ stencil: nil, # instead of preparing, use this one if set
51
+ warnings: true # be more quiet
55
52
  )
56
53
  @debug = debug
57
- @interval = interval
54
+ @interval = interval == :continuous ? :daily : interval
58
55
  @swap_type = swap_type
59
- @swaps = []
60
- @contract = contract
61
56
  @warnings = warnings
62
57
  step = case @interval
63
58
  when :hours, :hour; 1.hour
@@ -81,14 +76,14 @@ module Cotcube
81
76
  swap_type
82
77
  end
83
78
  # TODO: Check / warn / raise whether stencil (if provided) is a proper data type
84
- raise ArgumentError, "Stencil should be nil or Array" unless [NilClass, Array].include? stencil.class
79
+ raise ArgumentError, "EOD_Stencil should be nil or Array" unless [NilClass, Array].include? stencil.class
85
80
  raise ArgumentError, "Each stencil members should contain at least :datetime and :x" unless stencil.nil? or
86
81
  stencil.map{|x| ([:datetime, :x] - x.keys).empty? and [ActiveSupport::TimeWithZone, Day].include?( x[:datetime] ) and x[:x].is_a?(Integer)}.reduce(:&)
87
82
 
88
- base = stencil || Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version)
83
+ base = stencil || EOD_Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version)
89
84
 
90
- # fast forward to prev trading day
91
- date = CHICAGO.parse(date) unless [NilClass, Date, ActiveSupport::TimeWithZone].include? date.class
85
+ # fast rewind to previous trading day
86
+ date = timezone.parse(date) unless [NilClass, Date, ActiveSupport::TimeWithZone].include? date.class
92
87
  @date = date || Date.today
93
88
  best_match = base.select{|x| x[:datetime].to_date <= @date}.last[:datetime]
94
89
  @date = best_match
@@ -106,12 +101,11 @@ module Cotcube
106
101
  end
107
102
 
108
103
  def dup
109
- Stencil.new(
104
+ EOD_Stencil.new(
110
105
  debug: @debug,
111
106
  interval: @interval,
112
107
  swap_type: @swap_type,
113
108
  date: @date,
114
- contract: @contract,
115
109
  stencil: @base.map{|x| x.dup}
116
110
  )
117
111
  end
@@ -142,6 +136,22 @@ module Cotcube
142
136
  to.reject!{|x| x[:x].nil? }
143
137
  end
144
138
 
139
+ def use(with:, sym:, zero:, grace: -2)
140
+ # todo: validate with (check if vslid swap
141
+ # sym (check keys)
142
+ # zero (ohlc with x.zero?)
143
+ # side ( upper or lower)
144
+ swap = with.dup
145
+ high = swap[:side] == :upper
146
+ ohlc = high ? :high : :low
147
+ start = base.find{|x| swap[:datetime] == x[:datetime]}
148
+ swap[:current_change] = (swap[:tpi] * start[:x]).round(8)
149
+ swap[:current_value] = swap[:members].last[ ohlc ] + swap[:current_change] * sym[:ticksize]
150
+ swap[:current_diff] = (swap[:current_value] - zero[ohlc]) * (high ? 1 : -1 )
151
+ swap[:current_dist] = (swap[:current_diff] / sym[:ticksize]).to_i
152
+ swap[:exceeded] = zero[:datetime] if swap[:current_dist] < grace
153
+ swap
154
+ end
145
155
  end
146
156
 
147
157
  end