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 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