sqa 0.0.24 → 0.0.32

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 (203) 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 +95 -0
  6. data/CLAUDE.md +674 -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 +839 -265
  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 +1114 -0
  31. data/docs/api/index.md +126 -0
  32. data/docs/assets/css/custom.css +88 -0
  33. data/docs/assets/images/sqa.jpg +0 -0
  34. data/docs/assets/js/mathjax.js +18 -0
  35. data/docs/concepts/index.md +60 -0
  36. data/docs/contributing/index.md +60 -0
  37. data/docs/data-sources/index.md +66 -0
  38. data/docs/data_frame.md +316 -97
  39. data/docs/factors_that_impact_price.md +26 -0
  40. data/docs/finviz.md +11 -0
  41. data/docs/fx_pro_bit.md +25 -0
  42. data/docs/genetic_programming.md +104 -0
  43. data/docs/getting-started/index.md +107 -0
  44. data/docs/getting-started/installation.md +229 -0
  45. data/docs/getting-started/quick-start.md +244 -0
  46. data/docs/i_gotta_an_idea.md +22 -0
  47. data/docs/index.md +161 -0
  48. data/docs/indicators/index.md +97 -0
  49. data/docs/indicators.md +110 -24
  50. data/docs/options.md +8 -0
  51. data/docs/strategies/bollinger-bands.md +146 -0
  52. data/docs/strategies/consensus.md +64 -0
  53. data/docs/strategies/custom.md +310 -0
  54. data/docs/strategies/ema.md +53 -0
  55. data/docs/strategies/index.md +92 -0
  56. data/docs/strategies/kbs.md +164 -0
  57. data/docs/strategies/macd.md +96 -0
  58. data/docs/strategies/market-profile.md +54 -0
  59. data/docs/strategies/mean-reversion.md +58 -0
  60. data/docs/strategies/rsi.md +95 -0
  61. data/docs/strategies/sma.md +55 -0
  62. data/docs/strategies/stochastic.md +63 -0
  63. data/docs/strategies/volume-breakout.md +54 -0
  64. data/docs/tags.md +7 -0
  65. data/examples/README.md +354 -0
  66. data/examples/advanced_features_example.rb +350 -0
  67. data/examples/fpop_analysis_example.rb +191 -0
  68. data/examples/genetic_programming_example.rb +148 -0
  69. data/examples/kbs_strategy_example.rb +208 -0
  70. data/examples/pattern_context_example.rb +300 -0
  71. data/examples/rails_app/Gemfile +34 -0
  72. data/examples/rails_app/README.md +416 -0
  73. data/examples/rails_app/app/assets/javascripts/application.js +107 -0
  74. data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
  75. data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
  76. data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
  77. data/examples/rails_app/app/controllers/application_controller.rb +22 -0
  78. data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
  79. data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
  80. data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
  81. data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
  82. data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
  83. data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
  84. data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
  85. data/examples/rails_app/app/views/errors/show.html.erb +17 -0
  86. data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
  87. data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
  88. data/examples/rails_app/bin/rails +6 -0
  89. data/examples/rails_app/config/application.rb +45 -0
  90. data/examples/rails_app/config/boot.rb +5 -0
  91. data/examples/rails_app/config/database.yml +18 -0
  92. data/examples/rails_app/config/environment.rb +11 -0
  93. data/examples/rails_app/config/routes.rb +26 -0
  94. data/examples/rails_app/config.ru +8 -0
  95. data/examples/realtime_stream_example.rb +274 -0
  96. data/examples/sinatra_app/Gemfile +42 -0
  97. data/examples/sinatra_app/Gemfile.lock +268 -0
  98. data/examples/sinatra_app/QUICKSTART.md +169 -0
  99. data/examples/sinatra_app/README.md +471 -0
  100. data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +90 -0
  101. data/examples/sinatra_app/TROUBLESHOOTING.md +95 -0
  102. data/examples/sinatra_app/app.rb +404 -0
  103. data/examples/sinatra_app/config.ru +5 -0
  104. data/examples/sinatra_app/public/css/style.css +723 -0
  105. data/examples/sinatra_app/public/debug_macd.html +82 -0
  106. data/examples/sinatra_app/public/js/app.js +107 -0
  107. data/examples/sinatra_app/start.sh +53 -0
  108. data/examples/sinatra_app/views/analyze.erb +306 -0
  109. data/examples/sinatra_app/views/backtest.erb +325 -0
  110. data/examples/sinatra_app/views/dashboard.erb +831 -0
  111. data/examples/sinatra_app/views/error.erb +58 -0
  112. data/examples/sinatra_app/views/index.erb +118 -0
  113. data/examples/sinatra_app/views/layout.erb +61 -0
  114. data/examples/sinatra_app/views/portfolio.erb +43 -0
  115. data/examples/strategy_generator_example.rb +346 -0
  116. data/hsa_portfolio.csv +11 -0
  117. data/justfile +0 -0
  118. data/lib/api/alpha_vantage_api.rb +462 -0
  119. data/lib/sqa/backtest.rb +329 -0
  120. data/lib/sqa/data_frame/alpha_vantage.rb +51 -63
  121. data/lib/sqa/data_frame/data.rb +92 -0
  122. data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
  123. data/lib/sqa/data_frame.rb +154 -243
  124. data/lib/sqa/ensemble.rb +359 -0
  125. data/lib/sqa/fpop.rb +199 -0
  126. data/lib/sqa/gp.rb +259 -0
  127. data/lib/sqa/indicator.rb +16 -6
  128. data/lib/sqa/init.rb +15 -8
  129. data/lib/sqa/market_regime.rb +240 -0
  130. data/lib/sqa/multi_timeframe.rb +379 -0
  131. data/lib/sqa/pattern_matcher.rb +497 -0
  132. data/lib/sqa/portfolio.rb +260 -6
  133. data/lib/sqa/portfolio_optimizer.rb +377 -0
  134. data/lib/sqa/risk_manager.rb +442 -0
  135. data/lib/sqa/seasonal_analyzer.rb +209 -0
  136. data/lib/sqa/sector_analyzer.rb +300 -0
  137. data/lib/sqa/stock.rb +131 -127
  138. data/lib/sqa/strategy/bollinger_bands.rb +42 -0
  139. data/lib/sqa/strategy/consensus.rb +5 -2
  140. data/lib/sqa/strategy/kbs_strategy.rb +470 -0
  141. data/lib/sqa/strategy/macd.rb +46 -0
  142. data/lib/sqa/strategy/mp.rb +1 -1
  143. data/lib/sqa/strategy/stochastic.rb +60 -0
  144. data/lib/sqa/strategy/volume_breakout.rb +57 -0
  145. data/lib/sqa/strategy.rb +5 -0
  146. data/lib/sqa/strategy_generator.rb +947 -0
  147. data/lib/sqa/stream.rb +361 -0
  148. data/lib/sqa/ticker.rb +9 -2
  149. data/lib/sqa/version.rb +1 -7
  150. data/lib/sqa.rb +35 -20
  151. data/main.just +81 -0
  152. data/mkdocs.yml +252 -0
  153. data/trace.log +0 -0
  154. metadata +265 -69
  155. data/bin/sqa +0 -6
  156. data/docs/alpha_vantage_technical_indicators.md +0 -62
  157. data/docs/average_true_range.md +0 -9
  158. data/docs/bollinger_bands.md +0 -15
  159. data/docs/candlestick_pattern_recognizer.md +0 -4
  160. data/docs/donchian_channel.md +0 -5
  161. data/docs/double_top_bottom_pattern.md +0 -3
  162. data/docs/exponential_moving_average.md +0 -19
  163. data/docs/fibonacci_retracement.md +0 -30
  164. data/docs/head_and_shoulders_pattern.md +0 -3
  165. data/docs/market_profile.md +0 -4
  166. data/docs/momentum.md +0 -19
  167. data/docs/moving_average_convergence_divergence.md +0 -23
  168. data/docs/peaks_and_valleys.md +0 -11
  169. data/docs/relative_strength_index.md +0 -6
  170. data/docs/simple_moving_average.md +0 -8
  171. data/docs/stochastic_oscillator.md +0 -4
  172. data/docs/ta_lib.md +0 -160
  173. data/docs/true_range.md +0 -12
  174. data/lib/patches/dry-cli.rb +0 -228
  175. data/lib/sqa/activity.rb +0 -10
  176. data/lib/sqa/cli.rb +0 -62
  177. data/lib/sqa/commands/analysis.rb +0 -309
  178. data/lib/sqa/commands/base.rb +0 -139
  179. data/lib/sqa/commands/web.rb +0 -199
  180. data/lib/sqa/commands.rb +0 -22
  181. data/lib/sqa/constants.rb +0 -23
  182. data/lib/sqa/indicator/average_true_range.rb +0 -33
  183. data/lib/sqa/indicator/bollinger_bands.rb +0 -28
  184. data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
  185. data/lib/sqa/indicator/donchian_channel.rb +0 -29
  186. data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
  187. data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
  188. data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
  189. data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
  190. data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
  191. data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
  192. data/lib/sqa/indicator/market_profile.rb +0 -32
  193. data/lib/sqa/indicator/mean_reversion.rb +0 -37
  194. data/lib/sqa/indicator/momentum.rb +0 -28
  195. data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
  196. data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
  197. data/lib/sqa/indicator/predict_next_value.rb +0 -202
  198. data/lib/sqa/indicator/relative_strength_index.rb +0 -47
  199. data/lib/sqa/indicator/simple_moving_average.rb +0 -24
  200. data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
  201. data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
  202. data/lib/sqa/indicator/true_range.rb +0 -39
  203. data/lib/sqa/trade.rb +0 -26
