Back-test result#

This module defines BacktestResult.

This is the object that is returned by the cvxportfolio.MarketSimulator.backtest() method, and also by the same method in derived classes of cvxportfolio.MarketSimulator. It contains all relevant information from a back-test and implements the logic to compute various performance metrics, in addition to the BacktestResult.plot() method for producing plots and __repr__ magic method, which is invoked when the user prints an instance.

Added in version 1.1.0: The BacktestResult.log property, which returns the logs produced during the back-test, at level INFO or higher. It works also for back-tests run in parallel!

class cvxportfolio.result.BacktestResult(universe, trading_calendar, costs)View on GitHub#

Store the data from a back-test and produce metrics and plots.

Additionally, record all logs produced by the simulator, market data server, and policy object during the back-test. These are stored in the logs attribute as a newline separated string. This is done in a multi-process safe manner, so that if you run parallel back-tests with cvxportfolio.MarketSimulator.backtest_many(), only the logs from the right process are recorded.

Parameters:
  • universe (pandas.Index) – Best initial guess of the trading universe.

  • trading_calendar (pd.DateTimeIndex) – Trading calendar. Can be a best guess, but the first timestamp must be the actual.

  • costs (list) – Simulator cost objects whose value is logged. Note: we use only the classes’ names here.

Note

The initializer of this class is still experimental, we might still change its signature without the guarantee of semantic versioning.

plot(show=True, how_many_weights=7)View on GitHub#

Make plot and show it.

Parameters:
  • show (bool) – if True, call matplotlib.Figure.show, helpful when running in the interpreter.

  • how_many_weights (int) – How many assets’ weights are shown in the weights plots. The ones with largest average absolute value are chosen.

Returns:

Resulting matplotlib figure.

Return type:

matplotlib.figure.Figure

times_plot(show=True)View on GitHub#

Plot all execution times of the back-test.

Parameters:

show (bool) – if True, call matplotlib.Figure.show, helpful when running in the interpreter.

Returns:

Resulting matplotlib figure.

Return type:

matplotlib.figure.Figure

property logsView on GitHub#

Logs from the policy, simulator, market data server, ….

Returns:

Logs produced during the back-test, newline separated.

Return type:

str

property cash_keyView on GitHub#

The name of the cash unit used (e.g., USDOLLAR).

Returns:

Name of the cash accounting unit.

Return type:

str

property periods_per_yearView on GitHub#

Average trading periods per year in this backtest (rounded).

Returns:

Average periods per year.

Return type:

int

property vView on GitHub#

The total value (or NAV) of the portfolio at each period.

Returns:

Total value at each period.

Return type:

pandas.Series

property profitView on GitHub#

The total profit (PnL) in this backtest.

Returns:

Total profit.

Return type:

float

property wView on GitHub#

The weights of the portfolio at each period.

Returns:

Portfolio weights at each period.

Return type:

pandas.DataFrame

property h_plusView on GitHub#

The post-trade portfolio (holdings) at each period.

Returns:

Post-trade holdings at each period.

Return type:

pandas.DataFrame

property w_plusView on GitHub#

The post-trade weights of the portfolio at each period.

Returns:

Post-trade weights at each period.

Return type:

pandas.DataFrame

property leverageView on GitHub#

Leverage of the portfolio at each period.

This is defined as:

\[\| {(h_t)}_{1:n} \|_1 / v_t,\]

where \(h_t\) is the portfolio (the holdings) at time \(t\), we exclude the cash account from the \(\ell_1\) norm, and \(v_t\) is the total value (NAV) of the portfolio at time \(t\).

Returns:

Leverage at each period.

Return type:

pandas.Series

property turnoverView on GitHub#

The turnover of the portfolio at each period.

This is defined as:

\[\| {(u_t)}_{1:n} \|_1 / (2 v_t),\]

where \(u_t\) are the portfolio trades at time \(t\), we exclude the cash account from the \(\ell_1\) norm, and \(v_t\) is the total value (NAV) of the portfolio at time \(t\).

Returns:

Turnover at each period.

Return type:

pandas.Series

property returnsView on GitHub#

The portfolio returns at each period.

This is defined as:

\[R_t^\text{p} = \frac{v_{t+1} - v_t}{v_t}\]

in terms of the portfolio value (NAV).

Returns:

Portfolio returns at each period.

Return type:

pandas.Series

property average_returnView on GitHub#

The average realized return \(\overline{R^\text{p}}\).

Returns:

