cotcube-helpers 0.1.9.1 → 0.2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Gemfile +2 -0
- data/VERSION +1 -1
- data/lib/cotcube-helpers/array_ext.rb +4 -4
- data/lib/cotcube-helpers/constants.rb +18 -8
- data/lib/cotcube-helpers/data_client.rb +177 -0
- data/lib/cotcube-helpers/datetime_ext.rb +3 -0
- data/lib/cotcube-helpers/get_id_set.rb +9 -7
- data/lib/cotcube-helpers/ib_contracts.rb +69 -0
- data/lib/cotcube-helpers/init.rb +2 -1
- data/lib/cotcube-helpers/numeric_ext.rb +8 -0
- data/lib/cotcube-helpers/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 +53 -5
- data/lib/cotcube-helpers.rb +15 -1
- data/scripts/cron_ruby_wrapper.sh +25 -0
- data/scripts/symbols +13 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1228015a21f3920ec5b1d87e3c5918e3c69e0a577746b6a18579ae51e1528ce2
|
4
|
+
data.tar.gz: 431798647c5d4e9d5a7a32816636bd768c4e0e9c70d789dd491c374fde75f768
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b931c2dad6f54a1f2de64590e703c1be03ed2330691a299c2e4d4a64f5cb5d20d3be9ba44584866db66116160c2d01c87cd17f7d84f5ae82ae2668f5a8cf0bae
|
7
|
+
data.tar.gz: 18b50ba7237d373dec19d3144814ad55c5ec3d36274213c5749041bbf66ce008461d2be89cda0240245529e29ed383110352d945af4a82cb761d53a6d702f09a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
## 0.2.1.1 (November 10, 2021)
|
2
|
+
- Bump version to 0.2.1.
|
3
|
+
- Bump version to 0.2.1.
|
4
|
+
|
5
|
+
## 0.2.1 (November 10, 2021)
|
6
|
+
- added new class 'dataclient' for communication with dataproxy
|
7
|
+
- added .translate_ib_contract
|
8
|
+
|
9
|
+
## 0.2.0 (November 07, 2021)
|
10
|
+
- added module Candlestick_Recognition
|
11
|
+
- added instance_inspect method to 'scan' objects for contents of instance variables
|
12
|
+
- symbols: made selection of symbols more versatile by key
|
13
|
+
- added headers (:ib_symbol, :internal, :exchange, :currency) to symbol headers as well as symbol examples
|
14
|
+
- added scripts/symbols to list (and filter) symbols from command line (put to PATH!)
|
15
|
+
|
16
|
+
## 0.1.10 (October 28, 2021)
|
17
|
+
- added script cron_ruby_wrapper.sh (linkable as /usr/local/bin/cruw.sh)
|
18
|
+
- added numeric ext .with_delimiter to support printing like 123_456_789.00121
|
19
|
+
- added micros to module
|
20
|
+
- added Helpers.micros to symbols.rb
|
21
|
+
- subpattern: excaping regex pattern to avoid ESC errors
|
22
|
+
- minor change
|
23
|
+
|
24
|
+
## 0.1.9.2 (July 24, 2021)
|
25
|
+
- added missing module_functions
|
26
|
+
- init: minor fix
|
27
|
+
- datetime_ext: added warning comment / TODO, as switch from to daylight time will produce erroneous results
|
28
|
+
- constants: minor fix (typo)
|
29
|
+
- array_ext: added param to provide a default return value if result is an empty array
|
30
|
+
|
1
31
|
## 0.1.9.1 (May 07, 2021)
|
2
32
|
- moved 'get_id_set' to Cotcube::Helpers
|
3
33
|
- minor fix to suppress some warning during build
|
data/Gemfile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.2.1.1
|
@@ -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
|
|
@@ -29,6 +33,12 @@ module Cotcube
|
|
29
33
|
|
30
34
|
DATE_FMT = '%Y-%m-%d'
|
31
35
|
|
36
|
+
# Simple mapper to get from MONTH to LETTER
|
37
|
+
LETTERS = { "JAN"=> "F", "FEB"=> "G", "MAR"=> "H",
|
38
|
+
"APR"=> "J", "MAY"=> "K", "JUN"=> "M",
|
39
|
+
"JUL"=> "N", "AUG"=> "Q", "SEP"=> "U",
|
40
|
+
"OCT"=> "V", "NOV"=> "X", "DEC"=> "Z" }
|
41
|
+
|
32
42
|
end
|
33
43
|
end
|
34
44
|
|
@@ -0,0 +1,177 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bunny'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Cotcube
|
6
|
+
module Helpers
|
7
|
+
class DataClient
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@connection = Bunny.new(automatically_recover: true)
|
11
|
+
@connection.start
|
12
|
+
|
13
|
+
@channel = connection.create_channel
|
14
|
+
@exchange = channel.direct('dataproxy_commands', auto_delete: true)
|
15
|
+
@requests = {}
|
16
|
+
@persistent = { depth: {}, realtimebars: {}, ticks: {} }
|
17
|
+
@response = nil
|
18
|
+
|
19
|
+
setup_reply_queue
|
20
|
+
end
|
21
|
+
|
22
|
+
def help
|
23
|
+
puts "The following commands are available:\n\n"\
|
24
|
+
"\tcontracts = client.get_contracts(symbol:)\n"\
|
25
|
+
"\tbars = client.get_historical(contract:, duration:, interval:, before: nil)"\
|
26
|
+
"\trequest_id = client.start_realtimebars(contract: )\n"\
|
27
|
+
"\t client.stop_realtimebars(request_id: )\n"\
|
28
|
+
"\trequest_id = client.start_ticks(contract: )\n"\
|
29
|
+
"\t client.stop_ticks(request_id: )\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_command(command, timeout: 5)
|
33
|
+
command = { command: command.to_s } unless command.is_a? Hash
|
34
|
+
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
35
|
+
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
36
|
+
requests[request_id] = { request: command, id: request_id }
|
37
|
+
|
38
|
+
exchange.publish(command.to_json,
|
39
|
+
content_type: 'application/json',
|
40
|
+
routing_key: 'dataproxy_commands',
|
41
|
+
correlation_id: request_id,
|
42
|
+
reply_to: reply_queue.name)
|
43
|
+
|
44
|
+
# wait for the signal to continue the execution
|
45
|
+
lock.synchronize {
|
46
|
+
condition.wait(lock, timeout)
|
47
|
+
}
|
48
|
+
|
49
|
+
response
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop
|
53
|
+
channel.close
|
54
|
+
connection.close
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def get_contracts(symbol: )
|
59
|
+
send_command( { command: :get_contracts, symbol: symbol } )
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_historical(contract:, interval:, duration: nil, before: nil, rth_only: false, based_on: :trades)
|
63
|
+
# rth.true? means data outside of rth is skipped
|
64
|
+
rth_only = rth_only ? 1 : 0
|
65
|
+
default_durations = {
|
66
|
+
sec1: '30_M',
|
67
|
+
sec5: '2_H',
|
68
|
+
sec15: '6_H',
|
69
|
+
sec30: '12_H',
|
70
|
+
min1: '1_D',
|
71
|
+
min2: '2_D',
|
72
|
+
min5: '5_D',
|
73
|
+
min15: '1_W',
|
74
|
+
min30: '1_W',
|
75
|
+
hour1: '1_W',
|
76
|
+
day1: '1_Y'
|
77
|
+
}
|
78
|
+
raise "Invalid interval '#{interval}', should be in '#{default_durations.keys}'." unless default_durations.keys.include? interval
|
79
|
+
# TODO: Check for valid duration specification
|
80
|
+
duration ||= default_durations[interval]
|
81
|
+
send_command( {
|
82
|
+
command: :historical,
|
83
|
+
contract: contract,
|
84
|
+
interval: interval,
|
85
|
+
duration: duration,
|
86
|
+
based_on: based_on.to_s.upcase,
|
87
|
+
rth_only: rth_only,
|
88
|
+
before: nil
|
89
|
+
}, timeout: 20 )
|
90
|
+
end
|
91
|
+
|
92
|
+
def start_persistent(contract:, type: :realtimebars, &block)
|
93
|
+
unless %i[ depth ticks realtimebars].include? type.to_sym
|
94
|
+
puts "ERROR: Inappropriate type in stop_realtimebars with #{type}"
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
|
98
|
+
ib_contract = Cotcube::Helpers.get_ib_contract(contract)
|
99
|
+
exchange = channel.fanout( "dataproxy_#{type.to_s}_#{contract}", auto_delete: true)
|
100
|
+
queue = channel.queue('', exclusive: true, auto_delete: true)
|
101
|
+
queue.bind(exchange)
|
102
|
+
block ||= ->(bar){ puts "#{bar}" }
|
103
|
+
queue.subscribe do |_delivery_info, properties, payload|
|
104
|
+
block.call(JSON.parse(payload, symbolize_names: true))
|
105
|
+
end
|
106
|
+
command = { command: type, contract: contract, con_id: ib_contract[:con_id], delivery: queue.name, exchange: exchange.name }
|
107
|
+
persistent[type][queue.name] = command
|
108
|
+
persistent[type][queue.name][:queue] = queue
|
109
|
+
send_command(command)
|
110
|
+
end
|
111
|
+
|
112
|
+
def stop_persistent(contract:, type: :realtimebars )
|
113
|
+
unless %i[ depth ticks realtimebars].include? type.to_sym
|
114
|
+
puts "ERROR: Inappropriate type in stop_realtimebars with #{type}"
|
115
|
+
return false
|
116
|
+
end
|
117
|
+
ib_contract = Cotcube::Helpers.get_ib_contract(contract)
|
118
|
+
command = { command: "stop_#{type}", contract: contract, con_id: ib_contract[:con_id] }
|
119
|
+
send_command(command)
|
120
|
+
end
|
121
|
+
|
122
|
+
attr_accessor :response
|
123
|
+
attr_reader :lock, :condition
|
124
|
+
|
125
|
+
private
|
126
|
+
attr_reader :call_id, :connection, :requests, :persistent,
|
127
|
+
:channel, :server_queue_name, :reply_queue, :exchange
|
128
|
+
|
129
|
+
|
130
|
+
def setup_reply_queue
|
131
|
+
@lock = Mutex.new
|
132
|
+
@condition = ConditionVariable.new
|
133
|
+
that = self
|
134
|
+
@reply_queue = channel.queue('', exclusive: true, auto_delete: true)
|
135
|
+
@reply_queue.bind(channel.exchange('dataproxy_replies', auto_delete: true), routing_key: @reply_queue.name)
|
136
|
+
|
137
|
+
reply_queue.subscribe do |delivery_info, properties, payload|
|
138
|
+
|
139
|
+
__id__ = properties[:correlation_id]
|
140
|
+
|
141
|
+
if __id__.nil?
|
142
|
+
puts "Received without __id__: #{delivery_info.map{|k,v| "#{k}\t#{v}"}.join("\n")
|
143
|
+
}\n\n#{properties .map{|k,v| "#{k}\t#{v}"}.join("\n")
|
144
|
+
}\n\n#{JSON.parse(payload).map{|k,v| "#{k}\t#{v}"}.join("\n")}"
|
145
|
+
|
146
|
+
elsif requests[__id__].nil?
|
147
|
+
puts "Received non-matching response: \n\n#{_delivery_info}\n\n#{properties}\n\n#{payload}\n."
|
148
|
+
else
|
149
|
+
that.response = payload
|
150
|
+
|
151
|
+
# sends the signal to continue the execution of #call
|
152
|
+
requests.delete(__id__)
|
153
|
+
that.lock.synchronize { that.condition.signal }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
__END__
|
163
|
+
begin
|
164
|
+
client = DataClient.new
|
165
|
+
reply = client.send_command( { command: 'ping' } ) #{ command: :hist, contract: 'A6Z21', con_id: 259130514, interval: :min15 } )
|
166
|
+
puts reply.nil? ? 'nil' : JSON.parse(reply)
|
167
|
+
reply = client.get_historical( contract: 'A6Z21', con_id: 259130514, interval: :min15 , rth_only: false)
|
168
|
+
JSON.parse(reply, symbolize_names: true)[:result].map{|z|
|
169
|
+
z[:datetime] = Cotcube::Helpers::CHICAGO.parse(z[:time]).strftime('%Y-%m-%d %H:%M:%S')
|
170
|
+
z.delete(:created_at)
|
171
|
+
z.delete(:time)
|
172
|
+
p z.slice(*%i[datetime open high low close volume]).values
|
173
|
+
|
174
|
+
}
|
175
|
+
ensure
|
176
|
+
client.stop
|
177
|
+
e,nd
|
@@ -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
|
@@ -3,12 +3,13 @@
|
|
3
3
|
module Cotcube
|
4
4
|
module Helpers
|
5
5
|
|
6
|
-
def get_id_set(symbol: nil, id: nil, contract: nil, config: init)
|
6
|
+
def get_id_set(symbol: nil, id: nil, contract: nil, config: init, mini: false, micro: false)
|
7
|
+
micro = mini || micro
|
7
8
|
contract = contract.to_s.upcase if contract.is_a? Symbol
|
8
9
|
id = id.to_s.upcase if id.is_a? Symbol
|
9
10
|
symbol = symbol.to_s.upcase if symbol.is_a? Symbol
|
10
11
|
|
11
|
-
if contract.is_a?(String) && (contract.length
|
12
|
+
if contract.is_a?(String) && ([2,3,4,5].include? contract.length)
|
12
13
|
c_symbol = contract[0..1]
|
13
14
|
if (not symbol.nil?) && (symbol != c_symbol)
|
14
15
|
raise ArgumentError,
|
@@ -19,25 +20,26 @@ module Cotcube
|
|
19
20
|
end
|
20
21
|
|
21
22
|
unless symbol.nil?
|
22
|
-
sym = symbols.
|
23
|
+
sym = symbols(symbol: symbol).presence || micros(symbol: symbol)
|
23
24
|
if sym.nil? || sym[:id].nil?
|
24
25
|
raise ArgumentError,
|
25
|
-
"Could not find match in #{config[:symbols_file]} for given symbol #{symbol}"
|
26
|
+
"Could not find match in #{config[:symbols_file]} or #{config[:micros_file]} for given symbol #{symbol}"
|
26
27
|
end
|
27
28
|
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if (not id.nil?) && (sym[:id] != id)
|
28
29
|
|
29
|
-
return sym
|
30
|
+
return micro ? micros(id: sym[:id]) : sym
|
30
31
|
end
|
31
32
|
unless id.nil?
|
32
|
-
sym = symbols
|
33
|
+
sym = symbols(id: id)
|
33
34
|
if sym.nil? || sym[:id].nil?
|
34
35
|
raise ArgumentError,
|
35
36
|
"Could not find match in #{config[:symbols_file]} for given id #{id}"
|
36
37
|
end
|
37
|
-
return sym
|
38
|
+
return micro ? micros(id: sym[:id]) : sym
|
38
39
|
end
|
39
40
|
raise ArgumentError, 'Need :id, :symbol or valid :contract '
|
40
41
|
end
|
42
|
+
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Cotcube
|
2
|
+
module Helpers
|
3
|
+
def get_ib_contract(contract)
|
4
|
+
symbol = contract[..1]
|
5
|
+
# TODO consider file location to be found in configfile
|
6
|
+
filepath = '/etc/cotcube/ibsymbols/'
|
7
|
+
result = YAML.load(File.read( "#{filepath}/#{symbol}.yml"))[contract].transform_keys(&:to_sym) rescue nil
|
8
|
+
result.nil? ? update_ib_contracts(symbol: contract[..1]) : (return result)
|
9
|
+
YAML.load(File.read( "#{filepath}/#{symbol}.yml"))[contract].transform_keys(&:to_sym) rescue nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_ib_contracts(symbol: nil)
|
13
|
+
begin
|
14
|
+
client = DataClient.new
|
15
|
+
(Cotcube::Helpers.symbols + Cotcube::Helpers.micros).each do |sym|
|
16
|
+
|
17
|
+
# TODO: consider file location to be located in config
|
18
|
+
file = "/etc/cotcube/ibsymbols/#{sym[:symbol]}.yml"
|
19
|
+
|
20
|
+
# TODO: VI publishes weekly options which dont match, the 3 others need multiplier enabled to work
|
21
|
+
next if %w[ DY TM SI VI ].include? sym[:symbol]
|
22
|
+
next if symbol and sym[:symbol] != symbol
|
23
|
+
begin
|
24
|
+
if File.exist? file
|
25
|
+
next if Time.now - File.mtime(file) < 5.days
|
26
|
+
data = nil
|
27
|
+
data = YAML.load(File.read(file))
|
28
|
+
else
|
29
|
+
data = {}
|
30
|
+
end
|
31
|
+
p file
|
32
|
+
%w[ symbol sec_type exchange multiplier ticksize power internal ].each {|z| data.delete z}
|
33
|
+
raw = client.get_contracts(symbol: sym[:symbol])
|
34
|
+
reply = JSON.parse(raw)['result']
|
35
|
+
reply.each do |set|
|
36
|
+
contract = translate_ib_contract set['local_symbol']
|
37
|
+
data[contract] ||= set
|
38
|
+
end
|
39
|
+
keys = data.keys.sort_by{|z| z[2]}.sort_by{|z| z[-2..] }.select{|z| z[..1] == sym[:symbol] }
|
40
|
+
data = data.slice(*keys)
|
41
|
+
File.open(file, 'w'){|f| f.write(data.to_yaml) }
|
42
|
+
rescue Exception => e
|
43
|
+
puts e.full_message
|
44
|
+
p sym
|
45
|
+
binding.irb
|
46
|
+
end
|
47
|
+
end
|
48
|
+
ensure
|
49
|
+
client.stop
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def translate_ib_contract(contract)
|
55
|
+
short = contract.split(" ").size == 1
|
56
|
+
sym_a = contract.split(short ? '' : ' ')
|
57
|
+
year = sym_a.pop.to_i + (short ? 20 : 0)
|
58
|
+
if short and sym_a[-1].to_i > 0
|
59
|
+
year = year - 20 + sym_a.pop.to_i * 10
|
60
|
+
end
|
61
|
+
month = short ? sym_a.pop : LETTERS[sym_a.pop]
|
62
|
+
sym = Cotcube::Helpers.symbols(internal: sym_a.join)[:symbol] rescue nil
|
63
|
+
sym ||= Cotcube::Helpers.micros(internal: sym_a.join)[:symbol] rescue nil
|
64
|
+
sym.nil? ? false : "#{sym}#{month}#{year}"
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
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,66 @@ module Cotcube
|
|
4
4
|
# Missing top level documentation
|
5
5
|
module Helpers
|
6
6
|
|
7
|
-
|
7
|
+
SYMBOL_HEADERS = %i[ id symbol ib_symbol internal exchange currency ticksize power months type bcf reports format name ]
|
8
|
+
|
9
|
+
def symbols(config: init, **args)
|
8
10
|
if config[:symbols_file].nil?
|
9
11
|
SYMBOL_EXAMPLES
|
10
12
|
else
|
11
13
|
CSV
|
12
|
-
.read(config[:symbols_file], headers:
|
14
|
+
.read(config[:symbols_file], headers: SYMBOL_HEADERS)
|
13
15
|
.map{|row| row.to_h }
|
14
|
-
.map{|row|
|
16
|
+
.map{|row|
|
17
|
+
[ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f}
|
18
|
+
row[:format] = "%#{row[:format]}f"
|
19
|
+
row[:currency] ||= 'USD'
|
20
|
+
row[:multiplier] = (row[:power] / row[:ticksize]).round(8)
|
21
|
+
row
|
22
|
+
}
|
15
23
|
.reject{|row| row[:id].nil? }
|
16
|
-
.tap{|all|
|
17
|
-
|
24
|
+
.tap{ |all|
|
25
|
+
args.keys.each { |header|
|
26
|
+
unless SYMBOL_HEADERS.include? header
|
27
|
+
puts "WARNING in Cotcube::Helpers.symbols: '#{header}' is not a valid symbol header. Skipping..."
|
28
|
+
next
|
29
|
+
end
|
30
|
+
all.select!{|x| x[header] == args[header]} unless args[header].nil?
|
31
|
+
return all.first if all.size == 1
|
32
|
+
}
|
33
|
+
return all
|
34
|
+
}
|
18
35
|
end
|
19
36
|
end
|
20
37
|
|
38
|
+
def micros(config: init, **args)
|
39
|
+
if config[:micros_file].nil?
|
40
|
+
MICRO_EXAMPLES
|
41
|
+
else
|
42
|
+
CSV
|
43
|
+
.read(config[:micros_file], headers: SYMBOL_HEADERS)
|
44
|
+
.map{|row| row.to_h }
|
45
|
+
.map{|row|
|
46
|
+
[ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f }
|
47
|
+
row[:format] = "%#{row[:format]}f"
|
48
|
+
row[:currency] ||= 'USD'
|
49
|
+
row[:multiplier] = (row[:power] / row[:ticksize]).round(8)
|
50
|
+
row
|
51
|
+
}
|
52
|
+
.reject{|row| row[:id].nil? }
|
53
|
+
.tap{ |all|
|
54
|
+
args.keys.each { |header|
|
55
|
+
unless SYMBOL_HEADERS.include? header
|
56
|
+
puts "WARNING in Cotcube::Helpers.micros: '#{header}' is not a valid symbol header. Skipping..."
|
57
|
+
next
|
58
|
+
end
|
59
|
+
all.select!{|x| x[header] == args[header]} unless args[header].nil?
|
60
|
+
return all.first if all.size == 1
|
61
|
+
}
|
62
|
+
return all
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
21
69
|
end
|
data/lib/cotcube-helpers.rb
CHANGED
@@ -6,6 +6,9 @@ require 'active_support'
|
|
6
6
|
require 'active_support/core_ext/time'
|
7
7
|
require 'active_support/core_ext/numeric'
|
8
8
|
require 'parallel'
|
9
|
+
require 'csv'
|
10
|
+
require 'yaml'
|
11
|
+
require 'json'
|
9
12
|
|
10
13
|
require_relative 'cotcube-helpers/array_ext'
|
11
14
|
require_relative 'cotcube-helpers/enum_ext'
|
@@ -13,26 +16,37 @@ require_relative 'cotcube-helpers/hash_ext'
|
|
13
16
|
require_relative 'cotcube-helpers/range_ext'
|
14
17
|
require_relative 'cotcube-helpers/string_ext'
|
15
18
|
require_relative 'cotcube-helpers/datetime_ext'
|
19
|
+
require_relative 'cotcube-helpers/numeric_ext'
|
16
20
|
require_relative 'cotcube-helpers/subpattern'
|
17
21
|
require_relative 'cotcube-helpers/parallelize'
|
18
22
|
require_relative 'cotcube-helpers/simple_output'
|
19
23
|
require_relative 'cotcube-helpers/simple_series_stats'
|
24
|
+
require_relative 'cotcube-helpers/constants'
|
20
25
|
require_relative 'cotcube-helpers/input'
|
26
|
+
require_relative 'cotcube-helpers/output'
|
21
27
|
require_relative 'cotcube-helpers/reduce'
|
22
|
-
require_relative 'cotcube-helpers/constants'
|
23
28
|
require_relative 'cotcube-helpers/symbols'
|
24
29
|
require_relative 'cotcube-helpers/init'
|
25
30
|
require_relative 'cotcube-helpers/get_id_set'
|
31
|
+
require_relative 'cotcube-helpers/ib_contracts'
|
32
|
+
require_relative 'cotcube-helpers/recognition'
|
33
|
+
require_relative 'cotcube-helpers/data_client'
|
26
34
|
|
27
35
|
module Cotcube
|
28
36
|
module Helpers
|
29
37
|
module_function :sub,
|
30
38
|
:parallelize,
|
39
|
+
:config_path,
|
40
|
+
:config_prefix,
|
31
41
|
:reduce,
|
32
42
|
:simple_series_stats,
|
33
43
|
:keystroke,
|
34
44
|
:symbols,
|
45
|
+
:micros,
|
35
46
|
:get_id_set,
|
47
|
+
:get_ib_contract,
|
48
|
+
:update_ib_contracts,
|
49
|
+
:translate_ib_contract,
|
36
50
|
:init
|
37
51
|
|
38
52
|
# 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.
|
4
|
+
version: 0.2.1.1
|
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-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -97,14 +97,19 @@ files:
|
|
97
97
|
- lib/cotcube-helpers.rb
|
98
98
|
- lib/cotcube-helpers/array_ext.rb
|
99
99
|
- lib/cotcube-helpers/constants.rb
|
100
|
+
- lib/cotcube-helpers/data_client.rb
|
100
101
|
- lib/cotcube-helpers/datetime_ext.rb
|
101
102
|
- lib/cotcube-helpers/enum_ext.rb
|
102
103
|
- lib/cotcube-helpers/get_id_set.rb
|
103
104
|
- lib/cotcube-helpers/hash_ext.rb
|
105
|
+
- lib/cotcube-helpers/ib_contracts.rb
|
104
106
|
- lib/cotcube-helpers/init.rb
|
105
107
|
- lib/cotcube-helpers/input.rb
|
108
|
+
- lib/cotcube-helpers/numeric_ext.rb
|
109
|
+
- lib/cotcube-helpers/output.rb
|
106
110
|
- lib/cotcube-helpers/parallelize.rb
|
107
111
|
- lib/cotcube-helpers/range_ext.rb
|
112
|
+
- lib/cotcube-helpers/recognition.rb
|
108
113
|
- lib/cotcube-helpers/reduce.rb
|
109
114
|
- lib/cotcube-helpers/simple_output.rb
|
110
115
|
- lib/cotcube-helpers/simple_series_stats.rb
|
@@ -114,6 +119,8 @@ files:
|
|
114
119
|
- lib/cotcube-helpers/swig/fill_x.rb
|
115
120
|
- lib/cotcube-helpers/swig/recognition.rb
|
116
121
|
- lib/cotcube-helpers/symbols.rb
|
122
|
+
- scripts/cron_ruby_wrapper.sh
|
123
|
+
- scripts/symbols
|
117
124
|
homepage: https://github.com/donkeybridge/cotcube-helpers
|
118
125
|
licenses:
|
119
126
|
- BSD-4-Clause
|
@@ -136,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
143
|
- !ruby/object:Gem::Version
|
137
144
|
version: '0'
|
138
145
|
requirements: []
|
139
|
-
rubygems_version: 3.1.
|
146
|
+
rubygems_version: 3.1.6
|
140
147
|
signing_key:
|
141
148
|
specification_version: 4
|
142
149
|
summary: Some helpers and core extensions as part of the Cotcube Suite.
|