End-User Documentation

1. Project Purpose

The Portfolio Backtester is a web-based application designed to help investors and financial advisors analyze and compare investment portfolio strategies. It allows users to:

  • Define custom investment portfolios with specific asset allocations (tickers and weights).
  • Specify rebalancing frequencies for each portfolio.
  • Model cashflows (contributions and withdrawals) with various rules (e.g., constant dollar, guardrails, tax drag).
  • Run backtests against historical market data to simulate portfolio performance over time.
  • Analyze results using standard financial metrics, charts, and advanced simulations like Monte Carlo and Withdrawal Rate analysis.

The core goal is to provide a flexible and powerful tool for understanding the potential risks and returns associated with different long-term investment approaches. The application utilizes a combination of Python for the web interface and data handling, and C++ for performance-critical backtesting calculations.

2. Web Application User Interface Guide

The web application provides an interactive interface to configure and run backtests.

2.1 Managing Simulated Ticker Data

2.1.1 Overview

This documentation describes how to use the “Manage Data” section within the Portfolio Backtester application. This interface allows users to define “simulated” tickers. A simulated ticker uses custom historical data (provided via a CSV file) for a period before the associated “real” ticker’s data begins, effectively extending the history of an asset further back in time than publicly available data allows.

2.1.2 Accessing the UI

The interface for managing simulated tickers is located within a collapsible panel on the main backtesting page:

  1. Locate the panel titled “Manage Data”.
  2. Click on the title bar. The panel will expand to reveal the management interface or collapse if already open. The chevron icon indicates the current state.

2.1.3 UI Elements and Actions

The “Manage Data” panel contains the following elements and allows these actions:

  • Existing Tickers Display:
    • Lists all currently configured simulated tickers.
    • Each entry shows:
      • An input field for the Simulated Ticker name (the custom ticker you will use in portfolios).
      • An input field for the Real Ticker name (the actual market ticker (from Yahoo Finance), whose percentage changes will be used to complete the data).
      • A Download button: Appears only if CSV data has been previously associated with this ticker. Click to download the currently stored (and normalized) CSV data for this simulated ticker.
      • A Delete button: Removes the simulated ticker definition.
      • An Upload button: Allows you to upload or replace the CSV data associated with this ticker.
      • A File Status Indicator: Shows the name of the uploaded file if a new CSV has been selected but not yet saved.
  • Add New Button:
    • Clicking “Add New” adds a blank row to the list, allowing you to define a new simulated ticker.
    • You must upload a CSV file for any newly added ticker before saving.
  • Editing Tickers:
    • You can directly type into the Simulated Ticker and Real Ticker input fields to modify existing entries or define new ones.
  • Uploading CSV Data:
    1. Click the Upload button for the desired ticker row.
    2. A file selection dialog will appear. Select the CSV file containing the historical data. See below for further information regarding the supported CSV file format.
    3. The name of the selected file will appear next to the upload button, indicating it’s staged for saving.
    4. The actual upload and processing happen when you click “Save Changes”.
  • Deleting a Ticker:
    1. Click the Delete button next to the ticker you want to remove.
    2. The row disappears from the UI.
    3. The deletion is finalized when you click “Save Changes”.
  • Save Changes Button:
    • Appears enabled only when valid changes have been made (additions, deletions, edits, uploads).
    • Clicking “Save Changes” sends all modifications (additions, updates, deletions, and uploaded CSV data) to the backend server.
    • The backend validates the data, normalizes and stores the CSV files, updates the simulated ticker mapping, and refreshes the list.
    • A success or error notification will appear at the top of the screen.
  • Discard Changes Button:
    • Appears enabled only when changes have been made since the last save or page load.
    • Clicking “Discard Changes” cancels all modifications made in the UI and reloads the currently saved simulated ticker list from the server.
  • Validation Feedback:
    • An indicator appears next to the “Save Changes” button if the current state is invalid.
    • Hovering over the indicator displays a tooltip listing the specific validation errors.
    • The “Save Changes” button is disabled until all validation errors are resolved.

2.1.4 CSV File Format

