sqa 0.0.24 → 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 +82 -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 +812 -262
- 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/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/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/data_frame/alpha_vantage.rb +43 -65
- data/lib/sqa/data_frame/data.rb +92 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
- 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 +15 -8
- 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/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 -125
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- 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 +23 -16
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +261 -51
- data/bin/sqa +0 -6
- data/lib/patches/dry-cli.rb +0 -228
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/cli.rb +0 -62
- data/lib/sqa/commands/analysis.rb +0 -309
- data/lib/sqa/commands/base.rb +0 -139
- data/lib/sqa/commands/web.rb +0 -199
- data/lib/sqa/commands.rb +0 -22
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -33
- 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/activity.rb
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# lib/sqa/activity.rb
|
|
2
|
-
|
|
3
|
-
# Historical daily stock activity
|
|
4
|
-
# primary id is [ticker, date]
|
|
5
|
-
|
|
6
|
-
class SQA::Activity < ActiveRecord::Base
|
|
7
|
-
# belongs_to :stock using ticker as the foreign key
|
|
8
|
-
# need a unique constraint on [ticker, date]
|
|
9
|
-
# should date be saved as a Date object or string?
|
|
10
|
-
end
|
data/lib/sqa/cli.rb
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# lib/sqa/cli.rb
|
|
2
|
-
|
|
3
|
-
require 'dry/cli'
|
|
4
|
-
|
|
5
|
-
require_relative '../sqa'
|
|
6
|
-
require_relative 'commands'
|
|
7
|
-
|
|
8
|
-
module SQA::CLI
|
|
9
|
-
class << self
|
|
10
|
-
def run!
|
|
11
|
-
Dry::CLI.new(SQA::Commands).call
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
__END__
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# header "Stock Quantitative Analysis (SQA)"
|
|
25
|
-
# footer "WARNING: This is a toy, a play thing, not intended for serious use."
|
|
26
|
-
|
|
27
|
-
# program "sqa"
|
|
28
|
-
# desc "A collection of things"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class << self
|
|
34
|
-
|
|
35
|
-
##################################################
|
|
36
|
-
def run(argv = ARGV)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
elsif params[:dump_config]
|
|
40
|
-
SQA.config.config_file = params[:dump_config]
|
|
41
|
-
`touch #{SQA.config.config_file}`
|
|
42
|
-
SQA.config.dump_file
|
|
43
|
-
exit(0)
|
|
44
|
-
|
|
45
|
-
elsif params[:config_file]
|
|
46
|
-
# Override the defaults <- envars <- config file content
|
|
47
|
-
params[:config_file] = SQA.homify params[:config_file]
|
|
48
|
-
SQA.config.config_file = params[:config_file]
|
|
49
|
-
SQA.config.from_file
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# Override the defaults <- envars <- config file <- cli parameters
|
|
55
|
-
SQA.config.merge!(remove_temps params.to_h)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
# sqa/lib/sqa/commands/analysis.rb
|
|
2
|
-
|
|
3
|
-
class Commands::Analysis < Commands::Base
|
|
4
|
-
VERSION = "0.0.1-analysis"
|
|
5
|
-
|
|
6
|
-
Commands.register "analysis", self
|
|
7
|
-
|
|
8
|
-
desc "Provide an Analysis of a Portfolio"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def initialize
|
|
12
|
-
# TODO: something
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def call(params)
|
|
16
|
-
config = super
|
|
17
|
-
|
|
18
|
-
puts <<~EOS
|
|
19
|
-
##################################
|
|
20
|
-
## Running the Analysis Command ##
|
|
21
|
-
##################################
|
|
22
|
-
EOS
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
__END__
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
###################################################
|
|
31
|
-
## This is the old thing that got me started ...
|
|
32
|
-
|
|
33
|
-
#!/usr/bin/env ruby
|
|
34
|
-
# experiments/stocks/analysis.rb
|
|
35
|
-
#
|
|
36
|
-
# Some technical indicators from FinTech gem
|
|
37
|
-
#
|
|
38
|
-
# optional date CLI option in format YYYY-mm-dd
|
|
39
|
-
# if not present uses Date.today
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
INVEST = 1000.00
|
|
44
|
-
|
|
45
|
-
require 'pathname'
|
|
46
|
-
|
|
47
|
-
require_relative 'stock'
|
|
48
|
-
require_relative 'datastore'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
STOCKS = Pathname.pwd + "stocks.txt"
|
|
52
|
-
TRADES = Pathname.pwd + "trades.csv"
|
|
53
|
-
|
|
54
|
-
TRADES_FILE = File.open(TRADES, 'a')
|
|
55
|
-
|
|
56
|
-
unless STOCKS.exist?
|
|
57
|
-
puts
|
|
58
|
-
puts "ERROR: The 'stocks.txt' file does not exist."
|
|
59
|
-
puts
|
|
60
|
-
exot(-1)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
require 'debug_me'
|
|
64
|
-
include DebugMe
|
|
65
|
-
|
|
66
|
-
require 'csv'
|
|
67
|
-
require 'date'
|
|
68
|
-
require 'tty-table'
|
|
69
|
-
|
|
70
|
-
require 'fin_tech'
|
|
71
|
-
require 'previous_dow'
|
|
72
|
-
|
|
73
|
-
class NilClass
|
|
74
|
-
def blank?() = true
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
class String
|
|
78
|
-
def blank?() = strip().empty?
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
class Array
|
|
82
|
-
def blank?() = empty?
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def tickers
|
|
87
|
-
return @tickers unless @tickers.blank?
|
|
88
|
-
|
|
89
|
-
@tickers = []
|
|
90
|
-
|
|
91
|
-
STOCKS.readlines.each do |a_line|
|
|
92
|
-
ticker_symbol = a_line.chomp.strip.split()&.first&.downcase
|
|
93
|
-
next if ticker_symbol.blank? || '#' == ticker_symbol
|
|
94
|
-
@tickers << ticker_symbol unless @tickers.include?(ticker_symbol)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
@tickers.sort!
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
given_date = ARGV.first ? Date.parse(ARGV.first) : Date.today
|
|
101
|
-
|
|
102
|
-
start_date = Date.new(2019, 1, 1)
|
|
103
|
-
end_date = previous_dow(:friday, given_date)
|
|
104
|
-
|
|
105
|
-
ASOF = end_date.to_s.tr('-','')
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
#######################################################################
|
|
109
|
-
# download a CSV file from https://query1.finance.yahoo.com
|
|
110
|
-
# given a stock ticker symbol as a String
|
|
111
|
-
# start and end dates
|
|
112
|
-
#
|
|
113
|
-
# For ticker "aapl" the downloaded file will be named "aapl.csv"
|
|
114
|
-
# That filename will be renamed to "aapl_YYYYmmdd.csv" where the
|
|
115
|
-
# date suffix is the end_date of the historical data.
|
|
116
|
-
#
|
|
117
|
-
def download_historical_prices(ticker, start_date, end_date)
|
|
118
|
-
data_path = Pathname.pwd + "#{ticker}_#{ASOF}.csv"
|
|
119
|
-
return if data_path.exist?
|
|
120
|
-
|
|
121
|
-
mew_path = Pathname.pwd + "#{ticker}.csv"
|
|
122
|
-
|
|
123
|
-
start_timestamp = start_date.to_time.to_i
|
|
124
|
-
end_timestamp = end_date.to_time.to_i
|
|
125
|
-
ticker_upcase = ticker.upcase
|
|
126
|
-
filename = "#{ticker.downcase}.csv"
|
|
127
|
-
|
|
128
|
-
`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"`
|
|
129
|
-
|
|
130
|
-
mew_path.rename data_path
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#######################################################################
|
|
135
|
-
# Read the CSV file associated with the give ticker symbol
|
|
136
|
-
# and the ASOF date.
|
|
137
|
-
#
|
|
138
|
-
def read_csv(ticker)
|
|
139
|
-
filename = "#{ticker.downcase}_#{ASOF}.csv"
|
|
140
|
-
data = []
|
|
141
|
-
|
|
142
|
-
CSV.foreach(filename, headers: true) do |row|
|
|
143
|
-
data << row.to_h
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
data
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
##########################
|
|
150
|
-
# record a recommend trade
|
|
151
|
-
|
|
152
|
-
def trade(ticker, signal, shares, price)
|
|
153
|
-
TRADES_FILE.puts "#{ticker},#{ASOF},#{signal},#{shares},#{price}"
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
#######################################################################
|
|
157
|
-
###
|
|
158
|
-
## Main
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
tickers.each do |ticker|
|
|
163
|
-
download_historical_prices(ticker, start_date, end_date)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
result = {}
|
|
167
|
-
|
|
168
|
-
mwfd = 14 # moving_window_forcast_days
|
|
169
|
-
|
|
170
|
-
headers = %w[ Ticker AdjClose Trend Slope M'tum RSI Analysis MACD Target Signal $]
|
|
171
|
-
values = []
|
|
172
|
-
|
|
173
|
-
tickers.each do |ticker|
|
|
174
|
-
|
|
175
|
-
data = read_csv ticker
|
|
176
|
-
prices = data.map{|r| r["Adj Close"].to_f}
|
|
177
|
-
volumes = data.map{|r| r["volume"].to_f}
|
|
178
|
-
|
|
179
|
-
if data.blank?
|
|
180
|
-
puts
|
|
181
|
-
puts "ERROR: cannot get data for #{ticker}"
|
|
182
|
-
puts
|
|
183
|
-
next
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
result[ticker] = {
|
|
188
|
-
date: data.last["Date"],
|
|
189
|
-
adj_close: data.last["Adj Close"].to_f
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
result[ticker][:market] = FinTech.classify_market_profile(
|
|
193
|
-
volumes.last(mwfd),
|
|
194
|
-
prices.last(mwfd),
|
|
195
|
-
prices.last(mwfd).first,
|
|
196
|
-
prices.last
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
fr = FinTech.fibonacci_retracement( prices.last(mwfd).first,
|
|
200
|
-
prices.last).map{|x| x.round(3)}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
puts "\n#{result[ticker][:market]} .. #{ticker}\t#{fr}"
|
|
204
|
-
print "\t"
|
|
205
|
-
print FinTech.head_and_shoulders_pattern?(prices.last(mwfd))
|
|
206
|
-
print "\t"
|
|
207
|
-
print FinTech.double_top_bottom_pattern?(prices.last(mwfd))
|
|
208
|
-
print "\t"
|
|
209
|
-
mr = FinTech.mean_reversion?(prices, mwfd, 0.5)
|
|
210
|
-
print mr
|
|
211
|
-
|
|
212
|
-
if mr
|
|
213
|
-
print "\t"
|
|
214
|
-
print FinTech.mr_mean(prices, mwfd).round(3)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
print "\t"
|
|
218
|
-
print FinTech.identify_wave_condition?(prices, 2*mwfd, 1.0)
|
|
219
|
-
puts
|
|
220
|
-
|
|
221
|
-
print "\t"
|
|
222
|
-
print FinTech.ema_analysis(prices, mwfd).except(:ema_values)
|
|
223
|
-
|
|
224
|
-
puts
|
|
225
|
-
|
|
226
|
-
row = [ ticker ]
|
|
227
|
-
|
|
228
|
-
# result[ticker][:moving_averages] = FinTech.sma(data, mwfd)
|
|
229
|
-
result[ticker][:trend] = FinTech.sma_trend(data, mwfd)
|
|
230
|
-
result[ticker][:momentum] = FinTech.momentum(prices, mwfd)
|
|
231
|
-
result[ticker][:rsi] = FinTech.rsi(data, mwfd)
|
|
232
|
-
result[ticker][:bollinger_bands] = FinTech.bollinger_bands(data, mwfd, 2)
|
|
233
|
-
result[ticker][:macd] = FinTech.macd(data, mwfd, 2*mwfd, mwfd/2)
|
|
234
|
-
|
|
235
|
-
price = result[ticker][:adj_close].round(3)
|
|
236
|
-
|
|
237
|
-
row << price
|
|
238
|
-
row << result[ticker][:trend][:trend]
|
|
239
|
-
row << result[ticker][:trend][:angle].round(3)
|
|
240
|
-
row << result[ticker][:momentum].round(3)
|
|
241
|
-
row << result[ticker][:rsi][:rsi].round(3)
|
|
242
|
-
row << result[ticker][:rsi][:meaning]
|
|
243
|
-
row << result[ticker][:macd].first.round(3)
|
|
244
|
-
row << result[ticker][:macd].last.round(3)
|
|
245
|
-
|
|
246
|
-
analysis = result[ticker][:rsi][:meaning]
|
|
247
|
-
|
|
248
|
-
signal = ""
|
|
249
|
-
macd_diff = result[ticker][:macd].first
|
|
250
|
-
target = result[ticker][:macd].last
|
|
251
|
-
current = result[ticker][:adj_close]
|
|
252
|
-
|
|
253
|
-
trend_down = "down" == result[ticker][:trend][:trend]
|
|
254
|
-
|
|
255
|
-
if current < target
|
|
256
|
-
signal = "buy" unless "Over Bought" == analysis
|
|
257
|
-
elsif (current > target) && trend_down
|
|
258
|
-
signal = "sell" unless "Over Sold" == analysis
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
if "buy" == signal
|
|
262
|
-
pps = target - price
|
|
263
|
-
shares = INVEST.to_i / price.to_i
|
|
264
|
-
upside = (shares * pps).round(2)
|
|
265
|
-
trade(ticker, signal, shares, price)
|
|
266
|
-
elsif "sell" == signal
|
|
267
|
-
pps = target - price
|
|
268
|
-
shares = INVEST.to_i / price.to_i
|
|
269
|
-
upside = (shares * pps).round(2)
|
|
270
|
-
trade(ticker, signal, shares, price)
|
|
271
|
-
else
|
|
272
|
-
upside = ""
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
row << signal
|
|
276
|
-
row << upside
|
|
277
|
-
|
|
278
|
-
values << row
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
the_table = TTY::Table.new(headers, values)
|
|
282
|
-
|
|
283
|
-
puts
|
|
284
|
-
puts "Analysis as of Friday Close: #{end_date}"
|
|
285
|
-
|
|
286
|
-
puts the_table.render(
|
|
287
|
-
:unicode,
|
|
288
|
-
{
|
|
289
|
-
padding: [0, 0, 0, 0],
|
|
290
|
-
alignments: [
|
|
291
|
-
:left, # ticker
|
|
292
|
-
:right, # adj close
|
|
293
|
-
:center, # trend
|
|
294
|
-
:right, # slope
|
|
295
|
-
:right, # momentum
|
|
296
|
-
:right, # rsi
|
|
297
|
-
:center, # meaning / analysis
|
|
298
|
-
:right, # macd
|
|
299
|
-
:right, # target
|
|
300
|
-
:center, # signal
|
|
301
|
-
:right # upside
|
|
302
|
-
],
|
|
303
|
-
}
|
|
304
|
-
)
|
|
305
|
-
puts
|
|
306
|
-
|
|
307
|
-
TRADES_FILE.close
|
|
308
|
-
|
|
309
|
-
|
data/lib/sqa/commands/base.rb
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# .../sqa/cli/commands/base.rb
|
|
2
|
-
|
|
3
|
-
# SQA.config will be built with its defaults
|
|
4
|
-
# and envar over-rides BEFORE a command is
|
|
5
|
-
# process. This means that options do not
|
|
6
|
-
# need to have a "default" value.
|
|
7
|
-
|
|
8
|
-
# Establish a Base command class that has global options
|
|
9
|
-
# available to all commands.
|
|
10
|
-
|
|
11
|
-
class Commands::Base < Dry::CLI::Command
|
|
12
|
-
# keys from Dry::Cli options which we do not want in the
|
|
13
|
-
# config object.
|
|
14
|
-
IGNORE_OPTIONS = %i[ version ]
|
|
15
|
-
|
|
16
|
-
global_header <<~EOS
|
|
17
|
-
|
|
18
|
-
SQA - Stock Quantitative Analysis
|
|
19
|
-
by: MadBomber
|
|
20
|
-
|
|
21
|
-
This is a work in progress. It is not fit for anything
|
|
22
|
-
other than play time. ** Do not ** use it to make any
|
|
23
|
-
kind of serious trading decisions.
|
|
24
|
-
|
|
25
|
-
EOS
|
|
26
|
-
|
|
27
|
-
global_footer <<~EOS
|
|
28
|
-
|
|
29
|
-
SARNING: This product is a work in progress. DO NOT USE
|
|
30
|
-
for serious trading decisions.
|
|
31
|
-
|
|
32
|
-
Copyright (c) 2023 - MadBomber Software
|
|
33
|
-
|
|
34
|
-
EOS
|
|
35
|
-
|
|
36
|
-
option :debug,
|
|
37
|
-
required: false,
|
|
38
|
-
type: :boolean,
|
|
39
|
-
desc: 'Print debug information',
|
|
40
|
-
aliases: %w[-d --debug]
|
|
41
|
-
|
|
42
|
-
option :verbose,
|
|
43
|
-
required: false,
|
|
44
|
-
type: :boolean,
|
|
45
|
-
desc: 'Print verbose information',
|
|
46
|
-
aliases: %w[-v --verbose]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
option :version,
|
|
50
|
-
required: false,
|
|
51
|
-
type: :boolean,
|
|
52
|
-
default: false,
|
|
53
|
-
desc: 'Print version(s) and exit',
|
|
54
|
-
aliases: %w[--version]
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
option :config_file,
|
|
58
|
-
required: false,
|
|
59
|
-
type: :string,
|
|
60
|
-
desc: "Path to the config file"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
option :log_level,
|
|
64
|
-
required: false,
|
|
65
|
-
type: :string,
|
|
66
|
-
values: %w[debug info warn error fatal ],
|
|
67
|
-
desc: "Set the log level"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
option :portfolio,
|
|
71
|
-
required: false,
|
|
72
|
-
aliases: %w[ --portfolio --folio --file -f ],
|
|
73
|
-
type: :string,
|
|
74
|
-
desc: "Set the filename of the portfolio"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
option :trades,
|
|
78
|
-
required: false,
|
|
79
|
-
aliases: %w[ --trades ],
|
|
80
|
-
type: :string,
|
|
81
|
-
desc: "Set the filename into which trades are stored"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
option :data_dir,
|
|
85
|
-
required: false,
|
|
86
|
-
aliases: %w[ --data-dir --data --dir ],
|
|
87
|
-
type: :string,
|
|
88
|
-
desc: "Set the directory for the SQA data"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
option :dump_config,
|
|
92
|
-
required: false,
|
|
93
|
-
type: :string,
|
|
94
|
-
desc: "Dump the current configuration to a file"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# All command class call methods should start with
|
|
98
|
-
# super so that this method is invoked.
|
|
99
|
-
#
|
|
100
|
-
# params is a Hash from Dry::CLI where keys are Symbol
|
|
101
|
-
|
|
102
|
-
def call(params)
|
|
103
|
-
show_versions_and_exit if params[:version]
|
|
104
|
-
|
|
105
|
-
unless params[:config_file].nil? || params[:config_file].empty?
|
|
106
|
-
SQA.config.config_file = params[:config_file]
|
|
107
|
-
SQA.config.from_file
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
update_config(params)
|
|
111
|
-
|
|
112
|
-
unless params[:dump_config].nil? || params[:dump_config].empty?
|
|
113
|
-
SQA.config.config_file = params[:dump_config]
|
|
114
|
-
SQA.config.dump_file
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
SQA.config
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
################################################
|
|
121
|
-
private
|
|
122
|
-
|
|
123
|
-
def show_versions_and_exit
|
|
124
|
-
self.class.ancestors.each do |ancestor|
|
|
125
|
-
next unless ancestor.const_defined?(:VERSION)
|
|
126
|
-
puts "#{ancestor}: #{ancestor::VERSION}"
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
puts "SQA: #{SQA::VERSION}" if SQA.const_defined?(:VERSION)
|
|
130
|
-
|
|
131
|
-
exit(0)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def update_config(params)
|
|
135
|
-
SQA.config.inject_additional_properties
|
|
136
|
-
my_hash = params.reject { |key, _| IGNORE_OPTIONS.include?(key) }
|
|
137
|
-
SQA.config.merge!(my_hash)
|
|
138
|
-
end
|
|
139
|
-
end
|
data/lib/sqa/commands/web.rb
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
# sqa/lib/sqa/commands/web.rb
|
|
2
|
-
|
|
3
|
-
class Commands::Web < Commands::Base
|
|
4
|
-
VERSION = "0.0.1-web"
|
|
5
|
-
|
|
6
|
-
Commands.register "web", self
|
|
7
|
-
|
|
8
|
-
desc "Start a web application"
|
|
9
|
-
|
|
10
|
-
option :image,
|
|
11
|
-
required: true,
|
|
12
|
-
type: :string,
|
|
13
|
-
desc: "The name of the image to use"
|
|
14
|
-
|
|
15
|
-
SQA::PluginManager.new_property(:restart, default: 'no', coerce: String)
|
|
16
|
-
|
|
17
|
-
option :restart,
|
|
18
|
-
aliases: %w[ --restart ],
|
|
19
|
-
type: :string,
|
|
20
|
-
default: "no",
|
|
21
|
-
values: %w[ no on-failure always unless-stopped ],
|
|
22
|
-
desc: "Restart policy to apply when a container exits"
|
|
23
|
-
|
|
24
|
-
SQA::PluginManager.new_property(:detach, default: 'no', coerce: String)
|
|
25
|
-
|
|
26
|
-
option :detach,
|
|
27
|
-
aliases: %w[ --detach ],
|
|
28
|
-
type: :boolean,
|
|
29
|
-
default: false,
|
|
30
|
-
desc: "Run container in background and print container ID"
|
|
31
|
-
|
|
32
|
-
SQA::PluginManager.new_property(:port, default: 4567, coerce: Integer)
|
|
33
|
-
|
|
34
|
-
option :port,
|
|
35
|
-
aliases: %w[ -p --port ],
|
|
36
|
-
type: :integer,
|
|
37
|
-
default: 4567,
|
|
38
|
-
desc: "The port where the web app will run"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def initialize
|
|
42
|
-
# TODO: make it happen
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# params is Object from the ARGV parser
|
|
47
|
-
def call(params)
|
|
48
|
-
config = super
|
|
49
|
-
|
|
50
|
-
puts <<~EOS
|
|
51
|
-
###############################
|
|
52
|
-
## Running the Web Interface ##
|
|
53
|
-
###############################
|
|
54
|
-
EOS
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
__END__
|
|
61
|
-
|
|
62
|
-
require 'sinatra/base'
|
|
63
|
-
|
|
64
|
-
module SQA
|
|
65
|
-
class Web < Sinatra::Base
|
|
66
|
-
set :port, SQA.config.port || 4567
|
|
67
|
-
|
|
68
|
-
get '/' do
|
|
69
|
-
"Welcome to SQA Web Interface!"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
get '/stocks/:ticker' do
|
|
74
|
-
ticker = params[:ticker]
|
|
75
|
-
stock = SQA::Stock.new(ticker: ticker, source: :alpha_vantage)
|
|
76
|
-
|
|
77
|
-
"Stock: #{stock.data.name}, Ticker: #{stock.data.ticker}"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
get '/stocks/:ticker/indicators/:indicator' do
|
|
82
|
-
ticker = params[:ticker]
|
|
83
|
-
indicator = params[:indicator]
|
|
84
|
-
stock = SQA::Stock.new(ticker: ticker, source: :alpha_vantage)
|
|
85
|
-
|
|
86
|
-
indicator_value = SQA::Indicator.send(indicator, stock.df.adj_close_price, 14)
|
|
87
|
-
|
|
88
|
-
"Indicator #{indicator} for Stock #{ticker} is #{indicator_value}"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# TODO: Add more routes as needed to expose more functionality
|
|
92
|
-
|
|
93
|
-
# start the server if ruby file executed directly
|
|
94
|
-
run! if app_file == $0
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
###################################################
|
|
101
|
-
#!/usr/bin/env ruby
|
|
102
|
-
# experiments/sinatra_examples/svg_viewer.rb
|
|
103
|
-
# builds on md_viewer.rb
|
|
104
|
-
|
|
105
|
-
require 'sinatra'
|
|
106
|
-
require 'kramdown'
|
|
107
|
-
require 'nenv'
|
|
108
|
-
|
|
109
|
-
# class MdViewer < Sinatra::Application
|
|
110
|
-
|
|
111
|
-
# Set the directory location
|
|
112
|
-
set :markdown_directory, Nenv.home + '/Downloads'
|
|
113
|
-
|
|
114
|
-
# Sinatra route to show a markdown file as html
|
|
115
|
-
get '/md/:filename' do
|
|
116
|
-
# Get the file name from the URL parameter
|
|
117
|
-
filename = params[:filename]
|
|
118
|
-
|
|
119
|
-
# Check if the file exists in the specified directory
|
|
120
|
-
if File.file?(File.join(settings.markdown_directory, filename))
|
|
121
|
-
# Read the markdown file
|
|
122
|
-
markdown_content = File.read(File.join(settings.markdown_directory, filename))
|
|
123
|
-
|
|
124
|
-
# Convert the markdown to HTML using kramdown
|
|
125
|
-
converted_html = Kramdown::Document.new(markdown_content).to_html
|
|
126
|
-
|
|
127
|
-
# Display the generated HTML
|
|
128
|
-
content_type :html
|
|
129
|
-
converted_html
|
|
130
|
-
else
|
|
131
|
-
# File not found error
|
|
132
|
-
status 404
|
|
133
|
-
"File not found: #{filename} in #{markdown_directory}"
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
# Sinatra route to show a markdown file as html
|
|
139
|
-
get '/svg/:filename' do
|
|
140
|
-
# Get the file name from the URL parameter
|
|
141
|
-
filename = params[:filename]
|
|
142
|
-
|
|
143
|
-
# Check if the file exists in the specified directory
|
|
144
|
-
if File.file?(File.join(settings.markdown_directory, filename))
|
|
145
|
-
# Read the svg file
|
|
146
|
-
svg_content = File.read(File.join(settings.markdown_directory, filename))
|
|
147
|
-
|
|
148
|
-
# Convert the svg to HTML
|
|
149
|
-
converted_html = <<~HTML
|
|
150
|
-
<!DOCTYPE html>
|
|
151
|
-
<html>
|
|
152
|
-
<head>
|
|
153
|
-
<meta charset="utf-8">
|
|
154
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
155
|
-
<title>#{filename}</title>
|
|
156
|
-
</head>
|
|
157
|
-
<body>
|
|
158
|
-
#{svg_content}
|
|
159
|
-
</body>
|
|
160
|
-
</html>
|
|
161
|
-
HTML
|
|
162
|
-
|
|
163
|
-
# Display the generated HTML
|
|
164
|
-
content_type :html
|
|
165
|
-
converted_html
|
|
166
|
-
else
|
|
167
|
-
# File not found error
|
|
168
|
-
status 404
|
|
169
|
-
"File not found: #{filename} in #{markdown_directory}"
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# end
|
|
177
|
-
|
|
178
|
-
# Start the Sinatra app
|
|
179
|
-
# run MdViewer
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
__END__
|
|
183
|
-
|
|
184
|
-
ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
|
|
185
|
-
|
|
186
|
-
Options are:
|
|
187
|
-
|
|
188
|
-
-h # help
|
|
189
|
-
-p # set the port (default is 4567)
|
|
190
|
-
-o # set the host (default is 0.0.0.0)
|
|
191
|
-
-e # set the environment (default is development)
|
|
192
|
-
-s # specify rack server/handler (default is puma)
|
|
193
|
-
-q # turn on quiet mode for server (default is off)
|
|
194
|
-
-x # turn on the mutex lock (default is off)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|