stockcruncher 1.0.2 → 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: c1fe544c0ab3e0bb05c2cca11cb4363bce698cf88b565d897098420b3b8206ac
4
- data.tar.gz: bc0de76838cf286f9c5400ddc368da33ee2bd748f0a5a29ebed653010e183d8a
3
+ metadata.gz: '00734941064bb0a639312c8908fb983698df14f5ccfe50b5957c0631998d0da7'
4
+ data.tar.gz: 5e27da219578442b05ceebc79863d7e02048d1b97a13b5bd5fa6c8939e1fe2a4
5
5
  SHA512:
6
- metadata.gz: b69c6d91c2160e1e70a339ea68616b27012839b105dcfd0cef5ccf7a9643e3a08fb88738d794cf98e3d4de03e505c32db4a827d45a296d734f7e10d2334eb287
7
- data.tar.gz: 44ecc54323e85117c04bb0aa8b9c2ab54421020a2614b01356988700f610264e1e82a07e88d5fd62f62945c1469c508699b3a78c2dad52a73b988e508ce2264d
6
+ metadata.gz: 27ed506233c74a8690cb75e1e994f5ae639d55e281cd9adde3f2af9b96a675f7043d87395dfd42898af50b6759da7471352716bdd844f3a743ff3f1482ab4dae
7
+ data.tar.gz: 9780064a2b3928040b7acbababb3ba47f2b4ca115c750e8765a8d5bce48b7584c3542c87f2f996c31d81a62eb01676324254fd40c3db6778b9913b03599adbd6
data/CHANGELOG CHANGED
@@ -5,3 +5,26 @@ Changelog
5
5
  -----
6
6
 
7
7
  - Initial version
8
+
9
+ 1.1.0
10
+ -----
11
+
12
+ - Add InfluxDB as database.
13
+ - WIP for daily time serie.
14
+
15
+ 1.1.1
16
+ -----
17
+
18
+ - Rescue error on no data
19
+
20
+ 1.2.0
21
+ -----
22
+
23
+ - daily subcommand now recalculates missing data
24
+ - daily subcommand also writes data to database
25
+
26
+ 1.2.1
27
+ -----
28
+
29
+ - Refactor to preprocess data in source class
30
+ - Refactor to print pretty json instead of comma separated striing
data/README.md CHANGED
@@ -25,29 +25,38 @@ to write the data in a database for later calculation and use.
25
25
 
26
26
  $ gem install stockcruncher
27
27
  $ mkdir /etc/stockcruncher && cd /etc/stockcruncher
28
- $ echo $'AlphaVantage:\n apikey: CHANGEME' > stockcruncher.yml
28
+ $ echo 'AlphaVantage:' > stockcruncher.yml
29
+ $ echo ' apikey: CHANGEME' >> stockcruncher.yml
30
+ $ echo 'InfluxDB:' >> stockcruncher.yml
31
+ $ echo ' scheme: http' >> stockcruncher.yml
32
+ $ echo ' host: localhost' >> stockcruncher.yml
33
+ $ echo ' port: 8086' >> stockcruncher.yml
34
+ $ echo ' user: CHANGEMEAGAIN' >> stockcruncher.yml
35
+ $ echo ' password: CHANGEMETOO' >> stockcruncher.yml
36
+ $ echo ' dbname: stock' >> stockcruncher.yml
29
37
 
30
38
  ## Usage
31
39
 
32
40
  An interactive help is available with:
33
41
 
34
- $ stockcruncher help
42
+ $ stockcruncher help [subcommand]
35
43
 
36
44
  ## Examples
37
45
 
38
46
  To get daily time serie data of a symbol:
39
47
 
40
- $ stockcruncher crunch AAPL daily
48
+ $ stockcruncher daily AAPL
41
49
 
42
50
  To get last day endpoint data of a symbol:
43
51
 
44
- $ stockcruncher crunch AAPL quote_endpoint
52
+ $ stockcruncher quote AAPL
45
53
 
46
54
  ## Limitations
