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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b44c8314e88c5aca5675c78b3295f082730ddce80afc445d80228260e5c566e
4
- data.tar.gz: 1bb545d175194aa375675b715ac63defbc1bef7101d828197d3d2aac6456cc4f
3
+ metadata.gz: 38df15256f996b15dfeb1755f7464e932a98541e62aa43efda0a264be58bafb2
4
+ data.tar.gz: cd336b6a91442a6d3bbace66ec8a25049ae979ab78eef03d1c8b44a548da8023
5
5
  SHA512:
6
- metadata.gz: 1bf9097c6a01afb4d68bb4bf884d7766fad12481f585ee8df9a6def9fbc82b0cef5ce0fa98eeaaad65d682406d9ac11bae8993f760a60ae390cea23aceeba791
7
- data.tar.gz: 1f987e28e73bc9cbb66da5f8d51f7d0c4200f03c683ee36dc7f228b3684149b33aa6ed9d2d490ace172e5c8811d1f344e427324449066a2621fca55394556e67
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.9
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.' unless self.last.respond_to?(:[]=)
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.' unless self.last.respond_to?(:[]=)
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 SwapSeeker
4
+ module Helpers
6
5
  SYMBOL_EXAMPLES = [
7
- { id: '13874U', symbol: 'ET', ticksize: 0.25, power: 1.25, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'S&P 500 MICRO' },
8
- { id: '209747', symbol: 'NM', ticksize: 0.25, power: 0.5, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'NASDAQ 100 MICRO' }
6
+ { id: '13874U', symbol: 'ES', ib_symbol: 'ES', internal: 'ES', exchange: 'GLOBEX', currency: 'USD', ticksize: 0.25, power: 12.5, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'S&P 500 MICRO' },
7
+ { id: '209747', symbol: 'NQ', ib_symbol: 'NQ', internal: 'NQ', exchange: 'GLOBEx', currency: 'USD', ticksize: 0.25, power: 5.0, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'NASDAQ 100 MICRO' }
8
+ ].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
- 1 => 'F', 2 => 'G', 3 => 'H',
23
- 4 => 'J', 5 => 'K', 6 => 'M',
24
- 7 => 'N', 8 => 'Q', 9 => 'U',
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
+
@@ -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.down_case}.yml"
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::Core::sub"
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
- def symbols(config: init, type: nil, symbol: nil)
7
+ SYMBOL_HEADERS = %i[ id symbol ib_symbol internal exchange currency ticksize power months type bcf reports format name ]
8
+
9
+ def symbols(config: init, **args)
8
10
  if config[:symbols_file].nil?
9
11
  SYMBOL_EXAMPLES
10
12
  else
11
13
  CSV
12
- .read(config[:symbols_file], headers: %i{ id symbol ticksize power months type bcf reports format name})
14
+ .read(config[:symbols_file], headers: SYMBOL_HEADERS)
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| [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f}; row[:format] = "%#{row[:format]}f"; 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| all.select!{|x| x[:type] == type} unless type.nil? }
17
- .tap { |all| all.select! { |x| x[:symbol] == symbol } unless symbol.nil? }
52
+ .tap{ |all|
53
+ args.keys.each { |header|
54
+ unless SYMBOL_HEADERS.include? header
55
+ puts "WARNING in Cotcube::Helpers.symbols: '#{header}' is not a valid symbol header. Skipping..."
56
+ next
57
+ end
58
+ all.select!{|x| x[header] == args[header]} unless args[header].nil?
59
+ return all.first if all.size == 1
60
+ }
61
+ return all
62
+ }
18
63
  end
19
64
  end
65
+ end
20
66
 
21
67
  end
@@ -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-heleprs/init'
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.1.9
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-05-07 00:00:00.000000000 Z
11
+ date: 2021-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -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.2
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.