The CSV file provides the historical data that precedes the data available for the “Real Ticker”. The backend service will attempt to normalize the data, but adhering to the following format ensures correct processing:

  • Structure: The file must contain exactly two columns.
  • Header Row (Optional but Recommended):
    • If included, the first column header should ideally be "Date".
    • The second column header should ideally match the Simulated Ticker name you are defining (e.g., "UPROSIM").
    • (Note: The backend will attempt to handle missing headers or different second column headers, but matching the simulated ticker is the clearest convention).
    • If no header row is present, the system assumes the first column is the date and the second is the value.
  • Date Column (Column 1):
    • Must contain dates that can be parsed by pandas. Standard formats like YYYY-MM-DD, YYYY/MM/DD, MM/DD/YYYY are generally acceptable. YYYY-MM-DD is recommended.
    • This column will be used as the index for the time series.
    • Dates should be sorted chronologically (oldest first).
  • Value Column (Column 2):
    • Must contain numeric data (integers or floating-point numbers).
    • Interpretation: The system determines the type of values based on whether they are all positive:
      • Absolute Values: If all numbers in this column are positive (> 0), they are interpreted as absolute price or index levels (e.g., the closing price for each day).
      • Percentage Changes: If any number in this column is zero or negative, the entire column is interpreted as daily percentage changes.
        • Changes can be represented as decimals (e.g., 0.01 for +1%, -0.005 for -0.5%) or percentages (e.g., 1 for +1%, -0.5 for -0.5%). The system detects which format is used based on the range of values.
        • The system converts these percentage changes into a cumulative price series internally, starting from an arbitrary base (usually 1) before combining it with the real ticker data.
  • Encoding: UTF-8 encoding is recommended.
  • File Extension: Must be .csv.

Example 1: CSV with Absolute Values

Date,MY_SIM_INDEX
2000-01-03,100.00
2000-01-04,100.50
2000-01-05,100.25
...

Example 2: CSV with Percentage Changes (Decimal Format)

Date,MY_SIM_INDEX_CHANGE
2000-01-03,0.0000
2000-01-04,0.0050
2000-01-05,-0.0025
...

Normalization Note: The backend processes the uploaded CSV and attempts to standardize the format (e.g., ensure correct headers, convert percent changes to absolute values if necessary) before saving. However, starting with the recommended format will prevent potential errors.

2.1.5 How Simulated Data is Used in Backtests

When you include a Simulated Ticker (e.g., MYSIM) in a portfolio for backtesting:

  1. The backtester retrieves the processed data associated with MYSIM.
  2. This data consists of the historical values from your CSV file up to the start date of the associated Real Ticker (e.g., VFINX).
  3. From the Real Ticker’s start date onwards, the backtester uses the price series generated by applying the Real Ticker’s daily percentage changes to the last value derived from your CSV.
  4. The result is a single, continuous price series for MYSIM that extends further back than VFINX’s own data.

2.2 Global Parameters

This section sets the overall context for the backtest:

  • Start Date: (Optional) The earliest date for the backtest simulation. If blank, the backtest starts as early as possible given the inception dates of the selected tickers. The effective start date might be later if limited by ticker data (indicated by an info icon in the results).
  • End Date: (Optional) The latest date for the backtest simulation. If blank, the backtest runs up to the most recent available data.
  • Initial Amount: The starting capital for each portfolio in the simulation.
  • Home Currency: The primary currency for reporting results (CAD or USD). Portfolio values and metrics will be displayed in this currency.
  • Rolling Window (months): The time period (in months) used for calculating rolling performance metrics (e.g., rolling CAGR, Volatility, Sharpe Ratio, Correlations).

2.3 Cashflows

This section allows you to model regular contributions or withdrawals affecting the portfolios during the simulation.

  • Add Cashflow: Click to add a new cashflow definition block.
  • Cashflow Block:
    • Name: (Optional) A unique name for this cashflow (defaults to “Cashflow X”).
    • Frequency: How often the cashflow occurs (Yearly or Monthly). Note: Profit Tax Drag is currently Yearly only.
    • Type: Whether it’s a Withdrawal or Contribution.
    • Subtype: The rule governing the cashflow amount:
      • Constant Dollar: A fixed amount, adjusted annually for inflation (based on the previous year’s rate for the home currency).
      • Constant Dollar With Guardrails: (Withdrawal only) Same as Constant Dollar, but the withdrawal amount is adjusted if it falls outside specified percentage bands (Low/High Guardrail) of the current portfolio value (eg. if the percentage of the portfolio being withdrawn exceeds the high guardrail, the withdrawal amount is reduced to match the high guardrail). The guardrails are defined as annual percentages but applied at the cashflow frequency (e.g., a 6% annual high guardrail becomes 0.5% if the frequency is monthly).
      • Profit Tax Drag: (Withdrawal only) Simulates tax drag by withdrawing a percentage (Drag) of the previous year’s profits made on specified tickers. Applied at the beginning of each calendar year.
    • Starts: When the cashflow begins (Immediately or In... X Years/Months from the backtest start date).
    • Ends: When the cashflow stops (Never or In... X Years/Months from the backtest start date).
    • Parameters: Specific inputs depending on the subtype:
      • Initial Amount: The base dollar amount for Constant Dollar types (adjusted for inflation annually).
      • Low/High Guardrail (%): Percentage thresholds for the Guardrails subtype.
      • Tax Drag Tickers/Drag (%): Ticker and percentage drag for the Profit Tax Drag subtype. You can add multiple tickers.

