sqa 0.0.10 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b699ff032eee80858f9cfc3c3cba7e1ad5c0b537cd9b014085d184f4ce052451
4
- data.tar.gz: 3c7211690c31b6486718306d7e62fdf71c9466a421cfc694955a64e0d0184e66
3
+ metadata.gz: 39185fb1deccd8cc248d4ad776b724ea5ebe1a7e93fdc1d6ada4f53661a55bba
4
+ data.tar.gz: 3b394f997cdcdd67ec9eced2cd7bc08c1af2b407b8945e1a76e0383591ca3495
5
5
  SHA512:
6
- metadata.gz: f8a319f9b59ce9af8cccf6ee0cd436e7847db90e95703da13ea50cf33d03cd5c5aeb996b87503f2ddc6f7991f18441be7c49a099377a19a89b6c8bd7ceb4eb3b
7
- data.tar.gz: 9b7e5b5a16f21d256c44f8a491b6f4bc0b60cc507d9124b49b12d6da9c2e487f64b0f2388ecdc4e0d24e10eb4b4f2fd69f5ca190eb5a4f51af9aba7a1123d6ea
6
+ metadata.gz: 3d805f6db11375738db0cdad8385f7a74c26c3fba39f0a7aa88593532e1127af7c4c690844eae56f6203696743e1b655bb9d996f2e71745cefb4f04227bbe622
7
+ data.tar.gz: c52327bfbf15f25159a14af31be0881112e169dc73dbdab7d7c7ac7519208e366314e4ab6bd31d44134fbda5238dc7b56e553dcc8c7d231938b78af3ec17d137
@@ -0,0 +1 @@
1
+ 9fad1def1db23b0f3f28e116cac5032d263921d31f336dba356c5f5fb5cfe911aa73c69615710fcc22662560b96bcc2b683dc5db1044b4acb3337726d6244a45
@@ -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
- incoming = ::JSON.load(File.open(config_file).read)
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
- incoming = TomlRB.load_file(config_file)
151
- debug_me{[ :incoming ]}
155
+ TomlRB.load_file(config_file).symbolize_keys
152
156
  end
153
157
 
154
158
  def from_yaml
155
- incoming = ::YAML.load_file(config_file)
156
- debug_me{[ :incoming ]}
157
- merge! incoming
159
+ ::YAML.load_file(config_file).symbolize_keys
158
160
  end
159
161
 
160
162
 
@@ -7,14 +7,14 @@ require 'nokogiri'
7
7
  class SQA::DataFrame < Daru::DataFrame
8
8
  class YahooFinance
9
9
  CONNECTION = Faraday.new(url: 'https://finance.yahoo.com')
10
- HEADERS = %i[
11
- timestamp # 0
12
- open_price # 1
13
- high_price # 2
14
- low_price # 3
15
- close_price # 4
16
- adj_close_price # 5
17
- volume # 6
10
+ HEADERS = [
11
+ :timestamp, # 0
12
+ :open_price, # 1
13
+ :high_price, # 2
14
+ :low_price, # 3
15
+ :close_price, # 4
16
+ :adj_close_price, # 5
17
+ :volume, # 6
18
18
  ]
19
19
 
20
20
  # The Yahoo Finance Headers are being remapped so that
@@ -64,8 +64,18 @@ class SQA::DataFrame < Daru::DataFrame
64
64
  data = []
65
65
 
66
66
  rows.each do |row|
67
- cols = row.css('td').map{|c| c&.text}
67
+ cols = row.css('td').map{|c| c.children[0].text}
68
+
69
+ next unless 7 == cols.size
68
70
  next if cols[1]&.include?("Dividend")
71
+
72
+ if cols.any?(nil)
73
+ debug_me('== ERROR =='){[
74
+ :cols
75
+ ]}
76
+ next
77
+ end
78
+
69
79
  cols[0] = Date.parse(cols[0]).to_s
70
80
  cols[6] = cols[6].tr(',','').to_i
71
81
  (1..5).each {|x| cols[x] = cols[x].to_f}
@@ -35,7 +35,7 @@ class SQA::DataFrame < Daru::DataFrame
35
35
  #################################################
36
36
 
37
37
  def self.path(filename)
38
- Pathname.new SQA.config.data_dir + filename
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
- def predict_next_values(array, predictions)
9
- result = []
10
-
11
- array.each_cons(2) do |a, b|
12
- # TODO: take 3 at a time. compare 3rd
13
- # prediction. Generate an average delta
14
- # between predicted and actual. Return
15
- # the prediction as a range???
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 predictions > 0
20
- (1..predictions).each do |_|
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,38 +97,105 @@ class SQA::Indicator; class << self
25
97
  end
26
98
  end
27
99
 
28
- result.last(predictions)
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
- # Returns a forecast for future values based on the near past
34
- #
35
- # When I wrote this I was thinking of hurricane forecasting and how
36
- # the cone of probability gets larger the further into the future
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 = array.size - 1 # indexes are zero based
112
+ last_inx = prices.size - 1 # indexes are zero based
46
113
 
47
- predictions.times do |x|
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 = array[last_inx-x..]
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 << [high, midpoint, low]
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
+
153
+ result
154
+ end
155
+
156
+
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
164
+
165
+ # Loop through the prediction window size
166
+ (1..window).each do |x|
167
+
168
+ # Calculate the percentage change between the current price and its previous price
169
+ percentage_change = (current_price - prices[-x]) / prices[-x]
170
+
171
+ result << current_price + (current_price * percentage_change)
172
+ end
173
+
174
+ prediction_test(known, result) if testing
175
+
176
+ result
177
+ end
178
+
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
+
60
199
  result
61
200
  end
62
201
 
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
@@ -21,10 +23,12 @@ class SQA::Stock
21
23
  df2 = @klass.recent(@ticker)
22
24
  @df = @klass.append(df1, df2)
23
25
 
24
- unless @df.size == df1.size
26
+ if @df.nrows > df1.nrows
25
27
  @df.send("to_#{@type}", SQA::DataFrame.path(@filename))
26
28
  end
27
29
 
30
+ # Adding a ticker vector in case I want to do
31
+ # some multi-stock analysis in the same data frame.
28
32
  @df[:ticker] = @ticker
29
33
  end
30
34
 
data/lib/sqa/version.rb CHANGED
@@ -4,7 +4,7 @@ require 'sem_version'
4
4
  require 'sem_version/core_ext'
5
5
 
6
6
  module SQA
7
- VERSION = "0.0.10"
7
+ VERSION = "0.0.12"
8
8
 
9
9
  class << self
10
10
  def version
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.10
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-09 00:00:00.000000000 Z
11
+ date: 2023-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -279,6 +279,8 @@ files:
279
279
  - bin/sqa
280
280
  - checksums/sqa-0.0.1.gem.sha512
281
281
  - checksums/sqa-0.0.10.gem.sha512
282
+ - checksums/sqa-0.0.11.gem.sha512
283
+ - checksums/sqa-0.0.12.gem.sha512
282
284
  - checksums/sqa-0.0.2.gem.sha512
283
285
  - checksums/sqa-0.0.3.gem.sha512
284
286
  - checksums/sqa-0.0.4.gem.sha512
@@ -389,7 +391,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
389
391
  - !ruby/object:Gem::Version
390
392
  version: '0'
391
393
  requirements: []
392
- rubygems_version: 3.4.18
394
+ rubygems_version: 3.4.19
393
395
  signing_key:
394
396
  specification_version: 4
395
397
  summary: sqa - Stock Qualitative Analysis