sqa 0.0.22 → 0.0.31
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/.goose/memory/development.txt +3 -0
- data/.semver +6 -0
- data/ARCHITECTURE.md +648 -0
- data/CHANGELOG.md +86 -0
- data/CLAUDE.md +653 -0
- data/COMMITS.md +196 -0
- data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
- data/NEXT-STEPS.md +154 -0
- data/README.md +833 -213
- data/TASKS.md +358 -0
- data/TEST_RESULTS.md +140 -0
- data/TODO.md +42 -0
- data/_notes.txt +25 -0
- data/bin/sqa-console +11 -0
- data/checksums/sqa-0.0.23.gem.sha512 +1 -0
- data/checksums/sqa-0.0.24.gem.sha512 +1 -0
- data/data/talk_talk.json +103284 -0
- data/develop_summary.md +313 -0
- data/docs/advanced/backtesting.md +206 -0
- data/docs/advanced/ensemble.md +68 -0
- data/docs/advanced/fpop.md +153 -0
- data/docs/advanced/index.md +112 -0
- data/docs/advanced/multi-timeframe.md +67 -0
- data/docs/advanced/pattern-matcher.md +75 -0
- data/docs/advanced/portfolio-optimizer.md +79 -0
- data/docs/advanced/portfolio.md +166 -0
- data/docs/advanced/risk-management.md +210 -0
- data/docs/advanced/strategy-generator.md +158 -0
- data/docs/advanced/streaming.md +209 -0
- data/docs/ai_and_ml.md +80 -0
- data/docs/api/dataframe.md +1115 -0
- data/docs/api/index.md +126 -0
- data/docs/assets/css/custom.css +88 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/concepts/index.md +68 -0
- data/docs/contributing/index.md +60 -0
- data/docs/data-sources/index.md +66 -0
- data/docs/data_frame.md +317 -97
- data/docs/factors_that_impact_price.md +26 -0
- data/docs/finviz.md +11 -0
- data/docs/fx_pro_bit.md +25 -0
- data/docs/genetic_programming.md +104 -0
- data/docs/getting-started/index.md +123 -0
- data/docs/getting-started/installation.md +229 -0
- data/docs/getting-started/quick-start.md +244 -0
- data/docs/i_gotta_an_idea.md +22 -0
- data/docs/index.md +163 -0
- data/docs/indicators/index.md +97 -0
- data/docs/indicators.md +110 -24
- data/docs/options.md +8 -0
- data/docs/strategies/bollinger-bands.md +146 -0
- data/docs/strategies/consensus.md +64 -0
- data/docs/strategies/custom.md +310 -0
- data/docs/strategies/ema.md +53 -0
- data/docs/strategies/index.md +92 -0
- data/docs/strategies/kbs.md +164 -0
- data/docs/strategies/macd.md +96 -0
- data/docs/strategies/market-profile.md +54 -0
- data/docs/strategies/mean-reversion.md +58 -0
- data/docs/strategies/rsi.md +95 -0
- data/docs/strategies/sma.md +55 -0
- data/docs/strategies/stochastic.md +63 -0
- data/docs/strategies/volume-breakout.md +54 -0
- data/docs/ta_lib.md +160 -0
- data/docs/tags.md +7 -0
- data/docs/true_strength_index.md +46 -0
- data/docs/weighted_moving_average.md +48 -0
- data/examples/README.md +354 -0
- data/examples/advanced_features_example.rb +350 -0
- data/examples/fpop_analysis_example.rb +191 -0
- data/examples/genetic_programming_example.rb +148 -0
- data/examples/kbs_strategy_example.rb +208 -0
- data/examples/pattern_context_example.rb +300 -0
- data/examples/rails_app/Gemfile +34 -0
- data/examples/rails_app/README.md +416 -0
- data/examples/rails_app/app/assets/javascripts/application.js +107 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
- data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
- data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
- data/examples/rails_app/app/controllers/application_controller.rb +22 -0
- data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
- data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
- data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
- data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
- data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
- data/examples/rails_app/app/views/errors/show.html.erb +17 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
- data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
- data/examples/rails_app/bin/rails +6 -0
- data/examples/rails_app/config/application.rb +45 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +18 -0
- data/examples/rails_app/config/environment.rb +11 -0
- data/examples/rails_app/config/routes.rb +26 -0
- data/examples/rails_app/config.ru +8 -0
- data/examples/realtime_stream_example.rb +274 -0
- data/examples/sinatra_app/Gemfile +22 -0
- data/examples/sinatra_app/QUICKSTART.md +159 -0
- data/examples/sinatra_app/README.md +461 -0
- data/examples/sinatra_app/app.rb +344 -0
- data/examples/sinatra_app/config.ru +5 -0
- data/examples/sinatra_app/public/css/style.css +659 -0
- data/examples/sinatra_app/public/js/app.js +107 -0
- data/examples/sinatra_app/views/analyze.erb +306 -0
- data/examples/sinatra_app/views/backtest.erb +325 -0
- data/examples/sinatra_app/views/dashboard.erb +419 -0
- data/examples/sinatra_app/views/error.erb +58 -0
- data/examples/sinatra_app/views/index.erb +118 -0
- data/examples/sinatra_app/views/layout.erb +61 -0
- data/examples/sinatra_app/views/portfolio.erb +43 -0
- data/examples/strategy_generator_example.rb +346 -0
- data/hsa_portfolio.csv +11 -0
- data/justfile +0 -0
- data/lib/api/alpha_vantage_api.rb +462 -0
- data/lib/sqa/backtest.rb +329 -0
- data/lib/sqa/config.rb +22 -9
- data/lib/sqa/data_frame/alpha_vantage.rb +43 -65
- data/lib/sqa/data_frame/data.rb +92 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +34 -41
- data/lib/sqa/data_frame.rb +148 -243
- data/lib/sqa/ensemble.rb +359 -0
- data/lib/sqa/fpop.rb +199 -0
- data/lib/sqa/gp.rb +259 -0
- data/lib/sqa/indicator.rb +5 -8
- data/lib/sqa/init.rb +16 -9
- data/lib/sqa/market_regime.rb +240 -0
- data/lib/sqa/multi_timeframe.rb +379 -0
- data/lib/sqa/pattern_matcher.rb +497 -0
- data/lib/sqa/plugin_manager.rb +20 -0
- data/lib/sqa/portfolio.rb +260 -6
- data/lib/sqa/portfolio_optimizer.rb +377 -0
- data/lib/sqa/risk_manager.rb +442 -0
- data/lib/sqa/seasonal_analyzer.rb +209 -0
- data/lib/sqa/sector_analyzer.rb +300 -0
- data/lib/sqa/stock.rb +67 -96
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- data/lib/sqa/strategy/common.rb +0 -2
- data/lib/sqa/strategy/consensus.rb +5 -2
- data/lib/sqa/strategy/kbs_strategy.rb +470 -0
- data/lib/sqa/strategy/macd.rb +46 -0
- data/lib/sqa/strategy/mp.rb +1 -1
- data/lib/sqa/strategy/stochastic.rb +60 -0
- data/lib/sqa/strategy/volume_breakout.rb +57 -0
- data/lib/sqa/strategy.rb +5 -0
- data/lib/sqa/strategy_generator.rb +947 -0
- data/lib/sqa/stream.rb +361 -0
- data/lib/sqa/version.rb +1 -7
- data/lib/sqa.rb +41 -14
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +279 -48
- data/bin/sqa +0 -6
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/analysis.rb +0 -306
- data/lib/sqa/cli.rb +0 -173
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -43
- data/lib/sqa/indicator/bollinger_bands.rb +0 -28
- data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
- data/lib/sqa/indicator/donchian_channel.rb +0 -29
- data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
- data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
- data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
- data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
- data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
- data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
- data/lib/sqa/indicator/market_profile.rb +0 -32
- data/lib/sqa/indicator/mean_reversion.rb +0 -37
- data/lib/sqa/indicator/momentum.rb +0 -28
- data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
- data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
- data/lib/sqa/indicator/predict_next_value.rb +0 -202
- data/lib/sqa/indicator/relative_strength_index.rb +0 -47
- data/lib/sqa/indicator/simple_moving_average.rb +0 -24
- data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
- data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
- data/lib/sqa/indicator/true_range.rb +0 -39
- data/lib/sqa/trade.rb +0 -26
- data/lib/sqa/web.rb +0 -159
data/lib/sqa/analysis.rb
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
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
|
-
|
data/lib/sqa/cli.rb
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# lib/sqa/cli.rb
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require_relative '../sqa'
|
|
5
|
-
|
|
6
|
-
# SMELL: Architectyre has become confused between CLI and Command
|
|
7
|
-
|
|
8
|
-
# TODO: Fix the mess between CLI and Command
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
module SQA
|
|
12
|
-
class CLI
|
|
13
|
-
include TTY::Option
|
|
14
|
-
|
|
15
|
-
header "Stock Quantitative Analysis (SQA)"
|
|
16
|
-
footer "WARNING: This is a toy, a play thing, not intended for serious use."
|
|
17
|
-
|
|
18
|
-
program "sqa"
|
|
19
|
-
desc "A collection of things"
|
|
20
|
-
|
|
21
|
-
example "sqa -c ~/.sqa.yml -p portfolio.csv -t trades.csv --data-dir ~/sqa_data"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
option :config_file do
|
|
25
|
-
short "-c string"
|
|
26
|
-
long "--config string"
|
|
27
|
-
desc "Path to the config file"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
option :log_level do
|
|
31
|
-
short "-l string"
|
|
32
|
-
long "--log_level string"
|
|
33
|
-
# default SQA.config.log_level
|
|
34
|
-
desc "Set the log level (debug, info, warn, error, fatal)"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
option :portfolio do
|
|
38
|
-
short "-p string"
|
|
39
|
-
long "--portfolio string"
|
|
40
|
-
# default SQA.config.portfolio_filename
|
|
41
|
-
desc "Set the filename of the portfolio"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
option :trades do
|
|
46
|
-
short "-t string"
|
|
47
|
-
long "--trades string"
|
|
48
|
-
# default SQA.config.trades_filename
|
|
49
|
-
desc "Set the filename into which trades are stored"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
option :data_dir do
|
|
54
|
-
long "--data-dir string"
|
|
55
|
-
# default SQA.config.data_dir
|
|
56
|
-
desc "Set the directory for the SQA data"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
option :dump_config do
|
|
60
|
-
long "--dump-config path_to_file"
|
|
61
|
-
desc "Dump the current configuration"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
flag :help do
|
|
65
|
-
short "-h"
|
|
66
|
-
long "--help"
|
|
67
|
-
desc "Print usage"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
flag :version do
|
|
71
|
-
long "--version"
|
|
72
|
-
desc "Print version"
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
flag :debug do
|
|
76
|
-
short "-d"
|
|
77
|
-
long "--debug"
|
|
78
|
-
# default SQA.config.debug
|
|
79
|
-
desc "Turn on debugging output"
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
flag :verbose do
|
|
83
|
-
short "-v"
|
|
84
|
-
long "--verbose"
|
|
85
|
-
# default SQA.config.debug
|
|
86
|
-
desc "Print verbosely"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
class << self
|
|
90
|
-
@@subclasses = []
|
|
91
|
-
@@commands_available = []
|
|
92
|
-
|
|
93
|
-
def names
|
|
94
|
-
'['+ @@commands_available.join('|')+']'
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def inherited(subclass)
|
|
98
|
-
super
|
|
99
|
-
@@subclasses << subclass
|
|
100
|
-
@@commands_available << subclass.command.join
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def command_descriptions
|
|
104
|
-
help_block = "Optional Command Available:"
|
|
105
|
-
|
|
106
|
-
@@commands_available.size.times do |x|
|
|
107
|
-
klass = @@subclasses[x]
|
|
108
|
-
help_block << "\n " + @@commands_available[x] + " - "
|
|
109
|
-
help_block << klass.desc.join
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
help_block
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
##################################################
|
|
117
|
-
def run(argv = ARGV)
|
|
118
|
-
cli = new
|
|
119
|
-
parser = cli.parse(argv)
|
|
120
|
-
params = parser.params
|
|
121
|
-
|
|
122
|
-
if params[:help]
|
|
123
|
-
print parser.help
|
|
124
|
-
exit(0)
|
|
125
|
-
|
|
126
|
-
elsif params.errors.any?
|
|
127
|
-
puts params.errors.summary
|
|
128
|
-
exit(1)
|
|
129
|
-
|
|
130
|
-
elsif params[:version]
|
|
131
|
-
puts SQA.version
|
|
132
|
-
exit(0)
|
|
133
|
-
|
|
134
|
-
elsif params[:dump_config]
|
|
135
|
-
SQA.config.config_file = params[:dump_config]
|
|
136
|
-
`touch #{SQA.config.config_file}`
|
|
137
|
-
SQA.config.dump_file
|
|
138
|
-
exit(0)
|
|
139
|
-
|
|
140
|
-
elsif params[:config_file]
|
|
141
|
-
# Override the defaults <- envars <- config file content
|
|
142
|
-
params[:config_file] = SQA.homify params[:config_file]
|
|
143
|
-
SQA.config.config_file = params[:config_file]
|
|
144
|
-
SQA.config.from_file
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Override the defaults <- envars <- config file <- cli parameters
|
|
148
|
-
SQA.config.merge!(remove_temps params.to_h)
|
|
149
|
-
|
|
150
|
-
if SQA.debug? || SQA.verbose?
|
|
151
|
-
debug_me("config after CLI parameters"){[
|
|
152
|
-
"SQA.config"
|
|
153
|
-
]}
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def remove_temps(a_hash)
|
|
158
|
-
temps = %i[ help version dump ]
|
|
159
|
-
# debug_me{[ :a_hash ]}
|
|
160
|
-
a_hash.reject{|k, _| temps.include? k}
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
require_relative 'analysis'
|
|
167
|
-
require_relative 'web'
|
|
168
|
-
|
|
169
|
-
# First Load TTY-Option's command content with all available commands
|
|
170
|
-
# then these have access to the entire ObjectSpace ...
|
|
171
|
-
SQA::CLI.command SQA::CLI.names
|
|
172
|
-
SQA::CLI.example SQA::CLI.command_descriptions
|
|
173
|
-
|
data/lib/sqa/constants.rb
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# lib/sqa/constants.rb
|
|
2
|
-
|
|
3
|
-
module SQA
|
|
4
|
-
module Constants
|
|
5
|
-
Signal = {
|
|
6
|
-
hold: 0,
|
|
7
|
-
buy: 1,
|
|
8
|
-
sell: 2
|
|
9
|
-
}.freeze
|
|
10
|
-
|
|
11
|
-
Trend = {
|
|
12
|
-
up: 0,
|
|
13
|
-
down: 1
|
|
14
|
-
}.freeze
|
|
15
|
-
|
|
16
|
-
Swing = {
|
|
17
|
-
valley: 0,
|
|
18
|
-
peak: 1,
|
|
19
|
-
}.freeze
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
include Constants
|
|
23
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/average_true_range.rb
|
|
2
|
-
|
|
3
|
-
# See Also: true_range
|
|
4
|
-
|
|
5
|
-
class SQA::Indicator; class << self
|
|
6
|
-
|
|
7
|
-
def average_true_range(
|
|
8
|
-
high_prices, # Array of the day's high price
|
|
9
|
-
low_prices, # Array of the day's low price
|
|
10
|
-
close_prices, # Array of the day's closing price
|
|
11
|
-
period # Integer the number of days to consider
|
|
12
|
-
)
|
|
13
|
-
true_ranges = true_range(high_prices, low_prices, close_prices)
|
|
14
|
-
atr_values = []
|
|
15
|
-
|
|
16
|
-
# debug_me{[ :period, :true_ranges ]}
|
|
17
|
-
|
|
18
|
-
window_span = period - 1
|
|
19
|
-
|
|
20
|
-
true_ranges.size.times do |inx|
|
|
21
|
-
start_inx = inx - window_span
|
|
22
|
-
end_inx = start_inx + window_span
|
|
23
|
-
|
|
24
|
-
start_inx = 0 if start_inx < 0
|
|
25
|
-
|
|
26
|
-
window = true_ranges[start_inx..end_inx]
|
|
27
|
-
|
|
28
|
-
# debug_me{[
|
|
29
|
-
# :inx,
|
|
30
|
-
# :start_inx,
|
|
31
|
-
# :end_inx,
|
|
32
|
-
# :window,
|
|
33
|
-
# "window.mean"
|
|
34
|
-
# ]}
|
|
35
|
-
|
|
36
|
-
atr_values << window.mean
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
atr_values # Array
|
|
40
|
-
end
|
|
41
|
-
alias_method :atr, :average_true_range
|
|
42
|
-
|
|
43
|
-
end; end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/bollinger_bands.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def bollinger_bands(
|
|
6
|
-
prices, # Array of prices
|
|
7
|
-
period, # Integer number of entries to consider
|
|
8
|
-
num_std_devs=2 # Integer number of standard deviations
|
|
9
|
-
)
|
|
10
|
-
moving_averages = simple_moving_average(prices, period)
|
|
11
|
-
standard_deviations = []
|
|
12
|
-
|
|
13
|
-
prices.each_cons(period) do |window|
|
|
14
|
-
standard_deviation = Math.sqrt(window.map { |price| (price - moving_averages.last) ** 2 }.sum / period)
|
|
15
|
-
standard_deviations << standard_deviation
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
upper_band = moving_averages.last + (num_std_devs * standard_deviations.last)
|
|
19
|
-
lower_band = moving_averages.last - (num_std_devs * standard_deviations.last)
|
|
20
|
-
|
|
21
|
-
{
|
|
22
|
-
upper_band: upper_band, # Array
|
|
23
|
-
lower_band: lower_band # Array
|
|
24
|
-
}
|
|
25
|
-
end
|
|
26
|
-
alias_method :bb, :bollinger_bands
|
|
27
|
-
|
|
28
|
-
end; end
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/candlestick_pattern_recognizer.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def candlestick_pattern_recognizer(
|
|
6
|
-
open_prices, # Array day's ppen price
|
|
7
|
-
high_prices, # Array day's high price
|
|
8
|
-
low_prices, # Array day's low price
|
|
9
|
-
close_prices # Array day's closing price
|
|
10
|
-
)
|
|
11
|
-
patterns = []
|
|
12
|
-
|
|
13
|
-
close_prices.each_with_index do |close, index|
|
|
14
|
-
if index >= 2
|
|
15
|
-
previous_close = close_prices[index - 1]
|
|
16
|
-
previous_open = open_prices[index - 1]
|
|
17
|
-
previous_high = high_prices[index - 1]
|
|
18
|
-
previous_low = low_prices[index - 1]
|
|
19
|
-
|
|
20
|
-
second_previous_close = close_prices[index - 2]
|
|
21
|
-
second_previous_open = open_prices[index - 2]
|
|
22
|
-
second_previous_high = high_prices[index - 2]
|
|
23
|
-
second_previous_low = low_prices[index - 2]
|
|
24
|
-
|
|
25
|
-
if close > previous_close &&
|
|
26
|
-
previous_close < previous_open &&
|
|
27
|
-
close < previous_open &&
|
|
28
|
-
close > previous_low &&
|
|
29
|
-
close > second_previous_close
|
|
30
|
-
patterns << :bullish_engulfing
|
|
31
|
-
|
|
32
|
-
elsif close < previous_close &&
|
|
33
|
-
previous_close > previous_open &&
|
|
34
|
-
close > previous_open &&
|
|
35
|
-
close < previous_high &&
|
|
36
|
-
close < second_previous_close
|
|
37
|
-
patterns << :bearish_engulfing
|
|
38
|
-
|
|
39
|
-
elsif close > previous_close &&
|
|
40
|
-
previous_close < previous_open &&
|
|
41
|
-
close < previous_open &&
|
|
42
|
-
close < previous_low &&
|
|
43
|
-
close < second_previous_close
|
|
44
|
-
patterns << :bearish_harami
|
|
45
|
-
|
|
46
|
-
elsif close < previous_close &&
|
|
47
|
-
previous_close > previous_open &&
|
|
48
|
-
close > previous_open &&
|
|
49
|
-
close > previous_high &&
|
|
50
|
-
close > second_previous_close
|
|
51
|
-
patterns << :bullish_harami
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
patterns
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
end; end
|
|
60
|
-
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/donchian_channel.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def donchian_channel(
|
|
6
|
-
prices, # Array of prices
|
|
7
|
-
period # Integer number of entries to consider
|
|
8
|
-
)
|
|
9
|
-
max = -999999999
|
|
10
|
-
min = 999999999
|
|
11
|
-
donchian_channel = []
|
|
12
|
-
|
|
13
|
-
prices.each_with_index do |value, index|
|
|
14
|
-
value = value.to_f
|
|
15
|
-
max = value if value > max
|
|
16
|
-
min = value if value < min
|
|
17
|
-
|
|
18
|
-
if index >= period - 1
|
|
19
|
-
donchian_channel << [max, min, (max + min) / 2]
|
|
20
|
-
max = -999999999
|
|
21
|
-
min = 999999999
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
donchian_channel
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
end; end
|
|
29
|
-
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/double_top_bottom_pattern.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def double_top_bottom_pattern(
|
|
6
|
-
prices # Array of prices
|
|
7
|
-
)
|
|
8
|
-
return :no_pattern if prices.length < 5
|
|
9
|
-
|
|
10
|
-
data = prices.last(5)
|
|
11
|
-
|
|
12
|
-
first_peak = data[0]
|
|
13
|
-
valley = data[1]
|
|
14
|
-
second_peak = data[2]
|
|
15
|
-
neckline = data[3]
|
|
16
|
-
confirmation_price = data[4]
|
|
17
|
-
|
|
18
|
-
if first_peak < second_peak &&
|
|
19
|
-
valley > first_peak &&
|
|
20
|
-
valley > second_peak &&
|
|
21
|
-
confirmation_price < neckline
|
|
22
|
-
:double_top
|
|
23
|
-
elsif first_peak > second_peak &&
|
|
24
|
-
valley < first_peak &&
|
|
25
|
-
valley < second_peak &&
|
|
26
|
-
confirmation_price > neckline
|
|
27
|
-
:double_bottom
|
|
28
|
-
else
|
|
29
|
-
:no_pattern
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
end; end
|
|
34
|
-
|