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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/CLAUDE.md +21 -0
- data/README.md +56 -32
- data/docs/api/dataframe.md +0 -1
- data/docs/assets/images/sqa.jpg +0 -0
- data/docs/concepts/index.md +2 -10
- data/docs/data_frame.md +0 -1
- data/docs/getting-started/index.md +0 -16
- data/docs/getting-started/installation.md +2 -2
- data/docs/getting-started/quick-start.md +4 -4
- data/docs/index.md +0 -2
- data/docs/strategies/bollinger-bands.md +1 -1
- data/docs/strategies/rsi.md +1 -1
- data/examples/sinatra_app/Gemfile +20 -0
- data/examples/sinatra_app/Gemfile.lock +268 -0
- data/examples/sinatra_app/QUICKSTART.md +13 -3
- data/examples/sinatra_app/README.md +12 -2
- data/examples/sinatra_app/RUNNING_WITHOUT_TALIB.md +90 -0
- data/examples/sinatra_app/TROUBLESHOOTING.md +95 -0
- data/examples/sinatra_app/app.rb +85 -25
- data/examples/sinatra_app/public/css/style.css +101 -37
- data/examples/sinatra_app/public/debug_macd.html +82 -0
- data/examples/sinatra_app/start.sh +53 -0
- data/examples/sinatra_app/views/dashboard.erb +558 -146
- data/examples/sinatra_app/views/layout.erb +2 -2
- data/lib/sqa/data_frame/alpha_vantage.rb +13 -3
- data/lib/sqa/data_frame.rb +21 -15
- data/lib/sqa/indicator.rb +17 -4
- data/lib/sqa/stock.rb +73 -11
- data/lib/sqa/ticker.rb +9 -2
- data/lib/sqa/version.rb +1 -1
- data/lib/sqa.rb +12 -4
- data/mkdocs.yml +4 -40
- metadata +7 -21
- data/docs/alpha_vantage_technical_indicators.md +0 -62
- data/docs/average_true_range.md +0 -9
- data/docs/bollinger_bands.md +0 -15
- data/docs/candlestick_pattern_recognizer.md +0 -4
- data/docs/donchian_channel.md +0 -5
- data/docs/double_top_bottom_pattern.md +0 -3
- data/docs/exponential_moving_average.md +0 -19
- data/docs/fibonacci_retracement.md +0 -30
- data/docs/head_and_shoulders_pattern.md +0 -3
- data/docs/market_profile.md +0 -4
- data/docs/momentum.md +0 -19
- data/docs/moving_average_convergence_divergence.md +0 -23
- data/docs/peaks_and_valleys.md +0 -11
- data/docs/relative_strength_index.md +0 -6
- data/docs/simple_moving_average.md +0 -8
- data/docs/stochastic_oscillator.md +0 -4
- data/docs/ta_lib.md +0 -160
- data/docs/true_range.md +0 -12
- data/docs/true_strength_index.md +0 -46
- 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
|
data/examples/sinatra_app/app.rb
CHANGED
|
@@ -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["
|
|
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 =
|
|
116
|
-
prev_price =
|
|
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:
|
|
126
|
-
low_52w:
|
|
127
|
-
dates:
|
|
128
|
-
open:
|
|
129
|
-
high:
|
|
130
|
-
low:
|
|
131
|
-
close:
|
|
132
|
-
volume:
|
|
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["
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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: #
|
|
10
|
-
--secondary-color: #
|
|
11
|
-
--success-color: #
|
|
12
|
-
--danger-color: #
|
|
13
|
-
--warning-color: #
|
|
14
|
-
--dark-bg: #
|
|
15
|
-
--
|
|
16
|
-
--card-bg: #
|
|
17
|
-
--text-primary: #
|
|
18
|
-
--text-secondary: #
|
|
19
|
-
--border-color: #
|
|
20
|
-
--positive-color: #
|
|
21
|
-
--negative-color: #
|
|
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(--
|
|
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, #
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
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(--
|
|
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
|
-
|
|
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:
|
|
378
|
+
background: var(--card-bg);
|
|
326
379
|
border-radius: 12px;
|
|
327
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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
|
-
|
|
526
|
+
width: 100%;
|
|
527
|
+
height: 500px;
|
|
470
528
|
}
|
|
471
529
|
|
|
472
530
|
.chart-small {
|
|
473
|
-
|
|
531
|
+
width: 100%;
|
|
532
|
+
height: 350px;
|
|
474
533
|
}
|
|
475
534
|
|
|
476
535
|
.indicators-grid {
|
|
477
|
-
display:
|
|
478
|
-
|
|
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(--
|
|
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(--
|
|
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:
|
|
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.
|
|
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 {
|