sqa 0.0.32 → 0.0.38

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +154 -1
  3. data/README.md +4 -0
  4. data/Rakefile +52 -10
  5. data/docs/advanced/index.md +1 -13
  6. data/docs/api/index.md +547 -61
  7. data/docs/api-reference/alphavantageapi.md +1057 -0
  8. data/docs/api-reference/apierror.md +31 -0
  9. data/docs/api-reference/index.md +221 -0
  10. data/docs/api-reference/notimplemented.md +27 -0
  11. data/docs/api-reference/sqa.md +267 -0
  12. data/docs/api-reference/sqa_backtest.md +171 -0
  13. data/docs/api-reference/sqa_backtest_results.md +530 -0
  14. data/docs/api-reference/sqa_badparametererror.md +13 -0
  15. data/docs/api-reference/sqa_config.md +538 -0
  16. data/docs/api-reference/sqa_configurationerror.md +13 -0
  17. data/docs/api-reference/sqa_datafetcherror.md +56 -0
  18. data/docs/api-reference/sqa_dataframe.md +779 -0
  19. data/docs/api-reference/sqa_dataframe_alphavantage.md +30 -0
  20. data/docs/api-reference/sqa_dataframe_data.md +325 -0
  21. data/docs/api-reference/sqa_dataframe_yahoofinance.md +25 -0
  22. data/docs/api-reference/sqa_ensemble.md +413 -0
  23. data/docs/api-reference/sqa_fpop.md +211 -0
  24. data/docs/api-reference/sqa_geneticprogram.md +325 -0
  25. data/docs/api-reference/sqa_geneticprogram_individual.md +114 -0
  26. data/docs/api-reference/sqa_marketregime.md +212 -0
  27. data/docs/api-reference/sqa_multitimeframe.md +227 -0
  28. data/docs/api-reference/sqa_patternmatcher.md +195 -0
  29. data/docs/api-reference/sqa_pluginmanager.md +55 -0
  30. data/docs/api-reference/sqa_portfolio.md +512 -0
  31. data/docs/api-reference/sqa_portfolio_position.md +220 -0
  32. data/docs/api-reference/sqa_portfolio_trade.md +332 -0
  33. data/docs/api-reference/sqa_portfoliooptimizer.md +248 -0
  34. data/docs/api-reference/sqa_riskmanager.md +388 -0
  35. data/docs/api-reference/sqa_seasonalanalyzer.md +121 -0
  36. data/docs/api-reference/sqa_sectoranalyzer.md +163 -0
  37. data/docs/api-reference/sqa_stock.md +661 -0
  38. data/docs/api-reference/sqa_strategy.md +178 -0
  39. data/docs/api-reference/sqa_strategy_bollingerbands.md +26 -0
  40. data/docs/api-reference/sqa_strategy_common.md +29 -0
  41. data/docs/api-reference/sqa_strategy_consensus.md +129 -0
  42. data/docs/api-reference/sqa_strategy_ema.md +41 -0
  43. data/docs/api-reference/sqa_strategy_kbs.md +154 -0
  44. data/docs/api-reference/sqa_strategy_macd.md +26 -0
  45. data/docs/api-reference/sqa_strategy_mp.md +41 -0
  46. data/docs/api-reference/sqa_strategy_mr.md +41 -0
  47. data/docs/api-reference/sqa_strategy_random.md +41 -0
  48. data/docs/api-reference/sqa_strategy_rsi.md +41 -0
  49. data/docs/api-reference/sqa_strategy_sma.md +41 -0
  50. data/docs/api-reference/sqa_strategy_stochastic.md +26 -0
  51. data/docs/api-reference/sqa_strategy_volumebreakout.md +26 -0
  52. data/docs/api-reference/sqa_strategygenerator.md +298 -0
  53. data/docs/api-reference/sqa_strategygenerator_pattern.md +264 -0
  54. data/docs/api-reference/sqa_strategygenerator_patterncontext.md +326 -0
  55. data/docs/api-reference/sqa_strategygenerator_profitablepoint.md +424 -0
  56. data/docs/api-reference/sqa_stream.md +256 -0
  57. data/docs/api-reference/sqa_ticker.md +175 -0
  58. data/docs/api-reference/string.md +135 -0
  59. data/docs/assets/images/advanced-workflow.svg +89 -0
  60. data/docs/assets/images/architecture.svg +107 -0
  61. data/docs/assets/images/data-flow.svg +138 -0
  62. data/docs/assets/images/getting-started-workflow.svg +88 -0
  63. data/docs/assets/images/strategy-flow.svg +78 -0
  64. data/docs/assets/images/system-architecture.svg +150 -0
  65. data/docs/concepts/index.md +292 -19
  66. data/docs/file_formats.md +250 -0
  67. data/docs/getting-started/index.md +1 -14
  68. data/docs/index.md +26 -23
  69. data/docs/llms.txt +109 -0
  70. data/docs/strategies/kbs.md +15 -14
  71. data/docs/strategy.md +381 -3
  72. data/docs/terms_of_use.md +1 -1
  73. data/examples/README.md +10 -0
  74. data/lib/api/alpha_vantage_api.rb +3 -7
  75. data/lib/sqa/backtest.rb +32 -0
  76. data/lib/sqa/config.rb +109 -28
  77. data/lib/sqa/data_frame/data.rb +13 -1
  78. data/lib/sqa/data_frame.rb +193 -26
  79. data/lib/sqa/errors.rb +79 -17
  80. data/lib/sqa/init.rb +70 -15
  81. data/lib/sqa/pattern_matcher.rb +4 -4
  82. data/lib/sqa/portfolio.rb +55 -1
  83. data/lib/sqa/sector_analyzer.rb +3 -11
  84. data/lib/sqa/stock.rb +180 -15
  85. data/lib/sqa/strategy.rb +62 -4
  86. data/lib/sqa/ticker.rb +106 -48
  87. data/lib/sqa/version.rb +1 -1
  88. data/lib/sqa.rb +4 -4
  89. data/mkdocs.yml +69 -81
  90. metadata +89 -21
  91. data/docs/README.md +0 -43
  92. data/examples/sinatra_app/Gemfile +0 -42
  93. data/examples/sinatra_app/Gemfile.lock +0 -268
  94. data/examples/sinatra_app/QUICKSTART.md +0 -169
  95. data/examples/sinatra_app/README.md +0 -471
  96. data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +0 -90
  97. data/examples/sinatra_app/TROUBLESHOOTING.md +0 -95
  98. data/examples/sinatra_app/app.rb +0 -404
  99. data/examples/sinatra_app/config.ru +0 -5
  100. data/examples/sinatra_app/public/css/style.css +0 -723
  101. data/examples/sinatra_app/public/debug_macd.html +0 -82
  102. data/examples/sinatra_app/public/js/app.js +0 -107
  103. data/examples/sinatra_app/start.sh +0 -53
  104. data/examples/sinatra_app/views/analyze.erb +0 -306
  105. data/examples/sinatra_app/views/backtest.erb +0 -325
  106. data/examples/sinatra_app/views/dashboard.erb +0 -831
  107. data/examples/sinatra_app/views/error.erb +0 -58
  108. data/examples/sinatra_app/views/index.erb +0 -118
  109. data/examples/sinatra_app/views/layout.erb +0 -61
  110. data/examples/sinatra_app/views/portfolio.erb +0 -43
