cotcube-level 0.2.0 → 0.3.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: c1c9fc6eb075ccc486c29c13d9989e52b5e659330cba2b900bdd4f420d085bb8
4
- data.tar.gz: 6303babcc58fd8def53c747174c0feaa669c848787c0c32eaa811e0f76aad61f
3
+ metadata.gz: 7756ccc0318477cf223fc205720c986daaaad4304439108b960ca410a15acf79
4
+ data.tar.gz: ba7a79e928da4c89b0e7103394899096ab42f5977986892c5e7396fe5d4abeff
5
5
  SHA512:
6
- metadata.gz: a5bc9e406caecd100ad2aebaf5934f9c2b6f0b3c43dc51aa76b63575bb52bd77fec7b06f5b183801149f6b35fb367462261e93bb2c174ef8b10b29bb61ee2370
7
- data.tar.gz: b1de063e02b133b76aea5ffe01b357fad9b9ed99461225cf8780b7cc592ea00bd043afef6d957a3a2f78db02e75291bfac775aa2c56c3cd36886378fbd7f7767
6
+ metadata.gz: ceb38b4a6c827b0d8a7b09e0c0f86e9050e49dce54504cf5075d2d2da5fb9fe99186c8e9aa3403a14b61d5d6fa831e86a43886279dea8e20efd1c818e119d1f0
7
+ data.tar.gz: f53b20c6848b4d7cc825a18819ace61ebade1238706dbd125e76318eeb2dd1e4abbc6dca2d9872accb0069df8ac9edb16762c45ea7bc9a8293b95452d36102a6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.3.1 (August 24, 2021)
2
+ - renaming triangulation to tritangulation
3
+ - minor fixes in README and gemspec
4
+
5
+ ## 0.3.0 (August 24, 2021)
6
+ - removed tests, moving to cotcube-jobs
7
+ - all: added documentation
8
+ - triangulate: added continuous support and documentation
9
+ - eod_stencil: added support for continuous futures
10
+ - adapted tests to recent changes
11
+ - cotcube-level: included intraday_stencil
12
+ - intraday_stencil: added to repo
13
+ - eod_stencil: fixing minor typo
14
+ - triangulate: fixed indention
15
+ - stencil: slight changes, incl rename to EODStencil
16
+ - detect_slope: now returns all members, regardless of amount (i.e. check whether it is a valid swap will be done later)
17
+
1
18
  ## 0.2.0 (August 17, 2021)
2
19
  - cotcube-level: added new module_functions, added restrictive constants for intervals and swaptypes
3
20
  - adding new features to so-called 'test suite'
data/README.md CHANGED
@@ -5,20 +5,20 @@ of the development process. All in the beginning was the wish to create an algor
5
5
  lines. So for like 2 year I was whirling my head on how to put an algorithmic ruler on a chart and rotate it until
6
6
  one or a set of trend lines are concise outpout.
7
7
 
8
- As most hard do accomplish thing turn out to be much easier when you put them up side down, same happend to me in this
8
+ As most hard to accomplish things turn out to be much easier when you put them up side down, same happened to me in this
9
9
  matter. I found rotating the ruler is too hard for, but instead transforming (shearing) the chart itself while keeping
10
10
  the ruler at its _level_ is much more eligible.
11
11
 
12
12
  The idea and the development of the algorithm had taken place within another Cotcube project named 'SwapSeeker'. At
13
- some point the SwapSeeker has become too complex so I decided to decouple the Level and the Stencil as independent
13
+ some point the SwapSeeker has become too complex so I decided to decouple the Level and the Stencils as separate
14
14
  functional unit, that can be used on arbitrary time series.
15
15
 
16
16
  ### The shear mapping
17
17
 
18
18
  There is really no magic in it. The timeseries (or basically an interval of a timeseries) needs to be prepared to locate
19
19
  in Cartesian Quadrant I with fitting x==0 to y==0, and then a binary search on shearing angles determines the resulting
20
- muiltitangent of which as the origin ('now') one point already is given. As limitation only shearing between 0 and 90
21
- deg is supported.
20
+ muiltitangent of which the origin ('now') as one point already is given. One limitation applies: Only shearing between
21
+ 0 and 90 degrees is supported.
22
22
 
23
23
  The result contains
24
24
 
@@ -28,17 +28,25 @@ to happen if _deg -> 0_.
28
28
  - the origin and two or more points residing on the same level--what is the desired result showing mathematical accuracy where
