楼主: fantuanxiaot
12261 218

[源码分享] [Faruto原创]基于Python和MATLAB的一种简化的截面动量组合测试   [分享]

助理

Ψ▄┳一大卫卍卐席尔瓦

大师

9%

还不是VIP/贵宾

-

威望
7
论坛币
-235060 个
通用积分
15.3381
学术水平
3780 点
热心指数
3816 点
信用等级
3451 点
经验
150465 点
帖子
7694
精华
32
在线时间
1323 小时
注册时间
2013-2-3
最后登录
2019-10-31

初级学术勋章 初级热心勋章 中级热心勋章 中级学术勋章 初级信用勋章 中级信用勋章 高级热心勋章 高级学术勋章 特级学术勋章 特级热心勋章 高级信用勋章 特级信用勋章

fantuanxiaot 发表于 2015-3-4 08:53:50 |显示全部楼层

一种简化的截面动量组合测试[Python&MATLAB]

    下面的例子来源书籍<Python for Data Analysis>
    策略比较简单,使用N只股票构建投资组合,计算每只股票的过去LookBack(参数)日的动量,并作标准化处理作为股票权重,持有Holding(参数)日,其中权重允许为负数,即允许做空股票。简要说来就是做过过去表现强势的股票,做空过去表现弱势的股票。虽然策略比较简单,但整体的测试流程和框架很明了清晰,对类似策略的回测实现具有参考意义,整体的流程框架为:
数据获取(基于付费或者免费的数据源)——》
数据的时间轴对齐以及缺失数据填充——》
子函数编写:特定回顾期的动量计算并作标准化——》
子函数编写:给定回顾期和持有期,组合的夏普比例计算——》
计算不同回顾期和持有期下组合的夏普比例值——》
进行策略参数分布图形展示
一、Python下的实现测试。
(1)主要用到numpy、pandas、matplotlib等几个包
  1. # -*- coding: utf-8 -*-
  2. """
  3. SimpleMomentumPortfolioTest
  4. Created on 2015/05/01
  5. @author: LiYang(faruto)
  6. @group : FQuantStudio
  7. @contact: farutoliyang@foxmail.com
  8. """
  9. #%% import
  10. import numpy as np
  11. import pandas as pd
  12. import pandas.io.data as web
  13. import matplotlib.pylab as plt
  14. from pandas import Series,DataFrame
  15. from collections import defaultdict
复制代码

(2)数据的获取基于pandas包
  1. #%% GetData
  2. names = ['AAPL', 'GOOGL', 'MSFT', 'IBM', 'GS', 'MS', 'BAC', 'C']
  3. def get_px(stock, start, end):
  4.     return web.get_data_yahoo(stock, start, end)['Adj Close']
  5. px = DataFrame({n: get_px(n, '1/1/2005', '1/1/2015') for n in names})
  6. px.plot()
复制代码


(3)数据的时间轴对齐以及缺失数据填充
  1. #%% plot
  2. px = px.asfreq('B').fillna(method='pad')
  3. rets = px.pct_change()
  4. retcum = (1+rets).cumprod()-1
  5. retcum.plot()
复制代码


640.jpg

(4)子函数编写:特定回顾期的动量计算并作标准化
  1. #%% calc_mom
  2. def calc_mom(price, lookback, lag):
  3.     mom_ret = price.shift(lag).pct_change(lookback)
  4.     ranks = mom_ret.rank(axis=1, ascending=False)
  5.     demeaned = ranks.subtract(ranks.mean(axis=1), axis=0)
  6.     return demeaned.divide(demeaned.std(axis=1), axis=0)
复制代码


当然这里是做了排序后把排序变量做标准化后返回。
(5)子函数编写:给定回顾期和持有期,组合的夏普比例计算
  1. #%% strat_sr
  2. compound = lambda x : (1 + x).prod() - 1
  3. daily_sr = lambda x: x.mean() / x.std()
  4. def strat_sr(prices, lb, hold):
  5.     # Compute portfolio weights
  6.     freq = '%dB' % hold
  7.     port = calc_mom(prices, lb, lag=1)

  8.     daily_rets = prices.pct_change()
  9.     # Compute portfolio returns
  10.     port = port.shift(1).resample(freq, how='first')
  11.     returns = daily_rets.resample(freq, how=compound)
  12.     port_rets = (port * returns).sum(axis=1)
  13.     return daily_sr(port_rets) * np.sqrt(252 / hold)
  14. strat_sr(px, 70, 30)