Average portfolio return.

Return type:

float

property annualized_average_returnView on GitHub#

The average realized return, annualized.

Returns:

Average portfolio return, annualized.

Return type:

float

property growth_ratesView on GitHub#

The growth rate (or log-return) of the portfolio at each period.

This is defined as:

\[G^\text{p}_t = \log (v_{t+1} / v_t) = \log(1 + R^\text{p}_t).\]
Returns:

Growth rate of the portfolio value at each period.

Return type:

pandas.Series

property average_growth_rateView on GitHub#

The average portfolio growth rate \(\overline{G^\text{p}}\).

Returns:

Average growth rate.

Return type:

float

property annualized_average_growth_rateView on GitHub#

The average portfolio growth rate, annualized.

Returns:

Average growth rate, annualized.

Return type:

float

property volatilityView on GitHub#

Realized volatility (standard deviation of the portfolio returns).

Returns:

Volatility.

Return type:

float

property annualized_volatilityView on GitHub#

Realized volatility, annualized.

Returns:

Volatility, annualized.

Return type:

float

property quadratic_riskView on GitHub#

Quadratic risk, square of the realized volatility.

Returns:

Quadratic risk.

Return type:

float

property annualized_quadratic_riskView on GitHub#

Quadratic risk, annualized.

Returns:

Quadratic risk, annualized.

Return type:

float

property excess_returnsView on GitHub#

Excess portfolio returns with respect to the cash returns.

Returns:

Excess returns at each period.

Return type:

pandas.Series

property excess_volatilityView on GitHub#

Excess volatility (standard deviation of the excess returns).

Returns:

Average excess volatility.

Return type:

float

property average_excess_returnView on GitHub#

The average excess return \(\overline{R^\text{e}}\).

Returns:

Average excess portfolio return.

Return type:

float

property annualized_average_excess_returnView on GitHub#

The average excess return, annualized.

Returns:

Average excess portfolio return, annualized.

Return type:

float

property annualized_excess_volatilityView on GitHub#

Annualized excess volatility.

Returns:

Average excess volatility, annualized.

Return type:

float

property active_returnsView on GitHub#

Portfolio returns minus benchmark returns (if defined by policy).

Returns:

Active returns at each period if benchmark is defined, else nan.

Return type:

pandas.Series

property active_volatilityView on GitHub#

Active volatility (standard deviation of the active returns).

Returns:

Average active volatility if benchmark is defined, else nan.

Return type:

float

property average_active_returnView on GitHub#

The average active return \(\overline{R^\text{a}}\).

Returns:

Average active portfolio return if benchmark is defined, else nan.

Return type:

float

property annualized_average_active_returnView on GitHub#

The average active return, annualized.

Returns:

Average active portfolio return, annualized. If benchmark is not defined, nan.

Return type:

float

property annualized_active_volatilityView on GitHub#

Annualized active volatility.

Returns:

Average active volatility, annualized. If benchmark is not defined, nan.

Return type:

float

property sharpe_ratioView on GitHub#

Sharpe ratio (using annualized excess portfolio returns).

This is defined as

\[\text{SR} = \overline{R^\text{e}}/\sigma^\text{e}\]

where \(\overline{R^\text{e}}\) is the average excess portfolio return and \(\sigma^\text{e}\) its standard deviation. Both are annualized.

Returns:

Sharpe Ratio.

Return type:

float

property information_ratioView on GitHub#

Information ratio (using annualized active portfolio returns).

This is defined as

\[\text{IR} = \overline{R^\text{a}}/\sigma^\text{a}\]

where \(\overline{R^\text{a}}\) is the average active portfolio return and \(\sigma^\text{a}\) its standard deviation. Both are annualized.

Returns:

Information Ratio, nan if benchmark is not defined.

Return type:

float

property excess_growth_ratesView on GitHub#

The growth rate of the portfolio, relative to cash.

This is defined as:

\[G^\text{e}_t = \log(1 + R^\text{e}_t)\]

where \(R^\text{e}_t\) are the excess portfolio returns.

Returns:

Excess growth rates at each period.

Return type:

pandas.Series

property average_excess_growth_rateView on GitHub#

The average excess growth rate \(\overline{G^\text{e}}\).

Returns:

Average excess portfolio growth rates.

Return type:

float

property annualized_average_excess_growth_rateView on GitHub#

The average excess growth rate, annualized.

Returns:

Average excess portfolio growth rates, annualized.

Return type:

float

