sqa 0.0.31 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/CLAUDE.md +21 -0
  4. data/README.md +56 -32
  5. data/docs/api/dataframe.md +0 -1
  6. data/docs/assets/images/sqa.jpg +0 -0
  7. data/docs/concepts/index.md +2 -10
  8. data/docs/data_frame.md +0 -1
  9. data/docs/getting-started/index.md +0 -16
  10. data/docs/getting-started/installation.md +2 -2
  11. data/docs/getting-started/quick-start.md +4 -4
  12. data/docs/index.md +0 -2
  13. data/docs/strategies/bollinger-bands.md +1 -1
  14. data/docs/strategies/rsi.md +1 -1
  15. data/examples/sinatra_app/Gemfile +20 -0
  16. data/examples/sinatra_app/Gemfile.lock +268 -0
  17. data/examples/sinatra_app/QUICKSTART.md +13 -3
  18. data/examples/sinatra_app/README.md +12 -2
  19. data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +90 -0
  20. data/examples/sinatra_app/TROUBLESHOOTING.md +95 -0
  21. data/examples/sinatra_app/app.rb +85 -25
  22. data/examples/sinatra_app/public/css/style.css +101 -37
  23. data/examples/sinatra_app/public/debug_macd.html +82 -0
  24. data/examples/sinatra_app/start.sh +53 -0
  25. data/examples/sinatra_app/views/dashboard.erb +558 -146
  26. data/examples/sinatra_app/views/layout.erb +2 -2
  27. data/lib/sqa/data_frame/alpha_vantage.rb +13 -3
  28. data/lib/sqa/data_frame.rb +21 -15
  29. data/lib/sqa/indicator.rb +17 -4
  30. data/lib/sqa/stock.rb +73 -11
  31. data/lib/sqa/ticker.rb +9 -2
  32. data/lib/sqa/version.rb +1 -1
  33. data/lib/sqa.rb +12 -4
  34. data/mkdocs.yml +4 -40
  35. metadata +7 -21
  36. data/docs/alpha_vantage_technical_indicators.md +0 -62
  37. data/docs/average_true_range.md +0 -9
  38. data/docs/bollinger_bands.md +0 -15
  39. data/docs/candlestick_pattern_recognizer.md +0 -4
  40. data/docs/donchian_channel.md +0 -5
  41. data/docs/double_top_bottom_pattern.md +0 -3
  42. data/docs/exponential_moving_average.md +0 -19
  43. data/docs/fibonacci_retracement.md +0 -30
  44. data/docs/head_and_shoulders_pattern.md +0 -3
  45. data/docs/market_profile.md +0 -4
  46. data/docs/momentum.md +0 -19
  47. data/docs/moving_average_convergence_divergence.md +0 -23
  48. data/docs/peaks_and_valleys.md +0 -11
  49. data/docs/relative_strength_index.md +0 -6
  50. data/docs/simple_moving_average.md +0 -8
  51. data/docs/stochastic_oscillator.md +0 -4
  52. data/docs/ta_lib.md +0 -160
  53. data/docs/true_range.md +0 -12
  54. data/docs/true_strength_index.md +0 -46
  55. data/docs/weighted_moving_average.md +0 -48
