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

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

  • Перекрестные соединения скользящего среднего значения

  • Сходимость/расхождение скользящего среднего значения

  • Относительный индекс силы

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

Даже при том, что технические индикаторы обычно не используются в качестве автономных торговых стратегий, этот пример использует эти стратегии продемонстрировать, как создать инвестиционные стратегии на основе данных сигнала, когда вы используете backtestEngine объект в MATLAB®.

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

Загрузите настроенные ценовые данные для 15 запасов в течение года 2006. Этот пример использует маленький набор подходящих для инвестирования активов для удобочитаемости.

Считайте таблицу ежедневных настроенных окончательных цен для 2006 запасов DJIA.

T = readtable('dowPortfolio.xlsx');

Для удобочитаемости используйте только 15 из 30 запасов компонента DJI.

symbols = ["AA","CAT","DIS","GM","HPQ","JNJ","MCD","MMM","MO","MRK","MSFT","PFE","PG","T","XOM"];

Сократите таблицу, чтобы содержать только даты и выбранные запасы.

timeColumn = "Dates";
T = T(:,[timeColumn symbols]);

Преобразуйте данные в расписание.

pricesTT = table2timetable(T,'RowTimes','Dates');

Просмотрите структуру ценового расписания.

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

Смотрите набор данных

Визуализируйте корреляцию и совокупный доход каждого запаса в наборе данных.

% Visualize the correlation between the 15 stocks.
returns = tick2ret(pricesTT);
stockCorr = corr(returns.Variables);
heatmap(symbols,symbols,stockCorr,'Colormap',parula);

% Visualize the performance of each stock over the range of price data.
totalRet = ret2tick(returns);
plot(totalRet.Dates,totalRet.Variables);
legend(symbols,'Location','NW');
title('Growth of $1 for Each Stock')
ylabel('$')

% Get the total return of each stock for the duration of the data set.
totalRet(end,:)
ans=1×15 timetable
       Dates         AA       CAT       DIS        GM       HPQ       JNJ       MCD       MMM        MO       MRK       MSFT      PFE        PG        T        XOM 
    ___________    ______    ______    ______    ______    ______    ______    ______    ______    ______    ______    ______    ______    ______    ______    _____

    29-Dec-2006    1.0254    1.0781    1.4173    1.6852    1.4451    1.0965    1.3548    1.0087    1.1946    1.3856    1.1287    1.1304    1.1164    1.5181    1.336

Создайте таблицу сигнала

В дополнение к историческим настроенным ценам активов backtesting среда позволяет вам опционально задавать данные сигнала при выполнении backtest. Задайте данные сигнала похожим способом как цены при помощи MATLAB® timetable. Размерность "времени" расписания сигнала должна совпадать с размерностью ценового расписания — то есть, строки каждой таблицы должны иметь соответствие со значениями datetime для Time столбец.

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

  • Простое перекрестное соединение скользящего среднего значения (SMA) стратегия

  • Сходимость Скользящего среднего значения / Расхождение (MACD) стратегия

  • Стратегия Относительного индекса силы (RSI)

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

SMA: простое перекрестное соединение скользящего среднего значения

Индикатор SMA использует 5-дневные и 20-дневные простые скользящие средние значения, чтобы сделать, покупают и продают решения. Когда 5-дневный SMA пересекает 20-дневный SMA (перемещающийся вверх), затем запас куплен. Когда 5-дневный SMA пересекается ниже 20-дневного SMA, акции проданы.

% Create SMA timetables using the movavg function.
sma5  = movavg(pricesTT,'simple',5);
sma20 = movavg(pricesTT,'simple',20);

Создайте расписание сигнала индикатора SMA.

smaSignalNameEnding = '_SMA5over20';

smaSignal = timetable;
for i = 1:numel(symbols)
    symi = symbols(i);
    % Build a timetable for each symbol, then aggregate them together.
    smaSignali = timetable(pricesTT.Dates,...
        double(sma5.(symi) > sma20.(symi)),...
        'VariableNames',{sprintf('%s%s',symi,smaSignalNameEnding)});
    % Use the synchronize function to merge the timetables together.
    smaSignal = synchronize(smaSignal,smaSignali);
end

Расписание сигнала SMA содержит индикатор со значением 1 когда 5-дневное скользящее среднее значение выше 20-дневного скользящего среднего значения для каждого актива и 0 в противном случае. Имена столбцов для каждого индикатора запаса являются [символом запаса] SMA5over20. backtestStrategy объект принимает торговые решения на основе этих перекрестных событий.

Просмотрите структуру расписания сигнала SMA.

