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 +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
|