@@ -0,0 +1,90 @@
1
+ # Running the Sinatra App Without TA-Lib
2
+
3
+ The SQA Sinatra web app can run with **limited functionality** when the TA-Lib system library is not installed. This is useful for development, testing, or deployments where installing TA-Lib is difficult.
4
+
5
+ ## What Works Without TA-Lib
6
+
7
+ ✅ **Basic Stock Data Display**
8
+ - Loading stock price history from cached CSV files
9
+ - Displaying price charts (candlestick and line)
10
+ - Volume charts
11
+ - Price statistics (current price, 52-week high/low)
12
+
13
+ ✅ **Advanced Analysis Features**
14
+ - Market regime detection (bull/bear/sideways)
15
+ - Seasonal pattern analysis
16
+ - Risk metrics (VaR, Sharpe ratio, max drawdown)
17
+ - FPOP (Future Period Profit/Loss) analysis
18
+
19
+ ✅ **Strategy Comparison**
20
+ - Comparing different trading strategies via backtesting
21
+
22
+ ## What Doesn't Work Without TA-Lib
23
+
24
+ ❌ **Technical Indicators**
25
+ - RSI (Relative Strength Index)
26
+ - MACD (Moving Average Convergence Divergence)
27
+ - Bollinger Bands
28
+ - Moving Averages (SMA, EMA)
29
+ - All other TA-Lib indicators (150+ total)
30
+
31
+ These endpoints will return errors:
32
+ - `GET /api/indicators/:ticker` - Returns error when indicators are called
33
+
34
+ ## Running the App
35
+
36
+ ### With Cached Data (No API Key Required)
37
+
38
+ 1. Ensure you have cached stock data in `~/sqa_data/`:
39
+ ```
40
+ ~/sqa_data/
41
+ ├── aapl.csv # Price history
42
+ ├── aapl.json # Stock metadata
43
+ ```
44
+
45
+ 2. Install dependencies:
46
+ ```bash
47
+ cd examples/sinatra_app
48
+ bundle install
49
+ ```
50
+
51
+ 3. Start the server:
52
+ ```bash
53
+ bundle exec ruby app.rb
54
+ ```
55
+
56
+ 4. Visit http://localhost:4567
57
+
58
+ ### Installing TA-Lib (Optional)
59
+
60
+ To enable technical indicators, install the TA-Lib system library:
61
+
62
+ **macOS:**
63
+ ```bash
64
+ brew install ta-lib
65
+ ```
66
+
67
+ **Ubuntu/Debian:**
68
+ ```bash
69
+ wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
70
+ tar -xzf ta-lib-0.4.0-src.tar.gz
71
+ cd ta-lib/
72
+ ./configure --prefix=/usr
73
+ make
74
+ sudo make install
75
+ sudo ldconfig
76
+ ```
77
+
78
+ **Note:** After installing TA-Lib, you may need to reinstall the gems:
79
+ ```bash
80
+ bundle install
81
+ ```
82
+
83
+ ## Error Handling
84
+
85
+ The app gracefully handles missing indicators by:
86
+ - Starting successfully without TA-Lib
87
+ - Showing helpful error messages when indicator endpoints are accessed
88
+ - Allowing all non-indicator features to work normally
89
+
90
+ If you see errors about indicators, it's expected behavior when TA-Lib is not available.
@@ -0,0 +1,95 @@
1
+ # Troubleshooting the Sinatra App
2
+
3
+ ## Dashboard page loads but buttons/charts don't work
4
+
5
+ This usually means JavaScript errors are preventing the page from functioning.
6
+
7
+ ### Quick Fix Checklist
8
+
9
+ 1. **Install dependencies first:**
10
+ ```bash
11
+ cd examples/sinatra_app
12
+ bundle install
13
+ ```
14
+
15
+ 2. **Start the server with bundle exec:**
16
+ ```bash
17
+ bundle exec ruby app.rb
18
+ # OR
19
+ bundle exec rackup
20
+ ```
21
+
22
+ 3. **Check browser console for errors:**
23
+ - Open browser DevTools (F12 or Ctrl+Shift+I)
24
+ - Go to "Console" tab
25
+ - Look for red error messages
26
+ - Take a screenshot and check what's failing
27
+
28
+ 4. **Check Network tab:**
29
+ - Open DevTools → Network tab
30
+ - Reload the dashboard page
31
+ - Look for failed requests (red status codes)
32
+ - Check if `/api/stock/AAPL`, `/api/indicators/AAPL`, `/api/analyze/AAPL` are returning 200 or errors
33
+
34
+ ### Common Issues
35
+
36
+ #### Issue: "ApexCharts is not defined"
37
+ **Symptom:** Charts don't render, console shows `Uncaught ReferenceError: ApexCharts is not defined`
38
+
39
+ **Fix:** The CDN link for ApexCharts is missing or blocked. Check `views/layout.erb`:
40
+ ```html
41
+ <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
42
+ ```
43
+
44
+ #### Issue: API calls failing with 500 errors
45
+ **Symptom:** Network tab shows `/api/indicators/AAPL` returns 500
46
+
47
+ **Fix:** This is expected if TA-Lib isn't installed. The app should handle this gracefully.
48
+
49
+ #### Issue: Buttons don't respond
50
+ **Symptom:** Clicking buttons does nothing
51
+
52
+ **Possible causes:**
53
+ 1. JavaScript not loaded (check console for errors)
54
+ 2. Event handlers not attached (check if page finished loading)
55
+ 3. JavaScript errors earlier in the file preventing execution
56
+
57
+ ### Debug Mode
58
+
59
+ Run the server with verbose logging:
60
+ ```bash
61
+ RACK_ENV=development bundle exec ruby app.rb
62
+ ```
63
+
64
+ ### Test API Endpoints Manually
65
+
66
+ While server is running, test in another terminal:
67
+
68
+ ```bash
69
+ # Test stock data
70
+ curl -s "http://localhost:4567/api/stock/AAPL?period=30d" | python3 -m json.tool
71
+
72
+ # Test indicators (may fail without TA-Lib - that's OK)
73
+ curl -s "http://localhost:4567/api/indicators/AAPL?period=30d"
74
+
75
+ # Test analysis
76
+ curl -s "http://localhost:4567/api/analyze/AAPL" | python3 -m json.tool
77
+ ```
78
+
79
+ All should return JSON. If you get HTML error pages, there's a server-side issue.
80
+
81
+ ### Check if all gems are installed
82
+
83
+ ```bash
84
+ bundle check
85
+ ```
86
+
87
+ If it says "The Gemfile's dependencies are satisfied", you're good.
88
+
89
+ ### Still not working?
90
+
91
+ Please provide:
92
+ 1. Screenshot of browser console (DevTools → Console tab)
93
+ 2. Screenshot of network tab showing failed requests
94
+ 3. Output of `bundle check`
95
+ 4. Any errors from server console
@@ -35,6 +35,47 @@ helpers do
35
35
  def format_number(value)
