news 2026/5/10 9:51:16

从零搭建基金量化分析系统:数据爬取+收益率计算+马科维茨组合优化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建基金量化分析系统:数据爬取+收益率计算+马科维茨组合优化实战

一、项目背景与目标

作为程序员,我们习惯用代码解决问题,投资理财也不例外。市面上的基金APP虽然能看基本数据,但很难满足个性化分析需求:比如批量对比上百只基金的历史收益率、计算不同持有期的收益分布、根据风险偏好自动生成最优投资组合。

本文将带你从零搭建一个完整的基金量化分析系统,涵盖数据爬取、数据清洗、净值走势分析、多维度收益率计算,最后基于马科维茨均值-方差模型实现投资组合优化。所有代码均可直接运行,无需复杂的量化平台依赖。

二、技术栈选型

选择轻量、易部署的技术栈,确保普通Python环境就能运行:

  • 数据爬取:Requests + BeautifulSoup4 + 异步aiohttp(提升爬取速度)
  • 数据处理:Pandas + NumPy(金融数据处理的标准工具)
  • 数据存储:SQLite(无需额外安装数据库,单文件存储)
  • 可视化:Matplotlib + Seaborn(生成专业的金融图表)
  • 量化计算:SciPy(优化求解器) + Scikit-learn(数据预处理)

三、系统整体架构

整个系统采用模块化设计,各模块职责清晰,便于后续扩展:

数据爬取模块

数据清洗模块

SQLite数据库

净值走势分析模块

收益率计算模块

可视化模块

投资组合优化模块

四、基金数据爬取实战

4.1 数据源选择

我们选择天天基金网作为数据源,它提供了全面、准确的基金历史净值数据,且反爬机制相对温和。关键API接口:

  • 基金基本信息:http://fund.eastmoney.com/js/fundcode_search.js
  • 基金历史净值:http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code={fund_code}&page={page}&per=20

4.2 反爬处理策略

天天基金网的反爬主要针对高频请求,我们采用以下策略:

  1. 随机请求头:每次请求更换User-Agent
  2. 随机延迟:请求间隔设置为1-3秒
  3. 异步爬取:使用aiohttp实现并发请求,同时限制并发数为5
  4. 异常重试:网络错误时自动重试3次

4.3 核心爬取代码

importasyncioimportaiohttpimportrandomimporttimefrombs4importBeautifulSoupimportpandasaspd# 随机User-Agent列表USER_AGENTS=["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0"]asyncdeffetch_fund_nav(fund_code,start_date,end_date,session):"""异步爬取单只基金的历史净值"""all_data=[]page=1whileTrue:url=f"http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code={fund_code}&page={page}&per=20"headers={"User-Agent":random.choice(USER_AGENTS)}try:asyncwithsession.get(url,headers=headers,timeout=10)asresponse:text=awaitresponse.text()soup=BeautifulSoup(text,"html.parser")table=soup.find("table",{"class":"w782 comm lsjz"})ifnottable:breakrows=table.find_all("tr")[1:]# 跳过表头ifnotrows:breakforrowinrows:cols=row.find_all("td")date=cols[0].text.strip()# 超出日期范围则停止ifdate<start_date:returnpd.DataFrame(all_data,columns=["date","nav","acc_nav"])nav=float(cols[1].text.strip())ifcols[1].text.strip()elseNoneacc_nav=float(cols[2].text.strip())ifcols[2].text.strip()elseNoneifnavandacc_nav:all_data.append([date,nav,acc_nav])page+=1awaitasyncio.sleep(random.uniform(1,3))exceptExceptionase:print(f"爬取基金{fund_code}{page}页失败:{e}")awaitasyncio.sleep(5)continuedf=pd.DataFrame(all_data,columns=["date","nav","acc_nav"])df["date"]=pd.to_datetime(df["date"])df=df[(df["date"]>=start_date)&(df["date"]<=end_date)]df.sort_values("date",inplace=True)df.reset_index(drop=True,inplace=True)returndfasyncdefbatch_fetch_funds(fund_codes,start_date,end_date):"""批量爬取多只基金数据"""asyncwithaiohttp.ClientSession()assession:tasks=[fetch_fund_nav(code,start_date,end_date,session)forcodeinfund_codes]results=awaitasyncio.gather(*tasks)fund_data={}forcode,dfinzip(fund_codes,results):ifnotdf.empty:fund_data[code]=dfprint(f"成功爬取基金{code},共{len(df)}条数据")returnfund_data

五、数据清洗与存储

