如果将Zeta模型运用于选股:2022最好的选择是离场观望

举报
darkpard 发表于 2022/09/25 09:23:20 2022/09/25
【摘要】 Zeta模型是Z-Score模型的进阶版,由Altman等人提出,用于判断一家企业破产的可能性。今天我们来探索一下用这种方法进行选股的可行性,由于只是探索,有些细节问题只会简单地点出来,不会详细处理。但即使简单的尝试,它所给出的启示居然是2021年年底以来,股票投资的最好选择是离场观望。Zeta模型包括资产报酬率、收入稳定性、债务偿还、积累盈利、流动比率、资本化率、规模7个变量,我们分别用总...

Zeta模型是Z-Score模型的进阶版,由Altman等人提出,用于判断一家企业破产的可能性。今天我们来探索一下用这种方法进行选股的可行性,由于只是探索,有些细节问题只会简单地点出来,不会详细处理。但即使简单的尝试,它所给出的启示居然是2021年年底以来,股票投资的最好选择是离场观望。


Zeta模型包括资产报酬率、收入稳定性、债务偿还、积累盈利、流动比率、资本化率、规模7个变量,我们分别用总资产报酬率roa、近十年营业收入同比增长率or_yoy的标准差、已获利息倍数ebit_to_interest、盈余公积金surplus_rese、流动比率current_ratio、资产总计total_assets/股东权益total_hldr_eqy_inc_min_int、资产总计total_assets的对数来表征。这些数据分别可以从tushare的balancesheet接口和fina_indicator接口获取。


