sqa 0.0.32 → 0.0.37
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -1
- data/README.md +4 -0
- data/Rakefile +52 -10
- data/docs/IMPROVEMENT_PLAN.md +531 -0
- data/docs/advanced/index.md +1 -13
- data/docs/api/index.md +547 -61
- data/docs/api-reference/alphavantageapi.md +1057 -0
- data/docs/api-reference/apierror.md +31 -0
- data/docs/api-reference/index.md +221 -0
- data/docs/api-reference/notimplemented.md +27 -0
- data/docs/api-reference/sqa.md +267 -0
- data/docs/api-reference/sqa_backtest.md +137 -0
- data/docs/api-reference/sqa_backtest_results.md +530 -0
- data/docs/api-reference/sqa_badparametererror.md +13 -0
- data/docs/api-reference/sqa_config.md +538 -0
- data/docs/api-reference/sqa_configurationerror.md +13 -0
- data/docs/api-reference/sqa_datafetcherror.md +56 -0
- data/docs/api-reference/sqa_dataframe.md +752 -0
- data/docs/api-reference/sqa_dataframe_alphavantage.md +30 -0
- data/docs/api-reference/sqa_dataframe_data.md +325 -0
- data/docs/api-reference/sqa_dataframe_yahoofinance.md +25 -0
- data/docs/api-reference/sqa_ensemble.md +413 -0
- data/docs/api-reference/sqa_fpop.md +211 -0
- data/docs/api-reference/sqa_geneticprogram.md +325 -0
- data/docs/api-reference/sqa_geneticprogram_individual.md +114 -0
- data/docs/api-reference/sqa_marketregime.md +212 -0
- data/docs/api-reference/sqa_multitimeframe.md +227 -0
- data/docs/api-reference/sqa_patternmatcher.md +195 -0
- data/docs/api-reference/sqa_pluginmanager.md +55 -0
- data/docs/api-reference/sqa_portfolio.md +455 -0
- data/docs/api-reference/sqa_portfolio_position.md +220 -0
- data/docs/api-reference/sqa_portfolio_trade.md +332 -0
- data/docs/api-reference/sqa_portfoliooptimizer.md +248 -0
- data/docs/api-reference/sqa_riskmanager.md +388 -0
- data/docs/api-reference/sqa_seasonalanalyzer.md +121 -0
- data/docs/api-reference/sqa_sectoranalyzer.md +163 -0
- data/docs/api-reference/sqa_stock.md +649 -0
- data/docs/api-reference/sqa_strategy.md +178 -0
- data/docs/api-reference/sqa_strategy_bollingerbands.md +26 -0
- data/docs/api-reference/sqa_strategy_common.md +29 -0
- data/docs/api-reference/sqa_strategy_consensus.md +129 -0
- data/docs/api-reference/sqa_strategy_ema.md +41 -0
- data/docs/api-reference/sqa_strategy_kbs.md +154 -0
- data/docs/api-reference/sqa_strategy_macd.md +26 -0
- data/docs/api-reference/sqa_strategy_mp.md +41 -0
- data/docs/api-reference/sqa_strategy_mr.md +41 -0
- data/docs/api-reference/sqa_strategy_random.md +41 -0
- data/docs/api-reference/sqa_strategy_rsi.md +41 -0
- data/docs/api-reference/sqa_strategy_sma.md +41 -0
- data/docs/api-reference/sqa_strategy_stochastic.md +26 -0
- data/docs/api-reference/sqa_strategy_volumebreakout.md +26 -0
- data/docs/api-reference/sqa_strategygenerator.md +298 -0
- data/docs/api-reference/sqa_strategygenerator_pattern.md +264 -0
- data/docs/api-reference/sqa_strategygenerator_patterncontext.md +326 -0
- data/docs/api-reference/sqa_strategygenerator_profitablepoint.md +424 -0
- data/docs/api-reference/sqa_stream.md +256 -0
- data/docs/api-reference/sqa_ticker.md +175 -0
- data/docs/api-reference/string.md +135 -0
- data/docs/assets/images/advanced-workflow.svg +89 -0
- data/docs/assets/images/architecture.svg +107 -0
- data/docs/assets/images/data-flow.svg +138 -0
- data/docs/assets/images/getting-started-workflow.svg +88 -0
- data/docs/assets/images/strategy-flow.svg +78 -0
- data/docs/assets/images/system-architecture.svg +150 -0
- data/docs/concepts/index.md +292 -19
- data/docs/getting-started/index.md +1 -14
- data/docs/index.md +26 -23
- data/docs/llms.txt +109 -0
- data/docs/strategies/kbs.md +15 -14
- data/docs/strategy.md +381 -3
- data/docs/terms_of_use.md +1 -1
- data/examples/README.md +10 -0
- data/lib/api/alpha_vantage_api.rb +3 -7
- data/lib/sqa/config.rb +109 -28
- data/lib/sqa/data_frame/data.rb +13 -1
- data/lib/sqa/data_frame.rb +168 -26
- data/lib/sqa/errors.rb +79 -17
- data/lib/sqa/init.rb +70 -15
- data/lib/sqa/pattern_matcher.rb +4 -4
- data/lib/sqa/portfolio.rb +1 -1
- data/lib/sqa/sector_analyzer.rb +3 -11
- data/lib/sqa/stock.rb +169 -15
- data/lib/sqa/strategy.rb +62 -4
- data/lib/sqa/ticker.rb +106 -48
- data/lib/sqa/version.rb +1 -1
- data/lib/sqa.rb +4 -4
- data/mkdocs.yml +68 -81
- metadata +89 -21
- data/docs/README.md +0 -43
- data/examples/sinatra_app/Gemfile +0 -42
- data/examples/sinatra_app/Gemfile.lock +0 -268
- data/examples/sinatra_app/QUICKSTART.md +0 -169
- data/examples/sinatra_app/README.md +0 -471
- data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +0 -90
- data/examples/sinatra_app/TROUBLESHOOTING.md +0 -95
- data/examples/sinatra_app/app.rb +0 -404
- data/examples/sinatra_app/config.ru +0 -5
- data/examples/sinatra_app/public/css/style.css +0 -723
- data/examples/sinatra_app/public/debug_macd.html +0 -82
- data/examples/sinatra_app/public/js/app.js +0 -107
- data/examples/sinatra_app/start.sh +0 -53
- data/examples/sinatra_app/views/analyze.erb +0 -306
- data/examples/sinatra_app/views/backtest.erb +0 -325
- data/examples/sinatra_app/views/dashboard.erb +0 -831
- data/examples/sinatra_app/views/error.erb +0 -58
- data/examples/sinatra_app/views/index.erb +0 -118
- data/examples/sinatra_app/views/layout.erb +0 -61
- data/examples/sinatra_app/views/portfolio.erb +0 -43
|
@@ -1,831 +0,0 @@
|
|
|
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>
|