cotcube-level 0.2.0 → 0.3.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: 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