A Python-based tool for calculating GIPS-compliant investment returns across multiple brokerage accounts (IBKR and Exante). The system processes broker-specific reports and generates standardized returns metrics including Time-Weighted Returns (TWR), fee-adjusted net returns, and comprehensive NAV tracking.
- GIPS-Compliant Calculations: Beginning-of-period weighting, internal dispersion, large cash flow detection
- Multi-Brokerage Support: IBKR and Exante with extensible architecture
- Comprehensive Fee Tracking: Management fees (quarterly) and performance fees (with hurdle rate)
- NAV Tracking: Gross NAV (actual) and Net NAV (after fees) with flow handling
- Configurable: YAML-based configuration for clients, fees, and reporting periods
- Composite Returns: Asset-weighted composite across all accounts
pandas==2.2.3
numpy==2.0.2
openpyxl==3.1.5
PyYAML==6.0.1
numpy_financial==1.0.0
pytest==8.2.1
pytest-cov==5.0.0
hypothesis==6.98.8
Install dependencies:
pip install -r requirements.txtCopy the example configuration and customize:
cp config.example.yaml config.yamlEdit config.yaml to configure:
- Brokerages: Enable/disable IBKR, Exante
- Clients: List of clients to process
- Fees: Management fee (quarterly), performance fee rate, hurdle rate
- Periods: Fiscal year start month, reporting years
Example configuration:
# Fee structure
fees:
management_fee_quarterly: 0.0025 # 0.25% per quarter = 1% annual
performance_fee_rate: 0.25 # 25% of gains above hurdle
hurdle_rate_annual: 0.06 # 6% annual hurdle
# Fiscal year configuration
periods:
fiscal_year_start_month: 2 # February
reporting_years: [2022, 2023, 2024, 2025]ReturnsCalculator/
├── input/ # Input directory for raw broker files
│ ├── IBKR/ # IBKR input files (*.csv)
│ └── Exante/ # Exante input files (*.csv)
├── results/ # Generated results
│ ├── Combined/ # Asset-weighted composite results
│ ├── IBKR_{client}/ # Per-client IBKR results
│ └── Exante_{client}/ # Per-client Exante results
├── logs/ # Processing logs
├── tests/ # Test suite
├── config.yaml # Your configuration (gitignored)
├── config.example.yaml # Example configuration
├── twr_calculator.py # Main calculator engine
├── fee_calculator.py # Fee calculation logic
├── flow_utils.py # Cash flow utilities
├── config_loader.py # Configuration loading
├── IBKR.py # IBKR data processing
├── Exante.py # Exante data processing
└── utils.py # Utility functions
-
Export broker reports:
- IBKR: Export performance report as CSV from Client Portal/TWS
- Exante: Export account statement as CSV
-
Place files in input directories:
input/IBKR/{client_name}.csv input/Exante/{client_name}.csv -
Run the calculator:
python twr_calculator.py
# Use default config.yaml
python twr_calculator.py
# Specify custom config
python twr_calculator.py --config my_config.yaml
# Override input path
python twr_calculator.py --input-path /path/to/input
# Legacy mode (for backward compatibility)
python twr_calculator.py --legacy -i inputThe system processes broker files in three main steps:
-
Account Discovery
- Scans input directory for CSV files
- Identifies clients from filenames
- Creates processing directories
-
Broker-specific Processing
- IBKR: Extracts NAV from allocation reports, flows from deposits/withdrawals
- Exante: Converts to NAV.xlsx and Trades.xlsx
-
Returns Calculation
- Calculates sub-period returns between cash flows
- Computes monthly TWR through geometric linking
- Applies fees and tracks Net NAV
- Eliminates impact of cash flow timing
- Calculated between each cash flow (sub-period returns)
- Geometrically linked for monthly/annual periods
- GIPS-compliant for performance comparison
- Gross TWR: Pure investment performance before fees
- Net TWR: Returns after management and performance fees
- Net TWR = Compounded product of annual net returns
- Rate: 0.25% per quarter (1% annual)
- Timing: Deducted at start of each fiscal quarter
- Base: Current Net NAV
- Rate: 25% of gains above hurdle
- Hurdle: 6% annual, accumulates quarterly (Q1: 1.5%, Q2: 3%, Q3: 4.5%, Q4: 6%)
- Timing: Deducted at end of each fiscal quarter
- Base: Investment gain since year start (excluding flows)
- Carry-forward: Shortfall carries to next year (non-compounding)
- No Double-Counting: Only charges on incremental excess each quarter
Double-Counting Prevention Example:
Year Start NAV: €100,000
Q1: NAV = €110,000 (10% gain)
Hurdle = 1.5% = €1,500
Excess = €10,000 - €1,500 = €8,500
Fee = €8,500 × 25% = €2,125 ✓
Q2: NAV = €113,000 (13% total gain)
Hurdle = 3% = €3,000
Total Excess = €13,000 - €3,000 = €10,000
Already Charged = €8,500
NEW Excess = €10,000 - €8,500 = €1,500
Fee = €1,500 × 25% = €375 ✓
Without this fix, Q2 would incorrectly charge €10,000 × 25% = €2,500
The system maintains two parallel NAV series:
| NAV Type | Description |
|---|---|
| Gross NAV | Actual NAV from accounts (includes flows) |
| Net NAV | Gross NAV minus accumulated fees |
Net NAV = Gross NAV - Accumulated Fees
Fee Drag = Gross NAV - Net NAV
Results are saved with timestamps (YYYYMMDD_HHMMSS):
- Monthly TWR values
- Start/end-of-month NAVs
- Monthly flows
- Sub-period returns
- Daily NAV snapshots
- Flow attribution
Contains comprehensive metrics:
Performance Metrics:
- Total Absolute Return (Gross/Net TWR)
- Annualized Return (Gross/Net)
- Initial and Final NAV
- Total Flows
- Fee Impact
Quarterly NAV Tracking:
- Gross NAV (Actual from accounts)
- Net NAV (After fees)
- Management fee per quarter
- Performance fee per quarter
- Cumulative fee impact
Annual Summaries:
- Year-over-year NAV progression
- Annual fee totals
- Active accounts per period
Total Absolute Return (Gross TWR): 150.25%
Total Absolute Return (Net TWR): 112.40%
Annualized Return (Gross TWR): 25.10%
Annualized Return (Net TWR): 20.35%
Initial NAV (Actual): €100,000.00
Final NAV - Gross: €285,450.50
Final NAV - Net: €248,330.25
Total Flows: €25,000.00
Total Fees: €37,120.25 (Mgmt: €4,120 + Perf: €33,000)
Fee Impact: €37,120.25
2022 Returns (Fiscal Feb-Jan): Gross 22.50% → Net 18.15%
2023 Returns (Fiscal Feb-Jan): Gross 15.10% → Net 12.30%
2024 Returns (Fiscal Feb-Jan): Gross 35.40% → Net 28.90%
2025 Returns (Fiscal YTD): Gross 12.20% → Net 9.80%
- Beginning-of-Period Weighting: Composite weights use start-of-month NAV
- Internal Dispersion: Calculated for composites with 6+ accounts
- Large Cash Flow Detection: Flags flows >10% of NAV
- Active Account Tracking: Only includes accounts with data for each period
- Geometric Linking: Sub-period returns properly compounded
# Run all tests
pytest
# Run with coverage
pytest --cov=. --cov-report=html
# Run specific test file
pytest tests/test_fee_calculator.py
# Run in parallel
pytest -n auto- All monetary values are converted to EUR
- Fiscal year defaults to February start (Feb 1 - Jan 31)
- Logs are generated in the logs directory
- Configuration file (
config.yaml) is gitignored for security
- Validates input file formats
- Handles missing or corrupted data
- Logs processing errors and warnings
- Skips invalid return calculations
- Provides detailed error messages
Requires Python 3.8+ with:
- pandas: Data processing and analysis
- numpy: Numerical computations
- openpyxl: Excel file handling
- PyYAML: Configuration loading
See requirements.txt for complete list.