问题描述
官方描述:get_fundamental_continuously,出于性能方面考虑,我们做出了返回总条数不超过5000条的限制。 也就是说:查询的股票数量*count 要小于5000。 否则,返回的数据会不完整。
在做一些时间序列分析时,比如需要pe_ratio按天的数据,但5000个数据甚至只允许一只股票查询10几年的数据。
要是查几只股票的数据,时间段就特别短。
这个数也和get_price一样有挺多用的地方的。
可以考虑下放开这个函数的限制吗?
解决方案
在 JoinQuant (聚宽) 平台上,get_fundamentals_continuously 和 get_fundamentals 函数确实存在单次查询返回总条数不超过 5000 条的硬性限制(即:查询的股票数量 * count < 5000)。官方设置此限制主要是出于底层数据库性能和服务器资源保护的考虑,防止单次超大查询导致系统卡顿或崩溃。
虽然官方目前无法直接放开单次查询的硬性限制,但我们可以通过编写 Python 代码分批次循环获取数据并拼接的方式,轻松绕过这个限制,满足长周期时间序列分析的需求。
解决方案:分批循环获取数据
核心思路是:将一个长周期的时间段拆分成多个短周期(确保每次请求的数据量小于 5000),循环调用 API 获取数据,最后使用 pandas.concat 将结果合并。
示例代码:按时间分批获取长周期财务数据
以下代码展示了如何获取单只或多只股票超过 5000 条限制的 pe_ratio 数据:
import pandas as pd
import datetime
from jqdata import *
def get_long_term_fundamentals(query_object, end_date, total_count, step=100):
"""
分批获取长期财务数据,突破 5000 条限制
:param query_object: sqlalchemy.orm.query.Query 对象
:param end_date: 结束日期 (str 或 datetime)
:param total_count: 需要获取的总天数
:param step: 每次获取的天数 (需保证 股票数 * step < 5000)
:return: 拼接好的 pandas.DataFrame
"""
if isinstance(end_date, str):
end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d').date()
elif isinstance(end_date, datetime.datetime):
end_date = end_date.date()
all_data = []
current_end_date = end_date
remaining_count = total_count
# 获取交易日历,用于精确计算时间推移
trade_days = get_all_trade_days()
trade_days = list(trade_days[trade_days <= current_end_date])
while remaining_count > 0:
# 计算本次需要获取的天数
current_step = min(step, remaining_count)
# 调用 API 获取数据 (注意:panel=False 返回 DataFrame 更易于处理)
df = get_fundamentals_continuously(query_object, end_date=current_end_date, count=current_step, panel=False)
if df is not None and not df.empty:
all_data.append(df)
remaining_count -= current_step
# 更新下一次查询的 end_date (往前推 current_step 个交易日)
if remaining_count > 0:
# 找到当前 end_date 在交易日历中的索引
try:
idx = trade_days.index(current_end_date)
# 往前推 current_step 天
if idx >= current_step:
current_end_date = trade_days[idx - current_step]
else:
break # 交易日历不足,退出
except ValueError:
break
if all_data:
# 将所有分批数据拼接起来,并按日期排序
final_df = pd.concat(all_data)
# 如果有重复数据可以去重
final_df = final_df.drop_duplicates()
return final_df
else:
return pd.DataFrame()
# --- 使用示例 ---
# 假设我们要查询 5 只股票过去 2000 个交易日(约 8 年)的 PE 数据
# 股票数(5) * count(2000) = 10000 > 5000,直接查会报错或数据不全
stocks = ['000001.XSHE', '600000.XSHG', '000002.XSHE', '600036.XSHG', '601398.XSHG']
q = query(valuation.code, valuation.day, valuation.pe_ratio).filter(valuation.code.in_(stocks))
# 每次查 500 天 (5只股票 * 500天 = 2500条 < 5000条)
long_term_df = get_long_term_fundamentals(q, end_date='2023-10-01', total_count=2000, step=500)
print(f"成功获取数据条数: {len(long_term_df)}")
print(long_term_df.head())
替代方案:使用 get_valuation 函数
如果您仅仅是需要市值表(valuation)中的数据(例如 pe_ratio, pb_ratio, market_cap 等),JoinQuant 提供了一个专门的 API:get_valuation。
虽然 get_valuation 同样有每次最多返回 5000 条数据的限制,但它的调用方式更简单,同样可以通过上述的“分批按时间段循环”的方法来获取长周期数据。
总结建议
- 合理设置 Step:在编写分批循环时,
step(每次查询的天数)乘以您的股票池数量,必须严格小于 5000。 - 注意 Panel 参数:在 Pandas 较新版本中
Panel已被废弃,建议在调用get_fundamentals_continuously时显式设置panel=False,直接获取 DataFrame,这样在后续使用pd.concat拼接时会更加方便。 - 本地化存储:如果您在研究环境中需要频繁使用这些长周期的历史财务数据,建议分批下载后,使用
write_file保存为 CSV 文件,后续直接read_file读取,这样可以大幅提升研究效率,减少 API 调用耗时。