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 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'