head(smaSignal)
ans=8×15 timetable
       Time        AA_SMA5over20    CAT_SMA5over20    DIS_SMA5over20    GM_SMA5over20    HPQ_SMA5over20    JNJ_SMA5over20    MCD_SMA5over20    MMM_SMA5over20    MO_SMA5over20    MRK_SMA5over20    MSFT_SMA5over20    PFE_SMA5over20    PG_SMA5over20    T_SMA5over20    XOM_SMA5over20
    ___________    _____________    ______________    ______________    _____________    ______________    ______________    ______________    ______________    _____________    ______________    _______________    ______________    _____________    ____________    ______________

    03-Jan-2006          0                0                 0                 0                0                 0                 0                 0                 0                0                  0                 0                 0               0                0       
    04-Jan-2006          0                0                 0                 0                0                 0                 0                 0                 0                0                  0                 0                 0               0                0       
    05-Jan-2006          0                0                 0                 0                0                 0                 0                 0                 0                0                  0                 0                 0               0                0       
    06-Jan-2006          0                0                 0                 0                0                 0                 0                 0                 0                0                  0                 0                 0               0                0       
    09-Jan-2006          0                0                 0                 0                0                 0                 0                 0                 0                0                  0                 0                 0               0                0       
    10-Jan-2006          1                1                 1                 1                1                 1                 1                 0                 1                1                  1                 1                 1               1                1       
    11-Jan-2006          0                1                 1                 1                1                 1                 1                 0                 1                1                  1                 1                 1               1                1       
    12-Jan-2006          0                1                 1                 1                1                 1                 1                 0                 1                1                  1                 1                 1               1                1       

Постройте сигнал для одного актива, чтобы предварительно просмотреть торговую частоту.

plot(smaSignal.Time,smaSignal.CAT_SMA5over20);
ylim([-0.5, 1.5]);
ylabel('SMA 5 > SMA 20');
title(sprintf('SMA 5 over 20 for CAT'));

MACD: сходимость/Расхождение скользящего среднего значения

Можно использовать метрику MACD во множестве путей. Часто, MACD сравнивается со своим собственным экспоненциальным скользящим средним, но для этого примера, MACD служит триггером для сигнала покупки, когда MACD повышается выше 0. Положение продается, когда MACD отступает ниже 0.

% Create a timetable of the MACD metric using the MACD function.
macdTT = macd(pricesTT);

Создайте расписание сигнала индикатора MACD.

macdSignalNameEnding = '_MACD';

macdSignal = timetable;
for i = 1:numel(symbols)
    symi = symbols(i);
    % Build a timetable for each symbol, then aggregate the symbols together.
    macdSignali = timetable(pricesTT.Dates,...
        double(macdTT.(symi) > 0),...
        'VariableNames',{sprintf('%s%s',symi,macdSignalNameEnding)});
    macdSignal = synchronize(macdSignal,macdSignali);
end

Таблица сигнала MACD содержит столбец для каждого актива с именем [символ запаса] MACD. Каждый сигнал имеет значение 1 когда MACD запаса выше 0. Сигнал имеет значение 0 когда MACD запаса падает ниже 0.

head(macdSignal)
ans=8×15 timetable
       Time        AA_MACD    CAT_MACD    DIS_MACD    GM_MACD    HPQ_MACD    JNJ_MACD    MCD_MACD    MMM_MACD    MO_MACD    MRK_MACD    MSFT_MACD    PFE_MACD    PG_MACD    T_MACD    XOM_MACD
    ___________    _______    ________    ________    _______    ________    ________    ________    ________    _______    ________    _________    ________    _______    ______    ________

    03-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    
    04-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    
    05-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    
    06-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    
    09-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    
    10-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    
    11-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    
    12-Jan-2006       0          0           0           0          0           0           0           0           0          0            0           0           0         0          0    

Подобно SMA постройте сигнал для одного актива, чтобы предварительно просмотреть торговую частоту.

plot(macdSignal.Time,macdSignal.CAT_MACD)
ylim([-0.5, 1.5]);
ylabel('MACD > 0');
title(sprintf('MACD > 0 for CAT'));

RSI: относительный индекс силы

RSI является метрикой, чтобы получить импульс. Общая эвристика должна купить, когда RSI падает ниже 30 и продать, когда RSI повышается выше 70.

rsiSignalNameEnding = '_RSI';

rsiSignal = timetable;
for i = 1:numel(symbols)
    symi = symbols(i);
    rsiValues = rsindex(pricesTT.(symi));
    rsiBuySell = zeros(size(rsiValues));
    rsiBuySell(rsiValues < 30) = 1;
    rsiBuySell(rsiValues > 70) = -1;
    % Build a timetable for each symbol, then aggregate the symbols together.
    rsiSignali = timetable(pricesTT.Dates,...
        rsiBuySell,...
        'VariableNames',{sprintf('%s%s',symi,rsiSignalNameEnding)});
    rsiSignal = synchronize(rsiSignal,rsiSignali);
