cotcube-helpers 0.1.9 → 0.2.0
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 +26 -0
- data/VERSION +1 -1
- data/lib/cotcube-helpers/array_ext.rb +4 -4
- data/lib/cotcube-helpers/constants.rb +12 -8
- data/lib/cotcube-helpers/datetime_ext.rb +3 -0
- data/lib/cotcube-helpers/get_id_set.rb +44 -0
- data/lib/cotcube-helpers/init.rb +2 -1
- data/lib/cotcube-helpers/numeric_ext.rb +8 -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 +51 -5
- data/lib/cotcube-helpers.rb +11 -2
- data/scripts/cron_ruby_wrapper.sh +25 -0
- data/scripts/symbols +13 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38df15256f996b15dfeb1755f7464e932a98541e62aa43efda0a264be58bafb2
|
4
|
+
data.tar.gz: cd336b6a91442a6d3bbace66ec8a25049ae979ab78eef03d1c8b44a548da8023
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bcfc562ab118939e7c23dc37b89fee881871552b6b7b17ec7066cb4a2028fa2661e50cd5512725da28332dd1b983d6817d416d31ba05059dd15d824a0e101f7c
|
7
|
+
data.tar.gz: 9ca3305120eb630283e303206b764cf9d8dd533ceaf0a2aa044f702fa2f2a01055ff70a4331ec865d854580b1dcf367f365c5addbb988605537c0ee9b9b6fad9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,29 @@
|
|
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
|
+
|
8
|
+
## 0.1.10 (October 28, 2021)
|
9
|
+
- added script cron_ruby_wrapper.sh (linkable as /usr/local/bin/cruw.sh)
|
10
|
+
- added numeric ext .with_delimiter to support printing like 123_456_789.00121
|
11
|
+
- added micros to module
|
12
|
+
- added Helpers.micros to symbols.rb
|
13
|
+
- subpattern: excaping regex pattern to avoid ESC errors
|
14
|
+
- minor change
|
15
|
+
|
16
|
+
## 0.1.9.2 (July 24, 2021)
|
17
|
+
- added missing module_functions
|
18
|
+
- init: minor fix
|
19
|
+
- datetime_ext: added warning comment / TODO, as switch from to daylight time will produce erroneous results
|
20
|
+
- constants: minor fix (typo)
|
21
|
+
- array_ext: added param to provide a default return value if result is an empty array
|
22
|
+
|
23
|
+
## 0.1.9.1 (May 07, 2021)
|
24
|
+
- moved 'get_id_set' to Cotcube::Helpers
|
25
|
+
- minor fix to suppress some warning during build
|
26
|
+
|
1
27
|
## 0.1.9 (May 07, 2021)
|
2
28
|
- added constants, init and symbols to helpers
|
3
29
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -29,10 +29,10 @@ class Array
|
|
29
29
|
# This method iterates over an Array by calling the given block on all 2 consecutive elements
|
30
30
|
# it returns a Array of self.size - 1
|
31
31
|
#
|
32
|
-
def pairwise(ret=nil, &block)
|
32
|
+
def pairwise(ret=nil, empty: nil, &block)
|
33
33
|
raise ArgumentError, 'Array.one_by_one needs an arity of 2 (i.e. |a, b|)' unless block.arity == 2
|
34
|
-
raise ArgumentError, 'Each element of Array should respond to []=, at least the last one fails.'
|
35
|
-
return [] if size <= 1
|
34
|
+
raise ArgumentError, 'Each element of Array should respond to []=, at least the last one fails.' if not ret.nil? and not self.last.respond_to?(:[]=)
|
35
|
+
return empty ||= [] if size <= 1
|
36
36
|
|
37
37
|
each_index.map do |i|
|
38
38
|
next if i.zero?
|
@@ -47,7 +47,7 @@ class Array
|
|
47
47
|
# same as pairwise, but with arity of three
|
48
48
|
def triplewise(ret=nil, &block)
|
49
49
|
raise ArgumentError, 'Array.triplewise needs an arity of 3 (i.e. |a, b, c|)' unless block.arity == 3
|
50
|
-
raise ArgumentError, 'Each element of Array should respond to []=, at least the last one fails.'
|
50
|
+
raise ArgumentError, 'Each element of Array should respond to []=, at least the last one fails.' if not ret.nil? and not self.last.respond_to?(:[]=)
|
51
51
|
return [] if size <= 2
|
52
52
|
|
53
53
|
each_index.map do |i|
|
@@ -1,11 +1,15 @@
|
|
1
|
-
|
2
|
-
zen_string_literal: true
|
1
|
+
#frozen_string_literal: true
|
3
2
|
|
4
3
|
module Cotcube
|
5
|
-
module
|
4
|
+
module Helpers
|
6
5
|
SYMBOL_EXAMPLES = [
|
7
|
-
{ id: '13874U', symbol: '
|
8
|
-
{ id: '209747', symbol: '
|
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
|
+
].freeze
|
9
|
+
|
10
|
+
MICRO_EXAMPLES = [
|
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' }
|
9
13
|
].freeze
|
10
14
|
|
11
15
|
COLORS = %i[light_red light_yellow light_green red yellow green cyan magenta blue].freeze
|
@@ -19,9 +23,9 @@ module Cotcube
|
|
19
23
|
'J' => 4, 'K' => 5, 'M' => 6,
|
20
24
|
'N' => 7, 'Q' => 8, 'U' => 9,
|
21
25
|
'V' => 10, 'X' => 11, 'Z' => 12,
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
1 => 'F', 2 => 'G', 3 => 'H',
|
27
|
+
4 => 'J', 5 => 'K', 6 => 'M',
|
28
|
+
7 => 'N', 8 => 'Q', 9 => 'U',
|
25
29
|
10 => 'V', 11 => 'X', 12 => 'Z' }.freeze
|
26
30
|
|
27
31
|
|
@@ -4,6 +4,9 @@
|
|
4
4
|
class DateTime
|
5
5
|
# based on the fact that sunday is 'wday 0' plus that trading week starts
|
6
6
|
# sunday 0:00 (as trading starts sunday 5pm CT to fit tokyo monday morning)
|
7
|
+
#
|
8
|
+
# TODO: there is a slight flaw, that 1 sunday per year is 1 hour too short and another is 1 hour too long
|
9
|
+
#
|
7
10
|
def to_seconds_since_sunday_morning
|
8
11
|
wday * 86_400 + hour * 3600 + min * 60 + sec
|
9
12
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
def get_id_set(symbol: nil, id: nil, contract: nil, config: init, mini: false, micro: false)
|
7
|
+
micro = mini || micro
|
8
|
+
contract = contract.to_s.upcase if contract.is_a? Symbol
|
9
|
+
id = id.to_s.upcase if id.is_a? Symbol
|
10
|
+
symbol = symbol.to_s.upcase if symbol.is_a? Symbol
|
11
|
+
|
12
|
+
if contract.is_a?(String) && ([2,3,4,5].include? contract.length)
|
13
|
+
c_symbol = contract[0..1]
|
14
|
+
if (not symbol.nil?) && (symbol != c_symbol)
|
15
|
+
raise ArgumentError,
|
16
|
+
"Mismatch between given symbol #{symbol} and contract #{contract}"
|
17
|
+
end
|
18
|
+
|
19
|
+
symbol = c_symbol
|
20
|
+
end
|
21
|
+
|
22
|
+
unless symbol.nil?
|
23
|
+
sym = symbols(symbol: symbol)
|
24
|
+
if sym.nil? || sym[:id].nil?
|
25
|
+
raise ArgumentError,
|
26
|
+
"Could not find match in #{config[:symbols_file]} for given symbol #{symbol}"
|
27
|
+
end
|
28
|
+
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if (not id.nil?) && (sym[:id] != id)
|
29
|
+
|
30
|
+
return micro ? micros(id: sym[:id]) : sym
|
31
|
+
end
|
32
|
+
unless id.nil?
|
33
|
+
sym = symbols(id: id)
|
34
|
+
if sym.nil? || sym[:id].nil?
|
35
|
+
raise ArgumentError,
|
36
|
+
"Could not find match in #{config[:symbols_file]} for given id #{id}"
|
37
|
+
end
|
38
|
+
return micro ? micros(id: sym[:id]) : sym
|
39
|
+
end
|
40
|
+
raise ArgumentError, 'Need :id, :symbol or valid :contract '
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/lib/cotcube-helpers/init.rb
CHANGED
@@ -23,10 +23,11 @@ module Cotcube
|
|
23
23
|
gem_name: nil,
|
24
24
|
debug: false)
|
25
25
|
gem_name ||= self.ancestors.first.to_s
|
26
|
-
config_file_name = "#{gem_name.
|
26
|
+
config_file_name = "#{gem_name.downcase.split('::').last}.yml"
|
27
27
|
config_file = config_path + "/#{config_file_name}"
|
28
28
|
|
29
29
|
if File.exist?(config_file)
|
30
|
+
require 'yaml'
|
30
31
|
config = YAML.load(File.read config_file).transform_keys(&:to_sym)
|
31
32
|
else
|
32
33
|
config = {}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Numeric
|
2
|
+
def with_delimiter(deli=nil)
|
3
|
+
raise ArgumentError, "Param delimiter can't be nil" if deli.nil?
|
4
|
+
pre, post = self.to_s.split('.')
|
5
|
+
pre = pre.chars.to_a.reverse.each_slice(3).map(&:join).join(deli).reverse
|
6
|
+
post.nil? ? pre : [pre,post].join('.')
|
7
|
+
end
|
8
|
+
end
|
@@ -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
|
@@ -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,18 +4,64 @@ 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)
|
15
|
+
.map{|row| row.to_h }
|
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
|
+
}
|
23
|
+
.reject{|row| row[: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
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
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)
|
13
44
|
.map{|row| row.to_h }
|
14
|
-
.map{|row|
|
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
|
+
}
|
15
51
|
.reject{|row| row[:id].nil? }
|
16
|
-
.tap{|all|
|
17
|
-
|
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
|
+
}
|
18
63
|
end
|
19
64
|
end
|
65
|
+
end
|
20
66
|
|
21
67
|
end
|
data/lib/cotcube-helpers.rb
CHANGED
@@ -6,6 +6,7 @@ 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'
|
9
10
|
|
10
11
|
require_relative 'cotcube-helpers/array_ext'
|
11
12
|
require_relative 'cotcube-helpers/enum_ext'
|
@@ -13,24 +14,32 @@ require_relative 'cotcube-helpers/hash_ext'
|
|
13
14
|
require_relative 'cotcube-helpers/range_ext'
|
14
15
|
require_relative 'cotcube-helpers/string_ext'
|
15
16
|
require_relative 'cotcube-helpers/datetime_ext'
|
17
|
+
require_relative 'cotcube-helpers/numeric_ext'
|
16
18
|
require_relative 'cotcube-helpers/subpattern'
|
17
19
|
require_relative 'cotcube-helpers/parallelize'
|
18
20
|
require_relative 'cotcube-helpers/simple_output'
|
19
21
|
require_relative 'cotcube-helpers/simple_series_stats'
|
22
|
+
require_relative 'cotcube-helpers/constants'
|
20
23
|
require_relative 'cotcube-helpers/input'
|
24
|
+
require_relative 'cotcube-helpers/output'
|
21
25
|
require_relative 'cotcube-helpers/reduce'
|
22
|
-
require_relative 'cotcube-helpers/constants'
|
23
26
|
require_relative 'cotcube-helpers/symbols'
|
24
|
-
require_relative 'cotcube-
|
27
|
+
require_relative 'cotcube-helpers/init'
|
28
|
+
require_relative 'cotcube-helpers/get_id_set'
|
29
|
+
require_relative 'cotcube-helpers/recognition'
|
25
30
|
|
26
31
|
module Cotcube
|
27
32
|
module Helpers
|
28
33
|
module_function :sub,
|
29
34
|
:parallelize,
|
35
|
+
:config_path,
|
36
|
+
:config_prefix,
|
30
37
|
:reduce,
|
31
38
|
:simple_series_stats,
|
32
39
|
:keystroke,
|
33
40
|
:symbols,
|
41
|
+
:micros,
|
42
|
+
:get_id_set,
|
34
43
|
:init
|
35
44
|
|
36
45
|
# please not that module_functions of source provided in private files must be published there
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
export rubyenv=/home/pepe/.rvm/environments/default
|
4
|
+
|
5
|
+
. $rubyenv
|
6
|
+
cd /home/pepe/GEMS/${1}
|
7
|
+
export LC_ALL="en_US.utf8"
|
8
|
+
|
9
|
+
ruby ${2} ${3} ${4} ${5} ${6}
|
10
|
+
|
11
|
+
|
12
|
+
exit
|
13
|
+
for testing run
|
14
|
+
env - `cat /home/pepe/bin/cron_ruby_wrapper.sh | tail -n 6` /bin/bash
|
15
|
+
|
16
|
+
HOME=/home/pepe
|
17
|
+
LOGNAME=pepe
|
18
|
+
PATH=/usr/bin:/bin
|
19
|
+
LANG=en_US.UTF-8
|
20
|
+
SHELL=/bin/sh
|
21
|
+
PWD=/home/pepe
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
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.
|
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-
|
11
|
+
date: 2021-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -99,11 +99,15 @@ files:
|
|
99
99
|
- lib/cotcube-helpers/constants.rb
|
100
100
|
- lib/cotcube-helpers/datetime_ext.rb
|
101
101
|
- lib/cotcube-helpers/enum_ext.rb
|
102
|
+
- lib/cotcube-helpers/get_id_set.rb
|
102
103
|
- lib/cotcube-helpers/hash_ext.rb
|
103
104
|
- lib/cotcube-helpers/init.rb
|
104
105
|
- lib/cotcube-helpers/input.rb
|
106
|
+
- lib/cotcube-helpers/numeric_ext.rb
|
107
|
+
- lib/cotcube-helpers/output.rb
|
105
108
|
- lib/cotcube-helpers/parallelize.rb
|
106
109
|
- lib/cotcube-helpers/range_ext.rb
|
110
|
+
- lib/cotcube-helpers/recognition.rb
|
107
111
|
- lib/cotcube-helpers/reduce.rb
|
108
112
|
- lib/cotcube-helpers/simple_output.rb
|
109
113
|
- lib/cotcube-helpers/simple_series_stats.rb
|
@@ -113,6 +117,8 @@ files:
|
|
113
117
|
- lib/cotcube-helpers/swig/fill_x.rb
|
114
118
|
- lib/cotcube-helpers/swig/recognition.rb
|
115
119
|
- lib/cotcube-helpers/symbols.rb
|
120
|
+
- scripts/cron_ruby_wrapper.sh
|
121
|
+
- scripts/symbols
|
116
122
|
homepage: https://github.com/donkeybridge/cotcube-helpers
|
117
123
|
licenses:
|
118
124
|
- BSD-4-Clause
|
@@ -135,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
141
|
- !ruby/object:Gem::Version
|
136
142
|
version: '0'
|
137
143
|
requirements: []
|
138
|
-
rubygems_version: 3.1.
|
144
|
+
rubygems_version: 3.1.6
|
139
145
|
signing_key:
|
140
146
|
specification_version: 4
|
141
147
|
summary: Some helpers and core extensions as part of the Cotcube Suite.
|