复制代码


(6)计算不同回顾期和持有期下组合的夏普比例值
  1. #%% calc with diff lookbacks and holdings
  2. lookbacks = range(20, 90, 5)
  3. holdings = range(20, 90, 5)
  4. dd = defaultdict(dict)
  5. for lb in lookbacks:
  6.     for hold in holdings:
  7.         dd[lb][hold] = strat_sr(px, lb, hold)
  8. ddf = DataFrame(dd)
  9. ddf.index.name = 'Holding Period'
  10. ddf.columns.name = 'Lookback Period'
  11. (7)进行策略参数分布图形展示
  12. #%% heatmap
  13. def heatmap(df, cmap=plt.cm.gray_r):
  14.     fig = plt.figure()
  15.     ax = fig.add_subplot(111)
  16.     axim = ax.imshow(df.values, cmap=cmap, interpolation='nearest')
  17.     ax.set_xlabel(df.columns.name)
  18.     ax.set_xticks(np.arange(len(df.columns)))
  19.     ax.set_xticklabels(list(df.columns))
  20.     ax.set_ylabel(df.index.name)
  21.     ax.set_yticks(np.arange(len(df.index)))
  22.     ax.set_yticklabels(list(df.index))
  23.     plt.colorbar(axim)
  24. heatmap(ddf)
复制代码


640 (1).jpg



通过上图可以看到在此例下,大概回顾期为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)函数说明
  1. function SimpleMomentumPortfolioTest
  2. % by LiYang_faruto
  3. % Email:farutoliyang@foxmail.com
  4. % 2015/01/01
  5. %% A Little Clean Work
  6. % clear;
  7. % clc;
  8. % close all;
  9. format compact;
复制代码


(2)数据的获取基于FQuantToolBox
  1. %% GetDataFromWeb
  2. tic;
  3. StockCodeCell = {'600588sh','sh600030','600446','300024sz','sz000001','600570sh'};
  4. StockNameCell = {'用友网络','中信证券','金证股份','机器人','平安银行','恒生电子'};
  5. BeginDate = '20100101';
  6. EndDate = '20150101';
  7. Len = length(StockCodeCell);
  8. StockDataCell = cell(length(StockCodeCell),1);
  9. for i = 1:length(StockCodeCell)
  10.      StockCode = StockCodeCell{i};
  11.      [StockDataCell{i}] = GetStockTSDay_Web(StockCode,BeginDate,EndDate);
  12. end
  13. toc;
  14. %% 前复权数据生成
  15. StockDataCellXRD = StockDataCell;
  16. for i = 1:Len
  17.     StockData = StockDataCell{i};
  18.     AdjFlag = 1;
  19.     [StockDataCell{i}] = CalculateStockXRD(StockData, [], AdjFlag);
  20. end
  21. (3)数据的时间轴对齐以及缺失数据填充
  22. tic;
  23. sdate = datenum(BeginDate,'yyyymmdd');
  24. edate = datenum(EndDate,'yyyymmdd');
  25. bdates = busdays(sdate, edate, 'Daily');
  26. Bdates = str2num( datestr(bdates,'yyyymmdd') );
  27. StockDataCell_pre = StockDataCell;
  28. for i = 1:Len
  29.     tMat = zeros(length(Bdates),8);
  30.     tMat(:,1) = Bdates;
  31.    
  32.     tMat_pre = StockDataCell{i};
  33.     for j = 1:length(Bdates)
  34.         tD = Bdates(j);
  35.         ind = find(tMat_pre(:,1)<=tD, 1,'last');
  36.         tMat(j,2:end) = tMat_pre(ind,2:end);
  37.     end
  38.    
  39.     StockDataCell{i} = tMat;
  40. end
  41. toc;
  42. %% 每只股票累计收益
  43. StockMat = zeros(length(Bdates), Len+1);
  44. StockMat(:,1) = Bdates;
  45. for i = 1:Len
  46.     StockMat(:,i+1) = StockDataCell{i}(:,5);
  47. end
  48. Ret = tick2ret(StockMat(:,2:end));
  49. CumRet = cumprod((1+Ret))-1;

  50. scrsz = get(0,'ScreenSize');
  51. figure('Position',[scrsz(3)*1/4 scrsz(4)*1/6 scrsz(3)*4/5 scrsz(4)]*3/4);
  52. plot(CumRet,'LineWidth',1.5);
  53. xlim([0,length(Bdates)+1]);
  54. Dates = StockMat(2:end,1);
  55. LabelSet(gca, Dates, [], [], 1);

  56. M = StockNameCell;
  57. H = legend(M);
  58. H.Orientation = 'horizontal';
  59. H.FontWeight = 'Bold';
  60. H.FontSize = 12;
  61. H.Location = 'northoutside';

  62. str = '股票累计收益';
  63. H = title(str);
  64. H.FontWeight = 'Bold';
  65. H.FontSize = 15;