29
29
  human eye cannot detect it in time.
30
30
 
31
+ ### Tritangulation
32
+
33
+ The 'tri' is about that to find an artificially supported slope (i.e. a swap) at least 3 points
34
+ have to reside on it. It is leaned to triangulate, but instead of working with angles we are working with tangents here.
35
+
36
+ Shear mapping and slope detection might find a slope meeting the requirements. But in tritangulate, near misses are
37
+ considered and added as members.
38
+
31
39
  ### The stencil
32
40
 
33
41
  The shearing transformation is based on _x_ and _y_ values, where y obviously are the values of the series while _x_
34
42
  refers to the time. It turned out a much bigger challenge to create an expedient mapping from DateTime to Integer,
35
43
  where, as you foresee, 'now' should result in _x.zero?_.
36
44
 
37
- ## Usage
45
+ ## Usage (pure template text)
38
46
 
39
47
  TODO: Write usage instructions here
40
48
 
41
- ## Development
49
+ ## Development (pure template text)
42
50
 
43
51
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
44
52
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.1
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.email = ['donkeybridge@jtown.eu']
8
8
 
9
9
  spec.summary = 'A gem to shear a time series '
10
- spec.description = 'A gem to shear a time series, basically serving as a yet unssen class of indicators.'
10
+ spec.description = 'A gem to shear a time series, basically serving as a yet unseen class of indicators.'
11
11
 
12
12
  spec.homepage = 'https://github.com/donkeybridge/' + spec.name
13
13
  spec.license = 'BSD-3-Clause'
@@ -1,117 +1,98 @@
1
1
  module Cotcube
2
2
  module Level
3
- #
4
- # TODO: add support for slopes not only exactly matching but also allow 'dip' of n x ticksize
5
- def detect_slope(base:, max: 90, debug: false, format: '% 5.2f', calculus: false, ticksize: nil, max_dev: 200)
6
- raise ArgumentError, "'0 < max < 90, but got '#{max}'" unless max.is_a? Numeric and 0 < max and max <= 90
7
- #
8
- # aiming for a shearing angle, all but those in a line below the abscissa
9
- #
10
- # doing a binary search starting at part = 45 degrees
11
- # on each iteration,
12
- # part is halved and added or substracted based on current success
13
- # if more than the mandatory result is found, all negative results are removed and degrees are increased by part
14
- #
15
- raise ArgumentError, 'detect_slope needs param Array :base' unless base.is_a? Array
16
-
17
- # from given base, choose non-negative stencil containing values
18
- old_base = base.dup.select{|b| b[:x] >= 0 and not b[:y].nil? }
3
+ def detect_slope(base:, max: 90, debug: false, format: '% 5.2f', calculus: false, ticksize: nil, max_dev: 200)
4
+ raise ArgumentError, "'0 < max < 90, but got '#{max}'" unless max.is_a? Numeric and 0 < max and max <= 90
5
+ #
6
+ # this method processes a 'well prepared' stencil in a way, that :y values are sheared around stencil.zero,
7
+ # resulting to a temporary :yy value for each point. this process is iterated until no more :yy
8
+ # values are above the abscissa ( yy > 0 ) but at least one other values is on (yy == 0)
9
+ #
10
+ # the entire process initially aimed to find slopes that contain 3 or more members. the current version
11
+ # is confident with 2 members--or even one member, which results in an even slope.
12
+ #
13
+ # it works by running a binary search, whereon each iteration,
14
+ # - :part is halved and added or substracted based on current success
15
+ # - if more than the mandatory result is found, all negative results are removed and degrees are increased by part
16
+ # - if no results are found, the process is repeated with the same current base after degrees are decreased by part
17
+ #
18
+ raise ArgumentError, 'detect_slope needs param Array :base' unless base.is_a? Array
19
19
 
20
- # some debug output
21
- old_base.each {|x| p x} if old_base.size < 50 and debug
20
+ # from given base, choose non-negative stencil containing values
21
+ old_base = base.dup.select{|b| b[:x] >= 0 and not b[:y].nil? }
22
22
 
23
- # set initial shearing angle if not given as param
24
- deg ||= -max / 2.0
23
+ # set initial shearing angle if not given as param. This is a prepared functionality, that is not yet used.
24
+ # when implemented to use in tritangulate, it would speed up the binary search process, but initial part
25
+ # has to be set in a different way
26
+ deg ||= -max / 2.0
25
27
 