end

Сигнал RSI принимает значение 1 (указание на сигнал покупки), когда значение RSI для запаса падает ниже 30. Сигнал принимает значение -1 (указание на сигнал продажи), когда RSI для запаса повышается выше 70. В противном случае сигнал принимает значение 0, указание ни на какое действие.

Постройте сигнал для одного актива, чтобы предварительно просмотреть торговую частоту.

plot(rsiSignal.Time,rsiSignal.CAT_RSI)
ylim([-1.5, 1.5]);
ylabel('RSI Buy/Sell Signal');
title(sprintf('RSI Buy/Sell Signal for CAT'));

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

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

Сигналы требуют, чтобы достаточные запаздывающие данные вычислили торговые сигналы (например, вычисляя SMA20 в течение дня X требует цен с этих 20 дней до дня X). Все запаздывающие данные собраны в предварительно вычисленных торговых сигналах. Таким образом, для фактических стратегий нужно только 2-дневное lookback окно, чтобы принять торговые решения оценить когда крест сигналов торговые пороги.

Все стратегии платят операционные издержки на 25 пунктов на покупках, и продает.

Начальные веса вычисляются на основе значений сигналов после 20 торговых дней. backtest начинается после этого 20-дневного периода инициализации.

tradingCosts = 0.0025;

% Use the crossoverRebalanceFunction for both the SMA
% strategy as well as the MACD strategy.  This is because they both trade
% on their respective signals in the same way (buy when signal goes from
% 0->1, sell when signal goes from 1->0).  Build an anonymous
% function for the rebalance functions of the strategies that calls the
% shared crossoverRebalanceFcn() with the appropriate signal name string
% for each strategy.

% Each anonymous function takes the current weights (w), prices (p), 
% and signal (s) data from the backtest engine and passes it to the
% crossoverRebalanceFcn function with the signal name string.
smaInitWeights = computeInitialWeights(smaSignal(20,:));
smaRebalanceFcn = @(w,p,s) crossoverRebalanceFcn(w,p,s,smaSignalNameEnding);
smaStrategy = backtestStrategy('SMA',smaRebalanceFcn,...
    'TransactionCosts',tradingCosts,...
    'LookbackWindow',2,...
    'InitialWeights',smaInitWeights);

macdInitWeights = computeInitialWeights(macdSignal(20,:));
macdRebalanceFcn = @(w,p,s) crossoverRebalanceFcn(w,p,s,macdSignalNameEnding);
macdStrategy = backtestStrategy('MACD',macdRebalanceFcn,...
    'TransactionCosts',tradingCosts,...
    'LookbackWindow',2,...
    'InitialWeights',macdInitWeights);

% The RSI strategy uses its signal differently, buying on a 0->1
% transition and selling on a 0->-1 transition.  This logic is captured in
% the rsiRebalanceFcn dunction defined in the Local Functions section.
rsiInitWeights = computeInitialWeights(rsiSignal(20,:));
rsiStrategy = backtestStrategy('RSI',@rsiRebalanceFcn,...
    'TransactionCosts',tradingCosts,...
    'LookbackWindow',2,...
    'InitialWeights',rsiInitWeights);

Настройте Backtest

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

% The equal weight strategy requires no history, so set LookbackWindow to 0.
benchmarkStrategy = backtestStrategy('Benchmark',@equalWeightFcn,...
    'TransactionCosts',tradingCosts,...
    'RebalanceFrequency',20,...
    'LookbackWindow',0);

Агрегат каждое из отдельных расписаний сигнала в один backtest сигнализирует о расписании.

% Combine the three signal timetables.
signalTT = timetable;
signalTT = synchronize(signalTT, smaSignal);
signalTT = synchronize(signalTT, macdSignal);
signalTT = synchronize(signalTT, rsiSignal);

Используйте backtestEngine создать backtesting механизм и затем использовать runBacktest запускать backtest. Безрисковый уровень, заработанный на неинвестированных наличных деньгах, составляет пересчитанный на год 1% (который является 0.01/252 для ежедневных данных).

% Put the benchmark strategy and three signal strategies into an array.
strategies = [benchmarkStrategy smaStrategy macdStrategy rsiStrategy];
% Create the backtesting engine.
bt = backtestEngine(strategies,'RiskFreeRate',0.01 / 252)
bt = 
  backtestEngine with properties:

               Strategies: [1x4 backtestStrategy]
             RiskFreeRate: 3.9683e-05
           CashBorrowRate: 0
    InitialPortfolioValue: 10000
                NumAssets: []
                  Returns: []
                Positions: []
                 Turnover: []
                  BuyCost: []
                 SellCost: []

