cotcube-level 0.3.1 → 0.3.4.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 +35 -0
- data/VERSION +1 -1
- data/lib/cotcube-level/eod_stencil.rb +16 -3
- data/lib/cotcube-level/helpers.rb +143 -28
- data/lib/cotcube-level/intraday_stencil.rb +104 -70
- data/lib/cotcube-level/tritangulate.rb +29 -20
- data/lib/cotcube-level.rb +2 -1
- metadata +2 -3
- data/.irbrc.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71bc6b4ca29ab4ff089b95568a0d592d4bc2b3d8c1824ea9158d661792f1c5b7
|
4
|
+
data.tar.gz: 670d5a69457411fc7539542a1bd0ad1712ef9111f7b5a876b3c7bdd5d6de90c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3221bf00e3532ae51f62d98f191332273a2241ae34f0eff99e9840f28f41e339a816d23d100150aca70719855c6a1d0507f6509f2598cad75c880f86089286b3
|
7
|
+
data.tar.gz: 652072c05872418b438ca7ac937e3e5735a5967804c0d0e8f75b4e5381553b55e8b64ecf769cb820745650717077eae1ac7b0b844325725a1c0c78b6b2fbbf85
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
## 0.3.4.1 (October 09, 2021)
|
2
|
+
- intraday_stencil: fixing @index, that did not work when used outside active hours
|
3
|
+
|
4
|
+
## 0.3.4 (October 06, 2021)
|
5
|
+
- intraday_stencil: major rework resp. rebuild ... now beta-ready
|
6
|
+
- helpers: preparing puts_swap and get_jsonl_name to work intraday
|
7
|
+
- puts_swap: changed output scheme to provide exceedance as NOTE
|
8
|
+
|
9
|
+
## 0.3.3 (October 05, 2021)
|
10
|
+
- helpers::load_swaps: added :exceed to allow 1(sic) swap to be exceeded while loading
|
11
|
+
- tritangulate: added :manual for feature of manual swap creation with base of 2 members
|
12
|
+
- helpers::load_swap added :digest to filter for swaps starting with pattern
|
13
|
+
- helpers: minor readability improvements
|
14
|
+
- eod_stencil: minor readability improvements
|
15
|
+
- helpers: few optimizations
|
16
|
+
|
17
|
+
## 0.3.2 (August 29, 2021)
|
18
|
+
- tritangulate: fixing 'finalize', as Integer zero won't comparte to Float zero
|
19
|
+
- cotcube-level.rb: added :check_exceedance
|
20
|
+
- helpers:member_to_human: added :daily param to distinguish stencils
|
21
|
+
- tritangulate: added 'min_ratio' as param with lambda
|
22
|
+
- eod_stencil: added #use to calculate current swap line value / dist;
|
23
|
+
- tritangulate: added interval to be saved with the swap information.
|
24
|
+
- helpers: moved puts_swaps to puts_swap, and added a :short switch for 1-liners per swap
|
25
|
+
|
26
|
+
## 0.3.1.1 (August 25, 2021)
|
27
|
+
- trying to fix versioning mistake
|
28
|
+
- Bump version to 0.3.2.1.
|
29
|
+
- minor fixes correcting mistakes sneaked in during documentation rework
|
30
|
+
- minor fix
|
31
|
+
|
32
|
+
## 0.3.2.1 (August 25, 2021)
|
33
|
+
- minor fixes correcting mistakes sneaked in during documentation rework
|
34
|
+
- minor fix
|
35
|
+
|
1
36
|
## 0.3.1 (August 24, 2021)
|
2
37
|
- renaming triangulation to tritangulation
|
3
38
|
- minor fixes in README and gemspec
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.1
|
1
|
+
0.3.4.1
|
@@ -43,7 +43,6 @@ module Cotcube
|
|
43
43
|
range: nil, # used to shrink the stencil size, accepts String or Date
|
44
44
|
interval:,
|
45
45
|
swap_type:,
|
46
|
-
contract: nil,
|
47
46
|
date: nil,
|
48
47
|
debug: false,
|
49
48
|
version: nil, # when referring to a specicic version of the stencil
|
@@ -54,7 +53,6 @@ module Cotcube
|
|
54
53
|
@debug = debug
|
55
54
|
@interval = interval == :continuous ? :daily : interval
|
56
55
|
@swap_type = swap_type
|
57
|
-
@contract = contract
|
58
56
|
@warnings = warnings
|
59
57
|
step = case @interval
|
60
58
|
when :hours, :hour; 1.hour
|
@@ -108,7 +106,6 @@ module Cotcube
|
|
108
106
|
interval: @interval,
|
109
107
|
swap_type: @swap_type,
|
110
108
|
date: @date,
|
111
|
-
contract: @contract,
|
112
109
|
stencil: @base.map{|x| x.dup}
|
113
110
|
)
|
114
111
|
end
|
@@ -139,6 +136,22 @@ module Cotcube
|
|
139
136
|
to.reject!{|x| x[:x].nil? }
|
140
137
|
end
|
141
138
|
|
139
|
+
def use(with:, sym:, zero:, grace: -2)
|
140
|
+
# todo: validate with (check if vslid swap
|
141
|
+
# sym (check keys)
|
142
|
+
# zero (ohlc with x.zero?)
|
143
|
+
# side ( upper or lower)
|
144
|
+
swap = with.dup
|
145
|
+
high = swap[:side] == :upper
|
146
|
+
ohlc = high ? :high : :low
|
147
|
+
start = base.find{|x| swap[:datetime] == x[:datetime]}
|
148
|
+
swap[:current_change] = (swap[:tpi] * start[:x]).round(8)
|
149
|
+
swap[:current_value] = swap[:members].last[ ohlc ] + swap[:current_change] * sym[:ticksize]
|
150
|
+
swap[:current_diff] = (swap[:current_value] - zero[ohlc]) * (high ? 1 : -1 )
|
151
|
+
swap[:current_dist] = (swap[:current_diff] / sym[:ticksize]).to_i
|
152
|
+
swap[:exceeded] = zero[:datetime] if swap[:current_dist] < grace
|
153
|
+
swap
|
154
|
+
end
|
142
155
|
end
|
143
156
|
|
144
157
|
end
|
@@ -17,41 +17,62 @@ module Cotcube
|
|
17
17
|
member[:yy] =
|
18
18
|
member[:y] +
|
19
19
|
(member[:dx].nil? ? member[:x] : member[:dx]) * tan
|
20
|
-
|
20
|
+
member
|
21
21
|
}
|
22
22
|
end
|
23
23
|
|
24
24
|
# human readable output
|
25
25
|
# please note the format must be given, that should be taken from :sym
|
26
|
-
def member_to_human(member,side: ,format:)
|
27
|
-
high = side == :upper
|
28
|
-
"#{member[:datetime].strftime("%a, %Y-%m-%d %I:%M%p")
|
29
|
-
} x: #{format '%-4d',
|
26
|
+
def member_to_human(member,side: ,format:, daily: false)
|
27
|
+
high = (side == :upper)
|
28
|
+
"#{ member[:datetime].strftime("%a, %Y-%m-%d#{daily ? "" :" %I:%M%p"}")
|
29
|
+
} x: #{format '%-4d', member[:x]
|
30
30
|
} dx: #{format '%-8.3f', (member[:dx].nil? ? member[:x] : member[:dx].round(3))
|
31
31
|
} #{high ? "high" : "low"
|
32
|
-
}: #{format format,
|
33
|
-
} i: #{(format '%4d',
|
34
|
-
} d: #{format '%6.2f',
|
32
|
+
}: #{format format, member[high ? :high : :low]
|
33
|
+
} i: #{(format '%4d', member[:i]) unless member[:i].nil?
|
34
|
+
} d: #{format '%6.2f', member[:dev] unless member[:dev].nil?
|
35
35
|
} #{member[:near].nil? ? '' : "near: #{member[:near]}"
|
36
36
|
}"
|
37
37
|
end
|
38
38
|
|
39
39
|
# human readable output
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
# format: e.g. sym[:format]
|
41
|
+
# short: print one line / less verbose
|
42
|
+
# notice: add this to output as well
|
43
|
+
def puts_swap(swap, format: , short: true, notice: nil, hash: 3)
|
44
|
+
return if swap[:empty]
|
45
|
+
daily = %i[ continuous daily ].include?(swap[:interval].to_sym) rescue false
|
46
|
+
datetime_format = daily ? '%Y-%m-%d' : '%Y-%m-%d %I:%M %p'
|
47
|
+
high = swap[:side] == :high
|
48
|
+
ohlc = high ? :high : :low
|
49
|
+
if notice.nil? and swap[:exceeded]
|
50
|
+
notice = "exceeded #{swap[:exceeded].strftime(datetime_format)}"
|
51
|
+
end
|
52
|
+
if short
|
53
|
+
puts (hash ? "#{swap[:digest][...hash]} ".colorize(:light_white) : '') +
|
54
|
+
"S: #{swap[:side]
|
55
|
+
} L: #{ format '%4d', swap[:length]
|
56
|
+
} R: #{ format '%4d', swap[:rating]
|
57
|
+
} D: #{ format '%4d', swap[:depth]
|
58
|
+
} P: #{ format '%10s', (format '%5.2f', swap[:ppi])
|
59
|
+
} F: #{ format format, swap[:members].last[ ohlc ]
|
60
|
+
} S: #{ swap[:members].first[:datetime].strftime(datetime_format)
|
61
|
+
} - #{ swap[:members].last[:datetime].strftime(datetime_format)
|
62
|
+
}#{" NOTE: #{notice}" unless notice.nil?}".colorize(swap[:color] || :white )
|
63
|
+
else
|
44
64
|
puts "side: #{swap[:side] }\tlen: #{swap[:length]} \trating: #{swap[:rating]}".colorize(swap[:color] || :white )
|
45
65
|
puts "diff: #{swap[:ticks]}\tdif: #{swap[:diff].round(7)}\tdepth: #{swap[:depth]}".colorize(swap[:color] || :white )
|
46
66
|
puts "tpi: #{swap[:tpi] }\tppi: #{swap[:ppi]}".colorize(swap[:color] || :white )
|
47
|
-
|
67
|
+
puts "NOTE: #{notice}".colorize(:light_white) unless notice.nil?
|
68
|
+
swap[:members].each {|x| puts member_to_human(x, side: swap[:side], format: format, daily: daily) }
|
48
69
|
end
|
49
70
|
end
|
50
71
|
|
51
72
|
# create a standardized name for the cache files
|
52
73
|
# and, on-the-fly, create these files plus their directory
|
53
74
|
def get_jsonl_name(interval:, swap_type:, contract:, sym: nil)
|
54
|
-
raise "Interval #{interval } is not supported, please choose from #{INTERVALS}" unless INTERVALS.include? interval
|
75
|
+
raise "Interval #{interval } is not supported, please choose from #{INTERVALS}" unless INTERVALS.include?(interval) || interval.is_a?(Integer)
|
55
76
|
raise "Swaptype #{swap_type} is not supported, please choose from #{SWAPTYPES}" unless SWAPTYPES.include? swap_type
|
56
77
|
sym ||= Cotcube::Helpers.get_id_set(contract: contract)
|
57
78
|
root = '/var/cotcube/level'
|
@@ -66,42 +87,136 @@ module Cotcube
|
|
66
87
|
|
67
88
|
# the name says it all.
|
68
89
|
# just note the addition of a digest, that serves to check whether same swap has been yet saved
|
69
|
-
# to the cache
|
70
|
-
|
90
|
+
# to the cache.
|
91
|
+
#
|
92
|
+
# there are actually 3 types of information, that are saved here:
|
93
|
+
# 1. a swap
|
94
|
+
# 2. an 'empty' information, referring to an interval that has been processed but no swaps were found
|
95
|
+
# 3. an 'exceeded' information, referring to another swap, that has been exceeded
|
96
|
+
#
|
97
|
+
def save_swaps(swaps, interval:, swap_type:, contract:, sym: nil, quiet: false)
|
71
98
|
file = get_jsonl_name(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
|
99
|
+
swaps = [ swaps ] unless swaps.is_a? Array
|
72
100
|
swaps.each do |swap|
|
73
|
-
|
101
|
+
raise "Illegal swap info: Must contain keys :datetime, :side... #{swap}" unless (%i[ datetime side ] - swap.keys).empty?
|
102
|
+
%i[ interval swap_type ].map {|key| swap.delete(key) }
|
103
|
+
sorted_keys = [ :datetime, :side ] + ( swap.keys - [ :datetime, :side ])
|
104
|
+
swap_json = swap.slice(*sorted_keys).to_json
|
74
105
|
digest = Digest::SHA256.hexdigest swap_json
|
75
|
-
res = `cat #{file} | grep #{digest}`.strip
|
106
|
+
res = `cat #{file} | grep '"digest":"#{digest}"'`.strip
|
76
107
|
unless res.empty?
|
77
|
-
puts "Cannot save swap, it is already in #{file}:"
|
78
|
-
p swap
|
108
|
+
puts "Cannot save swap, it is already in #{file}:".light_red unless quiet
|
109
|
+
p swap unless quiet
|
79
110
|
else
|
80
111
|
swap[:digest] = digest
|
81
|
-
|
112
|
+
sorted_keys += %i[digest]
|
113
|
+
File.open(file, 'a+'){|f| f.write(swap.slice(*sorted_keys).to_json + "\n") }
|
82
114
|
end
|
83
115
|
end
|
84
116
|
end
|
85
117
|
|
86
118
|
# loading of swaps is also straight forward
|
87
119
|
# it takes few more efforts to normalize the values to their expected format
|
88
|
-
def load_swaps(interval:, swap_type:, contract:, sym: nil)
|
120
|
+
def load_swaps(interval:, swap_type:, contract:, sym: nil, datetime: nil, recent: false, digest: nil, quiet: false, exceed: false)
|
89
121
|
file = get_jsonl_name(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
|
90
122
|
jsonl = File.read(file)
|
91
|
-
jsonl.
|
123
|
+
data = jsonl.
|
92
124
|
each_line.
|
93
125
|
map do |x|
|
94
126
|
JSON.parse(x).
|
95
127
|
deep_transform_keys(&:to_sym).
|
96
128
|
tap do |sw|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
sw[:
|
101
|
-
sw[:
|
129
|
+
sw[:datetime] = DateTime.parse(sw[:datetime]) rescue nil
|
130
|
+
(sw[:exceeded] = DateTime.parse(sw[:exceeded]) rescue nil) if sw[:exceeded]
|
131
|
+
sw[:interval] = interval
|
132
|
+
sw[:swap_type] = swap_type
|
133
|
+
sw[:contract] = contract
|
134
|
+
%i[ side ].each {|key| sw[key] = sw[key].to_sym rescue false }
|
135
|
+
unless sw[:empty] or sw[:exceeded]
|
136
|
+
sw[:color] = sw[:color].to_sym
|
137
|
+
sw[:members].map{|mem| mem[:datetime] = DateTime.parse(mem[:datetime]) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
# assign exceedance data to actual swaps
|
142
|
+
data.select{|swap| swap[:exceeded] }.each do |exc|
|
143
|
+
swap = data.find{|ref| ref[:digest] == exc[:ref]}
|
144
|
+
raise RuntimeError, "Inconsistent history for '#{exc}'. Origin not found." if swap.nil?
|
145
|
+
swap[:exceeded] = exc[:exceeded]
|
146
|
+
end
|
147
|
+
# do not return bare exceedance information
|
148
|
+
data.reject!{|swap| swap[:exceeded] and swap[:members].nil? }
|
149
|
+
# do not return swaps that are found 'later'
|
150
|
+
data.reject!{|swap| swap[:datetime] > datetime } unless datetime.nil?
|
151
|
+
# do not return exceeded swaps, that are exceeded in the past
|
152
|
+
recent = 7.days if recent.is_a? TrueClass
|
153
|
+
recent += 5.hours if recent
|
154
|
+
data.reject!{|swap| swap[:exceeded] and swap[:exceeded] < datetime - (recent ? recent : 0) } unless datetime.nil?
|
155
|
+
# remove exceedance information that is found 'later'
|
156
|
+
data.map{|swap| swap.delete(:exceeded) if swap[:exceeded] and swap[:exceeded] > datetime} unless datetime.nil?
|
157
|
+
unless digest.nil?
|
158
|
+
data.select! do |z|
|
159
|
+
(Cotcube::Helpers.sub(minimum: digest.length){ z[:digest] } === digest) and
|
160
|
+
not z[:empty]
|
161
|
+
end
|
162
|
+
case data.size
|
163
|
+
when 0
|
164
|
+
puts "No swaps found for digest '#{digest}'." unless quiet
|
165
|
+
when 1
|
166
|
+
sym ||= Cotcube::Helpers.get_id_set(contract: contract)
|
167
|
+
if not quiet or exceed
|
168
|
+
puts "Found 1 digest: "
|
169
|
+
data.each {|d| puts_swap( d, format: sym[:format], short: true, hash: digest.size + 2) }
|
170
|
+
if exceed
|
171
|
+
exceed = DateTime.now if exceed.is_a? TrueClass
|
172
|
+
mark_exceeded(swap: data.first, datetime: exceed)
|
173
|
+
puts "Swap marked exceeded."
|
174
|
+
end
|
175
|
+
end
|
176
|
+
else
|
177
|
+
sym ||= Cotcube::Helpers.get_id_set(contract: contract)
|
178
|
+
unless quiet
|
179
|
+
puts "Too many digests found for digest '#{digest}', please consider sending more figures: "
|
180
|
+
data.each {|d| puts_swap( d, format: sym[:format], short: true, hash: digest.size + 3)}
|
102
181
|
end
|
103
182
|
end
|
104
183
|
end
|
184
|
+
data
|
185
|
+
end
|
186
|
+
|
187
|
+
# :swaps is an array of swaps
|
188
|
+
# :zero is the current interval (ohlc)
|
189
|
+
# :stencil is the according current stencil (eod or intraday)
|
190
|
+
def check_exceedance(swaps:, zero:, stencil:, contract:, sym:, debug: false)
|
191
|
+
swaps.map do |swap|
|
192
|
+
# swaps cannot exceed the day they are found (or if they are found in the future)
|
193
|
+
next if swap[:datetime] >= zero[:datetime] or swap[:empty]
|
194
|
+
update = stencil.use with: swap, sym: sym, zero: zero
|
195
|
+
if update[:exceeded]
|
196
|
+
to_save = {
|
197
|
+
datetime: zero[:datetime],
|
198
|
+
ref: swap[:digest],
|
199
|
+
side: swap[:side],
|
200
|
+
exceeded: update[:exceeded]
|
201
|
+
}
|
202
|
+
save_swaps to_save, interval: swap[:interval], swap_type: swap[:swap_type], contract: contract, sym: sym, quiet: (not debug)
|
203
|
+
swap[:exceeded] = update[:exceeded]
|
204
|
+
end
|
205
|
+
%i[ current_change current_value current_diff current_dist ].map{|key| swap[key] = update[key] }
|
206
|
+
swap
|
207
|
+
end.compact
|
208
|
+
end
|
209
|
+
|
210
|
+
def mark_exceeded(swap:, datetime:, debug: false)
|
211
|
+
to_save = {
|
212
|
+
datetime: datetime,
|
213
|
+
ref: swap[:digest],
|
214
|
+
side: swap[:side],
|
215
|
+
exceeded: datetime
|
216
|
+
}
|
217
|
+
save_swaps to_save, interval: swap[:interval], swap_type: swap[:swap_type], sym: Cotcube::Helpers.get_id_set(contract: swap[:contract]), contract: swap[:contract], quiet: (not debug)
|
218
|
+
swap[:exceeded] = datetime
|
219
|
+
swap
|
105
220
|
end
|
106
221
|
|
107
222
|
end
|
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cotcube
|
2
4
|
module Level
|
3
|
-
|
5
|
+
|
4
6
|
class Intraday_Stencil
|
5
7
|
|
6
|
-
GLOBAL_SOW = { 'CT' => '0000-1700' }
|
7
|
-
GLOBAL_EOW = { 'CT' => '1700-0000' }
|
8
|
-
GLOBAL_EOD = { 'CT' => '1600-1700' }
|
9
8
|
|
9
|
+
# Class method that loads the (latest) shiftset for given asset
|
10
|
+
# These raw stencils are located in /var/cotcube/level/stencils/shiftsets.csv
|
11
|
+
#
|
10
12
|
|
11
13
|
def self.shiftset(asset:, sym: nil)
|
12
14
|
shiftset_file = '/var/cotcube/level/stencils/shiftsets.csv'
|
@@ -18,45 +20,53 @@ module Cotcube
|
|
18
20
|
sym ||= Cotcube::Helpers.get_id_set(symbol: asset)
|
19
21
|
current_set = shiftsets.find{|s| s[:symbols] =~ /#{sym[:type]}/ }
|
20
22
|
return current_set.tap{|s| headers.map{|h| s[h] = nil if s[h] == '---------' }; s[:rth5] ||= s[:rth]; s[:mpost5] ||= s[:mpost] } unless current_set.nil?
|
21
|
-
raise "Cannot get shiftset for #{sym[:type]}: #{asset}, please prepare #{shiftset_file} before!"
|
23
|
+
raise "Cannot get shiftset for #{sym[:type]}: #{asset}, please prepare #{shiftset_file} before!"
|
22
24
|
end
|
23
25
|
|
24
26
|
attr_reader :base, :shiftset, :timezone, :datetime, :zero, :index
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
#
|
39
|
-
|
40
|
-
@
|
41
|
-
@
|
42
|
-
@
|
28
|
+
|
29
|
+
|
30
|
+
def initialize(
|
31
|
+
asset:,
|
32
|
+
interval: 30.minutes,
|
33
|
+
swap_type: :full,
|
34
|
+
datetime: nil,
|
35
|
+
debug: false,
|
36
|
+
weeks: 6,
|
37
|
+
future: 2,
|
38
|
+
version: nil, # when referring to a specicic version of the stencil
|
39
|
+
stencil: nil, # instead of preparing, use this one if set
|
40
|
+
warnings: true # be more quiet
|
41
|
+
)
|
42
|
+
@shiftset = Intraday_Stencil.shiftset(asset: asset)
|
43
|
+
@timezone = Cotcube::Level::TIMEZONES[@shiftset[:tz]]
|
44
|
+
@debug = debug
|
45
|
+
@interval = interval
|
46
|
+
@swap_type = swap_type
|
47
|
+
@warnings = warnings
|
48
|
+
datetime ||= DateTime.now
|
49
|
+
datetime = @timezone.at(datetime.to_i) unless datetime.is_a? ActiveSupport::TimeWithZone
|
50
|
+
@datetime = datetime.beginning_of_day
|
51
|
+
@datetime += interval while @datetime <= datetime - interval
|
52
|
+
@datetime -= interval
|
53
|
+
|
43
54
|
const = "RAW_INTRA_STENCIL_#{@shiftset[:nr]}_#{interval.in_minutes.to_i}".to_sym
|
44
55
|
if Object.const_defined? const
|
45
56
|
@base = (Object.const_get const).map{|z| z.dup}
|
46
57
|
else
|
47
|
-
|
48
58
|
start_time = lambda {|x| @shiftset[x].split('-').first rescue '' }
|
49
59
|
start_hours = lambda {|x| @shiftset[x].split('-').first[ 0.. 1].to_i.send(:hours) rescue 0 }
|
50
60
|
start_minutes = lambda {|x| @shiftset[x].split('-').first[-2..-1].to_i.send(:minutes) rescue 0 }
|
51
61
|
end_time = lambda {|x| @shiftset[x].split('-').last rescue '' }
|
52
62
|
end_hours = lambda {|x| @shiftset[x].split('-').last [ 0.. 1].to_i.send(:hours) rescue 0 }
|
53
|
-
end_minutes = lambda {|x| @shiftset[x].split('-').last [-2..-1].to_i.send(:minutes) rescue 0 }
|
63
|
+
end_minutes = lambda {|x| @shiftset[x].split('-').last [-2..-1].to_i.send(:minutes) rescue 0 }
|
54
64
|
|
55
|
-
|
65
|
+
runner = (@datetime -
|
56
66
|
weeks * 7.days).beginning_of_week(:sunday)
|
57
67
|
tm_runner = lambda { runner.strftime('%H%M') }
|
58
|
-
@base = []
|
59
|
-
(weeks+future).times do
|
68
|
+
@base = []
|
69
|
+
(weeks+future).times do
|
60
70
|
while tm_runner.call < GLOBAL_SOW[@shiftset[:tz]].split('-').last
|
61
71
|
# if daylight is switched, this phase will be shorter or longer
|
62
72
|
@base << { datetime: runner, type: :sow }
|
@@ -81,10 +91,10 @@ module Cotcube
|
|
81
91
|
yet_rth = true
|
82
92
|
end
|
83
93
|
end
|
84
|
-
while ((sophase > eophase) ? (tm_runner.call >= sophase or tm_runner.call < eophase) : (tm_runner.call < eophase))
|
94
|
+
while ((sophase > eophase) ? (tm_runner.call >= sophase or tm_runner.call < eophase) : (tm_runner.call < eophase))
|
85
95
|
current = { datetime: runner, type: phase }
|
86
96
|
if phase == :rth and not yet_rth
|
87
|
-
current[:block] = true
|
97
|
+
current[:block] = true
|
88
98
|
yet_rth = true
|
89
99
|
end
|
90
100
|
@base << current
|
@@ -100,80 +110,104 @@ module Cotcube
|
|
100
110
|
while runner < end_of_week
|
101
111
|
@base << { datetime: runner, type: :eow }
|
102
112
|
runner += interval
|
103
|
-
end
|
113
|
+
end
|
104
114
|
end
|
105
115
|
Object.const_set(const, @base.map{|z| z.dup})
|
106
116
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
117
|
+
|
118
|
+
case swap_type
|
119
|
+
when :full
|
120
|
+
@base.select!{|x| %i[ pre rth post ].include?(x[:type]) }
|
121
|
+
when :rth
|
122
|
+
@base.select!{|x| x[:type] == :rth }
|
123
|
+
when :flow
|
124
|
+
@base.reject!{|x| %i[ sow eow mpost mpost ].include?(x[:type]) }
|
125
|
+
@base.
|
126
|
+
map{ |x|
|
127
|
+
[:high, :low, :volume].map{|z| x[z] = nil} unless x[:type] == :rth
|
128
|
+
}
|
129
|
+
when :run
|
130
|
+
@base.select!{|x| %i[ pre rth post ].include? x[:type]}
|
131
|
+
else
|
132
|
+
raise ArgumentError, "Unknown stencil/swap type '#{swap_type}'"
|
133
|
+
end
|
134
|
+
@base.map!{|z| z.dup}
|
135
|
+
|
136
|
+
# zero is, were either x[:datetime] == @datetime (when we are intraday)
|
137
|
+
# or otherwise {x[:datetime] <= @datetime}.last (when on maintenance)
|
138
|
+
@index = @base.index{|x| x == @base.select{|y| y[:datetime] <= @datetime }.last }
|
139
|
+
@index -= 1 while %i[sow sod mpre mpost eod eow].include? @base[@index][:type]
|
110
140
|
@datetime = @base[@index][:datetime]
|
111
141
|
@zero = @base[@index]
|
112
|
-
counter = 0
|
142
|
+
counter = 0
|
113
143
|
while @base[@index - counter] and @index - counter >= 0
|
114
144
|
@base[@index - counter][:x] = counter
|
115
145
|
counter += 1
|
116
146
|
end
|
117
|
-
counter = 0
|
147
|
+
counter = 0
|
118
148
|
while @base[@index + counter] and @index + counter < @base.length
|
119
149
|
@base[@index + counter][:x] = -counter
|
120
150
|
counter += 1
|
121
151
|
end
|
122
|
-
@base.select!{|z| z[:x] <= 0 or z[:high]}
|
123
152
|
end
|
124
153
|
|
125
|
-
|
126
|
-
|
154
|
+
=begin
|
155
|
+
def dup
|
156
|
+
Intraday_Stencil.new(
|
157
|
+
debug: @debug,
|
158
|
+
interval: @interval,
|
159
|
+
swap_type: @swap_type,
|
160
|
+
datetime: @datetime,
|
161
|
+
stencil: @base.map{|x| x.dup}
|
162
|
+
)
|
127
163
|
end
|
164
|
+
=end
|
128
165
|
|
129
|
-
|
130
|
-
|
166
|
+
def zero
|
167
|
+
@zero ||= @base.find{|b| b[:x].zero? }
|
168
|
+
end
|
169
|
+
|
170
|
+
def apply(to: )
|
131
171
|
offset = 0
|
132
|
-
|
172
|
+
@base.each_index do |i|
|
133
173
|
begin
|
134
|
-
offset += 1 while
|
135
|
-
puts "#{i}\t#{offset}\t#{@base[i+offset][:datetime]} < #{to[i][:datetime]}" if debug
|
174
|
+
offset += 1 while to[i+offset][:datetime] < @base[i][:datetime]
|
136
175
|
rescue
|
137
176
|
# appending
|
138
|
-
|
139
|
-
@base << to[i]
|
177
|
+
to << @base[i]
|
140
178
|
next
|
141
179
|
end
|
142
|
-
if
|
180
|
+
if to[i+offset][:datetime] > @base[i][:datetime]
|
143
181
|
# skipping
|
144
|
-
puts "skipping #{i}\t#{offset}\t#{@base[i+offset][:datetime]} < #{to[i][:datetime]}" if debug
|
145
182
|
offset -= 1
|
146
183
|
next
|
147
184
|
end
|
148
185
|
# merging
|
149
|
-
|
150
|
-
|
151
|
-
puts "MERGED:\t#{i}\t#{offset}\t#{@base[j]}" if debug
|
186
|
+
to[i+offset][:x] = @base[i][:x]
|
187
|
+
to[i+offset][:type] = @base[i][:type]
|
152
188
|
end
|
153
189
|
# finally remove all bars that do not belong to the stencil (i.e. holidays)
|
154
|
-
|
155
|
-
when :full
|
156
|
-
@base.select!{|x| %i[ pre rth post ].include?(x[:type]) }
|
157
|
-
when :rth
|
158
|
-
@base.select!{|x| x[:type] == :rth }
|
159
|
-
# to.map{ |x| [:high, :low, :volume].map{|z| x[z] = nil} if x[:block] }
|
160
|
-
when :flow
|
161
|
-
@base.reject!{|x| %i[ meow postmm postmm5 ].include?(x[:type]) }
|
162
|
-
@base.
|
163
|
-
map{ |x|
|
164
|
-
[:high, :low, :volume].map{|z| x[z] = nil} unless x[:type] == :rth
|
165
|
-
# [:high, :low, :volume].map{|z| x[z] = nil} if x[:block]
|
166
|
-
}
|
167
|
-
when :run
|
168
|
-
@base.select!{|x| %i[ premarket rth postmarket ].include? x[:type]}
|
169
|
-
else
|
170
|
-
raise ArgumentError, "Unknown stencil/swap type '#{type}'"
|
171
|
-
end
|
172
|
-
@base.map!{|z| z.dup}
|
190
|
+
to.reject!{|x| x[:x].nil? }
|
173
191
|
end
|
174
192
|
|
193
|
+
def use(with:, sym:, zero:, grace: -2)
|
194
|
+
# todo: validate with (check if vslid swap
|
195
|
+
# sym (check keys)
|
196
|
+
# zero (ohlc with x.zero?)
|
197
|
+
# side ( upper or lower)
|
198
|
+
swap = with.dup
|
199
|
+
high = swap[:side] == :upper
|
200
|
+
ohlc = high ? :high : :low
|
201
|
+
start = base.find{|x| swap[:datetime] == x[:datetime]}
|
202
|
+
swap[:current_change] = (swap[:tpi] * start[:x]).round(8)
|
203
|
+
swap[:current_value] = swap[:members].last[ ohlc ] + swap[:current_change] * sym[:ticksize]
|
204
|
+
swap[:current_diff] = (swap[:current_value] - zero[ohlc]) * (high ? 1 : -1 )
|
205
|
+
swap[:current_dist] = (swap[:current_diff] / sym[:ticksize]).to_i
|
206
|
+
swap[:exceeded] = zero[:datetime] if swap[:current_dist] < grace
|
207
|
+
swap
|
208
|
+
end
|
175
209
|
end
|
176
210
|
|
177
|
-
Intraday_Stencils = Intraday_Stencil
|
178
211
|
end
|
212
|
+
|
179
213
|
end
|
@@ -9,15 +9,19 @@ module Cotcube
|
|
9
9
|
range: (0..-1), # range is relative to base
|
10
10
|
max: 90, # the range which to scan for swaps goes from deg 0 to max
|
11
11
|
debug: false,
|
12
|
-
min_rating: 3, # swaps having a lower rating are discarded
|
13
|
-
min_length: 8, # shorter swaps are discared
|
12
|
+
min_rating: 3, # 1st criteria: swaps having a lower rating are discarded
|
13
|
+
min_length: 8, # 2nd criteria: shorter swaps are discared
|
14
|
+
min_ratio: # 3rd criteria: the ratio between rating and length (if true, swap is discarded)
|
15
|
+
lambda {|r,l| r < l / 4.0 },
|
14
16
|
save: true, # allow saving of results
|
15
17
|
cached: true, # allow loading of cached results
|
16
18
|
interval: , # interval (currently) is one of %i[ daily continuous halfs ]
|
17
19
|
swap_type: nil, # if not given, a warning is printed and swaps won't be saved or loaded
|
18
20
|
with_flaws: 0, # the maximum amount of consecutive bars that would actually break the current swap
|
19
21
|
# should be set to 0 for dailies and I suggest no more than 3 for intraday
|
20
|
-
|
22
|
+
manual: false, # some triggers must be set differently when manual entry is used
|
23
|
+
deviation: 2 # the maximum shift of :x-values of found members
|
24
|
+
)
|
21
25
|
|
22
26
|
raise ArgumentError, "'0 < max < 90, but got '#{max}'" unless max.is_a? Numeric and 0 < max and max <= 90
|
23
27
|
raise ArgumentError, 'need :side either :upper or :lower for dots' unless [:upper, :lower].include? side
|
@@ -26,7 +30,7 @@ module Cotcube
|
|
26
30
|
# init some helpers
|
27
31
|
#
|
28
32
|
high = side == :upper
|
29
|
-
|
33
|
+
first = base.to_a.find{|x| not x[:high].nil? }
|
30
34
|
zero = base.select{|x| x[:x].zero? }
|
31
35
|
raise ArgumentError, "Inappropriate base, it should contain ONE :x.zero, but contains #{zero.size}." unless zero.size==1
|
32
36
|
zero = zero.first
|
@@ -34,15 +38,16 @@ module Cotcube
|
|
34
38
|
contract ||= zero[:contract]
|
35
39
|
sym ||= Cotcube::Helpers.get_id_set(contract: contract)
|
36
40
|
|
41
|
+
|
37
42
|
if cached
|
38
43
|
if interval.nil? or swap_type.nil?
|
39
44
|
puts "Warning: Cannot use cache as both :interval and :swap_type must be given".light_yellow
|
40
45
|
else
|
41
|
-
cache = load_swaps(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
|
42
|
-
# if the current datetime
|
46
|
+
cache = load_swaps(interval: interval, swap_type: swap_type, contract: contract, sym: sym, datetime: zero[:datetime])
|
47
|
+
# if the current datetime was already processed but nothing has been found,
|
43
48
|
# an 'empty' value is saved.
|
44
|
-
# that means, if neither
|
45
|
-
selected = cache.select{|sw| sw[:datetime] == zero[:datetime] and sw[:side] == side}
|
49
|
+
# that means, if neither a swap (or more) nor :empty is found, the datetime has not been processed yet
|
50
|
+
selected = cache.select{|sw| sw[:datetime] == zero[:datetime] and sw[:side] == side }
|
46
51
|
unless selected.empty?
|
47
52
|
puts 'cache_hit'.light_white if debug
|
48
53
|
return (selected.first[:empty] ? [] : selected )
|
@@ -66,8 +71,10 @@ module Cotcube
|
|
66
71
|
|
67
72
|
# abs_peak is the absolute high / low of the base. the shearing operation ends there,
|
68
73
|
# but results might be influenced when abs_peak becomes affected by :with_flaws
|
69
|
-
|
70
|
-
|
74
|
+
unless manual
|
75
|
+
abs_peak = base.send(high ? :max_by : :min_by){|x| x[high ? :high : :low] }[:datetime]
|
76
|
+
base.reject!{|x| x[:datetime] < abs_peak}
|
77
|
+
end
|
71
78
|
|
72
79
|
###########################################################################################################################z
|
73
80
|
# only if (and only if) the range portion above change the underlying base
|
@@ -100,13 +107,12 @@ module Cotcube
|
|
100
107
|
finalize = lambda do |results|
|
101
108
|
results.map do |result|
|
102
109
|
result[:members].each do |member|
|
103
|
-
next if member[:yy].nil? or member[:yy].zero?
|
110
|
+
next if member[:yy].nil? or member[:yy].round(PRECISION-5).zero?
|
104
111
|
|
105
112
|
diff = (member[:x] - member[:dx]).abs / 2.0
|
106
113
|
member[:dx] = member[:x] + diff
|
107
114
|
# it employs another binary-search
|
108
|
-
while member[:yy].round(PRECISION) != 0
|
109
|
-
print '.' if debug
|
115
|
+
while member[:yy].round(PRECISION-5) != 0.0
|
110
116
|
member[:yy] = shear_to_deg(deg: result[:deg], base: [ member ] ).first[:yy]
|
111
117
|
diff /= 2.0
|
112
118
|
if member[:yy] > 0
|
@@ -115,7 +121,7 @@ module Cotcube
|
|
115
121
|
member[:dx] -= diff
|
116
122
|
end
|
117
123
|
end
|
118
|
-
member[:yy] = member[:yy].abs.round(
|
124
|
+
member[:yy] = member[:yy].abs.round(PRECISION-5)
|
119
125
|
end
|
120
126
|
|
121
127
|
puts 'done!'.magenta if debug
|
@@ -152,7 +158,7 @@ module Cotcube
|
|
152
158
|
# first member is solitary
|
153
159
|
if new_members.empty?
|
154
160
|
mem_sorted=members.sort
|
155
|
-
if mem_sorted[1] == mem_sorted[0] + 1
|
161
|
+
if mem_sorted[1] == mem_sorted[0] + 1 and not manual
|
156
162
|
b2 = b[mem_sorted[1]..mem_sorted[-1]].map{|x| x.dup; x[:dx] = nil; x}
|
157
163
|
puts 'starting recursive rerun'.light_red if debug
|
158
164
|
alternative_slope = get_slope.call(b2)
|
@@ -174,7 +180,7 @@ module Cotcube
|
|
174
180
|
current_slope[:members] << b[i] unless current_slope[:members].map{|x| x[:datetime]}.include? b[i][:datetime]
|
175
181
|
current_slope[:members].sort_by!{|x| x[:datetime]}
|
176
182
|
end
|
177
|
-
current_slope
|
183
|
+
return current_slope
|
178
184
|
|
179
185
|
end
|
180
186
|
# all new members found in current iteration have now receive their new :x value, depending on their distance to
|
@@ -215,6 +221,7 @@ module Cotcube
|
|
215
221
|
swap_base = shear_to_deg(base: swap_base, deg: swap[:deg])
|
216
222
|
swap_base.map!{|x| x[:dev] = (x[:yy] / sym[:ticksize].to_f); x[:dev] = -( x[:dev] > 0 ? x[:dev].floor : x[:dev].ceil); x}
|
217
223
|
invalids = swap_base.select{|x| x[:dev] < 0 }
|
224
|
+
with_flaws = 0 unless with_flaws # support legacy versions, where with_flaws was boolean
|
218
225
|
if with_flaws > 0
|
219
226
|
# TODO: this behaves only as expected when with_flaws == 2
|
220
227
|
last_invalid = invalids[(invalids[-2][:i] + 1 == invalids[-1][:i] ) ? -3 : -2] rescue nil
|
@@ -243,6 +250,8 @@ module Cotcube
|
|
243
250
|
swap[:avg_dev] = (swap_base.reject{|x| x[:dev].zero?}.map{|x| x[:dev].abs}.reduce(:+) / (swap_base.size - swap[:members].size).to_f).ceil rescue 0
|
244
251
|
# depth: the maximum distance to the swap line
|
245
252
|
swap[:depth] = swap_base.max_by{|x| x[:dev]}[:dev]
|
253
|
+
swap[:interval] = interval
|
254
|
+
swap[:swap_type] = swap_type
|
246
255
|
swap[:raw] = swap[:members].map{|x| x[:x]}.reverse
|
247
256
|
swap[:size] = swap[:members].size
|
248
257
|
swap[:length] = swap[:raw][-1] - swap[:raw][0]
|
@@ -256,11 +265,11 @@ module Cotcube
|
|
256
265
|
unless %i[ daily continuous ].include? interval
|
257
266
|
swap[:color] = ((rat > 150) ? :light_blue : (rat > 80) ? :magenta : (rat > 30) ? :light_magenta : (rat > 15) ? :light_yellow : high ? :green : :red)
|
258
267
|
end
|
259
|
-
swap[:diff] = swap[:members].last[ high ? :high : :low ] - swap[:members].first[ high ? :high : :low ]
|
268
|
+
swap[:diff] = (swap[:members].last[ high ? :high : :low ] - swap[:members].first[ high ? :high : :low ]).round(8)
|
260
269
|
swap[:ticks] = (swap[:diff] / sym[:ticksize]).to_i
|
261
270
|
# tpi: ticks per interval, how many ticks are passed each :interval
|
262
271
|
swap[:tpi] = (swap[:ticks].to_f / swap[:length]).round(3)
|
263
|
-
# ppi: power per interval, how many dollar value is passed each :interval
|
272
|
+
# ppi: power per interval, how many $dollar value is passed each :interval
|
264
273
|
swap[:ppi] = (swap[:tpi] * sym[:power]).round(3)
|
265
274
|
end # swap
|
266
275
|
end # lambda
|
@@ -303,7 +312,7 @@ module Cotcube
|
|
303
312
|
binding.irb if debug
|
304
313
|
|
305
314
|
# reject all results that do not suffice
|
306
|
-
current_results.reject!{|swap| swap[:rating] < min_rating or swap[:length] < min_length or swap[:rating]
|
315
|
+
current_results.reject!{|swap| swap[:rating] < min_rating or swap[:length] < min_length or min_ratio.call(swap[:rating],swap[:length])}
|
307
316
|
|
308
317
|
#####################################################################################################################3
|
309
318
|
# finally save results for caching and return them
|
@@ -312,7 +321,7 @@ module Cotcube
|
|
312
321
|
puts "WARNING: Cannot save swaps, as both :interval and :swap_type must be given".colorize(:light_yellow)
|
313
322
|
else
|
314
323
|
current_results.map{|sw| mem = sw[:members]; sw[:slope] = (mem.last[:y] - mem.first[:y]) / (mem.last[mem.last[:dx].nil? ? :x : :dx] - mem.first[mem.first[:dx].nil? ? :x : :dx]).to_f }
|
315
|
-
to_save = current_results.empty? ? [ { datetime: zero[:datetime], side: side, empty: true } ] : current_results
|
324
|
+
to_save = current_results.empty? ? [ { datetime: zero[:datetime], side: side, empty: true, interval: interval, swap_type: swap_type } ] : current_results
|
316
325
|
save_swaps(to_save, interval: interval, swap_type: swap_type, contract: contract, sym: sym)
|
317
326
|
end
|
318
327
|
end
|
data/lib/cotcube-level.rb
CHANGED
@@ -35,10 +35,11 @@ module Cotcube
|
|
35
35
|
:shear_to_rad, # same all below
|
36
36
|
:rad2deg,
|
37
37
|
:deg2rad,
|
38
|
-
:
|
38
|
+
:puts_swap,
|
39
39
|
:save_swaps,
|
40
40
|
:get_jsonl_name,
|
41
41
|
:load_swaps,
|
42
|
+
:check_exceedance,
|
42
43
|
:member_to_human
|
43
44
|
|
44
45
|
# please note that module_functions of sources provided in non-public files must slso be published within these
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cotcube-level
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.1
|
4
|
+
version: 0.3.4.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-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -116,7 +116,6 @@ executables: []
|
|
116
116
|
extensions: []
|
117
117
|
extra_rdoc_files: []
|
118
118
|
files:
|
119
|
-
- ".irbrc.rb"
|
120
119
|
- CHANGELOG.md
|
121
120
|
- Gemfile
|
122
121
|
- README.md
|
data/.irbrc.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
def verbose_toggle
|
2
|
-
irb_context.echo ? irb_context.echo = false : irb_context.echo = true
|
3
|
-
end
|
4
|
-
|
5
|
-
alias vt verbose_toggle
|
6
|
-
|
7
|
-
$debug = true
|
8
|
-
IRB.conf[:USE_MULTILINE] = false
|
9
|
-
# require 'bundler'
|
10
|
-
# Bundler.require
|
11
|
-
|
12
|
-
require_relative 'lib/cotcube-level'
|