2.4 Portfolios

Define the investment portfolios you want to backtest.

  • Add Empty / Add Preset: Add a new portfolio block, either blank or based on a predefined template.
  • Portfolio Block:
    • Clone Button (Copy Icon): Duplicates the current portfolio block.
    • Close Button (X): Removes the portfolio block.
    • Name: (Optional) A unique name for this portfolio (defaults to “Portfolio X”).
    • Rebalance: How often the portfolio is rebalanced back to its target weights (Yearly, Quarterly, Monthly).
    • Holdings:
      • Add Holding (+): Adds a new row for a ticker and weight.
      • Clear Tickers (X): Clears all ticker symbols in the portfolio.
      • Set Equal Weight (Balance Icon): Distributes 100% weight evenly across all holdings.
      • Set Remaining Weight (Unbalanced Icon): Distributes the remaining weight (up to 100%) evenly across holdings that currently have no weight assigned.
      • Clear Weights (X): Clears all weight values.
      • Ticker: The Yahoo Finance stock/ETF symbol (e.g., VTI, XIC.TO). Supports parameters like C=CAD for currency hedging or D=0.75 for yearly drag (see Methodology). Tickers with extended history are available and are listed here.
      • Weight (%): The target allocation percentage for the ticker. Negative weights are supported to short the specified asset (borrowing costs are not taken into account).
      • Remove Holding (Trash Icon): Removes the specific holding row.
    • Total Weight: Displays the sum of weights entered. It turns green when exactly 100%.

2.5 Running the Backtest

  • Run Backtest Button: Click this to submit the configuration and start the simulation. The first time you run a simulation with a given ticker, it will download the data for that ticker. The data for each ticker is saved to a cache. The cached data is refreshed as needed once a day. Downloading data for tickers can take several seconds so you’ll have to be patient the first time you run a simulation with a ticker for which there’s no cached data.
  • Loading Indicator: A spinner appears while the backtest is running.

2.6 Results

Once the backtest completes, results are displayed in tabs:

  • Summary: Key performance metrics table (CAGR, Volatility, Sharpe, Max Drawdown, etc.), portfolio value chart (linear/log scale), and drawdown chart.
  • Annual Returns: Table showing calendar year returns for each portfolio and asset. Cells are color-coded based on performance.
  • Rolling Metrics: Chart displaying rolling performance metrics (CAGR, Volatility, Sharpe) over the user-defined window.
  • Asset Metrics: Similar to the Summary tab, but showing metrics for each individual underlying asset included in the portfolios. Includes asset value and drawdown charts.
  • Asset Correlations: Static correlation matrix between all assets over the full period, and a chart showing rolling correlations (if enough data exists).
  • Cashflows: Detailed table showing each planned vs. actual cashflow transaction for a selected portfolio, including portfolio value at the time and percentage impact.
  • Monte Carlo: Results of the Monte Carlo simulation (see Methodology section). Shows success rates and percentile outcomes for final portfolio values.

3. Methodology

