Risk-adjusted performance analysis of Brazilian investment funds
This project provides a comprehensive analytical framework for evaluating Brazilian investment funds (fundos de investimento) through the lens of risk-adjusted performance. Rather than simply ranking funds by raw return, we decompose performance into its risk, consistency, and alpha components to answer a fundamental investment question:
"Quais fundos brasileiros realmente entregaram retorno ajustado ao risco superior ao benchmark — e de onde veio essa performance?"
The analysis covers ~30-50 multimarket funds (multimercado) over the 2019-2025 period, spanning pre-COVID, the March 2020 crash, the recovery rally, and multiple interest rate cycles.
- 15+ quantitative metrics per fund (Sharpe, Sortino, Calmar, VaR, CVaR, rolling alpha, etc.)
- Fund Quality Score — proprietary composite scoring methodology
- Fund Selection Simulator — profile-based fund recommendation engine
- Out-of-sample backtest validating scoring methodology's predictive power
- Power BI dashboard with 7 interactive pages
- Research-grade report (8-15 pages)
The core research question drives every analytical decision:
After controlling for volatility, drawdown, and factor exposure, which Brazilian multimarket funds have consistently delivered superior risk-adjusted returns relative to their benchmark, and what explains this performance?
Sub-questions:
- Is the outperformance persistent (rolling metrics) or concentrated in few periods (attribution)?
- Does the Fund Quality Score have out-of-sample predictive power?
- Which risk profile (conservative/moderate/aggressive) has the most attractive risk-return tradeoff?
| Parameter | Value |
|---|---|
| Fund class | Multimercado (ANBIMA) |
| Minimum AUM | R$100M (average over period) |
| Minimum history | 5 years |
| Status | Active only |
| Period | January 2019 — August 2025 |
| Expected universe size | 30–50 funds |
| Data | Source | Method |
|---|---|---|
| Daily NAV (cotas) | CVM Dados Abertos | HTTP/CSV monthly ZIPs |
| AUM & shareholders | CVM Dados Abertos | HTTP/CSV |
| Fund registry | CVM Dados Abertos | HTTP/CSV |
| CDI | BCB SGS (série 12) | python-bcb API |
| IPCA | BCB SGS (série 433) | python-bcb API |
| Dólar PTAX | BCB SGS (série 1) | python-bcb API |
| Ibovespa | Yahoo Finance (^BVSP) | yfinance |
| IMA-B | Yahoo Finance (IMAB11.SA) | yfinance |
funds ──┬── fund_prices (daily NAV + returns)
├── fund_aum (PL + cotistas)
└── [benchmark] ── benchmark_prices
5 tables in DuckDB (columnar, analytical-optimized). See src/database/schema.sql.
| Metric | Formula | Purpose |
|---|---|---|
| Cumulative return | ∏(1 + rᵢ) - 1 | Total performance |
| Annualized return | (1 + r_cum)^(252/n) - 1 | Comparable across periods |
| Rolling 12M/36M | Moving window (252/756 days) | Temporal stability |
| Alpha | Annualized excess vs benchmark | Skill measurement |
| Metric | Formula | Purpose |
|---|---|---|
| Annualized volatility | σ(r) × √252 | Total risk |
| Downside deviation | σ(min(r - MAR, 0)) × √252 | Downside-only risk |
| VaR 95% | Percentile 5% of returns | Tail risk (1-day) |
| CVaR 95% | E[r | r ≤ VaR] | Expected shortfall |
| Ratio | Formula | Interpretation |
|---|---|---|
| Sharpe | (r_p - r_f) / σ_p | Return per unit of total risk |
| Sortino | (r_p - r_f) / σ_down | Return per unit of downside risk |
| Calmar | r_annual / |MaxDD| | Return per unit of drawdown |
| Information Ratio | α / σ(α) | Active return per tracking error |
Each drawdown episode is decomposed into:
Peak → Queda → Fundo (trough) → Recuperação (recovery)
With metrics: maximum drawdown, duration (trading days), and recovery time.
Proprietary composite score (0-100) with configurable weights:
| Component | Weight | Metric |
|---|---|---|
| Risk-adjusted return | 30% | Annualized Sharpe |
| Drawdown | 20% | Max Drawdown (inverted) |
| Consistency | 15% | % months above benchmark |
| Alpha | 15% | Annualized excess return |
| Downside risk | 10% | Downside deviation (inverted) |
| AUM stability | 10% | Inverse CV of AUM |
Normalization: Min-max within universe, with 5th/95th percentile winsorization.
⚠️ The Fund Quality Score is a proprietary methodology created for comparing funds within the analyzed universe. It does not constitute investment advice or recommendation.
Results are generated dynamically by running the pipeline. See the dashboard and report for current findings.
| Page | Content |
|---|---|
| Overview | Universe summary — # funds, total AUM, average return vs CDI |
| Performance | Cumulative return vs benchmark, comparative table |
| Risk | Volatility, VaR, CVaR — heatmaps and scatter plots |
| Consistency | Rolling Sharpe 12M/24M/36M, rolling alpha |
| Ranking | Fund Quality Score — sortable table with component breakdown |
| Fund Deep Dive | Single-fund analysis: "What explains this fund's performance?" |
| Simulator | Fund Selection by risk profile (Conservador/Moderado/Agressivo) |
The Fund Quality Score is validated out-of-sample:
- Split: 70% training / 30% test
- Train: Compute FQS on training period only
- Select: Top-10 funds by FQS
- Test: Measure their Sharpe in the test period
- Compare: Top-10 vs universe median
Success criterion: Top-10 Sharpe > Universe median Sharpe in the test period.
This section exists intentionally. Acknowledging limitations is not a weakness — it demonstrates analytical rigor and a market-oriented mindset.
| Limitation | Impact | Mitigation |
|---|---|---|
| Survivorship bias | Only active funds are analyzed; failed funds excluded | Explicitly acknowledged; results may overstate average performance |
| Look-ahead bias | Universe selection uses current AUM, not point-in-time | Could be mitigated with historical AUM snapshots (future work) |
| Data availability | CVM publishes D+1; some funds report with delays | Gaps handled via forward-fill; funds with >5% missing data excluded |
| Transaction costs | Not modeled (management fees are embedded in NAV) | Performance fees vary; actual investor returns may differ |
| Liquidity | Redemption periods (D+30, D+60) not considered | Illiquid funds may appear more attractive than accessible alternatives |
| Model assumptions | Normalization assumes returns are roughly symmetric | Winsorization at 5th/95th percentile mitigates outlier distortion |
| Benchmark selection | CDI as default benchmark may not suit all strategies | Benchmark mismatch can inflate/deflate alpha for certain fund types |
- Python 3.10+
- Internet connection (for data download)
# Clone the repository
git clone https://github.com/mvasss/fund-analytics-lab.git
cd fund-analytics-lab
# Create virtual environment
python -m venv venv
venv\Scripts\activate # Windows
source venv/bin/activate # Linux/macOS
# Install dependencies
pip install -r requirements.txt# Full pipeline (download → clean → load → metrics → score → export)
python scripts/run_pipeline.py
# Or run individual steps
python scripts/run_pipeline.py --step 1 # Download only
python scripts/run_pipeline.py --step 4 # Metrics only (requires DB)
# Export data for dashboard
python scripts/export_to_dashboard.pypytest tests/ -vfund-analytics-lab/
├── README.md
├── requirements.txt
├── config/
│ ├── settings.py # Global configuration
│ ├── fund_universe.yaml # Universe definition
│ └── scoring_weights.yaml # FQS weight configuration
├── data/
│ ├── raw/ # Raw CVM/BCB CSVs
│ └── processed/ # Processed Parquet files
├── src/
│ ├── ingestion/ # Data download (CVM, BCB, yfinance)
│ ├── cleaning/ # Validation & universe filtering
│ ├── database/ # DuckDB schema, manager, queries
│ ├── features/ # Metrics (returns, risk, performance, drawdown, rolling)
│ ├── scoring/ # Fund Quality Score
│ ├── analysis/ # Attribution & consistency
│ ├── backtesting/ # Out-of-sample validation
│ └── simulator/ # Fund Selection Simulator
├── notebooks/ # Exploratory analysis
├── dashboard/ # Power BI files
├── reports/ # Investment report PDF
├── tests/ # pytest test suite
└── scripts/ # Pipeline orchestration
The Power BI dashboard consumes pre-aggregated Parquet files from data/processed/dashboard/.
To regenerate dashboard data:
python scripts/export_to_dashboard.pyMIT