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,107 @@
1
+ // Global functions for modal and navigation
2
+
3
+ function showTickerModal() {
4
+ document.getElementById('tickerModal').style.display = 'block';
5
+ document.getElementById('tickerInput').focus();
6
+ }
7
+
8
+ function closeTickerModal() {
9
+ document.getElementById('tickerModal').style.display = 'none';
10
+ }
11
+
12
+ function searchTicker(event) {
13
+ event.preventDefault();
14
+
15
+ const input = event.target.querySelector('input[type="text"]');
16
+ const ticker = input.value.trim().toUpperCase();
17
+
18
+ if (!ticker) {
19
+ alert('Please enter a stock ticker symbol');
20
+ return false;
21
+ }
22
+
23
+ // Validate ticker format (letters and optional dot)
24
+ if (!/^[A-Z]{1,5}(\.[A-Z]{1,2})?$/.test(ticker)) {
25
+ alert('Please enter a valid ticker symbol (e.g., AAPL, BRK.A)');
26
+ return false;
27
+ }
28
+
29
+ // Navigate to dashboard
30
+ window.location.href = `/dashboard/${ticker}`;
31
+ return false;
32
+ }
33
+
34
+ // Close modal when clicking outside
35
+ window.onclick = function(event) {
36
+ const modal = document.getElementById('tickerModal');
37
+ if (event.target === modal) {
38
+ closeTickerModal();
39
+ }
40
+ }
41
+
42
+ // Keyboard shortcuts
43
+ document.addEventListener('keydown', function(event) {
44
+ // Ctrl/Cmd + K to open search
45
+ if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
46
+ event.preventDefault();
47
+ showTickerModal();
48
+ }
49
+
50
+ // Escape to close modal
51
+ if (event.key === 'Escape') {
52
+ closeTickerModal();
53
+ }
54
+ });
55
+
56
+ // Utility functions
57
+ function formatCurrency(value) {
58
+ return new Intl.NumberFormat('en-US', {
59
+ style: 'currency',
60
+ currency: 'USD'
61
+ }).format(value);
62
+ }
63
+
64
+ function formatPercent(value) {
65
+ return `${value >= 0 ? '+' : ''}${value.toFixed(2)}%`;
66
+ }
67
+
68
+ function formatNumber(value) {
69
+ return new Intl.NumberFormat('en-US').format(value);
70
+ }
71
+
72
+ // Show loading indicator
73
+ function showLoading(elementId) {
74
+ const element = document.getElementById(elementId);
75
+ if (element) {
76
+ element.innerHTML = '<p class="loading"><i class="fas fa-spinner fa-spin"></i> Loading...</p>';
77
+ }
78
+ }
79
+
80
+ // Show error message
81
+ function showError(elementId, message) {
82
+ const element = document.getElementById(elementId);
83
+ if (element) {
84
+ element.innerHTML = `<p class="error"><i class="fas fa-exclamation-circle"></i> ${message}</p>`;
85
+ }
86
+ }
87
+
88
+ // Debounce function for performance
89
+ function debounce(func, wait) {
90
+ let timeout;
91
+ return function executedFunction(...args) {
92
+ const later = () => {
93
+ clearTimeout(timeout);
94
+ func(...args);
95
+ };
96
+ clearTimeout(timeout);
97
+ timeout = setTimeout(later, wait);
98
+ };
99
+ }
100
+
101
+ // Console welcome message
102
+ console.log('%cSQA Analytics', 'font-size: 24px; font-weight: bold; color: #2196F3;');
103
+ console.log('%cPowered by Ruby & Plotly.js', 'font-size: 14px; color: #666;');
104
+ console.log('');
105
+ console.log('Keyboard shortcuts:');
106
+ console.log(' Ctrl/Cmd + K: Open ticker search');
107
+ console.log(' Escape: Close modal');
@@ -0,0 +1,306 @@
1
+ <div class="dashboard">
2
+ <div class="dashboard-header">
3
+ <div class="ticker-info">
4
+ <h1><%= @ticker %> - Market Analysis</h1>
5
+ </div>
6
+ <div class="header-actions">
7
+ <button onclick="location.href='/dashboard/<%= @ticker %>'" class="btn btn-secondary">
8
+ <i class="fas fa-chart-line"></i> Dashboard
9
+ </button>
10
+ <button onclick="location.href='/backtest/<%= @ticker %>'" class="btn btn-secondary">
11
+ <i class="fas fa-history"></i> Backtest
12
+ </button>
13
+ </div>
14
+ </div>
15
+
16
+ <!-- Market Regime Analysis -->
17
+ <div class="chart-container">
18
+ <div class="chart-header">
19
+ <h2><i class="fas fa-chart-area"></i> Market Regime Analysis</h2>
20
+ </div>
21
+ <div id="regimeAnalysis" class="analysis-content">
22
+ <p class="loading"><i class="fas fa-spinner fa-spin"></i> Loading market regime data...</p>
23
+ </div>
24
+ </div>
25
+
26
+ <!-- Seasonal Pattern Analysis -->
27
+ <div class="chart-container">
28
+ <div class="chart-header">
29
+ <h2><i class="fas fa-calendar-alt"></i> Seasonal Pattern Analysis</h2>
30
+ </div>
31
+ <div id="seasonalAnalysis" class="analysis-content">
32
+ <p class="loading"><i class="fas fa-spinner fa-spin"></i> Loading seasonal data...</p>
33
+ </div>
34
+ </div>
35
+
36
+ <!-- FPOP Analysis -->
37
+ <div class="chart-container">
38
+ <div class="chart-header">
39
+ <h2><i class="fas fa-crystal-ball"></i> Future Period Analysis (FPOP)</h2>
40
+ <p class="chart-subtitle">Potential future price movements based on historical patterns</p>
41
+ </div>
42
+ <div id="fpopAnalysis" class="analysis-content">
43
+ <p class="loading"><i class="fas fa-spinner fa-spin"></i> Loading FPOP analysis...</p>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Risk Metrics -->
48
+ <div class="chart-container">
49
+ <div class="chart-header">
50
+ <h2><i class="fas fa-shield-alt"></i> Risk Metrics</h2>
51
+ </div>
52
+ <div id="riskMetrics" class="analysis-content">
53
+ <p class="loading"><i class="fas fa-spinner fa-spin"></i> Loading risk metrics...</p>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <style>
59
+ .analysis-content {
60
+ padding: 1rem 0;
61
+ }
62
+
63
+ .analysis-grid {
64
+ display: grid;
65
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
66
+ gap: 1.5rem;
67
+ margin-top: 1rem;
68
+ }
69
+
70
+ .analysis-card {
71
+ background: var(--light-bg);
72
+ padding: 1.5rem;
73
+ border-radius: 8px;
74
+ border-left: 4px solid var(--primary-color);
75
+ }
76
+
77
+ .analysis-card h3 {
78
+ font-size: 1.25rem;
79
+ margin-bottom: 1rem;
80
+ color: var(--text-primary);
81
+ }
82
+
83
+ .analysis-stat {
84
+ display: flex;
85
+ justify-content: space-between;
86
+ align-items: center;
87
+ padding: 0.5rem 0;
88
+ border-bottom: 1px solid var(--border-color);
89
+ }
90
+
91
+ .analysis-stat:last-child {
92
+ border-bottom: none;
93
+ }
94
+
95
+ .stat-label {
96
+ color: var(--text-secondary);
97
+ font-weight: 500;
98
+ }
99
+
100
+ .stat-value {
101
+ font-weight: 600;
102
+ color: var(--text-primary);
103
+ }
104
+
105
+ .fpop-table {
106
+ width: 100%;
107
+ margin-top: 1rem;
108
+ }
109
+
110
+ .fpop-table th,
111
+ .fpop-table td {
112
+ padding: 0.75rem;
113
+ text-align: left;
114
+ border-bottom: 1px solid var(--border-color);
115
+ }
116
+
117
+ .fpop-table thead {
118
+ background: var(--light-bg);
119
+ }
120
+
121
+ .chart-subtitle {
122
+ color: var(--text-secondary);
123
+ font-size: 0.875rem;
124
+ font-weight: normal;
125
+ }
126
+ </style>
127
+
128
+ <script>
129
+ const ticker = '<%= @ticker %>';
130
+
131
+ document.addEventListener('DOMContentLoaded', async function() {
132
+ await loadAnalysisData();
133
+ });
134
+
135
+ async function loadAnalysisData() {
136
+ try {
137
+ const response = await fetch(`/api/analyze/${ticker}`);
138
+ const data = await response.json();
139
+
140
+ renderRegimeAnalysis(data.regime);
141
+ renderSeasonalAnalysis(data.seasonal);
142
+ renderFPOPAnalysis(data.fpop);
143
+ renderRiskMetrics(data.risk);
144
+ } catch (error) {
145
+ console.error('Error loading analysis data:', error);
146
+ document.querySelectorAll('.analysis-content').forEach(el => {
147
+ el.innerHTML = '<p class="error">Failed to load analysis data. Please try again.</p>';
148
+ });
149
+ }
150
+ }
151
+
152
+ function renderRegimeAnalysis(regime) {
153
+ const regimeType = regime.type.toUpperCase();
154
+ const regimeClass = regimeType === 'BULL' ? 'signal-buy' :
155
+ regimeType === 'BEAR' ? 'signal-sell' : 'signal-neutral';
156
+
157
+ const html = `
158
+ <div class="analysis-grid">
159
+ <div class="analysis-card">
160
+ <h3>Current Market Regime</h3>
161
+ <div class="analysis-stat">
162
+ <span class="stat-label">Type</span>
163
+ <span class="stat-value ${regimeClass}">${regimeType}</span>
164
+ </div>
165
+ <div class="analysis-stat">
166
+ <span class="stat-label">Volatility</span>
167
+ <span class="stat-value">${regime.volatility.toUpperCase()}</span>
168
+ </div>
169
+ <div class="analysis-stat">
170
+ <span class="stat-label">Strength</span>
171
+ <span class="stat-value">${regime.strength.toFixed(2)}</span>
172
+ </div>
173
+ <div class="analysis-stat">
174
+ <span class="stat-label">Trend</span>
175
+ <span class="stat-value">${regime.trend.toFixed(2)}%</span>
176
+ </div>
177
+ </div>
178
+ <div class="analysis-card">
179
+ <h3>Interpretation</h3>
180
+ <p style="line-height: 1.8; color: var(--text-secondary);">
181
+ The stock is currently in a <strong class="${regimeClass}">${regimeType}</strong> market regime
182
+ with <strong>${regime.volatility}</strong> volatility. The regime strength of
183
+ <strong>${regime.strength.toFixed(2)}</strong> indicates how pronounced this regime is.
184
+ ${regime.type === 'bull' ? 'This is generally favorable for long positions.' :
185
+ regime.type === 'bear' ? 'Consider defensive strategies or short positions.' :
186
+ 'The market is consolidating. Wait for clearer signals.'}
187
+ </p>
188
+ </div>
189
+ </div>
190
+ `;
191
+
192
+ document.getElementById('regimeAnalysis').innerHTML = html;
193
+ }
194
+
195
+ function renderSeasonalAnalysis(seasonal) {
196
+ const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
197
+ const bestMonths = seasonal.best_months.map(m => monthNames[m]).join(', ');
198
+ const worstMonths = seasonal.worst_months.map(m => monthNames[m]).join(', ');
199
+
200
+ const html = `
201
+ <div class="analysis-grid">
202
+ <div class="analysis-card">
203
+ <h3>Monthly Patterns</h3>
204
+ <div class="analysis-stat">
205
+ <span class="stat-label">Best Months</span>
206
+ <span class="stat-value signal-buy">${bestMonths}</span>
207
+ </div>
208
+ <div class="analysis-stat">
209
+ <span class="stat-label">Worst Months</span>
210
+ <span class="stat-value signal-sell">${worstMonths}</span>
211
+ </div>
212
+ </div>
213
+ <div class="analysis-card">
214
+ <h3>Quarterly Patterns</h3>
215
+ <div class="analysis-stat">
216
+ <span class="stat-label">Best Quarters</span>
217
+ <span class="stat-value signal-buy">Q${seasonal.best_quarters.join(', Q')}</span>
218
+ </div>
219
+ <div class="analysis-stat">
220
+ <span class="stat-label">Has Seasonal Pattern</span>
221
+ <span class="stat-value">${seasonal.has_pattern ? 'Yes' : 'No'}</span>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ <p style="margin-top: 1.5rem; color: var(--text-secondary); font-style: italic;">
226
+ ${seasonal.has_pattern ?
227
+ 'This stock shows significant seasonal patterns. Consider timing trades around favorable periods.' :
228
+ 'No strong seasonal patterns detected. Seasonal effects may not be a major factor for this stock.'}
229
+ </p>
230
+ `;
231
+
232
+ document.getElementById('seasonalAnalysis').innerHTML = html;
233
+ }
234
+
235
+ function renderFPOPAnalysis(fpop) {
236
+ if (!fpop || fpop.length === 0) {
237
+ document.getElementById('fpopAnalysis').innerHTML =
238
+ '<p class="hint">No FPOP data available.</p>';
239
+ return;
240
+ }
241
+
242
+ let tableHTML = `
243
+ <table class="fpop-table">
244
+ <thead>
245
+ <tr>
246
+ <th>Direction</th>
247
+ <th>Magnitude</th>
248
+ <th>Risk</th>
249
+ <th>Interpretation</th>
250
+ </tr>
251
+ </thead>
252
+ <tbody>
253
+ `;
254
+
255
+ fpop.forEach(f => {
256
+ const dirClass = f.direction === 'UP' ? 'signal-buy' :
257
+ f.direction === 'DOWN' ? 'signal-sell' : 'signal-neutral';
258
+
259
+ tableHTML += `
260
+ <tr>
261
+ <td><span class="${dirClass}">${f.direction}</span></td>
262
+ <td>${f.magnitude.toFixed(2)}%</td>
263
+ <td>${f.risk.toFixed(2)}%</td>
264
+ <td>${f.interpretation}</td>
265
+ </tr>
266
+ `;
267
+ });
268
+
269
+ tableHTML += '</tbody></table>';
270
+
271
+ document.getElementById('fpopAnalysis').innerHTML = tableHTML;
272
+ }
273
+
274
+ function renderRiskMetrics(risk) {
275
+ const html = `
276
+ <div class="analysis-grid">
277
+ <div class="analysis-card">
278
+ <h3>Value at Risk (VaR)</h3>
279
+ <div class="analysis-stat">
280
+ <span class="stat-label">95% Confidence</span>
281
+ <span class="stat-value signal-sell">${(risk.var_95 * 100).toFixed(2)}%</span>
282
+ </div>
283
+ <p style="margin-top: 1rem; color: var(--text-secondary); font-size: 0.875rem;">
284
+ There's a 95% chance your loss will not exceed this amount on any given day.
285
+ </p>
286
+ </div>
287
+ <div class="analysis-card">
288
+ <h3>Performance Metrics</h3>
289
+ <div class="analysis-stat">
290
+ <span class="stat-label">Sharpe Ratio</span>
291
+ <span class="stat-value">${risk.sharpe_ratio.toFixed(2)}</span>
292
+ </div>
293
+ <div class="analysis-stat">
294
+ <span class="stat-label">Max Drawdown</span>
295
+ <span class="stat-value signal-sell">${(risk.max_drawdown * 100).toFixed(2)}%</span>
296
+ </div>
297
+ <p style="margin-top: 1rem; color: var(--text-secondary); font-size: 0.875rem;">
298
+ Sharpe ratio > 1 is good, > 2 is excellent. Lower drawdown indicates less risk.
299
+ </p>
300
+ </div>
301
+ </div>
302
+ `;
303
+
304
+ document.getElementById('riskMetrics').innerHTML = html;
305
+ }
306
+ </script>