47
55
 
48
56
  Data are currently scraped from AlphaVantage API.
49
57
  More source could be great especially because AlphaVantage doesn't provide EU
50
58
  intraday data.
59
+ InfluxDB is used as database to keep time series data.
51
60
 
52
61
  ## Development
53
62
 
@@ -10,6 +10,9 @@ module StockCruncher
10
10
 
11
11
  def start(args = ARGV)
12
12
  StockCruncher::CLI.start(args)
13
+ rescue StandardError => e
14
+ warn e.message
15
+ exit 1
13
16
  end
14
17
  end
15
18
  end
@@ -8,36 +8,97 @@ module StockCruncher
8
8
  class AlphaVantage < Cruncher
9
9
  API_URL = 'https://www.alphavantage.co/query?'
10
10
 
11
- # Main method to crunch data.
12
- def crunch_daily(symbol, opts)
13
- url = API_URL + parameters(symbol, 'TIME_SERIES_DAILY') + options_d(opts)
14
- res = request(url)
15
- puts res.body
11
+ # Method to calculate missing data (previousClose, change, changePercent)
12
+ def calculate_missing_data(hash)
13
+ keys = hash.keys
14
+ hash.each_with_index do |(date, v), index|
15
+ prevday = keys[index + 1]
16
+ next if prevday.nil?
17
+
18
+ prevclose = hash[prevday]['close']
19
+ hash[date] = v.merge(generate_missing_data(v['close'], prevclose))
20
+ end
21
+ hash
22
+ end
23
+
24
+ # Method to calculate change difference
25
+ def change(value, base)
26
+ (value - base).round(4).to_s
27
+ end
28
+
29
+ # Method to calculate percentage of change
30
+ def change_percent(value, base)
31
+ ((value / base - 1) * 100).round(4).to_s
32
+ end
33
+
34
+ # Method to create a new hash from two arrays of keys and values
35
+ def create_hash(descriptions, values)
36
+ descriptions.split(',').zip(values.split(',')).to_h
16
37
  end
17
38
 
18
39
  # Main method to crunch data.
19
- def crunch_quote(symbol, opts)
20
- url = API_URL + parameters(symbol, 'GLOBAL_QUOTE') + options_q(opts)
40
+ def crunch_daily(symbol, fullsize)
41
+ url = API_URL + parameters(symbol, 'TIME_SERIES_DAILY')
42
+ url += '&datatype=csv&outputsize=' + (fullsize ? 'full' : 'compact')
21
43
  res = request(url)
22
- puts res.body
44
+ transform_daily(res.body)
23
45
  end
24
46
 
25
- def options_d(opts)
26
- o = '&datatype=' + (opts['json'] ? 'json' : 'csv')
27
- o += '&outputsize=' + (opts['full'] ? 'full' : 'compact')
28
- o
47
+ # Main method to crunch data.
48
+ def crunch_quote(symbol)
49
+ url = API_URL + parameters(symbol, 'GLOBAL_QUOTE')
50
+ url += '&datatype=csv'
51
+ res = request(url)
52
+ transform_quote(res.body)
29
53
  end
30
54
 
31
- def options_q(opts)
32
- o = '&datatype=' + (opts['json'] ? 'json' : 'csv')
33
- o
55
+ # Method to generate missing data
56
+ def generate_missing_data(current, previous)
57
+ {
58
+ 'previousClose' => previous,
59
+ 'change' => change(current.to_f, previous.to_f),
60
+ 'changePercent' => change_percent(current.to_f, previous.to_f)
61
+ }
34
62
  end
35
63
 
64
+ # Set parameters of api call
36
65
  def parameters(symbol, serie)
37
66
  p = 'function=' + serie
38
67
  p += '&symbol=' + symbol
39
68
  p += '&apikey=' + @config[self.class.name.split('::').last]['apikey']
40
69
  p
41
70
  end
