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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +14 -6
- data/VERSION +1 -1
- data/cotcube-level.gemspec +1 -1
- data/lib/cotcube-level/detect_slope.rb +79 -98
- data/lib/cotcube-level/{stencil.rb → eod_stencil.rb} +10 -13
- data/lib/cotcube-level/helpers.rb +84 -75
- data/lib/cotcube-level/intraday_stencil.rb +179 -0
- data/lib/cotcube-level/tritangulate.rb +323 -0
- data/lib/cotcube-level.rb +15 -13
- metadata +6 -5
- data/lib/cotcube-level/triangulate.rb +0 -238
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7756ccc0318477cf223fc205720c986daaaad4304439108b960ca410a15acf79
|
4
|
+
data.tar.gz: ba7a79e928da4c89b0e7103394899096ab42f5977986892c5e7396fe5d4abeff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
21
|
-
|
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.
|
1
|
+
0.3.1
|
data/cotcube-level.gemspec
CHANGED
@@ -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
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
puts "
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
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
|
53
|
-
|
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, "
|
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 ||
|
85
|
+
base = stencil || EOD_Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version)
|
89
86
|
|
90
|
-
# fast
|
91
|
-
date =
|
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
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
37
|
-
|
34
|
+
} d: #{format '%6.2f', member[:dev] unless member[:dev].nil?
|
35
|
+
} #{member[:near].nil? ? '' : "near: #{member[:near]}"
|
36
|
+
}"
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
105
|
+
end
|
98
106
|
|
107
|
+
end
|
99
108
|
end
|
100
109
|
|