QuantJourney Backtester

QuantJourney Backtester

Share product feedback

Thank you.

Your note is now in the QuantJourney inbox.

backtester/portfolio/calc/returns.py

returns.py:
The return and NAV normalization layer.

This file defines the first clean contract in the analytics stack: prices become aligned return matrices, and return matrices become NAV paths. Risk, rolling stats, plots and reports should not each invent their own return convention.

Import from backtester.portfolio.calc import returns

When To Read This

  • 01
    You need to know whether a downstream metric expects simple returns, log returns or levels.
  • 02
    You are wiring a new data source and want the same date x instrument shape as the rest of the framework.
  • 03
    You are building plots or PDF tables that require a normalized NAV path.

File Anatomy

  • Return type handling: relative, log, difference, level and lagged-level return modes.
  • Frequency normalization: optional resampling through the compatibility DataFrequency layer.
  • Missing data policy: explicit forward-fill and first-row handling rather than implicit pandas defaults.
  • NAV reconstruction: simple-return compounding, log-return exponentiation and optional terminal or initial scaling.

Data Contract

Inputs

  • prices: pandas DataFrame indexed by trading dates, columns are instruments.
  • returns: pandas DataFrame, Series or ndarray for NAV conversion helpers.
  • return_type / is_log_returns / freq / fill flags decide the return convention at the boundary.

Outputs

  • pd.DataFrame of per-period returns or levels with the same instrument columns.
  • pd.Series of total or annualized return per column.
  • pd.DataFrame NAV path suitable for equity curves, reports and Monte Carlo inputs.

Invariants

  • Annualization uses the number of trading observations, not calendar-day distance.
  • Functions are stateless and do not mutate strategy or portfolio objects.
  • Simple returns and log returns must not be mixed downstream.

Public API And Key Internals

compute_periodic_returns

function
compute_periodic_returns(prices, *, is_log_returns=False, return_type=ReturnTypes.RELATIVE, freq=None, include_start_date=False, include_end_date=False, ffill_nans=True, drop_first=False, is_first_zero=False)

Converts a price matrix into the selected return representation. This is the canonical place to decide relative vs log vs difference returns.

Returns

pd.DataFrame aligned to the original or resampled index.

Implementation Notes

  • Uses DataFrequency.resample_to_frequency when freq is provided.
  • drop_first removes the natural NaN row after pct-change style calculations.

compute_total_returns

function
compute_total_returns(returns)

Compounds simple returns into total return per instrument.

Returns

pd.Series indexed by instrument.

compute_annualized_returns

function
compute_annualized_returns(returns, *, days_per_year=252)

Annualizes compounded total return using trading observations. This avoids calendar-day drift in daily backtests.

Returns

pd.Series indexed by instrument.

convert_returns_to_nav

function
convert_returns_to_nav(returns, *, init_period=1, terminal_value=None, init_value=None, freq=None, ffill_between_nans=True, constant_trade_level=False)

Builds a NAV path from simple returns, optionally treating returns as additive levels for constant-trade-level workflows.

Returns

pd.DataFrame NAV path.

convert_log_returns_to_nav

function
convert_log_returns_to_nav(log_returns, *, init_period=None, terminal_value=None, init_value=None)

Builds NAV from log returns through exp(cumsum(log_returns)).

Returns

pd.DataFrame NAV path.

Implementation Notes

  • The module deliberately keeps return creation separate from risk metrics. That makes the return convention visible and testable.
  • The first period is configurable because some report contexts want a zero first row while others prefer dropping it.
  • NAV scaling supports both initial-capital charts and terminal-value normalization without changing the original return matrix.

Code Walkthrough

Typical import and usage

One source price matrix becomes the shared contract for annualization and NAV charts.

returns_usage.py Python
import pandas as pd

from backtester.portfolio.calc import returns

prices = pd.DataFrame(
    {
        "AAPL": [180.0, 181.5, 179.0, 184.0],
        "MSFT": [410.0, 412.0, 416.0, 415.0],
    },
    index=pd.date_range("2025-01-02", periods=4, freq="B"),
)

asset_returns = returns.compute_periodic_returns(
    prices,
    drop_first=True,
)
annualized = returns.compute_annualized_returns(asset_returns)
nav = returns.convert_returns_to_nav(asset_returns, init_value=100_000)

Key implementation: return convention switch

This is the section that prevents the rest of the system from guessing what "returns" means.

backtester/portfolio/calc/returns.py Python
if return_type == ReturnTypes.LOG or is_log_returns:
    rets = np.log(df) - np.log(df.shift(1))
elif return_type == ReturnTypes.RELATIVE:
    rets = df.divide(df.shift(1)).subtract(1.0)
elif return_type == ReturnTypes.DIFFERENCE:
    rets = df.subtract(df.shift(1))
elif return_type == ReturnTypes.LEVEL:
    rets = df
elif return_type == ReturnTypes.LEVEL0:
    rets = df.shift(1)
else:
    raise NotImplementedError(f"Unsupported return type: {return_type}")