stockcruncher 1.2.1 → 1.3.0
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/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
|