sqa 0.0.11 → 0.0.12
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/checksums/sqa-0.0.12.gem.sha512 +1 -0
- data/lib/sqa/config.rb +12 -10
- data/lib/sqa/data_frame.rb +1 -1
- data/lib/sqa/indicator/predict_next_value.rb +153 -39
- data/lib/sqa/stock.rb +2 -0
- data/lib/sqa/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39185fb1deccd8cc248d4ad776b724ea5ebe1a7e93fdc1d6ada4f53661a55bba
|
4
|
+
data.tar.gz: 3b394f997cdcdd67ec9eced2cd7bc08c1af2b407b8945e1a76e0383591ca3495
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d805f6db11375738db0cdad8385f7a74c26c3fba39f0a7aa88593532e1127af7c4c690844eae56f6203696743e1b655bb9d996f2e71745cefb4f04227bbe622
|
7
|
+
data.tar.gz: c52327bfbf15f25159a14af31be0881112e169dc73dbdab7d7c7ac7519208e366314e4ab6bd31d44134fbda5238dc7b56e553dcc8c7d231938b78af3ec17d137
|
@@ -0,0 +1 @@
|
|
1
|
+
2d4a5156118549d604963b8f4dd0b311459f5ad6a1747f2c4925d45f12fe94a863eb225d3c593f82face964773ccbf59478612a014ee9fc32814baf627f730c7
|
data/lib/sqa/config.rb
CHANGED
@@ -86,17 +86,23 @@ module SQA
|
|
86
86
|
# TODO: arrange order in mostly often used
|
87
87
|
|
88
88
|
if ".json" == type
|
89
|
-
form_json
|
89
|
+
incoming = form_json
|
90
90
|
|
91
91
|
elsif %w[.yml .yaml].include?(type)
|
92
|
-
from_yaml
|
92
|
+
incoming = from_yaml
|
93
93
|
|
94
94
|
elsif ".toml" == type
|
95
|
-
from_toml
|
95
|
+
incoming = from_toml
|
96
96
|
|
97
97
|
else
|
98
98
|
raise BadParameterError, "Invalid Config File: #{config_file}"
|
99
99
|
end
|
100
|
+
|
101
|
+
if incoming.has_key? :data_dir
|
102
|
+
incoming[:data_dir] = incoming[:data_dir].gsub(/^~/, Nenv.home)
|
103
|
+
end
|
104
|
+
|
105
|
+
merge! incoming
|
100
106
|
end
|
101
107
|
|
102
108
|
def dump_file
|
@@ -142,19 +148,15 @@ module SQA
|
|
142
148
|
## override values from a config file
|
143
149
|
|
144
150
|
def from_json
|
145
|
-
|
146
|
-
debug_me{[ :incoming ]}
|
151
|
+
::JSON.load(File.open(config_file).read).symbolize_keys
|
147
152
|
end
|
148
153
|
|
149
154
|
def from_toml
|
150
|
-
|
151
|
-
debug_me{[ :incoming ]}
|
155
|
+
TomlRB.load_file(config_file).symbolize_keys
|
152
156
|
end
|
153
157
|
|
154
158
|
def from_yaml
|
155
|
-
|
156
|
-
debug_me{[ :incoming ]}
|
157
|
-
merge! incoming
|
159
|
+
::YAML.load_file(config_file).symbolize_keys
|
158
160
|
end
|
159
161
|
|
160
162
|
|
data/lib/sqa/data_frame.rb
CHANGED
@@ -35,7 +35,7 @@ class SQA::DataFrame < Daru::DataFrame
|
|
35
35
|
#################################################
|
36
36
|
|
37
37
|
def self.path(filename)
|
38
|
-
Pathname.new
|
38
|
+
Pathname.new(SQA.config.data_dir) + filename
|
39
39
|
end
|
40
40
|
|
41
41
|
def self.load(filename, options={}, &block)
|
@@ -5,19 +5,91 @@ end
|
|
5
5
|
|
6
6
|
class SQA::Indicator; class << self
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
# Produce a Table show actual values and forecasted values
|
9
|
+
#
|
10
|
+
# actual .... Array of Float
|
11
|
+
# forecast .. Array of Float or Array of Array of Float
|
12
|
+
# entry is either a single value or
|
13
|
+
# an Array [high, guess, low]
|
14
|
+
#
|
15
|
+
def prediction_test(actual, forecast)
|
16
|
+
|
17
|
+
unless actual.size == forecast.size
|
18
|
+
debug_me("== ERROR =="){[
|
19
|
+
"actual.size",
|
20
|
+
"forecast.size"
|
21
|
+
]}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Method Under Test (MUT)
|
25
|
+
mut = caller[0][/`([^']*)'/, 1]
|
26
|
+
window = actual.size
|
27
|
+
hgl = forecast.first.is_a?(Array)
|
28
|
+
|
29
|
+
if hgl
|
30
|
+
headers = %w[ Actual Forecast Diff %off InRange? High Low ]
|
31
|
+
else
|
32
|
+
headers = %w[ Actual Forecast Diff %off ]
|
33
|
+
end
|
34
|
+
|
35
|
+
diff = []
|
36
|
+
percent = []
|
37
|
+
values = []
|
38
|
+
|
39
|
+
actual.map!{|v| v.round(3)}
|
40
|
+
|
41
|
+
if hgl
|
42
|
+
high = forecast.map{|v| v[0].round(3)}
|
43
|
+
guess = forecast.map{|v| v[1].round(3)}
|
44
|
+
low = forecast.map{|v| v[2].round(3)}
|
45
|
+
else
|
46
|
+
guess = forecast.map{|v| v.round(3)}
|
47
|
+
end
|
48
|
+
|
49
|
+
window.times do |x|
|
50
|
+
diff << (actual[x] - guess[x]).round(3)
|
51
|
+
percent << ((diff.last / guess[x])*100.0).round(3)
|
52
|
+
|
53
|
+
entry = [
|
54
|
+
actual[x], guess[x],
|
55
|
+
diff[x], percent[x],
|
56
|
+
]
|
57
|
+
|
58
|
+
if hgl
|
59
|
+
entry << ( (high[x] >= actual[x] && actual[x] >= low[x]) ? "Yes" : "" )
|
60
|
+
entry << high[x]
|
61
|
+
entry << low[x]
|
62
|
+
end
|
63
|
+
|
64
|
+
values << entry
|
65
|
+
end
|
66
|
+
|
67
|
+
the_table = TTY::Table.new(headers, values)
|
68
|
+
|
69
|
+
puts "\n#{mut} Result Validation"
|
70
|
+
|
71
|
+
puts the_table.render(
|
72
|
+
:unicode,
|
73
|
+
{
|
74
|
+
padding: [0, 0, 0, 0],
|
75
|
+
alignments: [:right]*values.first.size,
|
76
|
+
}
|
77
|
+
)
|
78
|
+
puts
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def predict_next_values(stock, window, testing=false)
|
83
|
+
prices = stock.df.adj_close_price.to_a
|
84
|
+
known = prices.pop(window) if testing
|
85
|
+
result = []
|
86
|
+
|
87
|
+
prices.each_cons(2) do |a, b|
|
16
88
|
result << b + (b - a)
|
17
89
|
end
|
18
90
|
|
19
|
-
if
|
20
|
-
(1..
|
91
|
+
if window > 0
|
92
|
+
(1..window).each do |_|
|
21
93
|
last_two_values = result.last(2)
|
22
94
|
delta = last_two_values.last - last_two_values.first
|
23
95
|
next_value = last_two_values.last + delta
|
@@ -25,64 +97,106 @@ class SQA::Indicator; class << self
|
|
25
97
|
end
|
26
98
|
end
|
27
99
|
|
28
|
-
result.last(
|
100
|
+
prediction_test(known, result.last(window)) if testing
|
101
|
+
|
102
|
+
result.last(window)
|
29
103
|
end
|
30
104
|
alias_method :pnv, :predict_next_values
|
31
105
|
|
32
106
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
# the forecast goes. This does not produce that kind of probability
|
38
|
-
# cone; but, that was hwat I was thinking about
|
39
|
-
#
|
40
|
-
# array is an Array - for example historical price data
|
41
|
-
# predictions is an Integer for how many predictions into the future
|
42
|
-
#
|
43
|
-
def pnv2(array, predictions)
|
107
|
+
def pnv2(stock, window, testing=false)
|
108
|
+
prices = stock.df.adj_close_price.to_a
|
109
|
+
known = prices.pop(window) if testing
|
110
|
+
|
44
111
|
result = []
|
45
|
-
last_inx =
|
112
|
+
last_inx = prices.size - 1 # indexes are zero based
|
46
113
|
|
47
|
-
|
114
|
+
window.times do |x|
|
48
115
|
x += 1 # forecasting 1 day into the future needs 2 days of near past data
|
49
116
|
|
50
117
|
# window is the near past values
|
51
|
-
window =
|
118
|
+
window = prices[last_inx-x..]
|
52
119
|
|
53
120
|
high = window.max
|
54
121
|
low = window.min
|
55
122
|
midpoint = (high + low) / 2.0
|
56
123
|
|
57
|
-
result
|
124
|
+
result << [high, midpoint, low]
|
125
|
+
end
|
126
|
+
|
127
|
+
prediction_test(known, result) if testing
|
128
|
+
|
129
|
+
result
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def pnv3(stock, window, testing=false)
|
134
|
+
prices = stock.df.adj_close_price.to_a
|
135
|
+
known = prices.pop(window) if testing
|
136
|
+
|
137
|
+
result = []
|
138
|
+
known = prices.last(window)
|
139
|
+
|
140
|
+
last_inx = prices.size - 1
|
141
|
+
|
142
|
+
(0..window-1).to_a.reverse.each do |x|
|
143
|
+
curr_inx = last_inx - x
|
144
|
+
prev_inx = curr_inx - 1
|
145
|
+
current_price = prices[curr_inx]
|
146
|
+
percentage_change = (current_price - prices[prev_inx]) / prices[prev_inx]
|
147
|
+
|
148
|
+
result << current_price + (current_price * percentage_change)
|
58
149
|
end
|
59
150
|
|
151
|
+
prediction_test(known, result) if testing
|
152
|
+
|
60
153
|
result
|
61
154
|
end
|
62
155
|
|
63
156
|
|
64
|
-
def
|
65
|
-
|
66
|
-
known
|
157
|
+
def pnv4(stock, window, testing=false)
|
158
|
+
prices = stock.df.adj_close_price.to_a
|
159
|
+
known = prices.pop(window) if testing
|
160
|
+
|
161
|
+
result = []
|
162
|
+
known = prices.last(window).dup
|
163
|
+
current_price = known.last
|
67
164
|
|
68
165
|
# Loop through the prediction window size
|
69
|
-
(1..
|
70
|
-
current_price = known.last
|
166
|
+
(1..window).each do |x|
|
71
167
|
|
72
168
|
# Calculate the percentage change between the current price and its previous price
|
73
|
-
percentage_change = (current_price -
|
169
|
+
percentage_change = (current_price - prices[-x]) / prices[-x]
|
74
170
|
|
75
|
-
|
76
|
-
predicted_price = current_price + (current_price * percentage_change)
|
77
|
-
predicted_prices.unshift(predicted_price)
|
78
|
-
|
79
|
-
# Update the prices array for the next iteration
|
80
|
-
known.pop
|
171
|
+
result << current_price + (current_price * percentage_change)
|
81
172
|
end
|
82
173
|
|
83
|
-
|
174
|
+
prediction_test(known, result) if testing
|
175
|
+
|
176
|
+
result
|
84
177
|
end
|
85
178
|
|
86
179
|
|
180
|
+
def pnv5(stock, window, testing=false)
|
181
|
+
prices = stock.df.adj_close_price.to_a
|
182
|
+
known = prices.pop(window) if testing
|
183
|
+
|
184
|
+
result = []
|
185
|
+
current_price = prices.last
|
186
|
+
|
187
|
+
rate = 0.9 # convert angle into percentage
|
188
|
+
sma_trend = stock.indicators.sma_trend
|
189
|
+
percentage_change = 1 + (sma_trend[:angle] / 100.0) * rate
|
190
|
+
|
191
|
+
# Assumes the SMA trend will continue
|
192
|
+
window.times do |_|
|
193
|
+
result << current_price * percentage_change
|
194
|
+
current_price = result.last
|
195
|
+
end
|
196
|
+
|
197
|
+
prediction_test(known, result) if testing
|
198
|
+
|
199
|
+
result
|
200
|
+
end
|
87
201
|
|
88
202
|
end; end
|
data/lib/sqa/stock.rb
CHANGED
@@ -4,6 +4,7 @@ class SQA::Stock
|
|
4
4
|
attr_accessor :company_name
|
5
5
|
attr_accessor :df # The DataFrane
|
6
6
|
attr_accessor :ticker
|
7
|
+
attr_accessor :indicators
|
7
8
|
|
8
9
|
def initialize(ticker:, source: :yahoo_finance, type: :csv)
|
9
10
|
@ticker = ticker.downcase
|
@@ -11,6 +12,7 @@ class SQA::Stock
|
|
11
12
|
@klass = "SQA::DataFrame::#{source.to_s.camelize}".constantize
|
12
13
|
@type = type
|
13
14
|
@filename = "#{@ticker}.#{type}"
|
15
|
+
@indicators = OpenStruct.new
|
14
16
|
|
15
17
|
update_the_dataframe
|
16
18
|
end
|
data/lib/sqa/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dewayne VanHoozer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -280,6 +280,7 @@ files:
|
|
280
280
|
- checksums/sqa-0.0.1.gem.sha512
|
281
281
|
- checksums/sqa-0.0.10.gem.sha512
|
282
282
|
- checksums/sqa-0.0.11.gem.sha512
|
283
|
+
- checksums/sqa-0.0.12.gem.sha512
|
283
284
|
- checksums/sqa-0.0.2.gem.sha512
|
284
285
|
- checksums/sqa-0.0.3.gem.sha512
|
285
286
|
- checksums/sqa-0.0.4.gem.sha512
|
@@ -390,7 +391,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
390
391
|
- !ruby/object:Gem::Version
|
391
392
|
version: '0'
|
392
393
|
requirements: []
|
393
|
-
rubygems_version: 3.4.
|
394
|
+
rubygems_version: 3.4.19
|
394
395
|
signing_key:
|
395
396
|
specification_version: 4
|
396
397
|
summary: sqa - Stock Qualitative Analysis
|