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.
- checksums.yaml +4 -4
- data/.goose/memory/development.txt +3 -0
- data/.semver +6 -0
- data/ARCHITECTURE.md +648 -0
- data/CHANGELOG.md +82 -0
- data/CLAUDE.md +653 -0
- data/COMMITS.md +196 -0
- data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
- data/NEXT-STEPS.md +154 -0
- data/README.md +812 -262
- data/TASKS.md +358 -0
- data/TEST_RESULTS.md +140 -0
- data/TODO.md +42 -0
- data/_notes.txt +25 -0
- data/bin/sqa-console +11 -0
- data/data/talk_talk.json +103284 -0
- data/develop_summary.md +313 -0
- data/docs/advanced/backtesting.md +206 -0
- data/docs/advanced/ensemble.md +68 -0
- data/docs/advanced/fpop.md +153 -0
- data/docs/advanced/index.md +112 -0
- data/docs/advanced/multi-timeframe.md +67 -0
- data/docs/advanced/pattern-matcher.md +75 -0
- data/docs/advanced/portfolio-optimizer.md +79 -0
- data/docs/advanced/portfolio.md +166 -0
- data/docs/advanced/risk-management.md +210 -0
- data/docs/advanced/strategy-generator.md +158 -0
- data/docs/advanced/streaming.md +209 -0
- data/docs/ai_and_ml.md +80 -0
- data/docs/api/dataframe.md +1115 -0
- data/docs/api/index.md +126 -0
- data/docs/assets/css/custom.css +88 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/concepts/index.md +68 -0
- data/docs/contributing/index.md +60 -0
- data/docs/data-sources/index.md +66 -0
- data/docs/data_frame.md +317 -97
- data/docs/factors_that_impact_price.md +26 -0
- data/docs/finviz.md +11 -0
- data/docs/fx_pro_bit.md +25 -0
- data/docs/genetic_programming.md +104 -0
- data/docs/getting-started/index.md +123 -0
- data/docs/getting-started/installation.md +229 -0
- data/docs/getting-started/quick-start.md +244 -0
- data/docs/i_gotta_an_idea.md +22 -0
- data/docs/index.md +163 -0
- data/docs/indicators/index.md +97 -0
- data/docs/indicators.md +110 -24
- data/docs/options.md +8 -0
- data/docs/strategies/bollinger-bands.md +146 -0
- data/docs/strategies/consensus.md +64 -0
- data/docs/strategies/custom.md +310 -0
- data/docs/strategies/ema.md +53 -0
- data/docs/strategies/index.md +92 -0
- data/docs/strategies/kbs.md +164 -0
- data/docs/strategies/macd.md +96 -0
- data/docs/strategies/market-profile.md +54 -0
- data/docs/strategies/mean-reversion.md +58 -0
- data/docs/strategies/rsi.md +95 -0
- data/docs/strategies/sma.md +55 -0
- data/docs/strategies/stochastic.md +63 -0
- data/docs/strategies/volume-breakout.md +54 -0
- data/docs/tags.md +7 -0
- data/docs/true_strength_index.md +46 -0
- data/docs/weighted_moving_average.md +48 -0
- data/examples/README.md +354 -0
- data/examples/advanced_features_example.rb +350 -0
- data/examples/fpop_analysis_example.rb +191 -0
- data/examples/genetic_programming_example.rb +148 -0
- data/examples/kbs_strategy_example.rb +208 -0
- data/examples/pattern_context_example.rb +300 -0
- data/examples/rails_app/Gemfile +34 -0
- data/examples/rails_app/README.md +416 -0
- data/examples/rails_app/app/assets/javascripts/application.js +107 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
- data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
- data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
- data/examples/rails_app/app/controllers/application_controller.rb +22 -0
- data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
- data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
- data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
- data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
- data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
- data/examples/rails_app/app/views/errors/show.html.erb +17 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
- data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
- data/examples/rails_app/bin/rails +6 -0
- data/examples/rails_app/config/application.rb +45 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +18 -0
- data/examples/rails_app/config/environment.rb +11 -0
- data/examples/rails_app/config/routes.rb +26 -0
- data/examples/rails_app/config.ru +8 -0
- data/examples/realtime_stream_example.rb +274 -0
- data/examples/sinatra_app/Gemfile +22 -0
- data/examples/sinatra_app/QUICKSTART.md +159 -0
- data/examples/sinatra_app/README.md +461 -0
- data/examples/sinatra_app/app.rb +344 -0
- data/examples/sinatra_app/config.ru +5 -0
- data/examples/sinatra_app/public/css/style.css +659 -0
- data/examples/sinatra_app/public/js/app.js +107 -0
- data/examples/sinatra_app/views/analyze.erb +306 -0
- data/examples/sinatra_app/views/backtest.erb +325 -0
- data/examples/sinatra_app/views/dashboard.erb +419 -0
- data/examples/sinatra_app/views/error.erb +58 -0
- data/examples/sinatra_app/views/index.erb +118 -0
- data/examples/sinatra_app/views/layout.erb +61 -0
- data/examples/sinatra_app/views/portfolio.erb +43 -0
- data/examples/strategy_generator_example.rb +346 -0
- data/hsa_portfolio.csv +11 -0
- data/justfile +0 -0
- data/lib/api/alpha_vantage_api.rb +462 -0
- data/lib/sqa/backtest.rb +329 -0
- data/lib/sqa/data_frame/alpha_vantage.rb +43 -65
- data/lib/sqa/data_frame/data.rb +92 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
- data/lib/sqa/data_frame.rb +148 -243
- data/lib/sqa/ensemble.rb +359 -0
- data/lib/sqa/fpop.rb +199 -0
- data/lib/sqa/gp.rb +259 -0
- data/lib/sqa/indicator.rb +5 -8
- data/lib/sqa/init.rb +15 -8
- data/lib/sqa/market_regime.rb +240 -0
- data/lib/sqa/multi_timeframe.rb +379 -0
- data/lib/sqa/pattern_matcher.rb +497 -0
- data/lib/sqa/portfolio.rb +260 -6
- data/lib/sqa/portfolio_optimizer.rb +377 -0
- data/lib/sqa/risk_manager.rb +442 -0
- data/lib/sqa/seasonal_analyzer.rb +209 -0
- data/lib/sqa/sector_analyzer.rb +300 -0
- data/lib/sqa/stock.rb +67 -125
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- data/lib/sqa/strategy/consensus.rb +5 -2
- data/lib/sqa/strategy/kbs_strategy.rb +470 -0
- data/lib/sqa/strategy/macd.rb +46 -0
- data/lib/sqa/strategy/mp.rb +1 -1
- data/lib/sqa/strategy/stochastic.rb +60 -0
- data/lib/sqa/strategy/volume_breakout.rb +57 -0
- data/lib/sqa/strategy.rb +5 -0
- data/lib/sqa/strategy_generator.rb +947 -0
- data/lib/sqa/stream.rb +361 -0
- data/lib/sqa/version.rb +1 -7
- data/lib/sqa.rb +23 -16
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +261 -51
- data/bin/sqa +0 -6
- data/lib/patches/dry-cli.rb +0 -228
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/cli.rb +0 -62
- data/lib/sqa/commands/analysis.rb +0 -309
- data/lib/sqa/commands/base.rb +0 -139
- data/lib/sqa/commands/web.rb +0 -199
- data/lib/sqa/commands.rb +0 -22
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -33
- data/lib/sqa/indicator/bollinger_bands.rb +0 -28
- data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
- data/lib/sqa/indicator/donchian_channel.rb +0 -29
- data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
- data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
- data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
- data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
- data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
- data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
- data/lib/sqa/indicator/market_profile.rb +0 -32
- data/lib/sqa/indicator/mean_reversion.rb +0 -37
- data/lib/sqa/indicator/momentum.rb +0 -28
- data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
- data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
- data/lib/sqa/indicator/predict_next_value.rb +0 -202
- data/lib/sqa/indicator/relative_strength_index.rb +0 -47
- data/lib/sqa/indicator/simple_moving_average.rb +0 -24
- data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
- data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
- data/lib/sqa/indicator/true_range.rb +0 -39
- data/lib/sqa/trade.rb +0 -26
|
@@ -0,0 +1,419 @@
|
|
|
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
|
+
<!-- Main Price Chart -->
|
|
46
|
+
<div class="chart-container">
|
|
47
|
+
<div class="chart-header">
|
|
48
|
+
<h2><i class="fas fa-chart-candlestick"></i> Price Chart</h2>
|
|
49
|
+
<div class="chart-controls">
|
|
50
|
+
<button onclick="updateChartType('candlestick')" class="btn-small active" data-chart="candlestick">
|
|
51
|
+
Candlestick
|
|
52
|
+
</button>
|
|
53
|
+
<button onclick="updateChartType('line')" class="btn-small" data-chart="line">
|
|
54
|
+
Line
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div id="priceChart" class="chart"></div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Volume Chart -->
|
|
62
|
+
<div class="chart-container">
|
|
63
|
+
<div class="chart-header">
|
|
64
|
+
<h2><i class="fas fa-chart-bar"></i> Volume</h2>
|
|
65
|
+
</div>
|
|
66
|
+
<div id="volumeChart" class="chart"></div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- Technical Indicators Grid -->
|
|
70
|
+
<div class="indicators-grid">
|
|
71
|
+
<!-- RSI Chart -->
|
|
72
|
+
<div class="chart-container">
|
|
73
|
+
<div class="chart-header">
|
|
74
|
+
<h3><i class="fas fa-wave-square"></i> RSI (14)</h3>
|
|
75
|
+
</div>
|
|
76
|
+
<div id="rsiChart" class="chart chart-small"></div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- MACD Chart -->
|
|
80
|
+
<div class="chart-container">
|
|
81
|
+
<div class="chart-header">
|
|
82
|
+
<h3><i class="fas fa-signal"></i> MACD</h3>
|
|
83
|
+
</div>
|
|
84
|
+
<div id="macdChart" class="chart chart-small"></div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Strategy Comparison -->
|
|
89
|
+
<div class="chart-container">
|
|
90
|
+
<div class="chart-header">
|
|
91
|
+
<h2><i class="fas fa-trophy"></i> Strategy Comparison</h2>
|
|
92
|
+
<button onclick="runStrategyComparison()" class="btn btn-primary">
|
|
93
|
+
<i class="fas fa-play"></i> Compare Strategies
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
<div id="strategyResults" class="strategy-results">
|
|
97
|
+
<p class="hint">Click "Compare Strategies" to see backtest results for different trading strategies.</p>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<script>
|
|
103
|
+
const ticker = '<%= @ticker %>';
|
|
104
|
+
let stockData = null;
|
|
105
|
+
let indicatorData = null;
|
|
106
|
+
let currentChartType = 'candlestick';
|
|
107
|
+
|
|
108
|
+
// Load data when page loads
|
|
109
|
+
document.addEventListener('DOMContentLoaded', async function() {
|
|
110
|
+
await loadStockData();
|
|
111
|
+
await loadIndicators();
|
|
112
|
+
await loadAnalysis();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
async function loadStockData() {
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch(`/api/stock/${ticker}`);
|
|
118
|
+
stockData = await response.json();
|
|
119
|
+
|
|
120
|
+
// Update price info
|
|
121
|
+
document.querySelector('.current-price').textContent = `$${stockData.current_price.toFixed(2)}`;
|
|
122
|
+
|
|
123
|
+
const changeClass = stockData.change >= 0 ? 'positive' : 'negative';
|
|
124
|
+
const changeSign = stockData.change >= 0 ? '+' : '';
|
|
125
|
+
document.querySelector('.price-change').className = `price-change ${changeClass}`;
|
|
126
|
+
document.querySelector('.price-change').textContent =
|
|
127
|
+
`${changeSign}${stockData.change.toFixed(2)} (${changeSign}${stockData.change_percent.toFixed(2)}%)`;
|
|
128
|
+
|
|
129
|
+
// Update metrics
|
|
130
|
+
document.getElementById('high52w').textContent = `$${stockData.high_52w.toFixed(2)}`;
|
|
131
|
+
document.getElementById('low52w').textContent = `$${stockData.low_52w.toFixed(2)}`;
|
|
132
|
+
|
|
133
|
+
// Render charts
|
|
134
|
+
renderPriceChart();
|
|
135
|
+
renderVolumeChart();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Error loading stock data:', error);
|
|
138
|
+
alert('Failed to load stock data. Please try again.');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function loadIndicators() {
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch(`/api/indicators/${ticker}`);
|
|
145
|
+
indicatorData = await response.json();
|
|
146
|
+
|
|
147
|
+
// Update RSI metric
|
|
148
|
+
const currentRSI = indicatorData.rsi[indicatorData.rsi.length - 1];
|
|
149
|
+
document.getElementById('currentRSI').textContent = currentRSI.toFixed(2);
|
|
150
|
+
|
|
151
|
+
let rsiSignal = '';
|
|
152
|
+
let rsiClass = '';
|
|
153
|
+
if (currentRSI < 30) {
|
|
154
|
+
rsiSignal = 'Oversold';
|
|
155
|
+
rsiClass = 'signal-buy';
|
|
156
|
+
} else if (currentRSI > 70) {
|
|
157
|
+
rsiSignal = 'Overbought';
|
|
158
|
+
rsiClass = 'signal-sell';
|
|
159
|
+
} else {
|
|
160
|
+
rsiSignal = 'Neutral';
|
|
161
|
+
rsiClass = 'signal-neutral';
|
|
162
|
+
}
|
|
163
|
+
const rsiSignalEl = document.getElementById('rsiSignal');
|
|
164
|
+
rsiSignalEl.textContent = rsiSignal;
|
|
165
|
+
rsiSignalEl.className = `metric-signal ${rsiClass}`;
|
|
166
|
+
|
|
167
|
+
// Render indicator charts
|
|
168
|
+
renderRSIChart();
|
|
169
|
+
renderMACDChart();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error('Error loading indicators:', error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function loadAnalysis() {
|
|
176
|
+
try {
|
|
177
|
+
const response = await fetch(`/api/analyze/${ticker}`);
|
|
178
|
+
const analysis = await response.json();
|
|
179
|
+
|
|
180
|
+
// Update market regime
|
|
181
|
+
const regime = analysis.regime.type.toUpperCase();
|
|
182
|
+
const regimeEl = document.getElementById('marketRegime');
|
|
183
|
+
regimeEl.textContent = regime;
|
|
184
|
+
|
|
185
|
+
let regimeClass = '';
|
|
186
|
+
if (regime === 'BULL') regimeClass = 'signal-buy';
|
|
187
|
+
else if (regime === 'BEAR') regimeClass = 'signal-sell';
|
|
188
|
+
else regimeClass = 'signal-neutral';
|
|
189
|
+
regimeEl.className = `metric-value ${regimeClass}`;
|
|
190
|
+
|
|
191
|
+
const regimeDetail = document.getElementById('regimeDetail');
|
|
192
|
+
regimeDetail.textContent = `${analysis.regime.volatility} volatility, ${analysis.regime.strength.toFixed(2)} strength`;
|
|
193
|
+
regimeDetail.className = 'metric-signal';
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Error loading analysis:', error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function renderPriceChart() {
|
|
200
|
+
const trace = currentChartType === 'candlestick' ? {
|
|
201
|
+
type: 'candlestick',
|
|
202
|
+
x: stockData.dates,
|
|
203
|
+
open: stockData.open,
|
|
204
|
+
high: stockData.high,
|
|
205
|
+
low: stockData.low,
|
|
206
|
+
close: stockData.close,
|
|
207
|
+
name: ticker,
|
|
208
|
+
increasing: { line: { color: '#26a69a' } },
|
|
209
|
+
decreasing: { line: { color: '#ef5350' } }
|
|
210
|
+
} : {
|
|
211
|
+
type: 'scatter',
|
|
212
|
+
mode: 'lines',
|
|
213
|
+
x: stockData.dates,
|
|
214
|
+
y: stockData.close,
|
|
215
|
+
name: ticker,
|
|
216
|
+
line: { color: '#2196F3', width: 2 }
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Add moving averages if we have indicator data
|
|
220
|
+
const traces = [trace];
|
|
221
|
+
if (indicatorData) {
|
|
222
|
+
traces.push({
|
|
223
|
+
type: 'scatter',
|
|
224
|
+
mode: 'lines',
|
|
225
|
+
x: indicatorData.dates,
|
|
226
|
+
y: indicatorData.sma_20,
|
|
227
|
+
name: 'SMA 20',
|
|
228
|
+
line: { color: '#FF9800', width: 1, dash: 'dash' }
|
|
229
|
+
});
|
|
230
|
+
traces.push({
|
|
231
|
+
type: 'scatter',
|
|
232
|
+
mode: 'lines',
|
|
233
|
+
x: indicatorData.dates,
|
|
234
|
+
y: indicatorData.sma_50,
|
|
235
|
+
name: 'SMA 50',
|
|
236
|
+
line: { color: '#9C27B0', width: 1, dash: 'dash' }
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const layout = {
|
|
241
|
+
title: '',
|
|
242
|
+
xaxis: { title: 'Date', rangeslider: { visible: false } },
|
|
243
|
+
yaxis: { title: 'Price ($)' },
|
|
244
|
+
plot_bgcolor: '#ffffff',
|
|
245
|
+
paper_bgcolor: '#ffffff',
|
|
246
|
+
margin: { l: 50, r: 50, t: 20, b: 50 },
|
|
247
|
+
height: 400,
|
|
248
|
+
showlegend: true,
|
|
249
|
+
legend: { x: 0, y: 1 }
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
Plotly.newPlot('priceChart', traces, layout, { responsive: true });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderVolumeChart() {
|
|
256
|
+
const colors = stockData.close.map((close, i) => {
|
|
257
|
+
if (i === 0) return '#2196F3';
|
|
258
|
+
return close >= stockData.close[i - 1] ? '#26a69a' : '#ef5350';
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const trace = {
|
|
262
|
+
type: 'bar',
|
|
263
|
+
x: stockData.dates,
|
|
264
|
+
y: stockData.volume,
|
|
265
|
+
name: 'Volume',
|
|
266
|
+
marker: { color: colors }
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const layout = {
|
|
270
|
+
title: '',
|
|
271
|
+
xaxis: { title: 'Date' },
|
|
272
|
+
yaxis: { title: 'Volume' },
|
|
273
|
+
plot_bgcolor: '#ffffff',
|
|
274
|
+
paper_bgcolor: '#ffffff',
|
|
275
|
+
margin: { l: 50, r: 50, t: 20, b: 50 },
|
|
276
|
+
height: 200,
|
|
277
|
+
showlegend: false
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
Plotly.newPlot('volumeChart', [trace], layout, { responsive: true });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function renderRSIChart() {
|
|
284
|
+
const trace = {
|
|
285
|
+
type: 'scatter',
|
|
286
|
+
mode: 'lines',
|
|
287
|
+
x: indicatorData.dates,
|
|
288
|
+
y: indicatorData.rsi,
|
|
289
|
+
name: 'RSI',
|
|
290
|
+
line: { color: '#2196F3', width: 2 }
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const oversold = {
|
|
294
|
+
type: 'scatter',
|
|
295
|
+
mode: 'lines',
|
|
296
|
+
x: indicatorData.dates,
|
|
297
|
+
y: Array(indicatorData.dates.length).fill(30),
|
|
298
|
+
name: 'Oversold',
|
|
299
|
+
line: { color: '#4CAF50', width: 1, dash: 'dash' }
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const overbought = {
|
|
303
|
+
type: 'scatter',
|
|
304
|
+
mode: 'lines',
|
|
305
|
+
x: indicatorData.dates,
|
|
306
|
+
y: Array(indicatorData.dates.length).fill(70),
|
|
307
|
+
name: 'Overbought',
|
|
308
|
+
line: { color: '#F44336', width: 1, dash: 'dash' }
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const layout = {
|
|
312
|
+
title: '',
|
|
313
|
+
xaxis: { title: 'Date' },
|
|
314
|
+
yaxis: { title: 'RSI', range: [0, 100] },
|
|
315
|
+
plot_bgcolor: '#ffffff',
|
|
316
|
+
paper_bgcolor: '#ffffff',
|
|
317
|
+
margin: { l: 50, r: 50, t: 20, b: 50 },
|
|
318
|
+
height: 250,
|
|
319
|
+
showlegend: true,
|
|
320
|
+
legend: { x: 0, y: 1 }
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
Plotly.newPlot('rsiChart', [trace, oversold, overbought], layout, { responsive: true });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function renderMACDChart() {
|
|
327
|
+
const macd = {
|
|
328
|
+
type: 'scatter',
|
|
329
|
+
mode: 'lines',
|
|
330
|
+
x: indicatorData.dates,
|
|
331
|
+
y: indicatorData.macd,
|
|
332
|
+
name: 'MACD',
|
|
333
|
+
line: { color: '#2196F3', width: 2 }
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const signal = {
|
|
337
|
+
type: 'scatter',
|
|
338
|
+
mode: 'lines',
|
|
339
|
+
x: indicatorData.dates,
|
|
340
|
+
y: indicatorData.macd_signal,
|
|
341
|
+
name: 'Signal',
|
|
342
|
+
line: { color: '#FF9800', width: 2 }
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const hist = {
|
|
346
|
+
type: 'bar',
|
|
347
|
+
x: indicatorData.dates,
|
|
348
|
+
y: indicatorData.macd_hist,
|
|
349
|
+
name: 'Histogram',
|
|
350
|
+
marker: {
|
|
351
|
+
color: indicatorData.macd_hist.map(v => v >= 0 ? '#26a69a' : '#ef5350')
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const layout = {
|
|
356
|
+
title: '',
|
|
357
|
+
xaxis: { title: 'Date' },
|
|
358
|
+
yaxis: { title: 'MACD' },
|
|
359
|
+
plot_bgcolor: '#ffffff',
|
|
360
|
+
paper_bgcolor: '#ffffff',
|
|
361
|
+
margin: { l: 50, r: 50, t: 20, b: 50 },
|
|
362
|
+
height: 250,
|
|
363
|
+
showlegend: true,
|
|
364
|
+
legend: { x: 0, y: 1 }
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
Plotly.newPlot('macdChart', [hist, macd, signal], layout, { responsive: true });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function updateChartType(type) {
|
|
371
|
+
currentChartType = type;
|
|
372
|
+
|
|
373
|
+
// Update button states
|
|
374
|
+
document.querySelectorAll('[data-chart]').forEach(btn => {
|
|
375
|
+
btn.classList.remove('active');
|
|
376
|
+
});
|
|
377
|
+
document.querySelector(`[data-chart="${type}"]`).classList.add('active');
|
|
378
|
+
|
|
379
|
+
// Re-render chart
|
|
380
|
+
renderPriceChart();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function runStrategyComparison() {
|
|
384
|
+
const resultsDiv = document.getElementById('strategyResults');
|
|
385
|
+
resultsDiv.innerHTML = '<p class="loading"><i class="fas fa-spinner fa-spin"></i> Running backtests...</p>';
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const response = await fetch(`/api/compare/${ticker}`, { method: 'POST' });
|
|
389
|
+
const results = await response.json();
|
|
390
|
+
|
|
391
|
+
let html = '<table class="results-table"><thead><tr>';
|
|
392
|
+
html += '<th>Strategy</th><th>Return</th><th>Sharpe</th><th>Drawdown</th><th>Win Rate</th><th>Trades</th>';
|
|
393
|
+
html += '</tr></thead><tbody>';
|
|
394
|
+
|
|
395
|
+
results.forEach((result, i) => {
|
|
396
|
+
const returnClass = result.return >= 0 ? 'positive' : 'negative';
|
|
397
|
+
const rank = i === 0 ? '<i class="fas fa-trophy" style="color: #FFD700;"></i> ' : '';
|
|
398
|
+
html += `<tr>
|
|
399
|
+
<td>${rank}${result.strategy}</td>
|
|
400
|
+
<td class="${returnClass}">${result.return.toFixed(2)}%</td>
|
|
401
|
+
<td>${result.sharpe.toFixed(2)}</td>
|
|
402
|
+
<td>${result.drawdown.toFixed(2)}%</td>
|
|
403
|
+
<td>${result.win_rate.toFixed(2)}%</td>
|
|
404
|
+
<td>${result.trades}</td>
|
|
405
|
+
</tr>`;
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
html += '</tbody></table>';
|
|
409
|
+
resultsDiv.innerHTML = html;
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.error('Error comparing strategies:', error);
|
|
412
|
+
resultsDiv.innerHTML = '<p class="error">Failed to compare strategies. Please try again.</p>';
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function refreshData() {
|
|
417
|
+
location.reload();
|
|
418
|
+
}
|
|
419
|
+
</script>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<div class="error-page">
|
|
2
|
+
<div class="error-container">
|
|
3
|
+
<div class="error-icon">
|
|
4
|
+
<i class="fas fa-exclamation-triangle"></i>
|
|
5
|
+
</div>
|
|
6
|
+
<h1>Oops! Something went wrong</h1>
|
|
7
|
+
<p class="error-message"><%= @error %></p>
|
|
8
|
+
<div class="error-actions">
|
|
9
|
+
<a href="/" class="btn btn-primary">
|
|
10
|
+
<i class="fas fa-home"></i> Back to Home
|
|
11
|
+
</a>
|
|
12
|
+
<button onclick="history.back()" class="btn btn-secondary">
|
|
13
|
+
<i class="fas fa-arrow-left"></i> Go Back
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
.error-page {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
min-height: 70vh;
|
|
25
|
+
padding: 2rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.error-container {
|
|
29
|
+
text-align: center;
|
|
30
|
+
max-width: 600px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.error-icon {
|
|
34
|
+
font-size: 6rem;
|
|
35
|
+
color: var(--danger-color);
|
|
36
|
+
margin-bottom: 2rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.error-page h1 {
|
|
40
|
+
font-size: 2.5rem;
|
|
41
|
+
color: var(--text-primary);
|
|
42
|
+
margin-bottom: 1rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.error-message {
|
|
46
|
+
font-size: 1.25rem;
|
|
47
|
+
color: var(--text-secondary);
|
|
48
|
+
margin-bottom: 2rem;
|
|
49
|
+
line-height: 1.6;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.error-actions {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 1rem;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<div class="hero">
|
|
2
|
+
<div class="hero-content">
|
|
3
|
+
<h1>
|
|
4
|
+
<i class="fas fa-chart-line"></i>
|
|
5
|
+
Stock Analysis Platform
|
|
6
|
+
</h1>
|
|
7
|
+
<p class="hero-subtitle">
|
|
8
|
+
Advanced technical analysis powered by SQA library
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<div class="ticker-search">
|
|
12
|
+
<form onsubmit="return searchTicker(event)" class="search-form">
|
|
13
|
+
<input
|
|
14
|
+
type="text"
|
|
15
|
+
id="mainTickerInput"
|
|
16
|
+
placeholder="Enter stock ticker (e.g., AAPL, MSFT, GOOGL)"
|
|
17
|
+
autocomplete="off"
|
|
18
|
+
>
|
|
19
|
+
<button type="submit" class="btn btn-primary btn-large">
|
|
20
|
+
<i class="fas fa-search"></i> Analyze
|
|
21
|
+
</button>
|
|
22
|
+
</form>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="quick-links">
|
|
26
|
+
<h3>Quick Analysis</h3>
|
|
27
|
+
<div class="ticker-buttons">
|
|
28
|
+
<a href="/dashboard/AAPL" class="ticker-btn">AAPL</a>
|
|
29
|
+
<a href="/dashboard/MSFT" class="ticker-btn">MSFT</a>
|
|
30
|
+
<a href="/dashboard/GOOGL" class="ticker-btn">GOOGL</a>
|
|
31
|
+
<a href="/dashboard/AMZN" class="ticker-btn">AMZN</a>
|
|
32
|
+
<a href="/dashboard/TSLA" class="ticker-btn">TSLA</a>
|
|
33
|
+
<a href="/dashboard/NVDA" class="ticker-btn">NVDA</a>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="features">
|
|
40
|
+
<div class="container">
|
|
41
|
+
<h2>Features</h2>
|
|
42
|
+
<div class="feature-grid">
|
|
43
|
+
<div class="feature-card">
|
|
44
|
+
<div class="feature-icon">
|
|
45
|
+
<i class="fas fa-chart-candlestick"></i>
|
|
46
|
+
</div>
|
|
47
|
+
<h3>Interactive Charts</h3>
|
|
48
|
+
<p>Candlestick charts with technical indicators including RSI, MACD, Bollinger Bands, and moving averages.</p>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="feature-card">
|
|
52
|
+
<div class="feature-icon">
|
|
53
|
+
<i class="fas fa-robot"></i>
|
|
54
|
+
</div>
|
|
55
|
+
<h3>Strategy Backtesting</h3>
|
|
56
|
+
<p>Test trading strategies on historical data with detailed performance metrics and comparison tools.</p>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="feature-card">
|
|
60
|
+
<div class="feature-icon">
|
|
61
|
+
<i class="fas fa-brain"></i>
|
|
62
|
+
</div>
|
|
63
|
+
<h3>Market Analysis</h3>
|
|
64
|
+
<p>Detect market regimes, seasonal patterns, and future price projections with FPOP analysis.</p>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="feature-card">
|
|
68
|
+
<div class="feature-icon">
|
|
69
|
+
<i class="fas fa-shield-alt"></i>
|
|
70
|
+
</div>
|
|
71
|
+
<h3>Risk Management</h3>
|
|
72
|
+
<p>Value at Risk (VaR), Sharpe ratio, maximum drawdown, and other risk metrics for informed decisions.</p>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="feature-card">
|
|
76
|
+
<div class="feature-icon">
|
|
77
|
+
<i class="fas fa-layer-group"></i>
|
|
78
|
+
</div>
|
|
79
|
+
<h3>Portfolio Optimization</h3>
|
|
80
|
+
<p>Optimize allocation across multiple stocks using maximum Sharpe, minimum variance, or risk parity.</p>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="feature-card">
|
|
84
|
+
<div class="feature-icon">
|
|
85
|
+
<i class="fas fa-search-dollar"></i>
|
|
86
|
+
</div>
|
|
87
|
+
<h3>Pattern Discovery</h3>
|
|
88
|
+
<p>Discover profitable trading patterns by reverse-engineering historical data and indicator combinations.</p>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="info-section">
|
|
95
|
+
<div class="container">
|
|
96
|
+
<div class="info-content">
|
|
97
|
+
<h2>About SQA</h2>
|
|
98
|
+
<p>
|
|
99
|
+
SQA (Simple Qualitative Analysis) is a comprehensive Ruby library for stock market
|
|
100
|
+
technical analysis. This web interface provides an intuitive way to visualize and
|
|
101
|
+
analyze stock data using advanced algorithms and machine learning techniques.
|
|
102
|
+
</p>
|
|
103
|
+
<ul class="info-list">
|
|
104
|
+
<li><i class="fas fa-check-circle"></i> 150+ technical indicators via TA-Lib</li>
|
|
105
|
+
<li><i class="fas fa-check-circle"></i> 12+ built-in trading strategies</li>
|
|
106
|
+
<li><i class="fas fa-check-circle"></i> High-performance Polars DataFrame backend</li>
|
|
107
|
+
<li><i class="fas fa-check-circle"></i> Real-time streaming support</li>
|
|
108
|
+
<li><i class="fas fa-check-circle"></i> Genetic programming for parameter optimization</li>
|
|
109
|
+
<li><i class="fas fa-check-circle"></i> Knowledge-based trading with RETE engine</li>
|
|
110
|
+
</ul>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<script>
|
|
116
|
+
// Focus on ticker input when page loads
|
|
117
|
+
document.getElementById('mainTickerInput').focus();
|
|
118
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>SQA - Stock Analysis Platform</title>
|
|
7
|
+
|
|
8
|
+
<!-- Plotly.js for charts -->
|
|
9
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
10
|
+
|
|
11
|
+
<!-- Custom CSS -->
|
|
12
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
13
|
+
|
|
14
|
+
<!-- Font Awesome for icons -->
|
|
15
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<!-- Navigation -->
|
|
19
|
+
<nav class="navbar">
|
|
20
|
+
<div class="nav-container">
|
|
21
|
+
<div class="nav-brand">
|
|
22
|
+
<i class="fas fa-chart-line"></i>
|
|
23
|
+
<span>SQA Analytics</span>
|
|
24
|
+
</div>
|
|
25
|
+
<ul class="nav-menu">
|
|
26
|
+
<li><a href="/" class="nav-link"><i class="fas fa-home"></i> Home</a></li>
|
|
27
|
+
<li><a href="#" class="nav-link" onclick="showTickerModal()"><i class="fas fa-search"></i> Analyze Stock</a></li>
|
|
28
|
+
<li><a href="/portfolio" class="nav-link"><i class="fas fa-briefcase"></i> Portfolio</a></li>
|
|
29
|
+
</ul>
|
|
30
|
+
</div>
|
|
31
|
+
</nav>
|
|
32
|
+
|
|
33
|
+
<!-- Main Content -->
|
|
34
|
+
<main class="main-content">
|
|
35
|
+
<%= yield %>
|
|
36
|
+
</main>
|
|
37
|
+
|
|
38
|
+
<!-- Footer -->
|
|
39
|
+
<footer class="footer">
|
|
40
|
+
<div class="footer-content">
|
|
41
|
+
<p>© 2025 SQA - Simple Qualitative Analysis. Educational purposes only.</p>
|
|
42
|
+
<p class="disclaimer">⚠️ Not financial advice. For educational use only.</p>
|
|
43
|
+
</div>
|
|
44
|
+
</footer>
|
|
45
|
+
|
|
46
|
+
<!-- Ticker Search Modal -->
|
|
47
|
+
<div id="tickerModal" class="modal">
|
|
48
|
+
<div class="modal-content">
|
|
49
|
+
<span class="close" onclick="closeTickerModal()">×</span>
|
|
50
|
+
<h2>Enter Stock Ticker</h2>
|
|
51
|
+
<form onsubmit="return searchTicker(event)">
|
|
52
|
+
<input type="text" id="tickerInput" placeholder="e.g., AAPL" autocomplete="off" autofocus>
|
|
53
|
+
<button type="submit" class="btn btn-primary">Analyze</button>
|
|
54
|
+
</form>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Custom JavaScript -->
|
|
59
|
+
<script src="/js/app.js"></script>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|