cotcube-helpers 0.1.1
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 +7 -0
- data/.irbrc.rb +12 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +5 -0
- data/VERSION +1 -0
- data/cotcube-helpers.gemspec +35 -0
- data/lib/cotcube-helpers.rb +31 -0
- data/lib/cotcube-helpers/array_ext.rb +53 -0
- data/lib/cotcube-helpers/enum_ext.rb +11 -0
- data/lib/cotcube-helpers/hash_ext.rb +15 -0
- data/lib/cotcube-helpers/input.rb +44 -0
- data/lib/cotcube-helpers/parallelize.rb +33 -0
- data/lib/cotcube-helpers/range_ext.rb +52 -0
- data/lib/cotcube-helpers/simple_output.rb +29 -0
- data/lib/cotcube-helpers/string_ext.rb +9 -0
- data/lib/cotcube-helpers/subpattern.rb +46 -0
- data/lib/cotcube-helpers/swig/date.rb +66 -0
- data/lib/cotcube-helpers/swig/fill_x.rb +57 -0
- data/lib/cotcube-helpers/swig/recognition.rb +330 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 819dae89b6428b3cec4226d40f4afa3b790108b2e7a0f8ff9784b49532a894e9
|
4
|
+
data.tar.gz: 2e4823aa3a4f86efa8d94ff07be306cb991a7bd08504cae0209025127e67d472
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 46647ed87173997191a22b05072e0ce4b53bba9be4a222d86a92605bf35d9c3d4d33ed0381c6ab3e46106e21851d10f4b9b3b178136bf780bdcf89ce055cc469
|
7
|
+
data.tar.gz: 7acdf173d676c7db61e6e2a1aa3111b22ae00c55768611d7355b4f13216c225f9e5ef9204a23796694b95d21d955e7a53e72d3b62b390e3735786aceb0b67801
|
data/.irbrc.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
def verbose_toggle
|
2
|
+
irb_context.echo ? irb_context.echo = false : irb_context.echo = true
|
3
|
+
end
|
4
|
+
|
5
|
+
alias vt verbose_toggle
|
6
|
+
|
7
|
+
$debug = true
|
8
|
+
IRB.conf[:USE_MULTILINE] = false
|
9
|
+
# require 'bundler'
|
10
|
+
# Bundler.require
|
11
|
+
|
12
|
+
require_relative 'lib/cotcube-helpers'
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'cotcube-helpers'
|
5
|
+
spec.version = File.read("#{__dir__}/VERSION")
|
6
|
+
spec.authors = ['Benjamin L. Tischendorf']
|
7
|
+
spec.email = ['donkeybridge@jtown.eu']
|
8
|
+
|
9
|
+
spec.summary = 'Some helpers and core extensions as part of the Cotcube Suite.'
|
10
|
+
spec.description = 'Some helpers and core extensions as part of the Cotcube Suite...'
|
11
|
+
|
12
|
+
spec.homepage = 'https://github.com/donkeybridge/'+ spec.name
|
13
|
+
spec.license = 'BSD-4-Clause'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('~> 2.7')
|
15
|
+
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
17
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
18
|
+
spec.metadata['changelog_uri'] = spec.homepage + '/CHANGELOG.md'
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = 'bin'
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ['lib']
|
28
|
+
|
29
|
+
spec.add_dependency 'activesupport'
|
30
|
+
|
31
|
+
|
32
|
+
spec.add_development_dependency 'rake'
|
33
|
+
spec.add_development_dependency 'rspec', '~>3.6'
|
34
|
+
spec.add_development_dependency 'yard', '~>0.9'
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/time'
|
5
|
+
require 'active_support/core_ext/numeric'
|
6
|
+
require 'parallel'
|
7
|
+
|
8
|
+
require_relative 'cotcube-helpers/array_ext'
|
9
|
+
require_relative 'cotcube-helpers/enum_ext'
|
10
|
+
require_relative 'cotcube-helpers/hash_ext'
|
11
|
+
require_relative 'cotcube-helpers/range_ext'
|
12
|
+
require_relative 'cotcube-helpers/string_ext'
|
13
|
+
require_relative 'cotcube-helpers/subpattern.rb'
|
14
|
+
require_relative 'cotcube-helpers/parallelize'
|
15
|
+
require_relative 'cotcube-helpers/simple_output'
|
16
|
+
require_relative 'cotcube-helpers/input'
|
17
|
+
|
18
|
+
|
19
|
+
module Cotcube
|
20
|
+
module Helpers
|
21
|
+
|
22
|
+
module_function :sub,
|
23
|
+
:parallelize,
|
24
|
+
:keystroke
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
# please not that module_functions of source provided in private files must be published there
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
# returns nil if the compacted array is empty, otherwise returns the compacted array
|
4
|
+
def compact_or_nil(*args, &block)
|
5
|
+
return nil if self.compact == []
|
6
|
+
yield self.compact
|
7
|
+
end
|
8
|
+
|
9
|
+
# sorts by a given attribute and then returns groups of where this attribute is equal
|
10
|
+
# .... seems like some_array.group_by(&attr).values
|
11
|
+
def split_by(attrib)
|
12
|
+
res = []
|
13
|
+
sub = []
|
14
|
+
self.sort_by(&attrib).each do |elem|
|
15
|
+
if sub.empty? or sub.last[attrib] == elem[attrib]
|
16
|
+
sub << elem
|
17
|
+
else
|
18
|
+
res << sub
|
19
|
+
sub = [ elem ]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
res << sub
|
23
|
+
res
|
24
|
+
end
|
25
|
+
|
26
|
+
# This method iterates over an Array by calling the given block on all 2 consecutive elements
|
27
|
+
# it returns a Array of self.size - 1
|
28
|
+
#
|
29
|
+
def pairwise(&block)
|
30
|
+
raise ArgumentError, 'Array.one_by_one needs an arity of 2 (i.e. |a, b|)' unless block.arity == 2
|
31
|
+
return [] if size <= 1
|
32
|
+
|
33
|
+
each_with_index.map do |_, i|
|
34
|
+
next if i.zero?
|
35
|
+
|
36
|
+
block.call(self[i - 1], self[i])
|
37
|
+
end.compact
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method :one_by_one, :pairwise
|
41
|
+
|
42
|
+
# same as pairwise, but with arity of three
|
43
|
+
def triplewise(&block)
|
44
|
+
raise ArgumentError, 'Array.triplewise needs an arity of 3 (i.e. |a, b, c|)' unless block.arity == 3
|
45
|
+
return [] if size <= 2
|
46
|
+
|
47
|
+
each_with_index.map do |_, i|
|
48
|
+
next if i < 2
|
49
|
+
|
50
|
+
block.call(self[i - 2], self[i-1], self[i])
|
51
|
+
end.compact
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def keys_to_sym
|
4
|
+
self.keys.each do |key|
|
5
|
+
case self[key].class.to_s
|
6
|
+
when "Hash"
|
7
|
+
self[key].keys_to_sym
|
8
|
+
when "Array"
|
9
|
+
self[key].map {|el| el.is_a?(Hash) ? el.keys_to_sym : el}
|
10
|
+
end
|
11
|
+
self[key.to_sym] = self.delete(key)
|
12
|
+
end
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Cotcube
|
2
|
+
module Helpers
|
3
|
+
def keystroke(quit: false)
|
4
|
+
begin
|
5
|
+
# save previous state of stty
|
6
|
+
old_state = `stty -g`
|
7
|
+
# disable echoing and enable raw (not having to press enter)
|
8
|
+
system "stty raw -echo"
|
9
|
+
c = STDIN.getc.chr
|
10
|
+
# gather next two characters of special keys
|
11
|
+
if(c=="\e")
|
12
|
+
extra_thread = Thread.new{
|
13
|
+
c = c + STDIN.getc.chr
|
14
|
+
c = c + STDIN.getc.chr
|
15
|
+
}
|
16
|
+
# wait just long enough for special keys to get swallowed
|
17
|
+
extra_thread.join(0.00001)
|
18
|
+
# kill thread so not-so-long special keys don't wait on getc
|
19
|
+
extra_thread.kill
|
20
|
+
end
|
21
|
+
rescue => ex
|
22
|
+
puts "#{ex.class}: #{ex.message}"
|
23
|
+
puts ex.backtrace
|
24
|
+
ensure
|
25
|
+
# restore previous state of stty
|
26
|
+
system "stty #{old_state}"
|
27
|
+
end
|
28
|
+
c.each_byte do |x|
|
29
|
+
case x
|
30
|
+
when 3
|
31
|
+
puts "Strg-C captured, exiting..."
|
32
|
+
quit ? exit : (return true)
|
33
|
+
when 13
|
34
|
+
return "_return_"
|
35
|
+
when 27
|
36
|
+
puts "ESCAPE gathered"
|
37
|
+
return "_esc_"
|
38
|
+
else
|
39
|
+
return c
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Cotcube
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
def parallelize(ary, opts = {}, &block)
|
5
|
+
processes = opts[:processes].nil? ? 1 : opts[:processes]
|
6
|
+
threads_per_process = opts[:threads ].nil? ? 1 : opts[:threads]
|
7
|
+
progress = opts[:progress ].nil? ? "" : opts[:progress]
|
8
|
+
chunks = []
|
9
|
+
if processes == 0 or processes == 1
|
10
|
+
r = Parallel.map(ary, in_threads: threads_per_process) {|u| v = yield(u); v}
|
11
|
+
elsif [0,1].include?(threads_per_process)
|
12
|
+
r = Parallel.map(ary, in_processes: processes) {|u| v = yield(u)}
|
13
|
+
else
|
14
|
+
ary.each_slice(threads_per_process) {|chunk| chunks << chunk }
|
15
|
+
if progress == ""
|
16
|
+
r = Parallel.map(chunks, :in_processes => processes) do |chunk|
|
17
|
+
Parallel.map(chunk, in_threads: threads_per_process) do |unit|
|
18
|
+
yield(unit)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
else
|
22
|
+
r = Parallel.map(ary, :progress => progress, :in_processes => processes) do |chunk|
|
23
|
+
Parallel.map(chunk, in_threads: threads_per_process) do |unit|
|
24
|
+
yield(unit)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
return r
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Range
|
2
|
+
def to_time_intervals(timezone: Time.find_zone('America/Chicago'), step:, ranges: nil)
|
3
|
+
|
4
|
+
raise ArgumentError, ":step must be a 'ActiveSupport::Duration', like '15.minutes', but '#{step}' is a '#{step.class}'" unless step.is_a? ActiveSupport::Duration
|
5
|
+
|
6
|
+
valid_classes = [ ActiveSupport::TimeWithZone, Time, Date, DateTime ]
|
7
|
+
raise "Expecting 'ActiveSupport::TimeZone' for :timezone, got '#{timezone.class}" unless timezone.is_a? ActiveSupport::TimeZone
|
8
|
+
starting = self.begin
|
9
|
+
ending = self.end
|
10
|
+
starting = timezone.parse(starting) if starting.is_a? String
|
11
|
+
ending = timezone.parse(ending) if ending.is_a? String
|
12
|
+
raise ArgumentError, ":self.begin seems not to be proper time value: #{starting} is a #{starting.class}" unless valid_classes.include? starting.class
|
13
|
+
raise ArgumentError, ":self.end seems not to be proper time value: #{ending} is a #{ending.class}" unless valid_classes.include? ending.class
|
14
|
+
|
15
|
+
##### The following is the actual big magic line:
|
16
|
+
#
|
17
|
+
result = (starting.to_time.to_i..ending.to_time.to_i).step(step).to_a.map{|x| timezone.at(x)}
|
18
|
+
#
|
19
|
+
####################<3
|
20
|
+
|
21
|
+
|
22
|
+
# with step.to_i >= 86400 we are risking stuff like 25.hours to return bogus
|
23
|
+
# also notice: When using this with swaps, you will loose 1 hour (#f**k_it)
|
24
|
+
#
|
25
|
+
# eventually, for dailies and above, return M-F default, return S-S when forced by empty ranges
|
26
|
+
return result.select{|x| (not ranges.nil? and ranges.empty?) ? true : (not [6,0].include?(x.wday)) } if step.to_i >= 86400
|
27
|
+
|
28
|
+
# sub-day is checked for DST and filtered along provided ranges
|
29
|
+
starting_with_dst = result.first.dst?
|
30
|
+
seconds_since_sunday_morning = lambda {|x| x.wday * 86400 + x.hour * 3600 + x.min * 60 + x.sec}
|
31
|
+
ranges ||= [
|
32
|
+
61200..143999,
|
33
|
+
147600..230399,
|
34
|
+
234000..316799,
|
35
|
+
320400..403199,
|
36
|
+
406800..489599
|
37
|
+
]
|
38
|
+
|
39
|
+
# if there was a change towards daylight saving time, substract 1 hour, otherwise add 1 hour
|
40
|
+
result.map! do |time|
|
41
|
+
if not starting_with_dst and time.dst?
|
42
|
+
time - 3600
|
43
|
+
elsif starting_with_dst and not time.dst?
|
44
|
+
time + 3600
|
45
|
+
else
|
46
|
+
time
|
47
|
+
end
|
48
|
+
end
|
49
|
+
return result if ranges.empty?
|
50
|
+
result.select{|x| ranges.map{|r| r.include? seconds_since_sunday_morning.call(x)}.reduce(:|) }
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
module Helpers
|
5
|
+
# SimpleOutput is a very basic outputhandler, which is actually only there to mock output handling until
|
6
|
+
# a more sophisticated solution is available (e.g. OutPutHandler gem is reworked and tested )
|
7
|
+
class SimpleOutput
|
8
|
+
# Aliasing puts and print, as they are included / inherited (?) from IO
|
9
|
+
alias_method :superputs, :puts # rubocop:disable Style/Alias
|
10
|
+
# Aliasing puts and print, as they are included / inherited (?) from IO
|
11
|
+
alias_method :superprint, :print # rubocop:disable Style/Alias
|
12
|
+
|
13
|
+
# ...
|
14
|
+
def puts(msg)
|
15
|
+
superputs msg
|
16
|
+
end
|
17
|
+
|
18
|
+
# ...
|
19
|
+
def print(msg)
|
20
|
+
superprint msg
|
21
|
+
end
|
22
|
+
|
23
|
+
# The source expects methods with exclamation mark (for unbuffered output) -- although it makes no sense
|
24
|
+
# here, we need to provide the syntax for later.
|
25
|
+
alias puts! puts
|
26
|
+
alias print! print
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cotcube
|
4
|
+
module Helpers
|
5
|
+
# sub (should be 'subpattern', but too long) is for use in case / when statements
|
6
|
+
# it returns a lambda, that checks the case'd expression for matching subpattern
|
7
|
+
# based on the the giving minimum. E.g. 'a', 'ab' .. 'abcd' will match sub(1){'abcd'}
|
8
|
+
# but only 'abc' and 'abcd' will match sub(3){'abcd'}
|
9
|
+
#
|
10
|
+
# The recommended use within evaluating user input, where abbreviation of incoming commands
|
11
|
+
# is desirable (h for hoover and hyper, what will translate to sub(2){'hoover'} and sub(2){hyper})
|
12
|
+
#
|
13
|
+
# To extend functionality even more, it is possible to send a group of patterns to, like
|
14
|
+
# sub(2){[:hyper,:mega]}, what will respond truthy to "hy" and "meg" but not to "m" or "hypo"
|
15
|
+
def sub(minimum = 1)
|
16
|
+
pattern = yield
|
17
|
+
case pattern
|
18
|
+
when String, Symbol, NilClass
|
19
|
+
pattern = pattern.to_s
|
20
|
+
lambda do |x|
|
21
|
+
return false if x.nil? || (x.size < minimum)
|
22
|
+
|
23
|
+
return ((pattern =~ /^#{x}/i).nil? ? false : true)
|
24
|
+
end
|
25
|
+
when Array
|
26
|
+
pattern.map do |x|
|
27
|
+
unless [String, Symbol, NilClass].include? x.class
|
28
|
+
raise TypeError, "Unsupported class '#{x.class}' for '#{x}'in pattern '#{pattern}'."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
lambda do |x|
|
32
|
+
pattern.each do |sub|
|
33
|
+
sub = sub.to_s
|
34
|
+
return false if x.size < minimum
|
35
|
+
|
36
|
+
result = ((sub =~ /^#{x}/i).nil? ? false : true)
|
37
|
+
return true if result
|
38
|
+
end
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
else
|
42
|
+
raise TypeError, "Unsupported class #{pattern.class} in Cotcube::Core::sub"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Time
|
2
|
+
def to_centi
|
3
|
+
(self.to_f * 1000.0).to_i
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_part(x=100)
|
7
|
+
(self.to_f * x.to_f).to_i % x
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class Date
|
13
|
+
def abbr_dayname
|
14
|
+
ABBR_DAYNAMES[self.wday]
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_string
|
18
|
+
self.strftime("%Y-%m-%d")
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_timestring
|
22
|
+
self.strftime("%Y-%m-%d-%H-%M-%S")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class String
|
28
|
+
def to_date
|
29
|
+
Date::strptime(self, "%Y-%m-%d")
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_time
|
33
|
+
DateTime::strptime(self, "%s")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Integer
|
38
|
+
def to_tz
|
39
|
+
return "+0000" if self == 0
|
40
|
+
sign = self > 0 ? "+" : "-"
|
41
|
+
value = self.abs
|
42
|
+
hours = (value / 3600.to_f).floor
|
43
|
+
minutes = ((value - 3600 * hours) / 60).floor
|
44
|
+
"#{sign}#{hours>9 ? "" : "0"}#{hours}#{minutes<10 ? "0" : "" }#{minutes}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_tod(zone="UTC")
|
48
|
+
self.to_time
|
49
|
+
#in_time_zone(zone).
|
50
|
+
#to_time_of_day
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_tod_i
|
54
|
+
(self / 100).to_time.strftime("%H%M%S").to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_date
|
58
|
+
Date::strptime(self.to_s, "%s")
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_time
|
62
|
+
DateTime.strptime(self.to_s, "%s")
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Tritangent
|
2
|
+
module Helpers
|
3
|
+
include ::Tod
|
4
|
+
#Notice the first use of keyword arguments here ;)
|
5
|
+
def fill_x (base:, discretion:, time:, attr:, maintenance: nil, zone: nil)
|
6
|
+
zone ||= case base[0][time].zone
|
7
|
+
when 'CDT','CT','CST'
|
8
|
+
"America/Chicago"
|
9
|
+
when 'EDT', 'ET', 'EST'
|
10
|
+
"AMerica/New_York"
|
11
|
+
else
|
12
|
+
"UTC"
|
13
|
+
end
|
14
|
+
|
15
|
+
maintenance ||= case zone
|
16
|
+
when "America/Chicago"
|
17
|
+
{ 0 => Shift.new(Tod::TimeOfDay.new( 0), Tod::TimeOfDay.new(17), true),
|
18
|
+
1 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
19
|
+
2 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
20
|
+
3 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
21
|
+
4 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
22
|
+
5 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new( 0), true)
|
23
|
+
}
|
24
|
+
when "America/New_York"
|
25
|
+
{ 0 => Tod::Shift.new(Tod::TimeOfDay.new( 0), Tod::TimeOfDay.new(17), true),
|
26
|
+
1 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
27
|
+
2 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
28
|
+
3 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
29
|
+
4 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new(17), true),
|
30
|
+
5 => Tod::Shift.new(Tod::TimeOfDay.new(16), Tod::TimeOfDay.new( 0), true)
|
31
|
+
}
|
32
|
+
else
|
33
|
+
raise "Tritangent::Helpers.fill_x needs :maintenance or :zone"
|
34
|
+
end
|
35
|
+
|
36
|
+
tz = Time.find_zone zone
|
37
|
+
exchange_open = lambda do |t0|
|
38
|
+
current_week_day = t0.wday
|
39
|
+
return false if current_week_day == 6
|
40
|
+
not maintenance[current_week_day].include?(Tod::TimeOfDay(t0.in_time_zone(zone)))
|
41
|
+
end
|
42
|
+
result = [] # [ { time => base.first[time], attr => base.first[attr] } ]
|
43
|
+
i = 0
|
44
|
+
timer = base.first[time]
|
45
|
+
while not base[i].nil?
|
46
|
+
if timer == base[i][time]
|
47
|
+
result << { time => timer, attr => base[i][attr] }
|
48
|
+
i += 1
|
49
|
+
else
|
50
|
+
result << { time => timer } if exchange_open.call(timer)
|
51
|
+
end
|
52
|
+
timer += discretion
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,330 @@
|
|
1
|
+
module Candlestick_Recognition
|
2
|
+
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def candles(candles, debug = false)
|
6
|
+
candles.each_with_index do |bar, i|
|
7
|
+
# 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
|
8
|
+
begin
|
9
|
+
rel = (bar[:high] - bar[:low]) * 0.1
|
10
|
+
rescue
|
11
|
+
puts "Warning, found inappropriate bar".light_white + " #{bar}"
|
12
|
+
raise
|
13
|
+
end
|
14
|
+
bar[:rel] = rel
|
15
|
+
|
16
|
+
bar[:upper] = [bar[:open], bar[:close]].max
|
17
|
+
bar[:lower] = [bar[:open], bar[:close]].min
|
18
|
+
bar[:bar_size] = (bar[:high] - bar[:low])
|
19
|
+
bar[:body_size] = (bar[:open] - bar[:close]).abs
|
20
|
+
bar[:lower_wick] = (bar[:lower] - bar[:low])
|
21
|
+
bar[:upper_wick] = (bar[:high] - bar[:upper])
|
22
|
+
bar.each{|k,v| bar[k] = v.round(8) if v.is_a? Float}
|
23
|
+
|
24
|
+
# a doji's open and close are same (or only differ by rel)
|
25
|
+
bar[:doji] = true if bar[:body_size] <= rel and bar[:dist] >= 3
|
26
|
+
bar[:tiny] = true if bar[:dist] <= 5
|
27
|
+
|
28
|
+
next if bar[:tiny]
|
29
|
+
|
30
|
+
bar[:bullish] = true if not bar[:doji] and bar[:close] > bar[:open]
|
31
|
+
bar[:bearish] = true if not bar[:doji] and bar[:close] < bar[:open]
|
32
|
+
|
33
|
+
bar[:spinning_top] = true if bar[:body_size] <= bar[:bar_size] / 4 and
|
34
|
+
bar[:lower_wick] >= bar[:bar_size] / 4 and
|
35
|
+
bar[:upper_wick] >= bar[:bar_size] / 4
|
36
|
+
|
37
|
+
# a marubozu open at high or low and closes at low or high
|
38
|
+
bar[:marubozu] = true if bar[:upper_wick] < rel and bar[:lower_wick] < rel
|
39
|
+
|
40
|
+
# a bar is considered bearish if it has at least a dist of 5 ticks and it's close it near high (low resp)
|
41
|
+
bar[:bullish_close] = true if (bar[:high] - bar[:close]) <= rel and not bar[:marubozu]
|
42
|
+
bar[:bearish_close] = true if (bar[:close] - bar[:low]) <= rel and not bar[:marubozu]
|
43
|
+
|
44
|
+
# 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
|
45
|
+
# TODO
|
46
|
+
|
47
|
+
end
|
48
|
+
candles
|
49
|
+
end
|
50
|
+
|
51
|
+
def comparebars(prev, curr)
|
52
|
+
bullishscore = 0
|
53
|
+
bearishscore = 0
|
54
|
+
bullishscore += 1 if prev[:high] <= curr[:high]
|
55
|
+
bullishscore += 1 if prev[:low] <= curr[:low]
|
56
|
+
bullishscore += 1 if prev[:close] <= curr[:close]
|
57
|
+
bearishscore += 1 if prev[:close] >= curr[:close]
|
58
|
+
bearishscore += 1 if prev[:low] >= curr[:low]
|
59
|
+
bearishscore += 1 if prev[:high] >= curr[:high]
|
60
|
+
r = {}
|
61
|
+
r[:bullish] = true if bullishscore >= 2
|
62
|
+
r[:bearish] = true if bearishscore >= 2
|
63
|
+
return r
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def patterns(candles, debug = false)
|
68
|
+
candles.each_with_index do |bar, i|
|
69
|
+
if i.zero?
|
70
|
+
bar[:slope] = 0
|
71
|
+
next
|
72
|
+
end
|
73
|
+
pprev= candles[i-2]
|
74
|
+
prev = candles[i-1]
|
75
|
+
succ = candles[i+1]
|
76
|
+
|
77
|
+
bar[:tr] = [ bar[:high], prev[:close] ].max - [bar[:low], prev[:close]].min
|
78
|
+
bar[:ranges] = prev[:ranges].nil? ? [ bar[:tr] ] : prev[:ranges] + [ bar[:tr] ]
|
79
|
+
bar[:ranges].shift while bar[:ranges].size > 5
|
80
|
+
bar[:atr] = (bar[:ranges].reduce(:+) / bar[:ranges].size.to_f).round(8) if bar[:ranges].size > 0
|
81
|
+
|
82
|
+
bar[:vranges] = prev[:vranges].nil? ? [ bar[:volume] ] : prev[:vranges] + [ bar[:volume] ]
|
83
|
+
bar[:vranges].shift while bar[:vranges].size > 5
|
84
|
+
bar[:vavg] = (bar[:vranges].reduce(:+) / bar[:vranges].size.to_f).round if bar[:vranges].compact.size > 0
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
# VOLUME
|
89
|
+
if bar[:volume] > bar[:vavg] * 1.3
|
90
|
+
bar[:BREAKN_volume] = true
|
91
|
+
elsif bar[:volume] >= bar[:vavg] * 1.1
|
92
|
+
bar[:RISING_volume] = true
|
93
|
+
elsif bar[:volume] < bar[:vavg] * 0.7
|
94
|
+
bar[:FAINTN_volume] = true
|
95
|
+
elsif bar[:volume] <= bar[:vavg] * 0.9
|
96
|
+
bar[:FALLIN_volume] = true
|
97
|
+
else
|
98
|
+
bar[:STABLE_volume] = true
|
99
|
+
end
|
100
|
+
|
101
|
+
# GAPS
|
102
|
+
bar[:bodygap] = true if bar[:lower] > prev[:upper] or bar[:upper] < prev[:lower]
|
103
|
+
bar[:gap] = true if bar[:low] > prev[:high] or bar[:high] < prev[:low]
|
104
|
+
|
105
|
+
|
106
|
+
bar[:slope] = slopescore(pprev, prev, bar, debug)
|
107
|
+
|
108
|
+
# UPPER_PIVOTs define by having higher highs and higher lows than their neighor
|
109
|
+
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
|
110
|
+
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
|
111
|
+
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]
|
112
|
+
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]
|
113
|
+
|
114
|
+
# stopping volume is defined as high volume candle during downtrend then closes above mid candle (i.e. lower_wick > body_size)
|
115
|
+
bar[:stopping_volume] = true if bar[:BREAKN_volume] and prev[:slope] < -5 and bar[:lower_wick] >= bar[:body_size]
|
116
|
+
bar[:stopping_volume] = true if bar[:BREAKN_volume] and prev[:slope] > 5 and bar[:upper_wick] >= bar[:body_size]
|
117
|
+
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]
|
118
|
+
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]
|
119
|
+
|
120
|
+
|
121
|
+
###################################
|
122
|
+
# SINGLE CANDLE PATTERNS
|
123
|
+
###################################
|
124
|
+
|
125
|
+
# 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
|
126
|
+
bar[:HAMMER] = true if bar[:upper_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 3 and bar[ :slope] <= -6
|
127
|
+
# same shape, but found at a raising slope without the need to gap away
|
128
|
+
bar[:HANGING_MAN] = true if bar[:upper_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 3 and prev[:slope] >= 6
|
129
|
+
|
130
|
+
# a shooting star is the inverse of the hammer, while the inverted hammer is the inverse of the hanging man
|
131
|
+
bar[:SHOOTING_STAR] = true if bar[:lower_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 2.5 and bar[ :slope] >= 6
|
132
|
+
bar[:INVERTED_HAMMER] = true if bar[:lower_wick] <= bar[:rel] and bar[:body_size] <= bar[:bar_size] / 3 and prev[:slope] <= -6
|
133
|
+
|
134
|
+
# a star is simply gapping away the preceding slope
|
135
|
+
bar[:STAR] = true if ((bar[:lower] >= prev[:upper] and bar[:slope] >= 6) or (bar[:upper] <= prev[:lower] and bar[:slope] <= -6)) and not bar[:doji]
|
136
|
+
bar[:DOJI_STAR] = true if ((bar[:lower] >= prev[:upper] and bar[:slope] >= 6) or (bar[:upper] <= prev[:lower] and bar[:slope] <= -6)) and bar[:doji]
|
137
|
+
|
138
|
+
# a belthold is has a gap in the open, but reverses strong
|
139
|
+
bar[:BULLISH_BELTHOLD] = true if bar[:lower_wick] <= bar[:rel] and bar[:body_size] >= bar[:bar_size] / 2 and
|
140
|
+
prev[:slope] <= -4 and bar[:lower] <= prev[:low ] and bar[:bullish] and not prev[:bullish] and bar[:bar_size] >= prev[:bar_size]
|
141
|
+
bar[:BEARISH_BELTHOLD] = true if bar[:upper_wick] <= bar[:rel] and bar[:body_size] >= bar[:bar_size] / 2 and
|
142
|
+
prev[:slope] >= -4 and bar[:upper] <= prev[:high] and bar[:bearish] and not prev[:bearish] and bar[:bar_size] >= prev[:bar_size]
|
143
|
+
|
144
|
+
|
145
|
+
###################################
|
146
|
+
# DUAL CANDLE PATTERNS
|
147
|
+
###################################
|
148
|
+
|
149
|
+
|
150
|
+
# ENGULFINGS
|
151
|
+
bar[:BULLISH_ENGULFING] = true if bar[:bullish] and prev[:bearish] and bar[:lower] <= prev[:lower] and bar[:upper] > prev[:upper] and prev[:slope] <= -6
|
152
|
+
bar[:BEARISH_ENGULFING] = true if bar[:bearish] and prev[:bullish] and bar[:lower] < prev[:lower] and bar[:upper] >= prev[:upper] and prev[:slope] >= 6
|
153
|
+
|
154
|
+
|
155
|
+
# DARK-CLOUD-COVER / PIERCING-LINE (on-neck / in-neck / thrusting / piercing / PDF pg 63)
|
156
|
+
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
|
157
|
+
not bar[:BEARISH_ENGULFING]
|
158
|
+
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
|
159
|
+
not bar[:BULLISH_ENGULFING]
|
160
|
+
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
|
161
|
+
not bar[:BEARISH_ENGULFING] and not bar[:DARK_CLOUD_COVER]
|
162
|
+
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
|
163
|
+
not bar[:BULLISH_ENGULFING] and not bar[:PIERCING_LINE]
|
164
|
+
|
165
|
+
|
166
|
+
# COUNTER ATTACKS are like piercings / cloud covers, but insist on a large reverse while only reaching the preceding close
|
167
|
+
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
|
168
|
+
bar[:body_size] >= bar[:bar_size] * 0.5 and bar[:bullish]
|
169
|
+
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
|
170
|
+
bar[:body_size] >= bar[:bar_size] * 0.5 and bar[:bearish]
|
171
|
+
|
172
|
+
|
173
|
+
# HARAMIs are an unusual long body embedding the following small body
|
174
|
+
bar[:HARAMI] = true if bar[:body_size] < prev[:body_size] / 2.5 and prev[:bar_size] >= bar[:atr] and
|
175
|
+
prev[:upper] > bar[:upper] and prev[:lower] < bar[:lower] and not bar[:doji]
|
176
|
+
bar[:HARAMI_CROSS] = true if bar[:body_size] < prev[:body_size] / 2.5 and prev[:bar_size] >= bar[:atr] and
|
177
|
+
prev[:upper] > bar[:upper] and prev[:lower] < bar[:lower] and bar[:doji]
|
178
|
+
if bar[:HARAMI] or bar[:HARAMI_CROSS]
|
179
|
+
puts [ :date, :open, :high, :low, :close, :upper, :lower ].map{|x| prev[x]}.join("\t") if debug
|
180
|
+
puts [ :date, :open, :high, :low, :close, :upper, :lower ].map{|x| bar[ x]}.join("\t") if debug
|
181
|
+
puts "" if debug
|
182
|
+
end
|
183
|
+
|
184
|
+
# TODO TWEEZER_TOP and TWEEZER_BOTTOM
|
185
|
+
# actually being a double top / bottom, this dual candle pattern has to be unfolded. It is valid on daily or weekly charts,
|
186
|
+
# and valid if
|
187
|
+
# 1 it has an according
|
188
|
+
|
189
|
+
|
190
|
+
###################################
|
191
|
+
# TRIPLE CANDLE PATTERNS
|
192
|
+
###################################
|
193
|
+
|
194
|
+
# morning star, morning doji star
|
195
|
+
next unless prev and pprev
|
196
|
+
bar[:MORNING_STAR] = true if prev[:STAR] and bar[:bullish] and bar[:close] >= pprev[:lower] and prev[:slope] < -6
|
197
|
+
bar[:MORNING_DOJI_STAR] = true if prev[:DOJI_STAR] and bar[:bullish] and bar[:close] >= pprev[:lower] and prev[:slope] < -6
|
198
|
+
bar[:EVENING_STAR] = true if prev[:STAR] and bar[:bearish] and bar[:close] <= pprev[:upper] and prev[:slope] > 6
|
199
|
+
bar[:EVENING_DOJI_STAR] = true if prev[:DOJI_STAR] and bar[:bearish] and bar[:close] <= pprev[:upper] and prev[:slope] > 6
|
200
|
+
|
201
|
+
# the abandoned baby escalates above stars by gapping the inner star candle to both framing it
|
202
|
+
bar[:ABANDONED_BABY] = true if (bar[:MORNING_STAR] or bar[:MORNING_DOJI_STAR]) and prev[:high] <= [ pprev[:low ], bar[:low ] ].min
|
203
|
+
bar[:ABANDONED_BABY] = true if (bar[:EVENING_STAR] or bar[:EVENING_DOJI_STAR]) and prev[:low ] >= [ pprev[:high], bar[:high] ].max
|
204
|
+
|
205
|
+
# UPSIDEGAP_TWO_CROWS
|
206
|
+
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]
|
207
|
+
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]
|
208
|
+
|
209
|
+
# THREE BLACK CROWS / THREE WHITE SOLDIERS
|
210
|
+
bar[:THREE_BLACK_CROWS] = true if [ bar, prev, pprev ].map{|x| x[:bearish] and x[:bar_size] > 0.5 * bar[:atr] }.reduce(:&) and
|
211
|
+
pprev[:close] - prev[ :close] > bar[:atr] * 0.2 and
|
212
|
+
prev[ :close] - bar[ :close] > bar[:atr] * 0.2
|
213
|
+
bar[:THREE_WHITE_SOLDIERS] = true if [ bar, prev, pprev ].map{|x| x[:bullish] and x[:bar_size] > 0.5 * bar[:atr] }.reduce(:&) and
|
214
|
+
prev[:close] - pprev[:close] > bar[:atr] * 0.2 and
|
215
|
+
bar[ :close] - prev[ :close] > bar[:atr] * 0.2
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
# SLOPE SCORE
|
221
|
+
def slopescore(pprev, prev, bar, debug = false)
|
222
|
+
# the slope between to bars is considered bullish, if 2 of three points match
|
223
|
+
# - higher high
|
224
|
+
# - higher close
|
225
|
+
# - higher low
|
226
|
+
# the opposite counts for bearish
|
227
|
+
#
|
228
|
+
# this comparison is done between the current bar and previous bar
|
229
|
+
# - if it confirms the score of the previous bar, the new slope score is prev + curr
|
230
|
+
# - otherwise the is compared to score of the pprevious bar
|
231
|
+
# - if it confirms there, the new slope score is pprev + curr
|
232
|
+
# - otherwise the trend is destroyed and tne new score is solely curr
|
233
|
+
|
234
|
+
if bar[:bullish]
|
235
|
+
curr = 1
|
236
|
+
curr += 1 if bar[:bullish_close]
|
237
|
+
elsif bar[:bearish]
|
238
|
+
curr = -1
|
239
|
+
curr -= 1 if bar[:bearish_close]
|
240
|
+
else
|
241
|
+
curr = 0
|
242
|
+
end
|
243
|
+
puts "curr set to #{curr} @ #{bar[:date]}".yellow if debug
|
244
|
+
if prev.nil?
|
245
|
+
puts "no prev found, score == curr: #{curr}" if debug
|
246
|
+
score = curr
|
247
|
+
else
|
248
|
+
comp = comparebars(prev, bar)
|
249
|
+
|
250
|
+
puts prev.select{|k,v| [:high,:low,:close,:score].include?(k)} if debug
|
251
|
+
puts bar if debug
|
252
|
+
puts "COMPARISON 1: #{comp}" if debug
|
253
|
+
|
254
|
+
if prev[:slope] >= 0 and comp[:bullish] # bullish slope confirmed
|
255
|
+
score = prev[:slope]
|
256
|
+
score += curr if curr > 0
|
257
|
+
[ :gap, :bodygap ] .each {|x| score += 0.5 if bar[x] }
|
258
|
+
score += 1 if bar[:RISING_volume]
|
259
|
+
score += 2 if bar[:BREAKN_volume]
|
260
|
+
puts "found bullish slope confirmed, new score #{score}" if debug
|
261
|
+
elsif prev[:slope] <= 0 and comp[:bearish] # bearish slope confirmed
|
262
|
+
score = prev[:slope]
|
263
|
+
score += curr if curr < 0
|
264
|
+
[ :gap, :bodygap ] .each {|x| score -= 0.5 if bar[x] }
|
265
|
+
score -= 1 if bar[:RISING_volume]
|
266
|
+
score -= 2 if bar[:BREAKN_volume]
|
267
|
+
puts "found bearish slope confirmed, new score #{score} (including #{curr} and #{bar[:bodygap]} and #{bar[:gap]}" if debug
|
268
|
+
else #if prev[:slope] > 0 # slopes failed
|
269
|
+
puts "confirmation failed: " if debug
|
270
|
+
if pprev.nil?
|
271
|
+
score = curr
|
272
|
+
else
|
273
|
+
comp2 = comparebars(pprev, bar)
|
274
|
+
puts "\t\tCOMPARISON 2: #{comp2}" if debug
|
275
|
+
if pprev[:slope] >= 0 and comp2[:bullish] # bullish slope confirmed on pprev
|
276
|
+
score = pprev[:slope]
|
277
|
+
score += curr if curr > 0
|
278
|
+
[ :gap, :bodygap ] .each {|x| score += 0.5 if bar[x] }
|
279
|
+
puts "\t\tfound bulliish slope confirmed, new score #{score}" if debug
|
280
|
+
score += 1 if bar[:RISING_volume]
|
281
|
+
score += 2 if bar[:BREAKN_volume]
|
282
|
+
elsif pprev[:slope] <= 0 and comp2[:bearish] # bearish slope confirmed
|
283
|
+
score = pprev[:slope]
|
284
|
+
score += curr if curr < 0
|
285
|
+
[ :gap, :bodygap ] .each {|x| score -= 0.5 if bar[x] }
|
286
|
+
score -= 1 if bar[:RISING_volume]
|
287
|
+
score -= 2 if bar[:BREAKN_volume]
|
288
|
+
puts "\t\tfound bearish slope confirmed, new score #{score}" if debug
|
289
|
+
else #slope confirmation finally failed
|
290
|
+
comp3 = comparebars(pprev, prev)
|
291
|
+
if prev[:slope] > 0 # was bullish, turning bearish now
|
292
|
+
score = curr
|
293
|
+
score -= 1 if comp3[:bearish]
|
294
|
+
score -= 1 if comp[:bearish]
|
295
|
+
score -= 1 if prev[:bearish]
|
296
|
+
score -= 1 if prev[:RISING_volume] and comp3[:bearish]
|
297
|
+
score -= 2 if prev[:BREAKN_volume] and comp3[:bearish]
|
298
|
+
score -= 1 if bar[:RISING_volume] and comp[:bearish]
|
299
|
+
score -= 2 if bar[:BREAKN_volume] and comp[:bearish]
|
300
|
+
score -= 1 if bar[:RISING_volume] and comp[:bearish]
|
301
|
+
score -= 2 if bar[:BREAKN_volume] and comp[:bearish]
|
302
|
+
[ :gap, :bodygap ] .each {|x| score += 0.5 if bar[x] }
|
303
|
+
puts "\t\tfinally gave up, turning bearish now, new score #{score}" if debug
|
304
|
+
elsif prev[:slope] < 0
|
305
|
+
score = curr
|
306
|
+
score += 1 if comp3[:bullish]
|
307
|
+
score += 1 if comp[:bullish]
|
308
|
+
score += 1 if prev[:bullish]
|
309
|
+
score += 1 if prev[:RISING_volume] and comp3[:bullish]
|
310
|
+
score += 2 if prev[:BREAKN_volume] and comp3[:bullish]
|
311
|
+
score += 1 if bar[:RISING_volume] and comp[:bullish]
|
312
|
+
score += 2 if bar[:BREAKN_volume] and comp[:bullish]
|
313
|
+
score += 1 if bar[:RISING_volume] and comp[:bullish]
|
314
|
+
score += 2 if bar[:BREAKN_volume] and comp[:bullish]
|
315
|
+
[ :gap, :bodygap ] .each {|x| score -= 0.5 if bar[x] } if curr < 0
|
316
|
+
puts "\t\tfinally gave up, turning bullish now, new score #{score}" if debug
|
317
|
+
else
|
318
|
+
score = 0
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
puts "" if debug
|
325
|
+
score
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|
329
|
+
|
330
|
+
CR = Candlestick_Recognition
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cotcube-helpers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Benjamin L. Tischendorf
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-12-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.9'
|
69
|
+
description: Some helpers and core extensions as part of the Cotcube Suite...
|
70
|
+
email:
|
71
|
+
- donkeybridge@jtown.eu
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".irbrc.rb"
|
77
|
+
- CHANGELOG.md
|
78
|
+
- Gemfile
|
79
|
+
- VERSION
|
80
|
+
- cotcube-helpers.gemspec
|
81
|
+
- lib/cotcube-helpers.rb
|
82
|
+
- lib/cotcube-helpers/array_ext.rb
|
83
|
+
- lib/cotcube-helpers/enum_ext.rb
|
84
|
+
- lib/cotcube-helpers/hash_ext.rb
|
85
|
+
- lib/cotcube-helpers/input.rb
|
86
|
+
- lib/cotcube-helpers/parallelize.rb
|
87
|
+
- lib/cotcube-helpers/range_ext.rb
|
88
|
+
- lib/cotcube-helpers/simple_output.rb
|
89
|
+
- lib/cotcube-helpers/string_ext.rb
|
90
|
+
- lib/cotcube-helpers/subpattern.rb
|
91
|
+
- lib/cotcube-helpers/swig/date.rb
|
92
|
+
- lib/cotcube-helpers/swig/fill_x.rb
|
93
|
+
- lib/cotcube-helpers/swig/recognition.rb
|
94
|
+
homepage: https://github.com/donkeybridge/cotcube-helpers
|
95
|
+
licenses:
|
96
|
+
- BSD-4-Clause
|
97
|
+
metadata:
|
98
|
+
homepage_uri: https://github.com/donkeybridge/cotcube-helpers
|
99
|
+
source_code_uri: https://github.com/donkeybridge/cotcube-helpers
|
100
|
+
changelog_uri: https://github.com/donkeybridge/cotcube-helpers/CHANGELOG.md
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.7'
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubygems_version: 3.1.2
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Some helpers and core extensions as part of the Cotcube Suite.
|
120
|
+
test_files: []
|