3.1 Data Handling

  • Sources: Historical market data (adjusted close prices, rates) is primarily sourced from Yahoo Finance, FRED (Federal Reserve Economic Data), and the Bank of Canada Valet API. Some specific total return series may use supplementary data from local CSV files (data/*.csv).
  • Frequency: Data is fetched at the highest available frequency (usually daily) and then aligned. Daily data is used for the core backtest simulation. Monthly data is used for Monte Carlo.
  • Caching: Downloaded data is cached locally (data_cache/, yfinance.cache) to speed up subsequent requests and reduce API calls. Cached data is refreshed daily.
  • Synthetic Tickers: Some tickers (e.g., VTISIM, SPYSIM, UPROSIM) represent total returns or mutual fund versions of ETFs. For periods before the live ticker existed, historical data from CSV files (often based on underlying indices or older mutual funds) is combined and back-filled using the percentage changes of a corresponding live ticker (e.g., VTI for VTISIM).
  • Currency Hedging Ticker Parameter: C=<CURRENCY> (e.g. GLDMSIM C=CAD) applies currency hedging. It calculates the asset’s return in its native currency and adds/subtracts the return (or inverse return) of the relevant exchange rate (USDCAD) to simulate holding the asset hedged to the specified currency. Supported currencies are CAD and USD.
  • Yearly Drag Ticker Parameter: D=<NUMBER> (e.g. D=0.5) applies a yearly drag on the return of the ticker. The drag is applied daily and in a way that results in the specified yearly drag percentage. The number is an integer or a decimal number greater than -100 and smaller than 100 and it represents the yearly drag in percentage (e.g. D=0.5 for 0.5% of yearly drag). Negative numbers are supported for getting the opposite effect of drag (ie. increase performance). This can be useful for example when a ticker has negative weight and there are yearly borrowing costs that need to be included (e.g. GLDMSIM D=-0.5 with a weight of -100 would result in increasing the performance of GLDMSIM by 0.5% per year, and therefore decrease the performance of the short allocation by 0.5% per year).
  • Risk-Free Rate: Uses specific tickers (CASHX.TO for CAD, CASHX for USD) derived from Bank of Canada and FRED data (e.g., 90-day T-bills) as proxies for the risk-free rate in Sharpe Ratio calculations.
  • Inflation Data: Annual inflation rates are derived from monthly CPI data (Canada: STATIC_INFLATIONCALC via BoC, US: CPIAUCNS via FRED).

3.2 Core Backtest Engine

  • Simulation: The backtest simulates portfolio performance day-by-day using the historical data.
  • Valuation: On each day, the value of each holding is updated based on its price change and relevant currency exchange rates.
  • Rebalancing: On rebalancing dates (determined by the selected frequency - Yearly, Quarterly, Monthly), the portfolio holdings are adjusted back to their target weights. Trades are simulated to sell overweighted assets and buy underweighted ones.
  • Cashflows: On cashflow dates (determined by frequency), contributions are added, or withdrawals are attempted according to the specified rules (Constant Dollar, Guardrails, Tax Drag). Withdrawals first use available cash in the home currency, then convert other currency cash, then sell risk-free assets, and finally sell other assets proportionally if necessary. If a withdrawal cannot be fully met, the actual withdrawn amount may be less than planned.
  • Implementation: The core simulation loop is implemented in C++ (btcpp-src) for performance and called from Python.

3.3 Monte Carlo Simulation

  • Purpose: To assess the range of potential outcomes based on historical return patterns, acknowledging that the future won’t perfectly repeat the past sequence.
  • Method: It uses a bootstrapping/resampling approach based on historical monthly returns.
    1. Calculate historical monthly percentage returns for all assets, including currency pairs and CPI (for inflation).
    2. For each simulation run (default: 10,000 runs):
      • Randomly shuffle the historical sequence of monthly returns.
      • Start with the initial portfolio value.
      • Step through the shuffled monthly returns, applying the return to the portfolio value.
      • Apply cashflow rules (adjusting amounts for inflation based on the simulated monthly CPI changes).
      • Rebalance according to the portfolio’s frequency (relative to the simulated months passing).
      • Track the portfolio value over the simulated period (35 years). If less than 35 years of data is available, the data is reshuffled and the simulation continues until 35 years have been simulated.
  • Success Rate: A simulation is considered successful if the portfolio value does not reach zero before the end of the simulation period.
  • Output: Reports the total number of simulations, the success rate, the simulation length, and the final portfolio values at various percentiles (e.g., 10th, 20th… 90th).

3.4 Safe Withdrawal Rate (SWR) and Permanent Withdrawal Rate (PWR) Analysis

  • Purpose: To estimate sustainable withdrawal rates a portfolio could have supported throughout history, providing detailed curves showing how withdrawal rates evolve over time.
  • Method: The analysis generates comprehensive withdrawal rate curves using advanced mathematical modeling and extrapolation techniques.

3.4.1 Data Generation Process

  1. Historical Start Points: The analysis identifies all possible historical starting points (quarterly intervals) where there’s sufficient data for meaningful backtests (at least 5 years).
  2. Curve Generation: For each starting point, the system generates withdrawal rate curves by:
    • Running backtests with varying time horizons (1 year, 2 years, etc.)
    • Using root-finding algorithms to determine the maximum sustainable withdrawal rate for each time horizon
    • Creating curves that show how withdrawal rates change over time

3.4.2 Safe Withdrawal Rate (SWR) Curves

  • Definition: SWR curves show the maximum withdrawal rate that would have depleted the portfolio to exactly $0 at each time horizon.
  • Multiple Scenarios: Each portfolio generates multiple SWR curves, one for each historical starting point.
  • Curve Characteristics:
    • Typically start high (100% withdrawal rate at 1 year) and decrease over time
    • Longer time horizons generally support lower withdrawal rates
    • Each curve represents a different historical scenario

3.4.3 Permanent Withdrawal Rate (PWR) Curve

  • Definition: PWR curves show the maximum withdrawal rate that would have preserved the inflation-adjusted principal value.
  • Single Curve: Each portfolio generates one PWR curve representing the worst-case scenario across all historical starting points.
  • Inflation Adjustment: The target preservation value is adjusted for inflation over the withdrawal period.

3.4.4 Advanced Extrapolation and Analysis

  • Master Curve Creation: The system creates a normalized “master curve” from the longest complete SWR scenarios to guide extrapolation.
  • Curve Extrapolation: Shorter historical scenarios are extrapolated to 60 years using:
    • Scaling factors based on the master curve
    • Exponential decay modeling for tail behavior
    • Mathematical curve fitting to ensure realistic projections
  • Key Metrics Extracted:
    • 30-Year SWR: Minimum safe withdrawal rate across all scenarios at the 30-year mark
    • 60-Year SWR: Minimum safe withdrawal rate across all scenarios at the 60-year mark
    • 30-Year PWR: Permanent withdrawal rate at the 30-year mark
    • 60-Year PWR: Permanent withdrawal rate at the 60-year mark

3.4.5 Statistical Range Analysis

  • Range Statistics: For each time horizon (30 and 60 years), the system provides:
    • Minimum Rate: The worst-case withdrawal rate across all scenarios
    • Maximum Rate: The best-case withdrawal rate across all scenarios
    • Mean Rate: The average withdrawal rate across all scenarios
  • Risk Assessment: These ranges help users understand the variability and uncertainty in withdrawal rate projections.

  • Output:
    • Complete SWR and PWR curve data for visualization
    • Four key withdrawal rate benchmarks (30/60-year SWR/PWR)
    • Statistical ranges showing best/worst/average case scenarios
    • Full extrapolated curves extending to 60 years for comprehensive analysis

3.4.6 API Response Structure

The withdrawal rate analysis returns a comprehensive data structure containing:

  • SWR Data: Contains all Safe Withdrawal Rate curves for each portfolio, with each curve representing a different historical starting scenario.
  • PWR Data: Contains the Permanent Withdrawal Rate curve for each portfolio.
  • Best Rates: Four key benchmark values:
    • swr_30_year: The minimum safe withdrawal rate at the 30-year mark across all scenarios
    • swr_60_year: The minimum safe withdrawal rate at the 60-year mark across all scenarios
    • pwr_30_year: The permanent withdrawal rate at the 30-year mark
    • pwr_60_year: The permanent withdrawal rate at the 60-year mark
  • Range Statistics: Statistical analysis of withdrawal rates at key time horizons:
    • swr_30_year_range: Min/max/mean statistics for SWR curves at 30 years
    • swr_60_year_range: Min/max/mean statistics for SWR curves at 60 years

Each curve consists of data points with years_elapsed and withdrawal_rate values, allowing for detailed visualization and analysis of how withdrawal sustainability changes over time.

3.5 Key Metrics Calculation

Standard financial metrics are calculated based on the daily portfolio values in the home currency:

  • CAGR (Compound Annual Growth Rate): Geometric average annual growth rate.
  • Volatility: Annualized standard deviation of daily returns.
  • Sharpe Ratio: Annualized excess return (over risk-free rate) divided by annualized volatility.
  • Max Drawdown: The largest peak-to-trough percentage decline in portfolio value.
  • Ulcer Index: Measures the depth and duration of drawdowns (square root of the average squared percentage drawdown).
  • Ulcer Performance Index (UPI): Total return divided by the Ulcer Index.
  • Beta: Measures the portfolio’s volatility relative to market benchmarks (VTISIM for US, XICSIM.TO for Canada, if data is available). Calculated using the covariance method.
  • Rolling Metrics: CAGR, Volatility, and Sharpe Ratio are recalculated over a sliding window (defined by the “Rolling Window” parameter) throughout the backtest period.
  • Correlations: Pearson correlation coefficients are calculated between the daily returns of all assets. Rolling correlations are also calculated over the specified window.

Disclaimer: This tool provides simulations based on historical data and specific methodologies. Past performance is not indicative of future results. This is not financial advice. Consult with a qualified financial advisor before making investment decisions.