问题描述
q = query(
indicator.code,
indicator.pubDate,
indicator.statDate,
indicator.inc_net_profit_year_on_year
)
df = get_fundamentals(
q.filter(
indicator.pubDate == '2020-08-20',
indicator.inc_net_profit_year_on_year > 20)
.order_by(
indicator.inc_net_profit_year_on_year.desc()))
df['name'] = df.code.map(lambda x: get_security_info(x).display_name)
df
本意是想找出中报的净利润同比增长率大于20%的相关标的,把上面的代码放在研究中执行,结果却是:
code pubDate statDate inc_net_profit_year_on_year name
0 300273.XSHE 2020-08-20 2020-06-30 7863.7700 和佳医疗
1 600592.XSHG 2020-08-20 2020-06-30 2678.3999 龙溪股份
2 603363.XSHG 2020-08-20 2020-06-30 1974.2800 傲农生物
3 002950.XSHE 2020-08-20 2020-06-30 1009.8400 奥美医疗
可以见到 300273.XSHE(和佳医疗)中报净利润同比增长率(%)为7863.7700??????
随便在任何一个股票软件都可以很明显看到和佳医疗中报的净利润增长是下降的。
当然除了indicator表,我看了下income表的净利润数据也是跟股票软件相差甚远的。
如果说中报的数据还没全部更新,但是同样的,我加入了statDate参数查了下2020一季度的数据,净利润同比增长率这个数据还是不准确的。
df = get_fundamentals(q.filter(
indicator.inc_net_profit_year_on_year > 20)
.order_by(
indicator.inc_net_profit_year_on_year.desc()
), statDate='2020q1')
也不知道我的使用方法是否有问题,还是聚宽的数据本来就是不准确的,总之求各位大佬解答,该怎么才能选出净利润同比增长率大于20%的相关标的,谢谢大家。
解决方案
你好!你遇到的这个问题是量化新手在使用聚宽财务数据时非常常见的一个“坑”。聚宽的数据并没有错,而是统计口径与普通炒股软件不同。
核心原因剖析:单季度 vs 累计
-
聚宽的财务数据默认是“单季度”的:
在 JoinQuant 中,indicator表和income表的数据默认都是单季度数据。当你查询statDate='2020-06-30'(即 2020q2)时,你获取到的inc_net_profit_year_on_year是2020年第二季度单季相比于2019年第二季度单季的同比增长率。 -
炒股软件的数据通常是“累计”的:
同花顺、东方财富等软件上显示的中报(半年报)净利润同比增长率,是**上半年累计(Q1 + Q2)**相比于去年上半年累计的同比增长率。
以你提到的“和佳医疗 (300273.XSHE)”为例:
- 假设它 2019年 Q2 单季度的净利润极低(比如只有几十万),而 2020年 Q2 单季度恢复到了几千万,那么单季度同比确实会暴增 7800%+。
- 但是,如果它 2019年 Q1 的净利润非常高,导致 2019年上半年(Q1+Q2)总基数很大,那么 2020年上半年(Q1+Q2)的累计同比就会显示为下降。
这就是为什么你看到的数据与炒股软件相差甚远的原因。
如何正确选出“中报累计净利润同比增长率 > 20%”的标的?
既然聚宽提供的是单季度数据,我们需要自己将 Q1 和 Q2 的净利润相加,计算出上半年的累计净利润,然后再计算同比增长率。
以下是完整的 Python 实现代码,你可以直接在聚宽的“研究”模块中运行:
from jqdata import *
import pandas as pd
# 1. 定义查询对象,只查代码和净利润
q = query(income.code, income.net_profit)
# 2. 分别获取 2020年和2019年 Q1、Q2 的单季度净利润
df_2020q1 = get_fundamentals(q, statDate='2020q1')
df_2020q2 = get_fundamentals(q, statDate='2020q2')
df_2019q1 = get_fundamentals(q, statDate='2019q1')
df_2019q2 = get_fundamentals(q, statDate='2019q2')
# 3. 合并并计算 2020年上半年(中报)累计净利润
df_2020 = pd.merge(df_2020q1, df_2020q2, on='code', suffixes=('_q1', '_q2'))
df_2020['net_profit_2020_h1'] = df_2020['net_profit_q1'] + df_2020['net_profit_q2']
# 4. 合并并计算 2019年上半年(中报)累计净利润
df_2019 = pd.merge(df_2019q1, df_2019q2, on='code', suffixes=('_q1', '_q2'))
df_2019['net_profit_2019_h1'] = df_2019['net_profit_q1'] + df_2019['net_profit_q2']
# 5. 将两年中报数据合并到一张表
df_total = pd.merge(df_2020, df_2019, on='code')
# 6. 过滤掉去年同期净利润为负或为0的标的(避免计算增长率时出现异常或失去意义)
df_total = df_total[df_total['net_profit_2019_h1'] > 0]
# 7. 计算累计净利润同比增长率 (%)
df_total['inc_rate_h1'] = (df_total['net_profit_2020_h1'] - df_total['net_profit_2019_h1']) / df_total['net_profit_2019_h1'] * 100
# 8. 筛选同比增长率大于 20% 的标的,并按增长率降序排列
result = df_total[df_total['inc_rate_h1'] > 20].sort_values(by='inc_rate_h1', ascending=False)
# 9. 映射股票名称以便查看
result['name'] = result['code'].map(lambda x: get_security_info(x).display_name)
# 打印前20条结果
print(result[['code', 'name', 'inc_rate_h1']].head(20))
总结建议
在量化回测中,使用单季度数据(如聚宽默认提供的)往往比使用累计数据更能真实反映企业近期的经营拐点。但如果你需要复刻传统基本面选股策略中的“中报/年报累计增长”,请务必像上面这样手动将单季度数据进行累加计算。