@@ -1,82 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>MACD Debug Test</title>
5
- </head>
6
- <body>
7
- <h1>MACD API Debug Test</h1>
8
- <p>Testing indicators API response...</p>
9
- <pre id="output"></pre>
10
-
11
- <script>
12
- async function testMACDData() {
13
- const output = document.getElementById('output');
14
-
15
- try {
16
- const response = await fetch('/api/indicators/AAPL?period=90d');
17
- const data = await response.json();
18
-
19
- if (data.error) {
20
- output.textContent = 'ERROR: ' + data.error;
21
- return;
22
- }
23
-
24
- let debug = '';
25
- debug += 'Total dates: ' + data.dates.length + '\n';
26
- debug += 'Total MACD values: ' + data.macd.length + '\n\n';
27
-
28
- // Check for null/NaN values
29
- let nullCount = 0;
30
- let validCount = 0;
31
- let firstValidIndex = -1;
32
-
33
- for (let i = 0; i < data.macd.length; i++) {
34
- if (data.macd[i] === null || isNaN(data.macd[i]) ||
35
- data.macd_signal[i] === null || isNaN(data.macd_signal[i]) ||
36
- data.macd_hist[i] === null || isNaN(data.macd_hist[i])) {
37
- nullCount++;
38
- } else {
39
- validCount++;
40
- if (firstValidIndex === -1) firstValidIndex = i;
41
- }
42
- }
43
-
44
- debug += 'Null/NaN values: ' + nullCount + '\n';
45
- debug += 'Valid values: ' + validCount + '\n';
46
- debug += 'First valid index: ' + firstValidIndex + '\n\n';
47
-
48
- if (validCount > 0) {
49
- debug += 'First 5 valid MACD values:\n';
50
- let count = 0;
51
- for (let i = firstValidIndex; i < data.macd.length && count < 5; i++) {
52
- if (data.macd[i] !== null && !isNaN(data.macd[i])) {
53
- debug += ` [${i}] Date: ${data.dates[i]}, MACD: ${data.macd[i]}, Signal: ${data.macd_signal[i]}, Hist: ${data.macd_hist[i]}\n`;
54
- count++;
55
- }
56
- }
57
-
58
- debug += '\nLast 5 MACD values:\n';
59
- for (let i = Math.max(0, data.macd.length - 5); i < data.macd.length; i++) {
60
- debug += ` [${i}] Date: ${data.dates[i]}, MACD: ${data.macd[i]}, Signal: ${data.macd_signal[i]}, Hist: ${data.macd_hist[i]}\n`;
61
- }
62
- } else {
63
- debug += '\nNO VALID MACD DATA FOUND!\n';
64
- debug += '\nSample of raw data:\n';
65
- for (let i = 0; i < Math.min(10, data.macd.length); i++) {
66
- debug += ` [${i}] MACD: ${data.macd[i]} (type: ${typeof data.macd[i]}), `;
67
- debug += `Signal: ${data.macd_signal[i]} (type: ${typeof data.macd_signal[i]}), `;
68
- debug += `Hist: ${data.macd_hist[i]} (type: ${typeof data.macd_hist[i]})\n`;
69
- }
70
- }
71
-
72
- output.textContent = debug;
73
-
74
- } catch (error) {
75
- output.textContent = 'ERROR: ' + error.message + '\n' + error.stack;
76
- }
77
- }
78
-
79
- testMACDData();
80
- </script>
81
- </body>
82
- </html>
@@ -1,107 +0,0 @@
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');
@@ -1,53 +0,0 @@
1
- #!/bin/bash
2
- # Startup script for SQA Sinatra App
3
- # This script checks dependencies and starts the server properly
4
-
5
- set -e
6
-
7
- echo "============================================================"
8
- echo "SQA Sinatra App - Startup Script"
9
- echo "============================================================"
10
- echo ""
11
-
12
- # Check if we're in the right directory
13
- if [ ! -f "app.rb" ]; then
14
- echo "❌ Error: app.rb not found. Please run this script from examples/sinatra_app/"
15
- exit 1
16
- fi
17
-
18
- # Check if Gemfile exists
19
- if [ ! -f "Gemfile" ]; then
20
- echo "❌ Error: Gemfile not found"
21
- exit 1
22
- fi
23
-
24
- # Check if bundler is installed
25
- if ! command -v bundle &> /dev/null; then
26
- echo "❌ Error: bundler is not installed"
27
- echo " Install with: gem install bundler"
28
- exit 1
29
- fi
30
-
31
- echo "✓ Found app.rb and Gemfile"
32
-
33
- # Check if bundle is satisfied
34
- echo ""
35
- echo "Checking dependencies..."
36
- if ! bundle check &> /dev/null; then
37
- echo "⚠️ Dependencies not installed. Running bundle install..."
38
- bundle install
39
- echo "✓ Dependencies installed"
40
- else
41
- echo "✓ All dependencies satisfied"
42
- fi
43
-
44
- echo ""
45
- echo "Starting server..."
46
- echo "============================================================"
47
- echo "Server will be available at: http://localhost:4567"
48
- echo "Press Ctrl+C to stop"
49
- echo "============================================================"
50
- echo ""
51
-
52
- # Start the server with bundle exec
53
- exec bundle exec ruby app.rb
@@ -1,306 +0,0 @@
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>