spanbars 0.1.2beta

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7698dcae6b52fb67ba0636e83ed8c818bc517ed0ccce2559c4ae61e9c37bad62
4
+ data.tar.gz: c7472948c2e2f5809e910548677f016b510eeadd711414bae016c027e04b46a9
5
+ SHA512:
6
+ metadata.gz: cda1887acff3b220c43e0667202ccf6eaa83c1c9ccfa80b6610056e1eea1a786a83baa409f34b146b165a60a769c1b11caed87037a879725a7b42a9964ff6c0f
7
+ data.tar.gz: c9a98b5a5bf355ee6cec2c9602309d05da34f9293189c0fdfd9a4d1537de96eda539b116d5ec6438a62a3c72c8df4f987fedcc953e1c760272bd9559996d9a13
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # SpanBars
2
+
3
+ SpanBars is a tiny tool to produce span bars from time series data, either directly from a time
4
+ series or based on OHLC bars.
5
+
6
+ ## Description
7
+
8
+ SpanBars reads record-by-record (or line-by-line) and provides the according span bars.
9
+ The current bar is closed and a new bar is created as soon as the given span is _exceeded_.
10
+
11
+ Spanbars are comparable to classic OHLC bars or candle sticks, but have the major
12
+ advantage of a CLOSE value always equalling either HIGH or LOW. All data eliminated
13
+ can be considered as _noise_.
14
+
15
+ Therefore exists 4 types of bars:
16
+
17
+ * TOP: CLOSE == HIGH
18
+ * UP: CLOSE == HIGH and OPEN == LOW
19
+ * BOTTOM: CLOSE == LOW
20
+ * DOWN: CLOSE == LOW and OPEN == HIGH
21
+
22
+ When generating spanbars based on a plain time series, these bars can be sent to output using
23
+ the parameter _--simple_. Without it will
24
+ process the resulting data again, aggregating all bars created in the first run to start
25
+ at an absolute HIGH (or LOW) and end at an absolute LOW (or HIGH resp.). Although these
26
+ spans of second type might be much larger than _--span_, they can be considered to
27
+ contain no noise _smaller or equal than_ span.
28
+
29
+ Creating spanbars from given OHLCs is slightly inaccurate, as even one OHLC might
30
+ contain several spanbars, but that cannot be reflected by the given input data. Also, for
31
+ processing OHLC-input data, note that the entire algorithm (simple spanbars first, strict spanbars seconds) is run 3 times:
32
+ 1. using (span / 2) on input HIGHS
33
+ 2. using (span / 2) on input LOWS
34
+ 1. using (span) on [ resulting Highs, resulting Lows ].sorted\_by\_time
35
+
36
+ The application area this gem is written for is denoising data for trend recognition
37
+ within monitored timeseries.
38
+
39
+ ## Basic usage
40
+ processor = SpanBarProcessor.new(span: 5)
41
+ File.read("./timeseries.csv").map{|x| { t: x[0], v: x[1] } }.sort_by{|x| x[:t]}.each do |data|
42
+ processor.add(data)
43
+ end
44
+ processor.bars.each {|x| puts x}
45
+
46
+ ## Basic usage via commandline
47
+
48
+ Using _spanbars_ on the commandline expects data on STDIN as CSV with timestamps on column 1 and
49
+ values on column 2. With _--ohlc_ enabled, it expects CSV with "timestamps,open,high,low,close".
50
+
51
+ Provided output will be CSV as well, for
52
+
53
+ * _simple_: "timestamp\_open,open, timestamp\_high,high, timestamp\_low, low, timestamp\_close, close, direction, path, momentum, direction"
54
+ * _strict_: "timestamp\_open,open, timestamp\_close, close, duration, path, momentum, effective\_span, direction"
55
+
56
+ I currently plan to implement 2 more parameters: _--human_ to create a human-readable table
57
+ (particularly concerning the time format), and _--intraday_ (ommiting the date part when using _--human_).
58
+
59
+ $ cat timeseries.csv | spanbars
60
+ $ spanbars --input ./timeseries.csv --span 5
61
+
62
+ ## List of parameters
63
+
64
+ * --span (defaults to 10)
65
+ * --ticksize (default to 1.0)
66
+ * --ohlc (defaults to false)
67
+ * --simple (defaults to false)
68
+ * --human (planned, defaults to false)
69
+ * --intraday (planned, defaults to false)
data/bin/spanbars ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
4
+ require File.dirname(THIS_FILE) + '/../lib/spanbarprocessor.rb'
5
+ require File.dirname(THIS_FILE) + '/../lib/spanbar.rb'
6
+
7
+
8
+ # prepare processing of incoming control commands (e.g. Ctrl-C)
9
+ Signal.trap("TERM") { local_interrupt }
10
+ Signal.trap("INT") { local_interrupt }
11
+
12
+ def local_interrupt
13
+ exit
14
+ end
15
+
16
+ # prepare command line parser and help text
17
+
18
+ op = Slop::Options.new
19
+ op.banner = "\n#{"Usage:".light_white} spanbars [options]"
20
+ op.separator ""
21
+ op.bool '--simple' , "Disable processing of strict StanBars", default: false
22
+ op.float '--ticksize' , "Set ticksize for processing", default: 1.0
23
+ op.integer '--span' , "Set span for processing", default: 10
24
+ op.boolean '--ohlc' , "Define OHLC input file instead of timeseries (overrides --simple and --both)", default: false
25
+ op.boolean '--human' , "Define human output", default: false
26
+ op.boolean '--intraday' , "Strip date portion (affects --human only)", default: false
27
+ op.boolean '--help' , "Print this help", default: false
28
+ op.boolean '--both' , "Returns both simple and strict bars (overrides simple)", default: false
29
+ op.separator ""
30
+ op.separator "#{"Please note:".light_white} spanbars relies on STDIN data, e.g. "
31
+ op.separator " #{"$".light_white} cat /tmp/timeseries.csv | spanbars --simple --span 5 --ticksize 0.1"
32
+ op.separator ""
33
+
34
+ optparser = Slop::Parser.new(op)
35
+
36
+ # print help upon unparsable commandline options
37
+ begin
38
+ opts = optparser.parse(ARGV)
39
+ rescue StandardError => e
40
+ puts e.inspect
41
+ puts op.to_s
42
+ exit
43
+ end
44
+
45
+ if opts.help?
46
+ puts op.to_s
47
+ exit
48
+ end
49
+
50
+ unless STDIN.tty?
51
+ unless opts.ohlc?
52
+ s = SpanBarProcessor.new(opts)
53
+ while csv = STDIN.gets
54
+ line = CSV.parse(csv.chomp).flatten
55
+ result = s.add(line[0].to_i, line[1].to_f)
56
+ if result
57
+ if opts[:human]
58
+ result.each {|r| r.set_intraday if opts[:intraday]; puts ([:up, :bottom].include? r.type) ? "#{r.to_human}".green : "#{r.to_human}".red }
59
+ else # CSV output
60
+ result.each {|r| CSV {|out| out << r.to_a } }
61
+ end
62
+ end
63
+ end
64
+ exit
65
+ else # if ohlc is given
66
+ data = [ ]
67
+ data << CSV.parse(csv.chomp).flatten while csv = STDIN.gets
68
+ opts = opts.to_hash
69
+ opts.delete(:simple)
70
+ opts.delete(:both)
71
+ highOpts = opts.dup
72
+ highOpts[:span] = (opts[:span] / 2.0).floor.to_i
73
+ highProc = SpanBarProcessor.new(highOpts)
74
+ lowProc = SpanBarProcessor.new(highOpts)
75
+ finProc = SpanBarProcessor.new(opts)
76
+ data.each do |d|
77
+ highProc.add(d[0].to_i, d[2].to_f)
78
+ lowProc. add(d[0].to_i, d[3].to_f)
79
+ end
80
+ res = highProc.spanBars.map{|bar| bar.type == :up ? bar.highval : bar.lowval } #.map{|peak| bar.highval }
81
+ res.flatten!
82
+ res.sort!{|a,b| a[:t] <=> b[:t]}
83
+ res.each {|peak| finProc.add peak[:t],peak[:p]}
84
+ if opts[:human]
85
+ finProc.spanBars.each {|r| r.set_intraday if opts[:intraday]; puts ([:up, :bottom].include? r.type) ? "#{r.to_human}".green : "#{r.to_human}".red }
86
+ else # CSV output
87
+ finProc.spanBars.each {|r| CSV {|out| out << r.to_a } }
88
+ end
89
+ end
90
+ else
91
+ puts op.to_s
92
+ end
93
+
94
+
95
+
96
+
97
+
@@ -0,0 +1,29 @@
1
+ Feature: Initializiation of SpanBarProcessor
2
+ Scenario: Testing provided arguments during initialization
3
+ * a new SpanBarProcessor created with span that is not Integer or <= 1 should raise ArgumentError
4
+ * a new SpanBarProcessor created with a valid span but non-Numeric ticksize or <= 0 should raise ArgumentError
5
+
6
+ Scenario Outline: Testing well initialized instance_variables
7
+ Given a SpanBarProcessor is initialized with "3", "3"
8
+ Then <instance_var> should be set to <value>
9
+ Examples:
10
+ | instance_var | value |
11
+ | @ticks | [] |
12
+ | @limit | 9 |
13
+ | @simpleMax | 0 |
14
+ | @simpleMin | Float::INFINITY |
15
+ | @simpleBar | [] |
16
+ | @simpleBars | [] |
17
+ | @intraday | false |
18
+ | @both | false |
19
+ | @simple | false |
20
+
21
+ #Scenario Outline: Testing methods and attr
22
+ #Given a SpanBarProcessor is initialized with "4", "3"
23
+ #Then it should respond to "<method>"
24
+ #Examples:
25
+ # | method |
26
+ # | add |
27
+ # | simpleBars |
28
+ # | create_strict_from |
29
+ #
@@ -0,0 +1,40 @@
1
+ Feature: Using #add
2
+
3
+ Scenario: Falsey input
4
+ Given a simple SpanBarProcessor is initialized with "3", "3"
5
+ Then it should raise ArgumentError when calling #add with less than 2 arguments
6
+ Then it should raise ArgumentError when calling #add and first argument is neither Integer nor Time
7
+ Then it should raise ArgumentError when calling #add and second argument is neither Numeric nor Nil
8
+
9
+ Scenario: Correct return values
10
+ Given a simple SpanBarProcessor is initialized with "3", "3"
11
+ Then calling #add with params 123 and nil should return nil
12
+ Then calling #add with params 123 and 2.5 should return false
13
+
14
+ Scenario: eesting a linear raising series
15
+ Given a simple SpanBarProcessor is initialized with "3", "1"
16
+ Then #add with params 1, 1 should return false
17
+ Then #add with params 2, 2 should return false
18
+ Then #add with params 3, 3 should return false
19
+ Then #add with params 4, 4 should return false
20
+ Then #add with params 5, 5 should return an Array
21
+
22
+ Scenario Outline: Running with reference data
23
+ Given a SpanBarProcessor is initialized with "<deviation>", "<ticksize>"
24
+ And a feeder with data from "<file>" is prepared
25
+ Then adding all data from file should not raise any error
26
+
27
+ Examples:
28
+ | file | deviation | ticksize |
29
+ | ./features/support/ref1.csv | 5 | 0.25 |
30
+ | ./features/support/ref1.csv | 8 | 0.1 |
31
+ | ./features/support/ref1.csv | 12 | 0.66 |
32
+ | ./features/support/ref2.csv | 5 | 1 |
33
+ | ./features/support/ref2.csv | 8 | 2 |
34
+ | ./features/support/ref2.csv | 12 | 5.5 |
35
+ | ./features/support/ref3.csv | 5 | 0.0000005 |
36
+ | ./features/support/ref3.csv | 8 | 0.000001 |
37
+ | ./features/support/ref3.csv | 12 | 0.00000025 |
38
+
39
+
40
+
@@ -0,0 +1,30 @@
1
+ Feature: Initializiation of SpanBar
2
+ Scenario: Testing provided arguments during initialization
3
+ * a new simple SpanBar created without options should raise ArgumentError
4
+ * a new simple SpanBar created by SpanBarProcessor should not raise
5
+
6
+ Scenario Outline: Testing well initialized instance_variables
7
+ Given a valid simple SpanBar is created by SpanBarProcessor
8
+ Then <instance_var> should be set to <value>
9
+ Examples:
10
+ | instance_var | value |
11
+ | @strict | false |
12
+ | @type | :up |
13
+ | @open | 1 |
14
+ | @momentum | Float::INFINITY |
15
+
16
+
17
+
18
+ Scenario Outline: Testing methods and attr of SpanBar
19
+ Given a valid simple SpanBar is created by SpanBarProcessor
20
+ Then it should respond to "<method>"
21
+ Examples:
22
+ | method |
23
+ | inspect |
24
+ | to_human |
25
+ | to_a |
26
+ | valid? |
27
+ | path |
28
+ | momentum |
29
+ | split_for|
30
+
@@ -0,0 +1,19 @@
1
+ Feature: Using spanbars on commandline
2
+ Scenario: When using bin/spanbars on the commandline without parameters
3
+ Given bin/spanbars is run on the commandline and neither parameters nor STDIN is given
4
+ #Then bin/spanbars should display help
5
+
6
+ Scenario Outline: Checking known input to produce known output
7
+ Given bin/spanbars is run with following parameters it should ouput
8
+ Then cat <input> | spanbars --both --ticksize <ticksize> --span <span> should produce <output>
9
+ Examples:
10
+ | input | ticksize | span | output |
11
+ | ref1 | 0.25 | 25 | ref1_out_0.25_25 |
12
+ | ref2 | 1 | 25 | ref2_out_1_25 |
13
+ | ref3 | 0.0000005 | 10 | ref3_out_0.0000005_10 |
14
+
15
+ Scenario: Checking whether false reference is recognized
16
+ Given bin/spanbars is run with following parameters it should ouput
17
+ Then 'cat ref3 | bin/spanbars --both --span 10 --ticksize 0.0000005' should not produce ref2_out_1_25
18
+
19
+
@@ -0,0 +1,36 @@
1
+ require './lib/spanbar.rb'
2
+ require './lib/spanbarprocessor.rb'
3
+
4
+ Given "a new SpanBarProcessor created with span that is not Integer or <= 1 should raise ArgumentError" do
5
+ expect{ SpanBarProcessor.new(span: "t") }.to raise_error(ArgumentError)
6
+ expect{ SpanBarProcessor.new(span: 1.6) }.to raise_error(ArgumentError)
7
+ expect{ SpanBarProcessor.new(span: 1) }.to raise_error(ArgumentError)
8
+ end
9
+
10
+ Given "a new SpanBarProcessor created with a valid span but non-Numeric ticksize or <= 0 should raise ArgumentError" do
11
+ expect{ SpanBarProcessor.new(span: 2, ticksize: "foo")}.to raise_error(ArgumentError)
12
+ expect{ SpanBarProcessor.new(span: 2, ticksize: 0)}.to raise_error(ArgumentError)
13
+ expect{ SpanBarProcessor.new(span: 2, ticksize: -1.2)}.to raise_error(ArgumentError)
14
+ end
15
+
16
+ Given /^a SpanBarProcessor is initialized with "([^"]*)", "([^"]*)"$/ do |span, ticksize|
17
+ @s = SpanBarProcessor.new(span: span.to_i, ticksize: ticksize.to_f)
18
+ end
19
+
20
+ Given /^a simple SpanBarProcessor is initialized with "([^"]*)", "([^"]*)"$/ do |span, ticksize|
21
+ @s = SpanBarProcessor.new(span: span.to_i, ticksize: ticksize.to_f, simple: true)
22
+ end
23
+
24
+
25
+ Then /^it should respond to "([^"]*)"$/ do |method|
26
+ expect(@s).to respond_to(method.to_sym)
27
+ end
28
+
29
+ Then /^([^\s]*) should be set to ([^\s]*)$/ do |var,value|
30
+ expect(@s.instance_variable_defined?(var.to_sym)).to be_truthy
31
+ res = nil
32
+ puts "#{var}---"
33
+ puts "#{value}---"
34
+ eval "res = @s.instance_variable_get(var.to_sym) == #{value}"
35
+ expect(res).to be_truthy
36
+ end
@@ -0,0 +1,42 @@
1
+ Then "it should raise ArgumentError when calling #add with less than 2 arguments" do
2
+ expect{ @s.add }.to raise_error(ArgumentError)
3
+ expect{ @s.add(1)}.to raise_error(ArgumentError)
4
+ end
5
+
6
+ Then "it should raise ArgumentError when calling #add and first argument is neither Integer nor Time" do
7
+ expect{ @s.add("foo", "bar") }.to raise_error(ArgumentError)
8
+ expect{ @s.add(124, 1.22) }.not_to raise_error
9
+ expect{ @s.add(Time.now, 1.22) }.not_to raise_error
10
+ end
11
+
12
+ Then "it should raise ArgumentError when calling #add and second argument is neither Numeric nor Nil" do
13
+ expect{ @s.add(123, "foo") }.to raise_error(ArgumentError)
14
+ expect{ @s.add(123, 11234) }.not_to raise_error
15
+ expect{ @s.add(123, nil ) }.not_to raise_error
16
+ end
17
+
18
+ Then /^calling #add with params (\d+) and nil should return nil$/ do |timestamp|
19
+ expect(@s.add(timestamp, nil)).to be_nil
20
+ end
21
+
22
+ Then /^calling #add with params (\d+) and ([0-9.]+) should return false$/ do |timestamp, value|
23
+ expect(@s.add(timestamp, value.to_f)).to be(false)
24
+ end
25
+
26
+ Then /^#add with params (\d+), ([0-9.]+) should return false$/ do |timestamp, value|
27
+ expect(@s.add(timestamp, value.to_f)).to be(false)
28
+ end
29
+
30
+ Then /^#add with params (\d+), ([0-9.]+) should return an Array$/ do |timestamp, value|
31
+ result = @s.add(timestamp, value.to_f)
32
+ expect(result.class).to be(Array)
33
+ end
34
+
35
+ Given /^a feeder with data from "([^"]+)" is prepared$/ do |filename|
36
+ require 'csv'
37
+ @data = CSV.read(filename).map{|x| [ x[0].to_i, x[1].to_f] }
38
+ end
39
+
40
+ Then "adding all data from file should not raise any error" do
41
+ expect { @data.each {|d| @s.add(d[0],d[1]) } } .not_to raise_error
42
+ end
@@ -0,0 +1,18 @@
1
+ Then "a new simple SpanBar created without options should raise ArgumentError" do
2
+ expect { SpanBar.new }.to raise_error(ArgumentError)
3
+ end
4
+
5
+ Then "a new simple SpanBar created by SpanBarProcessor should not raise" do
6
+ @p = SpanBarProcessor.new(simple: true)
7
+ expect { for i in (1..5); @p.add(i,i); end }.not_to raise_error
8
+ end
9
+
10
+ Given "a valid simple SpanBar is created by SpanBarProcessor" do
11
+ @p = SpanBarProcessor.new(simple: true); @s = true; i = 1;
12
+ while (not @s.is_a?(Array)) do @s = @p.add(i,i); i+=1; end
13
+ @s = @s[0]
14
+ end
15
+
16
+
17
+
18
+
@@ -0,0 +1,26 @@
1
+ Given /^bin\/spanbars is run on the commandline and neither parameters nor STDIN is given$/ do
2
+ end
3
+
4
+ Then /^bin\/spanbars should display help$/ do
5
+ expect{ system("bin/spanbars --help") }.to output(/usage/).to_stdout_from_any_process
6
+ end
7
+
8
+ Given /^bin\/spanbars is run with following parameters it should ouput$/ do
9
+ end
10
+
11
+ Then /^cat ([^ ]*) \| spanbars --both --ticksize ([^ ]*) --span ([^ ]*) should produce ([^ ]*)$/ do |input, ticksize, span, output|
12
+ inputfile = "features/support/#{input}.csv"
13
+ outputfile = "features/support/#{output}.csv"
14
+ result = `cat #{inputfile} | bin/spanbars --both --span #{span} --ticksize #{ticksize}`
15
+ reference = File.read(outputfile)
16
+ expect(result).to eq(reference)
17
+ end
18
+
19
+ Then /^'cat ([^ ]*) \| bin\/spanbars --both --span ([^ ]*) --ticksize ([^' ]*)' should not produce ([^"$ ]*)$/ do |input, span, ticksize, output|
20
+ inputfile = "features/support/#{input}.csv"
21
+ outputfile = "features/support/#{output}.csv"
22
+ result = `cat #{inputfile} | bin/spanbars --both --span #{span} --ticksize #{ticksize}`
23
+ reference = File.read(outputfile)
24
+ expect(result).not_to eq(reference)
25
+ end
26
+
@@ -0,0 +1,6 @@
1
+ require 'rspec'
2
+ RSpec.configure do |config|
3
+ config.expect_with :rspec do |c|
4
+ c.syntax = :expect
5
+ end
6
+ end