26
- # create first sheering. please note how selection working with d[:yy]
27
- new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 } #-ticksize }
28
+ # create first shearing. please note how selection works with d[:yy]
29
+ new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 }
28
30
 
29
- # debug output
30
- puts "Iterating slope:\t#{format '% 7.5f',deg
31
+ # debug output
32
+ puts "Iterating slope:\t#{format '% 7.5f',deg
31
33
  }\t\t#{new_base.size
32
34
  } || #{new_base.values_at(*[0]).map{|f| "'#{f[:x]
33
35
  } | #{format format,f[:y]
34
36
  } | #{format format,f[:yy]}'"}.join(" || ") }" if debug
35
- # set initial part to deg
36
- part = deg.abs
37
- #
38
- # the loop, that runs until either
39
- # - only two points are left on the slope
40
- # - the slope has even angle
41
- # - several points are on the slope in quite a good approximation ('round(7)')
42
- #
43
- until deg.round(PRECISION).zero? || part.round(PRECISION).zero? ||
44
- ((new_base.size >= 2) && (new_base.map { |f| f[:yy].round(PRECISION).zero? }.uniq.size == 1))
45
-
46
- part /= 2.0
47
- if new_base.size == 1
48
- # the graph was sheared too far, reuse old_base
49
- deg = deg + part
50
- else
51
- # the graph was sheared too short, continue with new base
52
- deg = deg - part
53
- old_base = new_base.dup unless deg.round(PRECISION).zero?
54
- end
37
+ # set initial part to deg
38
+ part = deg.abs
39
+ #
40
+ # the loop, that runs until either
41
+ # - only two points are left on the slope
42
+ # - the slope has even angle
43
+ # - several points are found on the slope in quite a good approximation ('round(PRECISION)')
44
+ #
45
+ until deg.round(PRECISION).zero? || part.round(PRECISION).zero? ||
46
+ ((new_base.size >= 2) && (new_base.map { |f| f[:yy].round(PRECISION / 2).zero? }.uniq.size == 1))
55
47
 
56
- # the actual sheering operation
57
- # note that this basically maps old_base with yy = y + (dx||x * tan(deg) )
58
- #
59
- new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 } #-ticksize }
60
- new_base.last[:dx] = 0.0
61
- if debug
62
- print " #{format '% 8.5f',deg}"
63
- puts "Iterating slope:\t#{format '% 8.5f',deg
64
- }\t#{new_base.size
65
- } || #{new_base.values_at(*[0]).map{|f| "'#{f[:x]
66
- } | #{format '%4.5f', part
67
- } | #{format format,f[:y]
68
- } | #{format format,f[:yy]}'"}.join(" || ") }"
69
- end
48
+ part /= 2.0
49
+ if new_base.size == 1
50
+ # the graph was sheared too far, reuse old_base
51
+ deg += part
52
+ else
53
+ # the graph was sheared too short, continue with new base
54
+ deg -= part
55
+ old_base = new_base.dup unless deg.round(PRECISION).zero?
70
56
  end
71
- puts ' done.' if debug
72
57
 
73
- ### Sheering ends here
58
+ # the actual shearing operation
59
+ # this basically maps old_base with yy = y + (dx||x * tan(deg) )
60
+ #
61
+ new_base = shear_to_deg(base: old_base, deg: deg).select { |d| d[:yy] >= 0 }
62
+ new_base.last[:dx] = 0.0
74
63
 
75
- # define the approximited result as (also) 0.0
76
- new_base.each{|x| x[:yy] = 0.0}
77
- if debug
78
- puts "RESULT: #{deg} #{deg2rad(deg)}"
79
- new_base.each {|f| puts "\t#{f.inspect}" }
80
- end
81
- # there is speacial treatment for even slopes
82
- if deg.round(PRECISION).zero?
83
- #puts "found even slope"
84
- # this is intentionally voided as evenness is calculated somewhere else
85
- # even_base = base.dup.select{|b| b[:x] >= 0 and not b[:y].nil? }[-2..-1].map{|x| x.dup}
86
- # last_barrier is the last bar, that exceeds
87
- #binding.irb
88
- #last_barrier = even_base.select{|bar| (bar[:y] - even_base.last[:y]).abs > evenness * ticksize}.last
89
- #even_base.select!{|bar| (bar[:y] - even_base.last[:y]).abs <= evenness * ticksize}
90
- # could be, that no last barrier exists, when there is a top or bottom plateau
91
- #even_base.select!{|bar| bar[:x] < last_barrier[:x]} unless last_barrier.nil?
92
- # TODO
93
- return { deg: 0, slope: 0, members: [] } #, members: even_base.map { |x| xx = x.dup; %i[y yy].map { |z| xx[z]=nil }; xx } })
64
+ # debug output is reduced by appr. 70%
65
+ if debug and Random.rand < 0.3
66
+ print " #{format '% 18.15f',deg}\t"
67
+ puts "Iterating slope:\t#{format '% 18.10f',deg
68
+ }\t#{new_base.size
69
+ } || #{new_base.values_at(*[0]).map{|f| "'#{f[:x]
70
+ } | #{format '%4.5f', part
71
+ } | #{format format,f[:y]
72
+ } | #{format format,f[:yy]}'"}.join(" || ") }"
94
73
  end
