source: db-scripts/p6_q4_annual_investing_diversification_concentration.sql@ 89156c1

Last change on this file since 89156c1 was 89156c1, checked in by Andrej <asumanovski@…>, 3 months ago

Phase 6 documentation

  • Property mode set to 100644
File size: 4.3 KB
Line 
1SET search_path TO trekr;
2
3-- P6 Q4 (Annual): Investing diversification, concentration risk, and contribution pacing.
4-- Change report_year in params CTE when needed.
5WITH params AS (
6 SELECT 2026::int AS report_year
7),
8months AS (
9 SELECT generate_series(1, 12) AS month_no
10),
11investor_base AS (
12 SELECT
13 iu.user_id,
14 u.username,
15 u.email
16 FROM investor_users iu
17 JOIN users u ON u.user_id = iu.user_id
18),
19annual_asset_lots AS (
20 SELECT
21 a.user_id,
22 a.ticker_symbol,
23 COALESCE(a.quantity, 0) AS quantity,
24 COALESCE(a.buy_price, 0) AS buy_price,
25 COALESCE(a.quantity, 0) * COALESCE(a.buy_price, 0) AS invested_amount,
26 a.buy_date
27 FROM assets a
28 WHERE EXTRACT(YEAR FROM a.buy_date)::int = (SELECT report_year FROM params)
29),
30ticker_rollup AS (
31 SELECT
32 aal.user_id,
33 aal.ticker_symbol,
34 SUM(aal.quantity) AS total_quantity,
35 SUM(aal.invested_amount) AS total_invested_amount,
36 COUNT(*) AS lot_count,
37 MIN(aal.buy_date) AS first_buy_date,
38 MAX(aal.buy_date) AS last_buy_date
39 FROM annual_asset_lots aal
40 GROUP BY aal.user_id, aal.ticker_symbol
41),
42portfolio_totals AS (
43 SELECT
44 user_id,
45 SUM(total_invested_amount) AS annual_total_invested,
46 SUM(lot_count) AS annual_lot_count,
47 COUNT(*) AS distinct_tickers
48 FROM ticker_rollup
49 GROUP BY user_id
50),
51weights AS (
52 SELECT
53 tr.user_id,
54 tr.ticker_symbol,
55 tr.total_invested_amount,
56 pt.annual_total_invested,
57 (tr.total_invested_amount / NULLIF(pt.annual_total_invested, 0)) AS position_weight,
58 DENSE_RANK() OVER (
59 PARTITION BY tr.user_id
60 ORDER BY tr.total_invested_amount DESC, tr.ticker_symbol ASC
61 ) AS position_rank
62 FROM ticker_rollup tr
63 JOIN portfolio_totals pt ON pt.user_id = tr.user_id
64),
65concentration AS (
66 SELECT
67 user_id,
68 SUM(position_weight * position_weight) AS hhi_concentration,
69 MAX(position_weight) AS top_position_weight,
70 MAX(ticker_symbol) FILTER (WHERE position_rank = 1) AS top_ticker
71 FROM weights
72 GROUP BY user_id
73),
74monthly_investment AS (
75 SELECT
76 ib.user_id,
77 m.month_no,
78 COALESCE(SUM(a.quantity * a.buy_price), 0) AS monthly_invested_amount
79 FROM investor_base ib
80 CROSS JOIN months m
81 LEFT JOIN assets a
82 ON a.user_id = ib.user_id
83 AND EXTRACT(YEAR FROM a.buy_date)::int = (SELECT report_year FROM params)
84 AND EXTRACT(MONTH FROM a.buy_date)::int = m.month_no
85 GROUP BY ib.user_id, m.month_no
86),
87monthly_investment_stats AS (
88 SELECT
89 user_id,
90 AVG(monthly_invested_amount) AS avg_monthly_contribution,
91 STDDEV_SAMP(monthly_invested_amount) AS contribution_stddev,
92 COUNT(*) FILTER (WHERE monthly_invested_amount > 0) AS active_investing_months
93 FROM monthly_investment
94 GROUP BY user_id
95)
96SELECT
97 ib.user_id,
98 ib.username,
99 ib.email,
100 COALESCE(pt.annual_total_invested, 0) AS annual_total_invested,
101 COALESCE(pt.annual_lot_count, 0) AS annual_lot_count,
102 COALESCE(pt.distinct_tickers, 0) AS distinct_tickers,
103 ROUND(COALESCE(ms.avg_monthly_contribution, 0)::numeric, 2) AS avg_monthly_contribution,
104 COALESCE(ms.active_investing_months, 0) AS active_investing_months,
105 ROUND((COALESCE(ms.active_investing_months, 0) / 12.0)::numeric, 4) AS activity_ratio,
106 ROUND(COALESCE(c.hhi_concentration, 0)::numeric, 4) AS hhi_concentration,
107 ROUND((1 - COALESCE(c.hhi_concentration, 1))::numeric, 4) AS diversification_index,
108 ROUND(COALESCE(c.top_position_weight, 0)::numeric, 4) AS top_position_weight,
109 c.top_ticker,
110 ROUND((COALESCE(ms.contribution_stddev, 0) / NULLIF(ms.avg_monthly_contribution, 0))::numeric, 4) AS contribution_volatility_cv,
111 DENSE_RANK() OVER (
112 ORDER BY
113 (1 - COALESCE(c.hhi_concentration, 1)) DESC,
114 COALESCE(pt.annual_total_invested, 0) DESC,
115 COALESCE(ms.active_investing_months, 0) DESC,
116 ib.user_id ASC
117 ) AS investing_annual_rank
118FROM investor_base ib
119LEFT JOIN portfolio_totals pt ON pt.user_id = ib.user_id
120LEFT JOIN concentration c ON c.user_id = ib.user_id
121LEFT JOIN monthly_investment_stats ms ON ms.user_id = ib.user_id
122ORDER BY investing_annual_rank, ib.user_id;
123
Note: See TracBrowser for help on using the repository browser.