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,118 @@
1
+ <div class="hero">
2
+ <div class="hero-content">
3
+ <h1>
4
+ <i class="fas fa-chart-line"></i>
5
+ Stock Analysis Platform
6
+ </h1>
7
+ <p class="hero-subtitle">
8
+ Advanced technical analysis powered by SQA library - Rails Edition
9
+ </p>
10
+
11
+ <div class="ticker-search">
12
+ <%= form_with url: "#", method: :get, local: true, html: { onsubmit: "return searchTicker(event)", class: "search-form" } do |f| %>
13
+ <input
14
+ type="text"
15
+ id="mainTickerInput"
16
+ placeholder="Enter stock ticker (e.g., AAPL, MSFT, GOOGL)"
17
+ autocomplete="off"
18
+ >
19
+ <button type="submit" class="btn btn-primary btn-large">
20
+ <i class="fas fa-search"></i> Analyze
21
+ </button>
22
+ <% end %>
23
+ </div>
24
+
25
+ <div class="quick-links">
26
+ <h3>Quick Analysis</h3>
27
+ <div class="ticker-buttons">
28
+ <%= link_to "AAPL", stock_dashboard_path("AAPL"), class: "ticker-btn" %>
29
+ <%= link_to "MSFT", stock_dashboard_path("MSFT"), class: "ticker-btn" %>
30
+ <%= link_to "GOOGL", stock_dashboard_path("GOOGL"), class: "ticker-btn" %>
31
+ <%= link_to "AMZN", stock_dashboard_path("AMZN"), class: "ticker-btn" %>
32
+ <%= link_to "TSLA", stock_dashboard_path("TSLA"), class: "ticker-btn" %>
33
+ <%= link_to "NVDA", stock_dashboard_path("NVDA"), class: "ticker-btn" %>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="features">
40
+ <div class="container">
41
+ <h2>Features</h2>
42
+ <div class="feature-grid">
43
+ <div class="feature-card">
44
+ <div class="feature-icon">
45
+ <i class="fas fa-chart-candlestick"></i>
46
+ </div>
47
+ <h3>Interactive Charts</h3>
48
+ <p>Candlestick charts with technical indicators including RSI, MACD, Bollinger Bands, and moving averages.</p>
49
+ </div>
50
+
51
+ <div class="feature-card">
52
+ <div class="feature-icon">
53
+ <i class="fas fa-robot"></i>
54
+ </div>
55
+ <h3>Strategy Backtesting</h3>
56
+ <p>Test trading strategies on historical data with detailed performance metrics and comparison tools.</p>
57
+ </div>
58
+
59
+ <div class="feature-card">
60
+ <div class="feature-icon">
61
+ <i class="fas fa-brain"></i>
62
+ </div>
63
+ <h3>Market Analysis</h3>
64
+ <p>Detect market regimes, seasonal patterns, and future price projections with FPOP analysis.</p>
65
+ </div>
66
+
67
+ <div class="feature-card">
68
+ <div class="feature-icon">
69
+ <i class="fas fa-shield-alt"></i>
70
+ </div>
71
+ <h3>Risk Management</h3>
72
+ <p>Value at Risk (VaR), Sharpe ratio, maximum drawdown, and other risk metrics for informed decisions.</p>
73
+ </div>
74
+
75
+ <div class="feature-card">
76
+ <div class="feature-icon">
77
+ <i class="fas fa-layer-group"></i>
78
+ </div>
79
+ <h3>Portfolio Optimization</h3>
80
+ <p>Optimize allocation across multiple stocks using maximum Sharpe, minimum variance, or risk parity.</p>
81
+ </div>
82
+
83
+ <div class="feature-card">
84
+ <div class="feature-icon">
85
+ <i class="fas fa-search-dollar"></i>
86
+ </div>
87
+ <h3>Pattern Discovery</h3>
88
+ <p>Discover profitable trading patterns by reverse-engineering historical data and indicator combinations.</p>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="info-section">
95
+ <div class="container">
96
+ <div class="info-content">
97
+ <h2>About SQA Rails Application</h2>
98
+ <p>
99
+ This Rails application provides a modern web interface for the SQA library. Built with Rails 7.1,
100
+ it offers the same powerful features as the Sinatra app with the added benefits of Rails conventions
101
+ and the MVC architecture.
102
+ </p>
103
+ <ul class="info-list">
104
+ <li><i class="fas fa-check-circle"></i> 150+ technical indicators via TA-Lib</li>
105
+ <li><i class="fas fa-check-circle"></i> 12+ built-in trading strategies</li>
106
+ <li><i class="fas fa-check-circle"></i> High-performance Polars DataFrame backend</li>
107
+ <li><i class="fas fa-check-circle"></i> Real-time streaming support</li>
108
+ <li><i class="fas fa-check-circle"></i> Genetic programming for parameter optimization</li>
109
+ <li><i class="fas fa-check-circle"></i> Knowledge-based trading with RETE engine</li>
110
+ </ul>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <script>
116
+ // Focus on ticker input when page loads
117
+ document.getElementById('mainTickerInput').focus();
118
+ </script>
@@ -0,0 +1,408 @@
1
+ <div class="dashboard">
2
+ <div class="dashboard-header">
3
+ <div class="ticker-info">
4
+ <h1><%= @ticker %></h1>
5
+ <div id="priceInfo" class="price-info">
6
+ <span class="current-price">Loading...</span>
7
+ <span class="price-change">--</span>
8
+ </div>
9
+ </div>
10
+ <div class="header-actions">
11
+ <%= link_to stock_analysis_path(@ticker), class: "btn btn-secondary" do %>
12
+ <i class="fas fa-analytics"></i> Analysis
13
+ <% end %>
14
+ <%= link_to stock_backtest_path(@ticker), class: "btn btn-secondary" do %>
15
+ <i class="fas fa-history"></i> Backtest
16
+ <% end %>
17
+ <button onclick="refreshData()" class="btn btn-secondary">
18
+ <i class="fas fa-sync-alt"></i> Refresh
19
+ </button>
20
+ </div>
21
+ </div>
22
+
23
+ <!-- Key Metrics Cards -->
24
+ <div class="metrics-grid">
25
+ <div class="metric-card">
26
+ <div class="metric-label">52-Week High</div>
27
+ <div id="high52w" class="metric-value">--</div>
28
+ </div>
29
+ <div class="metric-card">
30
+ <div class="metric-label">52-Week Low</div>
31
+ <div id="low52w" class="metric-value">--</div>
32
+ </div>
33
+ <div class="metric-card">
34
+ <div class="metric-label">Current RSI</div>
35
+ <div id="currentRSI" class="metric-value">--</div>
36
+ <div id="rsiSignal" class="metric-signal"></div>
37
+ </div>
38
+ <div class="metric-card">
39
+ <div class="metric-label">Market Regime</div>
40
+ <div id="marketRegime" class="metric-value">--</div>
41
+ <div id="regimeDetail" class="metric-signal"></div>
42
+ </div>
43
+ </div>
44
+
45
+ <!-- Main Price Chart -->
46
+ <div class="chart-container">
47
+ <div class="chart-header">
48
+ <h2><i class="fas fa-chart-candlestick"></i> Price Chart</h2>
49
+ <div class="chart-controls">
50
+ <button onclick="updateChartType('candlestick')" class="btn-small active" data-chart="candlestick">
51
+ Candlestick
52
+ </button>
53
+ <button onclick="updateChartType('line')" class="btn-small" data-chart="line">
54
+ Line
55
+ </button>
56
+ </div>
57
+ </div>
58
+ <div id="priceChart" class="chart"></div>
59
+ </div>
60
+
61
+ <!-- Volume Chart -->
62
+ <div class="chart-container">
63
+ <div class="chart-header">
64
+ <h2><i class="fas fa-chart-bar"></i> Volume</h2>
65
+ </div>
66
+ <div id="volumeChart" class="chart"></div>
67
+ </div>
68
+
69
+ <!-- Technical Indicators Grid -->
70
+ <div class="indicators-grid">
71
+ <!-- RSI Chart -->
72
+ <div class="chart-container">
73
+ <div class="chart-header">
74
+ <h3><i class="fas fa-wave-square"></i> RSI (14)</h3>
75
+ </div>
76
+ <div id="rsiChart" class="chart chart-small"></div>
77
+ </div>
78
+
79
+ <!-- MACD Chart -->
80
+ <div class="chart-container">
81
+ <div class="chart-header">
82
+ <h3><i class="fas fa-signal"></i> MACD</h3>
83
+ </div>
84
+ <div id="macdChart" class="chart chart-small"></div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Strategy Comparison -->
89
+ <div class="chart-container">
90
+ <div class="chart-header">
91
+ <h2><i class="fas fa-trophy"></i> Strategy Comparison</h2>
92
+ <button onclick="runStrategyComparison()" class="btn btn-primary">
93
+ <i class="fas fa-play"></i> Compare Strategies
94
+ </button>
95
+ </div>
96
+ <div id="strategyResults" class="strategy-results">
97
+ <p class="hint">Click "Compare Strategies" to see backtest results for different trading strategies.</p>
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <%= javascript_tag do %>
103
+ const ticker = '<%= @ticker %>';
104
+ let stockData = null;
105
+ let indicatorData = null;
106
+ let currentChartType = 'candlestick';
107
+
108
+ document.addEventListener('DOMContentLoaded', async function() {
109
+ await loadStockData();
110
+ await loadIndicators();
111
+ await loadAnalysis();
112
+ });
113
+
114
+ async function loadStockData() {
115
+ try {
116
+ const response = await fetch(`/api/v1/stock/${ticker}`);
117
+ stockData = await response.json();
118
+
119
+ document.querySelector('.current-price').textContent = `$${stockData.current_price.toFixed(2)}`;
120
+ const changeClass = stockData.change >= 0 ? 'positive' : 'negative';
121
+ const changeSign = stockData.change >= 0 ? '+' : '';
122
+ document.querySelector('.price-change').className = `price-change ${changeClass}`;
123
+ document.querySelector('.price-change').textContent =
124
+ `${changeSign}${stockData.change.toFixed(2)} (${changeSign}${stockData.change_percent.toFixed(2)}%)`;
125
+
126
+ document.getElementById('high52w').textContent = `$${stockData.high_52w.toFixed(2)}`;
127
+ document.getElementById('low52w').textContent = `$${stockData.low_52w.toFixed(2)}`;
128
+
129
+ renderPriceChart();
130
+ renderVolumeChart();
131
+ } catch (error) {
132
+ console.error('Error loading stock data:', error);
133
+ alert('Failed to load stock data. Please try again.');
134
+ }
135
+ }
136
+
137
+ async function loadIndicators() {
138
+ try {
139
+ const response = await fetch(`/api/v1/indicators/${ticker}`);
140
+ indicatorData = await response.json();
141
+
142
+ const currentRSI = indicatorData.rsi[indicatorData.rsi.length - 1];
143
+ document.getElementById('currentRSI').textContent = currentRSI.toFixed(2);
144
+
145
+ let rsiSignal = '', rsiClass = '';
146
+ if (currentRSI < 30) {
147
+ rsiSignal = 'Oversold';
148
+ rsiClass = 'signal-buy';
149
+ } else if (currentRSI > 70) {
150
+ rsiSignal = 'Overbought';
151
+ rsiClass = 'signal-sell';
152
+ } else {
153
+ rsiSignal = 'Neutral';
154
+ rsiClass = 'signal-neutral';
155
+ }
156
+ const rsiSignalEl = document.getElementById('rsiSignal');
157
+ rsiSignalEl.textContent = rsiSignal;
158
+ rsiSignalEl.className = `metric-signal ${rsiClass}`;
159
+
160
+ renderRSIChart();
161
+ renderMACDChart();
162
+ } catch (error) {
163
+ console.error('Error loading indicators:', error);
164
+ }
165
+ }
166
+
167
+ async function loadAnalysis() {
168
+ try {
169
+ const response = await fetch(`/api/v1/analyze/${ticker}`);
170
+ const analysis = await response.json();
171
+
172
+ const regime = analysis.regime.type.toUpperCase();
173
+ const regimeEl = document.getElementById('marketRegime');
174
+ regimeEl.textContent = regime;
175
+
176
+ let regimeClass = '';
177
+ if (regime === 'BULL') regimeClass = 'signal-buy';
178
+ else if (regime === 'BEAR') regimeClass = 'signal-sell';
179
+ else regimeClass = 'signal-neutral';
180
+ regimeEl.className = `metric-value ${regimeClass}`;
181
+
182
+ const regimeDetail = document.getElementById('regimeDetail');
183
+ regimeDetail.textContent = `${analysis.regime.volatility} volatility, ${analysis.regime.strength.toFixed(2)} strength`;
184
+ regimeDetail.className = 'metric-signal';
185
+ } catch (error) {
186
+ console.error('Error loading analysis:', error);
187
+ }
188
+ }
189
+
190
+ function renderPriceChart() {
191
+ const trace = currentChartType === 'candlestick' ? {
192
+ type: 'candlestick',
193
+ x: stockData.dates,
194
+ open: stockData.open,
195
+ high: stockData.high,
196
+ low: stockData.low,
197
+ close: stockData.close,
198
+ name: ticker,
199
+ increasing: { line: { color: '#26a69a' } },
200
+ decreasing: { line: { color: '#ef5350' } }
201
+ } : {
202
+ type: 'scatter',
203
+ mode: 'lines',
204
+ x: stockData.dates,
205
+ y: stockData.close,
206
+ name: ticker,
207
+ line: { color: '#2196F3', width: 2 }
208
+ };
209
+
210
+ const traces = [trace];
211
+ if (indicatorData) {
212
+ traces.push({
213
+ type: 'scatter',
214
+ mode: 'lines',
215
+ x: indicatorData.dates,
216
+ y: indicatorData.sma_20,
217
+ name: 'SMA 20',
218
+ line: { color: '#FF9800', width: 1, dash: 'dash' }
219
+ });
220
+ traces.push({
221
+ type: 'scatter',
222
+ mode: 'lines',
223
+ x: indicatorData.dates,
224
+ y: indicatorData.sma_50,
225
+ name: 'SMA 50',
226
+ line: { color: '#9C27B0', width: 1, dash: 'dash' }
227
+ });
228
+ }
229
+
230
+ const layout = {
231
+ title: '',
232
+ xaxis: { title: 'Date', rangeslider: { visible: false } },
233
+ yaxis: { title: 'Price ($)' },
234
+ plot_bgcolor: '#ffffff',
235
+ paper_bgcolor: '#ffffff',
236
+ margin: { l: 50, r: 50, t: 20, b: 50 },
237
+ height: 400,
238
+ showlegend: true,
239
+ legend: { x: 0, y: 1 }
240
+ };
241
+
242
+ Plotly.newPlot('priceChart', traces, layout, { responsive: true });
243
+ }
244
+
245
+ function renderVolumeChart() {
246
+ const colors = stockData.close.map((close, i) => {
247
+ if (i === 0) return '#2196F3';
248
+ return close >= stockData.close[i - 1] ? '#26a69a' : '#ef5350';
249
+ });
250
+
251
+ const trace = {
252
+ type: 'bar',
253
+ x: stockData.dates,
254
+ y: stockData.volume,
255
+ name: 'Volume',
256
+ marker: { color: colors }
257
+ };
258
+
259
+ const layout = {
260
+ title: '',
261
+ xaxis: { title: 'Date' },
262
+ yaxis: { title: 'Volume' },
263
+ plot_bgcolor: '#ffffff',
264
+ paper_bgcolor: '#ffffff',
265
+ margin: { l: 50, r: 50, t: 20, b: 50 },
266
+ height: 200,
267
+ showlegend: false
268
+ };
269
+
270
+ Plotly.newPlot('volumeChart', [trace], layout, { responsive: true });
271
+ }
272
+
273
+ function renderRSIChart() {
274
+ const trace = {
275
+ type: 'scatter',
276
+ mode: 'lines',
277
+ x: indicatorData.dates,
278
+ y: indicatorData.rsi,
279
+ name: 'RSI',
280
+ line: { color: '#2196F3', width: 2 }
281
+ };
282
+
283
+ const oversold = {
284
+ type: 'scatter',
285
+ mode: 'lines',
286
+ x: indicatorData.dates,
287
+ y: Array(indicatorData.dates.length).fill(30),
288
+ name: 'Oversold',
289
+ line: { color: '#4CAF50', width: 1, dash: 'dash' }
290
+ };
291
+
292
+ const overbought = {
293
+ type: 'scatter',
294
+ mode: 'lines',
295
+ x: indicatorData.dates,
296
+ y: Array(indicatorData.dates.length).fill(70),
297
+ name: 'Overbought',
298
+ line: { color: '#F44336', width: 1, dash: 'dash' }
299
+ };
300
+
301
+ const layout = {
302
+ title: '',
303
+ xaxis: { title: 'Date' },
304
+ yaxis: { title: 'RSI', range: [0, 100] },
305
+ plot_bgcolor: '#ffffff',
306
+ paper_bgcolor: '#ffffff',
307
+ margin: { l: 50, r: 50, t: 20, b: 50 },
308
+ height: 250,
309
+ showlegend: true,
310
+ legend: { x: 0, y: 1 }
311
+ };
312
+
313
+ Plotly.newPlot('rsiChart', [trace, oversold, overbought], layout, { responsive: true });
314
+ }
315
+
316
+ function renderMACDChart() {
317
+ const macd = {
318
+ type: 'scatter',
319
+ mode: 'lines',
320
+ x: indicatorData.dates,
321
+ y: indicatorData.macd,
322
+ name: 'MACD',
323
+ line: { color: '#2196F3', width: 2 }
324
+ };
325
+
326
+ const signal = {
327
+ type: 'scatter',
328
+ mode: 'lines',
329
+ x: indicatorData.dates,
330
+ y: indicatorData.macd_signal,
331
+ name: 'Signal',
332
+ line: { color: '#FF9800', width: 2 }
333
+ };
334
+
335
+ const hist = {
336
+ type: 'bar',
337
+ x: indicatorData.dates,
338
+ y: indicatorData.macd_hist,
339
+ name: 'Histogram',
340
+ marker: {
341
+ color: indicatorData.macd_hist.map(v => v >= 0 ? '#26a69a' : '#ef5350')
342
+ }
343
+ };
344
+
345
+ const layout = {
346
+ title: '',
347
+ xaxis: { title: 'Date' },
348
+ yaxis: { title: 'MACD' },
349
+ plot_bgcolor: '#ffffff',
350
+ paper_bgcolor: '#ffffff',
351
+ margin: { l: 50, r: 50, t: 20, b: 50 },
352
+ height: 250,
353
+ showlegend: true,
354
+ legend: { x: 0, y: 1 }
355
+ };
356
+
357
+ Plotly.newPlot('macdChart', [hist, macd, signal], layout, { responsive: true });
358
+ }
359
+
360
+ function updateChartType(type) {
361
+ currentChartType = type;
362
+ document.querySelectorAll('[data-chart]').forEach(btn => btn.classList.remove('active'));
363
+ document.querySelector(`[data-chart="${type}"]`).classList.add('active');
364
+ renderPriceChart();
365
+ }
366
+
367
+ async function runStrategyComparison() {
368
+ const resultsDiv = document.getElementById('strategyResults');
369
+ resultsDiv.innerHTML = '<p class="loading"><i class="fas fa-spinner fa-spin"></i> Running backtests...</p>';
370
+
371
+ try {
372
+ const response = await fetch(`/api/v1/compare/${ticker}`, {
373
+ method: 'POST',
374
+ headers: {
375
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
376
+ }
377
+ });
378
+ const results = await response.json();
379
+
380
+ let html = '<table class="results-table"><thead><tr>';
381
+ html += '<th>Strategy</th><th>Return</th><th>Sharpe</th><th>Drawdown</th><th>Win Rate</th><th>Trades</th>';
382
+ html += '</tr></thead><tbody>';
383
+
384
+ results.forEach((result, i) => {
385
+ const returnClass = result.return >= 0 ? 'positive' : 'negative';
386
+ const rank = i === 0 ? '<i class="fas fa-trophy" style="color: #FFD700;"></i> ' : '';
387
+ html += `<tr>
388
+ <td>${rank}${result.strategy}</td>
389
+ <td class="${returnClass}">${result.return.toFixed(2)}%</td>
390
+ <td>${result.sharpe.toFixed(2)}</td>
391
+ <td>${result.drawdown.toFixed(2)}%</td>
392
+ <td>${result.win_rate.toFixed(2)}%</td>
393
+ <td>${result.trades}</td>
394
+ </tr>`;
395
+ });
396
+
397
+ html += '</tbody></table>';
398
+ resultsDiv.innerHTML = html;
399
+ } catch (error) {
400
+ console.error('Error comparing strategies:', error);
401
+ resultsDiv.innerHTML = '<p class="error">Failed to compare strategies. Please try again.</p>';
402
+ }
403
+ }
404
+
405
+ function refreshData() {
406
+ location.reload();
407
+ }
408
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <div class="error-page">
2
+ <div class="error-container">
3
+ <div class="error-icon">
4
+ <i class="fas fa-exclamation-triangle"></i>
5
+ </div>
6
+ <h1>Oops! Something went wrong</h1>
7
+ <p class="error-message"><%= @error %></p>
8
+ <div class="error-actions">
9
+ <%= link_to root_path, class: "btn btn-primary" do %>
10
+ <i class="fas fa-home"></i> Back to Home
11
+ <% end %>
12
+ <button onclick="history.back()" class="btn btn-secondary">
13
+ <i class="fas fa-arrow-left"></i> Go Back
14
+ </button>
15
+ </div>
16
+ </div>
17
+ </div>
@@ -0,0 +1,60 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SQA - Stock Analysis Platform</title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+
10
+ <!-- Plotly.js for charts -->
11
+ <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
12
+
13
+ <!-- Font Awesome for icons -->
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
15
+
16
+ <%= stylesheet_link_tag "application", media: "all" %>
17
+ <%= javascript_importmap_tags %>
18
+ </head>
19
+ <body>
20
+ <!-- Navigation -->
21
+ <nav class="navbar">
22
+ <div class="nav-container">
23
+ <div class="nav-brand">
24
+ <i class="fas fa-chart-line"></i>
25
+ <span>SQA Analytics (Rails)</span>
26
+ </div>
27
+ <ul class="nav-menu">
28
+ <li><%= link_to root_path, class: "nav-link" do %><i class="fas fa-home"></i> Home<% end %></li>
29
+ <li><a href="#" class="nav-link" onclick="showTickerModal()"><i class="fas fa-search"></i> Analyze Stock</a></li>
30
+ <li><%= link_to portfolio_path, class: "nav-link" do %><i class="fas fa-briefcase"></i> Portfolio<% end %></li>
31
+ </ul>
32
+ </div>
33
+ </nav>
34
+
35
+ <!-- Main Content -->
36
+ <main class="main-content">
37
+ <%= yield %>
38
+ </main>
39
+
40
+ <!-- Footer -->
41
+ <footer class="footer">
42
+ <div class="footer-content">
43
+ <p>&copy; 2025 SQA - Simple Qualitative Analysis. Educational purposes only.</p>
44
+ <p class="disclaimer">⚠️ Not financial advice. For educational use only.</p>
45
+ </div>
46
+ </footer>
47
+
48
+ <!-- Ticker Search Modal -->
49
+ <div id="tickerModal" class="modal">
50
+ <div class="modal-content">
51
+ <span class="close" onclick="closeTickerModal()">&times;</span>
52
+ <h2>Enter Stock Ticker</h2>
53
+ <%= form_with url: "#", method: :get, local: true, html: { onsubmit: "return searchTicker(event)" } do |f| %>
54
+ <input type="text" id="tickerInput" placeholder="e.g., AAPL" autocomplete="off" autofocus>
55
+ <button type="submit" class="btn btn-primary">Analyze</button>
56
+ <% end %>
57
+ </div>
58
+ </div>
59
+ </body>
60
+ </html>
@@ -0,0 +1,33 @@
1
+ <div class="dashboard">
2
+ <div class="dashboard-header">
3
+ <div class="ticker-info">
4
+ <h1><i class="fas fa-briefcase"></i> Portfolio Optimization</h1>
5
+ <p style="color: var(--text-secondary); margin-top: 0.5rem;">
6
+ Optimize allocation across multiple stocks for maximum risk-adjusted returns
7
+ </p>
8
+ </div>
9
+ </div>
10
+
11
+ <div class="chart-container">
12
+ <div class="chart-header">
13
+ <h2><i class="fas fa-layer-group"></i> Coming Soon</h2>
14
+ </div>
15
+ <div style="padding: 3rem 2rem; text-align: center;">
16
+ <div style="font-size: 5rem; color: var(--primary-color); margin-bottom: 2rem;">
17
+ <i class="fas fa-tools"></i>
18
+ </div>
19
+ <h3 style="font-size: 2rem; margin-bottom: 1rem;">
20
+ Portfolio Optimization Feature
21
+ </h3>
22
+ <p style="font-size: 1.1rem; color: var(--text-secondary); max-width: 600px; margin: 0 auto 2rem;">
23
+ This feature will allow you to optimize portfolio allocation across multiple stocks
24
+ using various methods including Maximum Sharpe ratio, Minimum Variance, and Risk Parity.
25
+ </p>
26
+ <div style="margin-top: 2rem;">
27
+ <%= link_to root_path, class: "btn btn-primary" do %>
28
+ <i class="fas fa-home"></i> Back to Home
29
+ <% end %>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ APP_PATH = File.expand_path('../config/application', __dir__)
5
+ require_relative '../config/boot'
6
+ require 'rails/commands'