backtestStrategy
Create backtestStrategy
object to define portfolio allocation
strategy
Since R2020b
Description
Create a backtestStrategy
object which defines a portfolio
allocation strategy.
Use this workflow to develop and run a backtest:
Define the strategy logic using a
backtestStrategy
object to specify how the strategy rebalances a portfolio of assets.Use
backtestEngine
to create abacktestEngine
object that specifies the 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 Workflow and Backtest Investment Strategies Using Financial Toolbox.
Creation
Description
creates a strategy
= backtestStrategy(name
,rebalanceFcn
)backtestStrategy
object.
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,
strategy
= backtestStrategy(___,Name,Value
)strat =
backtestStrategy('MyStrategy',@myRebalFcn,'TransactionCost',0.005,'LookbackWindow',20)
.
Input Arguments
name
— Strategy name
string
Strategy name, specified as a string.
Note
The strategy name must be a unique and valid MATLAB® variable name. Any space character in the
name
is converted to an underscore
character.
Data Types: string
rebalanceFcn
— Rebalance function
function handle
Rebalance function, specified as a function handle which computes new
portfolio weights during the backtest. The
rebalanceFcn
argument implements the core logic
of the trading strategy.
The signature of the rebalanceFcn
depends on
whether UserData
and
EngineDataList
name-value arguments are
specified for the backtestStrategy
function. For an
example of rebalanceFcn
where
UserData
is specified, see Backtest with Brinson Attribution to Evaluate Portfolio Performance.
If
UserData
andEngineDataList
are EmptyIf the
UserData
andEngineDataList
name-value arguments are empty, then therebalanceFcn
must have one of the following signatures:new_weights = rebalanceFcn(weights,assetPrices)
new_weights = rebalanceFcn(weights,assetPrices,signalData)
If you do not specify
UserData
, the rebalance function returns a single output argument,new_weights
, which is a vector of asset weights specified as decimal percentages.If the
new_weights
sum to1
, then the portfolio is fully invested.If the
new_weights
sum to less than1
, then the portfolio has the remainder in cash, earning theRiskFreeRate
specified in thebacktestEngine
object.If the
new_weights
sum to more than1
, then there is a negative cash position (margin) and the cash borrowed accrues interest at the cash borrowing rate specified in theCashBorrowRate
property of thebacktestEngine
object.
If
UserData
is Empty andEngineDataList
is SpecifiedIf the
UserData
name-value argument is empty and theEngineDataList
name-value argument is specified, then therebalanceFcn
must have one of the following signatures:new_weights = rebalanceFcn(engineData,assetPrices)
new_weights = rebalanceFcn(engineData,assetPrices,signalData)
If
UserData
is Specified andEngineDataList
is EmptyIf you use the name-value argument for
UserData
to specify a strategy-specificuserData
struct but do not specify the name-value argument forEngineDataList
, the required syntax forrebalanceFcn
must have one of the following signatures:[new_weights,userData] = rebalanceFcn(weights,assetPrices,userData)
[new_weights,userData] = rebalanceFcn(weights,assetPrices,signalData,userData)
If
UserData
is specified, then in addition to an output fornew_weights
, there is also an output foruserData
returned as a struct.If Both
UserData
andEngineDataList
are SpecifiedIf you use the name-value argument for
UserData
to specify a strategy-specificuserData
struct and you use the name-value argumentEngineDataList
to specify a backtest engine state, the required syntax forrebalanceFcn
must have one of the following signatures:[new_weights,userData] = rebalanceFcn(engineData,assetPrices,userData)
[new_weights,userData] = rebalanceFcn(engineData,assetPrices,signalData,userData)
If
UserData
is specified, then in addition to an output fornew_weights
, there is also an output foruserData
returned as a struct.
The rebalanceFcn
function is called by the backtestEngine
object each time the strategy must be
rebalanced as specified in the RebalanceFrequency
name-value argument. The backtestEngine
object calls the
rebalanceFcn
function with the following arguments:
weights
— The current portfolio weights before rebalancing, specified as decimal percentages.assetPrices
— Atimetable
containing a rolling window of adjusted asset prices.signalData
— (Optional) Atimetable
containing a rolling window of signal data.If you provide signal data to the
backtestEngine
object, then the engine object passes it to the strategy rebalance function using the three input argument syntax. If do not provide signal data thebacktestEngine
object, then the engine object calls the rebalance function with the two input argument syntax.userData
— (Optional) A struct to contain strategy-specific user data.If the
UserData
property is set, theuserData
struct is passed intorebalanceFcn
.engineData
— (Optional) A struct containing backtest state data.If the
EngineDataList
property is set, theengineData
struct is passed intorebalanceFcn
.
For more information on developing a rebalanceFcn
function handle, see Backtest Investment Strategies Using Financial Toolbox.
Data Types: function_handle
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: strat =
backtestStrategy('MyStrategy',@myRebalFcn,'TransactionCost',0.005,'LookbackWindow',20)
RebalanceFrequency
— Rebalance frequency during backtest
1
(default) | integer | duration
object | calendarDuration
object | vector of datetime
objects
Rebalance frequency during the backtest, specified as the
comma-separated pair consisting of
'RebalanceFrequency'
and a scalar integer,
duration
or calendarDuration
object, or a vector of datetime
objects.
The RebalanceFrequency
specifies the schedule
of dates where the strategy will rebalance its portfolio. The
default is 1
, meaning the strategy rebalances
with each time step.
For more information, see Defining Schedules for Backtest Strategies.
Data Types: double
| object
| datetime
TransactionCosts
— Transaction costs for trades
0
(not computed) (default) | numeric | vector | function handle
Transaction costs for trades, specified as the comma-separated
pair consisting of 'TransactionCosts'
and a
scalar numeric, vector, or function handle. You can specify
transaction costs in three ways:
rate
— A scalar decimal percentage charge to both purchases and sales of assets. For example ,if you setTransactionCosts
to0.001
, then each transaction (buys and sells) would pay 0.1% in transaction fees.[buyRate, sellRate]
— A1
-by-2
vector of decimal percentage rates that specifies separate rates for buying and selling of assets.computeTransactionCostsFcn
— A function handle to compute customized transaction fees. If you specify a function handle, thebacktestEngine
object calls theTransactionCosts
function to compute the fees for each rebalance. The user-definedcomputeTransactionCostsFcn
function handle has different signatures depending on whether the optional name-value argumentsUserData
andEngineDataList
are specified and whether thesellCosts
,buyCosts
, ordetailedCosts
outputs are used.If
UserData
is Empty andEngineDataList
is Empty UsingbuyCosts
andsellCosts
OutputsWhen the optional name-value arguments
UserData
andEngineDataList
are not specified and the sellCosts and buyCosts outputs are used, thecomputeTransactionCostsFcn
function handle has the following signature:[buyCosts,sellCosts] = computeTransactionCostsFcn(deltaPositions)
The user-defined
computeTransactionCostsFcn
function handle takes a single input argument,deltaPositions
, which is a vector of changes in asset positions for all assets (in currency units) as a result of a rebalance. Positive elements in thedeltaPositions
vector indicate purchases while negative entries represent sales. The user-defined function handle must return two output argumentsbuyCosts
andsellCosts
, which contain the total costs (in currency) for the entire rebalance for each type of transaction.If
UserData
is Empty andEngineDataList
is Empty anddetailedCosts
Output is specifiedWhen the optional name-value arguments
UserData
andEngineDataList
are not specified anddetailedCosts
output is specified, thecomputeTransactionCostsFcn
function handle has the following signature:detailedCosts = computeTransactionCostsFcn(deltaPositions)
The user-defined
computeTransactionCostsFcn
function handle takes a single input argument,deltaPositions
, which is a vector of changes in asset positions for all assets (in currency units) as a result of a rebalance. Positive elements in thedeltaPositions
vector indicate purchases while negative entries represent sales. The user-defined function handle returns one output argument fordetailedCosts
for per-asset transaction costs.If
UserData
is Empty andEngineDataList
is Specified UsingbuyCosts
andsellCosts
OutputsWhen the optional name-value argument
UserData
is not specified, but the optional name-value argumentEngineDataList
is specified and thebuyCosts
andsellCosts
output are used, thecomputeTransactionCostsFcn
function handle has the following signatures:[buyCosts,sellCosts] = computeTransactionCostsFcn(deltaPositions,engineData)
If
UserData
is Empty andEngineDataList
is Specified anddetailedCosts
Output is SpecifiedWhen the optional name-value argument
UserData
is not specified, but the optional name-value argumentEngineDataList
is specified anddetailedCosts
output is specified, thecomputeTransactionCostsFcn
function handle has the following signatures:detailedCosts = computeTransactionCostsFcn(deltaPositions,engineData)
If
UserData
is Specified andEngineDataList
is Empty UsingbuyCosts
andsellCosts
OutputsWhen the optional name-value argument
UserData
is specified, but the optional name-value argumentEngineDataList
is not specified andbuyCosts
andsellCosts
outputs are used, thecomputeTransactionCostsFcn
function handle has the following signatures:[buyCosts,sellCosts,userData] = computeTransactionCostsFcn(deltaPositions,userData)
If
UserData
is Specified andEngineDataList
is Empty anddetailedCosts
Output is specifiedWhen the optional name-value argument
UserData
is specified, but the optional name-value argumentEngineDataList
is not specified anddetailedCosts
output is specified, thecomputeTransactionCostsFcn
function handle has the following signatures:[detailedCosts,userData] = computeTransactionCostsFcn(deltaPositions,userData)
If Both
UserData
andEngineDataList
are Specified UsingbuyCosts
andsellCosts
OutputsIf you use the optional name-value argument for
UserData
to specify a strategy-specificuserData
struct and the optional name-value argumentEngineDataList
to specify required backtest engine state data andbuyCosts
andsellCosts
arguments are used, the required syntax for thecomputeTransactionCostsFcn
function handle must have the following signature:If you specify the optional name-value argument[buyCosts,sellCosts,userData] = computeTransactionCostsFcn(deltaPositions,engineData,userData)
UserData
, then the strategyuserData
struct is passed to thecomputeTransactionCostsFcn
cost function as the final input argument. ThecomputeTransactionCostsFcn
function handle can read or write any information to theuserData
struct. The updateduserData
struct is returned as the third output argument.If Both
UserData
andEngineDataList
are Specified anddetailedCosts
Output is SpecifiedIf you use the optional name-value argument for
UserData
to specify a strategy-specificuserData
struct and the optional name-value argumentEngineDataList
to specify required backtest engine state data and thedetailedCosts
output is specified, the required syntax for thecomputeTransactionCostsFcn
function handle must have the following signature:If you specify the optional name-value argument[detailedCosts,userData] = computeTransactionCostsFcn(deltaPositions,engineData,userData)
UserData
, then the strategyuserData
struct is passed to thecomputeTransactionCostsFcn
cost function as the final input argument. ThecomputeTransactionCostsFcn
function handle can read or write any information to theuserData
struct. The updateduserData
struct is returned as the second output argument.
Data Types: double
| function_handle
LookbackWindow
— Lookback window
[0 Inf]
(default) | scalar or 1
-by-2
vector using nonnegative integers | scalar or 1
-by-2
duration
object | scalar or 1
-by-2
calendarDuration
object
Lookback window, specified as the comma-separated pair consisting
of 'LookbackWindow'
and a scalar or
1
-by-2
vector of integers,
a duration
or calendarDuration
object.
A scalar LookbackWindow
sets the minimum and
maximum lookback window to the same value. In other words, a scalar
LookbackWindow
has a fixed window
size.
A 1
-by-2
vector defines the
minimum and maximum size of the rolling window of data (asset prices
and signal data) that is passed to the rebalance function
(rebalanceFcn
). For an example, see Set the Size of Rolling Windows for Backtest Strategies.
When the inputs are integers, the lookback window refers to the number of rows of data from the asset (
pricesTT
) and signal (signalTT
) timetables passed to the rebalancing function. The lookback minimum sets the minimum number of rows that must be available, prior the row associated with the current rebalancing period, before a strategy rebalance can occur. The lookback maximum sets the maximum number of rows that is passed to the rebalance function.For example:
[10 Inf]
–— At least 10 rows of data must be available before the current rebalancing period.[0 50]
–— At most 50 rows of data prior to the current rebalancing period are passed to the rebalancing function.[0 Inf]
–— Use all available data up to the current rebalancing period; there is no minimum and no maximum. This is the default value.[20 20]
–— Exactly 20 rows of data prior to the current rebalancing period are passed to the rebalancing function; this is equivalent to settingLookbackWindow
to the scalar value20
.
When the inputs are either
duration
orcalendarDuration
objects, the lookback window minimum and maximum are defined in terms of timespans relative to the time of the current rebalance period. For example, if the lookback minimum is set to five days (that is,days(5)
), the rebalance only occurs if the backtest start time is at least five days prior to the rebalance time. Similarly, if the lookback maximum is set to six months (that is,calmonths(6)
), the lookback window only contains data from the six months prior to the rebalance period or later. For an example, see Backtest Investment Strategies Using datetime and calendarDuration.
Note
If the lookback minimum is non-zero, the software does not
call the rebalance function (rebalanceFcn
)
until at least the lookback minimum time steps have passed
before the current rebalancing period. This is regardless of the
value of the rebalance frequency
(RebalanceFrequency
).
Data Types: double
| object
InitialWeights
— Initial portfolio weights
[]
(default) | vector
Initial portfolio weights, specified as the comma-separated pair
consisting of 'InitialWeights'
and a vector. The
InitialWeights
vector sets the portfolio
weights before the backtestEngine
object begins the backtest. The size of
the initial weights vector must match the number of assets used in
the backtest.
Alternatively, you can set the InitialWeights
name-value pair argument to empty ([]
) to
indicate the strategy will begin with no investments and in a 100%
cash position. The default for InitialWeights
is empty ([ ]
).
Data Types: double
ManagementFee
— Management fee charged as an annualized percent of the total portfolio value
0
(default) | decimal
Since R2022b
Management fee charged as an annualized percent of the total
portfolio value, specified as the comma-separated pair consisting of
'ManagementFee'
and a numeric scalar. The
management fee is the annualized fee rate paid to cover the costs of
managing a strategy's portfolio. The management fee is charged based
on the strategy portfolio balance at the start of each date
specified by the ManagementFeeSchedule
. The
default is 0
, meaning no management fee is paid.
For more information, see Management Fees.
Data Types: double
ManagementFeeSchedule
— Management fee schedule
calyears(1)
(default) | integer | vector of datetimes | duration
object | calendarDuration
object
Since R2022b
Management fee schedule, specified as the comma-separated pair
consisting of 'ManagementFeeSchedule'
and a
numeric scalar, a duration
or calendarDuration
object, or alternatively, as a vector of datetimes.
The ManagementFeeSchedule
specifies the
schedule of dates where the management fee is charged (if a
ManagementFee
is defined). The default is
calyears(1)
, meaning the management fee is
charged annually.
For more information, see Defining Schedules for Backtest Strategies.
Data Types: double
| datetime
| object
PerformanceFee
— Performance fee charged as an absolute percent of the total portfolio growth
0
(default) | decimal
Since R2022b
Performance fee charged as a percent of the total portfolio
growth, specified as the comma-separated pair consisting of
'PerformanceFee'
and a numeric scalar. The
performance fee, sometimes called an incentive fee, is a fee paid to
reward a fund manager based on performance of the fund. The fee is
paid periodically based on the schedule specified by the
PerformanceFeeSchedule
. The default is
0
, meaning no performance fee is paid.
For more information, see Performance Fees.
Data Types: double
PerformanceHurdle
— Annualized hurdle rate or column in assets or signals to act as hurdle asset
0
(default) | decimal | string
Since R2022b
Annualized hurdle rate or column in assetPrices
or signalData
to act as hurdle asset, specified
as the comma-separated pair consisting of
'PerformanceHurdle'
and a numeric scalar or a
string. The PerformanceHurdle
sets a minimum
level of performance that a strategy must achieve before the
performance fee is paid.
If specified as a numeric scalar, the hurdle represents an annualized growth rate, for example 0.05 indicates a 5% growth rate. In this case, the performance fee is only paid if, on a performance fee date, the strategy portfolio value is greater than the high-water mark and the portfolio has produced a return greater than 5% annualized since the start of the backtest. The fee is paid only on the growth in excess of both the hurdle rate and the previous high-water mark.
If specified as a string, the hurdle must match a column in either the
assetPrices
orsignalData
timetables of the backtest and it indicates a hurdle asset. In this case, the performance fee is paid only if the portfolio value is greater than the high-water mark and the portfolio has produced a return greater than the hurdle asset return since the start of the backtest. The performance fee is charged only on the portfolio growth in excess of both the hurdle asset growth and the previous high-water mark.
For more information, see Performance Hurdle.
Data Types: double
| string
PerformanceFeeSchedule
— Performance fee schedule
calyears(1)
(default) | integer | vector of datetimes | duration
object | calendarDuration
object
Since R2022b
Performance fee schedule, specified as the comma-separated pair
consisting of 'PerformanceFeeSchedule'
and a
numeric scalar, a duration
or calendarDuration
object, or alternatively as a vector of datetimes.
PerformanceFeeSchedule
specifies the schedule
of dates where the performance fee is charged (if
PerformanceFee
is defined). The default is
calyears(1)
, meaning the performance fee is
charged annually.
For more information, see Defining Schedules for Backtest Strategies.
Data Types: double
| datetime
| object
UserData
— Strategy-specific user data for use in rebalanceFcn
[]
(default) | struct
Since R2023a
Strategy-specific user data for use in
rebalanceFcn
, specified as a struct. When
UserData
is set to a struct, the backtestEngine
provides all user-defined
rebalanceFcn
function handle functions with
the userData
struct as an additional input
argument. The rebalanceFcn
function handle
functions can read and write to the userData
struct. The rebalanceFcn
function handle
functions return the updated userData
struct as
an additional output argument.
For an example of using UserData
, see Use UserData Property to Create Stop Loss Trading Strategy and Backtest with Brinson Attribution to Evaluate Portfolio Performance.
Data Types: struct
EngineDataList
— Specify set of optional backtest state data a strategy needs in rebalanceFcn
[]
(default) | string | string array
Since R2023a
Specify a set of optional backtest state data that a strategy
needs in the rebalanceFcn
function handle,
specified as a scalar string or string array. By default, the
EngineDataList
property is empty, indicating
that no additional engine data is required.
If a strategy requires engine data, you can specify
EngineDataList
as a vector of strings
containing the names of the required engineData
.
The backtestEngine
creates an
engineData
struct with a field corresponding
to each element in the EngineDataList
. Valid
engineData
values are:
Weights
— The current portfolio weights before rebalancing,specified as decimal percentages. This is the default first argument to the rebalance function (RebalanceFcn
).WeightsHistory
— A timetable ofWeights
vectors for each past date in the backtest. This timetable holds the end-of-day asset weights for each asset for each day. Future dates in the timetable containNaN
values.PortfolioValue
— The current portfolio value at the time the rebalance function (RebalanceFcn
) is called, specified as a numeric scalar. ThisPortfolioValue
is potentially different from the "end of day" portfolio value reported by thebacktestEngine
at the end of the backtest. Transaction costs and other fees are paid after the rebalance function is called, so thePortfolioValue
passed into the rebalance function by theengineData
struct may be different from the final portfolio value reported for that day after the backtest completes.PortfolioValueHistory
— A timetable of portfolio values for the backtest. The timetable contains two columns. TheBeforeExpenses
column holds the portfolio value for each day before any expenses (transaction costs or fees). TheEndOfDay
column holds the portfolio value for each day, inclusive of all transaction costs and other fees. The values in the second column (EndOfDay
) match the final end-of-day portfolio values reported at the end of the backtest. Future dates in the timetable containNaN
values.FeesHistory
— A timetable of fees charged for each day in the backtest. TheFeesHistory
timetable has two columns:Management
andPerformance
for the management and performance fees, respectively. The management and performance fees are defined using theManagementFee
andPerformanceFee
name-value arguments. Future dates in the timetable containNaN
values.TransactionCostHistory
— A timetable of per-asset transaction costs charged for each day in the backtest. Future dates in the timetable containNaN
values. TheTransactionCostHistory
cannot be set if theTransactionCosts
function generate aggregate costs. TheTransactionCosts
computeTransactionCostsFcn
function handle must specify an output fordetailedCosts
to setTransactionCostHistory
.CashAsset
— A string array for items in thepricesTT
timetable specified as cash assets.DebtAsset
— A string array for items in thepricesTT
timetable specified as debt assets.
Specifying an EngineDataList
changes the
required syntax of the backtestStrategy
function
handles that you define using rebalanceFcn
. For
an example of using EngineDataList
, see Use EngineDataList Property to Enforce Trading Strategy of Whole Shares.
Data Types: string
Properties
Name
— Strategy name
string
Strategy name, specified as a string.
Data Types: string
RebalanceFcn
— Rebalance function
function handle
Rebalance function, specified as a function handle.
Data Types: function_handle
RebalanceFrequency
— Rebalance frequency during backtest
1
(default) | numeric | duration
object | calendarDuration
object | vector of datetimes
Rebalance frequency during the backtest, specified as a scalar numeric,
duration
or calendarDuration
object, or vector of datetimes.
Data Types: double
| object
| datetime
TransactionCosts
— Transaction costs
0
(default) | numeric | vector | function handle
Transaction costs, specified as a scalar numeric, vector, or function handle.
Data Types: double
| function_handle
LookbackWindow
— Lookback window
[0 Inf]
(default) | numeric | vector
Lookback window, specified as a scalar numeric or vector.
Data Types: double
InitialWeights
— Initial weights
[]
(default) | vector
Initial weights, specified as a vector.
Data Types: double
ManagementFee
— Management fee charged as an annualized percent of the total portfolio value
0
(default) | decimal
Since R2022b
Management fee charged as an annualized percent of the total portfolio value, specified as a numeric scalar.
Data Types: double
ManagementFeeSchedule
— Management fee schedule
calyears(1)
(default) | integer | vector of datetimes | duration
object | calendarDuration
object
Since R2022b
Management fee schedule, specified as scalar numeric, a vector of
datetimes, or a duration
or
calendarDuration
object.
Data Types: double
| datetime
| object
PerformanceFee
— Performance fee charged as an absolute percent of the total portfolio growth
0
(default) | decimal
Since R2022b
Performance fee charged as an absolute percent of the total portfolio growth, specified as a numeric scalar.
Data Types: double
PerformanceHurdle
— Annualized hurdle rate or column in assets or signals to act as hurdle asset
0
(default) | decimal | string
Since R2022b
Annualized hurdle rate or column in assetPrices
or
signalData
to act as hurdle asset, specified as a
numeric scalar or a string in either assetPrices
or
signalData
as a hurdle asset.
Data Types: double
| string
PerformanceFeeSchedule
— Performance fee schedule
calyears(1)
(default) | decimal | vector of datetimes | duration
object | calendarDuration
object
Since R2022b
Performance fee schedule, specified as a scalar numeric, vector of
datetimes, or a duration
or
calendarDuration
object.
Data Types: datetime
| object
UserData
— Strategy-specific user data for use in rebalanceFcn
[]
(default) | struct
Since R2023a
Strategy-specific user data for use in rebalanceFcn
,
specified as a struct.
Data Types: struct
EngineDataList
— Specify set of optional backtest state data a strategy needs in rebalanceFcn
[]
(default) | string
Since R2023a
Specify set of optional backtest state data a strategy needs in
rebalanceFcn
, specified as a scalar string or
string array.
Data Types: string
Examples
Create Backtesting Strategies
Define a backtest strategy by using a backtestStrategy
object. backtestStrategy
objects contain properties specific to a trading strategy, such as the rebalance frequency, transaction costs, and a rebalance function. The rebalance function implements the core logic of the strategy and is used by the backtesting engine during the backtest to allow the strategy to change its asset allocation and to make trades. In this example, to illustrate how to create and use backtest strategies in MATLAB®, you prepare two simple strategies for backtesting:
An equal weighted strategy
A strategy that attempts to "chase returns"
The strategy logic for these two strategies is defined in the rebalance functions.
Set Strategy Properties
A backtestStrategy
object has several properties that you set using parameters for the backtestStrategy
function.
Initial Weights
The InitialWeights property contains the asset allocation weights at the start of the backtest. The default value for InitialWeights
is empty ([]
), which indicates that the strategy begins the backtest uninvested, meaning that 100% of the capital is in cash earning the risk-free rate.
Set the InitialWeights
to a specific asset allocation. The size of the initial weights vector must match the number of assets in the backtest.
% Initialize the strategies with 30 weights, since the backtest % data comes from a year of the 30 DJIA stocks. numAssets = 30; % Give the initial weights for both strategies equal weighting. Weights % must sum to 1 to be fully invested. initialWeights = ones(1,numAssets); initialWeights = initialWeights / sum(initialWeights);
Transaction Costs
The TransactionCosts property allows you to set the fees that the strategy pays for trading assets. Transaction costs are paid as a percentage of the total change in position for each asset. Specify costs in decimal percentages. For example, if TransactionCosts
is set to 1% (0.01
) and the strategy buys $100 worth of a stock, then the transaction costs incurred are $1.
Transaction costs are set using a 1
-by-2
vector that sets separate fee rates for purchases and sales of assets. In this example, both strategies pay the same transaction costs — 25 basis points for asset purchases and 50 basis points for sales.
% Define the Transaction costs as [buyCosts sellCost] and specify the costs % as decimal percentages. tradingCosts = [0.0025 0.005];
You can also set the TransactionCosts property to a function handle if you need to implement arbitrarily complex transaction cost structures. For more information on creating transaction cost functions, see backtestStrategy
.
Rebalance Frequency
The RebalanceFrequency property determines how often the backtesting engine rebalances and reallocates the portfolio of a strategy using the rebalance function. Set the RebalanceFrequency
in terms of time steps in the backtest. For example, if the backtesting engine is testing a strategy with a set of daily price data, then set the rebalance function in days. Essentially, RebalanceFrequency
represents the number of rows of price data to process between each call to the strategy rebalance function.
% Both strategies rebalance every 4 weeks (20 days).
rebalFreq = 20;
Lookback Window
Each time the backtest engine calls a strategy rebalance function, a rolling window of asset price data (and possibly signal data) is passed to the rebalance function. The rebalance function can then make trading and allocation decisions based on that data. The LookbackWindow property sets the size of these rolling windows in terms of time steps. The window determines the amount of data from the asset price timetable that are passed to the rebalance function.
The LookbackWindow
property can be set in two ways. For a fixed-sized rolling window of data (for example, "50 days of price history"), the LookbackWindow
property is set to a single scalar value (N = 50
). The software then calls the rebalance function with a price timetable containing exactly N
rows of rolling price data.
Alternatively, you can define the LookbackWindow
property by using a 1
-by-2
vector [min max]
that specifies the minimum and maximum size of the rolling window. This way of defining the LookbackWindows allows for an expanding window of data. For example:
[10 Inf]
— At least 10 rows of data must be available before the current rebalancing period.[0 50]
— At most 50 rows of data prior to the current rebalancing period are passed to the rebalancing function.[0 Inf]
— Use all available data up to the current rebalancing period; there is no minimum and no maximum. This the default value.[20 20]
— Exactly 20 rows of data prior to the current rebalancing period are passed to the rebalancing function; this is equivalent to settingLookbackWindow
to the scalar value20
.
The software does not call the rebalance function if the data is insufficient to create a valid rolling window, regardless of the value of the RebalanceFrequency
property.
If the strategy does not require any price or signal data history, then you can indicate that the rebalance function requires no data by setting the LookbackWindow
property to 0
.
% The equal weight strategy does not require any price history data. ewLookback = 0; % The "chase returns" strategy bases its decisions on the trailing % 10-day asset returns. The lookback window is set to 11 since computing 10 days % of returns requires the close price from day 0. chaseLookback = 11;
Rebalance Function
The rebalance function (rebalanceFcn) is the user-authored function that contains the logic of the strategy. The backtesting engine calls the strategy rebalance function with a fixed set of parameters and expects it to return a vector of asset weights representing the new, desired portfolio allocation after a rebalance. For more information, see the rebalance functions.
Create Strategies
Using the prepared strategy properties, you can create the two strategy objects.
% Create the equal weighted strategy. The rebalance function @equalWeights % is defined in the Rebalance Functions section at the end of this example. equalWeightStrategy = backtestStrategy("EqualWeight",@equalWeight, ... 'RebalanceFrequency',rebalFreq, ... 'TransactionCosts',tradingCosts, ... 'LookbackWindow',ewLookback, ... 'InitialWeights',initialWeights)
equalWeightStrategy = backtestStrategy with properties: Name: "EqualWeight" RebalanceFcn: @equalWeight RebalanceFrequency: 20 TransactionCosts: [0.0025 0.0050] LookbackWindow: 0 InitialWeights: [0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333] ManagementFee: 0 ManagementFeeSchedule: 1y PerformanceFee: 0 PerformanceFeeSchedule: 1y PerformanceHurdle: 0 UserData: [0x0 struct] EngineDataList: [0x0 string]
% Create the "chase returns" strategy. The rebalance function % @chaseReturns is defined in the Rebalance Functions section at the end of this example. chaseReturnsStrategy = backtestStrategy("ChaseReturns",@chaseReturns, ... 'RebalanceFrequency',rebalFreq, ... 'TransactionCosts',tradingCosts, ... 'LookbackWindow',chaseLookback, ... 'InitialWeights',initialWeights)
chaseReturnsStrategy = backtestStrategy with properties: Name: "ChaseReturns" RebalanceFcn: @chaseReturns RebalanceFrequency: 20 TransactionCosts: [0.0025 0.0050] LookbackWindow: 11 InitialWeights: [0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333 0.0333] ManagementFee: 0 ManagementFeeSchedule: 1y PerformanceFee: 0 PerformanceFeeSchedule: 1y PerformanceHurdle: 0 UserData: [0x0 struct] EngineDataList: [0x0 string]
Set Up Backtesting Engine
To backtest the two strategies, use the backtestEngine
object. The backtesting engine sets parameters of the backtest that apply to all strategies, such as the risk-free rate and initial portfolio value. For more information, see backtestEngine
.
% Create an array of strategies for the backtestEngine. strategies = [equalWeightStrategy chaseReturnsStrategy]; % Create backtesting engine to test both strategies. backtester = backtestEngine(strategies);
Rebalance Functions
Strategy rebalance functions defined using the rebalanceFcn argument for backtestStrategy
must adhere to a fixed API that the backtest engine expects when interacting with each strategy. Rebalance functions must implement one of the following two syntaxes:
function new_weights = exampleRebalanceFcn(current_weights,assetPriceTimeTable) function new_weights = exampleRebalanceFcn(current_weights,assetPriceTimeTable,signalDataTimeTable)
All rebalance functions take as their first input argument the current allocation weights of the portfolio. current_weights
represents the asset allocation just before the rebalance occurs. During a rebalance, you can use current_weights
in a variety of ways. For example, you can use current_weights
to determine how far the portfolio allocation has drifted from the target allocation or to size trades during the rebalance to limit turnover.
The second and third arguments of the rebalance function syntax are the rolling windows of asset prices and optional signal data. The two tables contain the trailing N
rows of the asset and signal timetables that are passed to the runBacktest
function, where N
is set using the LookbackWindow
property of each strategy.
If optional signal data is provided to the runBacktest
function, then the backtest engine passes the rolling window of signal data to each strategy that supports it.
The equalWeight
strategy simply invests equally across all assets.
function new_weights = equalWeight(current_weights,assetPrices) %#ok<INUSD> % Invest equally across all assets. num_assets = numel(current_weights); new_weights = ones(1,num_assets) / num_assets; end
The chaseReturns
strategy invests only in the top X stocks based on their rolling returns in the lookback window. This naive strategy is used simply as an illustrative example.
function new_weights = chaseReturns(current_weights,assetPrices) % Set number of stocks to invest in. numStocks = 15; % Compute rolling returns from lookback window. rollingReturns = assetPrices{end,:} ./ assetPrices{1,:}; % Select the X best performing stocks over the lookback window [~,idx] = sort(rollingReturns,'descend'); bestStocksIndex = idx(1:numStocks); % Initialize new weights to all zeros. new_weights = zeros(size(current_weights)); % Invest equally across the top performing stocks. new_weights(bestStocksIndex) = 1; new_weights = new_weights / sum(new_weights); end
Use UserData
Property to Create Stop Loss Trading Strategy
Since R2023a
This example shows how to use the UserData property of backtestStrategy
to create a stop loss trading strategy. A stop loss strategy sets a threshold to define how much money that you are willing to lose before closing a position. For example, if you bought a stock at $100, you could set a stop loss at $95, and if the stock price falls below that point, then you sell your position. You can then set a new price that is your buy-in price to buy back into the stock.
Load Data
% Load equity adjusted price data and convert to timetable T = readtable('dowPortfolio.xlsx'); pricesTT = table2timetable(T(:,[1 3:end]),'RowTimes','Dates')
pricesTT=251×30 timetable
Dates AA AIG AXP BA C CAT DD DIS GE GM HD HON HPQ IBM INTC JNJ JPM KO MCD MMM MO MRK MSFT PFE PG T UTX VZ WMT XOM
___________ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____
03-Jan-2006 28.72 68.41 51.53 68.63 45.26 55.86 40.68 24.18 33.6 17.82 39.79 36.14 28.35 80.13 24.57 59.08 37.78 38.98 32.72 75.93 52.27 30.73 26.19 22.16 56.38 22.7 54.94 26.79 44.9 56.64
04-Jan-2006 28.89 68.51 51.03 69.34 44.42 57.29 40.46 23.77 33.56 18.3 39.05 35.99 29.18 80.03 24.9 59.99 37.56 38.91 33.01 75.54 52.65 31.08 26.32 22.88 56.48 22.87 54.61 27.58 44.99 56.74
05-Jan-2006 29.12 68.6 51.57 68.53 44.65 57.29 40.38 24.19 33.47 19.34 38.67 35.97 28.97 80.56 25.25 59.74 37.67 39.1 33.05 74.85 52.52 31.13 26.34 22.9 56.3 22.92 54.41 27.9 44.38 56.45
06-Jan-2006 29.02 68.89 51.75 67.57 44.65 58.43 40.55 24.52 33.7 19.61 38.96 36.53 29.8 82.96 25.28 60.01 37.94 39.47 33.25 75.47 52.95 31.08 26.26 23.16 56.24 23.21 54.58 28.01 44.56 57.57
09-Jan-2006 29.37 68.57 53.04 67.01 44.43 59.49 40.32 24.78 33.61 21.12 39.38 36.23 30.17 81.76 25.44 60.38 38.55 39.66 33.88 75.84 53.11 31.58 26.21 23.16 56.67 23.3 55.2 28.12 44.4 57.54
10-Jan-2006 28.44 69.18 52.88 67.33 44.57 59.25 40.2 25.09 33.43 20.79 40.33 36.17 30.33 82.1 25.1 60.49 38.61 39.7 33.91 75.37 53.04 31.27 26.35 22.77 56.45 23.16 55.24 28.24 44.54 57.99
11-Jan-2006 28.05 69.6 52.59 68.3 44.98 59.28 38.87 25.33 33.66 20.61 41.44 36.19 30.88 82.19 25.12 59.91 38.58 39.72 34.5 75.22 53.31 31.39 26.63 23.06 56.65 23.34 54.41 28.58 45.23 58.38
12-Jan-2006 27.68 69.04 52.6 67.9 45.02 60.13 38.02 25.41 33.25 19.76 41.05 35.77 30.57 81.61 24.96 59.63 37.87 39.5 33.96 74.57 53.23 31.41 26.48 22.9 56.02 23.24 53.9 28.69 44.43 57.77
13-Jan-2006 27.81 68.84 52.5 67.7 44.92 60.24 37.86 25.47 33.35 19.2 40.43 35.85 31.43 81.22 24.78 59.26 37.84 39.37 33.65 74.38 53.29 31.4 26.53 22.99 56.49 23.27 54.1 28.75 44.1 59.06
17-Jan-2006 27.97 67.84 52.03 66.93 44.47 60.85 37.75 25.15 33.2 18.68 40.11 35.56 31.2 81.05 24.52 58.74 37.64 39.11 33.77 73.99 52.85 31.16 26.34 22.63 56.25 23.13 54.41 28.12 43.66 59.61
18-Jan-2006 27.81 67.42 51.84 66.58 44.41 60.04 37.54 24.97 33.08 18.98 40.42 35.71 31.21 81.83 21.72 59.61 37.24 38.91 34.16 74.07 52.89 30.99 26.18 22.36 56.54 23.11 54.08 27.83 43.88 58.78
19-Jan-2006 28.33 66.92 51.66 66.42 44.02 60.66 37.69 26 32.95 19.1 39.83 35.88 31.77 81.14 21.53 59.6 37.03 39.01 34.36 73.81 52.68 31.26 26.36 23.27 56.39 23.18 54.41 28.07 44.49 59.57
20-Jan-2006 27.67 65.55 50.49 64.79 41.95 58.98 37.34 25.49 31.7 18.9 38.76 34.57 31.28 79.45 20.91 58.28 36.07 38.21 35.01 72.21 52.18 31.2 25.77 23.03 55.74 23.01 53.46 27.64 43.71 58.63
23-Jan-2006 28.03 65.46 50.53 65.3 42.24 59.23 37.37 25.29 31.63 20.6 38.29 34.78 30.88 79.5 20.52 58.66 36.28 38.6 34.86 72.65 52.09 30.8 25.71 23.19 55.55 22.77 52.94 27.69 43.95 59.28
24-Jan-2006 28.16 65.39 51.89 65.92 42.25 59.53 37.09 25.76 31.32 21.73 39.03 35.25 30.91 78.95 20.45 56.9 36.13 38.98 35 71.21 51.74 30.76 25.64 22.91 55.77 22.96 54.86 27.6 44.41 59.05
25-Jan-2006 28.57 64.67 51.97 65.19 42.45 60.23 37.05 25.21 31.13 22.48 38.57 34.79 31.64 79.01 20.38 56.08 36.48 39.21 34.32 70.06 51.49 31.14 25.76 23.14 56.35 23.47 55.4 28.03 44.65 58.32
⋮
Create Backtest Stop Loss Strategy
The stop loss strategy will either be 100% invested in the stock or 100% in cash. You can use two fields in the UserData
struct for backtestStrategy
to set the stop loss limits:
StopLossPercent
— How much you are willing to lose before selling, as a decimal percentBuyInPercent
— How much further a stock must fall after you sell it before we buy back in, as a percent
In addition, the UserData
struct has the following fields:
Asset
— Ticker symbol AIG for the asset you want to tradeStopLossPrice
— Threshold to sell the asset. If the price falls below this stop loss price, sell the asset and go to cash.BuyInPrice
— Target price to buy the asset. If the asset is at this price or lower, buy the asset.
stopLossStrat = backtestStrategy("StopLoss",@stopLossSingleAsset,RebalanceFrequency=1); % Setup the initial UserData stopLossStrat.UserData = struct(StopLossPercent=0.05,BuyInPercent=0.02,Asset="AIG",StopLossPrice=nan,BuyInPrice=Inf);
The backtest strategy starts 100% in cash. Normally the BuyInPrice
is set by the strategy after you sell a stock, but in this case, you can initialize the BuyInPrice
to be Inf
. This triggers a stock buy on the first rebalance (since any price is less than Inf
). You don't need to set the StopLossPrice
at this point (it is set to nan
) because the strategy sets the stop loss price once a stock purchase is made.
The strategy specifies that you are only willing to lose 5% of your capital before the stop loss is triggered and you sell. Then you will buy back into the stock if it falls by an additional 2%. You can set the rebalance frequency to 1
because this type of strategy has to "rebalance" every day in order to check the price to determine if any action is needed.
Run Backtest
Create a backtestEngine
object and run the backtest using runBacktest
.
backtester = backtestEngine(stopLossStrat); backtester = runBacktest(backtester,pricesTT);
Use equityCurve
to generate a plot the stop loss trading strategy.
equityCurve(backtester)
The flat parts of the equity curve are where the stop loss strategy has sold AIG and then later buys back into AIG.
Local Functions
function [new_weights,user_data] = incrementCounter(current_weights,prices,user_data) % Don't change the weights new_weights = current_weights; % Increment the user data counter user_data.Counter = user_data.Counter + 1; end
function [new_weights,user_data] = stopLossSingleAsset(current_weights,prices,user_data) % Start with the existing weights new_weights = current_weights; % Find column of the asset asset = char(user_data.Asset); assetIdx = find(strncmpi(asset,prices.Properties.VariableNames,numel(asset))); assetPrice = prices{end,assetIdx}; % Determine if currently invested or in cash inCash = sum(current_weights) < 1e-5; if inCash % You are in cash, see if you should buy back in if assetPrice <= user_data.BuyInPrice % Buy back in new_weights(assetIdx) = 1; % Set new stop loss price user_data.StopLossPrice = assetPrice * (1 - user_data.StopLossPercent); end else % You are in the stock, see if you should sell if assetPrice <= user_data.StopLossPrice % Sell the stock new_weights(assetIdx) = 0; % Set new buy-in price user_data.BuyInPrice = assetPrice * (1 - user_data.BuyInPercent); end end end
Use EngineDataList
Property to Enforce Trading Strategy of Whole Shares
Since R2023a
This example shows how to use the EngineDataList property of backtestStrategy
to enforce a trading strategy with a whole shares constraint.
Load Data
% Load equity adjusted price data and convert the data to timetable T = readtable('dowPortfolio.xlsx'); pricesTT = table2timetable(T(:,[1 3:end]),'RowTimes','Dates')
pricesTT=251×30 timetable
Dates AA AIG AXP BA C CAT DD DIS GE GM HD HON HPQ IBM INTC JNJ JPM KO MCD MMM MO MRK MSFT PFE PG T UTX VZ WMT XOM
___________ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____
03-Jan-2006 28.72 68.41 51.53 68.63 45.26 55.86 40.68 24.18 33.6 17.82 39.79 36.14 28.35 80.13 24.57 59.08 37.78 38.98 32.72 75.93 52.27 30.73 26.19 22.16 56.38 22.7 54.94 26.79 44.9 56.64
04-Jan-2006 28.89 68.51 51.03 69.34 44.42 57.29 40.46 23.77 33.56 18.3 39.05 35.99 29.18 80.03 24.9 59.99 37.56 38.91 33.01 75.54 52.65 31.08 26.32 22.88 56.48 22.87 54.61 27.58 44.99 56.74
05-Jan-2006 29.12 68.6 51.57 68.53 44.65 57.29 40.38 24.19 33.47 19.34 38.67 35.97 28.97 80.56 25.25 59.74 37.67 39.1 33.05 74.85 52.52 31.13 26.34 22.9 56.3 22.92 54.41 27.9 44.38 56.45
06-Jan-2006 29.02 68.89 51.75 67.57 44.65 58.43 40.55 24.52 33.7 19.61 38.96 36.53 29.8 82.96 25.28 60.01 37.94 39.47 33.25 75.47 52.95 31.08 26.26 23.16 56.24 23.21 54.58 28.01 44.56 57.57
09-Jan-2006 29.37 68.57 53.04 67.01 44.43 59.49 40.32 24.78 33.61 21.12 39.38 36.23 30.17 81.76 25.44 60.38 38.55 39.66 33.88 75.84 53.11 31.58 26.21 23.16 56.67 23.3 55.2 28.12 44.4 57.54
10-Jan-2006 28.44 69.18 52.88 67.33 44.57 59.25 40.2 25.09 33.43 20.79 40.33 36.17 30.33 82.1 25.1 60.49 38.61 39.7 33.91 75.37 53.04 31.27 26.35 22.77 56.45 23.16 55.24 28.24 44.54 57.99
11-Jan-2006 28.05 69.6 52.59 68.3 44.98 59.28 38.87 25.33 33.66 20.61 41.44 36.19 30.88 82.19 25.12 59.91 38.58 39.72 34.5 75.22 53.31 31.39 26.63 23.06 56.65 23.34 54.41 28.58 45.23 58.38
12-Jan-2006 27.68 69.04 52.6 67.9 45.02 60.13 38.02 25.41 33.25 19.76 41.05 35.77 30.57 81.61 24.96 59.63 37.87 39.5 33.96 74.57 53.23 31.41 26.48 22.9 56.02 23.24 53.9 28.69 44.43 57.77
13-Jan-2006 27.81 68.84 52.5 67.7 44.92 60.24 37.86 25.47 33.35 19.2 40.43 35.85 31.43 81.22 24.78 59.26 37.84 39.37 33.65 74.38 53.29 31.4 26.53 22.99 56.49 23.27 54.1 28.75 44.1 59.06
17-Jan-2006 27.97 67.84 52.03 66.93 44.47 60.85 37.75 25.15 33.2 18.68 40.11 35.56 31.2 81.05 24.52 58.74 37.64 39.11 33.77 73.99 52.85 31.16 26.34 22.63 56.25 23.13 54.41 28.12 43.66 59.61
18-Jan-2006 27.81 67.42 51.84 66.58 44.41 60.04 37.54 24.97 33.08 18.98 40.42 35.71 31.21 81.83 21.72 59.61 37.24 38.91 34.16 74.07 52.89 30.99 26.18 22.36 56.54 23.11 54.08 27.83 43.88 58.78
19-Jan-2006 28.33 66.92 51.66 66.42 44.02 60.66 37.69 26 32.95 19.1 39.83 35.88 31.77 81.14 21.53 59.6 37.03 39.01 34.36 73.81 52.68 31.26 26.36 23.27 56.39 23.18 54.41 28.07 44.49 59.57
20-Jan-2006 27.67 65.55 50.49 64.79 41.95 58.98 37.34 25.49 31.7 18.9 38.76 34.57 31.28 79.45 20.91 58.28 36.07 38.21 35.01 72.21 52.18 31.2 25.77 23.03 55.74 23.01 53.46 27.64 43.71 58.63
23-Jan-2006 28.03 65.46 50.53 65.3 42.24 59.23 37.37 25.29 31.63 20.6 38.29 34.78 30.88 79.5 20.52 58.66 36.28 38.6 34.86 72.65 52.09 30.8 25.71 23.19 55.55 22.77 52.94 27.69 43.95 59.28
24-Jan-2006 28.16 65.39 51.89 65.92 42.25 59.53 37.09 25.76 31.32 21.73 39.03 35.25 30.91 78.95 20.45 56.9 36.13 38.98 35 71.21 51.74 30.76 25.64 22.91 55.77 22.96 54.86 27.6 44.41 59.05
25-Jan-2006 28.57 64.67 51.97 65.19 42.45 60.23 37.05 25.21 31.13 22.48 38.57 34.79 31.64 79.01 20.38 56.08 36.48 39.21 34.32 70.06 51.49 31.14 25.76 23.14 56.35 23.47 55.4 28.03 44.65 58.32
⋮
Create Backtest Strategies
This example runs a backtest on two equal-weighted strategies and then compares the results. The first backtest strategy uses an exact equal-weight rebalance function that allows fractional shares. The second backtest strategy uses a EngineDataList
property for PortfolioValue
to enforce a whole shares constraint.
Use backtestStrategy
to create the first strategy for partial shares.
% Partial shares and equal weight for assets partialRebal = @(~,prices) ones(1,width(prices)) / width(prices); ewPartial = backtestStrategy("PartialShares",partialRebal,RebalanceFrequency=1);
Use backtestStrategy
to create the second strategy for whole shares.
% Whole shares and equal weight for assets ewWhole = backtestStrategy("WholeShares",@equalWeightShares,RebalanceFrequency=1,EngineDataList="PortfolioValue");
Run Backtest
Aggregate the two strategy objects, create a backtestEngine
object, and then run the backtest using runBacktest
.
strats = [ewPartial, ewWhole]; backtester = backtestEngine(strats,RiskFreeRate=0.02); backtester = runBacktest(backtester,pricesTT);
Use equityCurve
to generate a plot for both of the trading strategies.
equityCurve(backtester);
You can verify that the second strategy traded in whole shares.
% Plot the number of shares of each asset plot(backtester.Positions.PartialShares{:,2:end} ./ pricesTT{:,:}); title('Number of Shares per Asset (Partial Shares)')
plot(backtester.Positions.WholeShares{:,2:end} ./ pricesTT{:,:})
title('Number of Shares per Asset (Whole Shares)')
Local Functions
function new_weights = equalWeightShares(engine_data,prices) numAssets = width(prices); % Dollars per asset for equal weight with partial shares dollars_per_asset = engine_data.PortfolioValue / numAssets; % Compute shares (partial shares first, then round down) asset_shares = floor(dollars_per_asset ./ prices{end,:}); % Convert number of shares into dollars asset_dollars = asset_shares .* prices{end,:}; % Convert dollars into weights new_weights = asset_dollars / engine_data.PortfolioValue; end
Set the Size of Rolling Windows for Backtest Strategies
Since R2024a
This example shows how to use the LookbackWindow name-value argument to set the rolling windows of the backtesting strategies. Each time the backtest engine calls a strategy rebalance function, a rolling window of asset price data (and possibly signal data) is passed to the rebalance function (rebalanceFcn). The rebalance function can then make trading and allocation decisions based on that data. The LookbackWindow
property sets the size of these rolling windows in terms of time steps. The window determines the amount of data from the asset price timetable that is passed to the rebalance function.
The LookbackWindow
property can be set in two ways:
Expanding rolling window — For an expanding rolling window of data, set the
LookbackWindow
property to a1
-by-2
vector [min max
]. The software then calls the rebalance function with a price timetable containing at leastmin
rows and at mostmax
rows of rolling price data.Fixed-size rolling window — For a fixed-sized rolling window of data, set the
LookbackWindow
property to a single scalar value (N). The software then calls the rebalance function with a price timetable containing exactly N rows of rolling price data.
Load Data
Load 10 days of adjusted price data for a selected set of stocks.
% Read a table of daily adjusted close prices for 2006 DJIA stocks. T = readtable('dowPortfolio.xlsx'); % For readability, use only a few stocks. assetSymbols = ["AA","GM","MMM","MSFT","PG","T","XOM"]; % Prune the table to hold only 10 days of data and selected stocks. timeColumn = "Dates"; T = T(1:10,[timeColumn assetSymbols]); % Convert the table to a timetable. pricesTT = table2timetable(T,'RowTimes','Dates');
Set Expanding Rolling Window
Set the backtesting strategy to rebalance every two days.
% Define the rebalance frequency.
rebalFreq = 2;
Set the lookback window to a minimum of four days and a maximum of eight days.
% Define the lookback window. lookback = [4 8]; % Set backtest strategy. strat = backtestStrategy('MaxSharpeRatio',@maxSharpeRatioFcn, ... RebalanceFrequency=rebalFreq,LookbackWindow=lookback); % Define a backtest engine. backtester = backtestEngine(strat); % Run the backtest. runBacktest(backtester,pricesTT,Start=1);
################################################################################# Rebalancing period: 09-Jan-2006 Number of time periods in data: 5 ################################################################################# Dates AA GM MMM MSFT PG T XOM ___________ _____ _____ _____ _____ _____ _____ _____ 03-Jan-2006 28.72 17.82 75.93 26.19 56.38 22.7 56.64 04-Jan-2006 28.89 18.3 75.54 26.32 56.48 22.87 56.74 05-Jan-2006 29.12 19.34 74.85 26.34 56.3 22.92 56.45 06-Jan-2006 29.02 19.61 75.47 26.26 56.24 23.21 57.57 09-Jan-2006 29.37 21.12 75.84 26.21 56.67 23.3 57.54 ################################################################################# ################################################################################# Rebalancing period: 11-Jan-2006 Number of time periods in data: 7 ################################################################################# Dates AA GM MMM MSFT PG T XOM ___________ _____ _____ _____ _____ _____ _____ _____ 03-Jan-2006 28.72 17.82 75.93 26.19 56.38 22.7 56.64 04-Jan-2006 28.89 18.3 75.54 26.32 56.48 22.87 56.74 05-Jan-2006 29.12 19.34 74.85 26.34 56.3 22.92 56.45 06-Jan-2006 29.02 19.61 75.47 26.26 56.24 23.21 57.57 09-Jan-2006 29.37 21.12 75.84 26.21 56.67 23.3 57.54 10-Jan-2006 28.44 20.79 75.37 26.35 56.45 23.16 57.99 11-Jan-2006 28.05 20.61 75.22 26.63 56.65 23.34 58.38 ################################################################################# ################################################################################# Rebalancing period: 13-Jan-2006 Number of time periods in data: 8 ################################################################################# Dates AA GM MMM MSFT PG T XOM ___________ _____ _____ _____ _____ _____ _____ _____ 04-Jan-2006 28.89 18.3 75.54 26.32 56.48 22.87 56.74 05-Jan-2006 29.12 19.34 74.85 26.34 56.3 22.92 56.45 06-Jan-2006 29.02 19.61 75.47 26.26 56.24 23.21 57.57 09-Jan-2006 29.37 21.12 75.84 26.21 56.67 23.3 57.54 10-Jan-2006 28.44 20.79 75.37 26.35 56.45 23.16 57.99 11-Jan-2006 28.05 20.61 75.22 26.63 56.65 23.34 58.38 12-Jan-2006 27.68 19.76 74.57 26.48 56.02 23.24 57.77 13-Jan-2006 27.81 19.2 74.38 26.53 56.49 23.27 59.06 #################################################################################
Start=1
in runBacktest
sets the starting time period to "03-Jan-2006"
, the first date available in pricesTT
. Given that RebalanceFrequency
is set to 2
, you might expect the next rebablancing period to be "05-Jan-2006"
. However, the fist time the strategy rebalances is on "09-Jan-2006"
. This happens because the minimum value of the lookback window is set to 4
and on "05-Jan-2006"
there are only three time periods available, so the backtest engine does not call the rebalancing function on this time period. On the next rebalancing period, "11-Jan-2006"
, the data includes seven observations which correspond to all the rows up to "11-Jan-2006"
. On the last rebalancing period, "13-Jan-2006"
, the first input of pricesTT
is not included in the data. This happens because the maximum value of the lookback window is set to 8
, which means that only the last eight time periods are passed to the rebalancing function. Also, note that the rolling window increases in size from rebalancing period to rebalancing period until it reaches the maximum size.
Set Fixed-Sized Rolling Window
Set the backtesting strategies to rebalance every 2 days.
% Define the rebalance frequency.
rebalFreq = 2;
Set the lookback window to a fixed-size of 5 days.
% Define the lookback window. lookback = 5; % Set backtest strategy. strat = backtestStrategy('MaxSharpeRatio',@maxSharpeRatioFcn, ... RebalanceFrequency=rebalFreq,LookbackWindow=lookback); % Define a backtest engine. backtester = backtestEngine(strat); % Run the backtest. runBacktest(backtester,pricesTT,Start=1);
################################################################################# Rebalancing period: 09-Jan-2006 Number of time periods in data: 5 ################################################################################# Dates AA GM MMM MSFT PG T XOM ___________ _____ _____ _____ _____ _____ _____ _____ 03-Jan-2006 28.72 17.82 75.93 26.19 56.38 22.7 56.64 04-Jan-2006 28.89 18.3 75.54 26.32 56.48 22.87 56.74 05-Jan-2006 29.12 19.34 74.85 26.34 56.3 22.92 56.45 06-Jan-2006 29.02 19.61 75.47 26.26 56.24 23.21 57.57 09-Jan-2006 29.37 21.12 75.84 26.21 56.67 23.3 57.54 ################################################################################# ################################################################################# Rebalancing period: 11-Jan-2006 Number of time periods in data: 5 ################################################################################# Dates AA GM MMM MSFT PG T XOM ___________ _____ _____ _____ _____ _____ _____ _____ 05-Jan-2006 29.12 19.34 74.85 26.34 56.3 22.92 56.45 06-Jan-2006 29.02 19.61 75.47 26.26 56.24 23.21 57.57 09-Jan-2006 29.37 21.12 75.84 26.21 56.67 23.3 57.54 10-Jan-2006 28.44 20.79 75.37 26.35 56.45 23.16 57.99 11-Jan-2006 28.05 20.61 75.22 26.63 56.65 23.34 58.38 ################################################################################# ################################################################################# Rebalancing period: 13-Jan-2006 Number of time periods in data: 5 ################################################################################# Dates AA GM MMM MSFT PG T XOM ___________ _____ _____ _____ _____ _____ _____ _____ 09-Jan-2006 29.37 21.12 75.84 26.21 56.67 23.3 57.54 10-Jan-2006 28.44 20.79 75.37 26.35 56.45 23.16 57.99 11-Jan-2006 28.05 20.61 75.22 26.63 56.65 23.34 58.38 12-Jan-2006 27.68 19.76 74.57 26.48 56.02 23.24 57.77 13-Jan-2006 27.81 19.2 74.38 26.53 56.49 23.27 59.06 #################################################################################
Start=1
in runBacktest
sets the starting time period to "03-Jan-2006"
, the first date available in pricesTT
. Given that RebalanceFrequency
is set to 2
, you might expect the next rebalancing period to be "05-Jan-2006"
. However, the fist time the strategy rebalances is on "09-Jan-2006"
. This happens because LookbackWindow
is set to 5
, which automatically sets the minimum lookback window to 5
and at "05-Jan-2006"
there are only three time periods available. On the second rebalancing period, "11-Jan-2006"
, the first two inputs of pricesTT
are not included in the data passed to the rebalancing function. This happens because the maximum value of the lookback window is implicitly set to 5
, which means that only the last five time periods are passed to the rebalancing function. The same behavior happens in the last rebalancing period. Also, note that the size of the rolling window remains constant throughout all rebalancing periods.
Local Functions
function new_weights = maxSharpeRatioFcn(~, pricesTT) % Maximum Sharpe Ratio allocation % Display prices timetable and rebalancing period. fprintf('#################################################################################\n') fprintf('Rebalancing period: %s\n',string(pricesTT.Dates(end))) fprintf('Number of time periods in data: %i\n',size(pricesTT,1)) fprintf('#################################################################################\n') disp(pricesTT) fprintf('#################################################################################\n\n') % Compute asset returns. assetReturns = tick2ret(pricesTT); % Define porfolio problem. p = Portfolio; p = estimateAssetMoments(p, assetReturns{:,:}); p = setDefaultConstraints(p); % Estimate the Max Sharpe Ratio. new_weights = estimateMaxSharpeRatio(p); end
More About
Defining Schedules for Backtest Strategies
A backtesting schedule is a set of dates defining when specific events will occur.
The backtestStrategy
object uses schedules in several of ways,
including setting the rebalance frequency (RebalanceFrequency
),
the performance fee (PerformanceFeeSchedule
), and the
management fee (ManagementFeeSchedule
) schedules. Schedules are
specified with a syntax that supports a variety of data types, which include numeric
scalars, a vector of datetimes, and duration
or calendarDuration
objects. The
resulting schedules for the different data types are:
Numeric — If a schedule is defined using a numeric scalar, it refers to the number of rows (of the
assetPrices
timetable) between each event in the schedule, starting from the backtest start date. For example, if a schedule is set to 10, then the schedule event (a rebalance date or fee payment date) occurs on every 10th row of the prices timetable while the backtest runs.duration
orcalendarDuration
— If specified as aduration
orcalendarDuration
object, the schedule specifies a duration of time between each event (rebalance date or fee payment date). The schedule is calculated starting at the backtest start date and then schedule dates are added after each step of the specified duration. For an example, see Backtest Investment Strategies Using datetime and calendarDuration.Vector of datetimes — If the numeric or duration syntaxes are not appropriate, then you can explicitly specify schedules using a vector of datetimes.
Note
For both the duration and datetime syntaxes, if a resulting schedule date
is not found in the backtest data set (the assetPrices
timetable), the backtestEngine
either adjusts the date to the nearest valid date or else issues an error.
This behavior is controlled by the DateAdjustment
property of the backtestEngine
object. By default, dates are adjusted to the nearest previous date. For
example, if a schedule date falls on a Saturday, and is not found in the
backtest data set, then the date is adjusted to the previous Friday.
Alternatively, the backtestEngine
DateAdjustment
property allows you to adjust dates to the
next valid date, or to issue an error if a date is not found by requiring
exact dates.
Management Fees
The management fee is the annualized rate charged on the assets under management of the strategy to pay for the fund's management costs.
The fee is charged on each date specified by the
ManagementFeeSchedule
name-value argument. For more
information, see Defining Schedules for Backtest Strategies.
The management fee is forward looking, meaning that a fee paid on a given date
covers the management of the strategy from that date to the next management fee
date. For some syntaxes of theManagementFeeSchedule
, the
resulting schedule does not begin and end precisely on the backtest start and end
dates. In these cases, the backtest engine adds default dates to the start and end
of the schedule to ensure that the entire backtest is covered by management fees.
For example:
If
ManagementFeeSchedule
is specified as a vector of datetimes, and the backtest start date is not included in the vector, then the start date is added to the fee schedule.If
ManagementFeeSchedule
is specified as a numeric scalar or a vector of datetimes, and the final specified fee date occurs before the backtest end date, then the backtest end date is added to the fee schedule.If
ManagementFeeSchedule
is specified as aduration
orcalendarDuration
object, and the backtest end date is not included in the generated schedule, then a final fee date is added to the end by adding the duration object to the (previous) final fee date. This adjustment can produce a final fee date that is beyond the end of the backtest, but it results in more realistic fees paid at the final payment date.
For more information on where the resulting management fees are reported, see the
backtestEngine
object
read-only property Fees.
Performance Fees
A performance fee, or incentive fee, is a periodic payment that is paid by fund investors based on the fund's performance.
The performance fee can have a variety of structures, but it often is calculated as a percentage of a fund's increase in NAV over some tracked high-water mark. This calculation ensures the fee is paid only when the investment manager produces positive results. Performance fees in hedge funds are sometimes set at 20%, meaning if the fund NAV goes from $1M to $1.2M, a fee of $40k would be charged (20% of the $200k growth).
During the backtest, the backtest engine tracks a high-water mark. The high-water mark starts at the portfolio initial value. At each performance fee date, if the portfolio value is greater than the previous high-water mark, then the high-water mark is updated to the new portfolio value and the performance fee is paid on the growth of the portfolio. The portfolio growth is the difference between the new high-water mark and the previous one. If, on a performance fee date, the portfolio value is less than the previous high-water mark, then no fee is paid and the high-water mark is not updated.
The high-water mark is typically updated at each performance fee date and does not track maximum portfolio values between dates, as illustrated:
For more information on where the resulting performance fees are reported, see the
backtestEngine
object
read-only property Fees.
Performance Hurdle
A hurdle is an optional feature of a performance fee.
A hurdle sets a minimum performance threshold that a fund must beat before the performance fee is charged. Hurdles can be fixed rates of return, a hurdle rate, such as 5%. For example, if the fund NAV goes from $1M to $1.2M, the performance fee is charged only on the performance beyond 5%. A 5% hurdle rate on $1M sets the target hurdle value at $1.05M. The $1.2M portfolio is $150k over the hurdle value. The performance fee is charged only on that $150k, so 20% of $150k is a $30k performance fee.
The following figure illustrates a 5% hurdle rate.
You can also specify a performance hurdle as a benchmark asset or index. In this case, the performance fee is paid only on the fund performance in excess of the hurdle instrument. For example, assume that the fund NAV goes from $1M to $1.2M and you have specified the S&P500 as your hurdle. If the S&P500 index returns 25% over the same period, then no performance fee is paid because the fund did not produce returns in excess of the hurdle instrument. If the fund value is greater than the hurdle, but still less than the previous high-water mark, again, no performance fee is paid.
At each performance fee date, the fund charges the performance fee on the difference between the current fund value and the maximum of the high-water mark and the hurdle value. The high-water mark is updated only if a performance fee is paid.
For more information on where the resulting performance fees are reported, see the
backtestEngine
object
read-only property Fees.
Version History
Introduced in R2020bR2023b: Support for per-asset transaction cost
When using a function handle with the TransactionCosts
name-value argument, you can define a detailedCosts
output
argument to generate per-asset transaction costs when the backtest runs.
The EngineDataList
name-value argument supports a string
"TransactionCostHistory"
for reporting per-asset transaction
costs charged for each day in the backtest when you use runBacktest
.
R2023b: EngineDataList
supports information for CashAssets
and DebtAssets
The EngineDataList
name-value argument supports information
for CashAssets
and DebtAssets
. The
CashAssets
and DebtAssets
are defined as
name-value arguments when using runBacktest
.
R2023a: Support for data sharing in user-defined function handles
The backtestStrategy
name-value argument for
UserData
supports strategy-specific user data for use in
the user-defined function handles for the rebalance function. Also the
backtestStrategy
name-value argument for
EngineDataList
enables you to specify the set of optional
backtest state data that a strategy needs in the user-defined function
handles.
R2022b: Management and performance fees
The backtestStrategy
object supports name-value arguments for
ManagementFee
, ManagementFeeSchedule
,
PerformanceFee
, PerformanceHurdle
, and
PerformanceFeeSchedule
.
R2022a: Include NaN values in asset price data
The backtestStrategy
object supports NaN
s in
the assetPrices
timetable and NaN
s and
<missing>
in the signalData
timetable.
See Also
runBacktest
| summary
| backtestEngine
| equityCurve
| timetable
| duration
| calendarDuration
Topics
- Backtest Investment Strategies Using Financial Toolbox
- Backtest Investment Strategies with Trading Signals
- Backtest Investment Strategies Using datetime and calendarDuration
- Backtest Using Risk-Based Equity Indexation
- Backtest with Brinson Attribution to Evaluate Portfolio Performance
- runBacktest Processing Steps
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 MathWorks 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)
Asia Pacific
- Australia (English)
- India (English)
- New Zealand (English)
- 中国
- 日本Japanese (日本語)
- 한국Korean (한국어)