cotcube-helpers 0.1.3 → 0.1.4
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 +4 -4
- data/.irbrc.rb +3 -1
- data/CHANGELOG.md +4 -0
- data/Gemfile +3 -2
- data/README.md +22 -16
- data/VERSION +1 -1
- data/cotcube-helpers.gemspec +2 -3
- data/lib/cotcube-helpers.rb +7 -10
- data/lib/cotcube-helpers/array_ext.rb +13 -10
- data/lib/cotcube-helpers/enum_ext.rb +5 -3
- data/lib/cotcube-helpers/hash_ext.rb +8 -6
- data/lib/cotcube-helpers/input.rb +19 -15
- data/lib/cotcube-helpers/parallelize.rb +20 -22
- data/lib/cotcube-helpers/range_ext.rb +63 -26
- data/lib/cotcube-helpers/reduce.rb +44 -32
- data/lib/cotcube-helpers/string_ext.rb +11 -6
- data/lib/cotcube-helpers/subpattern.rb +3 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5e22624f57015916268dc4ca26ec98e2d759150ab9dc88dc3b5ceda93248148
|
4
|
+
data.tar.gz: 73628320f993e73c83423dded2880f44412174c8d1cd4dd201ace3f2c6c6e41e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b7b9051f22418e8a5a17245b1a410e284bf279787f1f9d279f58083f93a588c916926badc2b2834361241d874126a3af11c36805d20afe5b34049934c16cb6b
|
7
|
+
data.tar.gz: fd0dbfb22b5b7631c5ee220de6551e1adfe9a38311afe85918598fdf86d9dd50e1028663b633c8a675853238767455504bdfa9163a466ef3e362950aa44cf064
|
data/.irbrc.rb
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Returns the array compacted--or just nil, when the result is empty?
|
|
12
12
|
|
13
13
|
This is a rather old implementation. Most probably something I developed before I met Array#group\_by.
|
14
14
|
|
15
|
-
##### pairwise(&block) /
|
15
|
+
##### pairwise(&block) / one_by_one(&block)
|
16
16
|
|
17
17
|
Yields block on each consecutive pair of the array, hence returning array.size-1 results.
|
18
18
|
|
@@ -41,17 +41,18 @@ Uses the range of *date alikes* delivered to create an Array of time periods of
|
|
41
41
|
|
42
42
|
When the step is sub-day, the periods are cleared for DST-changes.
|
43
43
|
|
44
|
-
When ranges are nil, only periods are returned that are within the full trading hours.
|
45
|
-
|
44
|
+
When ranges are nil, only periods are returned that are within the full trading hours.
|
45
|
+
|
46
|
+
This is implemented with DateTime's propery, that Sunday is wday==0. If you want to use ranges, just send a an array
|
46
47
|
of ranges, providing seconds starting at Sunday morning midnight. Here is the default as example:
|
47
48
|
|
48
49
|
```
|
49
50
|
ranges ||= [
|
50
|
-
61200
|
51
|
-
147600
|
52
|
-
234000
|
53
|
-
320400
|
54
|
-
406800
|
51
|
+
61200...144000, # Sun 5pm .. Mon 4pm
|
52
|
+
147600...230400, # Mon 5pm .. Tue 4pm
|
53
|
+
234000...316800, # ...
|
54
|
+
320400...403300,
|
55
|
+
406800...489600
|
55
56
|
]
|
56
57
|
```
|
57
58
|
|
@@ -63,18 +64,19 @@ of ranges, providing seconds starting at Sunday morning midnight. Here is the de
|
|
63
64
|
|
64
65
|
##### sub(minimum: 1) { [:hyper, :mega] }
|
65
66
|
|
66
|
-
sub (should be 'subpattern', but too long) is for use in case / when statements
|
67
|
-
|
68
|
-
based on the the giving minimum. E.g. 'a', 'ab' .. 'abcd' will match sub(1){'abcd'}
|
67
|
+
sub (should be 'subpattern', but too long) is for use in `case / when` statements.
|
68
|
+
It returns a lambda, that checks the *case'd* expression for matching subpatterns
|
69
|
+
based on the the giving minimum. E.g. 'a', 'ab' .. 'abcd' .. 'abcd<any_garbage> will match sub(1){'abcd'}
|
69
70
|
but only 'abc' and 'abcd' will match sub(3){'abcd'}
|
70
71
|
|
71
|
-
|
72
|
+
It is developed for evaluating user input, where abbreviation of incoming commands
|
72
73
|
is desirable (h for hoover and hyper, what will translate to sub(2){'hoover'} and sub(2){hyper})
|
73
74
|
|
74
|
-
To extend functionality even more, it is possible to send
|
75
|
-
sub(2){[:hyper,:mega]}, what will respond truthy to "hy" and "meg" but not to "m" or "hypo"
|
75
|
+
To extend functionality even more, it is possible to send array of patterns like
|
76
|
+
sub(2){[:hyper,:mega]}, what will respond truthy to "hy" and "meg" but not to "m" or "hypo" (sadly, you can
|
77
|
+
set `:minimum` only once).
|
76
78
|
|
77
|
-
*
|
79
|
+
*Paired with keystroke() it allows an easy build of an inputhandler.*
|
78
80
|
|
79
81
|
#### SimpleOutput
|
80
82
|
|
@@ -90,7 +92,7 @@ flow like logs, to pause and continue output.
|
|
90
92
|
A version of STDIN.gets, that does not wait for pressing 'enter' but instantly returns the content
|
91
93
|
of the keystroke.
|
92
94
|
|
93
|
-
*
|
95
|
+
*Paired with subpattern it allows an easy build of an InputHandler.*
|
94
96
|
|
95
97
|
#### Parallelize
|
96
98
|
|
@@ -98,4 +100,8 @@ of the keystroke.
|
|
98
100
|
|
99
101
|
Based on https://github.com/grosser/parallel, it is a quite convenient way to parallelize tasks.
|
100
102
|
|
103
|
+
#### Reduce
|
104
|
+
|
105
|
+
##### reduce(bars: , to: nil, datelike: :datetime, &block)
|
101
106
|
|
107
|
+
Given a series resp. an array of bars (a bar is a set of contract/symbol, datelike, OHLC, volume) respresenting periods of e.g. 1.minute or 15.minutes, these are 'reduced' to an new series of a higher timeframe like hours or days. More target periods might be added in the futures.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.4
|
data/cotcube-helpers.gemspec
CHANGED
@@ -9,13 +9,13 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.summary = 'Some helpers and core extensions as part of the Cotcube Suite.'
|
10
10
|
spec.description = 'Some helpers and core extensions as part of the Cotcube Suite...'
|
11
11
|
|
12
|
-
spec.homepage =
|
12
|
+
spec.homepage = "https://github.com/donkeybridge/#{spec.name}"
|
13
13
|
spec.license = 'BSD-4-Clause'
|
14
14
|
spec.required_ruby_version = Gem::Requirement.new('~> 2.7')
|
15
15
|
|
16
16
|
spec.metadata['homepage_uri'] = spec.homepage
|
17
17
|
spec.metadata['source_code_uri'] = spec.homepage
|
18
|
-
spec.metadata['changelog_uri'] = spec.homepage
|
18
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/CHANGELOG.md"
|
19
19
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
|
29
29
|
spec.add_dependency 'activesupport'
|
30
30
|
|
31
|
-
|
32
31
|
spec.add_development_dependency 'rake'
|
33
32
|
spec.add_development_dependency 'rspec', '~>3.6'
|
34
33
|
spec.add_development_dependency 'yard', '~>0.9'
|
data/lib/cotcube-helpers.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# rubocop:disable Naming/FileName
|
2
|
+
# rubocop:enable Naming/FileName
|
1
3
|
# frozen_string_literal: true
|
2
4
|
|
3
5
|
require 'active_support'
|
@@ -10,24 +12,19 @@ require_relative 'cotcube-helpers/enum_ext'
|
|
10
12
|
require_relative 'cotcube-helpers/hash_ext'
|
11
13
|
require_relative 'cotcube-helpers/range_ext'
|
12
14
|
require_relative 'cotcube-helpers/string_ext'
|
13
|
-
require_relative 'cotcube-helpers/subpattern
|
15
|
+
require_relative 'cotcube-helpers/subpattern'
|
14
16
|
require_relative 'cotcube-helpers/parallelize'
|
15
17
|
require_relative 'cotcube-helpers/simple_output'
|
16
18
|
require_relative 'cotcube-helpers/input'
|
17
19
|
require_relative 'cotcube-helpers/reduce'
|
18
20
|
|
19
|
-
|
20
21
|
module Cotcube
|
21
22
|
module Helpers
|
23
|
+
module_function :sub,
|
24
|
+
:parallelize,
|
25
|
+
:reduce,
|
26
|
+
:keystroke
|
22
27
|
|
23
|
-
module_function :sub,
|
24
|
-
:parallelize,
|
25
|
-
:reduce,
|
26
|
-
:keystroke
|
27
|
-
|
28
|
-
|
29
|
-
|
30
28
|
# please not that module_functions of source provided in private files must be published there
|
31
29
|
end
|
32
30
|
end
|
33
|
-
|
@@ -1,22 +1,25 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Monkey patching the Ruby Core class Array
|
4
|
+
class Array
|
3
5
|
# returns nil if the compacted array is empty, otherwise returns the compacted array
|
4
|
-
def compact_or_nil(*
|
5
|
-
return nil if
|
6
|
-
|
6
|
+
def compact_or_nil(*_args)
|
7
|
+
return nil if compact == []
|
8
|
+
|
9
|
+
yield compact
|
7
10
|
end
|
8
11
|
|
9
12
|
# sorts by a given attribute and then returns groups of where this attribute is equal
|
10
13
|
# .... seems like some_array.group_by(&attr).values
|
11
14
|
def split_by(attrib)
|
12
15
|
res = []
|
13
|
-
sub = []
|
14
|
-
|
15
|
-
if sub.empty?
|
16
|
+
sub = []
|
17
|
+
sort_by(&attrib).each do |elem|
|
18
|
+
if sub.empty? || (sub.last[attrib] == elem[attrib])
|
16
19
|
sub << elem
|
17
20
|
else
|
18
21
|
res << sub
|
19
|
-
sub = [
|
22
|
+
sub = [elem]
|
20
23
|
end
|
21
24
|
end
|
22
25
|
res << sub
|
@@ -37,7 +40,7 @@ class Array
|
|
37
40
|
end.compact
|
38
41
|
end
|
39
42
|
|
40
|
-
|
43
|
+
alias one_by_one pairwise
|
41
44
|
|
42
45
|
# same as pairwise, but with arity of three
|
43
46
|
def triplewise(&block)
|
@@ -47,7 +50,7 @@ class Array
|
|
47
50
|
each_with_index.map do |_, i|
|
48
51
|
next if i < 2
|
49
52
|
|
50
|
-
block.call(self[i - 2], self[i-1], self[i])
|
53
|
+
block.call(self[i - 2], self[i - 1], self[i])
|
51
54
|
end.compact
|
52
55
|
end
|
53
56
|
end
|
@@ -1,14 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Monkey patching the Ruby Core class Hash
|
4
|
+
class Hash
|
3
5
|
def keys_to_sym
|
4
|
-
|
6
|
+
each_key do |key|
|
5
7
|
case self[key].class.to_s
|
6
|
-
when
|
8
|
+
when 'Hash'
|
7
9
|
self[key].keys_to_sym
|
8
|
-
when
|
9
|
-
self[key].map {|el| el.is_a?(Hash) ? el.keys_to_sym : el}
|
10
|
+
when 'Array'
|
11
|
+
self[key].map { |el| el.is_a?(Hash) ? el.keys_to_sym : el }
|
10
12
|
end
|
11
|
-
self[key.to_sym] =
|
13
|
+
self[key.to_sym] = delete(key)
|
12
14
|
end
|
13
15
|
self
|
14
16
|
end
|
@@ -1,40 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Top level comment missing!
|
1
4
|
module Cotcube
|
5
|
+
# noinspection ALL
|
2
6
|
module Helpers
|
3
7
|
def keystroke(quit: false)
|
4
8
|
begin
|
5
9
|
# save previous state of stty
|
6
10
|
old_state = `stty -g`
|
7
11
|
# disable echoing and enable raw (not having to press enter)
|
8
|
-
system
|
9
|
-
c =
|
12
|
+
system 'stty raw -echo'
|
13
|
+
c = $stdin.getc.chr rescue '_' # rubocop:disable Style/RescueModifier
|
10
14
|
# gather next two characters of special keys
|
11
|
-
if
|
12
|
-
extra_thread = Thread.new
|
13
|
-
c
|
14
|
-
c
|
15
|
-
|
15
|
+
if c == "\e"
|
16
|
+
extra_thread = Thread.new do
|
17
|
+
c += $stdin.getc.chr
|
18
|
+
c += $stdin.getc.chr
|
19
|
+
end
|
16
20
|
# wait just long enough for special keys to get swallowed
|
17
21
|
extra_thread.join(0.00001)
|
18
22
|
# kill thread so not-so-long special keys don't wait on getc
|
19
23
|
extra_thread.kill
|
20
24
|
end
|
21
|
-
rescue =>
|
22
|
-
puts "#{
|
23
|
-
puts
|
25
|
+
rescue StandardError => e
|
26
|
+
puts "#{e.class}: #{e.message}"
|
27
|
+
puts e.backtrace
|
24
28
|
ensure
|
25
29
|
# restore previous state of stty
|
26
30
|
system "stty #{old_state}"
|
27
31
|
end
|
28
|
-
c.each_byte do |x|
|
32
|
+
c.each_byte do |x| # rubocop:disable Lint/UnreachableLoop
|
29
33
|
case x
|
30
34
|
when 3
|
31
|
-
puts
|
35
|
+
puts 'Strg-C captured, exiting...'
|
32
36
|
quit ? exit : (return true)
|
33
37
|
when 13
|
34
|
-
return
|
38
|
+
return '_return_'
|
35
39
|
when 27
|
36
|
-
puts
|
37
|
-
return
|
40
|
+
puts 'ESCAPE gathered'
|
41
|
+
return '_esc_'
|
38
42
|
else
|
39
43
|
return c
|
40
44
|
end
|
@@ -1,30 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: Missing top level documentation!
|
1
4
|
module Cotcube
|
5
|
+
# TODO: Missing top level documentation!
|
2
6
|
module Helpers
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
result = Parallel.map(ary, in_processes: processes) {|u| v = yield(u)}
|
7
|
+
def parallelize(ary, processes: 1, threads: 1, progress: '', &block)
|
8
|
+
chunks = []
|
9
|
+
if [0, 1].include? processes
|
10
|
+
result = Parallel.map(ary, in_threads: threads) { |u, &in_thread| in_thread.call(u) }
|
11
|
+
elsif [0, 1].include?(threads)
|
12
|
+
result = Parallel.map(ary, in_processes: processes) { |u, &in_process| in_process.call(u) }
|
10
13
|
else
|
11
|
-
ary.each_slice(threads) {|chunk| chunks << chunk }
|
12
|
-
if progress ==
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
yield(unit)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
14
|
+
ary.each_slice(threads) { |chunk| chunks << chunk }
|
15
|
+
result = if progress == ''
|
16
|
+
Parallel.map(chunks, in_processes: processes) do |chunk|
|
17
|
+
Parallel.map(chunk, in_threads: threads, &block)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
Parallel.map(chunks, progress: progress, in_processes: processes) do |chunk|
|
21
|
+
Parallel.map(chunk, in_threads: threads, &block)
|
22
|
+
end
|
23
|
+
end
|
25
24
|
end
|
26
25
|
result
|
27
26
|
end
|
28
|
-
|
29
27
|
end
|
30
28
|
end
|
@@ -1,52 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Monkey patching the Ruby Core class Range
|
1
4
|
class Range
|
2
|
-
def to_time_intervals(timezone: Time.find_zone('America/Chicago'),
|
5
|
+
def to_time_intervals(step:, timezone: Time.find_zone('America/Chicago'), ranges: nil)
|
6
|
+
unless step.is_a? ActiveSupport::Duration
|
7
|
+
raise ArgumentError,
|
8
|
+
":step must be a 'ActiveSupport::Duration', like '15.minutes', but '#{step}' is a '#{step.class}'"
|
9
|
+
end
|
3
10
|
|
4
|
-
|
11
|
+
valid_classes = [ActiveSupport::TimeWithZone, Time, Date, DateTime]
|
12
|
+
unless timezone.is_a? ActiveSupport::TimeZone
|
13
|
+
raise "Expecting 'ActiveSupport::TimeZone' for :timezone, got '#{timezone.class}"
|
14
|
+
end
|
5
15
|
|
6
|
-
valid_classes = [ ActiveSupport::TimeWithZone, Time, Date, DateTime ]
|
7
|
-
raise "Expecting 'ActiveSupport::TimeZone' for :timezone, got '#{timezone.class}" unless timezone.is_a? ActiveSupport::TimeZone
|
8
16
|
starting = self.begin
|
9
17
|
ending = self.end
|
10
18
|
starting = timezone.parse(starting) if starting.is_a? String
|
11
19
|
ending = timezone.parse(ending) if ending.is_a? String
|
12
|
-
|
13
|
-
|
20
|
+
unless valid_classes.include? starting.class
|
21
|
+
raise ArgumentError,
|
22
|
+
":self.begin seems not to be proper time value: #{starting} is a #{starting.class}"
|
23
|
+
end
|
24
|
+
unless valid_classes.include? ending.class
|
25
|
+
raise ArgumentError,
|
26
|
+
":self.end seems not to be proper time value: #{ending} is a #{ending.class}"
|
27
|
+
end
|
14
28
|
|
15
|
-
##### The following is the actual big magic line:
|
16
|
-
#
|
17
|
-
result = (starting.to_time.to_i..ending.to_time.to_i).step(step).to_a.map{|x| timezone.at(x)}
|
29
|
+
##### The following is the actual big magic line, as it creates the raw target array:
|
18
30
|
#
|
19
|
-
|
20
|
-
|
31
|
+
result = (starting.to_time.to_i..ending.to_time.to_i).step(step).to_a.map { |x| timezone.at(x) }
|
32
|
+
#
|
33
|
+
# ###################<3##
|
21
34
|
|
22
35
|
# with step.to_i >= 86400 we are risking stuff like 25.hours to return bogus
|
23
36
|
# also notice: When using this with swaps, you will loose 1 hour (#f**k_it)
|
24
37
|
#
|
25
38
|
# eventually, for dailies and above, return M-F default, return S-S when forced by empty ranges
|
26
|
-
|
39
|
+
if step.to_i >= 86_400
|
40
|
+
return result.select do |x|
|
41
|
+
(not ranges.nil?) && ranges.empty? ? true : (not [6, 0].include?(x.wday))
|
42
|
+
end
|
43
|
+
end
|
27
44
|
|
28
45
|
# sub-day is checked for DST and filtered along provided ranges
|
29
46
|
starting_with_dst = result.first.dst?
|
30
|
-
|
47
|
+
|
48
|
+
# The following lambda is completely misplaces here.
|
49
|
+
# It should probably relocated to Cotcube::Bardata
|
50
|
+
# NOTE: In this current version 12 belongs to it succeeding hour
|
51
|
+
# i.e. 12am is right before 1am and 12pm right before 1pm
|
52
|
+
convert_to_sec_since = lambda do |clocking|
|
53
|
+
from_src, to_src = clocking.split(' - ')
|
54
|
+
regex = /^(?<hour>\d+):(?<minute>\d+)(?<morning>[pa]).?m.*/
|
55
|
+
|
56
|
+
from = from_src.match(regex)
|
57
|
+
to = to_src.match(regex)
|
58
|
+
|
59
|
+
from_i = from[:hour].to_i * 3600 + from[:minute].to_i * 60 + (from[:morning] == 'a' ? 2 : 1) * 12 * 3600
|
60
|
+
to_i = to[:hour].to_i * 3600 + to[:minute].to_i * 60 + (to[:morning] == 'a' ? 2 : 3) * 12 * 3600
|
61
|
+
|
62
|
+
(0...5).to_a.map { |i| [from_i + i * 24 * 3600, to_i + i * 24 * 3600] }
|
63
|
+
end
|
64
|
+
convert_to_sec_since.call('9:00a.m - 5:00p.m.')
|
65
|
+
|
66
|
+
seconds_since_sunday_morning = ->(x) { x.wday * 86_400 + x.hour * 3600 + x.min * 60 + x.sec }
|
31
67
|
ranges ||= [
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
68
|
+
61_200...144_000, # Sun 5pm .. Mon 4pm
|
69
|
+
147_600...230_400, # Mon 5pm .. Tue 4pm
|
70
|
+
234_000...316_800, # ...
|
71
|
+
320_400...403_200,
|
72
|
+
406_800...489_600
|
37
73
|
]
|
38
74
|
|
39
|
-
# if there was a change towards daylight saving time,
|
75
|
+
# if there was a change towards daylight saving time, subtract 1 hour, otherwise add 1 hour
|
40
76
|
result.map! do |time|
|
41
|
-
if
|
42
|
-
|
43
|
-
elsif
|
44
|
-
|
77
|
+
if (not starting_with_dst) && time.dst?
|
78
|
+
time - 3600
|
79
|
+
elsif starting_with_dst && (not time.dst?)
|
80
|
+
time + 3600
|
45
81
|
else
|
46
|
-
|
82
|
+
time
|
47
83
|
end
|
48
84
|
end
|
49
|
-
return result if ranges.empty?
|
50
|
-
|
85
|
+
return result if ranges.empty?
|
86
|
+
|
87
|
+
result.select { |x| ranges.map { |r| r.include? seconds_since_sunday_morning.call(x) }.reduce(:|) }
|
51
88
|
end
|
52
89
|
end
|
@@ -1,39 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cotcube
|
4
|
+
# Missing top level documentation
|
2
5
|
module Helpers
|
3
|
-
def reduce(bars
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
6
|
+
def reduce(bars:, to: nil, date_alike: :datetime, &block)
|
7
|
+
case to
|
8
|
+
when :days
|
9
|
+
terminators = %i[last daily beginning_of_day]
|
10
|
+
block = proc { |c, b| c[:day] == b[:day] } unless block_given?
|
11
|
+
when :hours
|
12
|
+
terminators = %i[first hours beginning_of_hour]
|
13
|
+
block = proc { |c, b| c[:day] == b[:day] and c[:datetime].hour == b[:datetime].hour } unless block_given?
|
14
|
+
when :weeks
|
15
|
+
terminators = %i[first weeks beginning_of_week]
|
16
|
+
block = proc { |a, b| a[:datetime].to_datetime.cweek == b[:datetime].to_datetime.cweek } unless block_given?
|
17
|
+
when :months
|
18
|
+
terminators = %i[first months beginning_of_month]
|
19
|
+
block = proc { |a, b| a[:datetime].to_datetime.month == b[:datetime].to_datetime.month } unless block_given?
|
20
|
+
else
|
21
|
+
raise ArgumentError, 'Currently supported are reductions to :hours, :days, :weeks, :months '
|
22
|
+
end
|
23
|
+
determine_date_alike = ->(ary) { ary.send(terminators.first)[date_alike].send(terminators.last) }
|
24
|
+
make_new_bar = lambda do |ary, _date = nil|
|
25
|
+
result = {
|
26
|
+
contract: ary.first[:contract],
|
27
|
+
symbol: ary.first[:symbol],
|
28
|
+
datetime: determine_date_alike.call(ary),
|
29
|
+
day: ary.first[:day],
|
30
|
+
open: ary.first[:open],
|
31
|
+
high: ary.map { |x| x[:high] }.max,
|
32
|
+
low: ary.map { |x| x[:low] }.min,
|
33
|
+
close: ary.last[:close],
|
34
|
+
volume: ary.map { |x| x[:volume] }.reduce(:+),
|
35
|
+
type: terminators[1]
|
36
|
+
}
|
37
|
+
result.map { |k, v| result.delete(k) if v.nil? }
|
38
|
+
result
|
26
39
|
end
|
27
|
-
collector = [
|
28
|
-
final = [
|
40
|
+
collector = []
|
41
|
+
final = []
|
29
42
|
bars.each do |bar|
|
30
|
-
if collector.empty?
|
31
|
-
|
32
|
-
|
43
|
+
if collector.empty? || block.call(collector.last, bar)
|
44
|
+
collector << bar
|
45
|
+
else
|
33
46
|
new_bar = make_new_bar.call(collector)
|
34
|
-
|
35
|
-
|
36
|
-
|
47
|
+
final << new_bar
|
48
|
+
collector = [bar]
|
49
|
+
end
|
37
50
|
end
|
38
51
|
new_bar = make_new_bar.call(collector)
|
39
52
|
final << new_bar
|
@@ -41,4 +54,3 @@ module Cotcube
|
|
41
54
|
end
|
42
55
|
end
|
43
56
|
end
|
44
|
-
|
@@ -1,9 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Monkey patching the Ruby Core class String
|
1
4
|
class String
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
# ...
|
6
|
+
def valid_json?
|
7
|
+
JSON.parse(self)
|
8
|
+
true
|
9
|
+
rescue JSON::ParserError
|
10
|
+
false
|
7
11
|
end
|
8
|
-
end
|
9
12
|
|
13
|
+
alias is_valid_json? valid_json?
|
14
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# ...
|
3
4
|
module Cotcube
|
5
|
+
# ...
|
4
6
|
module Helpers
|
5
7
|
# sub (should be 'subpattern', but too long) is for use in case / when statements
|
6
8
|
# it returns a lambda, that checks the case'd expression for matching subpattern
|
7
9
|
# based on the the giving minimum. E.g. 'a', 'ab' .. 'abcd' will match sub(1){'abcd'}
|
8
|
-
# but only 'abc' and 'abcd' will match sub(3){'abcd'}
|
10
|
+
# but only 'abc' and 'abcd' will match sub(3){'abcd'}.:
|
9
11
|
#
|
10
12
|
# The recommended use within evaluating user input, where abbreviation of incoming commands
|
11
13
|
# is desirable (h for hoover and hyper, what will translate to sub(2){'hoover'} and sub(2){hyper})
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cotcube-helpers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
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: 2020-12-
|
11
|
+
date: 2020-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|