runBacktest

Запуск backtest на одной или нескольких стратегиях

Описание

пример

backtester = runBacktest(backtester,pricesTT) выполнение бэктеста по расписанию скорректированных данных о ценах активов.

runBacktest инициализирует каждую стратегию, ранее определенную с помощью backtestStrategy на InitialPortfolioValue а затем начинает обработку расписания данных о ценах (pricesTT) следующим образом:

  1. На каждом временном шаге runBacktest функция применяет возвраты активов к позициям портфеля стратегии.

  2. The runBacktest функция определяет, какие стратегии для ребаланса основаны на RebalanceFrequency свойство backtestStrategy объекты.

  3. Для стратегий, которые нуждаются в ребалансировке, runBacktest функция вызывает их функции ребаланса с помощью скользящего окна данных о ценах основных средств на основе LookbackWindow свойство каждого backtestStrategy.

  4. Транзакционные затраты рассчитываются и начисляются на основе изменений в позициях основных средств и TransactionCosts свойство каждого backtestStrategy объект.

  5. После завершения бэктеста результаты сохраняются в нескольких свойствах backtestEngine объект.

пример

backtester = runBacktest(backtester,pricesTT,signalTT) запуск бэктеста с использованием скорректированных данных о цене активов и сигнальных данных. Когда вы задаете расписание данных сигнала (signalTT), затем runBacktest функция запускает backtest и дополнительно передает скользящее окно данных сигнала в функцию ребаланса каждой стратегии во время шага ребаланса.

пример

backtester = runBacktest(___,Name,Value) задает опции с использованием одного или нескольких необязательных аргументов пары "имя-значение" в дополнение к входным параметрам в предыдущем синтаксисе. Для примера, backtester = runBacktest(backtester,assetPrices,'Start',50,'End',100).

Примеры

свернуть все

Механизм обратного тестирования MATLAB ® запускает бэктесты стратегий портфельных инвестиций во timeseries данных о ценах активов. После создания набора стратегий backtest с использованием backtestStrategy и backtest engine, использующий backtestEngine, а runBacktest функция выполняет бэктест. Этот пример иллюстрирует, как использовать runBacktest функция для тестирования инвестиционных стратегий.

Загрузка данных

Загрузка данных о ценах на акции за один год. Для читаемости в этом примере используется только подмножество запасов DJIA.

% Read table of daily adjusted close prices for 2006 DJIA stocks
T = readtable('dowPortfolio.xlsx');

% Prune the table on only hold the dates and selected stocks
timeColumn = "Dates";
assetSymbols = ["BA", "CAT", "DIS", "GE", "IBM", "MCD", "MSFT"];
T = T(:,[timeColumn assetSymbols]);

% Convert to timetable
pricesTT = table2timetable(T,'RowTimes','Dates');

% View the final asset price timetable
head(pricesTT)
ans=8×7 timetable
       Dates        BA       CAT      DIS      GE       IBM      MCD     MSFT 
    ___________    _____    _____    _____    _____    _____    _____    _____

    03-Jan-2006    68.63    55.86    24.18     33.6    80.13    32.72    26.19
    04-Jan-2006    69.34    57.29    23.77    33.56    80.03    33.01    26.32
    05-Jan-2006    68.53    57.29    24.19    33.47    80.56    33.05    26.34
    06-Jan-2006    67.57    58.43    24.52     33.7    82.96    33.25    26.26
    09-Jan-2006    67.01    59.49    24.78    33.61    81.76    33.88    26.21
    10-Jan-2006    67.33    59.25    25.09    33.43     82.1    33.91    26.35
    11-Jan-2006     68.3    59.28    25.33    33.66    82.19     34.5    26.63
    12-Jan-2006     67.9    60.13    25.41    33.25    81.61    33.96    26.48

Создайте стратегию

В этом вступительном примере протестируйте равную взвешенную инвестиционную стратегию. Эта стратегия инвестирует равный фрагмент доступного капитала в каждый актив. В этом примере описываются подробные сведения о том, как создать стратегии backtest. Для получения дополнительной информации о создании стратегий backtest смотрите backtestStrategy.

