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 +4 -4
- data/CHANGELOG.md +49 -0
- data/README.md +14 -6
- data/VERSION +1 -1
- data/cotcube-level.gemspec +1 -1
- data/lib/cotcube-level/detect_slope.rb +79 -98
- data/lib/cotcube-level/{stencil.rb → eod_stencil.rb} +26 -16
- data/lib/cotcube-level/helpers.rb +198 -74
- data/lib/cotcube-level/intraday_stencil.rb +213 -0
- data/lib/cotcube-level/tritangulate.rb +332 -0
- data/lib/cotcube-level.rb +17 -14
- metadata +6 -6
- data/.irbrc.rb +0 -12
- data/lib/cotcube-level/triangulate.rb +0 -238
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '04184473172dd9ffd09c49a16bf6c2a724eb5cbef3f0f3d5423c44dea55f530f'
|
4
|
+
data.tar.gz: 75d1363041e3767a2f79c277a0d945fd830c1521df481194d8162c36b36044f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
21
|
-
|
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.
|
1
|
+
0.3.4
|
data/cotcube-level.gemspec
CHANGED
@@ -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
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
puts "
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
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
|
53
|
-
|
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, "
|
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 ||
|
83
|
+
base = stencil || EOD_Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version)
|
89
84
|
|
90
|
-
# fast
|
91
|
-
date =
|
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
|
-
|
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
|