这个例子展示了如何对交易策略中包含投资信号的投资组合策略进行回溯测试。这个词信号包括策略作者需要在资产价格历史之外做出的交易决策的任何信息。这些信息可以包括技术指标、机器学习模型的输出、情感数据、宏观经济数据等。本例使用了三种基于衍生信号数据的简单投资策略:
移动平均线跨界车
移动平均收敛/散度
相对强弱指标
在本例中,您可以使用这些策略对一年的股票数据进行回溯测试。然后分析结果,比较每种策略的性能。
即使技术指标通常不作为独立的交易策略使用,这个例子使用这些策略来演示如何建立基于信号数据的投资策略,当你使用backtestEngine
在MATLAB®对象。
输入15只股票2006年的调整后价格数据。这个例子使用了一小组可投资资产来提高可读性。
阅读2006年道琼斯指数股票的每日调整收盘价表。
T = readtable (“dowPortfolio.xlsx”);
为了便于阅读,只使用30个DJI组件股票中的15个。
符号= [“AA”,“猫”,“说”,“通用汽车”,“hp”,“公司”,“力”,“嗯”,“莫”,“期望”,“微软”,“工业”,“PG”,“T”,“XOM”];
修剪表,只保存日期和选定的股票。
timeColumn =“日期”;T = T(:,[timeccolumn symbols]);
将数据转换为时间表。
pricesTT = table2timetable (T)“RowTimes”,“日期”);
查看结构的价格时间表。
头(pricesTT)
ans =8×15时间表Dates AA Cat Dis GM HPQ JNJ MCD MMM Mo MRK MSFT PFE PG T XOM ________________ ________________ ____________________________________________________________________________________________________________________________M25.86 27.08 32.72 2835 59.08 32.72 2835 59.08 32.72 75.93 52.7 30.73 26.19 22.16 56.3822.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 - 2006年29.02 58.43 24.5219.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.4523.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
可视化数据集中每只股票的相关性和总回报。
想象一下这15只股票之间的相关性。回报= tick2ret (pricesTT);(returns.Variables stockCorr = corr);热图(符号、符号、stockCorr“Colormap”, parula);
在价格数据范围内可视化每只股票的表现。totalRet = ret2tick(回报);情节(totalRet.Dates totalRet.Variables);传奇(符号,“位置”,“西北”);标题(“每股增长1美元”) ylabel (“美元”)
获取数据集持续时间内每只股票的总回报率。totalRet(最终,:)
ans =1×15时间表日期AA猫说通用hp JNJ MCD嗯莫merck microsoft pfizer PG T XOM ___________ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ _____ 29日- 12月- 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
除了历史调整后的资产价格,回溯测试框架还允许您选择性地指定信号运行回测时的数据。使用MATLAB®以类似于价格的方式指定信号数据时间表
.“时间”维度信号时间表必须与价格的,即每个表的行必须具有匹配的datetime值时间
列。
这个例子构建了一个信号时间表来支持这三种投资策略:金宝app
简单的移动平均交叉(SMA)策略
移动均线趋同/背离策略
相对强弱指数(RSI)策略
每个策略都有一个预先计算的信号时间表。在运行回测之前,将三个单独的信号时间表合并为一个聚合信号时间表,用于回测。
SMA指标使用5天和20天的简单移动平均线来做买入和卖出的决定。当5日移动平均线越过20日移动平均线(向上移动)时,股票被买入。当5日移动平均线穿过20日移动平均线时,该股票被卖出。
%使用movavg函数创建SMA时间表。sma5 = movavg (pricesTT,“简单”5);sma20 = movavg (pricesTT,“简单”, 20);
创建SMA指示信号时间表。
smaSignalNameEnding =“_SMA5over20”;smaSignal =时间表;为I = 1:numel(symbols) symi = symbols(I);为每个符号建立一个时间表,然后将它们聚集在一起。(pricesTT smaSignali =时间表。日期,...双(sma5。(symi) > sma20。(symi)),...“VariableNames”{sprintf (' % s % s '、symi smaSignalNameEnding)});%使用synchronize函数将时间表合并到一起。smaSignal =同步(smaSignal smaSignali);结束
SMA信号时间表包含一个值为的指示器1
当每项资产的5天移动平均线高于20天移动平均线时,a0
否则。每个股票指标的列名为[股票symbol]SMA5over20
.的backtestStrategy
Object基于这些交叉事件做出交易决策。
查看SMA信号时间表的结构。
头(smaSignal)
ans =8×15时间表时间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 - 1月- 2006 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04 - 1月- 2006 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 05 - 1月- 2006 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 06 - 1月- 2006 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 09 - 1月- 2006年1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 10 - 1月- 2006年1 1 1 1 1 110111111 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
绘制单个资产的信号以预览交易频率。
情节(smaSignal.Time smaSignal.CAT_SMA5over20);ylim ([-0.5, 1.5]);ylabel (' sma 5 > sma 20');标题(sprintf (SMA 5 over 20 for CAT));
你可以用多种方式使用MACD指标。通常,MACD与其自身的指数移动均线相比较,但在本例中,MACD在MACD高于该水平时作为买入信号的触发点0
.当MACD指标回落至该水平以下时,卖出该头寸0
.
%使用MACD函数创建MACD指标的时间表。macdTT = macd (pricesTT);
创建MACD指标信号时间表。
macdSignalNameEnding =“_MACD”;macdSignal =时间表;为I = 1:numel(symbols) symi = symbols(I);为每个符号建立一个时间表,然后将这些符号汇总在一起。(pricesTT macdSignali =时间表。日期,...双(macdTT。(symi) > 0),...“VariableNames”{sprintf (' % s % s '、symi macdSignalNameEnding)});macdSignal =同步(macdSignal macdSignali);结束
MACD信号表包含每个资产的一列,列的名称为[股票代码]MACD
.每个信号都有一个值1
当MACD高于该股票时0
.信号的值为0
当MACD指数跌至以下时0
.
头(macdSignal)
ans =8×15时间表时间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 - 1月- 2006 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04 -简- 2006 0 0 0 0 0 0 0 0 0 00000005-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,绘制单个资产的信号以预览交易频率。
情节(macdSignal.Time macdSignal.CAT_MACD) ylim ([-0.5, 1.5]);ylabel (MACD > 0的);标题(sprintf ('MACD > 0 for CAT'));
RSI是捕捉动量的度量。一个常见的启发是当相对强弱指数低于时购买30.
并在相对强弱指数上升时卖出70
.
rsiSignalNameEnding ='_rsi';rsiSignal =时间表;为I = 1:numel(symbols) symi = symbols(I);rsiValues = rsindex (pricesTT。(symi));rsiBuySell = 0(大小(rsiValues));rsiBuySell(rsiValues < 30) = 1;rsiBuySell(rsiValues > 70) = -1; / /买入为每个符号建立一个时间表,然后将这些符号汇总在一起。(pricesTT rsiSignali =时间表。日期,...rsiBuySell,...“VariableNames”{sprintf (' % s % s '、symi rsiSignalNameEnding)});rsiSignal =同步(rsiSignal rsiSignali);结束
RSI信号的值为1
(表示买入信号)当股票的RSI值低于该值时30.
.信号的值为-1
(表示卖出信号)当股票的相对强弱指数高于该值时70
.否则,信号的值为0
,表示没有动作。
绘制单个资产的信号以预览交易频率。
情节(rsiSignal.Time rsiSignal.CAT_RSI) ylim ([-1.5, 1.5]);ylabel (“RSI买入/卖出信号”);标题(sprintf (CAT的RSI买入/卖出信号));
建立战略backtestStrategy
对象中定义的rebalance函数本地函数部分。每种策略都使用再平衡函数,根据适当的信号做出交易决定。
信号需要足够的跟踪数据来计算交易信号(例如,计算SMA20
为一天X要求价格从20天前的一天X).所有的跟踪数据都被捕捉到预先计算的交易信号中。因此,实际的策略只需要一个2天的回顾窗口来做出交易决策,以评估信号何时越过交易阈值。
所有的策略在买卖时都要支付25个基点的交易成本。
初始权值是根据20个交易日后的信号值计算的。回溯测试在20天初始化周期之后开始。
tradingCosts = 0.0025;对两个SMA都使用crossoverRebalanceFunction%策略以及MACD策略。这是因为他们都在交易%在他们各自的信号相同的方式(当信号从% 0->1,当信号从1->0上升时卖出)。建立一个匿名方法调用的策略的rebalance函数% shared crossoverRebalanceFcn() with适当的信号名称字符串每个策略的%。每个匿名函数取当前权重(w),价格(p),%和信号(s)数据,并将其传递给带有信号名称字符串的% crosoverrebalancefcn函数。: smaInitWeights = computeInitialWeights (smaSignal(20日));smaRebalanceFcn = @(w,p,s) crossoverRebalanceFcn(w,p,s,smaSignalNameEnding); / /设置smaRebalanceFcn = @(w,p,s);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);RSI策略使用不同的信号,在0->1买入%过渡和卖出在0->-1过渡。这个逻辑被捕获%在本地函数部分定义的rsiRebalanceFcn函数。: rsiInitWeights = computeInitialWeights (rsiSignal(20日));rsiStrategy = backtestStrategy (“肢体重复性劳损症”@rsiRebalanceFcn,...“TransactionCosts”tradingCosts,...“LookbackWindow”2,...“InitialWeights”, rsiInitWeights);
作为一个基准,这个例子还运行了一个简单的等权重策略,以确定交易信号是否为未来资产的回报提供了有价值的见解。基准策略每四周重新调整一次。
%相等权重策略不需要历史记录,因此将LookbackWindow设置为0。benchmarkStrategy = backtestStrategy (“基准”@equalWeightFcn,...“TransactionCosts”tradingCosts,...“RebalanceFrequency”, 20岁,...“LookbackWindow”, 0);
将每个单独的信号时间表聚合成一个单一的回测信号时间表。
结合三个信号时间表。signalTT =时间表;signalTT = synchronize(signalTT, smsignal); / /同步signalTT = synchronize(signalTT, macdSignal); / /同步signalTT = synchronize(signalTT, rssignal); / /同步
使用backtestEngine
来创建后台测试引擎,然后使用runBacktest
运行回测。未投资现金的无风险收益率年化为1%(日数据为0.01/252)。
%将基准策略和三个信号策略放入一个数组中。=[基准策略smaStrategy macdStrategy rsiStrategy];%创建后台测试引擎。bt = backtestEngine(策略,“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: []
%在初始权重计算热身期结束时开始。startIdx = 20;%运行backtest。bt = runBacktest (bt、pricesTT signalTT,“开始”, startIdx);
绘制策略股票曲线,以可视化他们在回测中的表现。
plot equity curve helper函数在Local Functions部分实现。plotEquityCurve (bt);
如前所述,这些策略通常不是作为单独的交易信号使用的。事实上,这三种策略在2006年的表现要比简单的基准策略差。您可以使用每日资产头寸的区域图来可视化策略配置如何随时间变化。要做到这一点,使用assetAreaPlot
辅助函数,在本地函数部分。
strategyName =“基准”;strategyName assetAreaPlot (bt)
2006年下半年,整个股市出现了6个月的牛市行情,而上述三种策略都未能充分抓住这种增长,因为它们留下了太多的现金。虽然这些策略中没有一个单独表现良好,但本例演示了如何构建基于信号的交易策略,并对其进行回测以评估其表现。
接下来是初始权重计算函数以及策略再平衡函数。
函数initial_weights = computeInitialWeights(信号)%根据最近的信号计算初始权值。nAssets =大小(信号,2);final_signal ={结束,:}信号;买盘= final_signal == 1;nAssets initial_weights = 0 (1);initial_weights(buys) = 1 / nAssets;结束
函数new_weights = cross - verrebalancefcn (current_weights, pricesTT, signalTT, signalNameEnding)%信号交叉平衡功能。%构建与交叉信号对应的信号名称单元格数组。符号= pricesTT.Properties.VariableNames;= cellfun(@(s) sprintf()' % s % s '年代,signalNameEnding),符号,“UniformOutput”、假);%拿出策略的相关信号数据。crossoverSignals = signalTT (:, signalNames);%从我们当前的权重开始。new_weights = current_weights;卖出任何信号变为0的现有多头仓位。idx = cross cross signals {end,:} == 0;new_weights (idx) = 0;找到新的交叉路口(信号从0变到1)。idx = cross verssignals {end,:} == 1 & cross verssignals {end-1,:} == 0;%打赌大小,分割可用资本在所有剩余资产,然后%只投资于新的积极的交叉资产。这使得一些未予投资之资金量,以供日后投资%零权资产。availableCapital = 1 - sum(new_weights);uninvestedAssets = sum(new_weights == 0);new_weights(idx) = availableCapital / uninvestedAssets;结束
函数new_weights = rsiRebalanceFcn(current_weights, pricesTT, signalTT)%买卖在1和-1平衡功能。signalNameEnding ='_rsi';%构建与交叉信号对应的信号名称单元格数组。符号= pricesTT.Properties.VariableNames;= cellfun(@(s) sprintf()' % s % s '年代,signalNameEnding),符号,“UniformOutput”、假);%拿出策略的相关信号数据。buySellSignals = signalTT (:, signalNames);%从当前权重开始。new_weights = current_weights;卖出任何信号变为-1的现有多头仓位。idx = buySellSignals{end,:} == -1;new_weights (idx) = 0;%找到新的购买(信号为1,当前权重为0)。idx = new_weights == 0 & buySellSignals{end,:} == 1;%打赌大小,分割可用资本在所有剩余资产,然后%只投资于新的积极的交叉资产。这使得一些未予投资之资金量,以供日后投资%零权资产。availableCapital = 1 - sum(new_weights);uninvestedAssets = sum(new_weights == 0);new_weights(idx) = availableCapital / uninvestedAssets;结束
函数new_weights = equalWeightFcn (current_weights ~)%等加权投资组合配置。nAssets =元素个数(current_weights);nAssets new_weights = 1 (1);New_weights = New_weights / sum(New_weights);结束
函数plotEquityCurve (bt)%使用ret2tick获得投资组合的总价值。dailyReturns = bt.Returns;portfolioValues = ret2tick (dailyReturns,“StartPrice”,英国电信。InitialPortfolioValue,“方法”,“简单”);情节(portfolioValues。时间,portfolioValues.Variables); datetick(“x”,“mm-yy”,“keeplimits”,“keepticks”);包含(“时间”);ylabel (“投资组合价值(美元)”);标题('股权曲线')%删除标记策略名称中的下划线。名称= {bt.Strategies.Name};@(n) strrep(n, n)“_”,' '),名字,“UniformOutput”、假);传奇(nameLabels“位置”,“最佳”网格)在结束
函数strategyName assetAreaPlot (val)%绘制资产分配面积图。t = backtester.Positions。(strategyName) .Time;位置= backtester.Positions。(strategyName) .Variables;h =区域(t,职位);标题(sprintf (“% s的立场”strrep (strategyName“_”,' ')));包含(“日期”);ylabel (“资产头寸”);datetick (“x”,“mm / dd”,“keepticks”);Xlim ([t(1) t(end)]) oldylim = ylim;ylim ([0 oldylim (2)]);厘米= parula(元素个数(h));为I = 1:numel(h) set(h(I),“FaceColor”厘米(我:));结束传奇(backtester.Positions (strategyName) .Properties.VariableNames)。结束
backtestEngine
|backtestStrategy
|runBacktest
|总结