复制代码
(3)数据的时间轴对齐以及缺失数据填充
  1. tic;
  2. sdate = datenum(BeginDate,'yyyymmdd');
  3. edate = datenum(EndDate,'yyyymmdd');
  4. bdates = busdays(sdate, edate, 'Daily');
  5. Bdates = str2num( datestr(bdates,'yyyymmdd') );

  6. StockDataCell_pre = StockDataCell;
  7. for i = 1:Len
  8.     tMat = zeros(length(Bdates),8);
  9.     tMat(:,1) = Bdates;
  10.    
  11.     tMat_pre = StockDataCell{i};
  12.     for j = 1:length(Bdates)
  13.         tD = Bdates(j);
  14.         ind = find(tMat_pre(:,1)<=tD, 1,'last');
  15.         tMat(j,2:end) = tMat_pre(ind,2:end);
  16.     end
  17.    
  18.     StockDataCell{i} = tMat;
  19. end
  20. toc;
  21. %% 每只股票累计收益
  22. StockMat = zeros(length(Bdates), Len+1);
  23. StockMat(:,1) = Bdates;
  24. for i = 1:Len
  25.     StockMat(:,i+1) = StockDataCell{i}(:,5);
  26. end

  27. Ret = tick2ret(StockMat(:,2:end));
  28. CumRet = cumprod((1+Ret))-1;

  29. scrsz = get(0,'ScreenSize');
  30. figure('Position',[scrsz(3)*1/4 scrsz(4)*1/6 scrsz(3)*4/5 scrsz(4)]*3/4);
  31. plot(CumRet,'LineWidth',1.5);
  32. xlim([0,length(Bdates)+1]);
  33. Dates = StockMat(2:end,1);
  34. LabelSet(gca, Dates, [], [], 1);

  35. M = StockNameCell;
  36. H = legend(M);
  37. H.Orientation = 'horizontal';
  38. H.FontWeight = 'Bold';
  39. H.FontSize = 12;
  40. H.Location = 'northoutside';

  41. str = '股票累计收益';
  42. H = title(str);
  43. H.FontWeight = 'Bold';
  44. H.FontSize = 15;
复制代码

640 (2).jpg

(4)子函数编写:特定回顾期的动量计算并作标准化
  1. %% sub fun calc_mom
  2. % ---------------------------------------------------
  3. %  calc_mom
  4. % ---------------------------------------------------
  5. function weight = calc_mom(price,lookback)
  6.     weight = zeros(size(price));
  7.     weight(:,1) = price(:,1);
  8.     [m,n] = size(price);
  9.     % weight(1:lookback,2:end) = nan;
  10.     weight(1:lookback,2:end) = 0;
  11.     for j = lookback+1:m
  12.         for i = 2:n
  13.             tData = price(:,i);
  14.             weight(j,i) =  (tData(j-1)-tData(j-lookback))/tData(j-lookback);
  15.         end
  16.         temp = weight(j,2:end);
  17.         weight(j,2:end) = (temp-mean(temp))./std(temp);
  18.     end
  19.    
  20.     weight(isnan(weight)) = 0;
  21. end   