36
36
  value.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
37
37
  end
38
+
39
+ # Filter data arrays by time period
40
+ # period can be: "30d", "60d", "90d", "1q", "2q", "3q", "4q", "all"
41
+ def filter_by_period(dates, *data_arrays, period: 'all')
42
+ return [dates, *data_arrays] if period == 'all' || dates.empty?
43
+
44
+ require 'date'
45
+
46
+ # Parse dates (they're strings in YYYY-MM-DD format)
47
+ parsed_dates = dates.map { |d| Date.parse(d) }
48
+ latest_date = parsed_dates.max
49
+
50
+ # Calculate cutoff date based on period
51
+ cutoff_date = case period
52
+ when '30d'
53
+ latest_date - 30
54
+ when '60d'
55
+ latest_date - 60
56
+ when '90d'
57
+ latest_date - 90
58
+ when '1q'
59
+ latest_date - 63 # ~3 months = 1 quarter (63 trading days)
60
+ when '2q'
61
+ latest_date - 126 # ~6 months = 2 quarters
62
+ when '3q'
63
+ latest_date - 189 # ~9 months = 3 quarters
64
+ when '4q'
65
+ latest_date - 252 # ~12 months = 4 quarters
66
+ else
67
+ parsed_dates.min # "all" - keep everything
68
+ end
69
+
70
+ # Find indices where date >= cutoff_date
71
+ indices = parsed_dates.each_with_index.select { |d, i| d >= cutoff_date }.map(&:last)
72
+
73
+ # Filter all arrays by the same indices
74
+ filtered_dates = indices.map { |i| dates[i] }
75
+ filtered_data = data_arrays.map { |arr| indices.map { |i| arr[i] } }
76
+
77
+ [filtered_dates, *filtered_data]
78
+ end
38
79
  end
39
80
 
40
81
  # Routes
@@ -98,38 +139,48 @@ get '/api/stock/:ticker' do
98
139
  content_type :json
99
140
 
100
141
  ticker = params[:ticker].upcase
142
+ period = params[:period] || 'all'
101
143
 
102
144
  begin
103
145
  stock = SQA::Stock.new(ticker: ticker)
104
146
  df = stock.df
105
147
 
106
- # Get price data
107
- dates = df["date"].to_a.map(&:to_s)
148
+ # Get price data (all data first)
149
+ dates = df["timestamp"].to_a.map(&:to_s)
108
150
  opens = df["open_price"].to_a
109
151
  highs = df["high_price"].to_a
110
152
  lows = df["low_price"].to_a
111
153
  closes = df["adj_close_price"].to_a
112
154
  volumes = df["volume"].to_a
113
155
 
156
+ # Filter by period
157
+ filtered_dates, filtered_opens, filtered_highs, filtered_lows, filtered_closes, filtered_volumes =
158
+ filter_by_period(dates, opens, highs, lows, closes, volumes, period: period)
159
+
114
160
  # Calculate basic stats
115
- current_price = closes.last
116
- prev_price = closes[-2]
161
+ current_price = filtered_closes.last
162
+ prev_price = filtered_closes[-2]
117
163
  change = current_price - prev_price
118
164
  change_pct = (change / prev_price) * 100
119
165
 
