stockcruncher 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/stockcruncher/cli.rb +15 -0
- data/lib/stockcruncher/influxdb.rb +53 -12
- data/lib/stockcruncher/stats.rb +74 -0
- data/lib/stockcruncher/version.rb +1 -1
- data/spec/stockcruncher/cli_spec.rb +6 -0
- data/spec/stockcruncher/stubs/servers_stubs.rb +7 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '00734941064bb0a639312c8908fb983698df14f5ccfe50b5957c0631998d0da7'
|
4
|
+
data.tar.gz: 5e27da219578442b05ceebc79863d7e02048d1b97a13b5bd5fa6c8939e1fe2a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27ed506233c74a8690cb75e1e994f5ae639d55e281cd9adde3f2af9b96a675f7043d87395dfd42898af50b6759da7471352716bdd844f3a743ff3f1482ab4dae
|
7
|
+
data.tar.gz: 9780064a2b3928040b7acbababb3ba47f2b4ca115c750e8765a8d5bce48b7584c3542c87f2f996c31d81a62eb01676324254fd40c3db6778b9913b03599adbd6
|
data/lib/stockcruncher/cli.rb
CHANGED
@@ -54,6 +54,21 @@ module StockCruncher
|
|
54
54
|
puts JSON.pretty_generate(data) unless opts['quiet']
|
55
55
|
end
|
56
56
|
|
57
|
+
desc('movingaverages SYMBOL [options]',
|
58
|
+
'Calculate and export moving averages for requested symbol.')
|
59
|
+
option(
|
60
|
+
:all,
|
61
|
+
aliases: ['-a'],
|
62
|
+
type: :boolean,
|
63
|
+
default: false,
|
64
|
+
desc: 'Recalculate all MA historical values.'
|
65
|
+
)
|
66
|
+
def movingaverages(symbol)
|
67
|
+
opts = options.dup
|
68
|
+
config = YAML.load_file(opts['config'])
|
69
|
+
StockCruncher::InfluxDB.new(config).moving_averages(symbol, opts['all'])
|
70
|
+
end
|
71
|
+
|
57
72
|
desc('quote SYMBOL [options]',
|
58
73
|
'Crunch SYMBOL stock market data for last day quote.')
|
59
74
|
def quote(symbol)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'date'
|
5
|
+
require 'json'
|
5
6
|
require 'net/http'
|
6
7
|
|
7
8
|
module StockCruncher
|
@@ -13,29 +14,69 @@ module StockCruncher
|
|
13
14
|
@insecure = insecure
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def get_daily_values(symbol, fullsize)
|
18
|
+
values = %w[close change changePercent volume]
|
19
|
+
data = query('daily', symbol, values, fullsize)
|
20
|
+
data['columns'].zip(data['values'].transpose).to_h
|
21
|
+
end
|
22
|
+
|
23
|
+
# Method to calculate moving averages based on last day values
|
24
|
+
def moving_averages(symbol, fullsize)
|
25
|
+
series = get_daily_values(symbol, fullsize)
|
26
|
+
tags = create_tags(symbol)
|
27
|
+
series['close'].each_index do |i|
|
28
|
+
date = series['time'][i]
|
29
|
+
serie = series['close'][i, 201]
|
30
|
+
weights = series['volume'][i, 201]
|
31
|
+
write_moving_averages(tags, serie, weights, date)
|
32
|
+
break unless fullsize
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Method to create tags hash containing only symbol
|
37
|
+
def create_tags(symbol)
|
38
|
+
{ 'symbol' => symbol }
|
21
39
|
end
|
22
40
|
|
23
41
|
# Method to export historical data to database
|
24
42
|
def export_history(symbol, timeseries)
|
25
|
-
tags =
|
43
|
+
tags = create_tags(symbol)
|
26
44
|
timeseries.each_pair do |date, values|
|
27
45
|
write('daily', tags, values, date)
|
28
46
|
end
|
29
47
|
end
|
30
48
|
|
49
|
+
# Method to export latest data to database
|
50
|
+
def export_last_day(values)
|
51
|
+
tags = create_tags(values.delete('symbol'))
|
52
|
+
date = values.delete('latestDay')
|
53
|
+
write('daily', tags, values, date)
|
54
|
+
end
|
55
|
+
|
31
56
|
# Method to format and array of values into comma separated string
|
32
57
|
def format_values(values)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
58
|
+
values.map { |k, v| "#{k}=#{v}" }.join(',')
|
59
|
+
end
|
60
|
+
|
61
|
+
# Method to calculate all statistics
|
62
|
+
def write_moving_averages(tags, serie, weights, date)
|
63
|
+
write('ema', tags, StockCruncher::Stats.list_ema(serie), date)
|
64
|
+
write('lwma', tags, StockCruncher::Stats.list_lwma(serie), date)
|
65
|
+
write('sma', tags, StockCruncher::Stats.list_sma(serie), date)
|
66
|
+
write('vwma', tags, StockCruncher::Stats.list_vwma(serie, weights), date)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Method to query data in bucket
|
70
|
+
def query(name, symbol, values, full)
|
71
|
+
url = "#{@cfg['scheme']}://#{@cfg['host']}:#{@cfg['port']}/query?" \
|
72
|
+
"db=#{@cfg['dbname']}"
|
73
|
+
size = full ? '' : 'LIMIT 201'
|
74
|
+
body = "q=SELECT #{values.join(',')} FROM #{name} " \
|
75
|
+
"WHERE symbol = '#{symbol}' ORDER BY time DESC #{size}"
|
76
|
+
data = JSON.parse(request(url, body).body)['results'][0]['series']
|
77
|
+
raise StandardError, 'No data' if data.nil?
|
78
|
+
|
79
|
+
data[0]
|
39
80
|
end
|
40
81
|
|
41
82
|
# Method to send http post request
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module StockCruncher
|
5
|
+
# this is a module with various statistic calculation methods
|
6
|
+
module Stats
|
7
|
+
extend self
|
8
|
+
|
9
|
+
RANGES = [5, 10, 20, 30, 50, 100, 200].freeze
|
10
|
+
|
11
|
+
# Calculate multiple range of exponential moving average
|
12
|
+
def list_ema(values)
|
13
|
+
h = {}
|
14
|
+
RANGES.each do |n|
|
15
|
+
next if values.size < n + 1
|
16
|
+
|
17
|
+
h["ema#{n}"] = ema(values[0, n + 1])
|
18
|
+
end
|
19
|
+
h
|
20
|
+
end
|
21
|
+
|
22
|
+
# Calculate multiple range of linearly weighted moving average
|
23
|
+
def list_lwma(values)
|
24
|
+
h = {}
|
25
|
+
RANGES.each do |n|
|
26
|
+
next if values.size < n
|
27
|
+
|
28
|
+
weights = (1..n).to_a.reverse
|
29
|
+
h["lwma#{n}"] = sma(values[0, n], weights)
|
30
|
+
end
|
31
|
+
h
|
32
|
+
end
|
33
|
+
|
34
|
+
# Calculate multiple range of simple moving average
|
35
|
+
def list_sma(values)
|
36
|
+
h = {}
|
37
|
+
RANGES.each do |n|
|
38
|
+
next if values.size < n
|
39
|
+
|
40
|
+
h["sma#{n}"] = sma(values[0, n])
|
41
|
+
end
|
42
|
+
h
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculate multiple range of volume weighted moving average
|
46
|
+
def list_vwma(values, volumes)
|
47
|
+
h = {}
|
48
|
+
RANGES.each do |n|
|
49
|
+
next if values.size < n
|
50
|
+
|
51
|
+
h["lwma#{n}"] = sma(values[0, n], volumes[0, n])
|
52
|
+
end
|
53
|
+
h
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Calculate exponential moving average
|
59
|
+
def ema(array, factor = 2, weights = nil)
|
60
|
+
f = factor.to_f / array.size
|
61
|
+
n = array.size - 1
|
62
|
+
tsma = sma(array[0, n], weights)
|
63
|
+
ysma = sma(array[1, n], weights)
|
64
|
+
(tsma * f + ysma * (1 - f)).round(4)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Calculate simple moving average
|
68
|
+
def sma(array, weights = nil)
|
69
|
+
factor = weights.nil? ? Array.new(array.size, 1) : weights
|
70
|
+
dividend = array.each_with_index.map { |v, i| v * factor[i] }
|
71
|
+
(dividend.sum.to_f / factor.sum).round(4)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -26,6 +26,12 @@ describe StockCruncher::CLI do # rubocop:disable Metrics/BlockLength
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
context 'movingaverages SYM -c spec/files/stockcruncher.yml' do
|
30
|
+
it 'Get the daily time serie for SYM.' do
|
31
|
+
expect { start(self) }.to output('').to_stdout
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
29
35
|
context 'quote NODATA -c spec/files/stockcruncher.yml' do
|
30
36
|
it 'Should not get any data and should fail.' do
|
31
37
|
expect { start(self) }.to raise_error(SystemExit)
|
@@ -12,6 +12,11 @@ daily = "timestamp,open,high,low,close,volume\r\n2020-08-03,23.8500,24.6500," \
|
|
12
12
|
"\n2020-07-29,24.7500,25.0300,24.0400,24.5600,4605804\r\n2020-07-28," \
|
13
13
|
"25.9900,26.0700,24.5100,24.7500,6904261\r\n"
|
14
14
|
d_err = "{\n \"Error Message\": \"Invalid API call.\"\n}"
|
15
|
+
mvavg = '{"results":[{"statement_id":0,"series":[{"name":"daily","columns":[' \
|
16
|
+
'"time","close","change","changePercent","volume"],"values":[["2017-' \
|
17
|
+
'03-01T18:00:00Z",1,0,0,1],["2017-03-02T18:00:00Z",1,0,0,1],["2017-0' \
|
18
|
+
'3-03T18:00:00Z",1,0,0,1],["2017-03-04T18:00:00Z",1,0,0,1],["2017-03' \
|
19
|
+
'-05T18:00:00Z",1,0,0,1],["2017-03-06T18:00:00Z",1,0,0,1]]}]}]}'
|
15
20
|
|
16
21
|
RSpec.configure do |config|
|
17
22
|
config.before(:each) do
|
@@ -30,6 +35,8 @@ RSpec.configure do |config|
|
|
30
35
|
'function=TIME_SERIES_DAILY&symbol=SYM&apikey=demo' \
|
31
36
|
'&datatype=csv&outputsize=compact')
|
32
37
|
.to_return('status' => 200, 'body' => daily, 'headers' => {})
|
38
|
+
stub_request(:post, 'http://localhost:8086/query?db=test')
|
39
|
+
.to_return('status' => 204, 'body' => mvavg, 'headers' => {})
|
33
40
|
stub_request(:post, 'http://localhost:8086/write?db=test')
|
34
41
|
.to_return('status' => 204, 'body' => '', 'headers' => {})
|
35
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stockcruncher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Delaplace
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -131,6 +131,7 @@ files:
|
|
131
131
|
- lib/stockcruncher/cli.rb
|
132
132
|
- lib/stockcruncher/cruncher.rb
|
133
133
|
- lib/stockcruncher/influxdb.rb
|
134
|
+
- lib/stockcruncher/stats.rb
|
134
135
|
- lib/stockcruncher/version.rb
|
135
136
|
- spec/files/SYM.daily
|
136
137
|
- spec/files/SYM.quote
|