cotcube-level 0.2.0 → 0.3.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: 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