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.
- checksums.yaml +4 -4
- data/Rakefile +9 -0
- data/checksums/sqa-0.0.7.gem.sha512 +1 -0
- data/checksums/sqa-0.0.8.gem.sha512 +1 -0
- data/checksums/sqa-0.0.9.gem.sha512 +1 -0
- data/docs/.gitignore +1 -0
- data/docs/predict_next_value.md +15 -0
- data/lib/patches/daru/category.rb +19 -0
- data/lib/patches/daru/data_frame.rb +19 -0
- data/lib/patches/daru/plotting/svg-graph/category.rb +55 -0
- data/lib/patches/daru/plotting/svg-graph/dataframe.rb +105 -0
- data/lib/patches/daru/plotting/svg-graph/vector.rb +102 -0
- data/lib/patches/daru/plotting/svg-graph.rb +7 -0
- data/lib/patches/daru/vector.rb +19 -0
- data/lib/patches/daru.rb +19 -0
- data/lib/sqa/analysis.rb +306 -0
- data/lib/sqa/cli.rb +145 -296
- data/lib/sqa/config.rb +179 -0
- data/lib/sqa/constants.rb +23 -0
- data/lib/sqa/data_frame.rb +1 -1
- data/lib/sqa/indicator/predict_next_value.rb +63 -0
- data/lib/sqa/strategy.rb +8 -0
- data/lib/sqa/version.rb +9 -2
- data/lib/sqa/web.rb +159 -0
- data/lib/sqa.rb +49 -49
- metadata +108 -6
data/lib/sqa/analysis.rb
ADDED
@@ -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
|
+
|