backtestStrategy
Create backtestStrategy object to define portfolio allocation
strategy
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
backtestStrategyobject to specify how the strategy rebalances a portfolio of assets.Use
backtestEngineto create abacktestEngineobject that specifies the parameters of the backtest.Use
runBacktestto run the backtest against historical asset price data and, optionally, trading signal data.Use
equityCurveto plot the equity curves of each strategy.Use
summaryto 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
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
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
UserDataandEngineDataListare EmptyIf the
UserDataandEngineDataListname-value arguments are empty, then therebalanceFcnmust 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_weightssum to1, then the portfolio is fully invested.If the
new_weightssum to less than1, then the portfolio has the remainder in cash, earning theRiskFreeRatespecified in thebacktestEngineobject.If the
new_weightssum to more than1, then there is a negative cash position (margin) and the cash borrowed accrues interest at the cash borrowing rate specified in theCashBorrowRateproperty of thebacktestEngineobject.
If
UserDatais Empty andEngineDataListis SpecifiedIf the
UserDataname-value argument is empty and theEngineDataListname-value argument is specified, then therebalanceFcnmust have one of the following signatures:new_weights = rebalanceFcn(engineData,assetPrices)new_weights = rebalanceFcn(engineData,assetPrices,signalData)
If
UserDatais Specified andEngineDataListis EmptyIf you use the name-value argument for
UserDatato specify a strategy-specificuserDatastruct but do not specify the name-value argument forEngineDataList, the required syntax forrebalanceFcnmust have one of the following signatures:[new_weights,userData] = rebalanceFcn(weights,assetPrices,userData)[new_weights,userData] = rebalanceFcn(weights,assetPrices,signalData,userData)
If
UserDatais specified, then in addition to an output fornew_weights, there is also an output foruserDatareturned as a struct.If Both
UserDataandEngineDataListare SpecifiedIf you use the name-value argument for
UserDatato specify a strategy-specificuserDatastruct and you use the name-value argumentEngineDataListto specify a backtest engine state, the required syntax forrebalanceFcnmust have one of the following signatures:[new_weights,userData] = rebalanceFcn(engineData,assetPrices,userData)[new_weights,userData] = rebalanceFcn(engineData,assetPrices,signalData,userData)
If
UserDatais specified, then in addition to an output fornew_weights, there is also an output foruserDatareturned 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— Atimetablecontaining a rolling window of adjusted asset prices.signalData— (Optional) Atimetablecontaining a rolling window of signal data.If you provide signal data to the
backtestEngineobject, then the engine object passes it to the strategy rebalance function using the three input argument syntax. If do not provide signal data thebacktestEngineobject, 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
UserDataproperty is set, theuserDatastruct is passed intorebalanceFcn.engineData— (Optional) A struct containing backtest state data.If the
EngineDataListproperty is set, theengineDatastruct is passed intorebalanceFcn.
For more information on developing a rebalanceFcn
function handle, see Backtest Investment Strategies Using Financial Toolbox.
Data Types: function_handle
Name-Value Arguments
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)
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
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 setTransactionCoststo0.001, then each transaction (buys and sells) would pay 0.1% in transaction fees.[buyRate, sellRate]— A1-by-2vector 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, thebacktestEngineobject calls theTransactionCostsfunction to compute the fees for each rebalance. The user-definedcomputeTransactionCostsFcnfunction handle has different signatures depending on whether the optional name-value argumentsUserDataandEngineDataListare specified and whether thesellCosts,buyCosts, ordetailedCostsoutputs are used.If
UserDatais Empty andEngineDataListis Empty UsingbuyCostsandsellCostsOutputsWhen the optional name-value arguments
UserDataandEngineDataListare not specified and the sellCosts and buyCosts outputs are used, thecomputeTransactionCostsFcnfunction handle has the following signature:[buyCosts,sellCosts] = computeTransactionCostsFcn(deltaPositions)
The user-defined
computeTransactionCostsFcnfunction 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 thedeltaPositionsvector indicate purchases while negative entries represent sales. The user-defined function handle must return two output argumentsbuyCostsandsellCosts, which contain the total costs (in currency) for the entire rebalance for each type of transaction.If
UserDatais Empty andEngineDataListis Empty anddetailedCostsOutput is specifiedWhen the optional name-value arguments
UserDataandEngineDataListare not specified anddetailedCostsoutput is specified, thecomputeTransactionCostsFcnfunction handle has the following signature:detailedCosts = computeTransactionCostsFcn(deltaPositions)
The user-defined
computeTransactionCostsFcnfunction 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 thedeltaPositionsvector indicate purchases while negative entries represent sales. The user-defined function handle returns one output argument fordetailedCostsfor per-asset transaction costs.If
UserDatais Empty andEngineDataListis Specified UsingbuyCostsandsellCostsOutputsWhen the optional name-value argument
UserDatais not specified, but the optional name-value argumentEngineDataListis specified and thebuyCostsandsellCostsoutput are used, thecomputeTransactionCostsFcnfunction handle has the following signatures:[buyCosts,sellCosts] = computeTransactionCostsFcn(deltaPositions,engineData)
If
UserDatais Empty andEngineDataListis Specified anddetailedCostsOutput is SpecifiedWhen the optional name-value argument
UserDatais not specified, but the optional name-value argumentEngineDataListis specified anddetailedCostsoutput is specified, thecomputeTransactionCostsFcnfunction handle has the following signatures:detailedCosts = computeTransactionCostsFcn(deltaPositions,engineData)
If
UserDatais Specified andEngineDataListis Empty UsingbuyCostsandsellCostsOutputsWhen the optional name-value argument
UserDatais specified, but the optional name-value argumentEngineDataListis not specified andbuyCostsandsellCostsoutputs are used, thecomputeTransactionCostsFcnfunction handle has the following signatures:[buyCosts,sellCosts,userData] = computeTransactionCostsFcn(deltaPositions,userData)
If
UserDatais Specified andEngineDataListis Empty anddetailedCostsOutput is specifiedWhen the optional name-value argument
UserDatais specified, but the optional name-value argumentEngineDataListis not specified anddetailedCostsoutput is specified, thecomputeTransactionCostsFcnfunction handle has the following signatures:[detailedCosts,userData] = computeTransactionCostsFcn(deltaPositions,userData)
If Both
UserDataandEngineDataListare Specified UsingbuyCostsandsellCostsOutputsIf you use the optional name-value argument for
UserDatato specify a strategy-specificuserDatastruct and the optional name-value argumentEngineDataListto specify required backtest engine state data andbuyCostsandsellCostsarguments are used, the required syntax for thecomputeTransactionCostsFcnfunction handle must have the following signature:If you specify the optional name-value argument[buyCosts,sellCosts,userData] = computeTransactionCostsFcn(deltaPositions,engineData,userData)
UserData, then the strategyuserDatastruct is passed to thecomputeTransactionCostsFcncost function as the final input argument. ThecomputeTransactionCostsFcnfunction handle can read or write any information to theuserDatastruct. The updateduserDatastruct is returned as the third output argument.If Both
UserDataandEngineDataListare Specified anddetailedCostsOutput is SpecifiedIf you use the optional name-value argument for
UserDatato specify a strategy-specificuserDatastruct and the optional name-value argumentEngineDataListto specify required backtest engine state data and thedetailedCostsoutput is specified, the required syntax for thecomputeTransactionCostsFcnfunction handle must have the following signature:If you specify the optional name-value argument[detailedCosts,userData] = computeTransactionCostsFcn(deltaPositions,engineData,userData)
UserData, then the strategyuserDatastruct is passed to thecomputeTransactionCostsFcncost function as the final input argument. ThecomputeTransactionCostsFcnfunction handle can read or write any information to theuserDatastruct. The updateduserDatastruct is returned as the second output argument.
Data Types: double | function_handle
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 settingLookbackWindowto the scalar value20.
When the inputs are either
durationorcalendarDurationobjects, 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
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
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
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
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
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
assetPricesorsignalDatatimetables 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
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
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
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 ofWeightsvectors 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 containNaNvalues.PortfolioValue— The current portfolio value at the time the rebalance function (RebalanceFcn) is called, specified as a numeric scalar. ThisPortfolioValueis potentially different from the "end of day" portfolio value reported by thebacktestEngineat the end of the backtest. Transaction costs and other fees are paid after the rebalance function is called, so thePortfolioValuepassed into the rebalance function by theengineDatastruct 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. TheBeforeExpensescolumn holds the portfolio value for each day before any expenses (transaction costs or fees). TheEndOfDaycolumn 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 containNaNvalues.FeesHistory— A timetable of fees charged for each day in the backtest. TheFeesHistorytimetable has two columns:ManagementandPerformancefor the management and performance fees, respectively. The management and performance fees are defined using theManagementFeeandPerformanceFeename-value arguments. Future dates in the timetable containNaNvalues.TransactionCostHistory— A timetable of per-asset transaction costs charged for each day in the backtest. Future dates in the timetable containNaNvalues. TheTransactionCostHistorycannot be set if theTransactionCostsfunction generate aggregate costs. TheTransactionCostscomputeTransactionCostsFcnfunction handle must specify an output fordetailedCoststo setTransactionCostHistory.CashAsset— A string array for items in thepricesTTtimetable specified as cash assets.DebtAsset— A string array for items in thepricesTTtimetable 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
Output Arguments
Backtest strategy, returned as a backtestStrategy
object.
Properties
Strategy name, specified as a string.
Data Types: string
Rebalance function, specified as a function handle.
Data Types: function_handle
Rebalance frequency during the backtest, specified as a scalar numeric,
duration or calendarDuration
object, or vector of datetimes.
Data Types: double | object | datetime
Transaction costs, specified as a scalar numeric, vector, or function handle.
Data Types: double | function_handle
Lookback window, specified as a scalar numeric or vector.
Data Types: double
Initial weights, specified as a vector.
Data Types: double
Since R2022b
Management fee charged as an annualized percent of the total portfolio value, specified as a numeric scalar.
Data Types: double
Since R2022b
Management fee schedule, specified as scalar numeric, a vector of
datetimes, or a duration or
calendarDuration object.
Data Types: double | datetime | object
Since R2022b
Performance fee charged as an absolute percent of the total portfolio growth, specified as a numeric scalar.
Data Types: double
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
Since R2022b
Performance fee schedule, specified as a scalar numeric, vector of
datetimes, or a duration or
calendarDuration object.
Data Types: datetime | object
Since R2023a
Strategy-specific user data for use in rebalanceFcn,
specified as a struct.
Data Types: struct
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
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 settingLookbackWindowto 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: [0×0 struct]
EngineDataList: [0×0 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: [0×0 struct]
EngineDataList: [0×0 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
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
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
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
LookbackWindowproperty to a1-by-2vector [min max]. The software then calls the rebalance function with a price timetable containing at leastminrows and at mostmaxrows of rolling price data.Fixed-size rolling window — For a fixed-sized rolling window of data, set the
LookbackWindowproperty 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
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
assetPricestimetable) 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.durationorcalendarDuration— If specified as adurationorcalendarDurationobject, 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.
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
ManagementFeeScheduleis 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
ManagementFeeScheduleis 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
ManagementFeeScheduleis specified as adurationorcalendarDurationobject, 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.
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.
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 R2020bWhen 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.
The EngineDataList name-value argument supports information
for CashAssets and DebtAssets. The
CashAssets and DebtAssets are defined as
name-value arguments when using runBacktest.
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.
The backtestStrategy object supports name-value arguments for
ManagementFee, ManagementFeeSchedule,
PerformanceFee, PerformanceHurdle, and
PerformanceFeeSchedule.
The backtestStrategy object supports NaNs in
the assetPrices timetable and NaNs 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)