74
+ end
75
+ ### Sheering ends here
95
76
 
77
+ # define the approximited result as (also) 0.0
78
+ new_base.each{|x| x[:yy] = 0.0}
96
79
 
97
- #####################################################################################
98
- # Calculate the slope bsaed on the angle that resulted above
99
- # y = m x + n -->
100
- # m = delta-y / delta-x
101
- # n = y0 - m * x0
102
- #
103
- slope = (new_base.first[:y] - new_base.last[:y]) / (
104
- (new_base.first[:dx].nil? ? new_base.first[:x] : new_base.first[:dx]).to_f -
105
- (new_base. last[:dx].nil? ? new_base. last[:x] : new_base. last[:dx]).to_f
106
- )
107
- # the result
108
- {
109
- deg: deg,
110
- slope: slope,
111
- members: new_base.map { |x| x.dup }
112
- }
113
- end
80
+ #####################################################################################
81
+ # Calculate the slope based on the angle that resulted above
82
+ # y = m x + n -->
83
+ # m = delta-y / delta-x
84
+ # n = y0 - m * x0
85
+ #
86
+ slope = deg.zero? ? 0 : (new_base.first[:y] - new_base.last[:y]) / (
87
+ (new_base.first[:dx].nil? ? new_base.first[:x] : new_base.first[:dx]).to_f -
88
+ (new_base. last[:dx].nil? ? new_base. last[:x] : new_base. last[:dx]).to_f
89
+ )
90
+ # the result
91
+ {
92
+ deg: deg,
93
+ slope: slope,
94
+ members: new_base.map { |x| x.dup }
95
+ }
96
+ end
114
97
  end
115
98
  end
116
-
117
-
@@ -3,7 +3,7 @@
3
3
  module Cotcube
4
4
  module Level
5
5
 
6
- class Stencil
6
+ class EOD_Stencil
7
7
  attr_accessor :base
8
8
  attr_reader :interval
9
9
 
@@ -43,20 +43,17 @@ module Cotcube
43
43
  range: nil, # used to shrink the stencil size, accepts String or Date
44
44
  interval:,
45
45
  swap_type:,
46
- ranges: nil, # currently not used, prepared to be used in connection intraday
47
46
  contract: nil,
48
47
  date: nil,
49
48
  debug: false,
50
49
  version: nil, # when referring to a specicic version of the stencil
51
- timezone: CHICAGO,
52
- stencil: nil, # instead of loading, use this data
53
- #config: init,
54
- warnings: true
50
+ timezone: Cotcube::Helpers::CHICAGO,
51
+ stencil: nil, # instead of preparing, use this one if set
52
+ warnings: true # be more quiet
55
53
  )
56
54
  @debug = debug
57
- @interval = interval
55
+ @interval = interval == :continuous ? :daily : interval
58
56
  @swap_type = swap_type
59
- @swaps = []
60
57
  @contract = contract
61
58
  @warnings = warnings
62
59
  step = case @interval
@@ -81,14 +78,14 @@ module Cotcube
81
78
  swap_type
82
79
  end
83
80
  # TODO: Check / warn / raise whether stencil (if provided) is a proper data type
84
- raise ArgumentError, "Stencil should be nil or Array" unless [NilClass, Array].include? stencil.class
81
+ raise ArgumentError, "EOD_Stencil should be nil or Array" unless [NilClass, Array].include? stencil.class
85
82
  raise ArgumentError, "Each stencil members should contain at least :datetime and :x" unless stencil.nil? or
86
83
  stencil.map{|x| ([:datetime, :x] - x.keys).empty? and [ActiveSupport::TimeWithZone, Day].include?( x[:datetime] ) and x[:x].is_a?(Integer)}.reduce(:&)
