CLI · commit 11862a2

folioperf

Investment performance analytics: XIRR, TWR, benchmarks & attribution

Download folioperf.zip
Quickstart
unzip folioperf.zip && cd folioperf
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/python -m pytest -q

folioperf

Investors rarely know their real return. folioperf reads your transactions and price history (funds, stocks, EPF/PPF, FDs) and computes the money-weighted return (XIRR), the time-weighted return, benchmark-equivalent returns using your own cash flows, attribution of where the return came from, and the cost of fees. It runs as a CLI and writes a self-contained HTML report.

Features

Install

uv venv .venv
uv pip install -p .venv/bin/python -e '.[dev]'
source .venv/bin/activate

Quickstart

python examples/generate.py            # regenerate the synthetic data (already checked in)
cd examples/portfolio
folioperf summary
folioperf xirr --by instrument --from 2021-04-01
folioperf twr --rolling 3y
folioperf vs-benchmark
folioperf attribution --period 2025
folioperf drawdowns
folioperf report -o report.html

Point at other data with --data DIR or FOLIOPERF_DATA=DIR.

Input files

All files live in one directory.

File Columns
transactions.csv date, account, instrument, type, units, amount, price
prices.csv date, instrument, price (funds, stocks and benchmark indices)
instruments.csv (optional) instrument, name, asset_class, category, expense_ratio, benchmark, direct_equivalent
riskfree.csv (optional) date, rate (annual percent)
targets.csv (optional) asset_class, weight, benchmark

Transaction types: buy, sip, sell, switch-in, switch-out, dividend, payout, fee, deposit, withdrawal, interest, plus split (ratio in units) and bonus (free units in units). Either units or amount may be omitted, and it is derived from the price series. Dates may be YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY or DD-Mon-YYYY. A dividend with units is reinvested; without units it is cash received. Instruments with no price history (EPF, PPF, FD) are balances at a price of 1: deposit, interest, withdrawal. Switch-out and switch-in rows on the same date and account are paired so cost basis carries over.

Importers:

folioperf import cas statement.csv -o transactions.csv            # folio, scheme, date, units, nav, amount[, description]
folioperf import tradebook trades.csv --account zerodha -o t.csv   # trade_date, symbol, trade_type, quantity, price[, brokerage]

Usage with real output

All of the following were run in examples/portfolio, an 8-year synthetic portfolio (six fund SIPs, one switch, stock trades with a split and a bonus, EPF, PPF and FDs, seeded GBM prices).

Common options on analysis commands: --data, --as-of, --from, --to, --account, --instrument, --asset-class, --rf. twr and vs-benchmark also accept --benchmark ID; xirr and vs-benchmark accept --by portfolio|account|instrument|asset-class|category; attribution accepts --period 2025|2025-Q2|2025-04|FY2024-25.

$ folioperf summary
Holdings as of 2026-03-31                                                                  
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┓
┃ Instrument ┃ Class         ┃      Units ┃       Cost ┃      Value ┃ Unrealised ┃   XIRR ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━┩
│ DEBT_DIR   │ Debt          │ 20,198.849 │  ₹6,72,000 │  ₹9,09,510 │  ₹2,37,510 │  7.44% │
│ EPF        │ Debt          │          - │ ₹10,84,860 │ ₹15,31,408 │  ₹4,46,548 │  9.12% │
│ FD_SBI2    │ Debt          │          - │  ₹3,00,000 │  ₹3,44,755 │    ₹44,755 │  6.51% │
│ FLEXI_DIR  │ Equity        │ 20,743.739 │ ₹20,26,442 │ ₹29,33,463 │  ₹9,07,022 │ 11.86% │
│ HDFCBANK   │ Equity        │    235.000 │  ₹4,46,963 │  ₹7,11,489 │  ₹2,64,526 │  9.65% │
│ INDEX_DIR  │ Equity        │ 16,671.610 │  ₹5,50,673 │  ₹7,47,835 │  ₹1,97,162 │  0.77% │
│ INTL_DIR   │ International │ 13,284.448 │  ₹3,45,000 │  ₹4,87,845 │  ₹1,42,845 │ 12.00% │
│ LARGE_DIR  │ Equity        │  6,346.491 │  ₹5,24,327 │  ₹6,59,475 │  ₹1,35,149 │ 11.03% │
│ MID_DIR    │ Equity        │  5,357.199 │  ₹8,15,100 │ ₹10,74,224 │  ₹2,59,124 │  7.95% │
│ PPF        │ Debt          │          - │ ₹12,00,000 │ ₹16,57,941 │  ₹4,57,941 │  7.17% │
│ RELIANCE   │ Equity        │    571.000 │  ₹2,91,539 │  ₹3,05,811 │    ₹14,273 │  4.10% │
│ SMALL_REG  │ Equity        │  6,684.158 │  ₹4,92,000 │  ₹7,24,511 │  ₹2,32,511 │ 11.19% │
│ TCS        │ Equity        │     77.000 │  ₹1,97,138 │  ₹8,84,663 │  ₹6,87,525 │ 31.73% │
└────────────┴───────────────┴────────────┴────────────┴────────────┴────────────┴────────┘

