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

张开发
2026/4/13 10:56:11 15 分钟阅读

分享文章

从零搭建基金量化分析系统:数据爬取+收益率计算+马科维茨组合优化实战
一、项目背景与目标作为程序员我们习惯用代码解决问题投资理财也不例外。市面上的基金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?typelsjzcode{fund_code}page{page}per204.2 反爬处理策略天天基金网的反爬主要针对高频请求我们采用以下策略随机请求头每次请求更换User-Agent随机延迟请求间隔设置为1-3秒异步爬取使用aiohttp实现并发请求同时限制并发数为5异常重试网络错误时自动重试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[]page1whileTrue:urlfhttp://fund.eastmoney.com/f10/F10DataApi.aspx?typelsjzcode{fund_code}page{page}per20headers{User-Agent:random.choice(USER_AGENTS)}try:asyncwithsession.get(url,headersheaders,timeout10)asresponse:textawaitresponse.text()soupBeautifulSoup(text,html.parser)tablesoup.find(table,{class:w782 comm lsjz})ifnottable:breakrowstable.find_all(tr)[1:]# 跳过表头ifnotrows:breakforrowinrows:colsrow.find_all(td)datecols[0].text.strip()# 超出日期范围则停止ifdatestart_date:returnpd.DataFrame(all_data,columns[date,nav,acc_nav])navfloat(cols[1].text.strip())ifcols[1].text.strip()elseNoneacc_navfloat(cols[2].text.strip())ifcols[2].text.strip()elseNoneifnavandacc_nav:all_data.append([date,nav,acc_nav])page1awaitasyncio.sleep(random.uniform(1,3))exceptExceptionase:print(f爬取基金{fund_code}第{page}页失败:{e})awaitasyncio.sleep(5)continuedfpd.DataFrame(all_data,columns[date,nav,acc_nav])df[date]pd.to_datetime(df[date])dfdf[(df[date]start_date)(df[date]end_date)]df.sort_values(date,inplaceTrue)df.reset_index(dropTrue,inplaceTrue)returndfasyncdefbatch_fetch_funds(fund_codes,start_date,end_date):批量爬取多只基金数据asyncwithaiohttp.ClientSession()assession:tasks[fetch_fund_nav(code,start_date,end_date,session)forcodeinfund_codes]resultsawaitasyncio.gather(*tasks)fund_data{}forcode,dfinzip(fund_codes,results):ifnotdf.empty:fund_data[code]dfprint(f成功爬取基金{code}共{len(df)}条数据)returnfund_data五、数据清洗与存储爬取到的原始数据存在缺失值、异常值和格式不一致的问题需要进行清洗缺失值处理使用前向填充法填充缺失的净值数据异常值处理删除日涨跌幅超过10%的异常数据基金单日涨跌幅限制通常为10%日期对齐确保所有基金的日期范围一致清洗后的数据存储到SQLite数据库中便于后续查询和分析importsqlite3defsave_to_database(fund_data,db_pathfund_data.db):将基金数据保存到SQLite数据库connsqlite3.connect(db_path)forcode,dfinfund_data.items():df.to_sql(ffund_{code},conn,if_existsreplace,indexFalse)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],labelfund_names[code])plt.title(基金累计净值走势对比,fontsize14)plt.xlabel(日期,fontsize12)plt.ylabel(累计净值,fontsize12)plt.legend()plt.grid(True,alpha0.3)plt.tight_layout()plt.savefig(nav_trend.png,dpi300)plt.show()6.2 多维度收益率计算我们计算以下关键收益率指标全面评估基金表现累计收益率(期末累计净值 - 期初累计净值) / 期初累计净值年化收益率(1 累计收益率) ** (252 / 交易日数) - 1最大回撤max(1 - 当日净值 / 之前最高净值)夏普比率(年化收益率 - 无风险利率) / 年化波动率defcalculate_returns(df,risk_free_rate0.03):计算基金收益率指标# 日收益率df[daily_return]df[acc_nav].pct_change()# 累计收益率cumulative_return(df[acc_nav].iloc[-1]/df[acc_nav].iloc[0])-1# 年化收益率trading_dayslen(df)annual_return(1cumulative_return)**(252/trading_days)-1# 年化波动率annual_volatilitydf[daily_return].std()*(252**0.5)# 最大回撤df[max_nav]df[acc_nav].cummax()df[drawdown]1-df[acc_nav]/df[max_nav]max_drawdowndf[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):马科维茨投资组合优化# 提取日收益率数据returnspd.DataFrame()forcode,dfinfund_data.items():returns[code]df[daily_return]returnsreturns.dropna()# 计算预期年化收益率和协方差矩阵expected_returnsreturns.mean()*252cov_matrixreturns.cov()*252num_assetslen(fund_data)# 目标函数最大化夏普比率defobjective(weights):portfolio_returnnp.sum(weights*expected_returns)portfolio_volatilitynp.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})# 变量边界boundstuple((0,1)for_inrange(num_assets))# 初始猜测initial_guessnp.array([1/num_assets]*num_assets)# 优化求解resultminimize(objective,initial_guess,methodSLSQP,boundsbounds,constraintsconstraints)optimal_weightsresult.x# 计算最优组合的指标optimal_returnnp.sum(optimal_weights*expected_returns)optimal_volatilitynp.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联接A001051南方中证500ETF联接A160119时间范围2020年1月1日至2025年12月31日8.1 收益率指标对比基金代码基金名称累计收益率(%)年化收益率(%)最大回撤(%)夏普比率005827易方达蓝筹精选87.3213.4554.210.52163406兴全合润混合125.6817.6843.890.78161005富国天惠成长102.4515.1238.760.71001051华夏上证50ETF45.237.7545.320.28160119南方中证500ETF68.9111.0341.560.498.2 最优投资组合结果最优权重: 兴全合润混合(163406): 42.35% 富国天惠成长(161005): 35.68% 南方中证500ETF(160119): 15.23% 易方达蓝筹精选(005827): 6.74% 华夏上证50ETF(001051): 0.00% 预期年化收益率: 15.87% 预期年化波动率: 18.23% 最优夏普比率: 0.71可以看到优化后的组合在保持较高收益率的同时降低了整体风险夏普比率优于单只基金。九、开发避坑总结数据对齐问题不同基金的交易日可能不一致必须先对齐日期再计算收益率复权净值一定要使用累计净值计算收益率单位净值会受分红拆分影响优化器选择SciPy的SLSQP算法适合带约束的二次规划问题收敛速度快过拟合风险不要使用过短的历史数据进行优化建议至少使用3年以上的数据模型局限性马科维茨模型基于历史数据预测未来实际投资中需要结合市场情况调整十、总结与展望本文实现了一个完整的基金量化分析系统从数据爬取到投资组合优化覆盖了量化投资的基本流程。这个系统可以帮助我们摆脱对第三方APP的依赖进行个性化的基金分析和投资决策。后续可以扩展的方向加入更多风险指标如索提诺比率、卡玛比率实现更多投资组合优化模型如Black-Litterman模型加入回测功能验证投资策略的有效性开发Web界面方便可视化操作和结果展示

更多文章