问题描述
目前有一个需求,需要获取公募基金的历史股票持仓数据,根据搜索社区的文章,找到一个连接:https://www.joinquant.com/help/api/help#OTCfund:获取基金持股信息
Img
通过以上介绍可以获取到需要的基金持股信息。
但是根据文档说明:“为了防止返回数据量过大, 我们每次最多返回3000行”
由于限制了获取到的数据量,导致需要的信息不全,请问大家如何才能获取到全部完整的数据?还请不吝赐教,感谢大家!
解决方案
在 JoinQuant 聚宽平台上,为了保护数据库性能和防止内存溢出,finance.run_query 等查询接口通常会限制单次查询的最大返回行数(如 3000 行或 4000 行)。当您需要获取全量的公募基金历史股票持仓数据时,单次查询往往无法满足需求。
要突破这个限制,获取全部完整的数据,最标准且高效的方法是使用 分页查询(Pagination),即结合 SQLAlchemy 的 limit() 和 offset() 方法进行循环抓取。此外,也可以通过按时间段或基金代码分批查询来实现。
以下是具体的解决方案和代码示例:
方法一:使用 limit 和 offset 进行分页查询(推荐)
这是最通用的方法。limit(N) 表示每次最多取 N 条数据,offset(M) 表示跳过前 M 条数据。通过在一个 while 循环中不断增加 offset 的值,直到某次查询返回的数据量小于 limit,即可确认数据已全部获取完毕。
Python 代码示例:
import pandas as pd
from jqdata import *
def get_full_fund_portfolio(fund_code=None, start_date='2020-01-01'):
"""
获取完整的基金持股信息
:param fund_code: 基金代码,若为 None 则查询所有基金
:param start_date: 起始发布日期
"""
all_data = pd.DataFrame()
limit_num = 3000 # 聚宽单次查询限制,可设为 3000 或 4000
offset_num = 0
while True:
# 构建基础查询
# 注意:此处以 finance.FUND_PORTFOLIO_STOCK 为例,请根据实际表名调整
q = query(finance.FUND_PORTFOLIO_STOCK).filter(
finance.FUND_PORTFOLIO_STOCK.pub_date >= start_date
)
# 如果指定了基金代码,则增加过滤条件
if fund_code:
q = q.filter(finance.FUND_PORTFOLIO_STOCK.code == fund_code)
# 添加分页参数
q = q.limit(limit_num).offset(offset_num)
# 执行查询
df = finance.run_query(q)
# 如果返回为空,说明数据已取完
if df.empty:
break
# 将本次查询结果追加到总数据中
all_data = pd.concat([all_data, df], ignore_index=True)
# 如果本次返回的数据行数小于 limit_num,说明已经是最后一页
if len(df) < limit_num:
break
# 更新 offset,准备获取下一页
offset_num += limit_num
return all_data
# 使用示例:获取某只基金自 2020 年以来的全部持仓
# df_portfolio = get_full_fund_portfolio(fund_code='000001.OF', start_date='2020-01-01')
# print(df_portfolio)
方法二:按时间段(或基金代码)分批查询
如果您需要获取全市场所有基金多年的持仓数据,数据量会非常庞大。此时,即使使用 offset,随着偏移量的增大,数据库查询效率也会降低。更好的做法是将大查询拆分为多个小查询,例如按季度或按单只基金进行循环。
Python 代码示例(按基金代码循环):
import pandas as pd
from jqdata import *
# 假设您已经有了一个需要查询的基金代码列表
fund_list = ['000001.OF', '000011.OF', '000021.OF']
all_portfolio = pd.DataFrame()
for fund in fund_list:
# 针对单只基金,数据量通常不会超过 3000 行,直接查询即可
q = query(finance.FUND_PORTFOLIO_STOCK).filter(
finance.FUND_PORTFOLIO_STOCK.code == fund,
finance.FUND_PORTFOLIO_STOCK.pub_date >= '2015-01-01'
).limit(3000)
df = finance.run_query(q)
if not df.empty:
all_portfolio = pd.concat([all_portfolio, df], ignore_index=True)
print(f"共获取到 {len(all_portfolio)} 条持仓数据")
💡 核心建议与注意事项:
- 避免全表扫描:尽量不要在不加任何
filter(如日期、代码)的情况下直接查询全表,这不仅极慢,而且容易触发平台的资源限制。 - 合理使用
concat:在循环中拼接 DataFrame 时,使用pd.concat放入列表中一次性拼接,或者在循环中逐步拼接(如示例所示,但数据量极大时建议先存入 list,最后统一 concat 以提升性能)。 - 注意 API 限制:虽然可以通过分页获取全部数据,但请注意聚宽平台对每日查询总次数或总数据量的潜在限制,合理安排数据抓取任务,建议将抓取到的历史静态数据保存到本地或聚宽的研究环境中(使用
write_file),避免重复抓取。