sqa 0.0.7 → 0.0.9

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.
@@ -0,0 +1,306 @@
1
+ # lib/sqa/command/analysis.rb
2
+
3
+ module SQA
4
+ class Analysis < CLI
5
+ include TTY::Option
6
+
7
+ command "Analysis"
8
+
9
+ desc "Provide an Analysis of a Portfolio"
10
+
11
+
12
+ def initialize
13
+ # TODO: something
14
+ end
15
+ end
16
+ end
17
+
18
+ __END__
19
+
20
+
21
+
22
+ ###################################################
23
+ ## This is the old thing that got me started ...
24
+
25
+ #!/usr/bin/env ruby
26
+ # experiments/stocks/analysis.rb
27
+ #
28
+ # Some technical indicators from FinTech gem
29
+ #
30
+ # optional date CLI option in format YYYY-mm-dd
31
+ # if not present uses Date.today
32
+ #
33
+
34
+
35
+ INVEST = 1000.00
36
+
37
+ require 'pathname'
38
+
39
+ require_relative 'stock'
40
+ require_relative 'datastore'
41
+
42
+
43
+ STOCKS = Pathname.pwd + "stocks.txt"
44
+ TRADES = Pathname.pwd + "trades.csv"
45
+
46
+ TRADES_FILE = File.open(TRADES, 'a')
47
+
48
+ unless STOCKS.exist?
49
+ puts
50
+ puts "ERROR: The 'stocks.txt' file does not exist."
51
+ puts
52
+ exot(-1)
53
+ end
54
+
55
+ require 'debug_me'
56
+ include DebugMe
57
+
58
+ require 'csv'
59
+ require 'date'
60
+ require 'tty-table'
61
+
62
+ require 'fin_tech'
63
+ require 'previous_dow'
64
+
65
+ class NilClass
66
+ def blank?() = true
67
+ end
68
+
69
+ class String
70
+ def blank?() = strip().empty?
71
+ end
72
+
73
+ class Array
74
+ def blank?() = empty?
75
+ end
76
+
77
+
78
+ def tickers
79
+ return @tickers unless @tickers.blank?
80
+
81
+ @tickers = []
82
+
83
+ STOCKS.readlines.each do |a_line|
84
+ ticker_symbol = a_line.chomp.strip.split()&.first&.downcase
85
+ next if ticker_symbol.blank? || '#' == ticker_symbol
86
+ @tickers << ticker_symbol unless @tickers.include?(ticker_symbol)
87
+ end
88
+
89
+ @tickers.sort!
90
+ end
91
+
92
+ given_date = ARGV.first ? Date.parse(ARGV.first) : Date.today
93
+
94
+ start_date = Date.new(2019, 1, 1)
95
+ end_date = previous_dow(:friday, given_date)
96
+
97
+ ASOF = end_date.to_s.tr('-','')
98
+
99
+
100
+ #######################################################################
101
+ # download a CSV file from https://query1.finance.yahoo.com
102
+ # given a stock ticker symbol as a String
103
+ # start and end dates
104
+ #
105
+ # For ticker "aapl" the downloaded file will be named "aapl.csv"
106
+ # That filename will be renamed to "aapl_YYYYmmdd.csv" where the
107
+ # date suffix is the end_date of the historical data.
108
+ #
109
+ def download_historical_prices(ticker, start_date, end_date)
110
+ data_path = Pathname.pwd + "#{ticker}_#{ASOF}.csv"
111
+ return if data_path.exist?
112
+
113
+ mew_path = Pathname.pwd + "#{ticker}.csv"
114
+
115
+ start_timestamp = start_date.to_time.to_i
116
+ end_timestamp = end_date.to_time.to_i
117
+ ticker_upcase = ticker.upcase
118
+ filename = "#{ticker.downcase}.csv"
119
+
120
+ `curl -o #{filename} "https://query1.finance.yahoo.com/v7/finance/download/#{ticker_upcase}?period1=#{start_timestamp}&period2=#{end_timestamp}&interval=1d&events=history&includeAdjustedClose=true"`
121
+
122
+ mew_path.rename data_path
123
+ end
124
+
125
+
126
+ #######################################################################
127
+ # Read the CSV file associated with the give ticker symbol
128
+ # and the ASOF date.
129
+ #
130
+ def read_csv(ticker)
131
+ filename = "#{ticker.downcase}_#{ASOF}.csv"
132
+ data = []
133
+
134
+ CSV.foreach(filename, headers: true) do |row|
135
+ data << row.to_h
136
+ end
137
+
138
+ data
139
+ end
140
+
141
+ ##########################
142
+ # record a recommend trade
143
+
144
+ def trade(ticker, signal, shares, price)
145
+ TRADES_FILE.puts "#{ticker},#{ASOF},#{signal},#{shares},#{price}"
146
+ end
147
+
148
+ #######################################################################
149
+ ###
150
+ ## Main
151
+ #
152
+
153
+
154
+ tickers.each do |ticker|
155
+ download_historical_prices(ticker, start_date, end_date)
156
+ end
157
+
158
+ result = {}
159
+
160
+ mwfd = 14 # moving_window_forcast_days
161
+
162
+ headers = %w[ Ticker AdjClose Trend Slope M'tum RSI Analysis MACD Target Signal $]
163
+ values = []
164
+
165
+ tickers.each do |ticker|
166
+
167
+ data = read_csv ticker
168
+ prices = data.map{|r| r["Adj Close"].to_f}
169
+ volumes = data.map{|r| r["volume"].to_f}
170
+
171
+ if data.blank?
172
+ puts
173
+ puts "ERROR: cannot get data for #{ticker}"
174
+ puts
175
+ next
176
+ end
177
+
178
+
179
+ result[ticker] = {
180
+ date: data.last["Date"],
181
+ adj_close: data.last["Adj Close"].to_f
182
+ }
183
+
184
+ result[ticker][:market] = FinTech.classify_market_profile(
185
+ volumes.last(mwfd),
186
+ prices.last(mwfd),
187
+ prices.last(mwfd).first,
188
+ prices.last
189
+ )
190
+
191
+ fr = FinTech.fibonacci_retracement( prices.last(mwfd).first,
192
+ prices.last).map{|x| x.round(3)}
193
+
194
+
195
+ puts "\n#{result[ticker][:market]} .. #{ticker}\t#{fr}"
196
+ print "\t"
197
+ print FinTech.head_and_shoulders_pattern?(prices.last(mwfd))
198
+ print "\t"
199
+ print FinTech.double_top_bottom_pattern?(prices.last(mwfd))
200
+ print "\t"
201
+ mr = FinTech.mean_reversion?(prices, mwfd, 0.5)
202
+ print mr
203
+
204
+ if mr
205
+ print "\t"
206
+ print FinTech.mr_mean(prices, mwfd).round(3)
207
+ end
208
+
209
+ print "\t"
210
+ print FinTech.identify_wave_condition?(prices, 2*mwfd, 1.0)
211
+ puts
212
+
213
+ print "\t"
214
+ print FinTech.ema_analysis(prices, mwfd).except(:ema_values)
215
+
216
+ puts
217
+
218
+ row = [ ticker ]
219
+
220
+ # result[ticker][:moving_averages] = FinTech.sma(data, mwfd)
221
+ result[ticker][:trend] = FinTech.sma_trend(data, mwfd)
222
+ result[ticker][:momentum] = FinTech.momentum(prices, mwfd)
223
+ result[ticker][:rsi] = FinTech.rsi(data, mwfd)
224
+ result[ticker][:bollinger_bands] = FinTech.bollinger_bands(data, mwfd, 2)
225
+ result[ticker][:macd] = FinTech.macd(data, mwfd, 2*mwfd, mwfd/2)
226
+
227
+ price = result[ticker][:adj_close].round(3)
228
+
229
+ row << price
230
+ row << result[ticker][:trend][:trend]
231
+ row << result[ticker][:trend][:angle].round(3)
232
+ row << result[ticker][:momentum].round(3)
233
+ row << result[ticker][:rsi][:rsi].round(3)
234
+ row << result[ticker][:rsi][:meaning]
235
+ row << result[ticker][:macd].first.round(3)
236
+ row << result[ticker][:macd].last.round(3)
237
+
238
+ analysis = result[ticker][:rsi][:meaning]
239
+
240
+ signal = ""
241
+ macd_diff = result[ticker][:macd].first
242
+ target = result[ticker][:macd].last
243
+ current = result[ticker][:adj_close]
244
+
245
+ trend_down = "down" == result[ticker][:trend][:trend]
246
+
247
+ if current < target
248
+ signal = "buy" unless "Over Bought" == analysis
249
+ elsif (current > target) && trend_down
250
+ signal = "sell" unless "Over Sold" == analysis
251
+ end
252
+
253
+ if "buy" == signal
254
+ pps = target - price
255
+ shares = INVEST.to_i / price.to_i
256
+ upside = (shares * pps).round(2)
257
+ trade(ticker, signal, shares, price)
258
+ elsif "sell" == signal
259
+ pps = target - price
260
+ shares = INVEST.to_i / price.to_i
261
+ upside = (shares * pps).round(2)
262
+ trade(ticker, signal, shares, price)
263
+ else
264
+ upside = ""
265
+ end
266
+
267
+ row << signal
268
+ row << upside
269
+
270
+ values << row
271
+ end
272
+
273
+ # debug_me{[
274
+ # :result
275
+ # ]}
276
+
277
+
278
+ the_table = TTY::Table.new(headers, values)
279
+
280
+ puts
281
+ puts "Analysis as of Friday Close: #{end_date}"
282
+
283
+ puts the_table.render(
284
+ :unicode,
285
+ {
286
+ padding: [0, 0, 0, 0],
287
+ alignments: [
288
+ :left, # ticker
289
+ :right, # adj close
290
+ :center, # trend
291
+ :right, # slope
292
+ :right, # momentum
293
+ :right, # rsi
294
+ :center, # meaning / analysis
295
+ :right, # macd
296
+ :right, # target
297
+ :center, # signal
298
+ :right # upside
299
+ ],
300
+ }
301
+ )
302
+ puts
303
+
304
+ TRADES_FILE.close
305
+
306
+