sqa 0.0.10 → 0.0.12

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