Установите RebalanceFrequency для ребаланса портфеля каждые 60 дней. Этот пример не использует интерполяционное окно для ребаланса.

% Create the strategy
numAssets = size(pricesTT,2);
equalWeightsVector = ones(1,numAssets) / numAssets;
equalWeightsRebalanceFcn = @(~,~) equalWeightsVector;

ewStrategy = backtestStrategy("EqualWeighted",equalWeightsRebalanceFcn,...
    'RebalanceFrequency',60,...
    'LookbackWindow',0,...
    'TransactionCosts',0.005,...
    'InitialWeights',equalWeightsVector)
ewStrategy = 
  backtestStrategy with properties:

                  Name: "EqualWeighted"
          RebalanceFcn: @(~,~)equalWeightsVector
    RebalanceFrequency: 60
      TransactionCosts: 0.0050
        LookbackWindow: 0
        InitialWeights: [0.1429 0.1429 0.1429 0.1429 0.1429 0.1429 0.1429]

Запуск бэктеста

Создайте механизм обратного тестирования и запустите бэктест за год хранения данных. Для получения дополнительной информации о создании узлов backtest смотрите backtestEngine.

% Create the backtest engine. The backtest engine properties that hold the
% results are initialized to empty.
backtester = backtestEngine(ewStrategy)
backtester = 
  backtestEngine with properties:

               Strategies: [1x1 backtestStrategy]
             RiskFreeRate: 0
           CashBorrowRate: 0
          RatesConvention: "Annualized"
                    Basis: 0
    InitialPortfolioValue: 10000
                NumAssets: []
                  Returns: []
                Positions: []
                 Turnover: []
                  BuyCost: []
                 SellCost: []

% Run the backtest. The empty properties are now populated with
% timetables of detailed backtest results.
backtester = runBacktest(backtester,pricesTT)
backtester = 
  backtestEngine with properties:

               Strategies: [1x1 backtestStrategy]
             RiskFreeRate: 0
           CashBorrowRate: 0
          RatesConvention: "Annualized"
                    Basis: 0
    InitialPortfolioValue: 10000
                NumAssets: 7
                  Returns: [250x1 timetable]
                Positions: [1x1 struct]
                 Turnover: [250x1 timetable]
                  BuyCost: [250x1 timetable]
                 SellCost: [250x1 timetable]

Бэктест- Сводные данные

Используйте summary функция для генерации сводной таблицы результатов backtest.

% Examing results. The summary table shows several performance metrics.
summary(backtester)
ans=9×1 table
                       EqualWeighted
                       _____________

    TotalReturn            0.22943  
    SharpeRatio            0.11415  
    Volatility           0.0075013  
    AverageTurnover     0.00054232  
    MaxTurnover           0.038694  
    AverageReturn       0.00085456  
    MaxDrawdown           0.098905  
    AverageBuyCost        0.030193  
    AverageSellCost       0.030193  

При запуске backtest в MATLAB ® необходимо понять, каковы начальные условия, когда backtest начинается. Начальные веса для каждой стратегии, размер окна поиска стратегии и любое потенциальное разделение набора данных на разделы обучения и проверки влияют на результаты бэктеста. В этом примере показано, как использовать runBacktest функция со 'Start' и 'End' аргументы пары "имя-значение", которые взаимодействуют со 'LookbackWindow' и 'RebalanceFrequency' свойства backtestStrategy объект для «теплого запуска» бэктеста.

Загрузка данных

Загрузка данных о ценах на акции за один год. Для читаемости в этом примере используется только подмножество запасов DJIA.

% Read table of daily adjusted close prices for 2006 DJIA stocks.
T = readtable('dowPortfolio.xlsx');

% Prune the table to include only the dates and selected stocks.
timeColumn = "Dates";
assetSymbols = ["BA", "CAT", "DIS", "GE", "IBM", "MCD", "MSFT"];
T = T(:,[timeColumn assetSymbols]);

% Convert to timetable.
pricesTT = table2timetable(T,'RowTimes','Dates');

