最近有一个网友让我帮他写一个工具分析基金回撤情况,前几天项目比较忙就直没动手,今天晚上有点时间,研究了一下。
先把今天的研究成果分享:
要分析基金净值回撤,首先肯定要有基金的净值变化数据。要抓数据肯定是到天天基金网上抓,毕竟人家是专业机构。
我找了一只个人比较喜欢的基金,易方达中小盘混合 (110011),在天天基金网站上很容易找到它的历史净值页面:
http://fundf10.eastmoney.com/jjjz_110011.html
因为之前抓过天天基金当日基金净值数据,知道它的页面数据是藏在js里面,在浏览器访问页面后,执行js从服务器动态获取再展示到浏览器。
要知道是哪个js脚本,方法有很多。我用的是谷歌浏览器自带的开发者工具,如下图:
打开开发者工具的情况下浏览基金净值页面,就看到该页面访问的所有资源网址。数据比较乱,不过关心的只是js,所以按类型排了序。不过可以看出来访问的js也不少,暂时还不好知道是哪一个js包含我要的数据。
由于默认只显示了第一页数据,所以我就随意点了其它页净值,从访问的资源列表变化情况,很容易发现想要的js网址,如图
把这些带有callback的js网址拷下来,如下:
http://api.fund.eastmoney.com/f10/lsjz?callback=jQuery183018519977574130597_1558194911277&fundCode=110011&pageIndex=6&pageSize=20&startDate=&endDate=&_=1558195435735
从命令参数上猜测fundCode是基金代码,pageIndex是净值页面页码,其它参数暂时不知道意义。
先尝试使用这个网址在浏览器访问看是啥情况:
可以看出网站对这个js访问做了控制,不让用户直接在浏览器访问。 由于对于http抓包不大熟悉,不清楚这种情况下要怎么处理,总不至于需要写一个浏览器来抓一个js数据吧。
于是找了一个熟悉抓包的朋友咨询一下。咨询结果明天再说,跟下面这个请求有关。
GET /f10/lsjz?callback=jQuery183018519977574130597_1558194911277&fundCode=110011&pageIndex=4&pageSize=20&startDate=&endDate=&_=1558195568400 HTTP/1.1 Host: api.fund.eastmoney.com Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 Accept: */* Referer: http://fundf10.eastmoney.com/jjjz_110011.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9
-----------------------------------------------
接上文,这是访问基金净值页面的http请求报文头。注意里面的Referer。百度百科上是这样说的:HTTP Referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器基此可以获得一些信息用于处理。
这里天天基金网站就是用Referer来防止别人盗链的。我直接在浏览器输入js网址,发送给服务器的请求里面的Referer是空的,但如果是在网页上点击,则Referer是网页的网址。
为了验证加上referer加上后是否能正常访问基金净值数据,我用curl做了一个试验:
curl "http://api.fund.eastmoney.com/f10/lsjz?callback=jQuery183018519977574130597_1558194911277&fundCode=110011&pageIndex=1&pageSize=300&startDate=2018-01-01&endDate=2018-12-31&_=1558194929451" --referer "http://fundf10.eastmoney.com/" --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" -o tmp.txt
试验成功,正常获取到了净值。
-----------------------------------------------
获取基金的方法找到了,接下来再看一下js网址:http://api.fund.eastmoney.com/f10/lsjz?callback=jQuery183018519977574130597_1558194911277&fundCode=110011&pageIndex=6&pageSize=20&startDate=&endDate=&_=1558195435735
简单猜测,这里面的fundCode是基金代码,pageIndex是页面编号,pageSize是每页净值数量,startDate和endDate是净值日期区间,如果要获取一年的净值,可以使用
pageIndex = 1,pageSize = 300, startDate = 2019-01-01,endDate = 2019-12-31,用curl再试验一下,猜测得对。
接下来就是体力活了,再写一个程序循环获取每年净值,然后再进行分析就可以。
-----------------------------------------------
看我分析110011 易方达中小盘的结果
可以看出易方达中小盘2008年成立,至今总收益是370%,也就是10年前把一万投入该基金并且设置了分红再投资,现在理论上市值有3万7。
在这期间买入该基金,最惨的是在2015-08-19,从那天起基金连续下跌,直到2015-08-25才开始止跌,期间总共跌幅接近15%。
最幸运的是在2015-07-08买入基金的投资者,他们买了后基金就一直上涨,直到涨了20%才开始回调。
今年易方达中小的业绩如下:
分析起止日期:[2019-01-02,2019-06-06]
区间回撤/上涨率: 29.92% [2019-01-02, 2019-06-06] [3.6184, 4.7011]
---------------------
最大连续回撤率: -5.21% [2019-04-30, 2019-05-06] [5.0403, 4.7775]
期间最大回撤率: -7.92% [2019-04-10, 2019-05-09] [5.1024, 4.6981]
从起始时间算最大回撤率: -1.34% [2019-01-02, 2019-01-03] [3.6184, 3.5699]
最大连续回撤天数率: 4 [2019-03-04, 2019-03-08] [4.5156, 4.2940]
---------------------
最大连续上涨率: 9.22% [2019-01-30, 2019-02-14] [3.8597, 4.2157]
期间最大上涨率: 42.93% [2019-01-03, 2019-04-10] [3.5699, 5.1024]
从起始时间算最大上涨率: 41.01% [2019-01-02, 2019-04-10] [3.6184, 5.1024]
最大连续上涨天数率: 6 [2019-01-30, 2019-02-14] [3.8597, 4.2157]
今年基金还不错的,接近30%,最赚钱的时候是20190410,那时基金赚了41%
-------------------------------------------------------------
有兴趣的同学可以到这里下载我这个小工具:
链接: https://pan.baidu.com/s/1x7S8X5Y5tdtPPdUNI92nJA 提取码: zg97 复制这段内容后打开百度网盘手机App,操作更方便哦
-------------------------------------------------------------
相关代码(写得比较简陋,因为想到一点写一点,以后有时间再考虑优化)
getRetracement.h
#pragma once #include "stdafx.h" #include <stdio.h> #include <iomanip> typedef struct __tagNetValueInfo{ string strNetValueDate; double dAccumulatedNet; __tagNetValueInfo() { dAccumulatedNet = 0; } __tagNetValueInfo(const string& v1, double v2) { strNetValueDate = v1; dAccumulatedNet = v2; } }NetValueInfo; typedef struct __StatisticalNetValueInfo{ string strValue; string strNode; string strBegDate; double dBegNetValue; string strEndDate; double dEndNetValue; __StatisticalNetValueInfo() { dBegNetValue = dEndNetValue = 0; strBegDate = strEndDate = "-"; } string getString() { string strRet; char buf[1024]; size_t nStrNodeLen = strNode.size() + 1; string strPad = ""; for (size_t i = nStrNodeLen / 8; i < 3; ++i) { strPad += " "; } _snprintf(buf, sizeof(buf), "%s:%s%s ", strNode.c_str(), strPad.c_str(), strValue.c_str()); strRet += buf; _snprintf(buf, sizeof(buf), "[%s,%s] ", strBegDate.c_str(), strEndDate.c_str()); strRet += buf; _snprintf(buf, sizeof(buf), "[%04f,%04f] ", dBegNetValue, dEndNetValue); strRet += buf; return strRet; } void print() { size_t nStrNodeLen = strNode.size() + 1; string strPad = ""; for (size_t i = nStrNodeLen / 8; i < 3; ++i) { strPad += " "; } cout << strNode << ":" << strPad << strValue << " ";// << endl; cout << "[" << strBegDate << ", " << strEndDate << "] ";// << endl; cout.fill('0'); cout << "[" << fixed << setprecision(4) << dBegNetValue << ", " << fixed << setprecision(4) << dEndNetValue << "]" << endl; } }StatisticalNetValueInfo; typedef struct __tagResultInfo{ //最大连续回撤率/上涨率信息 StatisticalNetValueInfo resMaxContinRetraceInfo; StatisticalNetValueInfo resMaxContinRiseInfo; //最大连续回撤天数/上涨天数信息 StatisticalNetValueInfo resMaxContinRetraceDaysInfo; StatisticalNetValueInfo resMaxContinRiseDaysInfo; //最大回撤率/上涨率信息 StatisticalNetValueInfo resMaxRetraceInfo; StatisticalNetValueInfo resMaxRiseInfo; //从起始时间算最大回撤率/上涨率信息 StatisticalNetValueInfo resMaxRetraceFromBeginInfo; StatisticalNetValueInfo resMaxRiseFromBeginInfo; //从起始时间算回撤率/上涨率信息 StatisticalNetValueInfo resRetraceRiseFromBeginInfo; __tagResultInfo() { resMaxContinRetraceInfo.strNode = "最大连续回撤率"; resMaxContinRetraceDaysInfo.strNode = "最大连续回撤天数率"; resMaxRetraceInfo.strNode = "期间最大回撤率"; resMaxRetraceFromBeginInfo.strNode = "从起始时间算最大回撤率"; resMaxContinRiseInfo.strNode = "最大连续上涨率"; resMaxContinRiseDaysInfo.strNode = "最大连续上涨天数率"; resMaxRiseInfo.strNode = "期间最大上涨率"; resMaxRiseFromBeginInfo.strNode = "从起始时间算最大上涨率"; resRetraceRiseFromBeginInfo.strNode = "区间回撤/上涨率"; } string getString() { string strRet; strRet += resRetraceRiseFromBeginInfo.getString(); strRet += "--------------------- "; strRet += resMaxContinRetraceInfo.getString(); strRet += resMaxRetraceInfo.getString(); strRet += resMaxRetraceFromBeginInfo.getString(); strRet += resMaxContinRetraceDaysInfo.getString(); strRet += "--------------------- "; strRet += resMaxContinRiseInfo.getString(); strRet += resMaxRiseInfo.getString(); strRet += resMaxRiseFromBeginInfo.getString(); strRet += resMaxContinRiseDaysInfo.getString(); return strRet; } void print() { resRetraceRiseFromBeginInfo.print(); cout << "---------------------" << endl; resMaxContinRetraceInfo.print(); resMaxRetraceInfo.print(); resMaxRetraceFromBeginInfo.print(); resMaxContinRetraceDaysInfo.print(); cout << "---------------------" << endl; resMaxContinRiseInfo.print(); resMaxRiseInfo.print(); resMaxRiseFromBeginInfo.print(); resMaxContinRiseDaysInfo.print(); } }ResultInfo; int getRectracement(const vector<NetValueInfo> &vecNetValueInfo, size_t nBegPos, size_t nEndPos, OUT ResultInfo& resultInfo); void testGetRectracement();
stdafx.h
// stdafx.h : 标准系统包含文件的包含文件, // 或是经常使用但不常更改的 // 特定于项目的包含文件 // #pragma once #define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料 #include <stdio.h> #include <tchar.h> // TODO: 在此处引用程序需要的其他头文件 #include <stdlib.h> #include <string> #include <vector> #include <iostream> using namespace std; #include <windows.h> #include <assert.h> #define itoa _itoa const string& stringReplace(string& str, const string& strToReplace, const string& strReplaceTo); char *stringReplace(char *str, const char *strToReplace, const char *strReplaceTo); // UTF8编码转换到GBK编码 int UTF8ToGBK(const char *lpUTF8Str, char *lpGBKStr,int nGBKStrLen); //GBK编码转换到UTF8编码 int GBKToUTF8(const char *lpGBKStr, char *lpUTF8Str, int nUTF8StrLen); enum URL_TYPE{unkowurl, eastmoney, howbuy, fund123, jjmmw, qq};
getFundNetValue.cpp
// getFundNetValue.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <algorithm> #include <string> #include <map> #include <ctime> #include <algorithm> #include "getRetracement.h" using namespace std; URL_TYPE getUrlType(const string& url) { string urlLower = url; transform(urlLower.begin(), urlLower.end(), urlLower.begin(), ::tolower); if (urlLower.find("eastmoney.com") != string::npos || url.find("1234567.com") != string::npos ) { return eastmoney; } else if (urlLower.find("howbuy.com") != string::npos) { return howbuy; } else if (urlLower.find("fund123.cn") != string::npos) { return fund123; } else if (urlLower.find("jjmmw.com") != string::npos) { return jjmmw; } else if (urlLower.find("qq.com") != string::npos) { return qq; } else { return unkowurl; } } size_t splitToVector(const char *src, const char *separator, vector<string>& vecOut) { const char *pFound, *pBegin = src; const size_t nSeparatorLen = strlen(separator); vecOut.clear(); while ( NULL != (pFound = strstr(pBegin, separator)) ) { vecOut.push_back(string(pBegin, pFound)); pBegin = pFound + nSeparatorLen; } if (*pBegin != '