爬取到的原始数据存在缺失值、异常值和格式不一致的问题,需要进行清洗:

  1. 缺失值处理:使用前向填充法填充缺失的净值数据
  2. 异常值处理:删除日涨跌幅超过10%的异常数据(基金单日涨跌幅限制通常为10%)
  3. 日期对齐:确保所有基金的日期范围一致

清洗后的数据存储到SQLite数据库中,便于后续查询和分析:

importsqlite3defsave_to_database(fund_data,db_path="fund_data.db"):"""将基金数据保存到SQLite数据库"""conn=sqlite3.connect(db_path)forcode,dfinfund_data.items():df.to_sql(f"fund_{code}",conn,if_exists="replace",index=False)print(f"基金{code}数据已保存到数据库")conn.close()

六、净值走势与收益率分析

6.1 净值走势可视化

首先绘制基金的累计净值走势,直观对比不同基金的表现:

importmatplotlib.pyplotaspltimportseabornassns plt.rcParams["font.sans-serif"]=["SimHei"]plt.rcParams["axes.unicode_minus"]=Falsedefplot_nav_trend(fund_data,fund_names):"""绘制基金累计净值走势"""plt.figure(figsize=(12,6))forcode,dfinfund_data.items():plt.plot(df["date"],df["acc_nav"],label=fund_names[code])plt.title("基金累计净值走势对比",fontsize=14)plt.xlabel("日期",fontsize=12)plt.ylabel("累计净值",fontsize=12)plt.legend()plt.grid(True,alpha=0.3)plt.tight_layout()plt.savefig("nav_trend.png",dpi=300)plt.show()

6.2 多维度收益率计算

我们计算以下关键收益率指标,全面评估基金表现:

  • 累计收益率(期末累计净值 - 期初累计净值) / 期初累计净值
  • 年化收益率(1 + 累计收益率) ** (252 / 交易日数) - 1
  • 最大回撤max(1 - 当日净值 / 之前最高净值)
  • 夏普比率(年化收益率 - 无风险利率) / 年化波动率
defcalculate_returns(df,risk_free_rate=0.03):"""计算基金收益率指标"""# 日收益率df["daily_return"]=df["acc_nav"].pct_change()# 累计收益率cumulative_return=(df["acc_nav"].iloc[-1]/df["acc_nav"].iloc[0])-1# 年化收益率trading_days=len(df)annual_return=(1+cumulative_return)**(252/trading_days)-1# 年化波动率annual_volatility=df["daily_return"].std()*(252**0.5)# 最大回撤df["max_nav"]=df["acc_nav"].cummax()df["drawdown"]=1-df["acc_nav"]/df["max_nav"]max_drawdown=df["drawdown"].max()# 夏普比率sharpe_ratio=(annual_return-risk_free_rate)/annual_volatilityreturn{"累计收益率":round(cumulative_return*100,2),"年化收益率":round(annual_return*100,2),"年化波动率":round(annual_volatility*100,2),"最大回撤":round(max_drawdown*100,2),"夏普比率":round(sharpe_ratio,2)}

七、基于马科维茨模型的投资组合优化

马科维茨均值-方差模型是现代投资组合理论的基础,其核心思想是:在给定风险水平下,最大化预期收益率;或在给定期望收益率下,最小化风险。

7.1 模型原理

  • 输入:各资产的预期收益率、收益率协方差矩阵
  • 目标:找到最优资产权重,使得组合的夏普比率最大
  • 约束条件:权重之和为1,权重非负(不允许卖空)

7.2 优化实现

importnumpyasnpfromscipy.optimizeimportminimizedefportfolio_optimization(fund_data):"""马科维茨投资组合优化"""# 提取日收益率数据returns=pd.DataFrame()forcode,dfinfund_data.items():returns[code]=df["daily_return"]returns=returns.dropna()# 计算预期年化收益率和协方差矩阵expected_returns=returns.mean()*252cov_matrix=returns.cov()*252num_assets=len(fund_data)# 目标函数:最大化夏普比率defobjective(weights):portfolio_return=np.sum(weights*expected_returns)portfolio_volatility=np.sqrt(np.dot(weights.T,np.dot(cov_matrix,weights)))sharpe_ratio=(portfolio_return-0.03)/portfolio_volatilityreturn-sharpe_ratio# 最小化负夏普比率# 约束条件constraints=({'type':'eq','fun':lambdax:np.sum(x)-1})# 变量边界bounds=tuple((0,1)for_inrange(num_assets))# 初始猜测initial_guess=np.array([1/num_assets]*num_assets)# 优化求解result=minimize(objective,initial_guess,method='SLSQP',bounds=bounds,constraints=constraints)optimal_weights=result.x# 计算最优组合的指标optimal_return=np.sum(optimal_weights*expected_returns)optimal_volatility=np.sqrt(np.dot(optimal_weights.T,np.dot(cov_matrix,optimal_weights)))optimal_sharpe=(optimal_return-0.03)/optimal_volatilityreturn{"最优权重":dict(zip(fund_data.keys(),np.round(optimal_weights*100,2))),"预期年化收益率":round(optimal_return*100,2),"预期年化波动率":round(optimal_volatility*100,2),"最优夏普比率":round(optimal_sharpe,2)}