property active_growth_ratesView on GitHub#

The growth rate of the portfolio, relative to benchmark.

This is defined as:

\[G^\text{a}_t = \log(1 + R^\text{a}_t)\]

where \(R^\text{a}_t\) are the active portfolio returns.

Returns:

Active growth rates at each period. If benchmark is not defined, nan.

Return type:

pandas.Series

property average_active_growth_rateView on GitHub#

The average active growth rate \(\overline{G^\text{a}}\).

Returns:

Average active portfolio growth rates. If benchmark is not defined, nan.

Return type:

float

property annualized_average_active_growth_rateView on GitHub#

The average active growth rate, annualized.

Returns:

Average active portfolio growth rates, annualized. If benchmark is not defined, nan.

Return type:

float

property drawdownView on GitHub#

The drawdown of the portfolio value over time.

Returns:

Drawdown of portfolio value at each period.

Return type:

pandas.Series

property policy_timesView on GitHub#

The computation time of the policy object at each period.

Returns:

Policy time in seconds at each period.

Return type:

pandas.Series

property simulator_timesView on GitHub#

The computation time of the simulator object at each period.

Returns:

Simulator time in seconds at each period.

Return type:

pandas.Series

property market_data_timesView on GitHub#

The computation time of the market data server at each period.

This is already included in simulator_times !

Returns:

Market data server time in seconds at each period.

Return type:

pandas.Series

property result_timesView on GitHub#

The computation time of the back-test result (this) at each period.

This is already included in simulator_times !

Returns:

Back-test result time in seconds at each period.

Return type:

pandas.Series

Interface methods with the market simulator#

class cvxportfolio.result.BacktestResult(universe, trading_calendar, costs)View on GitHub

Store the data from a back-test and produce metrics and plots.

Additionally, record all logs produced by the simulator, market data server, and policy object during the back-test. These are stored in the logs attribute as a newline separated string. This is done in a multi-process safe manner, so that if you run parallel back-tests with cvxportfolio.MarketSimulator.backtest_many(), only the logs from the right process are recorded.

Parameters:
  • universe (pandas.Index) – Best initial guess of the trading universe.

  • trading_calendar (pd.DateTimeIndex) – Trading calendar. Can be a best guess, but the first timestamp must be the actual.

  • costs (list) – Simulator cost objects whose value is logged. Note: we use only the classes’ names here.

Note

The initializer of this class is still experimental, we might still change its signature without the guarantee of semantic versioning.

log_trading(t: pd.Timestamp, h: pd.Series[float], u: pd.Series[float], z: pd.Series[float], costs: Dict[str, float], cash_return: float, benchmark_return: float or None, policy_time: float, simulator_time: float, market_data_time: float)View on GitHub#

Log one trading period.

Parameters:
  • t (pd.Timestamp) – Timestamp of execution.

  • h (pd.Series) – Initial holdings.

  • u (pd.Series) – Trade vectors in (e.g.) dollars.

  • z (pd.Series) – Trade weight vectors requested by the policy. Can be different from the actual trades because of rounding or any other filtering applied by the simulator. They are recorded but not used in accounting.

  • costs (dict) – Dictionary indexed by the cost class names with the current values of each. They are recorded but not used for accounting, that is done directly in the simulator and already included in the computed holdings.

  • cash_return (float) – Current return of the cash account (interest rate), used for excess metrics (e.g., Sharpe ratio).

  • benchmark_return (float or None) – Current return of the benchmark, if defined, otherwise None.

  • policy_time (float) – Time spent inside the policy object in this period.

  • simulator_time (float) – Time spent to back-test this period outside of the policy object.

  • market_data_time (float) – Time spent inside cvxportfolio.data.MarketData() for this period (also already included in simulator_time).

Note

This method is still experimental, we might change its signature without the guarantee of semantic versioning.

log_final(t, t_next, h, extra_simulator_time)View on GitHub#

Log final elements and (if necessary) clean up.

Also clean up if the back-test finishes before it was expected to (e.g., bankruptcy).

Parameters:
  • t (pd.Timestamp) – Last execution time.

  • t_next (pd.Timestamp) – Next execution time, at which we don’t run the policy but we do record the holdings.

  • h (pd.Series) – Last value of the holdings, at time t_next.

  • extra_simulator_time (float) – Any extra time, in seconds, spent in the simulator to propagate the holdings from t to t_next (other times are already accounted for).

Note

This method is still experimental, we might change its signature without the guarantee of semantic versioning.