cotcube-level 0.3.1 → 0.3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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'
|