下面的例子来源书籍
策略比较简单,使用N只股票构建投资组合,计算每只股票的过去LookBack(参数)日的动量,并作标准化处理作为股票权重,持有Holding(参数)日,其中权重允许为负数,即允许做空股票。简要说来就是做过过去表现强势的股票,做空过去表现弱势的股票。
虽然策略比较简单,但整体的测试流程和框架很明了清晰,对类似策略的回测实现具有参考意义,整体的流程框架为:
数据获取(基于付费或者免费的数据源)——》
数据的时间轴对齐以及缺失数据填充——》
子函数编写:特定回顾期的动量计算并作标准化——》
子函数编写:给定回顾期和持有期,组合的夏普比例计算——》
计算不同回顾期和持有期下组合的夏普比例值——》
进行策略参数分布图形展示
一、Python下的实现测试。
(1)主要用到numpy、pandas、matplotlib等几个包
# -*- coding: utf-8 -*-
"""
SimpleMomentumPortfolioTest
Created on 2015/05/01
@author: LiYang(faruto)
@group : FQuantStudio
@contact:
farutoliyang@foxmail.com
"""
#%% import
import numpy as np
import pandas as pd
import pandas.io.data as
web
import matplotlib.pylab
as plt
from pandas import
Series,DataFrame
from collections import
defaultdict
(2)数据的获取基于pandas包
#%% GetData
names = ['AAPL',
'GOOGL', 'MSFT', 'IBM', 'GS', 'MS', 'BAC', 'C']
def get_px(stock, start,
end):
return web.get_data_yahoo(stock, start,
end)['Adj Close']
px = DataFrame({n:
get_px(n, '1/1/2005', '1/1/2015') for n in names})
px.plot()
(3)数据的时间轴对齐以及缺失数据填充
#%% plot
px =
px.asfreq('B').fillna(method='pad')
rets = px.pct_change()
retcum =
(1+rets).cumprod()-1
retcum.plot()
(4)子函数编写:特定回顾期的动量计算并作标准化
#%% calc_mom
def calc_mom(price,
lookback, lag):
mom_ret =
price.shift(lag).pct_change(lookback)
ranks = mom_ret.rank(axis=1,
ascending=False)
demeaned =
ranks.subtract(ranks.mean(axis=1), axis=0)
return
demeaned.divide(demeaned.std(axis=1), axis=0)
当然这里是做了排序后把排序变量做标准化后返回。
(5)子函数编写:给定回顾期和持有期,组合的夏普比例计算
#%% strat_sr
compound = lambda x : (1
+ x).prod() - 1
daily_sr = lambda x:
x.mean() / x.std()
def strat_sr(prices, lb,
hold):
# Compute portfolio weights
freq = '%dB' % hold
port = calc_mom(prices, lb, lag=1)
daily_rets = prices.pct_change()
# Compute portfolio returns
port = port.shift(1).resample(freq,
how='first')
returns = daily_rets.resample(freq,
how=compound)
port_rets = (port * returns).sum(axis=1)
return daily_sr(port_rets) * np.sqrt(252 /
hold)
strat_sr(px, 70, 30)
(6)计算不同回顾期和持有期下组合的夏普比例值
#%% calc with diff
lookbacks and holdings
lookbacks = range(20,
90, 5)
holdings = range(20, 90,
5)
dd = defaultdict(dict)
for lb in lookbacks:
for hold in holdings:
dd[lb][hold] = strat_sr(px, lb, hold)
ddf = DataFrame(dd)
ddf.index.name =
'Holding Period'
ddf.columns.name =
'Lookback Period'
(7)进行策略参数分布图形展示
#%% heatmap
def heatmap(df, cmap=plt.cm.gray_r):
fig = plt.figure()
ax = fig.add_subplot(111)
axim = ax.imshow(df.values, cmap=cmap,
interpolation='nearest')
ax.set_xlabel(df.columns.name)
ax.set_xticks(np.arange(len(df.columns)))
ax.set_xticklabels(list(df.columns))
ax.set_ylabel(df.index.name)
ax.set_yticks(np.arange(len(df.index)))
ax.set_yticklabels(list(df.index))
plt.colorbar(axim)
heatmap(ddf)
通过上图可以看到在此例下,大概回顾期为55-60日,持有期为35-40日,会获取较高的夏普比率。
总结:可以看到在Python下使用pandas包整体的实现测试过程很简单明了。Pandas是个进行数据分析处理很赞的包。
二、MATLAB下的实现测试。
下面在来看下在MATLAB下的实现测试过程,不同的语言各有利弊,并无本质上哪个好,那个坏之说,本例中,由于Python下的pandas包的帮助,在Python下的代码更加简洁明了,MATLAB下代码稍显臃肿,当然也可以仿照Python下的pandas包,实现一个MATLAB下的pandas包,让相关的数据处理更加简洁方便。虽然MATLAB下的金融工具箱有fints(financial time series类)、timeseries类,但相关的实现和处理在效率和使用上并不是特别好,所以时序相关的处理我还是自己实现的,并没有全部使用MATLAB自带的一些包。
注:MATLAB的实现测试使用的A股的数据,数据的获取基于FQuantToolBox
(1)函数说明
function
SimpleMomentumPortfolioTest
% by LiYang_faruto
%
Email:farutoliyang@foxmail.com
% 2015/01/01
%% A Little Clean Work
% clear;
% clc;
% close all;
format compact;
(2)数据的获取基于FQuantToolBox
%% GetDataFromWeb
tic;
StockCodeCell =
{'600588sh','sh600030','600446','300024sz','sz000001','600570sh'};
StockNameCell = {'用友网络','中信证券','金证股份','机器人','平安银行','恒生电子'};
BeginDate = '20100101';
EndDate = '20150101';
Len =
length(StockCodeCell);
StockDataCell =
cell(length(StockCodeCell),1);
for i =
1:length(StockCodeCell)
StockCode = StockCodeCell{i};
[StockDataCell{i}] =
GetStockTSDay_Web(StockCode,BeginDate,EndDate);
end
toc;
%% 前复权数据生成
StockDataCellXRD =
StockDataCell;
for i = 1:Len
StockData = StockDataCell{i};
AdjFlag = 1;
[StockDataCell{i}] =
CalculateStockXRD(StockData, [], AdjFlag);
end
(3)数据的时间轴对齐以及缺失数据填充
tic;
sdate =
datenum(BeginDate,'yyyymmdd');
edate =
datenum(EndDate,'yyyymmdd');
bdates = busdays(sdate,
edate, 'Daily');
Bdates = str2num(
datestr(bdates,'yyyymmdd') );
StockDataCell_pre =
StockDataCell;
for i = 1:Len
tMat = zeros(length(Bdates),8);
tMat(:,1) = Bdates;
tMat_pre = StockDataCell{i};
for j = 1:length(Bdates)
tD = Bdates(j);
ind = find(tMat_pre(:,1)<=tD,
1,'last');
tMat(j,2:end) = tMat_pre(ind,2:end);
end
StockDataCell{i} = tMat;
end
toc;
%% 每只股票累计收益
StockMat = zeros(length(Bdates),
Len+1);
StockMat(:,1) = Bdates;
for i = 1:Len
StockMat(:,i+1) = StockDataCell{i}(:,5);
end
Ret =
tick2ret(StockMat(:,2:end));
CumRet =
cumprod((1+Ret))-1;
scrsz =
get(0,'ScreenSize');
figure('Position',[scrsz(3)*1/4
scrsz(4)*1/6 scrsz(3)*4/5 scrsz(4)]*3/4);
plot(CumRet,'LineWidth',1.5);
xlim([0,length(Bdates)+1]);
Dates =
StockMat(2:end,1);
LabelSet(gca, Dates, [],
[], 1);
M = StockNameCell;
H = legend(M);
H.Orientation =
'horizontal';
H.FontWeight = 'Bold';
H.FontSize = 12;
H.Location =
'northoutside';
str = '股票累计收益';
H = title(str);
H.FontWeight = 'Bold';
H.FontSize = 15;
(4)子函数编写:特定回顾期的动量计算并作标准化
%% sub fun calc_mom
%
---------------------------------------------------
% calc_mom
% ---------------------------------------------------
function weight =
calc_mom(price,lookback)
weight = zeros(size(price));
weight(:,1) = price(:,1);
[m,n] = size(price);
% weight(1:lookback,2:end) = nan;
weight(1:lookback,2:end) = 0;
for j = lookback+1:m
for i = 2:n
tData = price(:,i);
weight(j,i) =
(tData(j-1)-tData(j-lookback))/tData(j-lookback);
end
temp = weight(j,2:end);
weight(j,2:end) =
(temp-mean(temp))./std(temp);
end
weight(isnan(weight)) = 0;
end
当然这里与Python下稍有不同,就使用标准化后的动量值返回。
(5)子函数编写:给定回顾期和持有期,组合的夏普比例计算
%% sub fun strat_sr
%
---------------------------------------------------
% strat_sr
%
---------------------------------------------------
function SR =
strat_sr(prices, lb, hold)
SR = 0;
[m,n] = size(prices);
% 计算权重
port = calc_mom(prices,lb);
port(isnan(port)) = 0;
% 计算组合收益
PortResample = [];
Returns = [];
Ind = 1;
for i = hold:hold:m
PortResample(Ind,:) = port(i-hold+1,:);
Returns(Ind,:) = prices(i,:);
Returns(Ind,2:end) =
(prices(i,2:end)-prices(i-hold+1,2:end))./prices(i-hold+1,2:end);
Ind = Ind + 1;
end
port_rets = PortResample(:,2:end).*Returns(:,2:end);
port_rets = sum(port_rets,2);
% 计算年化Sharpe
Ratio
SR = mean(port_rets)/std(port_rets)*sqrt(
252/hold );
end
(6)计算不同回顾期和持有期下组合的夏普比例值
%% calc
tic;
lookbacks = 20:5:90;
holdings = 20:5:100;
DD =
zeros(length(lookbacks), length(holdings));
for i =
1:length(lookbacks)
for j = 1:length(holdings)
lb = lookbacks(i);
hold = holdings(j);
DD(i,j) = strat_sr(StockMat, lb, hold);
end
end
toc;
(7)进行策略参数分布图形展示
%% HeatPlot
temp =
num2cell(lookbacks);
temp =
cellfun(@num2str,temp,'UniformOutput',false);
YVarNames = temp;
temp =
num2cell(holdings);
temp =
cellfun(@num2str,temp,'UniformOutput',false);
XVarNames = temp;
XLabelString = 'Holding
Period';
YLabelString = 'Lookack
Period';
Fmatrixplot(DD,'ColorBar','On','XVarNames',XVarNames,'YVarNames',YVarNames,...
'XLabelString',XLabelString,'YLabelString',YLabelString);
通过上图可以看到在此例下,大概回顾期为25-35日,持有期为60日,会获取较高的夏普比率。
总结
本文给出了一个简化的截面动量组合测试,虽然策略比较简单,但整体的测试流程和框架很明了清晰,对类似策略的回测实现具有参考意义,整体的流程框架为:
数据获取(基于付费或者免费的数据源)——》
数据的时间轴对齐以及缺失数据填充——》
子函数编写:特定回顾期的动量计算并作标准化——》
子函数编写:给定回顾期和持有期,组合的夏普比例计算——》
计算不同回顾期和持有期下组合的夏普比例值——》
进行策略参数分布图形展示
在Python下pandas是个数据处理非常不错的一个包,另外在Python下免费的A股数据可以通过tushare 包(作者Jimmy)获取,tushare 包下载地址:TuShare -财经数据接口包。
在MATLAB下,虽然此例的实现测试稍显臃肿,但也不是非常复杂。在MATLAB下免费的A股数据可以通过FQuantToolBox(作者faruto)获取,FQuantToolBox下载地址:FQuantToolBoxHelpOnLine