% View the final asset price timetable.
head(pricesTT)
ans=8×7 timetable
       Dates        BA       CAT      DIS      GE       IBM      MCD     MSFT 
    ___________    _____    _____    _____    _____    _____    _____    _____

    03-Jan-2006    68.63    55.86    24.18     33.6    80.13    32.72    26.19
    04-Jan-2006    69.34    57.29    23.77    33.56    80.03    33.01    26.32
    05-Jan-2006    68.53    57.29    24.19    33.47    80.56    33.05    26.34
    06-Jan-2006    67.57    58.43    24.52     33.7    82.96    33.25    26.26
    09-Jan-2006    67.01    59.49    24.78    33.61    81.76    33.88    26.21
    10-Jan-2006    67.33    59.25    25.09    33.43     82.1    33.91    26.35
    11-Jan-2006     68.3    59.28    25.33    33.66    82.19     34.5    26.63
    12-Jan-2006     67.9    60.13    25.41    33.25    81.61    33.96    26.48

Создайте стратегию

В этом примере описывается стратегия «обратного отклонения». Функция обратного отклонения баланса реализована в разделе Local Functions. Для получения дополнительной информации о создании стратегий backtest смотрите backtestStrategy. Стратегия обратного отклонения использует ковариацию возвратов основных средств для принятия решений о распределении основных средств. The LookbackWindow для этой стратегии должны содержать по меньшей мере 30 дней конечных данных (около 6 недель) и самое большее 60 дней (около 12 недель).

Задайте RebalanceFrequency для backtestStrategy для ребаланса портфеля каждые 25 дней.

% Create the strategy
minLookback = 30;
maxLookback = 60;
ivStrategy = backtestStrategy("InverseVariance",@inverseVarianceFcn,...
    'RebalanceFrequency',25,...
    'LookbackWindow',[minLookback maxLookback],...
    'TransactionCosts',[0.0025 0.005])
ivStrategy = 
  backtestStrategy with properties:

                  Name: "InverseVariance"
          RebalanceFcn: @inverseVarianceFcn
    RebalanceFrequency: 25
      TransactionCosts: [0.0025 0.0050]
        LookbackWindow: [30 60]
        InitialWeights: [1x0 double]

Запустите Backtest и исследуйте результаты

Создайте механизм обратного тестирования и запустите бэктест за год хранения данных. Для получения дополнительной информации о создании узлов backtest смотрите backtestEngine.

% Create the backtest engine.
backtester = backtestEngine(ivStrategy);

% Run the backtest.
backtester = runBacktest(backtester,pricesTT);

Используйте assetAreaPlot helper function, заданная в разделе Локальные функции этого примера, чтобы отобразить изменение в распределении активов в течение бэктеста.

assetAreaPlot(backtester,"InverseVariance")

Figure contains an axes. The axes with title InverseVariance Positions contains 8 objects of type area. These objects represent Cash, BA, CAT, DIS, GE, IBM, MCD, MSFT.

Заметьте, что стратегия обратного отклонения начинается все наличными и остается в этом состоянии около 2,5 месяцев. Это потому, что backtestStrategy объект не имеет заданного набора начальных весов, которые вы задаете используя InitialPortfolioValue аргумент пары "имя-значение". Стратегия обратного отклонения требует 30 дней конечной ценовой истории актива перед ребалансировкой. Можно использовать printRebalanceTable helper, заданная в разделе Локальные функции, для отображения расписания ребаланса.

printRebalanceTable(ivStrategy,pricesTT,minLookback);
    First Day of Data    Backtest Start Date    Minimum Days to Rebalance
    _________________    ___________________    _________________________

       03-Jan-2006           03-Jan-2006                   30            



    Rebalance Dates    Days of Available Price History    Enough Data to Rebalance
    _______________    _______________________________    ________________________

      08-Feb-2006                     26                           "No"           
      16-Mar-2006                     51                           "Yes"          
      21-Apr-2006                     76                           "Yes"          
      26-May-2006                    101                           "Yes"          
      03-Jul-2006                    126                           "Yes"          
      08-Aug-2006                    151                           "Yes"          
      13-Sep-2006                    176                           "Yes"          
      18-Oct-2006                    201                           "Yes"          
      22-Nov-2006                    226                           "Yes"          
      29-Dec-2006                    251                           "Yes"          

