cotcube-helpers 0.1.9.2 → 0.2.2.3

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.
@@ -0,0 +1,509 @@
1
+ module Cotcube
2
+ module Helpers
3
+ module Candlestick_Recognition
4
+
5
+ SUPERFLUOUS = %i[wap datetime prev_slope upper lower bar_size body_size lower_wick upper_wick ranges vranges vavg trades type bullish bearish doji contract]
6
+ COMMON = [:symbol, :timestamp, :size, :rth, :time_end,
7
+ :open, :high, :vhigh, :vlow, :low, :close,
8
+ :interval, :offset, :appeared, :datetime, :volume, :dist, :vperd, :vol_i, :contract, :date,
9
+ :bar_size, :upper_wick, :lower_wick, :body_size, :upper, :lower, :slope, :rel, :tr, :atr, :ranges, :vranges, :vavg]
10
+
11
+ # recognize serves as interface
12
+ def recognize(contract:, interval: :quarters, short: true, base:, return_as_string: false, sym: )
13
+ s = bas
14
+ CR::candles s, ticksize: sym[:ticksize]
15
+ CR::patterns s, contract: contract, ticksize: sym[:ticksize]
16
+ s.map{|x| x[:datetime] += 7.hours } if s and %w[ GG DY ].include?(contract[..1]) and interval == :quarters
17
+ s.map{|x| x[:datetime] += 1.hour } if s and %w[ GC PA PL SI HG NG HO RB CL ].include?(contract[..1]) and interval == :quarters
18
+ make_string = lambda {|c| "#{contract
19
+ }\t#{c[:datetime].strftime( interval == :quarters ? '%Y-%m-%d %H:%M' : '%Y-%m-%d' )}".colorize(:light_white) +
20
+ "\t#{print_bar(bar: c, format: sym[:format], power: sym[:power], short: short)
21
+ } #{"\n\n" if c[:datetime].wday == 5 and interval == :days}" }
22
+ return_as_string ? s[-count..].map{|candle| make_string.call(candle) }.join("\n") : s
23
+ end
24
+
25
+
26
+ def print_bar(bar:, format:, power:, short: false)
27
+ x = bar.dup
28
+ dir = x[:bullish] ? :bullish : x[:bearish] ? :bearish : x[:doji] ? :doji : :none
29
+ contract = bar[:contract]
30
+ SUPERFLUOUS.map{|s| x.delete(s)}
31
+ %i[ UPPER_PIVOT LOWER_PIVOT UPPER_ISOPIVOT LOWER_ISOPIVOT ].map{|s| x.delete(s)}
32
+
33
+
34
+ vol = x.keys.select{|x| x.to_s =~ /_volume/}[0]
35
+ x.delete vol
36
+
37
+ vol = vol.to_s.split("_")[0]
38
+ col = case dir
39
+ when :bullish
40
+ :light_green
41
+ when :bearish
42
+ :light_red
43
+ when :doji
44
+ :light_blue
45
+ else:light_black
46
+ end
47
+
48
+ special = lambda do |s|
49
+ case s
50
+ when *%i[ THRUSTING_LINE SHOOTING_STAR HANGING_MAN EVENING_STAR EVENING_DOJI_STAR UPSIDEGAP_TWO_CROWS UMKEHRSTAB_BEARISH ]
51
+ s.to_s.colorize(:light_red)
52
+ when *%i[ PIERCING_LINE INVERTED_HAMMER HAMMER MORNING_STAR MORNING_DOJI_STAR DOWNGAP_TWO_RIVERS UMKEHRSTAB_BULLISH ]
53
+ s.to_s.colorize(:light_green)
54
+ else
55
+ s.to_s.colorize(:light_cyan)
56
+ end
57
+ end
58
+
59
+
60
+
61
+ "#{ format '%12s', (format % x[:open ]) }" +
62
+ " #{format '%12s', (format % x[:high ])}".colorize( (x.keys.include?(:UPPER_PIVOT) or x.keys.include?(:UPPER_ISOPIVOT)) ? :light_blue : :white ) +
63
+ " #{format '%12s', (format % x[:low ])}".colorize( (x.keys.include?(:LOWER_PIVOT) or x.keys.include?(:LOWER_ISOPIVOT)) ? :light_blue : :white ) +
64
+ " #{format '%12s', ( format % x[:close ])}" +
65
+ (short ? "" : " #{"%10s" % ("[ % -4.1f ]" % x[:slope])}".colorize( if x[:slope].abs < 3; :yellow; elsif x[:slope] >= 3; :green; else; :red; end)) +
66
+ (short ? "" : " #{"% 8d" % x[:volume]}") +
67
+ " #{vol}".colorize( case vol; when *["BREAKN", "FAINTN"]; :light_blue; when *["RISING","FALLIN"]; :cyan; else; :white; end) +
68
+ (short ? "" : " D:#{"%5d" % x[:dist]}" ) +
69
+ (short ? "" : " P:#{"%10.2f" % (x[:dist] * power)} ".cyan ) +
70
+ format('%10s', dir.to_s).colorize(col) +
71
+ (short ? "" : format('%-22s', " >>> #{x.keys.map{|v| (COMMON.include?(v) or v.upcase == v)? nil : v}.compact.join(" ")}").colorize( col )) +
72
+ "\t#{x.keys.map{|v| (COMMON.include?(v) or v.upcase != v)? nil : special.call(v)}.compact.join(" ")}"
73
+ end
74
+
75
+ def candles(candles, debug: false, sym: )
76
+ ticksize = sym[:ticksize]
77
+
78
+ candles.each_with_index do |bar, i|
79
+ # rel simply sets a grace limit based on the full height of the bar, so we won't need to use the hard limit of zero
80
+ begin
81
+ rel = ((bar[:high] - bar[:low]) * 0.05).round(8)
82
+ rel = 2 * ticksize if rel < 2 * ticksize
83
+ rescue
84
+ puts "Warning, found inappropriate bar".light_white + " #{bar}"
85
+ raise
86
+ end
87
+ bar[:rel] = rel
88
+ bar[:dist] ||= ((bar[:high] - bar[:low])/ticksize).round(8)
89
+
90
+ bar[:upper] = [bar[:open], bar[:close]].max
91
+ bar[:lower] = [bar[:open], bar[:close]].min
92
+ bar[:bar_size] = (bar[:high] - bar[:low])
93
+ bar[:body_size] = (bar[:open] - bar[:close]).abs
94
+ bar[:lower_wick] = (bar[:lower] - bar[:low])
95
+ bar[:upper_wick] = (bar[:high] - bar[:upper])
96
+ bar.each{|k,v| bar[k] = v.round(8) if v.is_a? Float}
97
+
98
+ # a doji's open and close are same (or only differ by rel)
99
+ bar[:doji] = true if bar[:body_size] <= rel and bar[:dist] >= 3
100
+ bar[:tiny] = true if bar[:dist] <= 5
101
+
102
+ next if bar[:tiny]
103
+
104
+ bar[:bullish] = true if not bar[:doji] and bar[:close] > bar[:open]
105
+ bar[:bearish] = true if not bar[:doji] and bar[:close] < bar[:open]
106
+
107
+ bar[:spinning_top] = true if bar[:body_size] <= bar[:bar_size] / 4 and
108
+ bar[:lower_wick] >= bar[:bar_size] / 4 and
109
+ bar[:upper_wick] >= bar[:bar_size] / 4
110
+
111
+ # a marubozu open at high or low and closes at low or high
112
+ bar[:marubozu] = true if bar[:upper_wick] < rel and bar[:lower_wick] < rel
113
+
114
+ # a bar is considered bearish if it has at least a dist of 5 ticks and it's close it near high (low resp)
115
+ bar[:bullish_close] = true if (bar[:high] - bar[:close]) <= rel and not bar[:marubozu]
116
+ bar[:bearish_close] = true if (bar[:close] - bar[:low]) <= rel and not bar[:marubozu]
117
+
118
+ # the distribution of main volume is shown in 5 segments, like [0|0|0|4|5] shows that most volume concentrated at the bottom, [0|0|3|0|0] is heavily centered
119
+ # TODO
120
+
121
+ end
122
+ candles
123
+ end
124
+
125
+ def comparebars(prev, curr)
126
+ bullishscore = 0
127
+ bearishscore = 0
128
+ bullishscore += 1 if prev[:high] <= curr[:high]
129
+ bullishscore += 1 if prev[:low] <= curr[:low]
130
+ bullishscore += 1 if prev[:close] <= curr[:close]
131
+ bearishscore += 1 if prev[:close] >= curr[:close]
132
+ bearishscore += 1 if prev[:low] >= curr[:low]
133
+ bearishscore += 1 if prev[:high] >= curr[:high]
134
+ r = {}
135
+ r[:bullish] = true if bullishscore >= 2
136
+ r[:bearish] = true if bearishscore >= 2
137
+ return r
138
+ end
139
+
140
+
141
+ def patterns(candles, debug: false, size: 5, contract:, ticksize: nil )
142
+ candles.each_with_index do |bar, i|
143
+ preceeding = candles.select{|x| x[:datetime] <= bar[:datetime] }
144
+ if i.zero?
145
+ bar[:slope] = 0
146
+ next
147
+ end
148
+ ppprev= candles[i-3]
149
+ pprev= candles[i-2]
150
+ prev = candles[i-1]
151
+ succ = candles[i+1]
152
+
153
+ bar[:huge] = true if bar[:true_range] >= bar[:atr] * 1.5
154
+ bar[:small] = true if bar[:true_range] <= bar[:atr5] * 0.6666
155
+
156
+ bar[:vavg] = (bar[:vranges].reduce(:+) / bar[:vranges].size.to_f).round if bar[:vranges] and bar[:vranges].compact.size > 0
157
+ bar[:vranges] = prev[:vranges].nil? ? [ bar[:volume] ] : prev[:vranges] + [ bar[:volume] ]
158
+ bar[:vranges].shift while bar[:vranges].size > size
159
+ bar[:vavg] ||= (bar[:vranges].reduce(:+) / bar[:vranges].size.to_f).round if bar[:vranges] and bar[:vranges].compact.size > 0
160
+
161
+
162
+
163
+ # VOLUME
164
+ if bar[:volume] > bar[:vavg] * 1.3
165
+ bar[:BREAKN_volume] = true
166
+ elsif bar[:volume] >= bar[:vavg] * 1.1
167
+ bar[:RISING_volume] = true
168
+ elsif bar[:volume] < bar[:vavg] * 0.7
169
+ bar[:FAINTN_volume] = true
170
+ elsif bar[:volume] <= bar[:vavg] * 0.9
171
+ bar[:FALLIN_volume] = true
172
+ else
173
+ bar[:STABLE_volume] = true
174
+ end
175
+
176
+ # GAPS
177
+ bar[:bodygap] = true if bar[:lower] > prev[:upper] or bar[:upper] < prev[:lower]
178
+ bar[:gap] = true if bar[:low] > prev[:high] or bar[:high] < prev[:low]
179
+
180
+
181
+ bar[:slope] = slopescore(pprev, prev, bar, debug)
182
+ bar[:prev_slope] = prev[:slope]
183
+
184
+ # UPPER_PIVOTs define by having higher highs and higher lows than their neighor
185
+ bar[:UPPER_ISOPIVOT] = true if succ and prev[:high] < bar[:high] and prev[:low] <= bar[:low] and succ[:high] < bar[:high] and succ[:low] <= bar[:low] and bar[:lower] >= [prev[:upper], succ[:upper]].max
186
+ bar[:LOWER_ISOPIVOT] = true if succ and prev[:high] >= bar[:high] and prev[:low] > bar[:low] and succ[:high] >= bar[:high] and succ[:low] > bar[:low] and bar[:upper] <= [prev[:lower], succ[:lower]].min
187
+ bar[:UPPER_PIVOT] = true if succ and prev[:high] < bar[:high] and prev[:low] <= bar[:low] and succ[:high] < bar[:high] and succ[:low] <= bar[:low] and not bar[:UPPER_ISOPIVOT]
188
+ bar[:LOWER_PIVOT] = true if succ and prev[:high] >= bar[:high] and prev[:low] > bar[:low] and succ[:high] >= bar[:high] and succ[:low] > bar[:low] and not bar[:LOWER_ISOPIVOT]
189
+
190
+ # stopping volume is defined as high volume candle during downtrend then closes above mid candle (i.e. lower_wick > body_size)
191
+ bar[:stopping_volume] = true if (bar[:BREAKN_volume] or bar[:RISING_volume]) and prev[:slope] < -5 and bar[:lower_wick] >= bar[:body_size]
192
+ bar[:stopping_volume] = true if (bar[:BREAKN_volume] or bar[:RISING_volume]) and prev[:slope] > 5 and bar[:upper_wick] >= bar[:body_size]
193
+ bar[:volume_lower_wick] = true if bar[:vhigh] and (bar[:vol_i].nil? or bar[:vol_i] >= 2) and bar[:vhigh] <= bar[:lower] and not bar[:FAINTN_volume] and not bar[:FALLIN_volume]
194
+ bar[:volume_upper_wick] = true if bar[:vlow] and (bar[:vol_i].nil? or bar[:vol_i] >= 2) and bar[:vlow] >= bar[:upper] and not bar[:FAINTN_volume] and not bar[:FALLIN_volume]
195
+
196
+
197
+ ###################################
198
+ # SINGLE CANDLE PATTERNS
199
+ ###################################
200
+
201
+ # a hammer is a bar, whose open or close is at the high and whose body is lte 1/3 of the size, found on falling slope, preferrably gapping away
202
+ bar[:HAMMER] = true if bar[:upper_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 3 and bar[ :slope] <= -6
203
+ # same shape, but found at a raising slope without the need to gap away
204
+ bar[:HANGING_MAN] = true if bar[:upper_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 3 and prev[:slope] >= 6
205
+
206
+ # a shooting star is the inverse of the hammer, while the inverted hammer is the inverse of the hanging man
207
+ bar[:SHOOTING_STAR] = true if bar[:lower_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 2.5 and bar[ :slope] >= 6
208
+ bar[:INVERTED_HAMMER] = true if bar[:lower_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 3 and prev[:slope] <= -6
209
+
210
+ # a star is simply gapping away the preceding slope
211
+ if ((bar[:lower] > prev[:upper] and bar[:slope] >= 6 and bar[:high] >= prev[:high]) or
212
+ (bar[:upper] < prev[:lower] and bar[:slope] <= -6) and bar[:low] <= prev[:low])
213
+ bar[:doji] ? bar[:DOJI_STAR] = true : bar[:STAR] = true
214
+ end
215
+
216
+
217
+ # a belthold is has a gap in the open, but reverses strong
218
+ bar[:BULLISH_BELTHOLD] = true if bar[:lower_wick] <= bar[:rel] and bar[:body_size] >= bar[:bar_size] / 2 and
219
+ prev[:slope] <= -4 and bar[:lower] <= prev[:low ] and bar[:bullish] and not prev[:bullish] and bar[:bar_size] >= prev[:bar_size]
220
+ bar[:BEARISH_BELTHOLD] = true if bar[:upper_wick] <= bar[:rel] and bar[:body_size] >= bar[:bar_size] / 2 and
221
+ prev[:slope] >= -4 and bar[:upper] <= prev[:high] and bar[:bearish] and not prev[:bearish] and bar[:bar_size] >= prev[:bar_size]
222
+
223
+
224
+ ###################################
225
+ # DUAL CANDLE PATTERNS
226
+ ###################################
227
+
228
+
229
+ # ENGULFINGS
230
+ bar[:BULLISH_ENGULFING] = true if bar[:bullish] and prev[:bearish] and bar[:lower] <= prev[:lower] and bar[:upper] > prev[:upper] and prev[:slope] <= -6
231
+ bar[:BEARISH_ENGULFING] = true if bar[:bearish] and prev[:bullish] and bar[:lower] < prev[:lower] and bar[:upper] >= prev[:upper] and prev[:slope] >= 6
232
+
233
+
234
+ # DARK-CLOUD-COVER / PIERCING-LINE (on-neck / in-neck / thrusting / piercing / PDF pg 63)
235
+ bar[:DARK_CLOUD_COVER] = true if bar[:slope] > 5 and prev[:bullish] and bar[:open] > prev[:high] and bar[:close] < prev[:upper] - prev[:body_size] * 0.5 and
236
+ not bar[:BEARISH_ENGULFING]
237
+ bar[:PIERCING_LINE] = true if bar[:slope] < -5 and prev[:bearish] and bar[:open] < prev[:low ] and bar[:close] > prev[:lower] + prev[:body_size] * 0.5 and
238
+ not bar[:BULLISH_ENGULFING]
239
+ bar[:SMALL_CLOUD_COVER] = true if bar[:slope] > 5 and prev[:bullish] and bar[:open] > prev[:high] and bar[:close] < prev[:upper] - prev[:body_size] * 0.25 and
240
+ not bar[:BEARISH_ENGULFING] and not bar[:DARK_CLOUD_COVER]
241
+ bar[:THRUSTING_LINE] = true if bar[:slope] < -5 and prev[:bearish] and bar[:open] < prev[:low ] and bar[:close] > prev[:lower] + prev[:body_size] * 0.25 and
242
+ not bar[:BULLISH_ENGULFING] and not bar[:PIERCING_LINE]
243
+
244
+
245
+ # COUNTER ATTACKS are like piercings / cloud covers, but insist on a large reverse while only reaching the preceding close
246
+ bar[:BULLISH_COUNTERATTACK] = true if bar[:slope] < 6 and prev[:bearish] and bar[:bar_size] > bar[:atr] * 0.66 and (bar[:close] - prev[:close]).abs < 2 * bar[:rel] and
247
+ bar[:body_size] >= bar[:bar_size] * 0.5 and bar[:bullish]
248
+ bar[:BEARISH_COUNTERATTACK] = true if bar[:slope] > 6 and prev[:bullish] and bar[:bar_size] > bar[:atr] * 0.66 and (bar[:close] - prev[:close]).abs < 2 * bar[:rel] and
249
+ bar[:body_size] >= bar[:bar_size] * 0.5 and bar[:bearish]
250
+
251
+
252
+ # HARAMIs are an unusual long body embedding the following small body
253
+ bar[:HARAMI] = true if bar[:body_size] < prev[:body_size] / 2.5 and prev[:bar_size] >= bar[:atr] and
254
+ prev[:upper] > bar[:upper] and prev[:lower] < bar[:lower] and not bar[:doji]
255
+ bar[:HARAMI_CROSS] = true if bar[:body_size] < prev[:body_size] / 2.5 and prev[:bar_size] >= bar[:atr] and
256
+ prev[:upper] > bar[:upper] and prev[:lower] < bar[:lower] and bar[:doji]
257
+ if bar[:HARAMI] or bar[:HARAMI_CROSS]
258
+ puts [ :date, :open, :high, :low, :close, :upper, :lower ].map{|x| prev[x]}.join("\t") if debug
259
+ puts [ :date, :open, :high, :low, :close, :upper, :lower ].map{|x| bar[ x]}.join("\t") if debug
260
+ puts "" if debug
261
+ end
262
+
263
+ # TODO TWEEZER_TOP and TWEEZER_BOTTOM
264
+ # actually being a double top / bottom, this dual candle pattern has to be unfolded. It is valid on daily or weekly charts,
265
+ # and valid if
266
+ # 1 it has an according
267
+
268
+
269
+ ###################################
270
+ # TRIPLE CANDLE PATTERNS
271
+ ###################################
272
+
273
+ # morning star, morning doji star
274
+ next unless prev and pprev
275
+ bar[:MORNING_STAR] = true if prev[:STAR] and bar[:bullish] and bar[:close] >= pprev[:lower] and prev[:slope] < -6
276
+ bar[:MORNING_DOJI_STAR] = true if prev[:DOJI_STAR] and bar[:bullish] and bar[:close] >= pprev[:lower] and prev[:slope] < -6
277
+ bar[:EVENING_STAR] = true if prev[:STAR] and bar[:bearish] and bar[:close] <= pprev[:upper] and prev[:slope] > 6
278
+ bar[:EVENING_DOJI_STAR] = true if prev[:DOJI_STAR] and bar[:bearish] and bar[:close] <= pprev[:upper] and prev[:slope] > 6
279
+
280
+ # the abandoned baby escalates above stars by gapping the inner star candle to both framing it
281
+ bar[:ABANDONED_BABY] = true if (bar[:MORNING_STAR] or bar[:MORNING_DOJI_STAR]) and prev[:high] <= [ pprev[:low ], bar[:low ] ].min
282
+ bar[:ABANDONED_BABY] = true if (bar[:EVENING_STAR] or bar[:EVENING_DOJI_STAR]) and prev[:low ] >= [ pprev[:high], bar[:high] ].max
283
+
284
+ # UPSIDEGAP_TWO_CROWS
285
+ bar[:UPSIDEGAP_TWO_CROWS] = true if (prev[:STAR] or prev[:DOJI_STAR]) and prev[:slope] > 4 and bar[:bearish] and prev[:bearish] and bar[:close] > pprev[:close]
286
+ bar[:DOWNGAP_TWO_RIVERS] = true if (prev[:STAR] or prev[:DOJI_STAR]) and prev[:slope] < 4 and bar[:bullish] and prev[:bullish] and bar[:close] < pprev[:close]
287
+
288
+ # THREE BLACK CROWS / THREE WHITE SOLDIERS
289
+ bar[:THREE_BLACK_CROWS] = true if [ bar, prev, pprev ].map{|x| x[:bearish] and x[:bar_size] > 0.5 * bar[:atr] }.reduce(:&) and
290
+ pprev[:close] - prev[ :close] > bar[:atr] * 0.2 and
291
+ prev[ :close] - bar[ :close] > bar[:atr] * 0.2
292
+ bar[:THREE_WHITE_SOLDIERS] = true if [ bar, prev, pprev ].map{|x| x[:bullish] and x[:bar_size] > 0.5 * bar[:atr] }.reduce(:&) and
293
+ prev[:close] - pprev[:close] > bar[:atr] * 0.2 and
294
+ bar[ :close] - prev[ :close] > bar[:atr] * 0.2
295
+
296
+ #### MARKTTECHNIK ####
297
+
298
+ # Umkehrstäbe
299
+ # Ein Umkehrstab bullish ist ein Candle, der zumindest einen Downtrend aus 3 Kerzen beenden könnte.
300
+ # dazu muss der stab selber ein niedrigers tief haben als seine beiden vorgänger,
301
+ # der TR muss above average sein und der vorgänger muss einen slopescore von < -5 haben
302
+ #
303
+ if pprev[:low] > prev[:low] and prev[:low] > bar[:low] and
304
+ ( bar[:tr] > bar[:atr] * 1.25 or bar[:BREAKN_volume] ) and
305
+ prev[:slope] <= -5 and
306
+ bar[:close] - bar[:low] >= (bar[:bar_size]) * 0.7
307
+ #bar[:close] >= (bar[:high] + bar[:low]) / 2.0
308
+ bar[:UMKEHRSTAB_BULLISH] = true
309
+ bar[:CLASSIC] = true
310
+ end
311
+
312
+ if pprev[:high] < prev[:high] and prev[:high] < bar[:high] and
313
+ ( bar[:tr] > bar[:atr] * 1.25 or bar[:BREAKN_volume] ) and
314
+ prev[:slope] >= 5 and
315
+ bar[:high] - bar[:close] >= (bar[:bar_size]) * 0.7
316
+ #bar[:close] <= (bar[:high] + bar[:low]) / 2.0
317
+ bar[:UMKEHRSTAB_BEARISH] = true
318
+ bar[:CLASSIC] = true
319
+ end
320
+
321
+ # TINY REVERSALS only work for short periods of time (i.e. lte 5 min)
322
+
323
+ if bar[:datetime] and bar[:datetime].respond_to?(:-) and bar[:datetime] - prev[:datetime] <= 5*60
324
+ if pprev[:low] > prev[:low] and prev[:low] > bar[:low] and
325
+ ((not bar[:tiny] and bar[:bar_size] >= 0.75 * bar[:atr]) or bar[:BREAKN_volume]) and
326
+ bar[:high] - bar[:close] < bar[:bar_size] / 2.5
327
+ bar[:UMKEHRSTAB_BULLISH] = true
328
+ bar[:TINY] = true
329
+ end
330
+ if pprev[:high] < prev[:high] and prev[:high] < bar[:high] and
331
+ ((not bar[:tiny] and bar[:bar_size] >= 0.75 * bar[:atr]) or bar[:BREAKN_volume]) and
332
+ bar[:close] - bar[:low] < bar[:bar_size] / 2.5
333
+ bar[:UMKEHRSTAB_BEARISH] = true
334
+ bar[:TINY] = true
335
+ end
336
+ end
337
+
338
+ # GEM reversals are just another set of criteria
339
+ if prev[:low] > bar[:low] and prev[:high] > bar[:high] and
340
+ (bar[:bar_size] >= bar[:atr] or bar[:BREAKN_volume]) and
341
+ bar[:high] - bar[:close] < bar[:bar_size] / 4.0
342
+ bar[:UMKEHRSTAB_BULLISH] = true
343
+ bar[:GEM] = true
344
+ end
345
+ if prev[:low] < bar[:low] and prev[:high] < bar[:high] and
346
+ (bar[:bar_size] >= bar[:atr] or bar[:BREAKN_volume]) and
347
+ bar[:close] - bar[:low] < bar[:bar_size] / 4.0
348
+ bar[:UMKEHRSTAB_BEARISH] = true
349
+ bar[:GEM] = true
350
+ end
351
+
352
+ # DOUBLEBAR reversals are reversals, that span over 2 bars instead of one
353
+ unless ppprev.nil? or pprev[:slope].nil?
354
+ pot = {
355
+ open: prev[:open],
356
+ high: [prev[:high], bar[:high]].max,
357
+ low: [prev[:low], bar[:low]].min,
358
+ close: bar[:close]
359
+ }
360
+ pot[:size] = (pot[:high] - pot[:low]).round(8)
361
+ if (pprev[:slope] >=8 or (prev[:low] > pprev[:low] and pprev[:low] > ppprev[:low])) and pot[:high] > pprev[:high] and
362
+ pot[:high] - pot[:close] >= pot[:size] * 0.7
363
+ bar[:UMKEHRSTAB_BEARISH] = true
364
+ bar[:DOUBLE] = true
365
+ end
366
+ if (pprev[:slope] <=-8 or (prev[:high] < pprev[:high] and pprev[:high] < ppprev[:high])) and
367
+ pot[:low] < pprev[:low] and
368
+ pot[:close] - pot[:low] >= pot[:size] * 0.7
369
+ bar[:UMKEHRSTAB_BULLISH] = true
370
+ bar[:DOUBLE] = true
371
+ end
372
+ end
373
+
374
+ # BULLISH_MOVE und BEARISH_MOVE
375
+
376
+ unless pprev[:atr].nil?
377
+ if pprev[:high] > prev[:high] and prev[:high] > bar[:high] and
378
+ pprev[:low] > prev[:low] and prev[:low] > bar[:low] and
379
+ bar[:bar_size] >= pprev[:atr] and pprev[:bar_size] >= pprev[:atr] and prev[:bar_size] >= pprev[:atr] and
380
+ bar[:close] - bar[:low] < bar[:bar_size] / 3
381
+ bar[:BEARISH_MOVE] = prev[:BEARISH_MOVE].nil? ? (pprev[:BEARISH_MOVE].nil? ? 1 : pprev[:BEARISH_MOVE] + 2) : prev[:BEARISH_MOVE] + 1
382
+ end
383
+ if pprev[:high] < prev[:high] and prev[:high] < bar[:high] and
384
+ pprev[:low] < prev[:low] and prev[:low] < bar[:low] and
385
+ bar[:bar_size] >= pprev[:atr] and pprev[:bar_size] >= pprev[:atr] and prev[:bar_size] >= pprev[:atr] and
386
+ bar[:high] - bar[:close] < bar[:bar_size] / 3
387
+ bar[:BULLISH_MOVE] = prev[:BULLISH_MOVE].nil? ? (pprev[:BULLISH_MOVE].nil? ? 1 : pprev[:BULLISH_MOVE] + 2) : prev[:BULLISH_MOVE] + 1
388
+ end
389
+ end
390
+
391
+ # support and resistance
392
+
393
+ end
394
+ end
395
+
396
+
397
+ # SLOPE SCORE
398
+ def slopescore(pprev, prev, bar, debug = false)
399
+ # the slope between to bars is considered bullish, if 2 of three points match
400
+ # - higher high
401
+ # - higher close
402
+ # - higher low
403
+ # the opposite counts for bearish
404
+ #
405
+ # this comparison is done between the current bar and previous bar
406
+ # - if it confirms the score of the previous bar, the new slope score is prev + curr
407
+ # - otherwise the is compared to score of the pprevious bar
408
+ # - if it confirms there, the new slope score is pprev + curr
409
+ # - otherwise the trend is destroyed and tne new score is solely curr
410
+
411
+ if bar[:bullish]
412
+ curr = 1
413
+ curr += 1 if bar[:bullish_close]
414
+ elsif bar[:bearish]
415
+ curr = -1
416
+ curr -= 1 if bar[:bearish_close]
417
+ else
418
+ curr = 0
419
+ end
420
+ puts "curr set to #{curr} @ #{bar[:date]}".yellow if debug
421
+ if prev.nil?
422
+ puts "no prev found, score == curr: #{curr}" if debug
423
+ score = curr
424
+ else
425
+ comp = comparebars(prev, bar)
426
+
427
+ puts prev.select{|k,v| [:high,:low,:close,:score].include?(k)} if debug
428
+ puts bar if debug
429
+ puts "COMPARISON 1: #{comp}" if debug
430
+
431
+ if prev[:slope] >= 0 and comp[:bullish] # bullish slope confirmed
432
+ score = prev[:slope]
433
+ score += curr if curr > 0
434
+ [ :gap, :bodygap ] .each {|x| score += 0.5 if bar[x] }
435
+ score += 1 if bar[:RISING_volume]
436
+ score += 2 if bar[:BREAKN_volume]
437
+ puts "found bullish slope confirmed, new score #{score}" if debug
438
+ elsif prev[:slope] <= 0 and comp[:bearish] # bearish slope confirmed
439
+ score = prev[:slope]
440
+ score += curr if curr < 0
441
+ [ :gap, :bodygap ] .each {|x| score -= 0.5 if bar[x] }
442
+ score -= 1 if bar[:RISING_volume]
443
+ score -= 2 if bar[:BREAKN_volume]
444
+ puts "found bearish slope confirmed, new score #{score} (including #{curr} and #{bar[:bodygap]} and #{bar[:gap]}" if debug
445
+ else #if prev[:slope] > 0 # slopes failed
446
+ puts "confirmation failed: " if debug
447
+ if pprev.nil?
448
+ score = curr
449
+ else
450
+ comp2 = comparebars(pprev, bar)
451
+ puts "\t\tCOMPARISON 2: #{comp2}" if debug
452
+ if pprev[:slope] >= 0 and comp2[:bullish] # bullish slope confirmed on pprev
453
+ score = pprev[:slope]
454
+ score += curr if curr > 0
455
+ [ :gap, :bodygap ] .each {|x| score += 0.5 if bar[x] }
456
+ puts "\t\tfound bullish slope confirmed, new score #{score}" if debug
457
+ score += 1 if bar[:RISING_volume]
458
+ score += 2 if bar[:BREAKN_volume]
459
+ elsif pprev[:slope] <= 0 and comp2[:bearish] # bearish slope confirmed
460
+ score = pprev[:slope]
461
+ score += curr if curr < 0
462
+ [ :gap, :bodygap ] .each {|x| score -= 0.5 if bar[x] }
463
+ score -= 1 if bar[:RISING_volume]
464
+ score -= 2 if bar[:BREAKN_volume]
465
+ puts "\t\tfound bearish slope confirmed, new score #{score}" if debug
466
+ else #slope confirmation finally failed
467
+ comp3 = comparebars(pprev, prev)
468
+ if prev[:slope] > 0 # was bullish, turning bearish now
469
+ score = curr
470
+ score -= 1 if comp3[:bearish]
471
+ score -= 1 if comp[:bearish]
472
+ score -= 1 if prev[:bearish]
473
+ score -= 1 if prev[:RISING_volume] and comp3[:bearish]
474
+ score -= 2 if prev[:BREAKN_volume] and comp3[:bearish]
475
+ score -= 1 if bar[:RISING_volume] and comp[:bearish]
476
+ score -= 2 if bar[:BREAKN_volume] and comp[:bearish]
477
+ score -= 1 if bar[:RISING_volume] and comp[:bearish]
478
+ score -= 2 if bar[:BREAKN_volume] and comp[:bearish]
479
+ [ :gap, :bodygap ] .each {|x| score += 0.5 if bar[x] }
480
+ puts "\t\tfinally gave up, turning bearish now, new score #{score}" if debug
481
+ elsif prev[:slope] < 0
482
+ score = curr
483
+ score += 1 if comp3[:bullish]
484
+ score += 1 if comp[:bullish]
485
+ score += 1 if prev[:bullish]
486
+ score += 1 if prev[:RISING_volume] and comp3[:bullish]
487
+ score += 2 if prev[:BREAKN_volume] and comp3[:bullish]
488
+ score += 1 if bar[:RISING_volume] and comp[:bullish]
489
+ score += 2 if bar[:BREAKN_volume] and comp[:bullish]
490
+ score += 1 if bar[:RISING_volume] and comp[:bullish]
491
+ score += 2 if bar[:BREAKN_volume] and comp[:bullish]
492
+ [ :gap, :bodygap ] .each {|x| score -= 0.5 if bar[x] } if curr < 0
493
+ puts "\t\tfinally gave up, turning bullish now, new score #{score}" if debug
494
+ else
495
+ score = 0
496
+ end
497
+ end
498
+ end
499
+ end
500
+ end
501
+ puts "" if debug
502
+ score
503
+ end
504
+
505
+ end
506
+
507
+ CR = Candlestick_Recognition
508
+ end
509
+ end
@@ -22,12 +22,12 @@ module Cotcube
22
22
  lambda do |x|
23
23
  return false if x.nil? || (x.size < minimum)
24
24
 
25
- return ((pattern =~ /^#{x}/i).nil? ? false : true)
25
+ return ((pattern =~ /^#{Regexp.escape x}/i).nil? ? false : true)
26
26
  end
27
27
  when Array
28
28
  pattern.map do |x|
29
29
  unless [String, Symbol, NilClass].include? x.class
30
- raise TypeError, "Unsupported class '#{x.class}' for '#{x}'in pattern '#{pattern}'."
30
+ raise TypeError, "Unsupported class '#{x.class}' for '#{x}' in pattern '#{pattern}'."
31
31
  end
32
32
  end
33
33
  lambda do |x|
@@ -35,13 +35,13 @@ module Cotcube
35
35
  sub = sub.to_s
36
36
  return false if x.size < minimum
37
37
 
38
- result = ((sub =~ /^#{x}/i).nil? ? false : true)
38
+ result = ((sub =~ /^#{Regexp.escape x}/i).nil? ? false : true)
39
39
  return true if result
40
40
  end
41
41
  return false
42
42
  end
43
43
  else
44
- raise TypeError, "Unsupported class #{pattern.class} in Cotcube::Core::sub"
44
+ raise TypeError, "Unsupported class #{pattern.class} in Cotcube::Helpers::sub"
45
45
  end
46
46
  end
47
47
  end
@@ -4,19 +4,66 @@ module Cotcube
4
4
  # Missing top level documentation
5
5
  module Helpers
6
6
 
7
- def symbols(config: init, type: nil, symbol: nil)
7
+ SYMBOL_HEADERS = %i[ id symbol ib_symbol internal exchange currency ticksize power months type bcf reports format name ]
8
+
9
+ def symbols(config: init, **args)
8
10
  if config[:symbols_file].nil?
9
11
  SYMBOL_EXAMPLES
10
12
  else
11
13
  CSV
12
- .read(config[:symbols_file], headers: %i{ id symbol ticksize power months type bcf reports format name})
14
+ .read(config[:symbols_file], headers: SYMBOL_HEADERS)
13
15
  .map{|row| row.to_h }
14
- .map{|row| [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f}; row[:format] = "%#{row[:format]}f"; row }
16
+ .map{|row|
17
+ [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f}
18
+ row[:format] = "%#{row[:format]}f"
19
+ row[:currency] ||= 'USD'
20
+ row[:multiplier] = (row[:power] / row[:ticksize]).round(8)
21
+ row
22
+ }
15
23
  .reject{|row| row[:id].nil? }
16
- .tap{|all| all.select!{|x| x[:type] == type} unless type.nil? }
17
- .tap { |all| all.select! { |x| x[:symbol] == symbol } unless symbol.nil? }
24
+ .tap{ |all|
25
+ args.keys.each { |header|
26
+ unless SYMBOL_HEADERS.include? header
27
+ puts "WARNING in Cotcube::Helpers.symbols: '#{header}' is not a valid symbol header. Skipping..."
28
+ next
29
+ end
30
+ all.select!{|x| x[header] == args[header]} unless args[header].nil?
31
+ return all.first if all.size == 1
32
+ }
33
+ return all
34
+ }
35
+ end
36
+ end
37
+
38
+ def micros(config: init, **args)
39
+ if config[:micros_file].nil?
40
+ MICRO_EXAMPLES
41
+ else
42
+ CSV
43
+ .read(config[:micros_file], headers: SYMBOL_HEADERS)
44
+ .map{|row| row.to_h }
45
+ .map{|row|
46
+ [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f }
47
+ row[:format] = "%#{row[:format]}f"
48
+ row[:currency] ||= 'USD'
49
+ row[:multiplier] = (row[:power] / row[:ticksize]).round(8)
50
+ row
51
+ }
52
+ .reject{|row| row[:id].nil? }
53
+ .tap{ |all|
54
+ args.keys.each { |header|
55
+ unless SYMBOL_HEADERS.include? header
56
+ puts "WARNING in Cotcube::Helpers.micros: '#{header}' is not a valid symbol header. Skipping..."
57
+ next
58
+ end
59
+ all.select!{|x| x[header] == args[header]} unless args[header].nil?
60
+ return all.first if all.size == 1
61
+ }
62
+ return all
63
+ }
18
64
  end
19
65
  end
20
66
 
21
67
  end
68
+
22
69
  end
@@ -6,6 +6,9 @@ require 'active_support'
6
6
  require 'active_support/core_ext/time'
7
7
  require 'active_support/core_ext/numeric'
8
8
  require 'parallel'
9
+ require 'csv'
10
+ require 'yaml'
11
+ require 'json'
9
12
 
10
13
  require_relative 'cotcube-helpers/array_ext'
11
14
  require_relative 'cotcube-helpers/enum_ext'
@@ -13,16 +16,20 @@ require_relative 'cotcube-helpers/hash_ext'
13
16
  require_relative 'cotcube-helpers/range_ext'
14
17
  require_relative 'cotcube-helpers/string_ext'
15
18
  require_relative 'cotcube-helpers/datetime_ext'
19
+ require_relative 'cotcube-helpers/numeric_ext'
16
20
  require_relative 'cotcube-helpers/subpattern'
17
21
  require_relative 'cotcube-helpers/parallelize'
18
22
  require_relative 'cotcube-helpers/simple_output'
19
23
  require_relative 'cotcube-helpers/simple_series_stats'
20
24
  require_relative 'cotcube-helpers/constants'
21
25
  require_relative 'cotcube-helpers/input'
26
+ require_relative 'cotcube-helpers/output'
22
27
  require_relative 'cotcube-helpers/reduce'
23
28
  require_relative 'cotcube-helpers/symbols'
24
29
  require_relative 'cotcube-helpers/init'
25
30
  require_relative 'cotcube-helpers/get_id_set'
31
+ require_relative 'cotcube-helpers/ib_contracts'
32
+ require_relative 'cotcube-helpers/recognition'
26
33
 
27
34
  module Cotcube
28
35
  module Helpers
@@ -34,9 +41,15 @@ module Cotcube
34
41
  :simple_series_stats,
35
42
  :keystroke,
36
43
  :symbols,
44
+ :micros,
37
45
  :get_id_set,
46
+ :get_ib_contract,
47
+ :update_ib_contracts,
48
+ :translate_ib_contract,
38
49
  :init
39
50
 
40
51
  # please not that module_functions of source provided in private files must be published there
41
52
  end
42
53
  end
54
+
55
+ require_relative 'cotcube-helpers/data_client'