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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7756ccc0318477cf223fc205720c986daaaad4304439108b960ca410a15acf79
4
- data.tar.gz: ba7a79e928da4c89b0e7103394899096ab42f5977986892c5e7396fe5d4abeff
3
+ metadata.gz: 71bc6b4ca29ab4ff089b95568a0d592d4bc2b3d8c1824ea9158d661792f1c5b7
4
+ data.tar.gz: 670d5a69457411fc7539542a1bd0ad1712ef9111f7b5a876b3c7bdd5d6de90c0
5
5
  SHA512:
6
- metadata.gz: ceb38b4a6c827b0d8a7b09e0c0f86e9050e49dce54504cf5075d2d2da5fb9fe99186c8e9aa3403a14b61d5d6fa831e86a43886279dea8e20efd1c818e119d1f0
7
- data.tar.gz: f53b20c6848b4d7cc825a18819ace61ebade1238706dbd125e76318eeb2dd1e4abbc6dca2d9872accb0069df8ac9edb16762c45ea7bc9a8293b95452d36102a6
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
- bar
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', member[:x]
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, 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?
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
- # list all swaps contained in an array (e.g. result of tritangulate)
41
- def puts_swaps(swaps, format: )
42
- swaps = [ swaps ] unless swaps.is_a? Array
43
- swaps.each do |swap|
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
- swap[:members].each {|x| puts member_to_human(x, side: swap[:side], format: format) }
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
- def save_swaps(swaps, interval:, swap_type:, contract:, sym: nil)
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
- swap_json = swap.to_json
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
- File.open(file, 'a+'){|f| f.write(swap.to_json + "\n") }
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
- sw[:datetime] = DateTime.parse(sw[:datetime]) rescue nil
98
- sw[:side] = sw[:side].to_sym
99
- unless sw[:empty]
100
- sw[:color] = sw[:color].to_sym
101
- sw[:members].map{|mem| mem[:datetime] = DateTime.parse(mem[:datetime]) }
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
- # TODO: Missing documentation of shiftsets, swap_types and stencil_types
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
- # asset: the asset the stencil will be applied to--or :full, if default stencil is desired
27
- # datetime: the datetime that will become 'zero'.
28
- # it will be calculated to the beginning of the previous interval
29
- # it must match the timezone of the asset
30
- # interval: the interval as minutes
31
- # weeks: the amount of weeks before the beginning of the current week
32
- # future: the amount of weeks after the beginning of the current week
33
- def initialize(asset:, sym: nil, datetime:, interval:, weeks:, future: 1, debug: false, type:, base: )
34
- @shiftset = Intraday_Stencils.shiftset(asset: asset)
35
- @timezone = TIMEZONES[@shiftset[:tz]]
36
- @debug = debug
37
- datetime = @timezone.at(datetime.to_i) unless datetime.is_a? ActiveSupport::TimeWithZone
38
- # slight flaw, as datetime does not carry the actuall timezone information but just the abbr. timezone name (like CDT or CEST)
39
- raise "Zone mismatch: Timezone of asset is #{@timezone.now.zone} but datetime given is #{dateime.zone}" unless @timezone.now.zone == datetime.zone
40
- @datetime = datetime.beginning_of_day
41
- @datetime += interval while @datetime <= datetime - interval
42
- @datetime -= interval
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
- runner = (@datetime -
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
- self.apply to: base, type: type
108
- @index = @base.index{|x| x[:datetime] == @datetime }
109
- @index -= 1 while %i[sow sod mpre mpost eod eow].include? @base[@index][:type]
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
- def apply!(to:, type:)
126
- apply(to: to, type: type, force: true)
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
- # :force will apply values to each bar regardless of existing ones
130
- def apply(to:, type:, force: false, debug: false)
166
+ def zero
167
+ @zero ||= @base.find{|b| b[:x].zero? }
168
+ end
169
+
170
+ def apply(to: )
131
171
  offset = 0
132
- to.each_index do |i|
172
+ @base.each_index do |i|
133
173
  begin
134
- offset += 1 while @base[i+offset][:datetime] < to[i][:datetime]
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
- puts "appending #{i}\t#{offset}\t#{@base[i+offset][:datetime]} < #{to[i][:datetime]}" if debug
139
- @base << to[i]
177
+ to << @base[i]
140
178
  next
141
179
  end
142
- if @base[i+offset][:datetime] > to[i][:datetime]
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
- j = i + offset
150
- @base[j]=@base[j].merge(to[i]) if force or (@base[j][:high].nil? and @base[j][:low].nil?)
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
- case type
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
- deviation: 2) # the maximum shift of :x-values of found members
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
- # DELETE first = base.to_a.find{|x| not x[:high].nil? }
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 has been yet processed but nothing has been found,
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 an array of swaps nor :empty is found, the datetime has not been processed yet
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
- abs_peak = base.send(high ? :max_by : :min_by){|x| x[high ? :high : :low] }[:datetime]
70
- base.reject!{|x| x[:datetime] < abs_peak}
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(8)
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] < swap[:length] / 4.to_f}
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
- :puts_swaps,
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-08-24 00:00:00.000000000 Z
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'