Expected Returns

Mean-variance optimization requires knowledge of the expected returns. In practice, these are rather difficult to know with any certainty. Thus the best we can do is to come up with estimates, for example by extrapolating historical data, This is the main flaw in mean-variance optimization – the optimization procedure is sound, and provides strong mathematical guarantees, given the correct inputs. This is one of the reasons why I have emphasised modularity: users should be able to come up with their own superior models and feed them into the optimizer.

Caution

Supplying expected returns can do more harm than good. If predicting stock returns were as easy as calculating the mean historical return, we’d all be rich! For most use-cases, I would suggest that you focus your efforts on choosing an appropriate risk model (see Risk Models).

As of v0.5.0, you can use Black-Litterman Allocation to significantly improve the quality of your estimate of the expected returns.

The expected_returns module provides functions for estimating the expected returns of the assets, which is a required input in mean-variance optimization.

By convention, the output of these methods is expected annual returns. It is assumed that daily prices are provided, though in reality the functions are agnostic to the time period (just change the frequency parameter). Asset prices must be given as a pandas dataframe, as per the format described in the User Guide.

All of the functions process the price data into percentage returns data, before calculating their respective estimates of expected returns.

Currently implemented:

  • general return model function, allowing you to run any return model from one function.
  • mean historical return
  • exponentially weighted mean historical return
  • CAPM estimate of returns

Additionally, we provide utility functions to convert from returns to prices and vice-versa.

Note

For any of these methods, if you would prefer to pass returns (the default is prices), set the boolean flag returns_data=True

pypfopt.expected_returns.mean_historical_return(prices, returns_data=False, compounding=True, frequency=252, log_returns=False)[source]

Calculate annualised mean (daily) historical return from input (daily) asset prices. Use compounding to toggle between the default geometric mean (CAGR) and the arithmetic mean.

Parameters:
  • prices (pd.DataFrame) – adjusted closing prices of the asset, each row is a date and each column is a ticker/id.
  • returns_data (bool, defaults to False.) – if true, the first argument is returns instead of prices. These should not be log returns.
  • compounding (bool, defaults to True) – computes geometric mean returns if True, arithmetic otherwise, optional.
  • frequency (int, optional) – number of time periods in a year, defaults to 252 (the number of trading days in a year)
  • log_returns (bool, defaults to False) – whether to compute using log returns
Returns:

annualised mean (daily) return for each asset

Return type:

pd.Series

This is probably the default textbook approach. It is intuitive and easily interpretable, however the estimates are subject to large uncertainty. This is a problem especially in the context of a mean-variance optimizer, which will maximise the erroneous inputs.

pypfopt.expected_returns.ema_historical_return(prices, returns_data=False, compounding=True, span=500, frequency=252, log_returns=False)[source]

Calculate the exponentially-weighted mean of (daily) historical returns, giving higher weight to more recent data.

Parameters:
  • prices (pd.DataFrame) – adjusted closing prices of the asset, each row is a date and each column is a ticker/id.
  • returns_data (bool, defaults to False.) – if true, the first argument is returns instead of prices. These should not be log returns.
  • compounding (bool, defaults to True) – computes geometric mean returns if True, arithmetic otherwise, optional.
  • frequency (int, optional) – number of time periods in a year, defaults to 252 (the number of trading days in a year)
  • span (int, optional) – the time-span for the EMA, defaults to 500-day EMA.
  • log_returns (bool, defaults to False) – whether to compute using log returns
Returns:

annualised exponentially-weighted mean (daily) return of each asset

Return type:

pd.Series

The exponential moving average is a simple improvement over the mean historical return; it gives more credence to recent returns and thus aims to increase the relevance of the estimates. This is parameterised by the span parameter, which gives users the ability to decide exactly how much more weight is given to recent data. Generally, I would err on the side of a higher span – in the limit, this tends towards the mean historical return. However, if you plan on rebalancing much more frequently, there is a case to be made for lowering the span in order to capture recent trends.

pypfopt.expected_returns.capm_return(prices, market_prices=None, returns_data=False, risk_free_rate=0.02, compounding=True, frequency=252, log_returns=False)[source]

Compute a return estimate using the Capital Asset Pricing Model. Under the CAPM, asset returns are equal to market returns plus a \(eta\) term encoding the relative risk of the asset.

\[R_i = R_f + \beta_i (E(R_m) - R_f)\]
Parameters:
  • prices (pd.DataFrame) – adjusted closing prices of the asset, each row is a date and each column is a ticker/id.
  • market_prices (pd.DataFrame, optional) – adjusted closing prices of the benchmark, defaults to None
  • returns_data (bool, defaults to False.) – if true, the first arguments are returns instead of prices.
  • risk_free_rate (float, optional) – risk-free rate of borrowing/lending, defaults to 0.02. You should use the appropriate time period, corresponding to the frequency parameter.
  • compounding (bool, defaults to True) – computes geometric mean returns if True, arithmetic otherwise, optional.
  • frequency (int, optional) – number of time periods in a year, defaults to 252 (the number of trading days in a year)
  • log_returns (bool, defaults to False) – whether to compute using log returns
Returns:

annualised return estimate

Return type:

pd.Series

pypfopt.expected_returns.returns_from_prices(prices, log_returns=False)[source]

Calculate the returns given prices.

Parameters:
  • prices (pd.DataFrame) – adjusted (daily) closing prices of the asset, each row is a date and each column is a ticker/id.
  • log_returns (bool, defaults to False) – whether to compute using log returns
Returns:

(daily) returns

Return type:

pd.DataFrame

pypfopt.expected_returns.prices_from_returns(returns, log_returns=False)[source]

Calculate the pseudo-prices given returns. These are not true prices because the initial prices are all set to 1, but it behaves as intended when passed to any PyPortfolioOpt method.

Parameters:
  • returns (pd.DataFrame) – (daily) percentage returns of the assets
  • log_returns (bool, defaults to False) – whether to compute using log returns
Returns:

(daily) pseudo-prices.

Return type:

pd.DataFrame