Portfolio                                         
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Metric                          ┃        Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ Current value                   │ ₹1,29,72,931 │
│ Net invested (window flows)     │   ₹81,11,823 │
│ Realised gains (all time)       │    ₹6,33,115 │
│ Fees and commissions (all time) │         ₹868 │
│ XIRR                            │       10.61% │
│ TWR (cumulative)                │      162.69% │
│ TWR (annualised)                │       12.85% │
│ Modified Dietz                  │      109.78% │
└─────────────────────────────────┴──────────────┘

$ folioperf xirr --by asset-class
XIRR by asset-class, 2018-04-05 to 2026-03-31              
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Asset-Class   ┃   Invested ┃      Value ┃   XIRR ┃ Note ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ Debt          │ ₹37,56,860 │ ₹44,43,614 │  7.41% │      │
│ Equity        │ ₹59,15,934 │ ₹80,41,472 │ 12.80% │      │
│ International │  ₹3,45,000 │  ₹4,87,845 │ 12.00% │      │
└───────────────┴────────────┴────────────┴────────┴──────┘

$ folioperf xirr --by instrument --from 2021-04-01
XIRR by instrument, 2021-04-01 to 2026-03-31            
┏━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Instrument ┃   Invested ┃      Value ┃   XIRR ┃ Note ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ DEBT_DIR   │  ₹7,04,381 │  ₹9,09,510 │  7.32% │      │
│ EPF        │ ₹11,43,653 │ ₹15,31,408 │  8.83% │      │
│ FD_SBI     │  ₹5,71,380 │         ₹0 │  4.89% │      │
│ FD_SBI2    │  ₹3,00,000 │  ₹3,44,755 │  6.51% │      │
│ FLEXI_DIR  │ ₹25,66,151 │ ₹29,33,463 │  3.65% │      │
│ HDFCBANK   │  ₹7,20,234 │  ₹7,11,489 │  0.08% │      │
│ INDEX_DIR  │  ₹7,32,871 │  ₹7,47,835 │  0.77% │      │
│ INTL_DIR   │  ₹3,53,407 │  ₹4,87,845 │ 11.20% │      │
│ LARGE_DIR  │ ₹11,29,587 │  ₹6,59,475 │  3.14% │      │
│ MID_DIR    │ ₹10,29,653 │ ₹10,74,224 │  1.20% │      │
│ PPF        │ ₹12,67,472 │ ₹16,57,941 │  7.16% │      │
│ RELIANCE   │  ₹4,09,261 │  ₹3,05,811 │ -1.99% │      │
│ SMALL_REG  │  ₹5,92,758 │  ₹7,24,511 │  5.78% │      │
│ TCS        │ ₹11,15,906 │  ₹8,84,663 │  6.69% │      │
└────────────┴────────────┴────────────┴────────┴──────┘

$ folioperf twr --rolling 1y,3y,5y
Time-weighted return                                    
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Metric                                     ┃   Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩
│ TWR (day-linked), 2018-04-06 to 2026-03-31 │ 162.69% │
│ Annualised                                 │  12.85% │
│ Modified Dietz (cross-check)               │ 109.78% │
│ blended benchmark cumulative               │ 132.51% │
│ blended benchmark annualised               │  11.14% │
│ Volatility                                 │  11.54% │
│ Sharpe                                     │    0.52 │
│ Sortino                                    │    0.75 │
│ Beta                                       │    0.95 │
│ Alpha (annual)                             │   1.31% │
│ Upside capture                             │    0.96 │
│ Downside capture                           │    0.93 │
└────────────────────────────────────────────┴─────────┘

