Инвестиционные стратегии Backtest

В этом примере показано, как выполнить backtesting стратегий портфеля с помощью backtesting среды, реализованной в MATLAB®. Backtesting является полезным инструментом, чтобы выдержать сравнение, как инвестиционные стратегии выполняют по историческим или симулированным данным о рынке. Этот пример разрабатывает пять различных инвестиционных стратегий и затем сравнивает их эффективность после работания на основе однолетнего периода исторических данных о запасе. backtesting среда реализована в двух классах MATLAB®: backtestStrategy и backtestEngine.

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

Загрузите один год настроенных ценовых данных для 30 запасов. backtesting среды требуют настроенных цен активов, означая цены, настроенные для дивидендов, разделений или других событий. Цены должны храниться в MATLAB® timetable с каждым столбцом, содержащим временные ряды цен активов на подходящий для инвестирования актив.

В данном примере используйте один год данных цен активов из запасов компонента промышленного индекса Доу-Джонса.

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

% For readability, use only 15 of the 30 DJI component stocks.
assetSymbols = ["AA","CAT","DIS","GM","HPQ","JNJ","MCD","MMM","MO","MRK","MSFT","PFE","PG","T","XOM"];

% Prune the table to hold only the dates and selected stocks.
timeColumn = "Dates";
T = T(:,[timeColumn assetSymbols]);

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

% View the structure of the prices timetable.
head(pricesTT)
ans=8×15 timetable
       Dates        AA       CAT      DIS      GM       HPQ      JNJ      MCD      MMM      MO       MRK     MSFT      PFE      PG        T       XOM 
    ___________    _____    _____    _____    _____    _____    _____    _____    _____    _____    _____    _____    _____    _____    _____    _____

    03-Jan-2006    28.72    55.86    24.18    17.82    28.35    59.08    32.72    75.93    52.27    30.73    26.19    22.16    56.38     22.7    56.64
    04-Jan-2006    28.89    57.29    23.77     18.3    29.18    59.99    33.01    75.54    52.65    31.08    26.32    22.88    56.48    22.87    56.74
    05-Jan-2006    29.12    57.29    24.19    19.34    28.97    59.74    33.05    74.85    52.52    31.13    26.34     22.9     56.3    22.92    56.45
    06-Jan-2006    29.02    58.43    24.52    19.61     29.8    60.01    33.25    75.47    52.95    31.08    26.26    23.16    56.24    23.21    57.57
    09-Jan-2006    29.37    59.49    24.78    21.12    30.17    60.38    33.88    75.84    53.11    31.58    26.21    23.16    56.67     23.3    57.54
    10-Jan-2006    28.44    59.25    25.09    20.79    30.33    60.49    33.91    75.37    53.04    31.27    26.35    22.77    56.45    23.16    57.99
    11-Jan-2006    28.05    59.28    25.33    20.61    30.88    59.91     34.5    75.22    53.31    31.39    26.63    23.06    56.65    23.34    58.38
    12-Jan-2006    27.68    60.13    25.41    19.76    30.57    59.63    33.96    74.57    53.23    31.41    26.48     22.9    56.02    23.24    57.77

% View the size of the asset price data set.
numSample = size(pricesTT.Variables, 1);
numAssets = size(pricesTT.Variables, 2);
table(numSample, numAssets)
ans=1×2 table
    numSample    numAssets
    _________    _________

       251          15    

Задайте стратегии

Инвестиционные стратегии получают логику, используемую, чтобы принять решения распределения активов, в то время как backtest запускается. Как запуски backtest, каждой стратегии периодически дают возможность обновить ее выделение портфеля на основе запаздывающего состояния рынка, которое это делает путем установки вектора из весов актива. Веса актива представляют процент ликвидного капитала, инвестированного в каждый актив с каждым элементом в соответствии вектора весов соответствующему столбцу в активе pricesTT расписание. Если суммой вектора весов является 1, затем портфель полностью инвестируют.

В этом примере существует пять backtest стратегий. backtest стратегии присваивают веса актива с помощью следующих критериев:

  • Равно взвешенный

ωEW=(ω1,ω2,...,ωN),ωi=1N

  • Максимизация отношения Шарпа

ωSR=argmax                  ω{rωωQω|ω0,1Nωi=1,0ω0.1}, где r вектор из ожидаемых доходов и Q ковариационная матрица актива, возвращается.

  • Обратное отклонение