复制代码


当然这里与Python下稍有不同,就使用标准化后的动量值返回。
(5)子函数编写:给定回顾期和持有期,组合的夏普比例计算
  1. %% sub fun strat_sr
  2. % ---------------------------------------------------
  3. %  strat_sr
  4. % ---------------------------------------------------
  5. function SR = strat_sr(prices, lb, hold)
  6.     SR = 0;
  7.     [m,n] = size(prices);
  8.     % 计算权重
  9.     port = calc_mom(prices,lb);
  10.     port(isnan(port)) = 0;
  11.    
  12.     % 计算组合收益
  13.     PortResample = [];
  14.     Returns = [];
  15.     Ind = 1;
  16.     for i = hold:hold:m
  17.         PortResample(Ind,:) = port(i-hold+1,:);
  18.         Returns(Ind,:) = prices(i,:);
  19.         Returns(Ind,2:end) = (prices(i,2:end)-prices(i-hold+1,2:end))./prices(i-hold+1,2:end);

  20.         Ind = Ind + 1;
  21.     end
  22.     port_rets = PortResample(:,2:end).*Returns(:,2:end);
  23.     port_rets = sum(port_rets,2);
  24.     % 计算年化Sharpe Ratio
  25.     SR = mean(port_rets)/std(port_rets)*sqrt( 252/hold );
  26. end
  27. (6)计算不同回顾期和持有期下组合的夏普比例值
  28. %% calc
  29. tic;
  30. lookbacks = 20:5:90;
  31. holdings = 20:5:100;

  32. DD = zeros(length(lookbacks), length(holdings));

  33. for i = 1:length(lookbacks)
  34.     for j = 1:length(holdings)
  35.         lb = lookbacks(i);
  36.         hold = holdings(j);
  37.         DD(i,j) = strat_sr(StockMat, lb, hold);
  38.     end
  39. end
  40. toc;
  41. (7)进行策略参数分布图形展示
  42. %% HeatPlot
  43. temp = num2cell(lookbacks);
  44. temp = cellfun(@num2str,temp,'UniformOutput',false);
  45. YVarNames = temp;

  46. temp = num2cell(holdings);
  47. temp = cellfun(@num2str,temp,'UniformOutput',false);
  48. XVarNames = temp;

  49. XLabelString = 'Holding Period';
  50. YLabelString = 'Lookack Period';
  51. Fmatrixplot(DD,'ColorBar','On','XVarNames',XVarNames,'YVarNames',YVarNames,...
  52.     'XLabelString',XLabelString,'YLabelString',YLabelString);
复制代码


640 (3).jpg

通过上图可以看到在此例下,大概回顾期为25-35日,持有期为60日,会获取较高的夏普比率。

总结


本文给出了一个简化的截面动量组合测试,虽然策略比较简单,但整体的测试流程和框架很明了清晰,对类似策略的回测实现具有参考意义,整体的流程框架为:
数据获取(基于付费或者免费的数据源)——》
数据的时间轴对齐以及缺失数据填充——》
子函数编写:特定回顾期的动量计算并作标准化——》
子函数编写:给定回顾期和持有期,组合的夏普比例计算——》
计算不同回顾期和持有期下组合的夏普比例值——》
进行策略参数分布图形展示
在Python下pandas是个数据处理非常不错的一个包,另外在Python下免费的A股数据可以通过tushare 包(作者Jimmy)获取,tushare 包下载地址:http://tushare.waditu.com/
在MATLAB下,虽然此例的实现测试稍显臃肿,但也不是非常复杂。在MATLAB下免费的A股数据可以通过FQuantToolBox(作者faruto)获取,FQuantToolBox下载地址:

https://bbs.pinggu.org/thread-3567352-1-1.html

更多内容请点击“阅读原文” 本公众号由李洋(微信faruto)维护。
更多量化投资、MATLAB应用内容可以关注:MATLAB技术论坛:
http://www.matlabsky.com/
李洋(faruto)的微博、博客
http://weibo.com/faruto
http://blog.sina.com.cn/faruto

===分享给朋友===

点击右上角,在弹出菜单中选择“发送给朋友”或“分享到朋友圈”

