继本专栏上期介绍上市公司利润表时间序列的标准化处理,本期介绍用于时间序列曲线形态识别的统计方法,以及批量识别时间序列曲线形态的计算机程序的Python代码实现。
当我们去识别时间序列随时间是增长还是下降,是匀速增长还是加速增长的时候,最简单的方法是使用绘图工具将时间序列数据绘制成折线图,再通过视觉判断时间序列的形态。但是,使用视觉判断的方法,在时间序列波动较大时,很容易陷入主观性视觉误判,这一点依靠人工标注进行训练的视觉智能算法也无法避免。尤其在处理大量时间序列曲线时,通过人工去进行时间序列形态分类的缺陷尤其突出。
通过定量化的区间估计统计方法并使用计算机程序加以实现,就能高效省时地解决批量时间序列形态识别的问题。
首先,处理时间序列数据 获得其1,2,3阶差分序列,,。
接着,对,, 分别进行显著性水平条件下的两侧单尾假设检验。
(1)计算获得的均值,标准差和自由度df
(2)查询获得t分布单侧显著性水平条件下的临界值
(3)进行右侧假设检验:
(3.1)建立虚拟假设,对立假设0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5En%20Y%3E0" />;
(3.2)计算的右侧临界值:
(3.3)如果right" src="https://latex.codecogs.com/gif.latex?%5CDelta%5En%20%5Coverline%7BY%7D%3Eright" />,则拒绝假设,接受对立假设0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5En%20Y%3E0" />。
(4)如果未接受假设0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5En%20Y%3E0" />,进行左侧假设检验:
(4.1)建立虚拟假设=0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5En%20Y%3E%3D0" />,对立假设;
(4.2)计算的左侧临界值:
(4.3)如果,则拒绝=0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5En%20Y%3E%3D0" />假设,接受对立假设。
(5)如果既未接受0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5En%20Y%3E0" />,也未接受,则不确定。
再根据上述的假设检验结果,按以下规则进行时间序列形态识别。
=> 随机波动
0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3Duncertain%2C%5CDelta%5E2Y%3Duncertain%2C%5CDelta%20Y%3E0" /> => 直线增长
=> 直线下降
0,Delta Y>0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3Duncertain%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3E0" /> => J形增长
0,Delta Y<0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3Duncertain%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3C0" /> => 反J形下降
0,Delta Y=uncertain" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3Duncertain%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3Duncertain" /> => U形先降后增
0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3Duncertain%2C%5CDelta%5E2Y%3C0%2C%5CDelta%20Y%3E0" /> => 反倒J形增长
=> 倒J形下降
=> 倒U形先增后降
0,Delta^2Y>0,Delta Y>0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3E0" /> => J形增长
0,Delta^2Y>0,Delta Y<0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3C0" /> => 反J形下降
0,Delta^2Y>0,Delta Y=uncertain" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3Duncertain" /> => U形先降后增
0,Delta^2Y<0,Delta Y>0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3C0%2C%5CDelta%20Y%3E0" /> => 反倒J形增长
0,Delta^2Y<0,Delta Y<0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3C0%2C%5CDelta%20Y%3C0" /> => 倒J形下降
0,Delta^2Y<0,Delta Y=uncertain" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3C0%2C%5CDelta%20Y%3Duncertain" /> => 倒U形先增后降
0,Delta^2Y=uncertain,Delta Y>0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3Duncertain%2C%5CDelta%20Y%3E0" /> => N形持续增长
0,Delta^2Y=uncertain,Delta Y<0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3Duncertain%2C%5CDelta%20Y%3C0" /> => 反S形下降
0,Delta^2Y=uncertain,Delta Y=uncertain" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3E0%2C%5CDelta%5E2Y%3Duncertain%2C%5CDelta%20Y%3Duncertain" />=> N形波折增长
0,Delta Y>0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3C0%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3E0" /> => J形增长
0,Delta Y<0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3C0%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3C0" /> => 反J形下降
0,Delta Y=uncertain" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3C0%2C%5CDelta%5E2Y%3E0%2C%5CDelta%20Y%3Duncertain" /> => U形先降后增
0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3C0%2C%5CDelta%5E2Y%3C0%2C%5CDelta%20Y%3E0" /> => 反倒J形增长
=> 倒J形下降
=> 倒U形先增后降
0" src="https://latex.codecogs.com/gif.latex?%5CDelta%5E3Y%3C0%2C%5CDelta%5E2Y%3Duncertain%2C%5CDelta%20Y%3E0" /> => S形增长
=> 倒N形持续下降
=> 倒N形波折下降
使用python实现上述统计识别功能,并且以按上期介绍方法进行标准化处理的沪深四千余上市公司的利润表历史每股数据(复权)作为示例,进行批量识别。附上代码:
# Author: YE KAIWEN
# Copyright (c) 2021 YE KAIWEN. All Rights Reserved.
import pandas as pd
from scipy.stats import t
#序列t检验
def series_t_test(series,alpha):
if (alpha<0)|(alpha>0.5):
raise Exception("非法Alpha")
seriesmean=series.mean()
seriesstd=series.std()
seriesdf=len(series)-1
positive_point = t.ppf(q=(1 - alpha), df=seriesdf) * seriesstd
negative_point = t.ppf(q=alpha, df=seriesdf) * seriesstd
if seriesmean>=positive_point:
return 'positive'
elif seriesmean<=negative_point:
return 'negative'
else:
return 'uncertain'
#获取n阶差分
def nth_difference(series,n):
ansseries=series.copy(deep=True)
for i in range(n):
#示例分析季度数据使用diff(4),非季节性数据可使用diff(1)
ansseries=ansseries.diff(4)
return ansseries.dropna()
#序列形态识别
def series_shape_recognition(series,alpha):
series_1st=nth_difference(series,1)
series_2nd=nth_difference(series_1st,1)
series_3rd=nth_difference(series_2nd,1)
if len(series_3rd)==0:
raise Exception("缺少三阶样本")
test_1st=series_t_test(series_1st,alpha)
test_2nd=series_t_test(series_2nd,alpha)
test_3rd=series_t_test(series_3rd,alpha)
test_res=(test_1st,test_2nd,test_3rd)
if test_3rd=='uncertain':
if test_2nd=='uncertain':
if test_1st=='uncertain':
return '随机波动',test_res
elif test_1st=='positive':
return '直线增长',test_res
elif test_1st=='negative':
return '直线下降',test_res
else:
raise Exception("未知1阶状态")
elif test_2nd=='positive':
if test_1st == 'positive':
return 'J形增长',test_res
elif test_1st == 'negative':
return '反J形下降',test_res
elif test_1st == 'uncertain':
return 'U形先降后增',test_res
else:
raise Exception("未知1阶状态")
elif test_2nd=='negative':
if test_1st == 'positive':
return '反倒J形增长', test_res
elif test_1st=='negative':
return '倒J形下降', test_res
elif test_1st == 'uncertain':
return '倒U形先增后降', test_res
else:
raise Exception("未知1阶状态")
else:
raise Exception("未知2阶状态")
elif test_3rd=='positive':
if test_2nd=='positive':
if test_1st=='positive':
return 'J形增长', test_res
elif test_1st=='negative':
return '反J形下降', test_res
elif test_1st=='uncertain':
return 'U形先降后增', test_res
else:
raise Exception("未知1阶状态")
elif test_2nd=='negative':
if test_1st == 'positive':
return '反倒J形增长', test_res
elif test_1st=='negative':
return '倒J形下降', test_res
elif test_1st == 'uncertain':
return '倒U形先增后降', test_res
else:
raise Exception("未知1阶状态")
elif test_2nd=='uncertain':
if test_1st == 'positive':
return 'N形持续增长', test_res
elif test_1st == 'negative':
return '反S形下降',test_res
elif test_1st == 'uncertain':
return 'N形波折增长', test_res
else:
raise Exception("未知1阶状态")
else:
raise Exception("未知2阶状态")
elif test_3rd=='negative':
if test_2nd == 'positive':
if test_1st == 'positive':
return 'J形增长', test_res
elif test_1st == 'negative':
return '反J形下降', test_res
elif test_1st == 'uncertain':
return 'U形先降后增', test_res
else:
raise Exception("未知1阶状态")
elif test_2nd == 'negative':
if test_1st == 'positive':
return '反倒J形增长', test_res
elif test_1st == 'negative':
return '倒J形下降', test_res
elif test_1st == 'uncertain':
return '倒U形先增后降', test_res
else:
raise Exception("未知1阶状态")
elif test_2nd == 'uncertain':
if test_1st == 'positive':
return 'S形增长', test_res
elif test_1st == 'negative':
return '倒N形持续下降', test_res
elif test_1st == 'uncertain':
return '倒N形波折下降', test_res
else:
raise Exception("未知1阶状态")
else:
raise Exception("未知2阶状态")
else:
raise Exception("未知3阶状态")
#识别上市公司财务报表各维度时间序列形态
#代码使用者需要自行获取上市公司财务报表,并且按自己使用习惯自行编写读取报表数据的代码
def stock_analysis(stockcode,alpha):
<读取上市公司财务报表本地数据的代码>
shapeInfo={}
testInfo={}
for field in effinc.columns:
if (effinc[field].dropna()==0).all():
continue
try:
shape,test_res=series_shape_recognition(effinc[field],alpha)
shapeInfo[field]=shape
testInfo[field]=test_res
except Exception as e:
print(e)
return shapeInfo,testInfo
#批量识别全市场上市公司财务报表各维度时间序列形态
#代码使用者需要自行获取全市场上市公司列表及其报表结构数据,并且按自己使用习惯自行编写读取数据的代码
#各行业结构的财务报表分析维度可按使用者的分析习惯进行设定,以下仅作示例
def stock_loop(alpha):
fieldsDict={
'NORMAL':['营业收入','营业总收入其他','营业总收入','营业成本','营业税金及附加','销售费用','管理费用','财务费用','营业其他费用','营业总成本','营业其他损益','营业利润','营业外收入','营业外支出','利润总额其他','利润总额','税后利润','所得税费用','净利润其他','净利润','综合收益其他','综合收益总额'],
'BANK':['营业收入','利息收入','利息支出','利息净收入','手续费及佣金净收入','投资收益','营业收入其他','营业支出','营业税金及附加','业务及管理费','减值损失等营业支出其他','营业利润','营业外收入','营业外支出','利润总额其他','利润总额','税后利润','所得税费用','净利润其他','净利润','综合收益其他','综合收益总额'],
'SECURITY':['营业收入','手续费及佣金净收入','经纪业务手续费净收入','投资银行业务手续费净收入','资产管理业务手续费净收入','手续费及佣金净收入其他','投资收益','营业收入其他','营业支出','营业税金及附加','业务及管理费','减值损失等营业支出其他','营业利润','营业外收入','营业外支出','利润总额其他','利润总额','税后利润','所得税费用','净利润其他','净利润','综合收益其他','综合收益总额'],
'INSURANCE':['营业收入','已赚保费','投资收益','营业收入其他','营业支出','退保金','赔付支出','摊回赔付支出','赔付支出净额','提取保险责任准备金','摊回保险责任准备金','提取保险责任准备金净额','保单红利支出','手续费及佣金支出','营业税金及附加','业务及管理费','营业支出其他','营业利润','营业外收入','营业外支出','利润总额其他','利润总额','税后利润','所得税费用','净利润其他','净利润','综合收益其他','综合收益总额']
}
for structure in ['NORMAL','BANK','SECURITY','INSURANCE']:
<按财务报表行业结构获取使用该结构的上市公司列表的代码>
fields=fieldsDict[structure.upper()]
shapeDF=pd.Dataframe(columns=fields)
testDF=pd.Dataframe(columns=fields)
stocklen=len(stocklist)
successcount=0
errorcount=0
for stockcode in stocklist:
try:
shapeInfo, testInfo=stock_analysis(stockcode,alpha)
shapeDF=shapeDF.append(pd.Series(shapeInfo,name=stockcode))
testDF=testDF.append(pd.Series(testInfo,name=stockcode))
successcount+=1
except Exception as e:
print(e)
errorcount+=1
print(structure,stockcode,'已计算。成功',successcount,'失败',errorcount,'共',stocklen)
shapeDF.to_csv('')
testDF.to_csv('')
def shape_stat(structure,alpha):
shapePath=''
shapeDF=pd.read_csv(shapePath,index_col=[0])
statDF=pd.Dataframe(index=['随机波动','直线增长','直线下降','J形增长','反J形下降','U形先降后增','反倒J形增长','倒J形下降','倒U形先增后降','N形持续增长','反S形下降','N形波折增长','S形增长','倒N形持续下降','倒N形波折下降'],columns=shapeDF.columns)
for field in statDF.columns:
for shape in statDF.index:
statDF.loc[shape,field]=len(shapeDF[field][shapeDF[field]==shape])
statDF.to_csv('')
分别按单尾显著性水平10%,20%,30%进行计算,得出结果如以下各表。可以直观地看到,即使是在条件较为宽松的,显著性水平为30%的条件下,每股综合收益总额历史数据呈增长形态的也是凤毛麟角。营业收入方面,显著性水平为30%的条件下,有不到1/3比例的每股综合收益总额历史数据呈增长形态,增长形态以直线增长为主。