87
84
 
88
- base = stencil || Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version)
85
+ base = stencil || EOD_Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version)
89
86
 
90
- # fast forward to prev trading day
91
- date = CHICAGO.parse(date) unless [NilClass, Date, ActiveSupport::TimeWithZone].include? date.class
87
+ # fast rewind to previous trading day
88
+ date = timezone.parse(date) unless [NilClass, Date, ActiveSupport::TimeWithZone].include? date.class
92
89
  @date = date || Date.today
93
90
  best_match = base.select{|x| x[:datetime].to_date <= @date}.last[:datetime]
94
91
  @date = best_match
@@ -106,7 +103,7 @@ module Cotcube
106
103
  end
107
104
 
108
105
  def dup
109
- Stencil.new(
106
+ EOD_Stencil.new(
110
107
  debug: @debug,
111
108
  interval: @interval,
112
109
  swap_type: @swap_type,
@@ -2,99 +2,108 @@
2
2
 
3
3
  module Cotcube
4
4
  module Level
5
- def rad2deg(deg)
6
- deg * 180 / Math::PI
7
- end
8
-
9
- def deg2rad(rad)
10
- rad * Math::PI / 180
11
- end
12
5
 
13
- def shear_to_deg(base:, deg:)
14
- shear_to_rad(base: base, rad: deg2rad(deg))
15
- end
6
+ # 3 simple, self-explaining helpers
7
+ def rad2deg(deg); deg * 180 / Math::PI; end
8
+ def deg2rad(rad); rad * Math::PI / 180; end
9
+ def shear_to_deg(base:, deg:); shear_to_rad(base: base, rad: deg2rad(deg)); end
16
10
 
17
- def shear_to_rad(base: , rad:)
18
- tan = Math.tan(rad)
19
- base.map { |bar|
20
- # separating lines for easier debugging
21
- bar[:yy] =
22
- bar[:y] +
23
- (bar[:dx].nil? ? bar[:x] : bar[:dx]) * tan
24
- bar
25
- }
26
- end
11
+ # the actual shearing takes place here. please not that shifting of :x takes place
12
+ # by setting the new :x as :dx. so if :dx is found, it is used, otherwise :x
13
+ def shear_to_rad(base: , rad:)
14
+ tan = Math.tan(rad)
15
+ base.map { |member|
16
+ # separating lines for easier debugging
17
+ member[:yy] =
18
+ member[:y] +
19
+ (member[:dx].nil? ? member[:x] : member[:dx]) * tan
20
+ bar
21
+ }
22
+ end
27
23
 
28
- def member_to_human(member,side: ,format:)
29
- high = side == :high
30
- "#{member[:datetime].strftime("%a, %Y-%m-%d %H:%M")
24
+ # human readable output
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")
31
29
  } x: #{format '%-4d', member[:x]
32
30
  } dx: #{format '%-8.3f', (member[:dx].nil? ? member[:x] : member[:dx].round(3))
33
31
  } #{high ? "high" : "low"
34
32
  }: #{format format, member[high ? :high : :low]
35
33
  } i: #{(format '%4d', member[:i]) unless member[:i].nil?
36
- } #{member[:miss].nil? ? '' : "miss: #{member[:miss]}" }"
37
- end
34
+ } d: #{format '%6.2f', member[:dev] unless member[:dev].nil?
35
+ } #{member[:near].nil? ? '' : "near: #{member[:near]}"
36
+ }"
37
+ end
38
38
 
39
- def puts_swaps(swaps, format: )
40
- swaps = [ swaps ] unless swaps.is_a? Array
41
- swaps.each do |swap|
42
- puts "side: #{swap[:side] }\tlen: #{swap[:length]} \trating: #{swap[:rating]}".colorize(swap[:color] || :white )
43
- puts "diff: #{swap[:ticks]}\tdif: #{swap[:diff].round(7)}\tdepth: #{swap[:depth]}".colorize(swap[:color] || :white )
44
- puts "tpi: #{swap[:tpi] }\tppi: #{swap[:ppi]}".colorize(swap[:color] || :white )
45
- swap[:members].each {|x| puts member_to_human(x, side: swap[:side], format: format) }
46
- end
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|
44
+ puts "side: #{swap[:side] }\tlen: #{swap[:length]} \trating: #{swap[:rating]}".colorize(swap[:color] || :white )
45
+ puts "diff: #{swap[:ticks]}\tdif: #{swap[:diff].round(7)}\tdepth: #{swap[:depth]}".colorize(swap[:color] || :white )
46
+ 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) }
47
48
  end
