本例展示如何将数据分成训练集和测试集。我们将回测一个配对交易策略,在训练集上优化参数,在测试集上观察效果。
GLD代表黄金的现货价格,GDX是一揽子采金企业股票,两者的价格是高度相关的,故GLD和GDX可用于做配对交易。不过我要到第7章才会讲训练集上的协整分析,结果表明,GLD多头和GDX空头所形成的差价呈均值回归。通过训练集上的回归分析可得出GLD和GDX之间的对冲比率,并设定配对交易策略进出市场的阀值。从后面可以看到,阀值在训练集上的优化会改变策略在测试集上的业绩。(程序文件可从epchan. com/book/example3-6.m下载,数据文件为GLD. xls和GLD.xls。)程序使用滞后命令,将时间序列滞后一期,这在epchan.com/book中也能找到。还会使用“普通最小二乘法(OLS)”命令进行线性回归,在spatial-econometrics.com上可免费打包下载。
使用MATLAB
%清除工作空间已有变量
clear;
%将“GLD.xls”读入MATLAB
[num,txt]=xlsread('GLD');
%第一列(从第二行开始)是交易日,格式为mm/dd/yyyy
tdayl=txt(2:end,1);
%将时间格式转化为yyyymmdd
tdayl=…
datestr(datenum (tdayl,'mm/dd/yyyy'),'yyyymmdd');
%将数据字符串转化为单元型变量,再转化为数值型变量
tdayl =str2double(cellstr(tdayl));
%最后一列是调整后收盘价
adjclsl=num(:,end);
%读入“GDX. x1s”
[num,txt]=xlsread('GDX');
%第一列(从第二行开始)是交易日,格式为mm/dd/yyyy
tday2=txt(2:end,1);
%将时间格式转化为yyyymmdd
tday2=…
datestr(datenum (tday2,'mm/dd/yyyy'),'yyyymmdd');
%将数据字符串转化为单元型变量,再转化为数位型变量
tday2=str2double(cellstr(tday2));
%最后一列是调整后收盘价
adjcls2=num(:,end);
%找到两组数据的交集并按升序排列
[tday,idxl,idx2]=intersect(tdayl,tday2);
cll=adjclsl(idxl);
c12=adjcls2(idx2);
trainset=1:252;%定义训练集一标
%定义测评集下标
testset = trainset(end)+1:length(tday);
%用回归函数计葬得到对冲比率
results=ols(cll (trainset),c12(trainset));
hedgeRatio= results. beta;
%差价=GLD一对冲比率*GDX
spread=cll一hedgeRatio*c12;
plot(spread(trainset));
figure;
plot(spread(testset));
figure;
%训练集平均差价
spreadMean= mean(spread(trainset ));
%训练集差价标准差
spreadStd =std(spread(trainset));
%差价标准化(用z-scores方法)
zscore = (spread一spreadMean). /spreadStd;
%在组合价值向下跌破2倍标准差时,购买此差价组合
longs=zscore<=一2;
%当组合价值上升超过2倍标准差时,做空该差价组合
shorts=zscore>=2;
%当组合价值回到1倍标准差以内时,清仓
exits=abs(zscore) <=1;
写初始化头寸数组
positions=NaN(length Way),2);
%多头入市
positions(shorts,:)= repmat[一1 1],[length(find(shorts))1]);
%空头入市
positions(longs,:)=repmat([1一1],[length(find(longs))1]);
%清仓
positions(exits,:)=zeros(length(find(exits)),2);
%确保继续持仓,除非出现清仓信号
%positions= fillMissingData(positions);
cl=[cllc12];%合并两个价格序列
dailyret=(cl一lagl(cl))./lagl(cl);
pnl=sum(lagl(positions).*dailyret,2);
%训练集的夏普比应该足2.3
sharpeTrainset=…
sgrt(252)*mean(pnl(trainset(2:end)))./std(pnl(trainset(2:end)))
%测评集的夏普比应该是1.5
sharpeTestset =sgrt(252)*mean(pnl(testset))./std(pnl(testset))
plot(cumsum(pnl(testset )));
sharpeTestset = sqrt(252)*mean(pnl(testset))./std(pnl(testset))
plot(cumsum(pnl(testset)));
%保存头寸文件以便检查数据先窥偏差
save example3-6-positions positions;
文件lagl. m:
function y=lagl(x)
%y=lag(x)
if(isnumeric(x))
%第一个元素填充为NaN
y=[NaN(1,size(x,2));x(1:end一1,:)];elseif(ischar(x))
%第一个元素填充为”
Y=[repmat(””,[1 size(x,2)]);x(1:end一1,:)];else error('Can only be numeric or char array’);
End
此配对交易策略在训练集和测试集上的夏普比率都很高,因此可认为它是无数据迁就偏差的。但也许还有进一步改进的空间。若把建仓阀值改为1倍标准差、清仓阀值改为0.5倍标准差,训练集上的夏普比率会上升到2.9 ,测试集上的夏普比率会上升到2.1。显然,这一阀值集更佳。
不过,在训练集上进行参数优化也许会降低测试集上的业绩。这种情况下,应选择使得训练集和测试集上的业绩结果都较好(也许不是最好)的参数集。
我没有将交易成本考虑在内(下一节会讨论交易成本)。读者可以自己做练习。由于这一策略不是频繁交易,因此,交易成本对所得的夏普比率影响并不大。
为观察这一策略是如何工作的,读者可参见所显示的差价。你将看到差价走势呈现出很明显的均值回归。因此,不断地低买高卖是很管用的。
最后,还要检测任何可能的数据前视偏差。在上面的MATLAB代码“cl2=adjcls2 (idx2);”之后,添加以下代码。
%将最近的交易日数据移除掉
cutoff=60;%移除最近60天的数据
tday(end一cutoff+l:end,:)=[];
cll(end一cutoff +l:end,:)=[];
c12(end-cutoff+1:end,:)=[];
将以下代码放在上面MATLAB程序的结尾,取代“save example3_6_positions positions”。
%检测数据先窥偏差的第二步
oldoutput=load (’example3-6-positions');
oldoutput. positions(end一cutoff+1:end,:)=[];
if (any( positions~=oldoutput. positions))
fprintf(1.'Program has look-forward-bias!\n')
End
将新的代码保存为“example3 - 6 - 1. m”并运行,你会发现“Program has look-forward-bias”语句不会被打印出来,这说明算法通过测试了。