71
+
72
+ # Method to transform raw data to constructed hash
73
+ def prepare_daily_timeserie(data)
74
+ lines = data.split("\r\n")
75
+ desc = lines.shift.split(',').drop(1)
76
+ hash = {}
77
+ lines.each do |line|
78
+ values = line.split(',')
79
+ date = values.shift
80
+ hash[date] = desc.zip(values).to_h
81
+ end
82
+ hash
83
+ end
84
+
85
+ # Method to transform daily result to nested hash
86
+ def transform_daily(rawdata)
87
+ raise StandardError, 'No data' if rawdata.match?(/Error Message/)
88
+
89
+ values = prepare_daily_timeserie(rawdata)
90
+ values = calculate_missing_data(values)
91
+ values
92
+ end
93
+
94
+ # Method to transform quote result to hash
95
+ def transform_quote(rawdata)
96
+ raise StandardError, 'No data' if rawdata.match?(/{}/)
97
+
98
+ values = create_hash(*rawdata.split("\r\n"))
99
+ values['close'] = values.delete('price')
100
+ values['changePercent'] = values['changePercent'].delete('%')
101
+ values
102
+ end
42
103
  end
43
104
  end
@@ -1,19 +1,14 @@
1
1
  #!/usr/bin/ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'json'
4
5
  require 'thor'
6
+ require 'yaml'
5
7
 
6
8
  module StockCruncher
7
9
  # Simple CLI for StockCruncher
8
10
  class CLI < Thor