Первая дата ребаланса наступает 8 февраля, но стратегии не хватает истории цен, чтобы заполнить допустимое окно поиска (минимум 30 дней), поэтому никакого ребаланса не происходит. Следующая дата ребаланса - 16 марта, полные 50 дней в бэктест.

Эта ситуация не идеальна, поскольку эти 50 дней, сидя в полностью кассовом положении, составляют примерно 20% от общего бэктеста. Следовательно, когда механизм обратного тестирования сообщает о производительности стратегии (то есть общего возврата, коэффициента Шарпа, волатильности и так далее), результаты не отражают «истинную» эффективность стратегии, потому что стратегия только начала принимать решения о распределении активов только около 20% в backtest.

Бэктест теплого запуска

Возможно «теплое начало» бэктеста. Теплый старт означает, что самые задние результаты отражают эффективность стратегии в рыночных условиях, отраженные в расписании цен. Чтобы начать, установите начальные веса стратегии, чтобы избежать запуска всех наличными.

Стратегия обратного отклонения требует 30 дней истории цен, чтобы заполнить допустимое окно поиска, поэтому можно разбить набор ценовых данных на два раздела, набор «прогрева» и «тестового» набора.

warmupRange = 1:30;
% The 30th row is included in both ranges since the day 30 price is used
% to compute the day 31 returns.
testRange = 30:height(pricesTT);

Используйте разогревающий раздел, чтобы задать начальные веса стратегии обратного отклонения. При этом можно начать бэктест со стратегии, которая уже «выполняется», и избежать начальных недель, проведенных в денежной позиции.

% Use the rebalance function to set the initial weights. This might
% or might not be possible for other strategies depending on the details of
% the strategy logic.
initWeights = inverseVarianceFcn([],pricesTT(warmupRange,:));

Обновите стратегию и перезапустите бэктест. Поскольку область значений прогрева используется для инициализации стратегии обратного отклонения, вы должны опустить эти данные из бэктеста, чтобы избежать смещения вперед или «увидеть будущее», и выполнить обратное тестирование только в «тестовой области значений».

% Set the initial weights on the strategy in the backtester. You can do this when you 
% create the strategy as well, using the 'InitialWeights' parameter.
backtester.Strategies(1).InitialWeights = initWeights;

% Rerun the backtest over the "test" range.
backtester = runBacktest(backtester,pricesTT(testRange,:));

Когда вы генерируете график области, можно увидеть, что проблема, где стратегия в наличных для первого фрагмента бэктеста, избегается.

assetAreaPlot(backtester,"InverseVariance")

Figure contains an axes. The axes with title InverseVariance Positions contains 8 objects of type area. These objects represent Cash, BA, CAT, DIS, GE, IBM, MCD, MSFT.

Однако, если посмотреть таблицу ребаланса, можно увидеть, что стратегия все еще «пропустила» первую дату ребаланса. Когда вы запускаете бэктест по тестовой области значений набора данных, первая дата ребаланса - 22 марта. Это связано с тем, что область значений прогрева опущена из ценовой истории, и в стратегии было доступно только 26 дней истории на эту дату (меньше, чем минимум 30 дней, необходимых для интерполяционного окна). Поэтому ребаланс 22 марта пропущен.

Чтобы избежать обратных тестов в области значений прогрева, область значений был удаляема из набора данных. Это означает, что новая дата начала бэктеста и все последующие даты ребаланса начинаются через 30 дней. Данные ценовой истории, содержащиеся в области значений прогрева, были полностью удалены, поэтому, когда самый задний двигатель достиг первой даты ребаланса, ценовая история была недостаточной для ребаланса.

