cotcube-helpers 0.1.10 → 0.2.0

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: e00fe82276d1be8a2f034759240a0bf820574f6c99de4ec6126bf436bf6a149b
4
- data.tar.gz: e512de0546fd46dcdb356a55265bb8bc874999447c98df67c77b1289a8e2cee4
3
+ metadata.gz: 38df15256f996b15dfeb1755f7464e932a98541e62aa43efda0a264be58bafb2
4
+ data.tar.gz: cd336b6a91442a6d3bbace66ec8a25049ae979ab78eef03d1c8b44a548da8023
5
5
  SHA512:
6
- metadata.gz: 5c0474d0638810ecf7af38f662c80c9a91ac531e5d38e71ff03e7872b559a4add577437c91c435ace0a8b1afc2adb1c1b686272426b6404d138d9d7b3fdfa8af
7
- data.tar.gz: 94b42e991e539ac38e672c0fea5aba4a052e9c5a2b3899bc48d3e855ce23541f04f3b8a8640bb9bb76bd144476e4110d226e4a28298c66ad45480b3d80031a51
6
+ metadata.gz: bcfc562ab118939e7c23dc37b89fee881871552b6b7b17ec7066cb4a2028fa2661e50cd5512725da28332dd1b983d6817d416d31ba05059dd15d824a0e101f7c
7
+ data.tar.gz: 9ca3305120eb630283e303206b764cf9d8dd533ceaf0a2aa044f702fa2f2a01055ff70a4331ec865d854580b1dcf367f365c5addbb988605537c0ee9b9b6fad9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.2.0 (November 07, 2021)
2
+ - added module Candlestick_Recognition
3
+ - added instance_inspect method to 'scan' objects for contents of instance variables
4
+ - symbols: made selection of symbols more versatile by key
5
+ - added headers (:ib_symbol, :internal, :exchange, :currency) to symbol headers as well as symbol examples
6
+ - added scripts/symbols to list (and filter) symbols from command line (put to PATH!)
7
+
1
8
  ## 0.1.10 (October 28, 2021)
2
9
  - added script cron_ruby_wrapper.sh (linkable as /usr/local/bin/cruw.sh)
3
10
  - added numeric ext .with_delimiter to support printing like 123_456_789.00121
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.10
1
+ 0.2.0
@@ -3,13 +3,13 @@
3
3
  module Cotcube
4
4
  module Helpers
5
5
  SYMBOL_EXAMPLES = [
6
- { id: '13874U', symbol: 'ES', ticksize: 0.25, power: 12.5, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'S&P 500 MICRO' },
7
- { id: '209747', symbol: 'NQ', ticksize: 0.25, power: 5.0, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'NASDAQ 100 MICRO' }
6
+ { id: '13874U', symbol: 'ES', ib_symbol: 'ES', internal: 'ES', exchange: 'GLOBEX', currency: 'USD', ticksize: 0.25, power: 12.5, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'S&P 500 MICRO' },
7
+ { id: '209747', symbol: 'NQ', ib_symbol: 'NQ', internal: 'NQ', exchange: 'GLOBEx', currency: 'USD', ticksize: 0.25, power: 5.0, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'NASDAQ 100 MICRO' }
8
8
  ].freeze
9
9
 
10
10
  MICRO_EXAMPLES = [
11
- { id: '13874U', symbol: 'ET', ticksize: 0.25, power: 1.25, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'MICRO S&P 500 MICRO' },
12
- { id: '209747', symbol: 'NM', ticksize: 0.25, power: 0.5, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'MICRO NASDAQ 100 MICRO' }
11
+ { id: '13874U', symbol: 'ET', ib_symbol: 'MES', internal: 'MES', exchange: 'GLOBEX', currency: 'USD', ticksize: 0.25, power: 1.25, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'MICRO S&P 500 MICRO' },
12
+ { id: '209747', symbol: 'NM', ib_symbol: 'MNQ', internal: 'MNQ', exchange: 'GLOBEX', currency: 'USD', ticksize: 0.25, power: 0.5, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'MICRO NASDAQ 100 MICRO' }
13
13
  ].freeze
14
14
 
15
15
  COLORS = %i[light_red light_yellow light_green red yellow green cyan magenta blue].freeze
@@ -0,0 +1,20 @@
1
+ module Cotcube
2
+ module Helpers
3
+
4
+ def instance_inspect(obj, keylength: 20, &block)
5
+ obj.instance_variables.map do |var|
6
+ if block_given?
7
+ block.call(var, obj.instance_variable_get(var))
8
+ else
9
+ puts "#{format "%-#{keylength}s", var
10
+ }: #{obj.instance_variable_get(var).inspect.scan(/.{1,120}/).join( "\n" + ' '*(keylength+2))
11
+ }"
12
+ end
13
+ end
14
+ end
15
+
16
+ module_function :instance_inspect
17
+
18
+ end
19
+ end
20
+
@@ -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
@@ -4,34 +4,64 @@ module Cotcube
4
4
  # Missing top level documentation
