🚀 AI 一键生成 joinquant 策略代码

为什么 JoinQuant 中上市不足5年的股票 sales_growth 因子不是 NaN?如何处理?

joinquant | 发布时间: 2026/5/27
以下内容由 EasyQuant 生成。

问题描述

sales_growth这个指标不够五年应该返回NaN才对

'603185.XSHG' 和 '601236.XSHG' 应该上市都不够五年,但都返回了数值,做排序什么的会有影响。怎么办?

df1 = get_factor_values(['603185.XSHG','601236.XSHG'], ['sales_growth'], end_date='2022-12-02', count=1)

display(pd.concat(df1))

q = query(indicator.eps, income.np_parent_company_owners, indicator.code).filter(
indicator.code.in_(['603185.XSHG','601236.XSHG'])
)
df3 = get_fundamentals(q, statDate='2018')
display('2018',df3)

df4 = get_fundamentals(q, statDate='2017')
display('2017',df4)
成长因子 growth

定义:0.18•long_term_predicted_earnings_growth + 0.11•short_term_predicted_earnings_growth + 0.24•earnings_growth + 0.47•sales_growth
解释
预期长期盈利增长率 long_term_predicted_earnings_growth:未来三年净利润分析师一致预期相对于净利润(不含少数股东损益)最新年报值的平均增长率。一个分析师预测的股票值为nan。
预期短期盈利增长率 short_term_predicted_earnings_growth:未来一年净利润分析师一致预期相对于净利润(不含少数股东损益)最新年报值的平均增长率,只有一个分析师预测的股票值为nan。
5年盈利增长率 earnings_growth:盈利增长率,过去5年的基本每股收益(basic_eps)关于[0,1,2,3,4]回归的斜率系数,然后再除以过去 5 年基本每股收益的均值的绝对值。
5年营业收入增长率 sales_growth:营收增长率,过去 5 年每股营业收入关于[0,1,2,3,4]回归的斜率系数,然后再除以过去 5 年每股营业收入的均值的绝对值。对于保险行业的股票,使用“已赚保费”代替“销售收入”计算每股营业收入,对于银行业的股票,sales_growth为nan。
earnings_growth和sales_growth至少需要有4年的财务数据,否则为nan。

解决方案

您好!您遇到的这个问题在量化数据处理中非常典型。简单来说,“上市时间不足5年”并不等于“财务数据不足4年”

以下是详细的原因分析以及在策略中规避此问题的解决方案。

一、 为什么上市不足5年的股票会有数值?

根据 JoinQuant 官方文档对 growth 风格因子的定义:

sales_growth 至少需要有 4年 的财务数据,否则为 nan。

这里强调的是财务数据的年限,而不是上市时间的年限。

在中国 A 股市场,公司在进行 IPO(首次公开募股)时,招股说明书中通常强制要求披露过去 3 年的完整财务数据。这意味着,当一只股票敲钟上市的第一天,数据库中就已经包含了它过去 3 年的年度财务数据。

  • 举个例子:假设某公司 2021 年上市,那么它在 2021 年就已经有了 2018、2019、2020 年的财务数据。
  • 当它上市满 1 年(到了 2022 年发布年报后),它在数据库中就拥有了 2018、2019、2020、2021 年共计 4年 的财务数据。

此时,它已经满足了 JoinQuant 因子库中 sales_growth 因子“至少需要有4年的财务数据”的计算条件,因此底层算法会利用这 4 年的数据进行线性回归计算斜率,从而返回一个具体的数值,而不是 NaN

二、 这种现象对排序的影响及解决办法

由于包含了上市前的数据,且次新股在上市前后的业绩波动特征往往与成熟期企业不同,直接将它们与老股放在一起进行因子排序(Rank)确实可能会产生偏差。

最佳的解决办法是:在获取因子值或进行排序之前,主动在股票池中剔除上市时间不足 N 年的股票(即剔除次新股)。

在量化策略中,剔除次新股、ST股、停牌股是标准的数据清洗流程。您不能完全依赖因子本身返回 NaN 来过滤股票。

Python 代码解决方案:主动过滤次新股

您可以使用 JoinQuant 提供的 get_security_info API 获取股票的上市日期 (start_date),然后计算其上市天数,将上市不足 5 年(或您认为合适的年限)的股票从列表中剔除。

import pandas as pd
import datetime
from jqdata import *

def filter_new_stocks(security_list, current_date, min_listed_days=365*5):
    """
    过滤掉上市时间不足 min_listed_days 天的股票
    
    :param security_list: 原始股票代码列表
    :param current_date: 当前逻辑日期 (datetime.date 或 datetime.datetime)
    :param min_listed_days: 最小上市天数,默认 5 年 (约 1825 天)
    :return: 过滤后的股票代码列表
    """
    valid_stocks = []
    for stock in security_list:
        info = get_security_info(stock)
        if not info:
            continue
        
        # 计算上市至今的天数
        listed_days = (current_date - info.start_date).days
        
        # 如果上市天数大于等于设定的阈值,则保留
        if listed_days >= min_listed_days:
            valid_stocks.append(stock)
            
    return valid_stocks

# ================= 测试示例 =================

# 假设当前回测/研究日期为 2022-12-02
current_dt = datetime.date(2022, 12, 2)

# 原始股票池,包含您提到的两只次新股和一只老股(平安银行)
raw_stocks = ['603185.XSHG', '601236.XSHG', '000001.XSHE']

# 1. 过滤掉上市不足 5 年 (1825天) 的股票
filtered_stocks = filter_new_stocks(raw_stocks, current_dt, min_listed_days=1825)
print(f"当前日期: {current_dt}")
print(f"原始股票池: {raw_stocks}")
print(f"过滤上市不足5年的股票后: {filtered_stocks}")

# 2. 仅对过滤后的老股获取 sales_growth 因子
if filtered_stocks:
    df_factor = get_factor_values(filtered_stocks, ['sales_growth'], end_date=current_dt, count=1)
    print("\n因子值获取结果:")
    display(df_factor['sales_growth'])
else:
    print("\n没有符合条件的股票。")

总结

  1. 机制正常:JoinQuant 因子库返回数值是符合其逻辑的,因为包含了招股书披露的上市前财务数据,满足了“至少4年数据”的条件。
  2. 主动清洗:为了避免次新股的财务特征干扰您的横截面排序(Cross-sectional Ranking),强烈建议在策略的 before_trading_start 或选股逻辑中,封装一个 filter_new_stocks 函数,主动将上市不足 3 年或 5 年的股票剔除出您的候选股票池(Universe)。