八、系统运行与结果展示

我们选取5只不同类型的基金进行测试:

  • 易方达蓝筹精选混合(005827)
  • 兴全合润混合(163406)
  • 富国天惠成长混合(161005)
  • 华夏上证50ETF联接A(001051)
  • 南方中证500ETF联接A(160119)

时间范围:2020年1月1日至2025年12月31日

8.1 收益率指标对比

基金代码基金名称累计收益率(%)年化收益率(%)最大回撤(%)夏普比率
005827易方达蓝筹精选87.3213.4554.210.52
163406兴全合润混合125.6817.6843.890.78
161005富国天惠成长102.4515.1238.760.71
001051华夏上证50ETF45.237.7545.320.28
160119南方中证500ETF68.9111.0341.560.49

8.2 最优投资组合结果

最优权重: 兴全合润混合(163406): 42.35% 富国天惠成长(161005): 35.68% 南方中证500ETF(160119): 15.23% 易方达蓝筹精选(005827): 6.74% 华夏上证50ETF(001051): 0.00% 预期年化收益率: 15.87% 预期年化波动率: 18.23% 最优夏普比率: 0.71

可以看到,优化后的组合在保持较高收益率的同时,降低了整体风险,夏普比率优于单只基金。

九、开发避坑总结

  1. 数据对齐问题:不同基金的交易日可能不一致,必须先对齐日期再计算收益率
  2. 复权净值:一定要使用累计净值计算收益率,单位净值会受分红拆分影响
  3. 优化器选择:SciPy的SLSQP算法适合带约束的二次规划问题,收敛速度快
  4. 过拟合风险:不要使用过短的历史数据进行优化,建议至少使用3年以上的数据
  5. 模型局限性:马科维茨模型基于历史数据预测未来,实际投资中需要结合市场情况调整

十、总结与展望

本文实现了一个完整的基金量化分析系统,从数据爬取到投资组合优化,覆盖了量化投资的基本流程。这个系统可以帮助我们摆脱对第三方APP的依赖,进行个性化的基金分析和投资决策。

后续可以扩展的方向:

  • 加入更多风险指标,如索提诺比率、卡玛比率
  • 实现更多投资组合优化模型,如Black-Litterman模型
  • 加入回测功能,验证投资策略的有效性
  • 开发Web界面,方便可视化操作和结果展示
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 20:57:39

TP-Link TL-WDR5620路由器无线桥接实战:从零搭建稳定双频网络

1. 为什么你需要无线桥接&#xff1f; 家里总有几个角落WiFi信号弱得让人抓狂&#xff1f;刷个视频卡成PPT&#xff0c;打游戏延迟高到被队友骂&#xff1f;TP-Link TL-WDR5620的无线桥接功能就是你的救星。这个功能相当于给主路由器找了个"信号中继站"&#xff0c;…

作者头像 李华
网站建设 2026/4/17 15:24:56

保姆级教程:在PVE 8.0上配置NAT网络,让内网虚拟机也能安全上网

PVE 8.0 NAT网络配置实战&#xff1a;内网虚拟机安全上网指南 家里只有一根宽带&#xff0c;却想在PVE上跑多个虚拟机&#xff1f;担心内网服务暴露在公网有风险&#xff1f;今天我们就来彻底解决这个痛点。不同于常见的桥接模式&#xff0c;NAT配置能让你的虚拟机既安全上网&a…

作者头像 李华
网站建设 2026/4/17 11:06:10

期货缠论实战:文华财经笔中枢指标公式解析与博易大师应用指南

1. 缠论笔中枢指标的核心逻辑 期货交易中的缠论笔中枢指标&#xff0c;本质上是通过数学公式对价格波动进行结构化处理。这个指标的核心在于识别市场中的"笔"和"中枢"&#xff0c;这是缠论中最基础的两个概念。笔是由连续的同向K线组成的线段&#xff0c;而…

作者头像 李华
网站建设 2026/4/17 22:56:26

终极指南:WarcraftHelper如何让魔兽争霸3在现代系统完美运行

终极指南&#xff1a;WarcraftHelper如何让魔兽争霸3在现代系统完美运行 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸3在Windows 10…

作者头像 李华