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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/Gemfile +2 -0
- data/VERSION +1 -1
- data/lib/cotcube-helpers/constants.rb +16 -5
- data/lib/cotcube-helpers/data_client.rb +230 -0
- data/lib/cotcube-helpers/expiration.rb +31 -0
- data/lib/cotcube-helpers/get_id_set.rb +9 -7
- data/lib/cotcube-helpers/ib_contracts.rb +69 -0
- data/lib/cotcube-helpers/init.rb +2 -1
- data/lib/cotcube-helpers/numeric_ext.rb +8 -0
- data/lib/cotcube-helpers/orderclient.rb +203 -0
- data/lib/cotcube-helpers/output.rb +20 -0
- data/lib/cotcube-helpers/recognition.rb +509 -0
- data/lib/cotcube-helpers/subpattern.rb +4 -4
- data/lib/cotcube-helpers/symbols.rb +52 -5
- data/lib/cotcube-helpers.rb +13 -0
- data/scripts/collect_market_depth +103 -0
- data/scripts/cron_ruby_wrapper.sh +25 -0
- data/scripts/symbols +13 -0
- metadata +13 -3
@@ -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::
|
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
|
-
|
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:
|
14
|
+
.read(config[:symbols_file], headers: SYMBOL_HEADERS)
|
13
15
|
.map{|row| row.to_h }
|
14
|
-
.map{|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|
|
17
|
-
|
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
|
data/lib/cotcube-helpers.rb
CHANGED
@@ -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'
|