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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.goose/memory/development.txt +3 -0
  3. data/.semver +6 -0
  4. data/ARCHITECTURE.md +648 -0
  5. data/CHANGELOG.md +82 -0
  6. data/CLAUDE.md +653 -0
  7. data/COMMITS.md +196 -0
  8. data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
  9. data/NEXT-STEPS.md +154 -0
  10. data/README.md +812 -262
  11. data/TASKS.md +358 -0
  12. data/TEST_RESULTS.md +140 -0
  13. data/TODO.md +42 -0
  14. data/_notes.txt +25 -0
  15. data/bin/sqa-console +11 -0
  16. data/data/talk_talk.json +103284 -0
  17. data/develop_summary.md +313 -0
  18. data/docs/advanced/backtesting.md +206 -0
  19. data/docs/advanced/ensemble.md +68 -0
  20. data/docs/advanced/fpop.md +153 -0
  21. data/docs/advanced/index.md +112 -0
  22. data/docs/advanced/multi-timeframe.md +67 -0
  23. data/docs/advanced/pattern-matcher.md +75 -0
  24. data/docs/advanced/portfolio-optimizer.md +79 -0
  25. data/docs/advanced/portfolio.md +166 -0
  26. data/docs/advanced/risk-management.md +210 -0
  27. data/docs/advanced/strategy-generator.md +158 -0
  28. data/docs/advanced/streaming.md +209 -0
  29. data/docs/ai_and_ml.md +80 -0
  30. data/docs/api/dataframe.md +1115 -0
  31. data/docs/api/index.md +126 -0
  32. data/docs/assets/css/custom.css +88 -0
  33. data/docs/assets/js/mathjax.js +18 -0
  34. data/docs/concepts/index.md +68 -0
  35. data/docs/contributing/index.md +60 -0
  36. data/docs/data-sources/index.md +66 -0
  37. data/docs/data_frame.md +317 -97
  38. data/docs/factors_that_impact_price.md +26 -0
  39. data/docs/finviz.md +11 -0
  40. data/docs/fx_pro_bit.md +25 -0
  41. data/docs/genetic_programming.md +104 -0
  42. data/docs/getting-started/index.md +123 -0
  43. data/docs/getting-started/installation.md +229 -0
  44. data/docs/getting-started/quick-start.md +244 -0
  45. data/docs/i_gotta_an_idea.md +22 -0
  46. data/docs/index.md +163 -0
  47. data/docs/indicators/index.md +97 -0
  48. data/docs/indicators.md +110 -24
  49. data/docs/options.md +8 -0
  50. data/docs/strategies/bollinger-bands.md +146 -0
  51. data/docs/strategies/consensus.md +64 -0
  52. data/docs/strategies/custom.md +310 -0
  53. data/docs/strategies/ema.md +53 -0
  54. data/docs/strategies/index.md +92 -0
  55. data/docs/strategies/kbs.md +164 -0
  56. data/docs/strategies/macd.md +96 -0
  57. data/docs/strategies/market-profile.md +54 -0
  58. data/docs/strategies/mean-reversion.md +58 -0
  59. data/docs/strategies/rsi.md +95 -0
  60. data/docs/strategies/sma.md +55 -0
  61. data/docs/strategies/stochastic.md +63 -0
  62. data/docs/strategies/volume-breakout.md +54 -0
  63. data/docs/tags.md +7 -0
  64. data/docs/true_strength_index.md +46 -0
  65. data/docs/weighted_moving_average.md +48 -0
  66. data/examples/README.md +354 -0
  67. data/examples/advanced_features_example.rb +350 -0
  68. data/examples/fpop_analysis_example.rb +191 -0
  69. data/examples/genetic_programming_example.rb +148 -0
  70. data/examples/kbs_strategy_example.rb +208 -0
  71. data/examples/pattern_context_example.rb +300 -0
  72. data/examples/rails_app/Gemfile +34 -0
  73. data/examples/rails_app/README.md +416 -0
  74. data/examples/rails_app/app/assets/javascripts/application.js +107 -0
  75. data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
  76. data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
  77. data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
  78. data/examples/rails_app/app/controllers/application_controller.rb +22 -0
  79. data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
  80. data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
  81. data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
  82. data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
  83. data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
  84. data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
  85. data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
  86. data/examples/rails_app/app/views/errors/show.html.erb +17 -0
  87. data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
  88. data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
  89. data/examples/rails_app/bin/rails +6 -0
  90. data/examples/rails_app/config/application.rb +45 -0
  91. data/examples/rails_app/config/boot.rb +5 -0
  92. data/examples/rails_app/config/database.yml +18 -0
  93. data/examples/rails_app/config/environment.rb +11 -0
  94. data/examples/rails_app/config/routes.rb +26 -0
  95. data/examples/rails_app/config.ru +8 -0
  96. data/examples/realtime_stream_example.rb +274 -0
  97. data/examples/sinatra_app/Gemfile +22 -0
  98. data/examples/sinatra_app/QUICKSTART.md +159 -0
  99. data/examples/sinatra_app/README.md +461 -0
  100. data/examples/sinatra_app/app.rb +344 -0
  101. data/examples/sinatra_app/config.ru +5 -0
  102. data/examples/sinatra_app/public/css/style.css +659 -0
  103. data/examples/sinatra_app/public/js/app.js +107 -0
  104. data/examples/sinatra_app/views/analyze.erb +306 -0
  105. data/examples/sinatra_app/views/backtest.erb +325 -0
  106. data/examples/sinatra_app/views/dashboard.erb +419 -0
  107. data/examples/sinatra_app/views/error.erb +58 -0
  108. data/examples/sinatra_app/views/index.erb +118 -0
  109. data/examples/sinatra_app/views/layout.erb +61 -0
  110. data/examples/sinatra_app/views/portfolio.erb +43 -0
  111. data/examples/strategy_generator_example.rb +346 -0
  112. data/hsa_portfolio.csv +11 -0
  113. data/justfile +0 -0
  114. data/lib/api/alpha_vantage_api.rb +462 -0
  115. data/lib/sqa/backtest.rb +329 -0
  116. data/lib/sqa/data_frame/alpha_vantage.rb +43 -65
  117. data/lib/sqa/data_frame/data.rb +92 -0
  118. data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
  119. data/lib/sqa/data_frame.rb +148 -243
  120. data/lib/sqa/ensemble.rb +359 -0
  121. data/lib/sqa/fpop.rb +199 -0
  122. data/lib/sqa/gp.rb +259 -0
  123. data/lib/sqa/indicator.rb +5 -8
  124. data/lib/sqa/init.rb +15 -8
  125. data/lib/sqa/market_regime.rb +240 -0
  126. data/lib/sqa/multi_timeframe.rb +379 -0
  127. data/lib/sqa/pattern_matcher.rb +497 -0
  128. data/lib/sqa/portfolio.rb +260 -6
  129. data/lib/sqa/portfolio_optimizer.rb +377 -0
  130. data/lib/sqa/risk_manager.rb +442 -0
  131. data/lib/sqa/seasonal_analyzer.rb +209 -0
  132. data/lib/sqa/sector_analyzer.rb +300 -0
  133. data/lib/sqa/stock.rb +67 -125
  134. data/lib/sqa/strategy/bollinger_bands.rb +42 -0
  135. data/lib/sqa/strategy/consensus.rb +5 -2
  136. data/lib/sqa/strategy/kbs_strategy.rb +470 -0
  137. data/lib/sqa/strategy/macd.rb +46 -0
  138. data/lib/sqa/strategy/mp.rb +1 -1
  139. data/lib/sqa/strategy/stochastic.rb +60 -0
  140. data/lib/sqa/strategy/volume_breakout.rb +57 -0
  141. data/lib/sqa/strategy.rb +5 -0
  142. data/lib/sqa/strategy_generator.rb +947 -0
  143. data/lib/sqa/stream.rb +361 -0
  144. data/lib/sqa/version.rb +1 -7
  145. data/lib/sqa.rb +23 -16
  146. data/main.just +81 -0
  147. data/mkdocs.yml +288 -0
  148. data/trace.log +0 -0
  149. metadata +261 -51
  150. data/bin/sqa +0 -6
  151. data/lib/patches/dry-cli.rb +0 -228
  152. data/lib/sqa/activity.rb +0 -10
  153. data/lib/sqa/cli.rb +0 -62
  154. data/lib/sqa/commands/analysis.rb +0 -309
  155. data/lib/sqa/commands/base.rb +0 -139
  156. data/lib/sqa/commands/web.rb +0 -199
  157. data/lib/sqa/commands.rb +0 -22
  158. data/lib/sqa/constants.rb +0 -23
  159. data/lib/sqa/indicator/average_true_range.rb +0 -33
  160. data/lib/sqa/indicator/bollinger_bands.rb +0 -28
  161. data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
  162. data/lib/sqa/indicator/donchian_channel.rb +0 -29
  163. data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
  164. data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
  165. data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
  166. data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
  167. data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
  168. data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
  169. data/lib/sqa/indicator/market_profile.rb +0 -32
  170. data/lib/sqa/indicator/mean_reversion.rb +0 -37
  171. data/lib/sqa/indicator/momentum.rb +0 -28
  172. data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
  173. data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
  174. data/lib/sqa/indicator/predict_next_value.rb +0 -202
  175. data/lib/sqa/indicator/relative_strength_index.rb +0 -47
  176. data/lib/sqa/indicator/simple_moving_average.rb +0 -24
  177. data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
  178. data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
  179. data/lib/sqa/indicator/true_range.rb +0 -39
  180. data/lib/sqa/trade.rb +0 -26
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'sinatra'
5
+ require 'sinatra/json'
6
+ require 'json'
7
+
8
+ # Add SQA lib to load path
9
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
10
+
11
+ require 'sqa'
12
+
13
+ # Initialize SQA
14
+ SQA.init
15
+
16
+ # Configure Sinatra
17
+ set :port, 4567
18
+ set :bind, '0.0.0.0'
19
+ set :public_folder, File.dirname(__FILE__) + '/public'
20
+ set :views, File.dirname(__FILE__) + '/views'
21
+
22
+ # Enable sessions for flash messages
23
+ enable :sessions
24
+
25
+ # Helpers
26
+ helpers do
27
+ def format_percent(value)
28
+ sprintf("%.2f%%", value)
29
+ end
30
+
31
+ def format_currency(value)
32
+ sprintf("$%.2f", value)
33
+ end
34
+
35
+ def format_number(value)
36
+ value.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
37
+ end
38
+ end
39
+
40
+ # Routes
41
+
42
+ # Home / Dashboard
43
+ get '/' do
44
+ erb :index
45
+ end
46
+
47
+ # Dashboard for specific ticker
48
+ get '/dashboard/:ticker' do
49
+ ticker = params[:ticker].upcase
50
+
51
+ begin
52
+ @stock = SQA::Stock.new(ticker: ticker)
53
+ @ticker = ticker
54
+ erb :dashboard
55
+ rescue => e
56
+ @error = "Failed to load data for #{ticker}: #{e.message}"
57
+ erb :error
58
+ end
59
+ end
60
+
61
+ # Analysis page
62
+ get '/analyze/:ticker' do
63
+ ticker = params[:ticker].upcase
64
+
65
+ begin
66
+ @stock = SQA::Stock.new(ticker: ticker)
67
+ @ticker = ticker
68
+ erb :analyze
69
+ rescue => e
70
+ @error = "Failed to load data for #{ticker}: #{e.message}"
71
+ erb :error
72
+ end
73
+ end
74
+
75
+ # Backtest page
76
+ get '/backtest/:ticker' do
77
+ ticker = params[:ticker].upcase
78
+
79
+ begin
80
+ @stock = SQA::Stock.new(ticker: ticker)
81
+ @ticker = ticker
82
+ erb :backtest
83
+ rescue => e
84
+ @error = "Failed to load data for #{ticker}: #{e.message}"
85
+ erb :error
86
+ end
87
+ end
88
+
89
+ # Portfolio optimizer
90
+ get '/portfolio' do
91
+ erb :portfolio
92
+ end
93
+
94
+ # API Endpoints
95
+
96
+ # Get stock data
97
+ get '/api/stock/:ticker' do
98
+ content_type :json
99
+
100
+ ticker = params[:ticker].upcase
101
+
102
+ begin
103
+ stock = SQA::Stock.new(ticker: ticker)
104
+ df = stock.df
105
+
106
+ # Get price data
107
+ dates = df["date"].to_a.map(&:to_s)
108
+ opens = df["open_price"].to_a
109
+ highs = df["high_price"].to_a
110
+ lows = df["low_price"].to_a
111
+ closes = df["adj_close_price"].to_a
112
+ volumes = df["volume"].to_a
113
+
114
+ # Calculate basic stats
115
+ current_price = closes.last
116
+ prev_price = closes[-2]
117
+ change = current_price - prev_price
118
+ change_pct = (change / prev_price) * 100
119
+
120
+ {
121
+ ticker: ticker,
122
+ current_price: current_price,
123
+ change: change,
124
+ change_percent: change_pct,
125
+ high_52w: closes.last(252).max,
126
+ low_52w: closes.last(252).min,
127
+ dates: dates,
128
+ open: opens,
129
+ high: highs,
130
+ low: lows,
131
+ close: closes,
132
+ volume: volumes
133
+ }.to_json
134
+ rescue => e
135
+ status 500
136
+ { error: e.message }.to_json
137
+ end
138
+ end
139
+
140
+ # Get technical indicators
141
+ get '/api/indicators/:ticker' do
142
+ content_type :json
143
+
144
+ ticker = params[:ticker].upcase
145
+
146
+ begin
147
+ stock = SQA::Stock.new(ticker: ticker)
148
+ df = stock.df
149
+
150
+ prices = df["adj_close_price"].to_a
151
+ highs = df["high_price"].to_a
152
+ lows = df["low_price"].to_a
153
+ dates = df["date"].to_a.map(&:to_s)
154
+
155
+ # Calculate indicators
156
+ rsi = SQAI.rsi(prices, period: 14)
157
+ macd_result = SQAI.macd(prices)
158
+ bb_result = SQAI.bbands(prices)
159
+ sma_20 = SQAI.sma(prices, period: 20)
160
+ sma_50 = SQAI.sma(prices, period: 50)
161
+ ema_20 = SQAI.ema(prices, period: 20)
162
+
163
+ {
164
+ dates: dates,
165
+ rsi: rsi,
166
+ macd: macd_result[0], # MACD line
167
+ macd_signal: macd_result[1], # Signal line
168
+ macd_hist: macd_result[2], # Histogram
169
+ bb_upper: bb_result[0],
170
+ bb_middle: bb_result[1],
171
+ bb_lower: bb_result[2],
172
+ sma_20: sma_20,
173
+ sma_50: sma_50,
174
+ ema_20: ema_20
175
+ }.to_json
176
+ rescue => e
177
+ status 500
178
+ { error: e.message }.to_json
179
+ end
180
+ end
181
+
182
+ # Run backtest
183
+ post '/api/backtest/:ticker' do
184
+ content_type :json
185
+
186
+ ticker = params[:ticker].upcase
187
+ strategy_name = params[:strategy] || 'RSI'
188
+
189
+ begin
190
+ stock = SQA::Stock.new(ticker: ticker)
191
+
192
+ # Resolve strategy
193
+ strategy = case strategy_name.upcase
194
+ when 'RSI' then SQA::Strategy::RSI
195
+ when 'SMA' then SQA::Strategy::SMA
196
+ when 'EMA' then SQA::Strategy::EMA
197
+ when 'MACD' then SQA::Strategy::MACD
198
+ when 'BOLLINGERBANDS' then SQA::Strategy::BollingerBands
199
+ when 'KBS' then SQA::Strategy::KBS
200
+ else SQA::Strategy::RSI
201
+ end
202
+
203
+ # Run backtest
204
+ backtest = SQA::Backtest.new(
205
+ stock: stock,
206
+ strategy: strategy,
207
+ initial_capital: 10_000.0,
208
+ commission: 1.0
209
+ )
210
+
211
+ results = backtest.run
212
+
213
+ {
214
+ total_return: results.total_return,
215
+ annualized_return: results.annualized_return,
216
+ sharpe_ratio: results.sharpe_ratio,
217
+ max_drawdown: results.max_drawdown,
218
+ win_rate: results.win_rate,
219
+ total_trades: results.total_trades,
220
+ profit_factor: results.profit_factor,
221
+ avg_win: results.avg_win,
222
+ avg_loss: results.avg_loss
223
+ }.to_json
224
+ rescue => e
225
+ status 500
226
+ { error: e.message }.to_json
227
+ end
228
+ end
229
+
230
+ # Run market analysis
231
+ get '/api/analyze/:ticker' do
232
+ content_type :json
233
+
234
+ ticker = params[:ticker].upcase
235
+
236
+ begin
237
+ stock = SQA::Stock.new(ticker: ticker)
238
+ prices = stock.df["adj_close_price"].to_a
239
+
240
+ # Market regime
241
+ regime = SQA::MarketRegime.detect(stock)
242
+
243
+ # Seasonal analysis
244
+ seasonal = SQA::SeasonalAnalyzer.analyze(stock)
245
+
246
+ # FPOP analysis
247
+ fpop_data = SQA::FPOP.fpl_analysis(prices, fpop: 10)
248
+ recent_fpop = fpop_data.last(10).map do |f|
249
+ {
250
+ direction: f[:direction],
251
+ magnitude: f[:magnitude],
252
+ risk: f[:risk],
253
+ interpretation: f[:interpretation]
254
+ }
255
+ end
256
+
257
+ # Risk metrics
258
+ returns = prices.each_cons(2).map { |a, b| (b - a) / a }
259
+ var_95 = SQA::RiskManager.var(returns, confidence: 0.95)
260
+ sharpe = SQA::RiskManager.sharpe_ratio(returns)
261
+ max_dd = SQA::RiskManager.max_drawdown(prices)
262
+
263
+ {
264
+ regime: {
265
+ type: regime[:type],
266
+ volatility: regime[:volatility],
267
+ strength: regime[:strength],
268
+ trend: regime[:trend]
269
+ },
270
+ seasonal: {
271
+ best_months: seasonal[:best_months],
272
+ worst_months: seasonal[:worst_months],
273
+ best_quarters: seasonal[:best_quarters],
274
+ has_pattern: seasonal[:has_seasonal_pattern]
275
+ },
276
+ fpop: recent_fpop,
277
+ risk: {
278
+ var_95: var_95,
279
+ sharpe_ratio: sharpe,
280
+ max_drawdown: max_dd[:max_drawdown]
281
+ }
282
+ }.to_json
283
+ rescue => e
284
+ status 500
285
+ { error: e.message }.to_json
286
+ end
287
+ end
288
+
289
+ # Compare strategies
290
+ post '/api/compare/:ticker' do
291
+ content_type :json
292
+
293
+ ticker = params[:ticker].upcase
294
+
295
+ begin
296
+ stock = SQA::Stock.new(ticker: ticker)
297
+
298
+ strategies = {
299
+ 'RSI' => SQA::Strategy::RSI,
300
+ 'SMA' => SQA::Strategy::SMA,
301
+ 'EMA' => SQA::Strategy::EMA,
302
+ 'MACD' => SQA::Strategy::MACD,
303
+ 'BollingerBands' => SQA::Strategy::BollingerBands
304
+ }
305
+
306
+ results = strategies.map do |name, strategy_class|
307
+ backtest = SQA::Backtest.new(
308
+ stock: stock,
309
+ strategy: strategy_class,
310
+ initial_capital: 10_000.0,
311
+ commission: 1.0
312
+ )
313
+
314
+ result = backtest.run
315
+
316
+ {
317
+ strategy: name,
318
+ return: result.total_return,
319
+ sharpe: result.sharpe_ratio,
320
+ drawdown: result.max_drawdown,
321
+ win_rate: result.win_rate,
322
+ trades: result.total_trades
323
+ }
324
+ rescue => e
325
+ nil
326
+ end.compact
327
+
328
+ results.sort_by! { |r| -r[:return] }
329
+ results.to_json
330
+ rescue => e
331
+ status 500
332
+ { error: e.message }.to_json
333
+ end
334
+ end
335
+
336
+ # Start server
337
+ if __FILE__ == $0
338
+ puts "=" * 60
339
+ puts "SQA Web Application"
340
+ puts "=" * 60
341
+ puts "Starting server on http://localhost:4567"
342
+ puts "Press Ctrl+C to stop"
343
+ puts "=" * 60
344
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'app'
4
+
5
+ run Sinatra::Application