首先,调用tushare接口:

    import tushare as ts
    pro = ts.pro_api('your_token')

    其次,获取所有股票代码:

      ts_codes = pro.stock_basic()
      ts_codes

      图片

      需要注意的是这里获取的是所有上市公司的代码,用它们进行回测可能会出现幸存者偏差。

      这里,我们只需要股票代码。

        ts_codes = ts_codes['ts_code']

        再次,依次从每个股票的年度资产负债表获取盈余公积金、资金总计和股东权益。实际操作中可以考虑用季度数据,但要小心有些行业在在季节性。

          ts_code = ts_codes[0]
          ts_code

          图片

            data = pro.balancesheet(ts_code=ts_code, end_type=4, fields='ts_code, end_date, surplus_rese, total_assets, total_hldr_eqy_inc_min_int')
            data.dropna(how='any', inplace=True)
            data

              import pandas as pd
              import time
              all_data = pd.DataFrame(columns=['ts_code', 'surplus_rese', 'total_assets', 'total_hldr_eqy_inc_min_int'])
              for ts_code in ts_codes:
                  print(ts_code)
                  data = pro.balancesheet(ts_code=ts_code, end_type=4, fields='ts_code, surplus_rese, total_assets, total_hldr_eqy_inc_min_int')
                  all_data = all_data.append(data)
                  time.sleep(3)
              all_data

              第三,依次从每个股票的年度财务指标数据中获取总资产报酬率roa、近十年营业收入同比增长率or_yoy的标准差、已获利息倍数ebit_to_interest、流动比率current_ratio。

                all_data.index = list(range(len(all_data)))
                ts_codes = list(set(all_data['ts_code'].to_list()))
                for ts_code in ts_codes:
                    print(ts_code)
                    data = pro.fina_indicator(ts_code=ts_code, fields='end_date, roa, or_yoy, ebit_to_interest, current_ratio')
                    for i in data.index:
                        index = all_data[(all_data['ts_code']==ts_code) & (all_data['end_date']==data['end_date'][i])].index
                        all_data.loc[index, 'roa'] = data['roa'][i]
                        all_data.loc[index, 'or_yoy'] = data['or_yoy'][i]
                        all_data.loc[index, 'ebit_to_interest'] = data['ebit_to_interest'][i]
                        all_data.loc[index, 'current_ratio'] = data['current_ratio'][i]
                    time.sleep(3)
                all_data


                第四,计算十年的营业收入增长率的波动率,删除不到十年的数据和中间有缺失的数据。

                  import numpy as np
                  all_data.index = list(range(len(all_data)))
                  ts_codes = list(set(all_data['ts_code'].to_list()))
                  all_data1 = all_data[all_data['ts_code']=='']
                  for ts_code in ts_codes:    print(ts_code)
                      data = all_data[all_data['ts_code']==ts_code]
                      if len(data)>9:
                          data.sort_values(by='end_date', inplace=True)
                          data['time_spread'] = np.array(data['end_date'].astype(int).to_list()) - np.array([int(data['end_date'].to_list()[0])-10000, ] + data['end_date'].astype(int).to_list()[:-1])
                          data.drop(index=(data[data['time_spread']==0].index), inplace=True)
                          data.index = list(range(len(data)))
                          if len(data[data['time_spread']>10000].index):
                              drop_index = max(data[data['time_spread']>10000].index)
                              data = data.loc[drop_index:]
                              data.index = list(range(len(data)))
                          for i in data.index[9:]:
                              data.loc[i, 'volatility'] = np.std(data['or_yoy'][i-9:i+1])
                          all_data1 = all_data1.append(data[9:])
                  all_data1

                  第五,计算资本化率

                    all_data1['cap_rate'] = all_data1['total_assets'] / all_data1['total_hldr_eqy_inc_min_int']

                    第六,数据整理和探索性分析,使用《几行代码实现可视的数据集探索性分析》中分享的pandas_profiling

                    可以看到数据集没有重复行,但有3%的空缺值,并且还有20条警告。


                    从警告可以看到,主要是某些变量相关性比较高,比如roa与ebit_to_interest,并且后者的缺失比较严重,所以我们对指标进行精简,去掉ebit_to_interest、surplus_rese和cap_rate。

                    第七,确定因变量,在一些研究中,往往把是否ST作为因变量,即使在一些股票投资的研究中。这明显是一个不合理的设计,我们这里改成年下跌幅度20%的为坏样本,当然,这也大概率不是一个合理的指标。我们用2021年之前的数据作为训练样本,2021年底以来的半年作为检测。

                      import data_operate as do
                      all_data3 = all_data2[all_data2['end_date']!='20211231']
                      all_data3.index = list(range(len(all_data3)))
                      for i in all_data3.index:
                          end_price = do.curfsql("select close from daily where ts_code='%s' and trade_date<=%d order by trade_date desc limit 0, 1" % (all_data3['ts_code'][i], int(all_data3['end_date'][i])+10000))
                          start_price = do.curfsql("select close from daily where ts_code='%s' and trade_date<=%d order by trade_date desc limit 0, 1" % (all_data3['ts_code'][i], int(all_data3['end_date'][i])))
                          if len(start_price):
                              all_data3.loc[i, 'return'] = 1 if end_price[0][0] / start_price[0][0] > 0.8 else 0
                      all_data3

                      第八,逻辑回归

                        all_data3.dropna(inplace=True)
                        import statsmodels.api as sm
                        train_cols = all_data3.columns[2:-1]
                        logit = sm.Logit(all_data3['return'].astype(bool), all_data3[train_cols])
                        result = logit.fit()
                        result.summary()

                        可以看到波动率不是显著的,因此去掉这个无效变量。

                          logit = sm.Logit(all_data3['return'].astype(bool), all_data3[['roa', 'current_ratio', 'total_assets']])
                          result = logit.fit()
                          result.summary()

                          第九,预测

                            all_data4 = all_data2[all_data2['end_date']=='20211231']
                            all_data4['result'] = result.predict(all_data4[['roa', 'current_ratio', 'total_assets']])
                            all_data4

                            先来统计下2021年底以来的回报率

                              all_data4.index = list(range(len(all_data4)))
                              for i in all_data4.index:
                                  end_price = do.curfsql("select close from daily where ts_code='%s' and trade_date<=%d order by trade_date desc limit 0, 1" % (all_data4['ts_code'][i], int(all_data4['end_date'][i])+10000))
                                  start_price = do.curfsql("select close from daily where ts_code='%s' and trade_date<=%d order by trade_date desc limit 0, 1" % (all_data4['ts_code'][i], int(all_data4['end_date'][i])))
                                  if len(start_price):
                                      all_data4.loc[i, 'return'] = end_price[0][0] / start_price[0][0] - 1
                              all_data4

                                all_data4.sort_values(by='result', inplace=True)
                                all_data4.dropna(inplace=True)
                                np.average(all_data4['return'][:342]), np.average(all_data4['return'][-342:])

                                可以看到,2021年底以来,评分最高的10%亏损6%,而评分最低的10%亏损16%。

                                而如果以0.5为分界线,2021年底以来的回归结果,90%以上的股票将下跌20%以上,空仓离场是最好的选择。

                                当然,这不能算一个严谨的研究,即使用于参考都还远远不够。

                                【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
                                • 点赞
                                • 收藏
                                • 关注作者

                                评论(0

                                0/1000
                                抱歉,系统识别当前为高风险访问,暂不支持该操作

                                全部回复

                                上滑加载中

                                设置昵称

                                在此一键设置昵称,即可参与社区互动!

                                *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

                                *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。