%% 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 backtesting engine calls a strategy rebalance function, a 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 a rolling window of market data. The LookbackWindow property sets the size of these rolling windows. Set the window in terms of time steps. The window determines the number of rows 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 for an expanding window of data. In this way, you can set flexible window sizes. For example:
% [10 Inf] — At least 10 rows of data
% [0 50] — No more than 50 rows of data
% [0 Inf]  — All available data (that is, no minimum, no maximum); this is the default value
% [20 20] — Exactly 20 rows of data; this is equivalent to setting LookbackWindow to the scalar value 20
% 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)
% 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)

%
%% 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

% Copyright 2020 The MathWorks, Inc.