交易成本
没有考虑交易成本的回测业绩是不真实的。第2章谈到了佣金、流动性成本、机会成本、市场冲击及滑价等交易成本,并且举了几个如何在策略回测中处理交易成本的例子。一项高夏普比率的策略,在考虑交易成本后变得无利可图,这是完全可能的,见例3.7。
交易成本与均值回归模型
麻省理工学院的Amir Khandani和Andrew Lo提出了一个简单的均值回归模型(可参见web. mit.edu/alo/www/Papers/august07. pdf )。策略非常简单:买入前一交易日日收益最差的股票,卖空前一交易日日收益最好的股票。在不考虑交易成本的情况下,1995年以来,这一简单策略的业绩一直非常出色((2006年的夏普比率为4.47)。我们要研究的是,假定每笔交易成本为5个基点时,策略业绩如何变化。(此处的一笔交易是指一笔买入或一笔卖出,而不是一笔来回交易。)这个例子不仅向我们展示了交易成本时业绩的影响,也展示了交易多只股票时MATLAB在模型回测中的强大功能。用Excel对模型在若干年中的交易进行回测需要设置一大堆标签,十分不便。但使用MATLAB也会遇到一个问题,即如何在几百个数据标签中检索历史数据,尤其是无存活偏差的数据。此处暂不考虑存活偏差的问题,因为获取无偏差数据十分昂贵,我们只要记得,所得的预测业绩相对实际业绩被高股估。
对股票选择策略进行回测时,遇到的第一个问题永远是:哪些股票可用于回测?标准普尔500指数成分股通常是首选,因为它的流动性是最好的。标准普尔50指数的当前成分股可从标准普尔网站下载(www.standardandpoors. com)。由于成分股一直在变化,你下载的名单可能会与我的不同。你可在epchan. com/book/SP500 20071121.xls找到我的名单。下载所有这些股票的历史数据,最简使的方法是购买一份HQuotePro的拷贝(可在HQuote. com下载)。这个软件可以使你很方便地剪切粘贴所需数据的所有标签列。使用软件检索和更新2001年1月1日至今的所有数据,只选择日期、开盘价、最高价、最低价、收盘价和成交量,不含数据名称栏,保存为文件“Export. txt”。通过以下MATLAB程序将其处理为运算时可使用的数据,在本地目录下保存为二进制文件“SPX_20071123. mat”。
clear;
inputFile='Export. txt';
outputFile='SPX_20071123';
[mysym,mytday, myop, myhi,mylo,mycl,myvol]=…
textread(inputFile,’%s %u %f %f %f %f写u’,'delimiter',’,’);
%找到不重复的股票集合
stocks=unique(mysym);
%找到不重复的日期集合
tday=unique(mytday);
op= NaN(length(tday),length(stocks));
hi= NaN(length(tday),length(stocks));
lo= NaN(length(tday),length(stocks));
cl=NaN(length(tday),length(stocks));
vol= NaN(length(tday),length(stocks));
for s=1:length(stocks)
stk=stocks(s);
%找到当前股票的下标
idxA=strmatch(stk,mysym,’exact');
%找到当前日期集的下标
[foo, idxtA,idxtB] =intersect (mytday(idxA),tday);
%获取当前股票的价格
op(idxtB, s)=myop(idxA(idxtA));
hi(idxtB, s)=myhi(idxA(idxtA));
lo(idxtB,s)=mylo(idxA(idxtA));
cl(idxtB,s)=mycl(idxA(idxtA));
vol(idxtB, s)=myvol(idxA(idxtA));
end
save(outputFile,'tday','stocks',’op’,’ih’,'lo','cl','vol' );
接下来,用这些历史数据来回测不考虑交易成本的均值回归策略:
clear;
startDate=20060101;
endDate=20061231;
load('SPX 20071123’,'tday','stocks','cl');
%日收益率
dailyret=(cl一lagl(cl))./lagl(cl);
%等权重的市场指数收益率
marketDailyret=smartmean(dailyret,2);
%股票的权重与其离市场收益率的距离的负数成正比
weights=…
一(dailyret一repmat(marketDailyret,[1 size(dailyret,2)]))./
repmat(smartsum(isfinite(cl),2),[1 size(dailyret,2)]);
%删除没有有效价格或日收益率的股票
weights(~isfinite(cl)|~isfinite(lagl(cl)))=0;
dailypnl=smartsum(lagl(weights).*dailyret,2);
%删除考虑期外的盈亏
dailypnl(tday〈startDate|tday〉endDate)=[];
%夏普比率应该是0.25
sharpe=sgrt(252)*smartmean(dailypnl,1)/smartstd(dailypnl,1)
这个程序文件包括在epchan. com/hook/example3_7. m。注意,2006年的夏普比率只有0.25,而非原作者所报告的4.47。业绩的显著降低,是因为本例的回测使用的是标准普尔500指数所有成分股。知果读了作者的文章,你就会发现绝大部分收益来自小盘股和微盘股。
在上面的MATLAB程序中,我使用了三个新数“smartsum”,“smartmean”和“smartstd”,其功能与“sum”、“mean”、和“std”三个常用函数的功能相似,但能自动忽略数据中所有NaN元素。这些函数在进行回测时非常有用,因为股票的价格序列经常是断断续续的。这些文件在epchan.com/book都能找到。
“Smartsum.m”
function y = smartsum(x,dim)
%y=smartsum(x,dim)
%对dim维求和,忽略NaN
hasData=isfinite(x);
x(~hasData) =0;
y=sum(x,dim);
y(all(~ hasData,dim))NaN;
“smartmean.m”
function y=smartmean(x,dim)
%y=smartmean(x,dim)
%对dim维求均值,忽略NaN
hasData= isfinite(x);
x(~hasData)=0;
y=sum(x,dim)./sum(hasData,dim);
y(all(~hasData, dim))二NaN;%若y的所有元素都是NaN,则y取值为NaN
“smartstd. m”
function y=smartstd(x, dim)
%y=smartstd(x,dim)
%时dim雄求标准差,忽略NaN和Inf
hasData=isfinite(x);
x(~hasData)=0;
y=std(x);
y(all(-hasData,dim))=NaN;
现在考虑每笔交易成本为5个基点时的回测。
%减去交易成本后的盈亏
onewaytcost =0.0005;%假设交易成本为5个基点
%删除考虑期外的权重
weights(tday<start Date}tday>endDate,:)=[];
%权重变化时产生交易成本
dailypnlminustcost=…
dailypnl一smartsum(abs(weights一lagl(weights)),2), * onewaytcost;
%夏普比率应该为一3.19
sharpeminu stcost= sqrt (252 )*smartmean(dailypnlminustcost,1)/…smartstd(dailypnlminustcost,1)
现在,这一策略的业绩非常差!