@@ -0,0 +1,831 @@
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
+ <button onclick="location.href='/analyze/<%= @ticker %>'" class="btn btn-secondary">
12
+ <i class="fas fa-analytics"></i> Analysis
13
+ </button>
14
+ <button onclick="location.href='/backtest/<%= @ticker %>'" class="btn btn-secondary">
15
+ <i class="fas fa-history"></i> Backtest
16
+ </button>
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
+ <!-- Time Period Selector -->
46
+ <div class="period-selector">
47
+ <label>Time Period:</label>
48
+ <div class="period-buttons">
49
+ <button onclick="updatePeriod('30d')" class="btn-period" data-period="30d">30 Days</button>
50
+ <button onclick="updatePeriod('60d')" class="btn-period" data-period="60d">60 Days</button>
51
+ <button onclick="updatePeriod('90d')" class="btn-period active" data-period="90d">90 Days</button>
52
+ <button onclick="updatePeriod('1q')" class="btn-period" data-period="1q">1 Quarter</button>
53
+ <button onclick="updatePeriod('2q')" class="btn-period" data-period="2q">2 Quarters</button>
54
+ <button onclick="updatePeriod('3q')" class="btn-period" data-period="3q">3 Quarters</button>
55
+ <button onclick="updatePeriod('4q')" class="btn-period" data-period="4q">4 Quarters</button>
56
+ <button onclick="updatePeriod('all')" class="btn-period" data-period="all">All Data</button>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- Main Price Chart -->
61
+ <div class="chart-container">
62
+ <div class="chart-header">
63
+ <h2><i class="fas fa-chart-candlestick"></i> Price Chart</h2>
64
+ <div class="chart-controls">
65
+ <button onclick="updateChartType('candlestick')" class="btn-small active" data-chart="candlestick">
66
+ Candlestick
67
+ </button>
68
+ <button onclick="updateChartType('line')" class="btn-small" data-chart="line">
69
+ Line
70
+ </button>
71
+ </div>
72
+ </div>
73
+ <div id="priceChart" class="chart"></div>
74
+ </div>
75
+
76
+ <!-- Volume Chart -->
77
+ <div class="chart-container">
78
+ <div class="chart-header">
79
+ <h2><i class="fas fa-chart-bar"></i> Volume</h2>
80
+ </div>
81
+ <div id="volumeChart" class="chart"></div>
82
+ </div>
83
+
84
+ <!-- Technical Indicators Grid -->
85
+ <div class="indicators-grid">
86
+ <!-- RSI Chart -->
87
+ <div class="chart-container">
88
+ <div class="chart-header">
89
+ <h3><i class="fas fa-wave-square"></i> RSI (14)</h3>
90
+ </div>
91
+ <div id="rsiChart" class="chart chart-small"></div>
92
+ </div>
93
+
94
+ <!-- MACD Chart -->
95
+ <div class="chart-container">
96
+ <div class="chart-header">
97
+ <h3><i class="fas fa-signal"></i> MACD</h3>
98
+ </div>
99
+ <div id="macdChart" class="chart chart-small"></div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- Strategy Comparison -->
104
+ <div class="chart-container">
105
+ <div class="chart-header">
106
+ <h2><i class="fas fa-trophy"></i> Strategy Comparison</h2>
107
+ <button onclick="runStrategyComparison()" class="btn btn-primary">
108
+ <i class="fas fa-play"></i> Compare Strategies
109
+ </button>
110
+ </div>
111
+ <div id="strategyResults" class="strategy-results">
112
+ <p class="hint">Click "Compare Strategies" to see backtest results for different trading strategies.</p>
113
+ </div>
114
+ </div>
115
+ </div>
116
+
117
+ <script>
118
+ const ticker = '<%= @ticker %>';
119
+ let stockData = null;
120
+ let indicatorData = null;
121
+ let currentChartType = 'candlestick';
122
+ let currentPeriod = '90d'; // Default to 90 days to avoid performance issues
123
+
124
+ // ApexCharts instances
125
+ let priceChart = null;
126
+ let volumeChart = null;
127
+ let rsiChart = null;
128
+ let macdChart = null;
129
+
130
+ // Load data when page loads
131
+ document.addEventListener('DOMContentLoaded', async function() {
132
+ await loadStockData();
133
+ await loadIndicators();
134
+ await loadAnalysis();
135
+ });
136
+
137
+ async function loadStockData() {
138
+ try {
139
+ const response = await fetch(`/api/stock/${ticker}?period=${currentPeriod}`);
140
+ stockData = await response.json();
141
+
142
+ // Update price info
143
+ document.querySelector('.current-price').textContent = `$${stockData.current_price.toFixed(2)}`;
144
+
145
+ const changeClass = stockData.change >= 0 ? 'positive' : 'negative';
146
+ const changeSign = stockData.change >= 0 ? '+' : '';
147
+ document.querySelector('.price-change').className = `price-change ${changeClass}`;
148
+ document.querySelector('.price-change').textContent =
149
+ `${changeSign}${stockData.change.toFixed(2)} (${changeSign}${stockData.change_percent.toFixed(2)}%)`;
150
+
151
+ // Update metrics
152
+ document.getElementById('high52w').textContent = `$${stockData.high_52w.toFixed(2)}`;
153
+ document.getElementById('low52w').textContent = `$${stockData.low_52w.toFixed(2)}`;
154
+
155
+ // Render charts
156
+ renderPriceChart();
157
+ renderVolumeChart();
158
+ } catch (error) {
159
+ console.error('Error loading stock data:', error);
160
+ alert('Failed to load stock data. Please try again.');
161
+ }
162
+ }
163
+
164
+ async function loadIndicators() {
165
+ try {
166
+ const response = await fetch(`/api/indicators/${ticker}?period=${currentPeriod}`);
167
+ const data = await response.json();
168
+
169
+ // Check if API returned an error
170
+ if (data.error) {
171
+ console.warn('Indicators not available:', data.error);
172
+ document.getElementById('currentRSI').textContent = 'N/A';
173
+ document.getElementById('rsiSignal').textContent = 'TA-Lib not installed';
174
+ document.getElementById('rsiSignal').className = 'metric-signal';
175
+ return;
176
+ }
177
+
178
+ indicatorData = data;
179
+
180
+ // Update RSI metric
181
+ const currentRSI = indicatorData.rsi[indicatorData.rsi.length - 1];
182
+ document.getElementById('currentRSI').textContent = currentRSI.toFixed(2);
183
+
184
+ let rsiSignal = '';
185
+ let rsiClass = '';
186
+ if (currentRSI < 30) {
187
+ rsiSignal = 'Oversold';
188
+ rsiClass = 'signal-buy';
189
+ } else if (currentRSI > 70) {
190
+ rsiSignal = 'Overbought';
191
+ rsiClass = 'signal-sell';
192
+ } else {
193
+ rsiSignal = 'Neutral';
194
+ rsiClass = 'signal-neutral';
195
+ }
196
+ const rsiSignalEl = document.getElementById('rsiSignal');
197
+ rsiSignalEl.textContent = rsiSignal;
198
+ rsiSignalEl.className = `metric-signal ${rsiClass}`;
199
+
200
+ // Render indicator charts
201
+ renderRSIChart();
202
+ renderMACDChart();
203
+ } catch (error) {
204
+ console.error('Error loading indicators:', error);
205
+ document.getElementById('currentRSI').textContent = 'Error';
206
+ document.getElementById('rsiSignal').textContent = 'Failed to load';
207
+ }
208
+ }
209
+
210
+ async function loadAnalysis() {
211
+ try {
212
+ const response = await fetch(`/api/analyze/${ticker}`);
213
+ const analysis = await response.json();
214
+
215
+ // Update market regime
216
+ const regime = analysis.regime.type.toUpperCase();
217
+ const regimeEl = document.getElementById('marketRegime');
218
+ regimeEl.textContent = regime;
219
+
220
+ let regimeClass = '';
221
+ if (regime === 'BULL') regimeClass = 'signal-buy';
222
+ else if (regime === 'BEAR') regimeClass = 'signal-sell';
223
+ else regimeClass = 'signal-neutral';
224
+ regimeEl.className = `metric-value ${regimeClass}`;
225
+
226
+ const regimeDetail = document.getElementById('regimeDetail');
227
+ const strengthText = typeof analysis.regime.strength === 'number'
228
+ ? analysis.regime.strength.toFixed(2)
229
+ : analysis.regime.strength || 'N/A';
230
+ regimeDetail.textContent = `${analysis.regime.volatility || 'unknown'} volatility, ${strengthText} strength`;
231
+ regimeDetail.className = 'metric-signal';
232
+ } catch (error) {
233
+ console.error('Error loading analysis:', error);
234
+ }
235
+ }
236
+
237
+ function renderPriceChart() {
238
+ // Destroy existing chart if it exists
239
+ if (priceChart) {
240
+ priceChart.destroy();
241
+ }
242
+
243
+ let options;
244
+
245
+ if (currentChartType === 'candlestick') {
246
+ // Prepare candlestick data
247
+ const candleData = stockData.dates.map((date, i) => ({
248
+ x: new Date(date),
249
+ y: [stockData.open[i], stockData.high[i], stockData.low[i], stockData.close[i]]
250
+ }));
251
+
252
+ options = {
253
+ series: [{
254
+ name: ticker,
255
+ data: candleData
256
+ }],
257
+ chart: {
258
+ type: 'candlestick',
259
+ height: 500,
260
+ group: 'stock-charts',
261
+ background: '#151b3d',
262
+ foreColor: '#e8eaf6',
263
+ toolbar: {
264
+ show: true,
265
+ tools: {
266
+ download: true,
267
+ zoom: true,
268
+ zoomin: true,
269
+ zoomout: true,
270
+ pan: true,
271
+ reset: true
272
+ }
273
+ },
274
+ zoom: {
275
+ enabled: true,
276
+ type: 'x'
277
+ }
278
+ },
279
+ dataLabels: {
280
+ enabled: false // Disable data labels for performance
281
+ },
282
+ plotOptions: {
283
+ candlestick: {
284
+ colors: {
285
+ upward: '#00ff88',
286
+ downward: '#ff3366'
287
+ }
288
+ }
289
+ },
290
+ xaxis: {
291
+ type: 'datetime',
292
+ labels: {
293
+ style: {
294
+ colors: '#9fa8da'
295
+ }
296
+ }
297
+ },
298
+ yaxis: {
299
+ tooltip: {
300
+ enabled: true
301
+ },
302
+ labels: {
303
+ style: {
304
+ colors: '#9fa8da'
305
+ },
306
+ formatter: (val) => '$' + val.toFixed(2)
307
+ }
308
+ },
309
+ grid: {
310
+ borderColor: '#2a3154',
311
+ strokeDashArray: 3
312
+ },
313
+ theme: {
314
+ mode: 'dark'
315
+ },
316
+ tooltip: {
317
+ theme: 'dark'
318
+ }
319
+ };
320
+ } else {
321
+ // Line chart with moving averages
322
+ const series = [{
323
+ name: ticker,
324
+ data: stockData.dates.map((date, i) => [new Date(date).getTime(), stockData.close[i]])
325
+ }];
326
+
327
+ // Add moving averages if available
328
+ if (indicatorData) {
329
+ series.push({
330
+ name: 'SMA 20',
331
+ data: indicatorData.dates.map((date, i) => [new Date(date).getTime(), indicatorData.sma_20[i]])
332
+ });
333
+ series.push({
334
+ name: 'SMA 50',
335
+ data: indicatorData.dates.map((date, i) => [new Date(date).getTime(), indicatorData.sma_50[i]])
336
+ });
337
+ }
338
+
339
+ options = {
340
+ series: series,
341
+ chart: {
342
+ type: 'line',
343
+ height: 500,
344
+ group: 'stock-charts',
345
+ background: '#151b3d',
346
+ foreColor: '#e8eaf6',
347
+ toolbar: {
348
+ show: true,
349
+ tools: {
350
+ download: true,
351
+ zoom: true,
352
+ zoomin: true,
353
+ zoomout: true,
354
+ pan: true,
355
+ reset: true
356
+ }
357
+ },
358
+ zoom: {
359
+ enabled: true,
360
+ type: 'x'
361
+ }
362
+ },
363
+ colors: ['#00d4ff', '#ffaa00', '#ff66ff'],
364
+ stroke: {
365
+ width: [3, 2, 2],
366
+ curve: 'smooth'
367
+ },
368
+ xaxis: {
369
+ type: 'datetime',
370
+ labels: {
371
+ style: {
372
+ colors: '#9fa8da'
373
+ }
374
+ }
375
+ },
376
+ yaxis: {
377
+ labels: {
378
+ style: {
379
+ colors: '#9fa8da'
380
+ },
381
+ formatter: (val) => '$' + val.toFixed(2)
382
+ }
383
+ },
384
+ grid: {
385
+ borderColor: '#2a3154',
386
+ strokeDashArray: 3
387
+ },
388
+ legend: {
389
+ position: 'top',
390
+ horizontalAlign: 'left',
391
+ fontSize: '14px',
392
+ labels: {
393
+ colors: '#e8eaf6'
394
+ }
395
+ },
396
+ theme: {
397
+ mode: 'dark'
398
+ },
399
+ tooltip: {
400
+ theme: 'dark',
401
+ shared: true,
402
+ intersect: false
403
+ }
404
+ };
405
+ }
406
+
407
+ priceChart = new ApexCharts(document.querySelector("#priceChart"), options);
408
+ priceChart.render();
409
+ }
410
+
411
+ function renderVolumeChart() {
412
+ // Destroy existing chart if it exists
413
+ if (volumeChart) {
414
+ volumeChart.destroy();
415
+ }
416
+
417
+ // Prepare volume data with timestamps and colors
418
+ const volumeData = stockData.dates.map((date, i) => ({
419
+ x: new Date(date).getTime(),
420
+ y: stockData.volume[i],
421
+ fillColor: i === 0 ? '#00d4ff' : (stockData.close[i] >= stockData.close[i - 1] ? '#00ff88' : '#ff3366')
422
+ }));
423
+
424
+ const options = {
425
+ series: [{
426
+ name: 'Volume',
427
+ data: volumeData
428
+ }],
429
+ chart: {
430
+ type: 'bar',
431
+ height: 300,
432
+ group: 'stock-charts',
433
+ background: '#151b3d',
434
+ foreColor: '#e8eaf6',
435
+ toolbar: {
436
+ show: true,
437
+ tools: {
438
+ download: true,
439
+ zoom: true,
440
+ zoomin: true,
441
+ zoomout: true,
442
+ pan: true,
443
+ reset: true
444
+ }
445
+ },
446
+ zoom: {
447
+ enabled: true,
448
+ type: 'x'
449
+ }
450
+ },
451
+ dataLabels: {
452
+ enabled: false // Disable data labels for performance
453
+ },
454
+ plotOptions: {
455
+ bar: {
456
+ columnWidth: '95%',
457
+ colors: {
458
+ ranges: []
459
+ }
460
+ }
461
+ },
462
+ xaxis: {
463
+ type: 'datetime',
464
+ labels: {
465
+ style: {
466
+ colors: '#9fa8da'
467
+ }
468
+ }
469
+ },
470
+ yaxis: {
471
+ labels: {
472
+ style: {
473
+ colors: '#9fa8da'
474
+ },
475
+ formatter: (val) => {
476
+ if (val >= 1e9) return (val / 1e9).toFixed(1) + 'B';
477
+ if (val >= 1e6) return (val / 1e6).toFixed(1) + 'M';
478
+ if (val >= 1e3) return (val / 1e3).toFixed(1) + 'K';
479
+ return val.toFixed(0);
480
+ }
481
+ }
482
+ },
483
+ grid: {
484
+ borderColor: '#2a3154',
485
+ strokeDashArray: 3
486
+ },
487
+ theme: {
488
+ mode: 'dark'
489
+ },
490
+ tooltip: {
491
+ theme: 'dark',
492
+ y: {
493
+ formatter: (val) => val.toLocaleString()
494
+ }
495
+ }
496
+ };
497
+
498
+ volumeChart = new ApexCharts(document.querySelector("#volumeChart"), options);
499
+ volumeChart.render();
500
+ }
501
+
502
+ function renderRSIChart() {
503
+ // Destroy existing chart if it exists
504
+ if (rsiChart) {
505
+ rsiChart.destroy();
506
+ }
507
+
508
+ // Skip if indicators not available
509
+ if (!indicatorData || !indicatorData.rsi) {
510
+ document.querySelector("#rsiChart").innerHTML = '<p style="text-align: center; padding: 40px; color: #9fa8da;">Technical indicators not available (TA-Lib required)</p>';
511
+ return;
512
+ }
513
+
514
+ // Filter out null/NaN/undefined values (RSI has warmup period)
515
+ const validRSIIndices = [];
516
+ for (let i = 0; i < indicatorData.rsi.length; i++) {
517
+ if (indicatorData.dates[i] !== null &&
518
+ indicatorData.dates[i] !== undefined &&
519
+ indicatorData.rsi[i] !== null &&
520
+ indicatorData.rsi[i] !== undefined &&
521
+ !isNaN(indicatorData.rsi[i])) {
522
+ validRSIIndices.push(i);
523
+ }
524
+ }
525
+
526
+ // If no valid data, show message
527
+ if (validRSIIndices.length === 0) {
528
+ document.querySelector("#rsiChart").innerHTML = '<p style="text-align: center; padding: 40px; color: #9fa8da;">No valid RSI data available (warmup period or data issue)</p>';
529
+ return;
530
+ }
531
+
532
+ const options = {
533
+ series: [
534
+ {
535
+ name: 'RSI',
536
+ data: validRSIIndices.map(i => [new Date(indicatorData.dates[i]).getTime(), indicatorData.rsi[i]])
537
+ },
538
+ {
539
+ name: 'Oversold (30)',
540
+ data: validRSIIndices.map(i => [new Date(indicatorData.dates[i]).getTime(), 30])
541
+ },
542
+ {
543
+ name: 'Overbought (70)',
544
+ data: validRSIIndices.map(i => [new Date(indicatorData.dates[i]).getTime(), 70])
545
+ }
546
+ ],
547
+ chart: {
548
+ type: 'line',
549
+ height: 350,
550
+ group: 'stock-charts',
551
+ background: '#151b3d',
552
+ foreColor: '#e8eaf6',
553
+ toolbar: {
554
+ show: true,
555
+ tools: {
556
+ download: true,
557
+ zoom: true,
558
+ zoomin: true,
559
+ zoomout: true,
560
+ pan: true,
561
+ reset: true
562
+ }
563
+ },
564
+ zoom: {
565
+ enabled: true,
566
+ type: 'x'
567
+ }
568
+ },
569
+ colors: ['#00d4ff', '#00ff88', '#ff3366'],
570
+ stroke: {
571
+ width: [3, 2, 2],
572
+ curve: 'smooth',
573
+ dashArray: [0, 5, 5]
574
+ },
575
+ xaxis: {
576
+ type: 'datetime',
577
+ labels: {
578
+ style: {
579
+ colors: '#9fa8da'
580
+ }
581
+ }
582
+ },
583
+ yaxis: {
584
+ min: 0,
585
+ max: 100,
586
+ labels: {
587
+ style: {
588
+ colors: '#9fa8da'
589
+ }
590
+ }
591
+ },
592
+ grid: {
593
+ borderColor: '#2a3154',
594
+ strokeDashArray: 3
595
+ },
596
+ legend: {
597
+ position: 'top',
598
+ horizontalAlign: 'left',
599
+ fontSize: '14px',
600
+ labels: {
601
+ colors: '#e8eaf6'
602
+ }
603
+ },
604
+ theme: {
605
+ mode: 'dark'
606
+ },
607
+ tooltip: {
608
+ theme: 'dark',
609
+ shared: true,
610
+ intersect: false
611
+ },
612
+ annotations: {
613
+ yaxis: [
614
+ {
615
+ y: 30,
616
+ borderColor: '#00ff88',
617
+ fillColor: '#00ff88',
618
+ opacity: 0.1
619
+ },
620
+ {
621
+ y: 70,
622
+ borderColor: '#ff3366',
623
+ fillColor: '#ff3366',
624
+ opacity: 0.1
625
+ }
626
+ ]
627
+ }
628
+ };
629
+
630
+ rsiChart = new ApexCharts(document.querySelector("#rsiChart"), options);
631
+ rsiChart.render();
632
+ }
633
+
634
+ function renderMACDChart() {
635
+ // Destroy existing chart if it exists
636
+ if (macdChart) {
637
+ macdChart.destroy();
638
+ }
639
+
640
+ // Skip if indicators not available
641
+ if (!indicatorData || !indicatorData.macd) {
642
+ document.querySelector("#macdChart").innerHTML = '<p style="text-align: center; padding: 40px; color: #9fa8da;">Technical indicators not available (TA-Lib required)</p>';
643
+ return;
644
+ }
645
+
646
+ // Filter out null/NaN/undefined values (MACD has warmup period)
647
+ const validIndices = [];
648
+ for (let i = 0; i < indicatorData.macd.length; i++) {
649
+ if (indicatorData.dates[i] !== null &&
650
+ indicatorData.dates[i] !== undefined &&
651
+ indicatorData.macd[i] !== null &&
652
+ indicatorData.macd[i] !== undefined &&
653
+ indicatorData.macd_signal[i] !== null &&
654
+ indicatorData.macd_signal[i] !== undefined &&
655
+ indicatorData.macd_hist[i] !== null &&
656
+ indicatorData.macd_hist[i] !== undefined &&
657
+ !isNaN(indicatorData.macd[i]) &&
658
+ !isNaN(indicatorData.macd_signal[i]) &&
659
+ !isNaN(indicatorData.macd_hist[i])) {
660
+ validIndices.push(i);
661
+ }
662
+ }
663
+
664
+ // If no valid data, show message
665
+ if (validIndices.length === 0) {
666
+ document.querySelector("#macdChart").innerHTML = '<p style="text-align: center; padding: 40px; color: #9fa8da;">No valid MACD data available (warmup period or data issue)</p>';
667
+ return;
668
+ }
669
+
670
+ // Prepare histogram data with colors (only valid values)
671
+ const histData = validIndices.map(i => ({
672
+ x: new Date(indicatorData.dates[i]).getTime(),
673
+ y: indicatorData.macd_hist[i],
674
+ fillColor: indicatorData.macd_hist[i] >= 0 ? '#00ff88' : '#ff3366'
675
+ }));
676
+
677
+ const options = {
678
+ series: [
679
+ {
680
+ name: 'Histogram',
681
+ type: 'bar',
682
+ data: histData
683
+ },
684
+ {
685
+ name: 'MACD',
686
+ type: 'line',
687
+ data: validIndices.map(i => [new Date(indicatorData.dates[i]).getTime(), indicatorData.macd[i]])
688
+ },
689
+ {
690
+ name: 'Signal',
691
+ type: 'line',
692
+ data: validIndices.map(i => [new Date(indicatorData.dates[i]).getTime(), indicatorData.macd_signal[i]])
693
+ }
694
+ ],
695
+ chart: {
696
+ height: 350,
697
+ group: 'stock-charts',
698
+ background: '#151b3d',
699
+ foreColor: '#e8eaf6',
700
+ toolbar: {
701
+ show: true,
702
+ tools: {
703
+ download: true,
704
+ zoom: true,
705
+ zoomin: true,
706
+ zoomout: true,
707
+ pan: true,
708
+ reset: true
709
+ }
710
+ },
711
+ zoom: {
712
+ enabled: true,
713
+ type: 'x'
714
+ }
715
+ },
716
+ plotOptions: {
717
+ bar: {
718
+ columnWidth: '80%'
719
+ }
720
+ },
721
+ colors: ['#00ff88', '#00d4ff', '#ffaa00'],
722
+ stroke: {
723
+ width: [0, 3, 3],
724
+ curve: 'smooth'
725
+ },
726
+ xaxis: {
727
+ type: 'datetime',
728
+ labels: {
729
+ style: {
730
+ colors: '#9fa8da'
731
+ }
732
+ }
733
+ },
734
+ yaxis: {
735
+ labels: {
736
+ style: {
737
+ colors: '#9fa8da'
738
+ },
739
+ formatter: (val) => val.toFixed(2)
740
+ }
741
+ },
742
+ grid: {
743
+ borderColor: '#2a3154',
744
+ strokeDashArray: 3
745
+ },
746
+ legend: {
747
+ position: 'top',
748
+ horizontalAlign: 'left',
749
+ fontSize: '14px',
750
+ labels: {
751
+ colors: '#e8eaf6'
752
+ }
753
+ },
754
+ theme: {
755
+ mode: 'dark'
756
+ },
757
+ tooltip: {
758
+ theme: 'dark',
759
+ shared: true,
760
+ intersect: false
761
+ }
762
+ };
763
+
764
+ macdChart = new ApexCharts(document.querySelector("#macdChart"), options);
765
+ macdChart.render();
766
+ }
767
+
768
+ function updateChartType(type) {
769
+ currentChartType = type;
770
+
771
+ // Update button states
772
+ document.querySelectorAll('[data-chart]').forEach(btn => {
773
+ btn.classList.remove('active');
774
+ });
775
+ document.querySelector(`[data-chart="${type}"]`).classList.add('active');
776
+
777
+ // Re-render chart
778
+ renderPriceChart();
779
+ }
780
+
781
+ async function runStrategyComparison() {
782
+ const resultsDiv = document.getElementById('strategyResults');
783
+ resultsDiv.innerHTML = '<p class="loading"><i class="fas fa-spinner fa-spin"></i> Running backtests...</p>';
784
+
785
+ try {
786
+ const response = await fetch(`/api/compare/${ticker}`, { method: 'POST' });
787
+ const results = await response.json();
788
+
789
+ let html = '<table class="results-table"><thead><tr>';
790
+ html += '<th>Strategy</th><th>Return</th><th>Sharpe</th><th>Drawdown</th><th>Win Rate</th><th>Trades</th>';
791
+ html += '</tr></thead><tbody>';
792
+
793
+ results.forEach((result, i) => {
794
+ const returnClass = result.return >= 0 ? 'positive' : 'negative';
795
+ const rank = i === 0 ? '<i class="fas fa-trophy" style="color: #FFD700;"></i> ' : '';
796
+ html += `<tr>
797
+ <td>${rank}${result.strategy}</td>
798
+ <td class="${returnClass}">${result.return.toFixed(2)}%</td>
799
+ <td>${result.sharpe.toFixed(2)}</td>
800
+ <td>${result.drawdown.toFixed(2)}%</td>
801
+ <td>${result.win_rate.toFixed(2)}%</td>
802
+ <td>${result.trades}</td>
803
+ </tr>`;
804
+ });
805
+
806
+ html += '</tbody></table>';
807
+ resultsDiv.innerHTML = html;
808
+ } catch (error) {
809
+ console.error('Error comparing strategies:', error);
810
+ resultsDiv.innerHTML = '<p class="error">Failed to compare strategies. Please try again.</p>';
811
+ }
812
+ }
813
+
814
+ async function updatePeriod(period) {
815
+ currentPeriod = period;
816
+
817
+ // Update button states
818
+ document.querySelectorAll('[data-period]').forEach(btn => {
819
+ btn.classList.remove('active');
820
+ });
821
+ document.querySelector(`[data-period="${period}"]`).classList.add('active');
822
+
823
+ // Reload data with new period
824
+ await loadStockData();
825
+ await loadIndicators();
826
+ }
827
+
828
+ function refreshData() {
829
+ location.reload();
830
+ }
831
+ </script>