printRebalanceTable(ivStrategy,pricesTT(testRange,:),minLookback);
    First Day of Data    Backtest Start Date    Minimum Days to Rebalance
    _________________    ___________________    _________________________

       14-Feb-2006           14-Feb-2006                   30            



    Rebalance Dates    Days of Available Price History    Enough Data to Rebalance
    _______________    _______________________________    ________________________

      22-Mar-2006                     26                           "No"           
      27-Apr-2006                     51                           "Yes"          
      02-Jun-2006                     76                           "Yes"          
      10-Jul-2006                    101                           "Yes"          
      14-Aug-2006                    126                           "Yes"          
      19-Sep-2006                    151                           "Yes"          
      24-Oct-2006                    176                           "Yes"          
      29-Nov-2006                    201                           "Yes"          

Этот сценарий также не верен, так как исходное расписание цен (разминка и тестовые разделы вместе) имеет достаточную историю цен к 22 марта, чтобы заполнить допустимое окно поиска. Однако более ранние данные недоступны для бэктеста, поскольку бэктест выполнялся с использованием только тестового раздела.

Использование Start и End Параметры для runBacktest

Идеальный рабочий процесс в этой ситуации состоит в том, чтобы опустить область значений данных разогрева из backtest, чтобы избежать интерполяционного смещения, но включить данные разогрева в историю цен, чтобы иметь возможность заполнить интерполяционное окно стратегии со всеми доступными данными истории цен. Вы можете сделать это при помощи 'Start' параметр для runBacktest функция.

The 'Start' и 'End' аргументы пары "имя-значение" для runBacktest позволяет вам начать и завершить бэктест в определенные даты. Можно задать 'Start' и 'End' как строки расписания цен или как значения datetime (см. документацию для runBacktest функция для получения дополнительной информации). The 'Start' аргумент позволяет backtest начинаться с определенной даты, предоставляя подсистеме backtest доступ к полному набору данных.

Перезапустите бэктест с помощью 'Start' аргумент пары "имя-значение", а не только в виде раздела исходного набора данных.

% Rerun the backtest starting on the last day of the warmup range.
startRow = warmupRange(end);
backtester = runBacktest(backtester,pricesTT,'Start',startRow);

Постройте график новой площади актива.

assetAreaPlot(backtester,"InverseVariance")

Figure contains an axes. The axes with title InverseVariance Positions contains 8 objects of type area. These objects represent Cash, BA, CAT, DIS, GE, IBM, MCD, MSFT.

Просмотр новой таблицы ребаланса с новой 'Start' параметр.

printRebalanceTable(ivStrategy,pricesTT,minLookback,startRow);
    First Day of Data    Backtest Start Date    Minimum Days to Rebalance
    _________________    ___________________    _________________________

       03-Jan-2006           14-Feb-2006                   30            



    Rebalance Dates    Days of Available Price History    Enough Data to Rebalance
    _______________    _______________________________    ________________________

      22-Mar-2006                     55                           "Yes"          
      27-Apr-2006                     80                           "Yes"          
      02-Jun-2006                    105                           "Yes"          
      10-Jul-2006                    130                           "Yes"          
      14-Aug-2006                    155                           "Yes"          
      19-Sep-2006                    180                           "Yes"          
      24-Oct-2006                    205                           "Yes"          
      29-Nov-2006                    230                           "Yes"          

Стратегия обратного отклонения теперь имеет достаточно данных для ребаланса на первую дату ребаланса (22 марта), а бэктест «теплый». При помощи исходного набора данных первый день данных остается 3 января, а 'Start' параметр позволяет вам переместить дату начала бэктеста вперед, чтобы избежать области значений прогрева.

Даже при том, что результаты не сильно отличаются, этот пример иллюстрирует взаимодействие между LookbackWindow and RebalanceFrequency аргументы пары "имя-значение" для backtestStrategy объект и область значений данных, используемых в runBacktest при оценке эффективности стратегии в backtest.

Локальные функции

Функция ребаланса стратегии реализована следующим образом. Для получения дополнительной информации о создании стратегий и записи функций ребаланса смотрите backtestStrategy.

function new_weights = inverseVarianceFcn(current_weights, pricesTT) 
% Inverse-variance portfolio allocation.

assetReturns = tick2ret(pricesTT);
assetCov = cov(assetReturns{:,:});
new_weights = 1 ./ diag(assetCov);
new_weights = new_weights / sum(new_weights);

end

Эти графики функций распределения активов как график площади.