===订阅FQuantStudio公众号===
https://bbs.pinggu.org/thread-3567352-1-1.html
关键词:MATLAB python atlab matla Lab 投资组合 数据源 书籍

回帖推荐

遥远的生命 发表于210楼  查看完整内容

楼主的文章太长,没细看 这种对称不一致的意思应该是,结果稍有区别,但是不是100%契合 其中的原因,如果数据等全部一致,也会出现误差(浮点数的识别等问题,我用R和matlab时候出想过这种问题) 另外的原因可能是楼主P用的是别人的吧去抓取数据,M是自己写的程序去抓数据,抓过来的数据不见得一致

琥珀糖 发表于133楼  查看完整内容

两种软件得出来的结果为什么是对称的,而不是一致的?

faruto 发表于2楼  查看完整内容

原文地址https://mp.weixin.qq.com/s?__biz=MzA5NzEzNDk4Mw==&mid=204989303&idx=1&sn=8438eb4cd073159db4be7814878c320d&scene=2&from=timeline&isappinstalled=0&key=8ea74966bf01cfb686684c482ef33e10cf24aecfa0a1b9eaf5a0e5c0a8b6847480c741ef23cdf81d7ecf1ad110994bd6&ascene=2&uin=OTM2MDkyNDIz&devicetype=android-10&version=25010030&pass_ticket=HUTj42uFWzHXYrktMohsl9zfMcPEIDSsjChMPAZgvrloBTA%2B%2BR0VklEwMV0Gviq7 ...
已有 7 人评分经验 论坛币 学术水平 热心指数 信用等级 收起 理由
静水深流 + 20 精彩帖子
我的素质低 + 100 + 5 精彩帖子
eric5488 + 3 + 3 + 3 精彩帖子
faruto + 5 + 5 + 5 非常好,格式漂亮多了。
离歌レ笑 + 100 + 5 精彩帖子
wwqqer + 5 + 5 + 5 精彩帖子
kychan + 60 + 5 + 5 + 5 精彩帖子

总评分: 经验 + 180  论坛币 + 100  学术水平 + 28  热心指数 + 18  信用等级 + 18   查看全部评分

本帖被以下文库推荐

stata SPSS
faruto 发表于 2015-3-4 08:57:01 |显示全部楼层

回帖奖励 +6

已有 1 人评分经验 论坛币 学术水平 热心指数 信用等级 收起 理由
fantuanxiaot + 10 + 10 + 1 + 1 + 1 精彩帖子

总评分: 经验 + 10  论坛币 + 10  学术水平 + 1  热心指数 + 1  信用等级 + 1   查看全部评分

回复

使用道具 举报

zhukeming 发表于 2015-3-4 08:59:44 |显示全部楼层

回帖奖励 +6

元宵福利来了之1
回复

使用道具 举报

jerker 发表于 2015-3-4 08:59:51 |显示全部楼层

回帖奖励 +6

元宵福利来了之1
回复

使用道具 举报

HILTER 发表于 2015-3-4 08:59:54 |显示全部楼层

回帖奖励 +6

THANKS A LOT
回复

使用道具 举报

zhukeming 发表于 2015-3-4 09:00:29 |显示全部楼层

回帖奖励 +6

谢谢楼主如此慷慨福利2
回复

使用道具 举报

HILTER 发表于 2015-3-4 09:00:34 |显示全部楼层

回帖奖励 +6

HAPPY LUNAR NEW YEAR!!!
回复

使用道具 举报

jerker 发表于 2015-3-4 09:00:34 |显示全部楼层

回帖奖励 +6

谢谢楼主如此慷慨福利2
回复

使用道具 举报

zhukeming 发表于 2015-3-4 09:01:03 |显示全部楼层
谢谢楼主如此慷慨福利3
回复

使用道具 举报

jerker 发表于 2015-3-4 09:01:18 |显示全部楼层

回帖奖励 +6

谢谢楼主如此慷慨福利3
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 我要注册

京ICP备16021002-2号 京B2-20170662号 京公网安备 11010802022788号 论坛法律顾问:王进律师 知识产权保护声明   免责及隐私声明

GMT+8, 2019-11-18 12:41