stockcruncher 1.2.0 → 1.2.1
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/CHANGELOG +6 -0
- data/lib/stockcruncher/alphavantage.rb +72 -2
- data/lib/stockcruncher/cli.rb +7 -6
- data/lib/stockcruncher/influxdb.rb +2 -61
- data/lib/stockcruncher/version.rb +1 -1
- data/spec/files/SYM.daily +49 -0
- data/spec/files/SYM.quote +10 -0
- data/spec/stockcruncher/cli_spec.rb +3 -8
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4729948dfb9635323240f73d947c2257b1ca4b8a6c2a065a812a8726f502599
|
4
|
+
data.tar.gz: 3686ad0bbb61ef2a3801a2a2c1b3258b543da3015c49b2c959570c9d7b0e8a3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24120c82f3e7cd9d622bcf1636ee528ea69d170fe0f60951b33b34754870a1b92837d5f2cbcd3f59ab4be85264195e7c2058036a557e8b486c8fe55cf81e5c18
|
7
|
+
data.tar.gz: e849aea61ae75175742d97611cffdc889aa3c95394aec2cc8ccbe93ed35bafc6958984fb0fc92b08190a41003c183b753ad287a51676990eee855d32f43bfceb
|
data/CHANGELOG
CHANGED
@@ -8,12 +8,40 @@ module StockCruncher
|
|
8
8
|
class AlphaVantage < Cruncher
|
9
9
|
API_URL = 'https://www.alphavantage.co/query?'
|
10
10
|
|
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
|
37
|
+
end
|
38
|
+
|
11
39
|
# Main method to crunch data.
|
12
40
|
def crunch_daily(symbol, fullsize)
|
13
41
|
url = API_URL + parameters(symbol, 'TIME_SERIES_DAILY')
|
14
42
|
url += '&datatype=csv&outputsize=' + (fullsize ? 'full' : 'compact')
|
15
43
|
res = request(url)
|
16
|
-
res.body
|
44
|
+
transform_daily(res.body)
|
17
45
|
end
|
18
46
|
|
19
47
|
# Main method to crunch data.
|
@@ -21,14 +49,56 @@ module StockCruncher
|
|
21
49
|
url = API_URL + parameters(symbol, 'GLOBAL_QUOTE')
|
22
50
|
url += '&datatype=csv'
|
23
51
|
res = request(url)
|
24
|
-
res.body
|
52
|
+
transform_quote(res.body)
|
25
53
|
end
|
26
54
|
|
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
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set parameters of api call
|
27
65
|
def parameters(symbol, serie)
|
28
66
|
p = 'function=' + serie
|
29
67
|
p += '&symbol=' + symbol
|
30
68
|
p += '&apikey=' + @config[self.class.name.split('::').last]['apikey']
|
31
69
|
p
|
32
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
|
33
103
|
end
|
34
104
|
end
|
data/lib/stockcruncher/cli.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'json'
|
4
5
|
require 'thor'
|
5
6
|
require 'yaml'
|
6
7
|
|
@@ -48,9 +49,9 @@ module StockCruncher
|
|
48
49
|
opts = options.dup
|
49
50
|
config = YAML.load_file(opts['config'])
|
50
51
|
cruncher = StockCruncher::AlphaVantage.new(config, opts['insecure'])
|
51
|
-
|
52
|
-
StockCruncher::InfluxDB.new(config).export_history(symbol,
|
53
|
-
puts
|
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']
|
54
55
|
end
|
55
56
|
|
56
57
|
desc('quote SYMBOL [options]',
|
@@ -59,9 +60,9 @@ module StockCruncher
|
|
59
60
|
opts = options.dup
|
60
61
|
config = YAML.load_file(opts['config'])
|
61
62
|
cruncher = StockCruncher::AlphaVantage.new(config, opts['insecure'])
|
62
|
-
|
63
|
-
StockCruncher::InfluxDB.new(config).export_last_day(
|
64
|
-
puts
|
63
|
+
data = cruncher.crunch_quote(symbol)
|
64
|
+
StockCruncher::InfluxDB.new(config).export_last_day(data)
|
65
|
+
puts JSON.pretty_generate(data) unless opts['quiet']
|
65
66
|
end
|
66
67
|
end
|
67
68
|
end
|
@@ -13,62 +13,16 @@ module StockCruncher
|
|
13
13
|
@insecure = insecure
|
14
14
|
end
|
15
15
|
|
16
|
-
# Method to calculate missing data (previousClose, change, changePercent)
|
17
|
-
def calculate_missing_data(hash)
|
18
|
-
keys = hash.keys
|
19
|
-
hash.each_with_index do |(date, v), index|
|
20
|
-
prevday = keys[index + 1]
|
21
|
-
next if prevday.nil?
|
22
|
-
|
23
|
-
prevclose = hash[prevday]['close']
|
24
|
-
hash[date] = v.merge(generate_missing_data(v['close'], prevclose))
|
25
|
-
end
|
26
|
-
hash
|
27
|
-
end
|
28
|
-
|
29
|
-
# Method to generate missing data
|
30
|
-
def generate_missing_data(current, previous)
|
31
|
-
{
|
32
|
-
'previousClose' => previous,
|
33
|
-
'change' => change(current.to_f, previous.to_f),
|
34
|
-
'changePercent' => change_percent(current.to_f, previous.to_f)
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
# Method to calculate change difference
|
39
|
-
def change(value, base)
|
40
|
-
(value - base).round(4).to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
# Method to calculate percentage of change
|
44
|
-
def change_percent(value, base)
|
45
|
-
((value / base - 1) * 100).round(4).to_s
|
46
|
-
end
|
47
|
-
|
48
|
-
# Method to create a new hash from two arrays of keys and values
|
49
|
-
def create_hash(descriptions, values)
|
50
|
-
descriptions.split(',').zip(values.split(',')).to_h
|
51
|
-
end
|
52
|
-
|
53
16
|
# Method to export latest data to database
|
54
|
-
def export_last_day(
|
55
|
-
raise StandardError, 'No data to export' if raw.match?(/{}/)
|
56
|
-
|
57
|
-
values = create_hash(*raw.split("\r\n"))
|
58
|
-
values['close'] = values.delete('price')
|
59
|
-
values['changePercent'] = values['changePercent'].delete('%')
|
17
|
+
def export_last_day(values)
|
60
18
|
tags = { 'symbol' => values.delete('symbol') }
|
61
19
|
date = values.delete('latestDay')
|
62
20
|
write('daily', tags, values, date)
|
63
21
|
end
|
64
22
|
|
65
23
|
# Method to export historical data to database
|
66
|
-
def export_history(symbol,
|
67
|
-
raise StandardError, 'No data to export' if raw.match?(/Error Message/)
|
68
|
-
|
24
|
+
def export_history(symbol, timeseries)
|
69
25
|
tags = { 'symbol' => symbol }
|
70
|
-
timeseries = prepare_daily_timeserie(raw)
|
71
|
-
timeseries = calculate_missing_data(timeseries)
|
72
26
|
timeseries.each_pair do |date, values|
|
73
27
|
write('daily', tags, values, date)
|
74
28
|
end
|
@@ -84,19 +38,6 @@ module StockCruncher
|
|
84
38
|
string
|
85
39
|
end
|
86
40
|
|
87
|
-
# Method to transform raw data to constructed hash
|
88
|
-
def prepare_daily_timeserie(data)
|
89
|
-
lines = data.split("\r\n")
|
90
|
-
desc = lines.shift.split(',').drop(1)
|
91
|
-
hash = {}
|
92
|
-
lines.each do |line|
|
93
|
-
values = line.split(',')
|
94
|
-
date = values.shift
|
95
|
-
hash[date] = desc.zip(values).to_h
|
96
|
-
end
|
97
|
-
hash
|
98
|
-
end
|
99
|
-
|
100
41
|
# Method to send http post request
|
101
42
|
def request(url, body)
|
102
43
|
uri = URI.parse(url)
|
@@ -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
|
+
}
|
@@ -1,15 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
3
4
|
require 'spec_helper'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
"100.0000,0.0000,0.0000%\r\n"
|
8
|
-
daily = "timestamp,open,high,low,close,volume\r\n2020-08-03,23.8500,24.6500," \
|
9
|
-
"23.7400,24.5500,3112972\r\n2020-07-31,24.0100,24.5100,23.5900,23.81" \
|
10
|
-
"00,5482485\r\n2020-07-30,24.4700,24.6600,23.5200,23.6600,6466738\r" \
|
11
|
-
"\n2020-07-29,24.7500,25.0300,24.0400,24.5600,4605804\r\n2020-07-28," \
|
12
|
-
"25.9900,26.0700,24.5100,24.7500,6904261\r\n"
|
6
|
+
daily = File.read('spec/files/SYM.daily')
|
7
|
+
quote = File.read('spec/files/SYM.quote')
|
13
8
|
|
14
9
|
describe StockCruncher::CLI do # rubocop:disable Metrics/BlockLength
|
15
10
|
context 'version' do
|
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.
|
4
|
+
version: 1.2.1
|
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-
|
11
|
+
date: 2020-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -132,6 +132,8 @@ files:
|
|
132
132
|
- lib/stockcruncher/cruncher.rb
|
133
133
|
- lib/stockcruncher/influxdb.rb
|
134
134
|
- lib/stockcruncher/version.rb
|
135
|
+
- spec/files/SYM.daily
|
136
|
+
- spec/files/SYM.quote
|
135
137
|
- spec/files/stockcruncher.yml
|
136
138
|
- spec/spec_helper.rb
|
137
139
|
- spec/stockcruncher/cli_spec.rb
|