function assetAreaPlot(backtester,strategyName)

t = backtester.Positions.(strategyName).Time;
positions = backtester.Positions.(strategyName).Variables;
h = area(t,positions);
title(sprintf('%s Positions',strategyName));
xlabel('Date');
ylabel('Asset Positions');
datetick('x','mm/dd','keepticks');
xlim([t(1) t(end)])
oldylim = ylim;
ylim([0 oldylim(2)]);
cm = parula(numel(h));
for i = 1:numel(h)
    set(h(i),'FaceColor',cm(i,:));
end
legend(backtester.Positions.(strategyName).Properties.VariableNames)

end

Эта вспомогательная функция генерирует таблицу дат ребаланса вместе с доступной ценовой историей на каждую дату.

function printRebalanceTable(strategy,pricesTT,minLookback,startRow)

if nargin < 4
    startRow = 1;
end

allDates = pricesTT.(pricesTT.Properties.DimensionNames{1});
rebalanceDates = allDates(startRow:strategy.RebalanceFrequency:end);
[~,rebalanceIndices] = ismember(rebalanceDates,pricesTT.Dates);

disp(table(allDates(1),rebalanceDates(1),minLookback,'VariableNames',{'First Day of Data','Backtest Start Date','Minimum Days to Rebalance'}));
fprintf('\n\n');
numHistory = rebalanceIndices(2:end);
sufficient = repmat("No",size(numHistory));
sufficient(numHistory > minLookback) = "Yes";
disp(table(rebalanceDates(2:end),rebalanceIndices(2:end),sufficient,'VariableNames',{'Rebalance Dates','Days of Available Price History','Enough Data to Rebalance'}));

end

Входные параметры

свернуть все

Механизм обратного тестирования, заданный как backtestEngine объект. Использование backtestEngine чтобы создать backtester объект.

Типы данных: object

Цены активов, указанные как расписание цен активов, которые backtestEngine использует для обратной проверки стратегий. Каждый столбец расписания цен должен содержать timeseries цен для актива. Исторические цены активов должны быть скорректированы с учетом разделений и дивидендов.

Типы данных: timetable

(Необязательно) Данные сигнала, заданные как расписание торговых сигналов, которые используются стратегиями для принятия торговых решений. signalTT опционально. Если предусмотрено, backtestEngine вызывает функции восстановления баланса стратегии как с данными о цене основного средства, так и с данными о сигнале. The signalTT расписание должно иметь ту же временную размерность, что и pricesTT timetable.

Типы данных: timetable

Аргументы в виде пар имя-значение

Задайте необязательные разделенные разделенными запятой парами Name,Value аргументы. Name - имя аргумента и Value - соответствующее значение. Name должны находиться внутри кавычек. Можно задать несколько аргументов в виде пар имен и значений в любом порядке Name1,Value1,...,NameN,ValueN.

Пример: backtester = runBacktest(backtester,assetPrices,'Start',50,'End',100)

Временной шаг для запуска бэктеста, заданный как разделенная разделенными запятой парами, состоящая из 'Start' и скаляр целое число или datetime.

Если целое число, Start время относится к строке в pricesTT timetable, где начинается бэктест.

Если a datetime объект, backtest начнется в первый раз в расписании цен, которое происходит на или после 'Start' параметр. Бэктест закончится в последний раз в расписании цен, которое происходит на или перед 'End' параметр. The 'Start' и 'End' параметры устанавливают контур данных, включенных в бэктест.

Типы данных: double | datetime

Временной шаг для завершения обратного теста, заданный как разделенная разделенными запятой парами, состоящая из 'End' и скаляр целое число или datetime.

Если целое число, End время относится к строке в pricesTT расписание, где заканчивается бэктест.

Если a datetime объект, TBD

Типы данных: double | datetime

Выходные аргументы

свернуть все

Механизм обратного тестирования, возвращенный как обновленный backtestEngine объект. После завершения обратного тестирования runBacktest заполняет несколько свойств в backtestEngine объект с результатами бэктеста. Результирующие результаты можно подвести при помощи summary функция.

Введенный в R2020b