| 5 | | |
| | 5 | === Извештај за диверзификација на портфолио (report_portfolio_diversification) === |
| | 6 | |
| | 7 | Анализата на перформанси се врши врз основа на моменталната состојба во базата. |
| | 8 | |
| | 9 | '''SQL:''' |
| | 10 | {{{ |
| | 11 | -- Core query inside report_portfolio_diversification() |
| | 12 | WITH holding_values AS ( |
| | 13 | SELECT ph.portfolio_id AS hv_portfolio_id, |
| | 14 | ph.stock_id AS hv_stock_id, |
| | 15 | s.symbol AS hv_symbol, |
| | 16 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 17 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 18 | FROM portfolio_holdings ph |
| | 19 | JOIN stock s ON ph.stock_id = s.id |
| | 20 | WHERE ph.quantity > 0 |
| | 21 | ), |
| | 22 | stock_weights AS ( |
| | 23 | SELECT hv.hv_portfolio_id, |
| | 24 | hv.hv_stock_id, |
| | 25 | hv.hv_symbol, |
| | 26 | SUM(hv.market_value) AS stock_market_value, |
| | 27 | SUM(hv.unrealized_pnl) AS stock_unrealized_pnl, |
| | 28 | COUNT(*) AS lots_count |
| | 29 | FROM holding_values hv |
| | 30 | GROUP BY hv.hv_portfolio_id, hv.hv_stock_id, hv.hv_symbol |
| | 31 | ), |
| | 32 | portfolio_totals AS ( |
| | 33 | SELECT sw.hv_portfolio_id, |
| | 34 | SUM(sw.stock_market_value) AS total_holdings_value, |
| | 35 | SUM(sw.stock_unrealized_pnl) AS total_unrealized_pnl, |
| | 36 | COUNT(DISTINCT sw.hv_stock_id) AS num_stocks, |
| | 37 | SUM(sw.lots_count) AS total_lots |
| | 38 | FROM stock_weights sw |
| | 39 | GROUP BY sw.hv_portfolio_id |
| | 40 | ) |
| | 41 | SELECT u.id, u.username, p.id, p.balance, ... |
| | 42 | FROM users u |
| | 43 | JOIN portfolios p ON u.id = p.user_id |
| | 44 | LEFT JOIN portfolio_totals pt ON p.id = pt.hv_portfolio_id |
| | 45 | WHERE u.role = 'USER' |
| | 46 | ORDER BY hhi_score ASC, total_portfolio_value DESC; |
| | 47 | }}} |
| | 48 | |
| | 49 | |
| | 50 | '''EXPLAIN ANALYZE без индекси:''' |
| | 51 | {{{ |
| | 52 | EXPLAIN ANALYZE |
| | 53 | SELECT ph.portfolio_id, ph.stock_id, ph.quantity, s.current_price, s.symbol |
| | 54 | FROM portfolio_holdings ph |
| | 55 | JOIN stock s ON ph.stock_id = s.id |
| | 56 | WHERE ph.quantity > 0; |
| | 57 | |
| | 58 | -- резултат: |
| | 59 | Hash Join (cost=11.57..34.17 rows=313 width=544) (actual time=0.105..0.116 rows=4 loops=1) |
| | 60 | Hash Cond: (ph.stock_id = s.id) |
| | 61 | -> Seq Scan on portfolio_holdings ph (cost=0.00..21.75 rows=313 width=20) (actual time=0.034..0.042 rows=4 loops=1) |
| | 62 | Filter: (quantity > 0) |
| | 63 | -> Hash (cost=10.70..10.70 rows=70 width=532) (actual time=0.036..0.037 rows=4 loops=1) |
| | 64 | Buckets: 1024 Batches: 1 Memory Usage: 9kB |
| | 65 | -> Seq Scan on stock s (cost=0.00..10.70 rows=70 width=532) (actual time=0.019..0.020 rows=4 loops=1) |
| | 66 | Planning Time: 1.315 ms |
| | 67 | Execution Time: 0.180 ms |
| | 68 | }}} |
| | 69 | |
| | 70 | '''Индекси:''' |
| | 71 | |
| | 72 | **1. portfolio_holdings - Index (portfolio_id)** |
| | 73 | {{{ |
| | 74 | CREATE INDEX idx_ph_portfolio_id |
| | 75 | ON portfolio_holdings(portfolio_id); |
| | 76 | }}} |
| | 77 | Користење: GROUP BY и JOIN по `portfolio_id` во CTE-ата `stock_weights` и `portfolio_totals` |
| | 78 | |
| | 79 | Подобрување: Index Scan наместо Seq Scan при групирање по портфолио |
| | 80 | |
| | 81 | --- |
| | 82 | |
| | 83 | **2. portfolio_holdings - Index (stock_id)** |
| | 84 | {{{ |
| | 85 | CREATE INDEX idx_ph_stock_id |
| | 86 | ON portfolio_holdings(stock_id); |
| | 87 | }}} |
| | 88 | Користење: JOIN `ph.stock_id = s.id` во `holding_values` CTE |
| | 89 | |
| | 90 | Подобрување: Директен lookup наместо Hash Join со Seq Scan |
| | 91 | |
| | 92 | --- |
| | 93 | |
| | 94 | **3. portfolio_holdings - Partial Index (quantity > 0)** |
| | 95 | {{{ |
| | 96 | CREATE INDEX idx_ph_quantity_positive |
| | 97 | ON portfolio_holdings(portfolio_id, stock_id) |
| | 98 | WHERE quantity > 0; |
| | 99 | }}} |
| | 100 | Користење: WHERE филтер `ph.quantity > 0` во `holding_values` CTE |
| | 101 | |
| | 102 | Подобрување: Индексот ги содржи само редовите со quantity > 0, елиминирајќи ги нулираните холдинзи уште при скенирањето |
| | 103 | |
| | 104 | --- |
| | 105 | |
| | 106 | **4. users - Index (role)** |
| | 107 | {{{ |
| | 108 | CREATE INDEX idx_users_role |
| | 109 | ON users(role); |
| | 110 | }}} |
| | 111 | Користење: WHERE филтер `u.role = 'USER'` во финалниот SELECT |
| | 112 | |
| | 113 | Подобрување: Директен Index Scan наместо Seq Scan на целата users табела |
| | 114 | |
| | 115 | --- |
| | 116 | |
| | 117 | '''EXPLAIN ANALYZE со индекси:''' |
| | 118 | {{{ |
| | 119 | EXPLAIN ANALYZE |
| | 120 | SELECT ph.portfolio_id, ph.stock_id, ph.quantity, s.current_price, s.symbol |
| | 121 | FROM portfolio_holdings ph |
| | 122 | JOIN stock s ON ph.stock_id = s.id |
| | 123 | WHERE ph.quantity > 0; |
| | 124 | |
| | 125 | -- резултат: |
| | 126 | Nested Loop (cost=0.14..9.44 rows=1 width=544) (actual time=0.034..0.042 rows=4 loops=1) |
| | 127 | -> Seq Scan on portfolio_holdings ph (cost=0.00..1.05 rows=1 width=20) (actual time=0.015..0.017 rows=4 loops=1) |
| | 128 | Filter: (quantity > 0) |
| | 129 | -> Index Scan using stock_pkey on stock s (cost=0.14..8.16 rows=1 width=532) (actual time=0.005..0.005 rows=1 loops=4) |
| | 130 | Index Cond: (id = ph.stock_id) |
| | 131 | Planning Time: 0.724 ms |
| | 132 | Execution Time: 0.086 ms |
| | 133 | }}} |
| | 134 | |
| | 135 | '''Заклучок:''' |
| | 136 | Имаме забрзување. |
| | 137 | |
| | 138 | |
| | 139 | ---- |
| | 140 | === Извештај за волатилност на акции (report_stock_volatility_and_activity) === |
| | 141 | '''SQL:''' |
| | 142 | {{{ |
| | 143 | -- Core query inside report_stock_volatility_and_activity() |
| | 144 | WITH daily_returns AS ( |
| | 145 | SELECT sh.stock_id AS dr_stock_id, |
| | 146 | sh.price AS dr_price, |
| | 147 | (sh.price - LAG(sh.price) OVER ( |
| | 148 | PARTITION BY sh.stock_id ORDER BY sh.timestamp)) |
| | 149 | / NULLIF(LAG(sh.price) OVER ( |
| | 150 | PARTITION BY sh.stock_id ORDER BY sh.timestamp), 0) |
| | 151 | AS dr_daily_return |
| | 152 | FROM stock_history sh |
| | 153 | WHERE sh.timestamp >= p_from_date |
| | 154 | AND sh.timestamp <= p_to_date |
| | 155 | ), |
| | 156 | price_stats AS ( |
| | 157 | SELECT dr.dr_stock_id, |
| | 158 | COUNT(*) AS ps_total_points, |
| | 159 | MIN(dr.dr_price) AS ps_min_price, |
| | 160 | MAX(dr.dr_price) AS ps_max_price, |
| | 161 | AVG(dr.dr_price) AS ps_avg_price, |
| | 162 | COALESCE(STDDEV(dr.dr_daily_return) * 100, 0) AS ps_volatility_pct |
| | 163 | FROM daily_returns dr |
| | 164 | GROUP BY dr.dr_stock_id |
| | 165 | ), |
| | 166 | txn_stats AS ( |
| | 167 | SELECT t.stock_id AS ts_stock_id, |
| | 168 | SUM(t.price * t.quantity) AS ts_total_volume, |
| | 169 | COUNT(*) AS ts_total_count, |
| | 170 | COUNT(*) FILTER (WHERE t.type = 'BUY') AS ts_buy_count, |
| | 171 | COUNT(*) FILTER (WHERE t.type = 'SELL') AS ts_sell_count |
| | 172 | FROM transactions t |
| | 173 | WHERE t.timestamp >= p_from_date |
| | 174 | AND t.timestamp <= p_to_date |
| | 175 | GROUP BY t.stock_id |
| | 176 | ) |
| | 177 | SELECT s.id, s.symbol, s.name, s.current_price, ... |
| | 178 | FROM stock s |
| | 179 | LEFT JOIN price_stats ps ON s.id = ps.dr_stock_id |
| | 180 | LEFT JOIN txn_stats ts ON s.id = ts.ts_stock_id |
| | 181 | ORDER BY historical_volatility_pct DESC, activity_score DESC; |
| | 182 | }}} |
| | 183 | |
| | 184 | |
| | 185 | |
| | 186 | '''EXPLAIN ANALYZE без индекси:''' |
| | 187 | {{{ |
| | 188 | EXPLAIN ANALYZE |
| | 189 | SELECT sh.stock_id, sh.price, sh.timestamp |
| | 190 | FROM stock_history sh |
| | 191 | WHERE sh.timestamp >= '2024-01-01' |
| | 192 | AND sh.timestamp <= '2024-12-31' |
| | 193 | ORDER BY sh.stock_id, sh.timestamp; |
| | 194 | |
| | 195 | -- резултат: |
| | 196 | Sort (cost=31.85..31.87 rows=7 width=20) (actual time=0.051..0.052 rows=0 loops=1) |
| | 197 | Sort Key: stock_id, "timestamp" |
| | 198 | Sort Method: quicksort Memory: 25kB |
| | 199 | -> Seq Scan on stock_history sh (cost=0.00..31.75 rows=7 width=20) (actual time=0.018..0.018 rows=0 loops=1) |
| | 200 | Filter: (("timestamp" >= '2024-01-01'::date) AND ("timestamp" <= '2024-12-31'::date)) |
| | 201 | Rows Removed by Filter: 8 |
| | 202 | Planning Time: 0.417 ms |
| | 203 | Execution Time: 0.077 ms |
| | 204 | }}} |
| | 205 | |
| | 206 | |
| | 207 | '''Индекси:''' |
| | 208 | |
| | 209 | **1. stock_history - Composite Index (stock_id, timestamp)** |
| | 210 | {{{ |
| | 211 | CREATE INDEX idx_sh_stock_id_timestamp |
| | 212 | ON stock_history(stock_id, timestamp); |
| | 213 | }}} |
| | 214 | Користење: `PARTITION BY sh.stock_id ORDER BY sh.timestamp` во LAG прозорската функција и WHERE филтерот за дати |
| | 215 | |
| | 216 | Подобрување: Composite индексот ги покрива и партиционирањето и сортирањето -= елиминира Sort чекор |
| | 217 | |
| | 218 | --- |
| | 219 | |
| | 220 | **2. transactions - Composite Index (stock_id, timestamp)** |
| | 221 | {{{ |
| | 222 | CREATE INDEX idx_tx_stock_id_timestamp |
| | 223 | ON transactions(stock_id, timestamp); |
| | 224 | }}} |
| | 225 | Користење: GROUP BY `t.stock_id` и WHERE на `timestamp` во `txn_stats` |
| | 226 | |
| | 227 | Подобрување: Директен Index Scan наместо Seq Scan + Sort при временски филтрирани агрегации |
| | 228 | |
| | 229 | --- |
| | 230 | |
| | 231 | **3. watchlist - Index (stock_id)** |
| | 232 | {{{ |
| | 233 | CREATE INDEX idx_watchlist_stock_id |
| | 234 | ON watchlist(stock_id); |
| | 235 | }}} |
| | 236 | Користење: GROUP BY `stock_id` во `watchlist_stats` |
| | 237 | |
| | 238 | Подобрување: Index Scan при агрегација по акција наместо Seq Scan |
| | 239 | |
| | 240 | --- |
| | 241 | |
| | 242 | **4. trade_request - Composite Index (stock_symbol, status)** |
| | 243 | {{{ |
| | 244 | CREATE INDEX idx_tr_symbol_status |
| | 245 | ON trade_request(stock_symbol, status); |
| | 246 | }}} |
| | 247 | Користење: JOIN `tr.stock_symbol = s.symbol` и WHERE `tr.status = 'PENDING'` во `pending_stats` |
| | 248 | |
| | 249 | Подобрување: Composite индексот ги покрива и JOIN условот и WHERE филтерот истовремено |
| | 250 | |
| | 251 | --- |
| | 252 | |
| | 253 | '''EXPLAIN ANALYZE со индекси:''' |
| | 254 | {{{ |
| | 255 | EXPLAIN ANALYZE |
| | 256 | SELECT sh.stock_id, sh.price, sh.timestamp |
| | 257 | FROM stock_history sh |
| | 258 | WHERE sh.timestamp >= '2024-01-01' |
| | 259 | AND sh.timestamp <= '2024-12-31' |
| | 260 | ORDER BY sh.stock_id, sh.timestamp; |
| | 261 | |
| | 262 | -- резултат: |
| | 263 | Sort (cost=1.13..1.14 rows=1 width=20) (actual time=0.024..0.025 rows=0 loops=1) |
| | 264 | Sort Key: stock_id, "timestamp" |
| | 265 | Sort Method: quicksort Memory: 25kB |
| | 266 | -> Seq Scan on stock_history sh (cost=0.00..1.12 rows=1 width=20) (actual time=0.017..0.017 rows=0 loops=1) |
| | 267 | Filter: (("timestamp" >= '2024-01-01'::date) AND ("timestamp" <= '2024-12-31'::date)) |
| | 268 | Rows Removed by Filter: 8 |
| | 269 | Planning Time: 0.397 ms |
| | 270 | Execution Time: 0.054 ms |
| | 271 | }}} |
| | 272 | |
| | 273 | '''Заклучок:''' |
| | 274 | Имаме забрзување. |