9
- desc 'version', 'Print stockcruncher current version'
10
- def version
11
- puts "StockCruncher version #{StockCruncher::VERSION}"
12
- end
13
-
14
- desc('daily SYMBOL [options]',
15
- 'Crunch SYMBOL stock market data for daily time series.')
16
- option(
11
+ class_option(
17
12
  :config,
18
13
  aliases: ['-c'],
19
14
  type: :string,
@@ -21,13 +16,28 @@ module StockCruncher
21
16
  desc: 'Yaml formatted config file to load ' \
22
17
  '(default to /etc/stockcruncher/stockcruncher.yml).'
23
18
  )
24
- option(
25
- :json,
26
- aliases: ['-j'],
19
+ class_option(
20
+ :insecure,
21
+ aliases: ['-k'],
27
22
  type: :boolean,
28
23
  default: false,
29
- desc: 'Json format data (default to csv).'
24
+ desc: 'Ignore SSL certificate (default to false).'
30
25
  )
26
+ class_option(
27
+ :quiet,
28
+ aliases: ['-q'],
29
+ type: :boolean,
30
+ default: false,
31
+ desc: 'Run silently (default to false).'
32
+ )
33
+
34
+ desc 'version', 'Print stockcruncher current version'
35
+ def version
36
+ puts "StockCruncher version #{StockCruncher::VERSION}"
37
+ end
38
+
39
+ desc('daily SYMBOL [options]',
40
+ 'Crunch SYMBOL stock market data for daily time series.')
31
41
  option(
32
42
  :full,
33
43
  aliases: ['-f'],
@@ -37,31 +47,37 @@ module StockCruncher
37
47
  )
38
48
  def daily(symbol)
39
49
  opts = options.dup
40
- cruncher = StockCruncher::AlphaVantage.new(opts['config'])
41
- cruncher.crunch_daily(symbol, opts)
50
+ config = YAML.load_file(opts['config'])
51
+ cruncher = StockCruncher::AlphaVantage.new(config, opts['insecure'])
52
+ data = cruncher.crunch_daily(symbol, opts['full'])
53
+ StockCruncher::InfluxDB.new(config).export_history(symbol, data)
54
+ puts JSON.pretty_generate(data) unless opts['quiet']
42
55
  end
43
56
 
44
- desc('quote SYMBOL [options]',
45
- 'Crunch SYMBOL stock market data for last day quote.')
57
+ desc('movingaverages SYMBOL [options]',
58
+ 'Calculate and export moving averages for requested symbol.')
46
59
  option(
47
- :config,
48
- aliases: ['-c'],
49
- type: :string,
50
- default: '/etc/stockcruncher/stockcruncher.yml',
51
- desc: 'Yaml formatted config file to load ' \
52
- '(default to /etc/stockcruncher/stockcruncher.yml).'
53
- )
54
- option(
55
- :json,
56
- aliases: ['-j'],
60
+ :all,
61
+ aliases: ['-a'],
57
62
  type: :boolean,
58
63
  default: false,
59
- desc: 'Json format data (default to csv).'
64
+ desc: 'Recalculate all MA historical values.'
60
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
+
72
+ desc('quote SYMBOL [options]',
73
+ 'Crunch SYMBOL stock market data for last day quote.')
61
74
  def quote(symbol)
62
75
  opts = options.dup
63
- cruncher = StockCruncher::AlphaVantage.new(opts['config'])
64
- cruncher.crunch_quote(symbol, opts)
76
+ config = YAML.load_file(opts['config'])
77
+ cruncher = StockCruncher::AlphaVantage.new(config, opts['insecure'])
78
+ data = cruncher.crunch_quote(symbol)
79
+ StockCruncher::InfluxDB.new(config).export_last_day(data)
80
+ puts JSON.pretty_generate(data) unless opts['quiet']
65
81
  end
66
82
  end
67
83
  end
@@ -2,27 +2,22 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'net/http'
5
- require 'yaml'
6
5
 
7
6
  module StockCruncher
8
7
  # This is an data cruncher abstract class.
9
8
  class Cruncher
10
9
  # Class constructor method
11
- def initialize(file)
12
- @config = load_conf(file)
13
- end
14
-
15
- # Method to load configurations described in config_file.
16
- def load_conf(file)
17
- YAML.load_file(file)
10
+ def initialize(config, insecure = false)
11
+ @config = config
12
+ @insecure = insecure
18
13
  end
19
14
 
20
15
  # Method to send http get request
21
- def request(url, insecure = false)
16
+ def request(url)
22
17
  uri = URI.parse(url)
23
18
  http = Net::HTTP.new(uri.host, uri.port)
24
19
  http.use_ssl = uri.scheme.eql?('https')
25
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
20
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @insecure
26
21
  req = Net::HTTP::Get.new(uri.request_uri)
27
22
  http.request(req)
28
23
  end
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'date'
5
+ require 'json'
6
+ require 'net/http'
7
+
8
+ module StockCruncher
9
+ # this is a class to write time series to database
10
+ class InfluxDB
11
+ # Class constructor method
12
+ def initialize(config, insecure = false)
13
+ @cfg = config[self.class.name.split('::').last]
14
+ @insecure = insecure
15
+ end
16
+
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 }
39
+ end
40
+
41
+ # Method to export historical data to database
42
+ def export_history(symbol, timeseries)
43
+ tags = create_tags(symbol)
44
+ timeseries.each_pair do |date, values|
45
+ write('daily', tags, values, date)
46
+ end
47
+ end
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
+
56
+ # Method to format and array of values into comma separated string
57
+ def format_values(values)
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]
80
+ end
81
+
82
+ # Method to send http post request
83
+ def request(url, body)
84
+ uri = URI.parse(url)
85
+ http = Net::HTTP.new(uri.host, uri.port)
86
+ http.use_ssl = uri.scheme.eql?('https')
87
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @insecure
88
+ req = Net::HTTP::Post.new(uri.request_uri)
89
+ req.basic_auth(@cfg['user'], @cfg['password'])
90
+ req.body = body
91
+ http.request(req)
92
+ end
93
+
94
+ # Method to write data in bucket
95
+ def write(name, tags, values, date)
96
+ url = "#{@cfg['scheme']}://#{@cfg['host']}:#{@cfg['port']}/write?" \
97
+ "db=#{@cfg['dbname']}"
98
+ timestamp = DateTime.parse(date + 'T18:00:00').strftime('%s%N')
99
+ body = "#{name},#{format_values(tags)} #{format_values(values)} " \
100
+ "#{timestamp}"
101
+ request(url, body)
102
+ end
103
+ end
104
+ end
@@ -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.0.2'
5
+ VERSION = '1.3.0'
6
6
  end
@@ -0,0 +1,49 @@
1
+ {
2
+ "2020-08-03": {
3
+ "open": "23.8500",
4
+ "high": "24.6500",
5
+ "low": "23.7400",
6
+ "close": "24.5500",
7
+ "volume": "3112972",
8
+ "previousClose": "23.8100",
9
+ "change": "0.74",
10
+ "changePercent": "3.1079"
11
+ },
12
+ "2020-07-31": {
13
+ "open": "24.0100",
14
+ "high": "24.5100",
15
+ "low": "23.5900",
16
+ "close": "23.8100",
17
+ "volume": "5482485",
18
+ "previousClose": "23.6600",
19
+ "change": "0.15",
20
+ "changePercent": "0.634"
21
+ },
22
+ "2020-07-30": {
23
+ "open": "24.4700",
24
+ "high": "24.6600",
25
+ "low": "23.5200",
26
+ "close": "23.6600",
27
+ "volume": "6466738",
28
+ "previousClose": "24.5600",
29
+ "change": "-0.9",
30
+ "changePercent": "-3.6645"
31
+ },
32
+ "2020-07-29": {
33
+ "open": "24.7500",
34
+ "high": "25.0300",
35
+ "low": "24.0400",
36
+ "close": "24.5600",
37
+ "volume": "4605804",
38
+ "previousClose": "24.7500",
39
+ "change": "-0.19",
40
+ "changePercent": "-0.7677"
41
+ },
42
+ "2020-07-28": {
43
+ "open": "25.9900",
44
+ "high": "26.0700",
45
+ "low": "24.5100",
46
+ "close": "24.7500",
47
+ "volume": "6904261"
48
+ }
49
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "open": "100.0000",
3
+ "high": "100.1000",
4
+ "low": "99.9000",
5
+ "volume": "4",
6
+ "previousClose": "100.0000",
7
+ "change": "0.0000",
8
+ "changePercent": "0.0000",
9
+ "close": "100.0000"
10
+ }
@@ -1,2 +1,9 @@
1
1
  AlphaVantage:
2
2
  apikey: demo
3
+ InfluxDB:
4
+ scheme: http
5
+ host: localhost
6
+ port: 8086
7
+ user: testuser
8
+ password: testpassword
9
+ dbname: test
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require 'spec_helper'
4
5
 
5
- describe StockCruncher::CLI do
6
+ daily = File.read('spec/files/SYM.daily')
7
+ quote = File.read('spec/files/SYM.quote')
8
+
9
+ describe StockCruncher::CLI do # rubocop:disable Metrics/BlockLength
6
10
  context 'version' do
7
11
  it 'prints the version.' do
8
12
  out = "StockCruncher version #{StockCruncher::VERSION}\n"
@@ -10,15 +14,33 @@ describe StockCruncher::CLI do
10
14
  end
11
15
  end
12
16
 
17
+ context 'daily NODATA -c spec/files/stockcruncher.yml' do
18
+ it 'Should not get any data and should fail.' do
19
+ expect { start(self) }.to raise_error(SystemExit)
20
+ end
21
+ end
22
+
13
23
  context 'daily SYM -c spec/files/stockcruncher.yml' do
14
24
  it 'Get the daily time serie for SYM.' do
15
- expect { start(self) }.to output("{}\n").to_stdout
25
+ expect { start(self) }.to output(daily).to_stdout
26
+ end
27
+ end
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
+
35
+ context 'quote NODATA -c spec/files/stockcruncher.yml' do
36
+ it 'Should not get any data and should fail.' do
37
+ expect { start(self) }.to raise_error(SystemExit)
16
38
  end
17
39
  end
18
40
 
19
41
  context 'quote SYM -c spec/files/stockcruncher.yml' do
20
42
  it 'Get the quote for SYM.' do
21
- expect { start(self) }.to output("{}\n").to_stdout
43
+ expect { start(self) }.to output(quote).to_stdout
22
44
  end
23
45
  end
24
46
 
@@ -2,15 +2,42 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
+ quote = 'symbol,open,high,low,price,volume,latestDay,previousClose,change,ch' \
6
+ "angePercent\r\nSYM,100.0000,100.1000,99.9000,100.0000,4,2020-07-30," \
7
+ "100.0000,0.0000,0.0000%\r\n"
8
+ q_err = '{}'
9
+ daily = "timestamp,open,high,low,close,volume\r\n2020-08-03,23.8500,24.6500," \
10
+ "23.7400,24.5500,3112972\r\n2020-07-31,24.0100,24.5100,23.5900,23.81" \
11
+ "00,5482485\r\n2020-07-30,24.4700,24.6600,23.5200,23.6600,6466738\r" \
12
+ "\n2020-07-29,24.7500,25.0300,24.0400,24.5600,4605804\r\n2020-07-28," \
13
+ "25.9900,26.0700,24.5100,24.7500,6904261\r\n"
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]]}]}]}'
20
+
5
21
  RSpec.configure do |config|