ωIV=(ω1,ω2,...,ωN),ωi=(σii-1)i=1Nσii-1, где σii диагональные элементы актива, возвращают ковариационную матрицу.

  • Оптимизация портфеля Markowitz (максимизация возвращаются и минимизирующий риск с фиксированным коэффициентом нерасположенности к риску),

RMkwtz=maxω{rω-λωQω|ω0,1Nωi=1,0ω0.1}, где λ  коэффициент нерасположенности к риску.

  • Устойчивая оптимизация с неопределенностью в ожидаемых доходах

  • Устойчивая стратегия оптимизации портфеля, в отличие от детерминированной формулировки Markowitz, учитывает ожидаемые доходы неопределенности активов и их отклонений и ковариаций. Вместо того, чтобы моделировать неизвестные значения (например, ожидаемые доходы) как одна точка, обычно представленная средним значением, вычисленным от прошлого, неизвестные заданы в виде набора значений, которые содержат наиболее вероятную реализацию, r={r|rS(r0)}.

В этом случае ожидаемый доход задан не детерминированным вектором r0 но областью S(r0) вокруг вектора r0.

При принятии этого во внимание существует несколько способов переформулировать задачу оптимизации портфеля. Один из наиболее часто используемых методов должен сформулировать проблему как задачу нахождения максимум и минимум:

Rrobust=maxωminrS(r0){rω-λωQω|ω0,1Nωi=1,0ω0.1}

В этом примере, области неопределенности S(r0) задан как эллипсоид:

S(r0)={r|(r-r0)Σr-1(r-r0)κ2}

Здесь, κ - коэффициент отвращения неопределенности, который задает, насколько широкий область неопределенности, и Σr матрица ошибок расчета в ожидаемых доходах r.

Со сложением неопределенности эллипсоида к модели Markowitz устойчивая задача оптимизации переформулирована как:

Rrobust=maxω{rω-λωQω-kz|ω0,z0,ωΣrω-z20,1Nωi=1,0ω0.1}

Реализуйте функции восстановления равновесия стратегии

Логика ядра каждой стратегии реализована в функции восстановления равновесия. Функция восстановления равновесия является пользовательской функцией MATLAB®, которая задает, как стратегия выделяет капитал в портфеле. Функция восстановления равновесия является входным параметром к backtestStrategy. Функция восстановления равновесия должна реализовать следующую фиксированную подпись:

function new_weights = allocationFunctionName(current_weights, pricesTimetable)

Эта фиксированная подпись является API, который backtest среда использует при изменении баланса портфеля. Как запуски backtest, backtesting механизм вызывает функцию восстановления равновесия каждой стратегии, передающей в этих входных параметрах:

  • current_weights — Текущие веса портфеля перед изменением баланса

  • pricesTimetable — Объект расписания MATLAB®, содержащий прокручивающееся окно цен активов.

backtestStrategy изменяйте баланс функция использует эту информацию, чтобы вычислить желаемые новые веса портфеля, которые возвращены в backtesting механизм в функциональном выходе new_weights. Смотрите разделы Локальных функций для функции восстановления равновесия для каждой из этих пяти стратегий.

Вычислите начальные веса стратегии

Используйте функции восстановления равновесия стратегии, чтобы вычислить начальные веса для каждой стратегии. Установка начальных весов важна, потому что в противном случае стратегии начинают backtest с 100% наличными, зарабатывая безрисковый уровень, до первой даты восстановления равновесия.

Этот пример использует первые 40 дней набора данных (приблизительно 2 месяца), чтобы инициализировать стратегии. backtest затем запущен по остающимся данным (приблизительно 10 месяцев).

warmupPeriod = 40;

Начальные веса вычисляются путем вызова backtestStrategy восстановите равновесие функции таким же образом, что backtesting механизм вызовет ее. Для этого передайте в векторе из текущих весов (все нули, который является 100%-ми наличными деньгами), а также окно ценовых данных, которые стратегии будут использовать, чтобы установить желаемые веса (раздел данных прогрева). Используя функции восстановления равновесия, чтобы вычислить начальные веса таким образом не требуется. Начальные веса являются вектором из начальных весов портфеля и могут быть установлены в любое соответствующее значение. Функции восстановления равновесия в этом примере аппроксимируют состояние, в котором были бы стратегии, имел их уже выполнение в начале backtest.

% No current weights (100% cash position).
current_weights = zeros(1,numAssets);

% Warm-up partition of data set timetable.
warmupTT = pricesTT(1:warmupPeriod,:);

