cotcube-level 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -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} +10 -13
- data/lib/cotcube-level/helpers.rb +84 -75
- data/lib/cotcube-level/intraday_stencil.rb +179 -0
- data/lib/cotcube-level/tritangulate.rb +323 -0
- data/lib/cotcube-level.rb +15 -13
- metadata +6 -5
- data/lib/cotcube-level/triangulate.rb +0 -238
@@ -1,238 +0,0 @@
|
|
1
|
-
module Cotcube
|
2
|
-
module Level
|
3
|
-
def triangulate(
|
4
|
-
contract: nil, # contract actually isnt needed to triangulation, but allows much more convenient output
|
5
|
-
side:, # :upper or :lower
|
6
|
-
base:, # the base of a readily injected stencil
|
7
|
-
range: (0..-1), # range is relative to base
|
8
|
-
max: 90, # the range which to scan for swaps goes from deg 0 to max
|
9
|
-
debug: false,
|
10
|
-
format: '% 5.2f',
|
11
|
-
min_members: 3, # this param should not be changed manually, it is used for the guess operation
|
12
|
-
min_rating: 3, # swaps having a lower rating are discarded
|
13
|
-
allow_sub: true, # this param determines whether guess can be called or not
|
14
|
-
save: true, # allow saving of results
|
15
|
-
cached: true, # allow loading of yet cached intervals
|
16
|
-
interval: nil, # interval and swap_type are only needed if saving / caching of swaps is desired
|
17
|
-
swap_type: nil, # if not given, a warning is printed and swaps are not saved
|
18
|
-
deviation: 2)
|
19
|
-
|
20
|
-
raise ArgumentError, "'0 < max < 90, but got '#{max}'" unless max.is_a? Numeric and 0 < max and max <= 90
|
21
|
-
raise ArgumentError, 'need :side either :upper or :lower for dots' unless [:upper, :lower].include? side
|
22
|
-
|
23
|
-
###########################################################################################################################
|
24
|
-
# init some helpers
|
25
|
-
#
|
26
|
-
high = side == :upper
|
27
|
-
first = base.to_a.find{|x| not x[:high].nil? }
|
28
|
-
zero = base.select{|x| x[:x].zero? }
|
29
|
-
raise ArgumentError, "Inappropriate base, it should contain ONE :x.zero, but contains #{zero.size}." unless zero.size==1
|
30
|
-
zero = zero.first
|
31
|
-
contract ||= zero.contract
|
32
|
-
sym = Cotcube::Helpers.get_id_set(contract: contract)
|
33
|
-
|
34
|
-
if cached
|
35
|
-
if interval.nil? or swap_type.nil?
|
36
|
-
puts "Warning: Cannot use cache as both :interval and :swap_type must be given".light_yellow
|
37
|
-
else
|
38
|
-
cache = load_swaps(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
|
39
|
-
selected = cache.select{|sw| sw[:datetime] == zero[:datetime] and sw[:side] == side}
|
40
|
-
unless selected.empty?
|
41
|
-
puts 'cache_hit'.light_white if debug
|
42
|
-
return (selected.first[:empty] ? [] : selected )
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
ticksize = sym[:ticksize] / sym[:bcf] # need to adjust, as we are working on barchart data, not on exchange data !!
|
47
|
-
|
48
|
-
|
49
|
-
###########################################################################################################################
|
50
|
-
# prepare base (i.e. dupe the original, create proper :y, and reject unneeded items)
|
51
|
-
#
|
52
|
-
base = base.
|
53
|
-
map { |x|
|
54
|
-
y = x.dup
|
55
|
-
y[:y] = (high ?
|
56
|
-
(y[:high] - zero[:high]).round(8) :
|
57
|
-
(zero[:low] - y[:low]).round(8)
|
58
|
-
) unless y[:high].nil?
|
59
|
-
y
|
60
|
-
}.
|
61
|
-
reject{|b| b.nil? or b[:datetime] < first[:datetime] or b[:x] < 0 or b[:y].nil?}[range]
|
62
|
-
abs_peak = base.send(high ? :max_by : :min_by){|x| x[high ? :high : :low] }[:datetime]
|
63
|
-
|
64
|
-
base.reject!{|x| x[:datetime] < abs_peak}
|
65
|
-
|
66
|
-
###########################################################################################################################z
|
67
|
-
# only if (and only if) the range portion above change the underlying base
|
68
|
-
# the offset has to be fixed for :x and :y
|
69
|
-
|
70
|
-
unless range == (0..-1)
|
71
|
-
puts "adjusting range to '#{range}'".light_yellow if debug
|
72
|
-
offset_x = base.last[:x]
|
73
|
-
offset_y = base.last[:y]
|
74
|
-
base.map!{|b| b[:x] -= offset_x; b[:y] -= offset_y ; b}
|
75
|
-
end
|
76
|
-
base.each_index.map{|i| base[i][:i] = -base.size + i }
|
77
|
-
|
78
|
-
|
79
|
-
###########################################################################################################################
|
80
|
-
# LAMBDA no1: simplifying DEBUG output
|
81
|
-
#
|
82
|
-
present = lambda {|z| swap_to_human(z) }
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
###########################################################################################################################
|
87
|
-
# LAMBDA no2: all members except the pivot itself now most probably are too far to the left
|
88
|
-
# finalizing tries to get the proper dx value for them
|
89
|
-
#
|
90
|
-
finalize = lambda do |res|
|
91
|
-
res.map do |r|
|
92
|
-
r[:members].each do |m|
|
93
|
-
next if m[:yy].nil? or m[:yy].zero?
|
94
|
-
|
95
|
-
diff = (m[:x] - m[:dx]).abs / 2.0
|
96
|
-
m[:dx] = m[:x] + diff
|
97
|
-
# it employs another binary-search
|
98
|
-
while m[:yy].round(PRECISION) != 0 # or m[:yy].round(PRECISION) < -ticksize
|
99
|
-
print '.' if debug
|
100
|
-
m[:yy] = shear_to_deg(deg: r[:deg], base: [ m ] ).first[:yy]
|
101
|
-
diff /= 2.0
|
102
|
-
if m[:yy] > 0
|
103
|
-
m[:dx] += diff
|
104
|
-
else
|
105
|
-
m[:dx] -= diff
|
106
|
-
end
|
107
|
-
end
|
108
|
-
m[:yy] = m[:yy].abs.round(8)
|
109
|
-
end # r.members
|
110
|
-
puts 'done!'.light_yellow if debug
|
111
|
-
|
112
|
-
r[:members].each {|x| puts "finalizing #{x}".magenta } if debug
|
113
|
-
## The transforming part
|
114
|
-
|
115
|
-
r
|
116
|
-
end # res
|
117
|
-
end # lambda
|
118
|
-
|
119
|
-
###########################################################################################################################
|
120
|
-
# LAMDBA no3: the actual 'function' to retrieve the slope
|
121
|
-
#
|
122
|
-
get_slope = lambda do |b|
|
123
|
-
if debug
|
124
|
-
puts "in get_slope ... SETTING BASE: ".light_green
|
125
|
-
puts "Last:\t#{present.call b.last}"
|
126
|
-
puts "First:\t#{present.call b.first}"
|
127
|
-
end
|
128
|
-
members = [ b.last[:i] ]
|
129
|
-
loop do
|
130
|
-
current_slope = detect_slope(base: b, ticksize: ticksize, format: sym[:format], debug: debug)
|
131
|
-
current_members = current_slope[:members]
|
132
|
-
.map{|dot| dot[:i]}
|
133
|
-
new_members = current_members - members
|
134
|
-
puts "New members: #{new_members} as of #{current_members} - #{members}" if debug
|
135
|
-
# the return condition is if no new members are found in slope
|
136
|
-
# except lowest members are neighbours, what causes a re-run
|
137
|
-
if new_members.empty?
|
138
|
-
mem_sorted=members.sort
|
139
|
-
if mem_sorted[1] == mem_sorted[0] + 1
|
140
|
-
b2 = b[mem_sorted[1]..mem_sorted[-1]].map{|x| x.dup; x[:dx] = nil; x}
|
141
|
-
puts 'starting rerun' if debug
|
142
|
-
alternative_slope = get_slope.call(b2)
|
143
|
-
alternative = alternative_slope[:members].map{|bar| bar[:i]}
|
144
|
-
if (mem_sorted[1..-1] - alternative).empty?
|
145
|
-
current_slope = alternative_slope
|
146
|
-
members = alternative
|
147
|
-
end
|
148
|
-
end
|
149
|
-
if min_members >= 3 and members.size >= 3
|
150
|
-
current_slope[:raw] = members.map{|x| x.abs }.sort
|
151
|
-
current_slope[:length] = current_slope[:raw][-1] - current_slope[:raw][0]
|
152
|
-
current_slope[:rating] = current_slope[:raw][1..-2].map{|dot| [ dot - current_slope[:raw][0], current_slope[:raw][-1] - dot].min }.max
|
153
|
-
end
|
154
|
-
members.sort_by{|i| -i}.each do |x|
|
155
|
-
puts "#{range}\t#{present.call(b[x])}" if debug
|
156
|
-
current_slope[:members] << b[x] unless current_slope[:members].map{|x| x[:datetime]}.include? b[x][:datetime]
|
157
|
-
current_slope[:members].sort_by!{|x| x[:datetime]}
|
158
|
-
end
|
159
|
-
return current_slope
|
160
|
-
|
161
|
-
end
|
162
|
-
new_members.each do |mem|
|
163
|
-
current_deviation = (0.1 * b[mem][:x])
|
164
|
-
current_deviation = 1 if current_deviation < 1
|
165
|
-
current_deviation = deviation if current_deviation > deviation
|
166
|
-
b[mem][:dx] = b[mem][:x] + current_deviation
|
167
|
-
end
|
168
|
-
members += new_members
|
169
|
-
end
|
170
|
-
end # of lambda
|
171
|
-
|
172
|
-
analyze = lambda do |swaps|
|
173
|
-
swaps.each do |swap|
|
174
|
-
swap[:datetime] = swap[:members].last[:datetime]
|
175
|
-
swap[:side] = side
|
176
|
-
rat = swap[:rating]
|
177
|
-
swap[:color ] = (rat > 75) ? :light_blue : (rat > 30) ? :magenta : (rat > 15) ? :light_magenta : (rat > 7) ? (high ? :light_green : :light_red) : high ? :green : :red
|
178
|
-
swap[:diff] = swap[:members].last[ high ? :high : :low ] - swap[:members].first[ high ? :high : :low ]
|
179
|
-
swap[:ticks] = (swap[:diff] / sym[:ticksize]).to_i
|
180
|
-
swap[:tpi] = (swap[:ticks].to_f / swap[:length]).round(3)
|
181
|
-
swap[:ppi] = (swap[:tpi] * sym[:power]).round(3)
|
182
|
-
swap_base = shear_to_deg(base: base[swap[:members].first[:i]..], deg: swap[:deg]).map{|x| x[:dev] = (x[:yy] / sym[:ticksize]).abs.floor; x}
|
183
|
-
swap[:depth] = swap_base.max_by{|x| x[:dev]}[:dev]
|
184
|
-
swap[:avg_dev]= (swap_base.reject{|x| x[:dev].zero?}.map{|x| x[:dev]}.reduce(:+) / (swap_base.size - swap[:members].size).to_f).ceil rescue 0
|
185
|
-
# a miss is considered a point that is less than 10% of the average deviation away of the slope
|
186
|
-
unless swap[:avg_dev].zero?
|
187
|
-
misses = swap_base.select{|x| x[:dev] <= swap[:avg_dev] / 10.to_f and x[:dev] > 0}.map{|x| x[:miss] = x[:dev]; x}
|
188
|
-
# misses are sorted among members, but stay marked
|
189
|
-
swap[:members]= (swap[:members] + misses).sort_by{|x| x[:datetime] }
|
190
|
-
end
|
191
|
-
end # swap
|
192
|
-
end # of lambda
|
193
|
-
|
194
|
-
###########################################################################################################################
|
195
|
-
# after declaring lambdas, the rest is quite few code
|
196
|
-
#
|
197
|
-
current_range = (0..-1) # RANGE set
|
198
|
-
current_slope = { members: [] } # SLOPE reset
|
199
|
-
current_base = base[current_range] # BASE set
|
200
|
-
current_results = [ ] # RESULTS reset
|
201
|
-
while current_base.size >= 5 # LOOP
|
202
|
-
|
203
|
-
puts '-------------------------------------------------------------------------------------' if debug
|
204
|
-
|
205
|
-
while current_base.size >= 5 and current_slope[:members].size < min_members
|
206
|
-
puts "---- #{current_base.size} #{current_range.to_s.light_yellow} ------" if debug
|
207
|
-
current_slope = get_slope.call(current_base) # SLOPE call
|
208
|
-
next_i = current_slope[:members][-2]
|
209
|
-
current_range = ((next_i.nil? ? -2 : next_i[:i])+1..-1) # RANGE adjust
|
210
|
-
current_base = base[current_range] # BASE adjust
|
211
|
-
if debug
|
212
|
-
print 'Hit <enter> to continue...'
|
213
|
-
STDIN.gets
|
214
|
-
end
|
215
|
-
end
|
216
|
-
puts "Current slope: ".light_yellow + "#{current_slope}" if debug
|
217
|
-
current_results << current_slope if current_slope # RESULTS add
|
218
|
-
current_slope = { members: [] } # SLOPE reset
|
219
|
-
end
|
220
|
-
current_results.select!{|x| x[:members].size >= min_members }
|
221
|
-
|
222
|
-
# Adjust all members (except pivot) to fit the actual dx-value
|
223
|
-
finalize.call(current_results)
|
224
|
-
analyze.call(current_results)
|
225
|
-
current_results.reject!{|swap| swap[:rating] < min_rating}
|
226
|
-
if save
|
227
|
-
if interval.nil? or swap_type.nil?
|
228
|
-
puts "WARNING: Cannot save swaps, as both :interval and :swap_type must be given".colorize(:light_yellow)
|
229
|
-
else
|
230
|
-
to_save = current_results.empty? ? [ { datetime: zero[:datetime], side: side, empty: true } ] : current_results
|
231
|
-
save_swaps(to_save, interval: interval, swap_type: swap_type, contract: contract, sym: sym)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
current_results
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|