Стратегии Backtest

% Start with the end of the initial weights calculation warm-up period.
startIdx = 20;

% Run the backtest.
bt = runBacktest(bt,pricesTT,signalTT,'Start',startIdx);

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

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

% The plot equity curve helper function is implemented in the Local Functions section.
plotEquityCurve(bt);

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

strategyName = 'Benchmark';
assetAreaPlot (купленный, strategyName)

Заключение

Широкий фондовый рынок имел очень бычьи 6 месяцы во второй половине из 2 006 и всех трех из этих стратегий, отказавших, чтобы полностью получить тот рост путем отъезда слишком большого количества капитала наличными. В то время как ни одна из этих стратегий, выполняемых хорошо самостоятельно, этот пример, не демонстрирует, как можно создать основанные на сигнале торговые стратегии и backtest их, чтобы оценить их эффективность.

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

Начальная функция вычисления веса, а также стратегия, восстанавливающая равновесие функций, следует.

function initial_weights = computeInitialWeights(signals)
% Compute initial weights based on most recent signal.

nAssets = size(signals,2);
final_signal = signals{end,:};
buys = final_signal == 1;
initial_weights = zeros(1,nAssets);
initial_weights(buys) = 1 / nAssets;

end
function new_weights = crossoverRebalanceFcn(current_weights, pricesTT, signalTT, signalNameEnding)
% Signal crossover rebalance function.

% Build cell array of signal names that correspond to the crossover signals.
symbols = pricesTT.Properties.VariableNames;
signalNames = cellfun(@(s) sprintf('%s%s',s,signalNameEnding), symbols, 'UniformOutput', false);

% Pull out the relevant signal data for the strategy.
crossoverSignals = signalTT(:,signalNames);

% Start with our current weights.
new_weights = current_weights;

% Sell any existing long position where the signal has turned to 0.
idx = crossoverSignals{end,:} == 0;
new_weights(idx) = 0;

% Find the new crossovers (signal changed from 0 to 1).
idx = crossoverSignals{end,:} == 1 & crossoverSignals{end-1,:} == 0;

% Bet sizing, split available capital across all remaining assets, and then
% invest only in the new positive crossover assets.  This leaves some
% proportional amount of capital uninvested for future investments into the
% zero-weight assets.
availableCapital = 1 - sum(new_weights);
uninvestedAssets = sum(new_weights == 0);
new_weights(idx) = availableCapital / uninvestedAssets;

end
function new_weights = rsiRebalanceFcn(current_weights, pricesTT, signalTT)
% Buy and sell on 1 and -1 rebalance function.

signalNameEnding = '_RSI';

% Build cell array of signal names that correspond to the crossover signals.
symbols = pricesTT.Properties.VariableNames;
signalNames = cellfun(@(s) sprintf('%s%s',s,signalNameEnding), symbols, 'UniformOutput', false);

% Pull out the relevant signal data for the strategy.
buySellSignals = signalTT(:,signalNames);

% Start with the current weights.
new_weights = current_weights;

% Sell any existing long position where the signal has turned to -1.
idx = buySellSignals{end,:} == -1;
new_weights(idx) = 0;

% Find the new buys (signal is 1 and weights are currently 0).
idx = new_weights == 0 & buySellSignals{end,:} == 1;

% Bet sizing, split available capital across all remaining assets, and then
% invest only in the new positive crossover assets.  This leaves some
% proportional amount of capital uninvested for future investments into the
% zero-weight assets.
availableCapital = 1 - sum(new_weights);
uninvestedAssets = sum(new_weights == 0);
new_weights(idx) = availableCapital / uninvestedAssets;

end
function new_weights = equalWeightFcn(current_weights,~)
% Equal-weighted portfolio allocation.

nAssets = numel(current_weights);
new_weights = ones(1,nAssets);
new_weights = new_weights / sum(new_weights);

end
function plotEquityCurve(bt)

% Get total portfolio values using ret2tick.
dailyReturns = bt.Returns;
portfolioValues = ret2tick(dailyReturns,'StartPrice',bt.InitialPortfolioValue,'method','simple');
plot(portfolioValues.Time, portfolioValues.Variables);
datetick('x','mm-yy','keeplimits','keepticks');
xlabel('Time');
ylabel('Portfolio Value ($)');
title('Equity Curves')
% Remove the underscores in the strategy names for labeling.
names = {bt.Strategies.Name};
nameLabels = cellfun(@(n) strrep(n,'_',' '),names,'UniformOutput',false);
legend(nameLabels,'Location','best')
grid on

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

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

| | |

Похожие темы

Для просмотра документации необходимо авторизоваться на сайте