166
+ # 52-week high/low uses full data for reference
167
+ high_52w = closes.last(252).max rescue closes.max
168
+ low_52w = closes.last(252).min rescue closes.min
169
+
120
170
  {
121
171
  ticker: ticker,
172
+ period: period,
122
173
  current_price: current_price,
123
174
  change: change,
124
175
  change_percent: change_pct,
125
- high_52w: closes.last(252).max,
126
- low_52w: closes.last(252).min,
127
- dates: dates,
128
- open: opens,
129
- high: highs,
130
- low: lows,
131
- close: closes,
132
- volume: volumes
176
+ high_52w: high_52w,
177
+ low_52w: low_52w,
178
+ dates: filtered_dates,
179
+ open: filtered_opens,
180
+ high: filtered_highs,
181
+ low: filtered_lows,
182
+ close: filtered_closes,
183
+ volume: filtered_volumes
133
184
  }.to_json
134
185
  rescue => e
135
186
  status 500
@@ -142,6 +193,7 @@ get '/api/indicators/:ticker' do
142
193
  content_type :json
143
194
 
144
195
  ticker = params[:ticker].upcase
196
+ period = params[:period] || 'all'
145
197
 
146
198
  begin
147
199
  stock = SQA::Stock.new(ticker: ticker)
@@ -150,9 +202,9 @@ get '/api/indicators/:ticker' do
150
202
  prices = df["adj_close_price"].to_a
151
203
  highs = df["high_price"].to_a
152
204
  lows = df["low_price"].to_a
153
- dates = df["date"].to_a.map(&:to_s)
205
+ dates = df["timestamp"].to_a.map(&:to_s)
154
206
 
155
- # Calculate indicators
207
+ # Calculate indicators on full dataset (they need historical context)
156
208
  rsi = SQAI.rsi(prices, period: 14)
157
209
  macd_result = SQAI.macd(prices)
158
210
  bb_result = SQAI.bbands(prices)
@@ -160,18 +212,26 @@ get '/api/indicators/:ticker' do
160
212
  sma_50 = SQAI.sma(prices, period: 50)
161
213
  ema_20 = SQAI.ema(prices, period: 20)
162
214
 
215
+ # Filter results by period (keep indicators aligned with dates)
216
+ filtered_dates, filtered_rsi, filtered_macd, filtered_macd_signal, filtered_macd_hist,
217
+ filtered_bb_upper, filtered_bb_middle, filtered_bb_lower, filtered_sma_20, filtered_sma_50, filtered_ema_20 =
218
+ filter_by_period(dates, rsi, macd_result[0], macd_result[1], macd_result[2],
219
+ bb_result[0], bb_result[1], bb_result[2],
220
+ sma_20, sma_50, ema_20, period: period)
221
+
163
222
  {
164
- dates: dates,
165
- rsi: rsi,
166
- macd: macd_result[0], # MACD line
167
- macd_signal: macd_result[1], # Signal line
168
- macd_hist: macd_result[2], # Histogram
169
- bb_upper: bb_result[0],
170
- bb_middle: bb_result[1],
171
- bb_lower: bb_result[2],
172
- sma_20: sma_20,
173
- sma_50: sma_50,
174
- ema_20: ema_20
223
+ period: period,
224
+ dates: filtered_dates,
225
+ rsi: filtered_rsi,
226
+ macd: filtered_macd,
227
+ macd_signal: filtered_macd_signal,
228
+ macd_hist: filtered_macd_hist,
229
+ bb_upper: filtered_bb_upper,
230
+ bb_middle: filtered_bb_middle,
231
+ bb_lower: filtered_bb_lower,
232
+ sma_20: filtered_sma_20,
233
+ sma_50: filtered_sma_50,
234
+ ema_20: filtered_ema_20
175
235
  }.to_json
176
236
  rescue => e
177
237
  status 500
@@ -6,24 +6,26 @@
6
6
  }
7
7
 
8
8
  :root {
9
- --primary-color: #2196F3;
10
- --secondary-color: #1976D2;
11
- --success-color: #4CAF50;
12
- --danger-color: #F44336;
13
- --warning-color: #FF9800;
14
- --dark-bg: #1a1a2e;
15
- --light-bg: #f5f7fa;
16
- --card-bg: #ffffff;
17
- --text-primary: #2c3e50;
18
- --text-secondary: #7f8c8d;
19
- --border-color: #e0e0e0;
20
- --positive-color: #26a69a;
21
- --negative-color: #ef5350;
9
+ --primary-color: #00d4ff;
10
+ --secondary-color: #00a8cc;
11
+ --success-color: #00ff88;
12
+ --danger-color: #ff3366;
13
+ --warning-color: #ffaa00;
14
+ --dark-bg: #0a0e27;
15
+ --card-bg: #151b3d;
16
+ --card-bg-lighter: #1e2749;
17
+ --text-primary: #e8eaf6;
18
+ --text-secondary: #9fa8da;
19
+ --border-color: #2a3154;
20
+ --positive-color: #00ff88;
21
+ --negative-color: #ff3366;
22
+ --chart-bg: #0f1535;
23
+ --grid-color: #2a3154;
22
24
  }