% Compute the initial portfolio weights for each strategy.
equalWeight_initial     = equalWeightFcn(current_weights,warmupTT);
maxSharpeRatio_initial  = maxSharpeRatioFcn(current_weights,warmupTT);
inverseVariance_initial = inverseVarianceFcn(current_weights,warmupTT);
markowitz_initial       = markowitzFcn(current_weights,warmupTT);
robustOptim_initial     = robustOptimFcn(current_weights,warmupTT);

Визуализируйте начальные выделения веса из стратегий.

strategyNames = {'Equal Weighted', 'Max Sharpe Ratio', 'Inverse Variance', 'Markowitz Optimization','Robust Optimization'};
assetSymbols = pricesTT.Properties.VariableNames;
initialWeights = [equalWeight_initial(:), maxSharpeRatio_initial(:), inverseVariance_initial(:), markowitz_initial(:), robustOptim_initial(:)];
heatmap(strategyNames, assetSymbols, initialWeights, 'title','Initial Asset Allocations','Colormap', parula);

Figure contains an object of type heatmap. The chart of type heatmap has title Initial Asset Allocations.

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

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

Установите частоту восстановления равновесия, и lookback размер окна установлены в терминах количества временных шагов (то есть, строки pricesTT расписание. Поскольку данные являются ежедневными ценовыми данными, задайте частоту восстановления равновесия и прокручивающийся lookback окно в днях.

% Rebalance approximately every 1 month (252 / 12 = 21).
rebalFreq = 21;

% Set the rolling lookback window to be at least 40 days and at most 126
% days (about 6 months).
lookback  = [40 126];

% Use a fixed transaction cost (buy and sell costs are both 0.5% of amount
% traded).
transactionsFixed = 0.005;

% Customize the transaction costs using a function. See the
% variableTransactionCosts function below for an example.
transactionsVariable = @variableTransactionCosts;

% The first two strategies use fixed transaction costs. The equal-weighted
% strategy does not require a lookback window of trailing data, as its
% allocation is fixed.
strat1 = backtestStrategy('Equal Weighted', @equalWeightFcn,...
    'RebalanceFrequency', rebalFreq,...
    'LookbackWindow', 0,...
    'TransactionCosts', transactionsFixed,...
    'InitialWeights', equalWeight_initial);

strat2 = backtestStrategy('Max Sharpe Ratio', @maxSharpeRatioFcn,...
    'RebalanceFrequency', rebalFreq,...
    'LookbackWindow', lookback,...
    'TransactionCosts', transactionsFixed,...
    'InitialWeights', maxSharpeRatio_initial);

% Use variable transaction costs for the remaining strategies.
strat3 = backtestStrategy('Inverse Variance', @inverseVarianceFcn,...
    'RebalanceFrequency', rebalFreq,...
    'LookbackWindow', lookback,...
    'TransactionCosts', @variableTransactionCosts,...
    'InitialWeights', inverseVariance_initial);
strat4 = backtestStrategy('Markowitz Optimization', @markowitzFcn,...
    'RebalanceFrequency', rebalFreq,...
    'LookbackWindow', lookback,...
    'TransactionCosts', transactionsFixed,...
    'InitialWeights', markowitz_initial);
strat5 = backtestStrategy('Robust Optimization', @robustOptimFcn,...
    'RebalanceFrequency', rebalFreq,...
    'LookbackWindow', lookback,...
    'TransactionCosts', transactionsFixed,...
    'InitialWeights', robustOptim_initial);

% Aggregate the strategy objects into an array.
strategies = [strat1, strat2, strat3, strat4, strat5];

Backtest стратегии

Используйте следующий рабочий процесс для backtest стратегии с backtestEngine.

Задайте Engine Backtesting

backtestEngine функционируйте берет в качестве входа массив backtestStrategy объекты. Кроме того, при использовании backtestEngine, можно установить несколько опций, таких как безрисковый уровень и начальная стоимость портфеля. Когда безрисковый уровень задан в пересчитанных на год терминах, backtestEngine использование Basis свойство установить базу ежедневного расчета процентов. Для получения дополнительной информации о создании backtesting механизмы, смотрите backtestEngine.

% Risk-free rate is 1% annualized
annualRiskFreeRate = 0.01;

% Create the backtesting engine object
backtester = backtestEngine(strategies, 'RiskFreeRate', annualRiskFreeRate)
backtester = 
  backtestEngine with properties:

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

Запустите Backtest

Используйте runBacktest запускать backtest использование раздела тестовых данных. Используйте runBacktest аргумент пары "имя-значение" 'Start'избегать предварительного смещения (то есть, "видя будущее"). Начните backtest в конце периода "прогрева". Выполнение backtest заполняет пустые поля backtestEngine объект каждодневными результатами backtest.

backtester = runBacktest(backtester, pricesTT, 'Start', warmupPeriod)
backtester = 
  backtestEngine with properties:

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

Исследуйте результаты Backtest

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

summaryByStrategies = summary(backtester)
summaryByStrategies=9×5 table
                       Equal_Weighted    Max_Sharpe_Ratio    Inverse_Variance    Markowitz_Optimization    Robust_Optimization
                       ______________    ________________    ________________    ______________________    ___________________

    TotalReturn             0.18745            0.14991            0.15906                 0.17404                 0.15655     
    SharpeRatio             0.12559           0.092456            0.12179                 0.10339                 0.11442     
    Volatility            0.0063474          0.0070186          0.0055626               0.0072466               0.0058447     
    AverageTurnover      0.00087623          0.0065762          0.0028666               0.0058268               0.0025172     
    MaxTurnover            0.031251              0.239            0.09114                 0.21873                0.073746     
    AverageReturn        0.00083462         0.00068672          0.0007152              0.00078682              0.00070651     
    MaxDrawdown            0.072392           0.084768           0.054344                0.085544                0.064904     
    AverageBuyCost         0.047298             0.3449            0.15228                  0.3155                  0.1328     
    AverageSellCost        0.047298             0.3449            0.22842                  0.3155                  0.1328     

Подробные результаты backtest, включая ежедневную газету возвращается, положения актива и оборот, хранятся в свойствах backtestEngine объект.

Используйте equityCurve построить кривую акции для пяти различных инвестиционных стратегий.

equityCurve(backtester)

Figure contains an axes object. The axes object with title Equity Curve contains 5 objects of type line. These objects represent Equal Weighted, Max Sharpe Ratio, Inverse Variance, Markowitz Optimization, Robust Optimization.

Перемещение сводной таблицы, чтобы сделать графики определенных метрик может быть полезным.

% Transpose the summary table to plot the metrics.
summaryByMetrics = rows2vars(summaryByStrategies);
summaryByMetrics.Properties.VariableNames{1} = 'Strategy'
summaryByMetrics=5×10 table
             Strategy             TotalReturn    SharpeRatio    Volatility    AverageTurnover    MaxTurnover    AverageReturn    MaxDrawdown    AverageBuyCost    AverageSellCost
    __________________________    ___________    ___________    __________    _______________    ___________    _____________    ___________    ______________    _______________

    {'Equal_Weighted'        }      0.18745        0.12559      0.0063474       0.00087623        0.031251       0.00083462       0.072392         0.047298          0.047298    
    {'Max_Sharpe_Ratio'      }      0.14991       0.092456      0.0070186        0.0065762           0.239       0.00068672       0.084768           0.3449            0.3449    
    {'Inverse_Variance'      }      0.15906        0.12179      0.0055626        0.0028666         0.09114        0.0007152       0.054344          0.15228           0.22842    
    {'Markowitz_Optimization'}      0.17404        0.10339      0.0072466        0.0058268         0.21873       0.00078682       0.085544           0.3155            0.3155    
    {'Robust_Optimization'   }      0.15655        0.11442      0.0058447        0.0025172        0.073746       0.00070651       0.064904           0.1328            0.1328    

% Compare the strategy turnover.
names = [backtester.Strategies.Name];
nameLabels = strrep(names,'_',' ');
bar(summaryByMetrics.AverageTurnover)
title('Average Turnover')
ylabel('Daily Turnover (%)')
set(gca,'xticklabel',nameLabels)

Figure contains an axes object. The axes object with title Average Turnover contains an object of type bar.

Можно визуализировать изменение в выделениях стратегии в зависимости от времени с помощью диаграммы областей ежедневных положений актива. Для получения информации о assetAreaPlot функционируйте, смотрите раздел Local Functions.

strategyName = 'Max_Sharpe_Ratio';
assetAreaPlot (backtester, strategyName)

Figure contains an axes object. The axes object with title Max Sharpe Ratio Positions contains 16 objects of type area. These objects represent Cash, AA, CAT, DIS, GM, HPQ, JNJ, MCD, MMM, MO, MRK, MSFT, PFE, PG, T, XOM.

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

Стратегия, восстанавливающая равновесие функций, а также переменной функции операционных издержек, следует.

function new_weights = equalWeightFcn(current_weights, pricesTT)
% Equal-weighted portfolio allocation

nAssets = size(pricesTT, 2);
new_weights = ones(1,nAssets);
new_weights = new_weights / sum(new_weights);

end
function new_weights = maxSharpeRatioFcn(current_weights, pricesTT)
% Mean-variance portfolio allocation

nAssets = size(pricesTT, 2);
assetReturns = tick2ret(pricesTT);
% Max 25% into a single asset (including cash)
p = Portfolio('NumAssets',nAssets,...
    'LowerBound',0,'UpperBound',0.1,...
    'LowerBudget',1,'UpperBudget',1);
p = estimateAssetMoments(p, assetReturns{:,:});
new_weights = estimateMaxSharpeRatio(p);

end
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 new_weights = robustOptimFcn(current_weights, pricesTT) 
% Robust portfolio allocation

nAssets = size(pricesTT, 2);
assetReturns = tick2ret(pricesTT);

Q = cov(table2array(assetReturns));
SIGMAx = diag(diag(Q));

% Robust aversion coefficient
k = 1.1;

% Robust aversion coefficient
lambda = 0.05;

rPortfolio = mean(table2array(assetReturns))';

% Create the optimization problem
pRobust = optimproblem('Description','Robust Portfolio');

% Define the variables
% xRobust - x  allocation vector
xRobust = optimvar('x',nAssets,1,'Type','continuous','LowerBound',0.0,'UpperBound',0.1);
zRobust = optimvar('z','LowerBound',0);

% Define the budget constraint
pRobust.Constraints.budget = sum(xRobust) == 1;

% Define the robust constraint
pRobust.Constraints.robust = xRobust'*SIGMAx*xRobust - zRobust*zRobust <=0;
pRobust.Objective = -rPortfolio'*xRobust + k*zRobust + lambda*xRobust'*Q*xRobust;
x0.x = zeros(nAssets,1);
x0.z = 0;
opt = optimoptions('fmincon','Display','off');
[solRobust,~,~] = solve(pRobust,x0,'Options',opt);
new_weights = solRobust.x;

end
function new_weights = markowitzFcn(current_weights, pricesTT) 
% Robust portfolio allocation

nAssets = size(pricesTT, 2);
assetReturns = tick2ret(pricesTT);

Q = cov(table2array(assetReturns));

% Risk aversion coefficient
lambda = 0.05;

rPortfolio = mean(table2array(assetReturns))';

% Create the optimization problem
pMrkwtz = optimproblem('Description','Markowitz Mean Variance Portfolio ');

% Define the variables
% xRobust - x  allocation vector
xMrkwtz = optimvar('x',nAssets,1,'Type','continuous','LowerBound',0.0,'UpperBound',0.1);

% Define the budget constraint
pMrkwtz.Constraints.budget = sum(xMrkwtz) == 1;

% Define the Markowitz objective
pMrkwtz.Objective = -rPortfolio'*xMrkwtz + lambda*xMrkwtz'*Q*xMrkwtz;
x0.x = zeros(nAssets,1);

opt = optimoptions('quadprog','Display','off');
[solMrkwtz,~,~] = solve(pMrkwtz,x0,'Options',opt);
new_weights = solMrkwtz.x;

end
function [buy, sell] = variableTransactionCosts(deltaPositions)
% Variable transaction cost function
%
% This function is an example of how to compute variable transaction costs.
%
% Compute scaled transaction costs based on the change in market value of
% each asset after a rebalance.  Costs are computed at the following rates:
%
% Buys:
%   $0-$10,000 : 0.5%
%   $10,000+   : 0.35%
% Sells:
%   $0-$1,000  : 0.75%
%   $1,000+    : 0.5%

buy  = zeros(1,numel(deltaPositions));
sell = zeros(1,numel(deltaPositions));

% Buys
idx = 0 < deltaPositions & deltaPositions < 1e4;
buy(idx) = 0.005 * deltaPositions(idx); % 50 basis points
idx = 1e4 <= deltaPositions;
buy(idx) = 0.0035 * deltaPositions(idx); % 35 basis ponits
buy = sum(buy);

% Sells
idx = -1e3 < deltaPositions & deltaPositions < 0;
sell(idx) = 0.0075 * -deltaPositions(idx); % 75 basis points
idx = deltaPositions <= -1e3;
sell(idx) = 0.005 * -deltaPositions(idx); % 50 basis points
sell = sum(sell);

end
function assetAreaPlot(backtester,strategyName)
% Plot the asset allocation as an area plot.

t = backtester.Positions.(strategyName).Time;
positions = backtester.Positions.(strategyName).Variables;
h = area(t,positions);
title(sprintf('%s Positions',strrep(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

Смотрите также

| | |

Похожие темы