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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4729948dfb9635323240f73d947c2257b1ca4b8a6c2a065a812a8726f502599
4
- data.tar.gz: 3686ad0bbb61ef2a3801a2a2c1b3258b543da3015c49b2c959570c9d7b0e8a3a
3
+ metadata.gz: '00734941064bb0a639312c8908fb983698df14f5ccfe50b5957c0631998d0da7'
4
+ data.tar.gz: 5e27da219578442b05ceebc79863d7e02048d1b97a13b5bd5fa6c8939e1fe2a4
5
5
  SHA512:
6
- metadata.gz: 24120c82f3e7cd9d622bcf1636ee528ea69d170fe0f60951b33b34754870a1b92837d5f2cbcd3f59ab4be85264195e7c2058036a557e8b486c8fe55cf81e5c18
7
- data.tar.gz: e849aea61ae75175742d97611cffdc889aa3c95394aec2cc8ccbe93ed35bafc6958984fb0fc92b08190a41003c183b753ad287a51676990eee855d32f43bfceb
6
+ metadata.gz: 27ed506233c74a8690cb75e1e994f5ae639d55e281cd9adde3f2af9b96a675f7043d87395dfd42898af50b6759da7471352716bdd844f3a743ff3f1482ab4dae
7
+ data.tar.gz: 9780064a2b3928040b7acbababb3ba47f2b4ca115c750e8765a8d5bce48b7584c3542c87f2f996c31d81a62eb01676324254fd40c3db6778b9913b03599adbd6
@@ -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
- # Method to export latest data to database
17
- def export_last_day(values)
18
- tags = { 'symbol' => values.delete('symbol') }
19
- date = values.delete('latestDay')
20
- write('daily', tags, values, date)
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 = { 'symbol' => symbol }
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
- string = ''
34
- values.each_pair do |k, v|
35
- string += "#{k}=#{v}"
36
- string += ',' unless k == values.keys.last
37
- end
38
- string
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module StockCruncher
5
- VERSION = '1.2.1'
5
+ VERSION = '1.3.0'
6
6
  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.2.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-08-12 00:00:00.000000000 Z
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