23
25
 
24
26
  body {
25
27
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
26
- background-color: var(--light-bg);
28
+ background-color: var(--dark-bg);
27
29
  color: var(--text-primary);
28
30
  line-height: 1.6;
29
31
  }
@@ -88,7 +90,7 @@ body {
88
90
 
89
91
  /* Hero Section */
90
92
  .hero {
91
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
93
+ background: linear-gradient(135deg, #1a237e 0%, #4a148c 100%);
92
94
  color: white;
93
95
  padding: 4rem 2rem;
94
96
  text-align: center;
@@ -184,7 +186,7 @@ body {
184
186
  }
185
187
 
186
188
  .btn-secondary {
187
- background: white;
189
+ background: var(--card-bg-lighter);
188
190
  color: var(--text-primary);
189
191
  border: 2px solid var(--border-color);
190
192
  }
@@ -192,6 +194,7 @@ body {
192
194
  .btn-secondary:hover {
193
195
  border-color: var(--primary-color);
194
196
  color: var(--primary-color);
197
+ background: var(--card-bg);
195
198
  }
196
199
 
197
200
  .btn-large {
@@ -202,15 +205,63 @@ body {
202
205
  .btn-small {
203
206
  padding: 0.5rem 1rem;
204
207
  font-size: 0.875rem;
205
- background: white;
208
+ background: var(--card-bg-lighter);
206
209
  color: var(--text-primary);
207
210
  border: 1px solid var(--border-color);
208
211
  }
209
212
 
210
213
  .btn-small.active {
211
214
  background: var(--primary-color);
212
- color: white;
215
+ color: var(--dark-bg);
216
+ border-color: var(--primary-color);
217
+ font-weight: 700;
218
+ }
219
+
220
+ /* Period Selector */
221
+ .period-selector {
222
+ background: var(--card-bg);
223
+ padding: 1.5rem;
224
+ border-radius: 8px;
225
+ margin-bottom: 1.5rem;
226
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
227
+ }
228
+
229
+ .period-selector label {
230
+ font-weight: 600;
231
+ color: var(--text-primary);
232
+ margin-bottom: 0.75rem;
233
+ display: block;
234
+ }
235
+
236
+ .period-buttons {
237
+ display: flex;
238
+ gap: 0.5rem;
239
+ flex-wrap: wrap;
240
+ }
241
+
242
+ .btn-period {
243
+ padding: 0.5rem 1rem;
244
+ font-size: 0.875rem;
245
+ background: var(--card-bg-lighter);
246
+ color: var(--text-primary);
247
+ border: 1px solid var(--border-color);
248
+ border-radius: 4px;
249
+ cursor: pointer;
250
+ transition: all 0.2s;
251
+ font-family: inherit;
252
+ }
253
+
254
+ .btn-period:hover {
255
+ background: var(--card-bg);
256
+ border-color: var(--primary-color);
257
+ color: var(--primary-color);
258
+ }
259
+
260
+ .btn-period.active {
261
+ background: var(--primary-color);
262
+ color: var(--dark-bg);
213
263
  border-color: var(--primary-color);
264
+ font-weight: 700;
214
265
  }
215
266
 
216
267
  /* Container */
@@ -223,7 +274,7 @@ body {
223
274
  /* Features Section */
224
275
  .features {
225
276
  padding: 4rem 0;
226
- background: white;
277
+ background: var(--card-bg);
227
278
  }
228
279
 
229
280
  .features h2 {
@@ -241,7 +292,7 @@ body {
241
292
 
242
293
  .feature-card {
243
294
  padding: 2rem;
244
- background: var(--card-bg);
295
+ background: var(--card-bg-lighter);
245
296
  border-radius: 12px;
246
297
  border: 1px solid var(--border-color);
247
298
  transition: all 0.3s ease;
@@ -249,7 +300,8 @@ body {
249
300
 
250
301
  .feature-card:hover {
251
302
  transform: translateY(-5px);
252
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
303
+ box-shadow: 0 10px 30px rgba(0, 217, 255, 0.2);
304
+ border-color: var(--primary-color);
253
305
  }
254
306
 
255
307
  .feature-icon {
@@ -272,7 +324,7 @@ body {
272
324
  /* Info Section */
273
325
  .info-section {
274
326
  padding: 4rem 0;
275
- background: var(--light-bg);
327
+ background: var(--dark-bg);
276
328
  }
277
329
 
278
330
  .info-content h2 {
@@ -311,7 +363,8 @@ body {
311
363
 
312
364
  /* Dashboard */
313
365
  .dashboard {
314
- max-width: 1400px;
366
+ width: 100%;
367
+ max-width: 100%;
315
368
  margin: 0 auto;
316
369
  padding: 2rem;
317
370
  }
@@ -322,9 +375,10 @@ body {
322
375
  align-items: center;
323
376
  margin-bottom: 2rem;
324
377
  padding: 1.5rem;
325
- background: white;
378
+ background: var(--card-bg);
326
379
  border-radius: 12px;
327
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
380
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
381
+ border: 1px solid var(--border-color);
328
382
  }
329
383
 
330
384
  .ticker-info h1 {
@@ -376,10 +430,12 @@ body {
376
430
  }
377
431
 
378
432
  .metric-card {
379
- background: white;
433
+ background: var(--card-bg);
380
434
  padding: 1.5rem;
381
435
  border-radius: 12px;
382
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
436
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
437
+ border-left: 4px solid var(--primary-color);
438
+ border: 1px solid var(--border-color);
383
439
  border-left: 4px solid var(--primary-color);
384
440
  }
385
441
 
@@ -434,11 +490,12 @@ body {
434
490
 
435
491
  /* Charts */
436
492
  .chart-container {
437
- background: white;
493
+ background: var(--card-bg);
438
494
  padding: 1.5rem;
439
495
  border-radius: 12px;
440
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
496
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
441
497
  margin-bottom: 2rem;
498
+ border: 1px solid var(--border-color);
442
499
  }
443
500
 
444
501
  .chart-header {
@@ -466,16 +523,18 @@ body {
466
523
  }
467
524
 
468
525
  .chart {
469
- min-height: 300px;
526
+ width: 100%;
527
+ height: 500px;
470
528
  }
471
529
 
472
530
  .chart-small {
473
- min-height: 200px;
531
+ width: 100%;
532
+ height: 350px;
474
533
  }
475
534
 
476
535
  .indicators-grid {
477
- display: grid;
478
- grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
536
+ display: flex;
537
+ flex-direction: column;
479
538
  gap: 1.5rem;
480
539
  margin-bottom: 2rem;
481
540
  }
@@ -492,7 +551,7 @@ body {
492
551
  }
493
552
 
494
553
  .results-table thead {
495
- background: var(--light-bg);
554
+ background: var(--card-bg-lighter);
496
555
  }
497
556
 
498
557
  .results-table th,
@@ -500,6 +559,7 @@ body {
500
559
  padding: 1rem;
501
560
  text-align: left;
502
561
  border-bottom: 1px solid var(--border-color);
562
+ color: var(--text-primary);
503
563
  }
504
564
 
505
565
  .results-table th {
@@ -515,7 +575,7 @@ body {
515
575
  }
516
576
 
517
577
  .results-table tr:hover {
518
- background: var(--light-bg);
578
+ background: var(--card-bg-lighter);
519
579
  }
520
580
 
521
581
  .results-table .positive {
@@ -562,13 +622,14 @@ body {
562
622
  }
563
623
 
564
624
  .modal-content {
565
- background-color: white;
625
+ background-color: var(--card-bg);
566
626
  margin: 15% auto;
567
627
  padding: 2rem;
568
628
  border-radius: 12px;
569
629
  width: 90%;
570
630
  max-width: 500px;
571
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
631
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
632
+ border: 1px solid var(--border-color);
572
633
  }
573
634
 
574
635
  .modal-content h2 {
@@ -584,10 +645,13 @@ body {
584
645
  font-size: 1.1rem;
585
646
  margin-bottom: 1rem;
586
647
  outline: none;
648
+ background: var(--card-bg-lighter);
649
+ color: var(--text-primary);
587
650
  }
588
651
 
589
652
  .modal-content input:focus {
590
653
  border-color: var(--primary-color);
654
+ background: var(--card-bg);
591
655
  }
592
656
 
593
657
  .close {