5
5
  module Helpers
6
6
 
7
- def symbols(config: init, type: nil, symbol: nil, id: 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[:ticksize] / row[:power]).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| (return all.select { |x| x[:symbol] == symbol }.first ) unless symbol.nil? }
18
- .tap{ |all| (return all.select { |x| x[:id] == id }.first) unless id.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
+ }
19
35
  end
20
36
  end
21
37
 
22
- def micros(config: init, symbol: nil, id: nil)
23
- if config[:micros_file].nil?
24
- MICRO_EXAMPLES
25
- else
26
- CSV
27
- .read(config[:micros_file], headers: %i{ id symbol ticksize power months type bcf reports format name})
28
- .map{|row| row.to_h }
29
- .map{|row| [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f}; row[:format] = "%#{row[:format]}f"; row }
30
- .reject{|row| row[:id].nil? }
31
- .tap{ |all| (return all.select { |x| x[:symbol] == symbol }.first) unless symbol.nil? }
32
- .tap{ |all| (return all.select { |x| x[:id] == id }.first) unless id.nil? }
38
+ def micros(config: init, symbol: nil, id: nil)
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
50
+ }
51
+ .reject{|row| row[:id].nil? }
52
+ .tap{ |all|
53
+ args.keys.each { |header|
54
+ unless SYMBOL_HEADERS.include? header
55
+ puts "WARNING in Cotcube::Helpers.symbols: '#{header}' is not a valid symbol header. Skipping..."
56
+ next
57
+ end
58
+ all.select!{|x| x[header] == args[header]} unless args[header].nil?
59
+ return all.first if all.size == 1
60
+ }
61
+ return all
62
+ }
63
+ end
33
64
  end
34
65
  end
35
- end
36
66
 
37
67
  end
@@ -21,10 +21,12 @@ require_relative 'cotcube-helpers/simple_output'
21
21
  require_relative 'cotcube-helpers/simple_series_stats'
22
22
  require_relative 'cotcube-helpers/constants'
23
23
  require_relative 'cotcube-helpers/input'
24
+ require_relative 'cotcube-helpers/output'
24
25
  require_relative 'cotcube-helpers/reduce'
25
26
  require_relative 'cotcube-helpers/symbols'
26
27
  require_relative 'cotcube-helpers/init'
27
28
  require_relative 'cotcube-helpers/get_id_set'
29
+ require_relative 'cotcube-helpers/recognition'
28
30
 
29
31
  module Cotcube
30
32
  module Helpers
data/scripts/symbols ADDED
@@ -0,0 +1,13 @@
1
+ #!/bin/sh
2
+
3
+ option=$1
4
+ headers='ID,Symbol,ticksize,power,months,type,factor,reports,format,name'
5
+
6
+ if [ -z "${option}" ]
7
+ then
8
+ (echo ${headers} && cat /etc/cotcube/symbols.csv /etc/cotcube/symbols_micros.csv) | sed 's/,/ , /g' | column -s, -t
9
+ else
10
+ (echo ${headers} && cat /etc/cotcube/symbols.csv /etc/cotcube/symbols_micros.csv) | grep -i "$option\|reports,format" | sed 's/,/ , /g' | column -s, -t
11
+ fi
12
+
13
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cotcube-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin L. Tischendorf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-28 00:00:00.000000000 Z
11
+ date: 2021-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -104,8 +104,10 @@ files:
104
104
  - lib/cotcube-helpers/init.rb
105
105
  - lib/cotcube-helpers/input.rb
106
106
  - lib/cotcube-helpers/numeric_ext.rb
107
+ - lib/cotcube-helpers/output.rb
107
108
  - lib/cotcube-helpers/parallelize.rb
108
109
  - lib/cotcube-helpers/range_ext.rb
110
+ - lib/cotcube-helpers/recognition.rb
109
111
  - lib/cotcube-helpers/reduce.rb
110
112
  - lib/cotcube-helpers/simple_output.rb
111
113
  - lib/cotcube-helpers/simple_series_stats.rb
@@ -116,6 +118,7 @@ files:
116
118
  - lib/cotcube-helpers/swig/recognition.rb
117
119
  - lib/cotcube-helpers/symbols.rb
118
120
  - scripts/cron_ruby_wrapper.sh
121
+ - scripts/symbols
119
122
  homepage: https://github.com/donkeybridge/cotcube-helpers
120
123
  licenses:
121
124
  - BSD-4-Clause
@@ -138,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
141
  - !ruby/object:Gem::Version
139
142
  version: '0'
140
143
  requirements: []
141
- rubygems_version: 3.1.2
144
+ rubygems_version: 3.1.6
142
145
  signing_key:
143
146
  specification_version: 4
144
147
  summary: Some helpers and core extensions as part of the Cotcube Suite.