Rolling annualised returns (vs blended benchmark)                                          
┏━━━━━━━━┳━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Window ┃  Obs ┃     Min ┃    P25 ┃ Median ┃    P75 ┃    Max ┃ % negative ┃ % beating bm ┃
┡━━━━━━━━╇━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ 1Y     │ 2553 │ -10.40% │  2.82% │ 14.11% │ 24.23% │ 89.47% │      15.3% │        49.5% │
│ 3Y     │ 1822 │   2.56% │  9.06% │ 13.21% │ 20.27% │ 27.99% │       0.0% │        84.2% │
│ 5Y     │ 1092 │   4.72% │ 13.50% │ 14.66% │ 16.37% │ 20.70% │       0.0% │        97.3% │
└────────┴──────┴─────────┴────────┴────────┴────────┴────────┴────────────┴──────────────┘

$ folioperf vs-benchmark
Direct-equivalent (Long-Nickels PME)                                                       
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃ Instrument ┃        Value ┃ Benchmark value ┃   XIRR ┃ Benchmark XIRR ┃ Excess ┃ KS-PME ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│ DEBT_DIR   │    ₹9,09,510 │       ₹9,23,044 │  7.44% │          7.80% │ -0.36% │   0.99 │
│ EPF        │   ₹15,31,408 │      ₹14,55,874 │  9.12% │          7.80% │  1.32% │   1.05 │
│ FD_SBI     │           ₹0 │         ₹79,377 │  5.48% │          7.58% │ -2.10% │   0.91 │
│ FD_SBI2    │    ₹3,44,755 │       ₹3,57,134 │  6.51% │          8.23% │ -1.72% │   0.97 │
│ FLEXI_DIR  │   ₹29,33,463 │      ₹25,76,965 │ 11.86% │          8.45% │  3.41% │   1.14 │
│ HDFCBANK   │    ₹7,11,489 │       ₹6,25,024 │  9.65% │          7.14% │  2.51% │   1.13 │
│ INDEX_DIR  │    ₹7,47,835 │       ₹7,62,120 │  0.77% │          1.50% │ -0.73% │   0.98 │
│ INTL_DIR   │    ₹4,87,845 │       ₹4,12,769 │ 12.00% │          6.19% │  5.80% │   1.18 │
│ LARGE_DIR  │    ₹6,59,475 │       ₹5,82,145 │ 11.03% │          8.93% │  2.10% │   1.07 │
│ MID_DIR    │   ₹10,74,224 │       ₹9,79,007 │  7.95% │          5.29% │  2.66% │   1.10 │
│ PPF        │   ₹16,57,941 │      ₹17,04,942 │  7.17% │          7.79% │ -0.62% │   0.97 │
│ RELIANCE   │    ₹3,05,811 │       ₹4,37,246 │  4.10% │         11.80% │ -7.70% │   0.75 │
│ SMALL_REG  │    ₹7,24,511 │       ₹9,42,555 │ 11.19% │         18.79% │ -7.60% │   0.77 │
│ TCS        │    ₹8,84,663 │       ₹1,20,528 │ 31.73% │         18.73% │ 13.00% │   2.21 │
│ Portfolio  │ ₹1,29,72,931 │    ₹1,19,58,730 │ 10.61% │          8.78% │  1.82% │   1.07 │
└────────────┴──────────────┴─────────────────┴────────┴────────────────┴────────┴────────┘

$ folioperf attribution --period 2025
Contribution to return by instrument, 2025-01-01 to 2025-12-31
┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Instrument            ┃ Avg weight ┃ Return ┃ Contribution ┃
┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━┩
│ FLEXI_DIR             │     22.81% │ 11.53% │        2.60% │
│ RELIANCE              │      2.31% │ 51.46% │        1.07% │
│ SMALL_REG             │      6.21% │ 16.95% │        1.04% │
│ HDFCBANK              │      6.45% │ 12.52% │        0.81% │
│ EPF                   │      9.56% │  8.17% │        0.78% │
│ LARGE_DIR             │      4.88% │ 16.07% │        0.78% │
│ PPF                   │     11.15% │  7.15% │        0.78% │
│ DEBT_DIR              │      6.02% │  7.38% │        0.45% │
│ INDEX_DIR             │      6.27% │  5.84% │        0.37% │
│ TCS                   │      8.81% │  4.06% │        0.35% │
│ INTL_DIR              │      3.52% │  9.60% │        0.33% │
│ MID_DIR               │      9.61% │  2.58% │        0.30% │
│ FD_SBI2               │      2.41% │  7.20% │        0.19% │
│ Total (Carino-linked) │    100.00% │        │        9.84% │
└───────────────────────┴────────────┴────────┴──────────────┘

