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