6
22
  config.before(:each) do
7
23
  # requests an API without extra arguments
24
+ stub_request(:get, 'https://www.alphavantage.co/query?' \
25
+ 'function=GLOBAL_QUOTE&symbol=NODATA&apikey=demo&datatype=csv')
26
+ .to_return('status' => 200, 'body' => q_err, 'headers' => {})
8
27
  stub_request(:get, 'https://www.alphavantage.co/query?' \
9
28
  'function=GLOBAL_QUOTE&symbol=SYM&apikey=demo&datatype=csv')
10
- .to_return('status' => 200, 'body' => '{}', 'headers' => {})
29
+ .to_return('status' => 200, 'body' => quote, 'headers' => {})
30
+ stub_request(:get, 'https://www.alphavantage.co/query?' \
31
+ 'function=TIME_SERIES_DAILY&symbol=NODATA&apikey=demo' \
32
+ '&datatype=csv&outputsize=compact')
33
+ .to_return('status' => 200, 'body' => d_err, 'headers' => {})
11
34
  stub_request(:get, 'https://www.alphavantage.co/query?' \
12
35
  'function=TIME_SERIES_DAILY&symbol=SYM&apikey=demo' \
13
36
  '&datatype=csv&outputsize=compact')
14
- .to_return('status' => 200, 'body' => '{}', 'headers' => {})
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' => {})
40
+ stub_request(:post, 'http://localhost:8086/write?db=test')
41
+ .to_return('status' => 204, 'body' => '', 'headers' => {})
15
42
  end