Brinson-Fachler vs target allocation                                                                    
┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Asset class   ┃ Weight ┃ Target ┃ Return ┃ Benchmark ┃ Allocation ┃ Selection ┃ Interaction ┃  Total ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━┩
│ Debt          │ 29.13% │ 25.00% │  7.52% │     7.35% │     -0.12% │     0.09% │      -0.03% │ -0.07% │
│ Equity        │ 67.35% │ 65.00% │ 10.91% │     7.55% │     -0.04% │     2.19% │       0.12% │  2.26% │
│ International │  3.52% │ 10.00% │  9.60% │    -5.43% │      0.76% │     1.56% │      -1.01% │  1.30% │
│ Active return │        │        │  9.84% │     6.34% │            │           │             │  3.50% │
└───────────────┴────────┴────────┴────────┴───────────┴────────────┴───────────┴─────────────┴────────┘

$ folioperf drawdowns --top 3
Drawdowns (max -23.95%)                                                            
┏━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Peak       ┃     Trough ┃  Recovered ┃   Depth ┃ Peak to trough ┃ Recovery time ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ 2020-02-23 │ 2020-03-24 │ 2020-06-23 │ -23.95% │           30 d │          91 d │
│ 2025-11-05 │ 2026-03-24 │    not yet │ -18.74% │          139 d │             - │
│ 2021-11-30 │ 2022-06-06 │ 2024-03-25 │ -18.73% │          188 d │         658 d │
└────────────┴────────────┴────────────┴─────────┴────────────────┴───────────────┘

$ folioperf costs
Costs                                            
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Instrument ┃ Expense ratio drag ┃ Commissions ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ DEBT_DIR   │            ₹10,000 │          ₹0 │
│ FLEXI_DIR  │            ₹75,031 │          ₹0 │
│ HDFCBANK   │                 ₹0 │        ₹223 │
│ INDEX_DIR  │             ₹4,331 │          ₹0 │
│ INTL_DIR   │             ₹7,521 │          ₹0 │
│ LARGE_DIR  │            ₹34,849 │          ₹0 │
│ MID_DIR    │            ₹33,602 │          ₹0 │
│ RELIANCE   │                 ₹0 │        ₹211 │
│ SMALL_REG  │            ₹50,270 │          ₹0 │
│ TCS        │                 ₹0 │        ₹433 │
│ Total      │          ₹2,15,603 │        ₹868 │
└────────────┴────────────────────┴─────────────┘

Regular vs direct plan (same cash flows)                                                               
┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Regular   ┃    Direct ┃ Regular value ┃ Direct value ┃ Cost of regular ┃ XIRR regular ┃ XIRR direct ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ SMALL_REG │ SMALL_DIR │     ₹7,24,511 │    ₹7,64,270 │         ₹39,760 │       11.19% │      12.73% │
└───────────┴───────────┴───────────────┴──────────────┴─────────────────┴──────────────┴─────────────┘

folioperf report -o report.html produces the HTML report: headline numbers, value vs invested, rolling returns, drawdown chart, risk, XIRR and benchmark tables, attribution and costs.

Configuration

Setting Effect
--data DIR / FOLIOPERF_DATA Data directory (default: current directory).
--rf Risk-free percent per year when riskfree.csv is absent (default 6).
instruments.csv: benchmark Series each instrument's cash flows are replayed into.
instruments.csv: direct_equivalent NAV series of the direct plan for the regular-vs-direct comparison.

Architecture

Module Role
models.py Dataclasses, Series with forward fill.
loaders.py CSV parsing, validation messages with line numbers, CAS and tradebook importers.
ledger.py Holdings reconstruction, cost basis, daily value and flow arrays.
xirr.py Newton plus Brent XIRR, root counting.
returns.py TWR, Dietz, rolling, drawdowns, risk statistics.
benchmark.py Long-Nickels replay and KS-PME.
attribution.py Carino contributions and Brinson-Fachler.
costs.py Expense drag, commissions, direct vs regular.
portfolio.py Loads a data directory, scopes, windows, flow assembly.
analysis.py Glue producing the figures commands show.
charts.py, report.py SVG charts and the HTML report.
cli.py Click commands.

Design notes and formulas with worked examples: docs/methodology.md.

Tests

pytest

72 tests cover XIRR against Excel's documented example and edge cases, TWR against hand calculations, Modified Dietz, rolling windows, drawdowns, PME identities, Carino sums, Brinson identity, ledger reconstruction (switches, splits, bonus, dividends), importers, every CLI command on the example data, and the HTML report.

Limitations