sqa 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+