backtestEngine
Create backtestEngine
object to backtest strategies and
analyze results
Since R2020b
Description
Create a backtestEngine
to run a backtest of portfolio
investment strategies on historical data.
Use this workflow to develop and run a backtest:
Define the strategy logic using a
backtestStrategy
object to specify how a strategy rebalances a portfolio of assets.Use
backtestEngine
to create abacktestEngine
object that specifies parameters of the backtest.Use
runBacktest
to run the backtest against historical asset price data and, optionally, trading signal data.Use
equityCurve
to plot the equity curves of each strategy.Use
summary
to summarize the backtest results in a table format.
For more detailed information on this workflow, see Backtest Investment Strategies Using Financial Toolbox.
Creation
Description
creates a backtester
= backtestEngine(strategies
)backtestEngine
object. Use the
backtestEngine
object to backtest the portfolio
trading strategies defined in the backtestStrategy
objects.
sets properties using
name-value pair arguments and any of the arguments in the previous syntax.
You can specify multiple name-value pair arguments. For example,
backtester
= backtestEngine(___,Name,Value
)backtester =
backtestEngine(strategies,'RiskFreeRate',0.02,'InitialPortfolioValue',1000,'RatesConvention',"Annualized",'Basis',2)
.
Input Arguments
strategies
— Backtest strategies
vector of backtestStrategy
objects
Backtest strategies, specified as a vector of backtestStrategy
objects. Each backtestStrategy
object defines a portfolio trading
strategy.
Data Types: object
Specify optional pairs of arguments as
Name1=Value1,...,NameN=ValueN
, where Name
is
the argument name and Value
is the corresponding value.
Name-value arguments must appear after other arguments, but the order of the
pairs does not matter.
Before R2021a, use commas to separate each name and value, and enclose
Name
in quotes.
Example: backtester =
backtestEngine(strategies,'RiskFreeRate',0.02,'InitialPortfolioValue',1000,'RatesConvention',"Annualized",'Basis',2)
RiskFreeRate
— Risk free rate
0
(default) | numeric | timetable
Risk free rate, specified as the comma-separated pair consisting
of 'RiskFreeRate'
and a scalar numeric or a
one-column timetable.
Note
If you specify a timetable:
The dates in the specified
timetable
must include the start and end dates of the backtest.The series of dates in the specified timetable between the start and end dates (inclusive) must correspond exactly to the corresponding series of dates in the
assetPrices
timetable.
If RatesConvention
is
"Annualized"
, then
RiskFreeRate
specifies an annualized
rate.
If RatesConvention
is
"PerStep"
, then the
RiskFreeRate
is a decimal percentage and
represents the risk free rate for one time step in the backtest. For
example, if the backtest uses daily asset price data, then the
RiskFreeRate
value must be the daily rate of
return for cash.
Data Types: double
| timetable
CashBorrowRate
— Cash borrowing rate
0
(default) | numeric | timetable
Cash borrowing rate, specified as the comma-separated pair
consisting of 'CashBorrowRate'
and a scalar
numeric or a one-column timetable.
Note
If you specify a timetable:
The dates in the specified
timetable
must include the start and end dates of the backtest.The series of dates in the specified timetable between the start and end dates (inclusive) must correspond exactly to the corresponding series of dates in the
assetPrices
timetable.
The CashBorrowRate
specifies the rate of
interest accrual on negative cash balances (margin) during the
backtest.
If RatesConvention
is
"Annualized"
, then
CashBorrowRate
specifies an annualized
rate.
If RatesConvention
is
"PerStep"
, then the
CashBorrowRate
value is a decimal percentage
and represents the interest accrual rate for one time step in the
backtest. For example, if the backtest is using daily asset price
data, then the CashBorrowRate
value must be the
daily interest rate for negative cash balances.
Data Types: double
| timetable
InitialPortfolioValue
— Initial portfolio value
10000
(default) | numeric
Initial portfolio value, specified as the comma-separated pair
consisting of 'InitialPortfolioValue'
and a
scalar numeric.
Data Types: double
RatesConvention
— Defines how backtest engine uses RiskFreeRate
and CashBorrowRate
to compute interest
"Annualized"
(default) | character vector with value 'Annualized'
or
'PerStep'
| string with value "Annualized"
or
"PerStep"
Since R2021a
Defines how backtest engine uses RiskFreeRate
and CashBorrowRate
to compute interest,
specified as the comma-separated pair consisting of
'RatesConvention'
and a character vector or string.
'Annualized'
— The rates are treated as annualized rates and the backtest engine computes incremental interest based on the day count convention specified in theBasis
property. This is the default.'PerStep'
— The rates are treated as per-step rates and the backtest engine computes interest at the provided rates at each step of the backtest.
Data Types: char
| string
DateAdjustment
— Date handling behavior for rebalance dates that are missing from asset prices timetable
"Previous"
(default) | character vector with value 'Previous'
,
'Next'
, or 'None'
| string with value "Previous"
,
"Next"
, or "None"
Since R2022a
Date handling behavior for rebalance dates that are missing from
asset prices timetable, specified as the comma-separated pair
consisting of 'DateAdjustment'
and a character
vector or string.
'Previous'
— For each rebalance date in the rebalance schedule, the rebalance occurs on the nearest date in the asset timetable that occurs on or before the requested rebalance date. This is the default.'Next'
— Move to the next date.'None'
— Dates are not adjusted and the backtest engine errors when encountering a rebalance date that does not appear in the asset prices timetable.
Data Types: char
| string
Basis
— Defines day-count convention when computing interest at RiskFreeRate
or
CashBorrowRate
0
(default) | numeric values: 0
,1
,
2
, 3
,
4
, 6
, 7
,
8
, 9
,
10
, 11
,
12
, 13
Since R2021a
Defines the day-count convention when computing interest at the
RiskFreeRate
or
CashBorrowRate
, specified as the
comma-separated pair consisting of 'Basis'
and a
scalar integer using a supported value:
0 = actual/actual
1 = 30/360 (SIA)
2 = actual/360
3 = actual/365
4 = 30/360 (PSA)
5 = 30/360 (ISDA)
6 = 30/360 (European)
7 = actual/365 (Japanese)
8 = actual/actual (ICMA)
9 = actual/360 (ICMA)
10 = actual/365 (ICMA)
11 = 30/360E (ICMA)
12 = actual/365 (ISDA)
13 = BUS/252
For more information, see Basis.
Note
Basis
is only used when the
RatesConvention
property is set to
"Annualized"
. If the
RatesConvention
is
"PerStep"
, and
Basis
is set,
backtestEngine
ignores the
Basis
value.
Data Types: double
PayExpensesFromCash
— Indicates if transaction expenses paid from cash account or by reducing portfolio value
false
(default) | true
or false
Since R2023b
Indicates if backtest expenses (transaction costs or fees) are
paid from cash account or by reducing the total portfolio value,
specified as the comma-separated pair consisting of
'PayExpensesFromCash'
and a logical
value.
If set to false
(the default), backtest
expenses are paid by reducing the total portfolio value. This allows
the backtest engine to pay for expenses while maintaining the
strategy allocation weights exactly.
If set to true
, the backtest engine pays all
expenses from one or more cash accounts
(CashAssets
) or debt accounts
(DebtAssets
). This happens as follows:
If user-controlled cash assets are not specified (that is, the
CashAssets
orDebtAssets
parameters forrunBacktest
are not set), then expenses are paid from the unallocated cash account. This is the default behavior. The unallocated cash account contains the remaining portfolio value when thebacktestStrategy
rebalance function returns portfolio weights that do not sum to1
. Unallocated cash earns theRiskFreeRate
(orCashBorrowRate
if it goes negative).If user-controlled cash assets are specified (that is,
CashAssets
orDebtAssets
parameters forrunBacktest
are set), then expenses are paid using the first specified cash asset if it has sufficient funds. If not, the first cash asset is set to $0
and the engine moves on to the second cash asset, continuing in this way until the expense is paid. Once allCashAssets
are exhausted and if some expense remains unpaid, then the first specifiedDebtAsset
incurs all remaining expenses. If noDebtAsset
is specified, then the final cash asset goes negative to pay any remaining expense.
Data Types: logical
Properties
Strategies
— Backtest strategies
vector of backtestStrategy
objects
Backtest strategies, specified as a vector of backtestStrategy
objects.
Data Types: object
RiskFreeRate
— Risk free rate
0
(default) | numeric | timetable
Risk free rate, specified as a scalar numeric or timetable.
Data Types: double
CashBorrowRate
— Cash borrowing rate
0
(default) | numeric | timetable
Cash borrowing rate, specified as a scalar numeric or timetable.
Data Types: double
InitialPortfolioValue
— Initial portfolio value
10000
(default) | numeric
Initial portfolio value, specified as a scalar numeric.
Data Types: double
AnnualizedRates
— Use annualized rates for RiskFreeRate
and CashBorrowRate
true
(default) | logical with value true
or
false
Use annualized rates for RiskFreeRate
and
CashBorrowRate
, specified as a scalar
logical.
Data Types: logical
DateAdjustment
— Date handling behavior for rebalance dates that are missing from asset prices timetable
"Previous"
(default) | string with value "Previous"
,
"Next"
, or "None"
Date handling behavior for rebalance dates that are missing from asset prices timetable, specified as a string.
Data Types: char
| string
Basis
— Day-count basis of annualized rates for RiskFreeRate
and CashBorrowRate
0
(default) | numeric values: 0
,1
,
2
, 3
, 4
,
6
, 7
, 8
,
9
, 10
, 11
,
12
, 13
Day-count of annualized rates for RiskFreeRate
and
CashBorrowRate
, specified a scalar integer.
Data Types: double
NumAssets
— Number of assets in portfolio universe
[]
(default) | numeric
This property is read-only.
Number of assets in the portfolio universe, a numeric.
NumAssets
is derived from the timetable of adjusted
prices passed to runBacktest
.
NumAssets
is empty until you run the backtest using
the runBacktest
function.
Data Types: double
Returns
— Strategy returns
[]
(default) | timetable
This property is read-only.
Strategy returns, a
NumTimeSteps
-by-NumStrategies
timetable of strategy returns. Returns are per time step. For example, if
you use daily prices with runBacktest
,
then Returns
is the daily strategy returns.
Returns
is empty until you run the backtest using the
runBacktest
function.
Data Types: timetable
Positions
— Asset positions for each strategy
[]
(default) | structure
This property is read-only.
Asset positions for each strategy, a structure containing a
NumTimeSteps
-by-NumAssets
timetable of asset positions for each strategy. For example, if you use
daily prices in the runBacktest
,
then the Positions
structure holds timetables containing
the daily asset positions. Positions
is empty until you
run the backtest using the runBacktest
function.
Data Types: struct
Turnover
— Strategy turnover
[]
(default) | timetable
This property is read-only.
Strategy turnover, a
NumTimeSteps
-by-NumStrategies
timetable. Turnover
is empty until you run the backtest
using the runBacktest
function.
Data Types: timetable
BuyCost
— Transaction costs for asset purchases of each strategy
[]
(default) | timetable
This property is read-only.
Transaction costs for the asset purchases of each strategy, a
NumTimeSteps
-by-NumStrategies
timetable. BuyCost
is empty until you run the backtest
using the runBacktest
function.
Data Types: timetable
SellCost
— Transaction costs for asset sales of each strategy
[]
(default) | timetable
This property is read-only.
Transaction costs for the asset sales of each strategy, a
NumTimeSteps
-by-NumStrategies
timetable. SellCost
is empty until you run the backtest
using the runBacktest
function.
Data Types: timetable
Fees
— Paid fees for management and performance fees
[]
(default) | struct
Since R2022b
This property is read-only.
Paid fees for management and performance fees, a struct containing a
timetable for each strategy which holds all the fees paid by the strategy.
The Fees
timetable contains an entry for each date where
at least one fee was paid. Each column holds the amount paid for a
particular type of fee. If no fees are paid, then the
Fees
timetable is empty.
For more information on management and performance fees defined using a
backtestStrategy
object, see Management Fees, Performance Fees, and Performance Hurdle.
Data Types: timetable
PayExpensesFromCash
— Indicates if transaction expenses paid from cash account or by reducing portfolio value
false
(default) | true
or false
Since R2023b
This property is read-only.
Indicates if backtest expenses (transaction costs or fees) are paid from cash account or by reducing the total portfolio value, a logical value.
Data Types: logical
TransactionCosts
— Detailed transaction costs
[]
(default) | struct
Since R2023b
This property is read-only.
Detailed transaction costs for per-asset transaction costs for strategy, a
struct containing a timetable of detailed, per-asset transaction costs for
each strategy. The TransactionCosts
timetables contain
one row for each rebalance date and one column for each asset.
If the strategy generates aggregate transaction costs, then the
TransactionCosts
timetable for that strategy is
empty.
Data Types: timetable
Object Functions
runBacktest | Run backtest on one or more strategies |
summary | Generate summary table of backtest results |
equityCurve | Plot equity curves of strategies |
Examples
Backtest Strategy Using backtestEngine
Use a backtesting engine in MATLAB® to run a backtest on an investment strategy over a time series of market data. You can define a backtesting engine by using backtestEngine
object. A backtestEngine
object sets properties of the backtesting environment, such as the risk-free rate, and holds the results of the backtest. In this example, you can create a backtesting engine to run a simple backtest and examine the results.
Create Strategy
Define an investment strategy by using the backtestStrategy
function. This example builds a simple equal-weighted investment strategy that invests equally across all assets. For more information on creating backtest strategies, see backtestStrategy
.
% The rebalance function is simple enough that you can use an anonymous function equalWeightRebalanceFcn = @(current_weights,~) ones(size(current_weights)) / numel(current_weights); % Create the strategy strategy = backtestStrategy("EqualWeighted",equalWeightRebalanceFcn,... 'RebalanceFrequency',20,... 'TransactionCosts',[0.0025 0.005],... 'LookbackWindow',0)
strategy = backtestStrategy with properties: Name: "EqualWeighted" RebalanceFcn: @(current_weights,~)ones(size(current_weights))/numel(current_weights) RebalanceFrequency: 20 TransactionCosts: [0.0025 0.0050] LookbackWindow: 0 InitialWeights: [1x0 double] ManagementFee: 0 ManagementFeeSchedule: 1y PerformanceFee: 0 PerformanceFeeSchedule: 1y PerformanceHurdle: 0 UserData: [0x0 struct] EngineDataList: [0x0 string]
Set Backtesting Engine Properties
The backtesting engine has several properties that you set by using parameters to the backtestEngine
function.
Risk-Free Rate
The RiskFreeRate
property holds the interest rate earned for uninvested capital (that is, cash). When the sum of portfolio weights is below 1, the remaining capital is invested in cash and earns the risk-free rate. The risk-free rate and the cash-borrow rate can be defined in annualized terms or as explicit "per-time-step" interest rates. The RatesConvention
property is used to specify how the backtestEngine
interprets the two rates (the default interpretation is "Annualized"). For this example, set the risk-free rate to 2% annualized.
% 2% annualized risk-free rate
riskFreeRate = 0.02;
Cash Borrow Rate
The CashBorrowRate
property sets the interest accrual rate applied to negative cash balances. If at any time the portfolio weights sum to a value greater than 1, then the cash position is negative by the amount in excess of 1. This behavior of portfolio weights is analogous to borrowing capital on margin to invest with leverage. Like the RiskFreeRate
property, the CashBorrowRate
property can either be annualized or per-time-step depending on the value of the RatesConvention
property.
% 6% annualized margin interest rate
cashBorrowRate = 0.06;
Initial Portfolio Value
The InitialPortfolioValue
property sets the value of the portfolio at the start of the backtest for all strategies. The default is $10,000.
% Start backtest with $1M
initPortfolioValue = 1000000;
Create Backtest Engine
Using the prepared properties, create the backtesting engine using the backtestEngine
function.
% The backtesting engine takes an array of backtestStrategy objects as the first argument backtester = backtestEngine(strategy,... 'RiskFreeRate',riskFreeRate,... 'CashBorrowRate',cashBorrowRate,... 'InitialPortfolioValue',initPortfolioValue)
backtester = backtestEngine with properties: Strategies: [1x1 backtestStrategy] RiskFreeRate: 0.0200 CashBorrowRate: 0.0600 RatesConvention: "Annualized" Basis: 0 InitialPortfolioValue: 1000000 DateAdjustment: "Previous" PayExpensesFromCash: 0 NumAssets: [] Returns: [] Positions: [] Turnover: [] BuyCost: [] SellCost: [] TransactionCosts: [] Fees: []
Several additional properties of the backtesting engine are initialized to empty. The backtesting engine populates these properties, which contain the results of the backtest, upon completion of the backtest.
Load Data and Run Backtest
Run the backtest over daily price data from the 30 component stocks of the DJIA.
% Read table of daily adjusted close prices for 2006 DJIA stocks T = readtable('dowPortfolio.xlsx'); % Remove the DJI index column and convert to timetable pricesTT = table2timetable(T(:,[1 3:end]),'RowTimes','Dates');
Run the backtest using the runBacktest
function.
backtester = runBacktest(backtester,pricesTT)
backtester = backtestEngine with properties: Strategies: [1x1 backtestStrategy] RiskFreeRate: 0.0200 CashBorrowRate: 0.0600 RatesConvention: "Annualized" Basis: 0 InitialPortfolioValue: 1000000 DateAdjustment: "Previous" PayExpensesFromCash: 0 NumAssets: 30 Returns: [250x1 timetable] Positions: [1x1 struct] Turnover: [250x1 timetable] BuyCost: [250x1 timetable] SellCost: [250x1 timetable] TransactionCosts: [1x1 struct] Fees: [1x1 struct]
Examine Results
The backtesting engine populates the read-only properties of the backtestEngine
object with the backtest results. Daily values for portfolio returns, asset positions, turnover, transaction costs, and fees are available to examine.
Examine the daily returns.
% Generate a histogram of daily portfolio returns histogram(backtester.Returns{:,1}) title('Daily Portfolio Returns')
Use equityCurve
to plot the equity curve for the simple equal-weighted investment strategy.
equityCurve(backtester)
Version History
Introduced in R2020bR2023b: Detailed transaction costs
The backtestEngine
object supports a read-only property for
TransactionCosts
to contain per-asset transaction costs for
each rebalance date.
R2023b: User-controlled cash handling
The backtestEngine
object supports a read-only property for
ExpensesPaidFromCash
to indicate if transaction expenses
are paid from a cash account or debt account.
R2022b: Management and performance fees
The backtestEngine
object supports a read-only property for
Fees
that reports the management and performance fees paid
during a backtest. The Fees
property is a struct containing a
timetable for each strategy and the timetable holds all the fees paid by the
strategy.
R2022a: Specify time varying cash rates of return
The backtestEngine
name-value arguments for
RiskFreeRate
and CashBorrowRate
support a timetable
data type.
R2022a: Control how the backtesting framework handles missing rebalance dates
The name-value argument for DateAdjustment
enables you to
control the date handling behavior for rebalance dates that are missing from the
assetPrices
timetable. If a rebalance date falls on a
holiday, you can specify the "Next"
or "None"
option for DateAdjustment
.
Open Example
You have a modified version of this example. Do you want to open this example with your edits?
MATLAB Command
You clicked a link that corresponds to this MATLAB command:
Run the command by entering it in the MATLAB Command Window. Web browsers do not support MATLAB commands.
Select a Web Site
Choose a web site to get translated content where available and see local events and offers. Based on your location, we recommend that you select: .
You can also select a web site from the following list:
How to Get Best Site Performance
Select the China site (in Chinese or English) for best site performance. Other bat365 country sites are not optimized for visits from your location.
Americas
- América Latina (Español)
- Canada (English)
- United States (English)
Europe
- Belgium (English)
- Denmark (English)
- Deutschland (Deutsch)
- España (Español)
- Finland (English)
- France (Français)
- Ireland (English)
- Italia (Italiano)
- Luxembourg (English)
- Netherlands (English)
- Norway (English)
- Österreich (Deutsch)
- Portugal (English)
- Sweden (English)
- Switzerland
- United Kingdom (English)