49
+ end
48
50
 
49
- def get_jsonl_name(interval:, swap_type:, contract:, sym: nil)
50
- raise "Interval #{interval } is not supported, please choose from #{INTERVALS}" unless INTERVALS.include? interval
51
- raise "Swaptype #{swap_type} is not supported, please choose from #{SWAPTYPES}" unless SWAPTYPES.include? swap_type
52
- sym ||= Cotcube::Helpers.get_id_set(contract: contract)
53
- root = '/var/cotcube/level'
54
- dir = "#{root}/#{sym[:id]}"
55
- symlink = "#{root}/#{sym[:symbol]}"
56
- `mkdir -p #{dir}` unless File.exist?(dir)
57
- `ln -s #{dir} #{symlink}` unless File.exist?(symlink)
58
- file = "#{dir}/#{contract}_#{interval.to_s}_#{swap_type.to_s}.jsonl"
59
- `touch #{file}`
60
- file
61
- end
51
+ # create a standardized name for the cache files
52
+ # and, on-the-fly, create these files plus their directory
53
+ 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
55
+ raise "Swaptype #{swap_type} is not supported, please choose from #{SWAPTYPES}" unless SWAPTYPES.include? swap_type
56
+ sym ||= Cotcube::Helpers.get_id_set(contract: contract)
57
+ root = '/var/cotcube/level'
58
+ dir = "#{root}/#{sym[:id]}"
59
+ symlink = "#{root}/#{sym[:symbol]}"
60
+ `mkdir -p #{dir}` unless File.exist?(dir)
61
+ `ln -s #{dir} #{symlink}` unless File.exist?(symlink)
62
+ file = "#{dir}/#{contract}_#{interval.to_s}_#{swap_type.to_s}.jsonl"
63
+ `touch #{file}`
64
+ file
65
+ end
62
66
 
63
- def save_swaps(swaps, interval:, swap_type:, contract:, sym: nil)
64
- file = get_jsonl_name(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
65
- swaps.each do |swap|
66
- swap_json = swap.to_json
67
- digest = Digest::SHA256.hexdigest swap_json
68
- res = `cat #{file} | grep #{digest}`.strip
69
- unless res.empty?
70
- puts "Cannot save swap, it is already in #{file}:"
71
- p swap
72
- else
73
- swap[:digest] = digest
74
- File.open(file, 'a+'){|f| f.write(swap.to_json + "\n") }
75
- end
67
+ # the name says it all.
68
+ # 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)
71
+ file = get_jsonl_name(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
72
+ swaps.each do |swap|
73
+ swap_json = swap.to_json
74
+ digest = Digest::SHA256.hexdigest swap_json
75
+ res = `cat #{file} | grep #{digest}`.strip
76
+ unless res.empty?
77
+ puts "Cannot save swap, it is already in #{file}:"
78
+ p swap
79
+ else
80
+ swap[:digest] = digest
81
+ File.open(file, 'a+'){|f| f.write(swap.to_json + "\n") }
76
82
  end
77
83
  end
84
+ end
78
85
 
79
- def load_swaps(interval:, swap_type:, contract:, sym: nil)
80
- file = get_jsonl_name(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
81
- jsonl = File.read(file)
82
- jsonl.
83
- each_line.
84
- map do |x|
85
- JSON.parse(x).
86
- deep_transform_keys(&:to_sym).
87
- tap do |sw|
88
- sw[:datetime] = DateTime.parse(sw[:datetime]) rescue nil
89
- sw[:side] = sw[:side].to_sym
90
- unless sw[:empty]
91
- sw[:color] = sw[:color].to_sym
92
- sw[:members].map{|mem| mem[:datetime] = DateTime.parse(mem[:datetime]) }
93
- end
86
+ # loading of swaps is also straight forward
87
+ # it takes few more efforts to normalize the values to their expected format
88
+ def load_swaps(interval:, swap_type:, contract:, sym: nil)
89
+ file = get_jsonl_name(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
90
+ jsonl = File.read(file)
91
+ jsonl.
92
+ each_line.
93
+ map do |x|
94
+ JSON.parse(x).
95
+ deep_transform_keys(&:to_sym).
96
+ 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]) }
94
102
  end
95
103
  end
96
104
  end
97
- end
105
+ end
98
106
 
107
+ end
99
108
  end
100
109