16
43
  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.0.2
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-07-30 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
@@ -130,10 +130,13 @@ files:
130
130
  - lib/stockcruncher/alphavantage.rb
131
131
  - lib/stockcruncher/cli.rb
132
132
  - lib/stockcruncher/cruncher.rb
133
+ - lib/stockcruncher/influxdb.rb
134
+ - lib/stockcruncher/stats.rb
133
135
  - lib/stockcruncher/version.rb
136
+ - spec/files/SYM.daily
137
+ - spec/files/SYM.quote
134
138
  - spec/files/stockcruncher.yml
135
139
  - spec/spec_helper.rb
136
- - spec/stockcruncher/alphavantage_spec.rb
137
140
  - spec/stockcruncher/cli_spec.rb
138
141
  - spec/stockcruncher/stubs/servers_stubs.rb
139
142
  - spec/stockcruncher_spec.rb
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe StockCruncher::AlphaVantage do
6
- context 'daily SYM -c spec/files/stockcruncher.yml' do
7
- it 'requests a daily time serie.' do
8
- expect { start(self) }.to output("{}\n").to_stdout
9
- end
10
- end
11
- context 'quote SYM -c spec/files/stockcruncher.yml' do
12
- it 'requests a quote endpoint.' do
13
- expect { start(self) }.to output("{}\n").to_stdout
14
- end
15
- end
16
- end