siat 3.10.130__py3-none-any.whl → 3.10.132__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- build/lib/build/lib/siat/__init__.py +75 -0
- build/lib/build/lib/siat/allin.py +137 -0
- build/lib/build/lib/siat/assets_liquidity.py +915 -0
- build/lib/build/lib/siat/beta_adjustment.py +1058 -0
- build/lib/build/lib/siat/beta_adjustment_china.py +548 -0
- build/lib/build/lib/siat/blockchain.py +143 -0
- build/lib/build/lib/siat/bond.py +2900 -0
- build/lib/build/lib/siat/bond_base.py +992 -0
- build/lib/build/lib/siat/bond_china.py +100 -0
- build/lib/build/lib/siat/bond_zh_sina.py +143 -0
- build/lib/build/lib/siat/capm_beta.py +783 -0
- build/lib/build/lib/siat/capm_beta2.py +887 -0
- build/lib/build/lib/siat/common.py +5360 -0
- build/lib/build/lib/siat/compare_cross.py +642 -0
- build/lib/build/lib/siat/copyrights.py +18 -0
- build/lib/build/lib/siat/cryptocurrency.py +667 -0
- build/lib/build/lib/siat/economy.py +1471 -0
- build/lib/build/lib/siat/economy2.py +1853 -0
- build/lib/build/lib/siat/esg.py +536 -0
- build/lib/build/lib/siat/event_study.py +815 -0
- build/lib/build/lib/siat/fama_french.py +1521 -0
- build/lib/build/lib/siat/fin_stmt2_yahoo.py +982 -0
- build/lib/build/lib/siat/financial_base.py +1160 -0
- build/lib/build/lib/siat/financial_statements.py +598 -0
- build/lib/build/lib/siat/financials.py +2339 -0
- build/lib/build/lib/siat/financials2.py +1278 -0
- build/lib/build/lib/siat/financials_china.py +4433 -0
- build/lib/build/lib/siat/financials_china2.py +2212 -0
- build/lib/build/lib/siat/fund.py +629 -0
- build/lib/build/lib/siat/fund_china.py +3307 -0
- build/lib/build/lib/siat/future_china.py +551 -0
- build/lib/build/lib/siat/google_authenticator.py +47 -0
- build/lib/build/lib/siat/grafix.py +3636 -0
- build/lib/build/lib/siat/holding_risk.py +867 -0
- build/lib/build/lib/siat/luchy_draw.py +638 -0
- build/lib/build/lib/siat/market_china.py +1168 -0
- build/lib/build/lib/siat/markowitz.py +2363 -0
- build/lib/build/lib/siat/markowitz2.py +3150 -0
- build/lib/build/lib/siat/markowitz2_20250704.py +2969 -0
- build/lib/build/lib/siat/markowitz2_20250705.py +3158 -0
- build/lib/build/lib/siat/markowitz_simple.py +373 -0
- build/lib/build/lib/siat/ml_cases.py +2291 -0
- build/lib/build/lib/siat/ml_cases_example.py +60 -0
- build/lib/build/lib/siat/option_china.py +3069 -0
- build/lib/build/lib/siat/option_pricing.py +1925 -0
- build/lib/build/lib/siat/other_indexes.py +409 -0
- build/lib/build/lib/siat/risk_adjusted_return.py +1576 -0
- build/lib/build/lib/siat/risk_adjusted_return2.py +1900 -0
- build/lib/build/lib/siat/risk_evaluation.py +2218 -0
- build/lib/build/lib/siat/risk_free_rate.py +351 -0
- build/lib/build/lib/siat/sector_china.py +4140 -0
- build/lib/build/lib/siat/security_price2.py +727 -0
- build/lib/build/lib/siat/security_prices.py +3408 -0
- build/lib/build/lib/siat/security_trend.py +402 -0
- build/lib/build/lib/siat/security_trend2.py +646 -0
- build/lib/build/lib/siat/stock.py +4284 -0
- build/lib/build/lib/siat/stock_advice_linear.py +934 -0
- build/lib/build/lib/siat/stock_base.py +26 -0
- build/lib/build/lib/siat/stock_china.py +2095 -0
- build/lib/build/lib/siat/stock_prices_kneighbors.py +910 -0
- build/lib/build/lib/siat/stock_prices_linear.py +386 -0
- build/lib/build/lib/siat/stock_profile.py +707 -0
- build/lib/build/lib/siat/stock_technical.py +3305 -0
- build/lib/build/lib/siat/stooq.py +74 -0
- build/lib/build/lib/siat/transaction.py +347 -0
- build/lib/build/lib/siat/translate.py +5183 -0
- build/lib/build/lib/siat/valuation.py +1378 -0
- build/lib/build/lib/siat/valuation_china.py +2076 -0
- build/lib/build/lib/siat/var_model_validation.py +444 -0
- build/lib/build/lib/siat/yf_name.py +811 -0
- build/lib/siat/__init__.py +75 -0
- build/lib/siat/allin.py +137 -0
- build/lib/siat/assets_liquidity.py +915 -0
- build/lib/siat/beta_adjustment.py +1058 -0
- build/lib/siat/beta_adjustment_china.py +548 -0
- build/lib/siat/blockchain.py +143 -0
- build/lib/siat/bond.py +2900 -0
- build/lib/siat/bond_base.py +992 -0
- build/lib/siat/bond_china.py +100 -0
- build/lib/siat/bond_zh_sina.py +143 -0
- build/lib/siat/capm_beta.py +783 -0
- build/lib/siat/capm_beta2.py +887 -0
- build/lib/siat/common.py +5360 -0
- build/lib/siat/compare_cross.py +642 -0
- build/lib/siat/copyrights.py +18 -0
- build/lib/siat/cryptocurrency.py +667 -0
- build/lib/siat/economy.py +1471 -0
- build/lib/siat/economy2.py +1853 -0
- build/lib/siat/esg.py +536 -0
- build/lib/siat/event_study.py +815 -0
- build/lib/siat/fama_french.py +1521 -0
- build/lib/siat/fin_stmt2_yahoo.py +982 -0
- build/lib/siat/financial_base.py +1160 -0
- build/lib/siat/financial_statements.py +598 -0
- build/lib/siat/financials.py +2339 -0
- build/lib/siat/financials2.py +1278 -0
- build/lib/siat/financials_china.py +4433 -0
- build/lib/siat/financials_china2.py +2212 -0
- build/lib/siat/fund.py +629 -0
- build/lib/siat/fund_china.py +3307 -0
- build/lib/siat/future_china.py +551 -0
- build/lib/siat/google_authenticator.py +47 -0
- build/lib/siat/grafix.py +3636 -0
- build/lib/siat/holding_risk.py +867 -0
- build/lib/siat/luchy_draw.py +638 -0
- build/lib/siat/market_china.py +1168 -0
- build/lib/siat/markowitz.py +2363 -0
- build/lib/siat/markowitz2.py +3150 -0
- build/lib/siat/markowitz2_20250704.py +2969 -0
- build/lib/siat/markowitz2_20250705.py +3158 -0
- build/lib/siat/markowitz_simple.py +373 -0
- build/lib/siat/ml_cases.py +2291 -0
- build/lib/siat/ml_cases_example.py +60 -0
- build/lib/siat/option_china.py +3069 -0
- build/lib/siat/option_pricing.py +1925 -0
- build/lib/siat/other_indexes.py +409 -0
- build/lib/siat/risk_adjusted_return.py +1576 -0
- build/lib/siat/risk_adjusted_return2.py +1900 -0
- build/lib/siat/risk_evaluation.py +2218 -0
- build/lib/siat/risk_free_rate.py +351 -0
- build/lib/siat/sector_china.py +4140 -0
- build/lib/siat/security_price2.py +727 -0
- build/lib/siat/security_prices.py +3408 -0
- build/lib/siat/security_trend.py +402 -0
- build/lib/siat/security_trend2.py +646 -0
- build/lib/siat/stock.py +4284 -0
- build/lib/siat/stock_advice_linear.py +934 -0
- build/lib/siat/stock_base.py +26 -0
- build/lib/siat/stock_china.py +2095 -0
- build/lib/siat/stock_prices_kneighbors.py +910 -0
- build/lib/siat/stock_prices_linear.py +386 -0
- build/lib/siat/stock_profile.py +707 -0
- build/lib/siat/stock_technical.py +3305 -0
- build/lib/siat/stooq.py +74 -0
- build/lib/siat/transaction.py +347 -0
- build/lib/siat/translate.py +5183 -0
- build/lib/siat/valuation.py +1378 -0
- build/lib/siat/valuation_china.py +2076 -0
- build/lib/siat/var_model_validation.py +444 -0
- build/lib/siat/yf_name.py +811 -0
- siat/__init__.py +0 -0
- siat/allin.py +0 -0
- siat/assets_liquidity.py +0 -0
- siat/beta_adjustment.py +0 -0
- siat/beta_adjustment_china.py +0 -0
- siat/blockchain.py +0 -0
- siat/bond.py +0 -0
- siat/bond_base.py +0 -0
- siat/bond_china.py +0 -0
- siat/bond_zh_sina.py +0 -0
- siat/capm_beta.py +0 -0
- siat/capm_beta2.py +0 -0
- siat/common.py +94 -30
- siat/compare_cross.py +0 -0
- siat/copyrights.py +0 -0
- siat/cryptocurrency.py +0 -0
- siat/economy.py +0 -0
- siat/economy2.py +0 -0
- siat/esg.py +0 -0
- siat/event_study.py +0 -0
- siat/fama_french.py +0 -0
- siat/fin_stmt2_yahoo.py +0 -0
- siat/financial_base.py +0 -0
- siat/financial_statements.py +0 -0
- siat/financials.py +0 -0
- siat/financials2.py +0 -0
- siat/financials_china.py +0 -0
- siat/financials_china2.py +0 -0
- siat/fund.py +0 -0
- siat/fund_china.py +0 -0
- siat/future_china.py +0 -0
- siat/google_authenticator.py +0 -0
- siat/grafix.py +1 -1
- siat/holding_risk.py +0 -0
- siat/luchy_draw.py +0 -0
- siat/market_china.py +7 -1
- siat/markowitz.py +0 -0
- siat/markowitz2.py +240 -39
- siat/markowitz2_20250704.py +2969 -0
- siat/markowitz2_20250705.py +3158 -0
- siat/markowitz_simple.py +0 -0
- siat/ml_cases.py +0 -0
- siat/ml_cases_example.py +0 -0
- siat/option_china.py +0 -0
- siat/option_pricing.py +0 -0
- siat/other_indexes.py +0 -0
- siat/risk_adjusted_return.py +0 -0
- siat/risk_adjusted_return2.py +0 -0
- siat/risk_evaluation.py +0 -0
- siat/risk_free_rate.py +0 -0
- siat/sector_china.py +0 -0
- siat/security_price2.py +0 -0
- siat/security_prices.py +3 -1
- siat/security_trend.py +0 -0
- siat/security_trend2.py +1 -1
- siat/stock.py +4 -2
- siat/stock_advice_linear.py +0 -0
- siat/stock_base.py +0 -0
- siat/stock_china.py +0 -0
- siat/stock_prices_kneighbors.py +0 -0
- siat/stock_prices_linear.py +0 -0
- siat/stock_profile.py +0 -0
- siat/stock_technical.py +0 -0
- siat/stooq.py +0 -0
- siat/transaction.py +0 -0
- siat/translate.py +11 -11
- siat/valuation.py +0 -0
- siat/valuation_china.py +0 -0
- siat/var_model_validation.py +0 -0
- siat/yf_name.py +0 -0
- {siat-3.10.130.dist-info → siat-3.10.132.dist-info}/METADATA +11 -11
- siat-3.10.132.dist-info/RECORD +218 -0
- siat-3.10.132.dist-info/top_level.txt +4 -0
- siat-3.10.130.dist-info/RECORD +0 -76
- siat-3.10.130.dist-info/top_level.txt +0 -1
- {siat-3.10.130.dist-info → siat-3.10.132.dist-info}/WHEEL +0 -0
- {siat-3.10.130.dist-info → siat-3.10.132.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,2076 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
本模块功能:中国股市估值
|
4
|
+
作者:王德宏 (WANG Dehong, Peter)
|
5
|
+
作者单位:北京外国语大学国际商学院
|
6
|
+
作者邮件:wdehong2000@163.com
|
7
|
+
版权所有:王德宏
|
8
|
+
用途限制:仅限研究与教学使用,不可商用!商用需要额外授权。
|
9
|
+
特别声明:作者不对使用本工具进行证券投资导致的任何损益负责!
|
10
|
+
"""
|
11
|
+
#==============================================================================
|
12
|
+
#屏蔽所有警告性信息
|
13
|
+
import warnings; warnings.filterwarnings('ignore')
|
14
|
+
#==============================================================================
|
15
|
+
from siat.common import *
|
16
|
+
from siat.translate import *
|
17
|
+
from siat.grafix import *
|
18
|
+
from siat.security_prices import *
|
19
|
+
from siat.security_price2 import *
|
20
|
+
from siat.sector_china import *
|
21
|
+
|
22
|
+
#==============================================================================
|
23
|
+
|
24
|
+
if __name__ =="__main__":
|
25
|
+
start='2020-1-1'; end='2022-10-9'
|
26
|
+
measure='pb'; method='lyr'; value='value'; statistic='median'
|
27
|
+
|
28
|
+
def get_valuation_market_china(start,end,measure='pe',method='lyr',value='value',statistic='median'):
|
29
|
+
"""
|
30
|
+
功能:中国A股市场估值趋势,一段时间内
|
31
|
+
measure:默认市盈率'pe',可选市净率pb
|
32
|
+
method:默认滚动'ttm',可选静态lyr
|
33
|
+
value:默认数值'value',可选分位数quantile
|
34
|
+
statistic:默认使用中位数'median',可选等权重equal-weighted
|
35
|
+
"""
|
36
|
+
|
37
|
+
#检查日期的合理性
|
38
|
+
result,startpd,endpd=check_period(start,end)
|
39
|
+
if not result:
|
40
|
+
print(" #Error(get_valuation_market_china): invalid date period",start,end)
|
41
|
+
return None
|
42
|
+
|
43
|
+
#检查选项
|
44
|
+
measure1=measure.lower(); measurelist=['pe','pb']
|
45
|
+
method1=method.lower(); methodlist=['ttm','lyr']
|
46
|
+
value1=value.lower(); valuelist=['value','quantile']
|
47
|
+
statistic1=statistic.lower(); statisticlist=['median','equal-weighted']
|
48
|
+
|
49
|
+
if not (measure1 in measurelist):
|
50
|
+
print(" #Error(get_valuation_market_china): invalid measurement",measure)
|
51
|
+
print(" Valid measurement:")
|
52
|
+
return None
|
53
|
+
if not (method1 in methodlist):
|
54
|
+
print(" #Error(get_valuation_market_china): invalid method",method)
|
55
|
+
print(" Valid method:",methodlist)
|
56
|
+
return None
|
57
|
+
if not (value1 in valuelist):
|
58
|
+
print(" #Error(get_valuation_market_china): invalid value",value)
|
59
|
+
print(" Valid value:",valuelist)
|
60
|
+
return None
|
61
|
+
if not (statistic1 in statisticlist):
|
62
|
+
print(" #Error(get_valuation_market_china): invalid statistic",statistic)
|
63
|
+
print(" Valid statistic:",statisticlist)
|
64
|
+
return None
|
65
|
+
|
66
|
+
# 构造组合矩阵
|
67
|
+
import pandas as pd
|
68
|
+
matrix=pd.DataFrame([
|
69
|
+
#['pe ttm value median','middlePETTM','市盈率(滚动TTM,全A股中位数)'],
|
70
|
+
#['pe ttm value equal-weighted','averagePETTM','市盈率(滚动TTM,全A股等权平均)'],
|
71
|
+
#['pe ttm quantile median','quantileInRecent10YearsMiddlePeTtm','市盈率分位数(滚动TTM,全A股中位数,近10年)'],
|
72
|
+
#['pe ttm quantile equal-weighted','quantileInRecent10YearsAveragePeTtm','市盈率分位数(滚动TTM,全A股等权平均,近10年)'],
|
73
|
+
|
74
|
+
#['pe lyr value median','middlePELYR','市盈率(静态LYR,全A股中位数)'],
|
75
|
+
#['pe lyr value equal-weighted','averagePELYR','市盈率(静态LYR,全A股等权平均)'],
|
76
|
+
#['pe lyr quantile median','quantileInRecent10YearsMiddlePeLyr','市盈率分位数(静态LYR,全A股中位数,近10年)'],
|
77
|
+
#['pe lyr quantile equal-weighted','quantileInRecent10YearsAveragePeLyr','市盈率分位数(静态LYR,全A股等权平均,近10年)'],
|
78
|
+
|
79
|
+
#['pb lyr value median','middlePB','市净率(静态LYR,全A股中位数)'],
|
80
|
+
#['pb lyr value equal-weighted','equalWeightAveragePB','市净率(静态LYR,全A股等权平均)'],
|
81
|
+
#['pb lyr quantile median','quantileInRecent10YearsMiddlePB','市净率分位数(静态LYR,全A股中位数,近10年)'],
|
82
|
+
#['pb lyr quantile equal-weighted','quantileInRecent10YearsEqualWeightAveragePB','市净率分位数(静态LYR,全A股等权平均,近10年)'],
|
83
|
+
|
84
|
+
['pe ttm value median','middlePETTM','市盈率(TTM,全A股中位数)'],
|
85
|
+
['pe ttm value equal-weighted','averagePETTM','市盈率(TTM,全A股等权平均)'],
|
86
|
+
['pe ttm quantile median','quantileInRecent10YearsMiddlePeTtm','市盈率分位数(TTM,全A股近十年中位数)'],
|
87
|
+
['pe ttm quantile equal-weighted','quantileInRecent10YearsAveragePeTtm','市盈率分位数(TTM,全A股近十年等权平均)'],
|
88
|
+
|
89
|
+
['pe lyr value median','middlePELYR','市盈率(全A股中位数)'],
|
90
|
+
['pe lyr value equal-weighted','averagePELYR','市盈率(全A股等权平均)'],
|
91
|
+
['pe lyr quantile median','quantileInRecent10YearsMiddlePeLyr','市盈率分位数(全A股近十年中位数)'],
|
92
|
+
['pe lyr quantile equal-weighted','quantileInRecent10YearsAveragePeLyr','市盈率分位数(全A股近十年等权平均)'],
|
93
|
+
|
94
|
+
['pb lyr value median','middlePB','市净率(全A股中位数)'],
|
95
|
+
['pb lyr value equal-weighted','equalWeightAveragePB','市净率(全A股等权平均)'],
|
96
|
+
['pb lyr quantile median','quantileInRecent10YearsMiddlePB','市净率分位数(全A股近十年中位数)'],
|
97
|
+
['pb lyr quantile equal-weighted','quantileInRecent10YearsEqualWeightAveragePB','市净率分位数(全A股近十年等权平均)'],
|
98
|
+
|
99
|
+
], columns=['combine','field','desc'])
|
100
|
+
|
101
|
+
#查找组合方式对应的字段名称
|
102
|
+
combine=measure1+' '+method1+' '+value1+' '+statistic1
|
103
|
+
try:
|
104
|
+
field=matrix[matrix['combine']==combine]['field'].values[0]
|
105
|
+
desc=matrix[matrix['combine']==combine]['desc'].values[0]
|
106
|
+
except:
|
107
|
+
#未查到组合
|
108
|
+
print(" #Error(get_valuation_market_china): parameter combination not available for",combine)
|
109
|
+
return None
|
110
|
+
|
111
|
+
import akshare as ak
|
112
|
+
|
113
|
+
#获取全A股市场的市盈率
|
114
|
+
if measure1 == 'pe':
|
115
|
+
try:
|
116
|
+
mp = ak.stock_a_ttm_lyr()
|
117
|
+
except:
|
118
|
+
#akshare版本需要更新
|
119
|
+
print(" #Error(get_valuation_market_china): may need to upgrade akshare")
|
120
|
+
return None
|
121
|
+
|
122
|
+
#截取选定的日期范围
|
123
|
+
mp['Date']=mp['date']
|
124
|
+
mp.sort_values(by=['Date'],ascending=True,inplace=True)
|
125
|
+
mp.set_index(['Date'],inplace=True)
|
126
|
+
|
127
|
+
try:
|
128
|
+
mp1=mp[(mp.index >= startpd) & (mp.index <=endpd)]
|
129
|
+
except:
|
130
|
+
startpd=startpd.date()
|
131
|
+
endpd=endpd.date()
|
132
|
+
mp1=mp[(mp.index >= startpd) & (mp.index <=endpd)]
|
133
|
+
|
134
|
+
mp9=mp1[['date',field,'close']]
|
135
|
+
mp9['field']=mp9[field]
|
136
|
+
mp9['index']=mp9['close']
|
137
|
+
mp9['index name']="沪深300指数"
|
138
|
+
mp9['measure']=measure1
|
139
|
+
mp9['method']=method1
|
140
|
+
mp9['value']=value1
|
141
|
+
mp9['statistic']=statistic1
|
142
|
+
mp9['desc']=desc
|
143
|
+
|
144
|
+
|
145
|
+
#获取全A股市场的市净率
|
146
|
+
if measure1 == 'pb':
|
147
|
+
try:
|
148
|
+
mp = ak.stock_a_all_pb()
|
149
|
+
except:
|
150
|
+
#akshare版本需要更新
|
151
|
+
print(" #Error(get_valuation_market_china): may need to upgrade akshare")
|
152
|
+
return None
|
153
|
+
|
154
|
+
#截取选定的日期范围
|
155
|
+
mp['Date']=mp['date']
|
156
|
+
mp.sort_values(by=['Date'],ascending=True,inplace=True)
|
157
|
+
mp.set_index(['Date'],inplace=True)
|
158
|
+
|
159
|
+
try:
|
160
|
+
mp1=mp[(mp.index >= startpd) & (mp.index <=endpd)]
|
161
|
+
except:
|
162
|
+
startpd=startpd.date()
|
163
|
+
endpd=endpd.date()
|
164
|
+
mp1=mp[(mp.index >= startpd) & (mp.index <=endpd)]
|
165
|
+
|
166
|
+
mp9=mp1[['date',field,'close']]
|
167
|
+
mp9['field']=mp9[field]
|
168
|
+
mp9['index']=mp9['close']
|
169
|
+
mp9['index name']="上证综合指数"
|
170
|
+
mp9['measure']=measure1
|
171
|
+
mp9['method']=method1
|
172
|
+
mp9['value']=value1
|
173
|
+
mp9['statistic']=statistic1
|
174
|
+
mp9['desc']=desc
|
175
|
+
|
176
|
+
df=mp9[['date','field','index','index name','measure','method','value','statistic','desc']]
|
177
|
+
|
178
|
+
return df
|
179
|
+
|
180
|
+
if __name__ =="__main__":
|
181
|
+
start='2020-1-1'; end='2022-10-9'
|
182
|
+
df=get_valuation_market_china(start,end,measure='pe',method='lyr',value='value',statistic='median')
|
183
|
+
|
184
|
+
#==============================================================================
|
185
|
+
if __name__ =="__main__":
|
186
|
+
start='2024-1-1'; end='2025-3-31'
|
187
|
+
indicator='pe'
|
188
|
+
method='ttm'
|
189
|
+
value='value'
|
190
|
+
statistic='median'
|
191
|
+
|
192
|
+
indicator=['pb','pe']
|
193
|
+
method=['lyr','ttm']
|
194
|
+
value=['value','quantile']
|
195
|
+
statistic=['median','equal-weighted']
|
196
|
+
|
197
|
+
twinx=False; loc1='best'; loc2='best'
|
198
|
+
power=0; twinx=False; average_value=True
|
199
|
+
annotate=False; annotate_value=False; plus_sign=True
|
200
|
+
mark_top=False; mark_bottom=False; mark_end=False
|
201
|
+
|
202
|
+
loc1='upper left'; loc2='lower right'
|
203
|
+
facecolor='whitesmoke'; maxticks=20
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
def valuation_china(start='MRY',end='today',indicator='pe', \
|
208
|
+
method='lyr',value='value',statistic='median', \
|
209
|
+
average_value=False,show_index=False, \
|
210
|
+
power=0,twinx=False, \
|
211
|
+
|
212
|
+
band_area='', \
|
213
|
+
attention_value='',attention_value_area='', \
|
214
|
+
attention_point='',attention_point_area='', \
|
215
|
+
|
216
|
+
annotate=False,annotate_value=False,plus_sign=True, \
|
217
|
+
mark_start=False,mark_top=False,mark_bottom=False,mark_end=False, \
|
218
|
+
|
219
|
+
loc1='upper left',loc2='lower right', \
|
220
|
+
facecolor='whitesmoke',maxticks=20):
|
221
|
+
"""
|
222
|
+
===========================================================================
|
223
|
+
功能:比较中国全A股市场的估值指标变化趋势
|
224
|
+
start: 开始日期,格式YYYY-MM-DD。
|
225
|
+
或者简写版,例如'MRY'近1年、'L3Y'表示近3年等。
|
226
|
+
end: 结束日期,默认今天。
|
227
|
+
|
228
|
+
indicator: 估值指标市盈率'pe'或市净率'pb',或其组合。不支持股息率
|
229
|
+
method: 滚动'ttm或静态取样'lyr',或其组合
|
230
|
+
value: 直接采用估值指标数值'value'或分位数'quantile',或其组合
|
231
|
+
statistic: 采用中位数'median'或等权均值'equal-weighted',或其组合
|
232
|
+
twinx:是否使用双轴绘图法,默认否False。
|
233
|
+
如果同时选择了市盈率'pe'或市净率'pb',建议打开该选项True。
|
234
|
+
loc1/loc2:图例1/2的位置,默认自动决定'best'。
|
235
|
+
如果自动位置不理想,可以手动设置位置。
|
236
|
+
"""
|
237
|
+
print(" Working on valuating China stock market ... ...")
|
238
|
+
|
239
|
+
import pandas as pd
|
240
|
+
#处理日期
|
241
|
+
start,end=start_end_preprocess(start,end)
|
242
|
+
|
243
|
+
# 情形1:双indicator:双指标,PE+PB
|
244
|
+
if isinstance(indicator,list) and len(indicator) >= 2:
|
245
|
+
indicator=indicator[:2]
|
246
|
+
if isinstance(method,list): method=method[0]
|
247
|
+
|
248
|
+
if isinstance(value,list): value=value[0]
|
249
|
+
if isinstance(statistic,list): statistic=statistic[0]
|
250
|
+
|
251
|
+
df=None
|
252
|
+
for i in indicator:
|
253
|
+
if 'pb' != i.lower():
|
254
|
+
dftmp=get_valuation_market_china(start,end,measure=i.lower(), \
|
255
|
+
method=method, \
|
256
|
+
value=value,statistic=statistic)
|
257
|
+
else:
|
258
|
+
# pb不支持ttm
|
259
|
+
dftmp=get_valuation_market_china(start,end,measure=i.lower(), \
|
260
|
+
method='lyr', \
|
261
|
+
value=value,statistic=statistic)
|
262
|
+
|
263
|
+
val_desc=dftmp['desc'].values[0]
|
264
|
+
method_desc=dftmp['method'].values[0]
|
265
|
+
"""
|
266
|
+
if method_desc == 'lyr': val_desc=val_desc+'静态'
|
267
|
+
elif method_desc == 'ttm':
|
268
|
+
#val_desc=val_desc+'动态'
|
269
|
+
val_desc=val_desc
|
270
|
+
if value == 'value': val_desc=val_desc+'数值'
|
271
|
+
elif value == 'quantile': val_desc=val_desc+'分位数'
|
272
|
+
"""
|
273
|
+
dftmp[val_desc]=dftmp['field']
|
274
|
+
dftmp2=dftmp[[val_desc]]
|
275
|
+
|
276
|
+
if df is None:
|
277
|
+
df=dftmp2
|
278
|
+
else:
|
279
|
+
df=pd.merge(df,dftmp2,left_index=True,right_index=True)
|
280
|
+
|
281
|
+
# 情形2:单indicator+双method:ttm+lyr(仅适用于pe;pb仅适用lyr);
|
282
|
+
elif isinstance(method,list) and len(method) >= 2:
|
283
|
+
method=method[:2]
|
284
|
+
if isinstance(indicator,list): indicator=indicator[0]
|
285
|
+
|
286
|
+
if isinstance(value,list): value=value[0]
|
287
|
+
if isinstance(statistic,list): statistic=statistic[0]
|
288
|
+
|
289
|
+
df=None
|
290
|
+
for i in method:
|
291
|
+
if (indicator.lower() == 'pe') or \
|
292
|
+
(indicator.lower() == 'pb' and i.lower() == 'lyr'):
|
293
|
+
dftmp=get_valuation_market_china(start,end,measure=indicator.lower(), \
|
294
|
+
method=i.lower(), \
|
295
|
+
value=value,statistic=statistic)
|
296
|
+
|
297
|
+
val_desc=dftmp['desc'].values[0]
|
298
|
+
method_desc=dftmp['method'].values[0]
|
299
|
+
"""
|
300
|
+
if method_desc == 'lyr': val_desc=val_desc+'静态'
|
301
|
+
elif method_desc == 'ttm':
|
302
|
+
#val_desc=val_desc+'动态'
|
303
|
+
val_desc=val_desc
|
304
|
+
if value == 'value': val_desc=val_desc+'数值'
|
305
|
+
elif value == 'quantile': val_desc=val_desc+'分位数'
|
306
|
+
"""
|
307
|
+
dftmp[val_desc]=dftmp['field']
|
308
|
+
dftmp2=dftmp[[val_desc]]
|
309
|
+
|
310
|
+
if df is None:
|
311
|
+
df=dftmp2
|
312
|
+
else:
|
313
|
+
df=pd.merge(df,dftmp2,left_index=True,right_index=True)
|
314
|
+
|
315
|
+
# 情形3:单indicator+单method+双value;
|
316
|
+
elif isinstance(value,list) and len(value) >= 2:
|
317
|
+
value=value[:2]
|
318
|
+
if isinstance(indicator,list): indicator=indicator[0]
|
319
|
+
if isinstance(method,list): method=method[0]
|
320
|
+
if indicator == 'pb': method='lyr'
|
321
|
+
|
322
|
+
if isinstance(statistic,list): statistic=statistic[0]
|
323
|
+
|
324
|
+
df=None
|
325
|
+
for i in value:
|
326
|
+
dftmp=get_valuation_market_china(start,end,measure=indicator.lower(), \
|
327
|
+
method=method.lower(), \
|
328
|
+
value=i.lower(),statistic=statistic)
|
329
|
+
|
330
|
+
val_desc=dftmp['desc'].values[0]
|
331
|
+
method_desc=dftmp['method'].values[0]
|
332
|
+
"""
|
333
|
+
if method_desc == 'lyr': val_desc=val_desc+'静态'
|
334
|
+
elif method_desc == 'ttm':
|
335
|
+
#val_desc=val_desc+'动态'
|
336
|
+
val_desc=val_desc
|
337
|
+
if value == 'value': val_desc=val_desc+'数值'
|
338
|
+
elif value == 'quantile': val_desc=val_desc+'分位数'
|
339
|
+
"""
|
340
|
+
dftmp[val_desc]=dftmp['field']
|
341
|
+
dftmp2=dftmp[[val_desc]]
|
342
|
+
|
343
|
+
if df is None:
|
344
|
+
df=dftmp2
|
345
|
+
else:
|
346
|
+
df=pd.merge(df,dftmp2,left_index=True,right_index=True)
|
347
|
+
|
348
|
+
# 数值与分位数差距较大,使用twinx=True
|
349
|
+
twinx=True
|
350
|
+
|
351
|
+
# 情形4:单indicator+单method+单value+双statistic;
|
352
|
+
elif isinstance(statistic,list) and len(statistic) >= 2:
|
353
|
+
statistic=statistic[:2]
|
354
|
+
if isinstance(indicator,list): indicator=indicator[0]
|
355
|
+
if isinstance(method,list): method=method[0]
|
356
|
+
if indicator == 'pb': method='lyr'
|
357
|
+
|
358
|
+
df=None
|
359
|
+
for i in statistic:
|
360
|
+
dftmp=get_valuation_market_china(start,end,measure=indicator.lower(), \
|
361
|
+
method=method.lower(), \
|
362
|
+
value=value.lower(),statistic=i.lower())
|
363
|
+
|
364
|
+
val_desc=dftmp['desc'].values[0]
|
365
|
+
method_desc=dftmp['method'].values[0]
|
366
|
+
"""
|
367
|
+
if method_desc == 'lyr': val_desc=val_desc+'静态'
|
368
|
+
elif method_desc == 'ttm':
|
369
|
+
#val_desc=val_desc+'动态'
|
370
|
+
val_desc=val_desc
|
371
|
+
if value == 'value': val_desc=val_desc+'数值'
|
372
|
+
elif value == 'quantile': val_desc=val_desc+'分位数'
|
373
|
+
"""
|
374
|
+
dftmp[val_desc]=dftmp['field']
|
375
|
+
dftmp2=dftmp[[val_desc]]
|
376
|
+
|
377
|
+
if df is None:
|
378
|
+
df=dftmp2
|
379
|
+
else:
|
380
|
+
df=pd.merge(df,dftmp2,left_index=True,right_index=True)
|
381
|
+
|
382
|
+
# 情形5:单indicator+单method+单value+单statistic;
|
383
|
+
else:
|
384
|
+
if isinstance(indicator,list): indicator=indicator[0]
|
385
|
+
if isinstance(method,list): method=method[0]
|
386
|
+
if indicator == 'pb': method='lyr'
|
387
|
+
|
388
|
+
if isinstance(value,list): value=value[0]
|
389
|
+
if isinstance(statistic,list): statistic=statistic[0]
|
390
|
+
|
391
|
+
dftmp=get_valuation_market_china(start,end,measure=indicator.lower(), \
|
392
|
+
method=method.lower(), \
|
393
|
+
value=value.lower(),statistic=statistic.lower())
|
394
|
+
|
395
|
+
val_desc=dftmp['desc'].values[0]
|
396
|
+
method_desc=dftmp['method'].values[0]
|
397
|
+
"""
|
398
|
+
if method_desc == 'lyr': val_desc=val_desc+'静态'
|
399
|
+
elif method_desc == 'ttm':
|
400
|
+
#val_desc=val_desc+'动态'
|
401
|
+
val_desc=val_desc
|
402
|
+
if value == 'value': val_desc=val_desc+'数值'
|
403
|
+
elif value == 'quantile': val_desc=val_desc+'分位数'
|
404
|
+
"""
|
405
|
+
dftmp[val_desc]=dftmp['field']
|
406
|
+
if show_index:
|
407
|
+
index_desc=dftmp['index name'].values[0]
|
408
|
+
dftmp[index_desc]=dftmp['index']
|
409
|
+
df=dftmp[[val_desc,index_desc]]
|
410
|
+
|
411
|
+
# 指数与指标差异大,设定twinx=True
|
412
|
+
twinx=True
|
413
|
+
else:
|
414
|
+
df=dftmp[[val_desc]]
|
415
|
+
|
416
|
+
# 绘图
|
417
|
+
import datetime; todaydt = datetime.date.today()
|
418
|
+
sourcetxt=text_lang("数据来源:legulegu","Data source: legulegu")
|
419
|
+
footnote=sourcetxt+', '+str(todaydt)
|
420
|
+
|
421
|
+
if show_index:
|
422
|
+
# 显示股票市场指数
|
423
|
+
twinx=True
|
424
|
+
|
425
|
+
titletxt=text_lang("中国A股市场估值趋势","China A-share Market Valuation")
|
426
|
+
ylabeltxt=text_lang("估值指标","Valuation Indicator")
|
427
|
+
|
428
|
+
if len(list(df)) == 1:
|
429
|
+
colname=collabel=list(df)[0]
|
430
|
+
#titletxt=titletxt+': '+colname
|
431
|
+
ylabeltxt=colname
|
432
|
+
plot_line(df,colname,collabel,ylabeltxt,titletxt,footnote, \
|
433
|
+
power=power, \
|
434
|
+
attention_value=attention_value,attention_value_area=attention_value_area, \
|
435
|
+
attention_point=attention_point,attention_point_area=attention_point_area, \
|
436
|
+
average_value=average_value, \
|
437
|
+
|
438
|
+
loc=loc1, \
|
439
|
+
mark_start=True,mark_top=True,mark_bottom=True,mark_end=mark_end, \
|
440
|
+
facecolor=facecolor,maxticks=maxticks)
|
441
|
+
|
442
|
+
#elif twinx and len(list(df)) >= 2:
|
443
|
+
elif twinx != False and len(list(df)) >= 2:
|
444
|
+
ticker1=ticker2=''
|
445
|
+
colname1=label1=list(df)[0]; df1=df[[colname1]]
|
446
|
+
colname2=label2=list(df)[1]; df2=df[[colname2]]
|
447
|
+
|
448
|
+
plot_line2(df1,ticker1,colname1,label1, \
|
449
|
+
df2,ticker2,colname2,label2, \
|
450
|
+
ylabeltxt,titletxt,footnote, \
|
451
|
+
power=power,datatag1=False,datatag2=False,yscalemax=5, \
|
452
|
+
zeroline=False, \
|
453
|
+
twinx=twinx, \
|
454
|
+
yline=999,attention_value_area='', \
|
455
|
+
xline=999,attention_point_area='', \
|
456
|
+
resample_freq='H',loc1=loc1,loc2=loc2, \
|
457
|
+
color1='red',color2='blue',facecolor='whitesmoke', \
|
458
|
+
maxticks=maxticks)
|
459
|
+
else:
|
460
|
+
x_label=footnote
|
461
|
+
axhline_value=0; axhline_label=''
|
462
|
+
draw_lines(df,ylabeltxt,x_label,axhline_value,axhline_label,titletxt, \
|
463
|
+
band_area=band_area,loc=loc1, \
|
464
|
+
attention_value=attention_value,attention_value_area=attention_value_area, \
|
465
|
+
attention_point=attention_point,attention_point_area=attention_point_area, \
|
466
|
+
annotate=annotate,annotate_value=annotate_value,plus_sign=False, \
|
467
|
+
mark_top=mark_top,mark_bottom=mark_bottom,mark_end=mark_end, \
|
468
|
+
facecolor=facecolor,maxticks_enable=True,maxticks=maxticks)
|
469
|
+
|
470
|
+
return df
|
471
|
+
|
472
|
+
|
473
|
+
if __name__ =="__main__":
|
474
|
+
start='2020-1-1'; end='2022-10-9'
|
475
|
+
measures=['pb','pe']; methods='lyr'; values='value'; statistics='median'
|
476
|
+
|
477
|
+
measures='pe'; methods=['lyr','ttm']; values='value'; statistics='median'
|
478
|
+
|
479
|
+
|
480
|
+
def valuation_market_china(start='MRY',end='today',measures=['pe','pb'], \
|
481
|
+
methods='lyr',values='value',statistics='median', \
|
482
|
+
twinx=False,loc1='best',loc2='best'):
|
483
|
+
"""
|
484
|
+
===========================================================================
|
485
|
+
功能:比较中国全A股市场的估值指标变化趋势
|
486
|
+
start: 开始日期,格式YYYY-MM-DD。
|
487
|
+
或者简写版,例如'MRY'近1年、'L3Y'表示近3年等。
|
488
|
+
end: 结束日期,默认今天。
|
489
|
+
|
490
|
+
measures: 估值指标市盈率'pe'或市净率'pb',不支持股息率
|
491
|
+
methods: 滚动'ttm/静态取样'lyr'.
|
492
|
+
values: 直接采用估值指标数值'value'或分位数'quantile'
|
493
|
+
statistics: 采用中位数'median'或等权均值'equal-weighted'
|
494
|
+
twinx:是否使用双轴绘图法,默认否False。
|
495
|
+
如果同时选择了市盈率'pe'或市净率'pb',建议打开该选项True。
|
496
|
+
loc1/loc2:图例1/2的位置,默认自动决定'best'。
|
497
|
+
如果自动位置不理想,可以手动设置位置。
|
498
|
+
"""
|
499
|
+
#处理日期
|
500
|
+
start,end=start_end_preprocess(start,end)
|
501
|
+
|
502
|
+
#解析比较的指标,以第一个双指标为准
|
503
|
+
found2=False
|
504
|
+
parmlist=['measures','methods','values','statistics']
|
505
|
+
for v in parmlist:
|
506
|
+
|
507
|
+
#如果是一个字符串
|
508
|
+
if isinstance(eval(v),str):
|
509
|
+
globals()[v+'1']=eval(v)
|
510
|
+
globals()[v+'2']=eval(v)
|
511
|
+
|
512
|
+
#如果是一个列表
|
513
|
+
if isinstance(eval(v),list):
|
514
|
+
num = len(eval(v))
|
515
|
+
#print("num=",num)
|
516
|
+
|
517
|
+
if num == 0:
|
518
|
+
print(" #Error(valuation_market_china):need at least 1 parameter for",eval(v))
|
519
|
+
return None,None
|
520
|
+
|
521
|
+
if num == 1:
|
522
|
+
globals()[v+'1']=eval(v)[0]
|
523
|
+
globals()[v+'2']=eval(v)[0]
|
524
|
+
|
525
|
+
|
526
|
+
if num >= 2:
|
527
|
+
globals()[v+'1']=eval(v)[0]
|
528
|
+
globals()[v+'2']=eval(v)[1]
|
529
|
+
found2=True
|
530
|
+
|
531
|
+
if not found2:
|
532
|
+
print(" #Warning(valuation_market_china):parameters mismatch among",parmlist)
|
533
|
+
#return None,None
|
534
|
+
"""
|
535
|
+
print("measures1=",measures1,"measures2=",measures2)
|
536
|
+
print("methods1=",methods1,"methods2=",methods2)
|
537
|
+
print("values1=",values1,"values2=",values2)
|
538
|
+
print("statistics1=",statistics1,"statistics2=",statistics2)
|
539
|
+
"""
|
540
|
+
|
541
|
+
if values=='value':
|
542
|
+
ylabeltxt='估值比率'
|
543
|
+
else:
|
544
|
+
ylabeltxt='分位数'
|
545
|
+
|
546
|
+
print(" Working on valuating China stock market ... ...")
|
547
|
+
|
548
|
+
titletxt='中国全A股市场估值的变化趋势'
|
549
|
+
|
550
|
+
import datetime
|
551
|
+
today = datetime.date.today()
|
552
|
+
footnote="数据来源: legulegu,"+str(today)
|
553
|
+
|
554
|
+
#获取指标1
|
555
|
+
df1=get_valuation_market_china(start,end,measure=measures1,method=methods1,value=values1,statistic=statistics1)
|
556
|
+
if df1 is None:
|
557
|
+
print(" #Error(valuation_market_china):no data available for the combine of",measures1,methods1,values1,statistics1)
|
558
|
+
return None,None
|
559
|
+
|
560
|
+
ticker1=df1['desc'].values[0]
|
561
|
+
colname1='field'
|
562
|
+
label1=''
|
563
|
+
|
564
|
+
if not found2:
|
565
|
+
plot_line(df1,colname1,ticker1,ylabeltxt,titletxt,footnote, \
|
566
|
+
power=0,loc=loc1, \
|
567
|
+
date_fmt='%Y-%m-%d')
|
568
|
+
return df1,None
|
569
|
+
|
570
|
+
#获取指标2
|
571
|
+
df2=get_valuation_market_china(start,end,measure=measures2,method=methods2,value=values2,statistic=statistics2)
|
572
|
+
if df2 is None:
|
573
|
+
print(" #Error(valuation_market_china):data unavailable for the combine of",measures2,methods2,values2,statistics2)
|
574
|
+
return None,None
|
575
|
+
|
576
|
+
ticker2=df2['desc'].values[0]
|
577
|
+
colname2='field'
|
578
|
+
label2=''
|
579
|
+
|
580
|
+
if twinx == 'auto':
|
581
|
+
twinx=False
|
582
|
+
|
583
|
+
max1=df1[colname1].max()
|
584
|
+
max2=df2[colname2].max()
|
585
|
+
bili=max1/max2
|
586
|
+
if (bili > 2) or (bili < 0.5):
|
587
|
+
twinx=True
|
588
|
+
|
589
|
+
plot2_line2(df1,ticker1,colname1,label1, \
|
590
|
+
df2,ticker2,colname2,label2, \
|
591
|
+
ylabeltxt,titletxt,footnote, \
|
592
|
+
twinx=twinx,loc1=loc1,loc2=loc2)
|
593
|
+
#清除变量
|
594
|
+
#"""
|
595
|
+
for v in parmlist:
|
596
|
+
del globals()[v+'1'],globals()[v+'2']
|
597
|
+
#"""
|
598
|
+
|
599
|
+
return df1,df2
|
600
|
+
|
601
|
+
if __name__ =="__main__":
|
602
|
+
start='2020-1-1'; end='2022-10-9'
|
603
|
+
measures=['pe','pb']; methods='lyr'; values='value'; statistics='median'
|
604
|
+
df1,df2=valuation_market_china(start,end,measures=['pe','pb'],methods='lyr',values='value',statistics='median')
|
605
|
+
|
606
|
+
#==============================================================================
|
607
|
+
# 行业估值:申万宏远行业,韭圈儿
|
608
|
+
#==============================================================================
|
609
|
+
if __name__=='__main__':
|
610
|
+
top=5
|
611
|
+
vtype='PE'
|
612
|
+
vsorting='quantile'
|
613
|
+
printout=True
|
614
|
+
graph=True
|
615
|
+
axisamp=3
|
616
|
+
px=False
|
617
|
+
|
618
|
+
def industry_valuation_sw(top=10,vtype='PE',vsorting='quantile', \
|
619
|
+
graph=True,axisamp=1.2,px=False):
|
620
|
+
"""
|
621
|
+
功能:列示申万行业指数估值最高和最低的行业
|
622
|
+
vtype: PE, PB, dividend
|
623
|
+
vsorting: 分位数绝对值
|
624
|
+
"""
|
625
|
+
|
626
|
+
import akshare as ak
|
627
|
+
# 如果出错,升级一下akshare
|
628
|
+
df = ak.index_value_name_funddb()
|
629
|
+
|
630
|
+
# 筛选申万行业指数
|
631
|
+
substr='(申万)'
|
632
|
+
df['申万标志']=df['指数名称'].apply(lambda x: substr in x)
|
633
|
+
df1=df[df['申万标志']]
|
634
|
+
df1['行业代码']=df1['指数代码'].apply(lambda x: x[:6])
|
635
|
+
df1['行业名称']=df1['指数名称'].apply(lambda x: x[:x.index(substr)])
|
636
|
+
|
637
|
+
#检查估值类型
|
638
|
+
typelist=['pe','pb','dividend']
|
639
|
+
vtypeu=vtype.lower()
|
640
|
+
if not (vtypeu in typelist):
|
641
|
+
print(" #Warning(industry_valuation_sw): unsupported valuation type",vtype)
|
642
|
+
print(" Supported types:",typelist)
|
643
|
+
return None
|
644
|
+
|
645
|
+
#检查排序类型
|
646
|
+
sortlist=['quantile','value']
|
647
|
+
vsortingu=vsorting.lower()
|
648
|
+
if not (vsortingu in sortlist):
|
649
|
+
print(" #Warning(industry_valuation_sw): unsupported sorting type",vsorting)
|
650
|
+
print(" Supported types:",sortlist)
|
651
|
+
return None
|
652
|
+
|
653
|
+
#排序:高位优先
|
654
|
+
if vtypeu == 'pe':
|
655
|
+
if vsortingu == 'value':
|
656
|
+
df2=df1.sort_values(by='最新PE',ascending=False)
|
657
|
+
collist=['行业名称','最新PE','PE分位','最新PB','PB分位','股息率','股息率分位','行业代码']
|
658
|
+
colname='最新PE'
|
659
|
+
else:
|
660
|
+
df2=df1.sort_values(by='PE分位',ascending=False)
|
661
|
+
collist=['行业名称','PE分位','最新PE','PB分位','最新PB','股息率','股息率分位','行业代码']
|
662
|
+
colname='PE分位'
|
663
|
+
|
664
|
+
if vtypeu == 'pb':
|
665
|
+
if vsortingu == 'value':
|
666
|
+
df2=df1.sort_values(by='最新PB',ascending=False)
|
667
|
+
collist=['行业名称','最新PB','PB分位','最新PE','PE分位','股息率','股息率分位','行业代码']
|
668
|
+
colname='最新PB'
|
669
|
+
else:
|
670
|
+
df2=df1.sort_values(by='PB分位',ascending=False)
|
671
|
+
collist=['行业名称','PB分位','最新PB','最新PE','PE分位','股息率','股息率分位','行业代码']
|
672
|
+
colname='PB分位'
|
673
|
+
|
674
|
+
if vtypeu == 'dividend':
|
675
|
+
if vsortingu == 'value':
|
676
|
+
df2=df1.sort_values(by='股息率',ascending=False)
|
677
|
+
collist=['行业名称','股息率','股息率分位','最新PB','PB分位','最新PE','PE分位','行业代码']
|
678
|
+
colname='股息率'
|
679
|
+
else:
|
680
|
+
df2=df1.sort_values(by='股息率分位',ascending=False)
|
681
|
+
collist=['行业名称','股息率分位','股息率','最新PB','PB分位','最新PE','PE分位','行业代码']
|
682
|
+
colname='股息率分位'
|
683
|
+
|
684
|
+
df2.reset_index(drop=True,inplace=True)
|
685
|
+
df2.index=df2.index+1
|
686
|
+
df3=df2[collist]
|
687
|
+
|
688
|
+
if top > 0:
|
689
|
+
df4=df3.head(top)
|
690
|
+
elif top < 0:
|
691
|
+
df4=df3.tail(-top)
|
692
|
+
else:
|
693
|
+
df4=df3
|
694
|
+
df5=df4.set_index('行业名称')
|
695
|
+
df5.sort_values(by=colname,ascending=True,inplace=True)
|
696
|
+
|
697
|
+
#绘图
|
698
|
+
if graph:
|
699
|
+
if top > 0:
|
700
|
+
prefix="最高的"
|
701
|
+
else:
|
702
|
+
prefix="最低的"
|
703
|
+
|
704
|
+
if vsortingu=='quantile':
|
705
|
+
suffix="数百分比"
|
706
|
+
footnote1="分位数表示历史上比当前便宜的百分比,"
|
707
|
+
else:
|
708
|
+
suffix="数值"
|
709
|
+
footnote1=''
|
710
|
+
|
711
|
+
titletxt="估值分析:基于"+colname+suffix+","+prefix+str(abs(top))+"个行业"
|
712
|
+
|
713
|
+
import datetime
|
714
|
+
today = datetime.date.today()
|
715
|
+
footnote0="注:申万宏源行业分类,"
|
716
|
+
footnote2="数据来源: 申万宏源/韭圈儿,"+str(today)
|
717
|
+
footnote=footnote0+footnote1+footnote2
|
718
|
+
|
719
|
+
if not px:
|
720
|
+
fig=plot_barh(df5,colname,titletxt,footnote,axisamp=axisamp)
|
721
|
+
else:
|
722
|
+
fig=plot_barh2(df5,colname,titletxt,footnote)
|
723
|
+
|
724
|
+
return df2
|
725
|
+
|
726
|
+
if __name__=='__main__':
|
727
|
+
df=industry_valuation_sw(top=10,vtype='PE',vsorting='quantile',axisamp=2.3)
|
728
|
+
df=industry_valuation_sw(top=-10,vtype='PE',vsorting='quantile',axisamp=1.2)
|
729
|
+
df=industry_valuation_sw(top=10,vtype='PE',vsorting='value',axisamp=1.5)
|
730
|
+
df=industry_valuation_sw(top=-10,vtype='PE',vsorting='value',axisamp=1.6)
|
731
|
+
|
732
|
+
df=industry_valuation_sw(top=10,vtype='PB',vsorting='quantile',axisamp=2.1)
|
733
|
+
df=industry_valuation_sw(top=-10,vtype='PB',vsorting='quantile',axisamp=1.2)
|
734
|
+
df=industry_valuation_sw(top=10,vtype='PB',vsorting='value',axisamp=2)
|
735
|
+
df=industry_valuation_sw(top=-10,vtype='PB',vsorting='value',axisamp=1.6)
|
736
|
+
|
737
|
+
df=industry_valuation_sw(top=10,vtype='dividend',vsorting='quantile',axisamp=32)
|
738
|
+
df=industry_valuation_sw(top=-10,vtype='dividend',vsorting='quantile',axisamp=1.2)
|
739
|
+
df=industry_valuation_sw(top=10,vtype='dividend',vsorting='value',axisamp=2)
|
740
|
+
df=industry_valuation_sw(top=-10,vtype='dividend',vsorting='value',axisamp=1.3)
|
741
|
+
#==============================================================================
|
742
|
+
#==============================================================================
|
743
|
+
if __name__=='__main__':
|
744
|
+
industry='食品饮料'
|
745
|
+
industry='生猪养殖'
|
746
|
+
industry='国防军工'
|
747
|
+
|
748
|
+
industry='801853.SW'
|
749
|
+
|
750
|
+
start='2021-1-1'
|
751
|
+
end='2022-11-15'
|
752
|
+
vtype='PE'
|
753
|
+
|
754
|
+
graph=True
|
755
|
+
loc='best'
|
756
|
+
|
757
|
+
df=industry_valuation_history_sw_daily(industry,start,end,vtype)
|
758
|
+
|
759
|
+
def industry_valuation_history_sw_daily(industry,start,end,vtype='PE', \
|
760
|
+
graph=True,loc='best', \
|
761
|
+
error_message=True):
|
762
|
+
"""
|
763
|
+
功能:绘制一个申万行业的日历史估值趋势,不支持二级三级行业分类
|
764
|
+
vtype: PE, PB, dividend
|
765
|
+
|
766
|
+
*** 注意:必须安装插件ipywidgets
|
767
|
+
如果出现下列错误信息:Error displaying widget: model not found
|
768
|
+
先卸载再重新安装插件ipywidgets
|
769
|
+
"""
|
770
|
+
#检查日期期间
|
771
|
+
result,start1,end1=check_period(start,end)
|
772
|
+
if not result:
|
773
|
+
print(" #Warning(industry_valuation_history_sw_daily): invalid date period",start,end)
|
774
|
+
return None
|
775
|
+
|
776
|
+
#检查估值类型
|
777
|
+
typelist=['pe','pb','dividend']
|
778
|
+
vtypeu=vtype.lower()
|
779
|
+
if not (vtypeu in typelist):
|
780
|
+
print(" #Warning(industry_valuation_history_sw_daily): unsupported valuation type",vtype)
|
781
|
+
print(" Supported types:",typelist)
|
782
|
+
return None
|
783
|
+
|
784
|
+
vtypelist=['pe','pb','dividend']
|
785
|
+
typelist=['市盈率','市净率','股息率']
|
786
|
+
pos=vtypelist.index(vtypeu)
|
787
|
+
vtypes=typelist[pos]
|
788
|
+
|
789
|
+
# 适配industry名称/代码
|
790
|
+
industry_split=industry.split('.')
|
791
|
+
split1=industry.split('.')[0]
|
792
|
+
split1_name=industry_sw_code(split1)
|
793
|
+
|
794
|
+
#industry情形1:无后缀,名称
|
795
|
+
if len(industry_split)==1 and not split1.isdigit():
|
796
|
+
|
797
|
+
if not split1_name is None: #是申万名称
|
798
|
+
sindustry=industry+'(申万)'
|
799
|
+
else: #不是申万名称
|
800
|
+
sindustry=industry
|
801
|
+
#industry情形2:数字
|
802
|
+
else:
|
803
|
+
if not split1_name is None: #是申万代码
|
804
|
+
sindustry=industry_sw_name(split1)+'(申万)'
|
805
|
+
else: #不是申万代码
|
806
|
+
index_val=ak.index_value_name_funddb()
|
807
|
+
sindustry=index_val[index_val['指数代码']==industry]['指数名称'].values[0]
|
808
|
+
|
809
|
+
# 获取行业估值历史数据
|
810
|
+
import akshare as ak
|
811
|
+
try:
|
812
|
+
# symbol:指数名称,申万行业名称需要加后缀(申万),仅支持申万一级行业和部分二级行业,不支持申万三级行业
|
813
|
+
# 支持的指数查看:ak.index_value_name_funddb()
|
814
|
+
df = ak.index_value_hist_funddb(symbol=sindustry, indicator=vtypes)
|
815
|
+
except:
|
816
|
+
if error_message:
|
817
|
+
print(" #Warning(industry_valuation_history_sw_daily): failed to access index",sindustry)
|
818
|
+
return None
|
819
|
+
|
820
|
+
import pandas as pd
|
821
|
+
df['date']=pd.to_datetime(df['日期'])
|
822
|
+
df.set_index('date',inplace=True)
|
823
|
+
df1=df[[vtypes]]
|
824
|
+
|
825
|
+
#筛选期间
|
826
|
+
df2=df1[(df1.index >= start1) & (df1.index <= end1)]
|
827
|
+
|
828
|
+
#绘图
|
829
|
+
if graph:
|
830
|
+
df2['平均值']=df2[vtypes].mean()
|
831
|
+
df2['中位数']=df2[vtypes].median()
|
832
|
+
|
833
|
+
titletxt="行业估值趋势:"+industry_sw_name(industry)+','+vtypes
|
834
|
+
|
835
|
+
footnote0="注:申万宏源行业指数,"
|
836
|
+
footnote1=''
|
837
|
+
import datetime
|
838
|
+
today = datetime.date.today()
|
839
|
+
footnote2="数据来源: 申万宏源/韭圈儿,"+str(today)
|
840
|
+
footnote=footnote0+footnote1+footnote2
|
841
|
+
|
842
|
+
colname=vtypes
|
843
|
+
collabel=vtypes
|
844
|
+
ylabeltxt=vtypes
|
845
|
+
|
846
|
+
draw_lines(df2,y_label=ylabeltxt,x_label=footnote, \
|
847
|
+
axhline_value=0,axhline_label='', \
|
848
|
+
title_txt=titletxt,data_label=False,resample_freq='D')
|
849
|
+
|
850
|
+
return df2
|
851
|
+
|
852
|
+
if __name__=='__main__':
|
853
|
+
df=industry_valuation_history_sw_daily(industry,start,end,vtype='PE')
|
854
|
+
df=industry_valuation_history_sw_daily(industry,start,end,vtype='PB')
|
855
|
+
df=industry_valuation_history_sw_daily(industry,start,end,vtype='dividend')
|
856
|
+
|
857
|
+
df=industry_valuation_history_sw_daily(industry='纺织服饰',start=start,end=end,vtype='PE')
|
858
|
+
df=industry_valuation_history_sw_daily(industry='纺织服饰',start=start,end=end,vtype='PB')
|
859
|
+
df=industry_valuation_history_sw_daily(industry='纺织服饰',start=start,end=end,vtype='dividend')
|
860
|
+
|
861
|
+
#==============================================================================
|
862
|
+
#==============================================================================
|
863
|
+
if __name__=='__main__':
|
864
|
+
adate='2023-12-14'
|
865
|
+
|
866
|
+
def get_last_friday(adate):
|
867
|
+
"""
|
868
|
+
功能:给定日期,找出上一个周五的日期,配合申万指数估值函数使用
|
869
|
+
"""
|
870
|
+
|
871
|
+
result,fdate=check_date2(adate)
|
872
|
+
if not result:
|
873
|
+
return None
|
874
|
+
|
875
|
+
import pendulum
|
876
|
+
wrk=pendulum.parse(fdate).day_of_week
|
877
|
+
|
878
|
+
import datetime
|
879
|
+
todaydt = datetime.date.today().strftime('%Y-%m-%d')
|
880
|
+
if fdate > todaydt:
|
881
|
+
fdate=todaydt
|
882
|
+
|
883
|
+
if wrk==5:
|
884
|
+
if fdate != todaydt:
|
885
|
+
adj=-1
|
886
|
+
else:
|
887
|
+
adj=-(2+wrk)
|
888
|
+
elif wrk==6:
|
889
|
+
adj=-1
|
890
|
+
else:
|
891
|
+
adj=-(2+wrk)
|
892
|
+
last_fri=date_adjust(fdate,adjust=adj)
|
893
|
+
|
894
|
+
return last_fri
|
895
|
+
|
896
|
+
if __name__=='__main__':
|
897
|
+
start='2023-1-1'
|
898
|
+
end='2023-12-14'
|
899
|
+
get_all_friday(start,end)
|
900
|
+
|
901
|
+
def get_all_friday(start,end):
|
902
|
+
"""
|
903
|
+
功能:获取start和end之间所有的周五日期,配合申万指数估值函数使用
|
904
|
+
"""
|
905
|
+
#import pandas as pd
|
906
|
+
start_fri=get_last_friday(start)
|
907
|
+
end_fri=get_last_friday(end)
|
908
|
+
|
909
|
+
import akshare as ak
|
910
|
+
wrk_df=ak.index_analysis_week_month_sw("week")
|
911
|
+
wrk_df['Date']=wrk_df['date'].apply(lambda x:x.strftime('%Y-%m-%d'))
|
912
|
+
frilist=list(wrk_df['Date'])
|
913
|
+
|
914
|
+
period_frilist=[]
|
915
|
+
for f in frilist:
|
916
|
+
if (f >= start_fri) and (f <= end_fri):
|
917
|
+
period_frilist=period_frilist+[f]
|
918
|
+
|
919
|
+
return period_frilist
|
920
|
+
|
921
|
+
#==============================================================================
|
922
|
+
|
923
|
+
if __name__=='__main__':
|
924
|
+
industry='食品饮料'
|
925
|
+
industry='白酒Ⅱ'
|
926
|
+
start='2023-10-1'
|
927
|
+
end='2023-12-15'
|
928
|
+
vtype='PE'
|
929
|
+
|
930
|
+
graph=True
|
931
|
+
loc='best'
|
932
|
+
df=industry_valuation_history_sw_weekly(industry,start,end,vtype)
|
933
|
+
|
934
|
+
def industry_valuation_history_sw_weekly(industry,start,end,vtype='PE', \
|
935
|
+
graph=True,loc='best'):
|
936
|
+
"""
|
937
|
+
功能:绘制一个申万行业的周历史估值趋势,支持申万"市场表征", "一级行业", "二级行业", "风格指数"
|
938
|
+
不支持三级行业,若为非二级行业,转为industry_valuation_history_sw_daily函数处理日数据,专注处理二级行业周数据
|
939
|
+
vtype: PE, PB, dividend
|
940
|
+
|
941
|
+
"""
|
942
|
+
#检查日期期间
|
943
|
+
result,start1,end1=check_period(start,end)
|
944
|
+
if not result:
|
945
|
+
print(" #Warning(industry_valuation_history_sw_weekly): invalid date period",start,end)
|
946
|
+
return None
|
947
|
+
fridays=get_all_friday(start,end)
|
948
|
+
|
949
|
+
#检查估值类型
|
950
|
+
typelist=['pe','pb','dividend']
|
951
|
+
vtypeu=vtype.lower()
|
952
|
+
if not (vtypeu in typelist):
|
953
|
+
print(" #Warning(industry_valuation_history_sw_weekly): unsupported valuation type",vtype)
|
954
|
+
print(" Supported types:",typelist)
|
955
|
+
return None
|
956
|
+
|
957
|
+
vtypelist=['pe','pb','dividend']
|
958
|
+
typelist=['市盈率','市净率','股息率']
|
959
|
+
pos=vtypelist.index(vtypeu)
|
960
|
+
vtypes=typelist[pos]
|
961
|
+
|
962
|
+
#分辨申万行业代码类别
|
963
|
+
sw_codes=industry_sw_list()
|
964
|
+
sw_codes['type_name']=''
|
965
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '市场表征' if x['type']=='F' else x['type_name'],axis=1)
|
966
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '一级行业' if x['type']=='I' else x['type_name'],axis=1)
|
967
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '二级行业' if x['type']=='T' else x['type_name'],axis=1)
|
968
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '风格指数' if x['type']=='S' else x['type_name'],axis=1)
|
969
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '三级行业' if x['type']=='3' else x['type_name'],axis=1)
|
970
|
+
|
971
|
+
industry1=industry.split('.')[0]
|
972
|
+
industry_name_flag=industry_code_flag=False
|
973
|
+
type_name=''
|
974
|
+
try:
|
975
|
+
type_name=sw_codes[sw_codes['name']==industry1]['type_name'].values[0]
|
976
|
+
industry_name_flag=True
|
977
|
+
except:
|
978
|
+
try:
|
979
|
+
type_name=sw_codes[sw_codes['code']==industry1]['type_name'].values[0]
|
980
|
+
industry_code_flag=True
|
981
|
+
except:
|
982
|
+
print(" #Warning(industry_valuation_history_sw_weekly): not a Shenwan index for",industry)
|
983
|
+
#return None
|
984
|
+
|
985
|
+
if type_name=='三级行业':
|
986
|
+
print(" #Warning(industry_valuation_history_sw_weekly): currently does not support Shenwan 3rd_level industry",industry)
|
987
|
+
return None
|
988
|
+
|
989
|
+
if not (type_name in ['二级行业','风格指数']):
|
990
|
+
df=industry_valuation_history_sw_daily(industry=industry,start=start,end=end,vtype=vtype, \
|
991
|
+
graph=graph,loc=loc,error_message=False)
|
992
|
+
if not (df is None):
|
993
|
+
return df
|
994
|
+
|
995
|
+
# 获取行业估值历史周数据:啰嗦方法,需要反复下载,容易出ipywidgets引起的错误,需要安装之或卸载后重新安装
|
996
|
+
import pandas as pd
|
997
|
+
import akshare as ak
|
998
|
+
df=None
|
999
|
+
for f in fridays:
|
1000
|
+
f1=f[:4]+f[5:7]+f[8:]
|
1001
|
+
try:
|
1002
|
+
dft=ak.index_analysis_weekly_sw(symbol=type_name, date=f1)
|
1003
|
+
except:
|
1004
|
+
continue
|
1005
|
+
|
1006
|
+
"""
|
1007
|
+
dft的结构:
|
1008
|
+
['指数代码','指数名称','发布日期','收盘指数','成交量','涨跌幅','换手率',
|
1009
|
+
'市盈率','市净率','均价','成交额占比','流通市值','平均流通市值','股息率']
|
1010
|
+
"""
|
1011
|
+
|
1012
|
+
if not (dft is None):
|
1013
|
+
if industry_name_flag:
|
1014
|
+
dft2=dft[dft['指数名称']==industry1]
|
1015
|
+
if industry_code_flag:
|
1016
|
+
dft2=dft[dft['指数代码']==industry1]
|
1017
|
+
|
1018
|
+
if df is None:
|
1019
|
+
df=dft2
|
1020
|
+
else:
|
1021
|
+
df=pd.concat([df,dft2])
|
1022
|
+
|
1023
|
+
df['date']=pd.to_datetime(df['发布日期'])
|
1024
|
+
df.set_index('date',inplace=True)
|
1025
|
+
df1=df[[vtypes]]
|
1026
|
+
|
1027
|
+
#筛选期间
|
1028
|
+
#df2=df1[(df1.index >= start1) & (df1.index <= end1)]
|
1029
|
+
df2=df1.dropna()
|
1030
|
+
|
1031
|
+
#绘图
|
1032
|
+
if graph:
|
1033
|
+
df2['平均值']=df2[vtypes].mean()
|
1034
|
+
df2['中位数']=df2[vtypes].median()
|
1035
|
+
|
1036
|
+
titletxt="行业估值趋势:"+industry+','+vtypes
|
1037
|
+
|
1038
|
+
footnote0="注:申万宏源行业指数,"
|
1039
|
+
footnote1=''
|
1040
|
+
import datetime
|
1041
|
+
today = datetime.date.today()
|
1042
|
+
footnote2="数据来源: 申万宏源,"+str(today)
|
1043
|
+
footnote=footnote0+footnote1+footnote2
|
1044
|
+
|
1045
|
+
colname=vtypes
|
1046
|
+
collabel=vtypes
|
1047
|
+
ylabeltxt=vtypes
|
1048
|
+
|
1049
|
+
draw_lines(df2,y_label=ylabeltxt,x_label=footnote, \
|
1050
|
+
axhline_value=0,axhline_label='', \
|
1051
|
+
title_txt=titletxt,data_label=False,resample_freq='D')
|
1052
|
+
|
1053
|
+
return df2
|
1054
|
+
|
1055
|
+
#==============================================================================
|
1056
|
+
|
1057
|
+
if __name__=='__main__':
|
1058
|
+
industry='食品饮料'
|
1059
|
+
industry='白酒Ⅱ'
|
1060
|
+
industry='绩优股指数'
|
1061
|
+
industry='801853.SW'
|
1062
|
+
|
1063
|
+
start='2023-10-1'
|
1064
|
+
end='2023-12-15'
|
1065
|
+
vtype='PE'
|
1066
|
+
|
1067
|
+
graph=True
|
1068
|
+
loc='best'
|
1069
|
+
df=industry_valuation_history_sw(industry,start,end,vtype)
|
1070
|
+
|
1071
|
+
def industry_valuation_history_sw(industry,start,end,vtype='PE', \
|
1072
|
+
graph=True,loc='best'):
|
1073
|
+
"""
|
1074
|
+
功能:绘制一个申万行业的日历史估值趋势,支持申万"市场表征", "一级行业", "二级行业", "风格指数"
|
1075
|
+
不支持三级行业,若为非二级行业,转为industry_valuation_history_sw_daily函数处理日数据,专注处理二级行业日数据
|
1076
|
+
vtype: PE, PB, dividend
|
1077
|
+
|
1078
|
+
"""
|
1079
|
+
#检查日期期间
|
1080
|
+
result,start1,end1=check_period(start,end)
|
1081
|
+
if not result:
|
1082
|
+
print(" #Warning(industry_valuation_history_sw): invalid date period",start,end)
|
1083
|
+
return None
|
1084
|
+
|
1085
|
+
#检查估值类型
|
1086
|
+
typelist=['pe','pb','dividend']
|
1087
|
+
vtypeu=vtype.lower()
|
1088
|
+
if not (vtypeu in typelist):
|
1089
|
+
print(" #Warning(industry_valuation_history_sw): unsupported valuation type",vtype)
|
1090
|
+
print(" Supported types:",typelist)
|
1091
|
+
return None
|
1092
|
+
|
1093
|
+
vtypelist=['pe','pb','dividend']
|
1094
|
+
typelist=['市盈率','市净率','股息率']
|
1095
|
+
pos=vtypelist.index(vtypeu)
|
1096
|
+
vtypes=typelist[pos]
|
1097
|
+
|
1098
|
+
#分辨申万行业代码类别
|
1099
|
+
sw_codes=industry_sw_list()
|
1100
|
+
sw_codes['type_name']=''
|
1101
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '市场表征' if x['type']=='F' else x['type_name'],axis=1)
|
1102
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '一级行业' if x['type']=='I' else x['type_name'],axis=1)
|
1103
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '二级行业' if x['type']=='T' else x['type_name'],axis=1)
|
1104
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '风格指数' if x['type']=='S' else x['type_name'],axis=1)
|
1105
|
+
sw_codes['type_name']=sw_codes.apply(lambda x: '三级行业' if x['type']=='3' else x['type_name'],axis=1)
|
1106
|
+
|
1107
|
+
industry1=industry.split('.')[0]
|
1108
|
+
industry_name_flag=industry_code_flag=False
|
1109
|
+
try:
|
1110
|
+
type_name=sw_codes[sw_codes['name']==industry1]['type_name'].values[0]
|
1111
|
+
industry_name_flag=True; industry_name=industry1
|
1112
|
+
except:
|
1113
|
+
try:
|
1114
|
+
type_name=sw_codes[sw_codes['code']==industry1]['type_name'].values[0]
|
1115
|
+
industry_code_flag=True
|
1116
|
+
industry_name=sw_codes[sw_codes['code']==industry1]['name'].values[0]
|
1117
|
+
except:
|
1118
|
+
print(" #Error(industry_valuation_history_sw): Shenwan industry not found for",industry)
|
1119
|
+
return None
|
1120
|
+
|
1121
|
+
if type_name=='三级行业':
|
1122
|
+
print(" #Error(industry_valuation_history_sw): currently does not support Shenwan 3rd_level industry",industry)
|
1123
|
+
return None
|
1124
|
+
|
1125
|
+
#if not (type_name=='二级行业'):
|
1126
|
+
if not (type_name in ['二级行业','风格指数']):
|
1127
|
+
df=industry_valuation_history_sw_daily(industry=industry,start=start,end=end,vtype=vtype, \
|
1128
|
+
graph=graph,loc=loc)
|
1129
|
+
if not (df is None):
|
1130
|
+
return df
|
1131
|
+
|
1132
|
+
# 获取行业估值历史周数据:笨方法,反复下载。易出ipywidgets引起的错误,可卸载后重装
|
1133
|
+
import pandas as pd
|
1134
|
+
import akshare as ak
|
1135
|
+
start2=start1.strftime('%Y-%m-%d')
|
1136
|
+
end2 =end1.strftime('%Y-%m-%d')
|
1137
|
+
pdate=end2
|
1138
|
+
dstep=7
|
1139
|
+
df=None
|
1140
|
+
while (pdate >= start2) or (abs(date_delta(pdate,start2)) < dstep):
|
1141
|
+
if pdate >= start2:
|
1142
|
+
enddate=pdate
|
1143
|
+
fromdate=date_adjust(pdate,adjust=-dstep)
|
1144
|
+
else:
|
1145
|
+
enddate=start2
|
1146
|
+
fromdate=pdate
|
1147
|
+
|
1148
|
+
try:
|
1149
|
+
fromdate1=fromdate[:4]+fromdate[5:7]+fromdate[8:10]
|
1150
|
+
enddate1=enddate[:4]+enddate[5:7]+enddate[8:10]
|
1151
|
+
dft=ak.index_analysis_daily_sw(symbol=type_name,start_date=fromdate1,end_date=enddate1)
|
1152
|
+
except:
|
1153
|
+
dft=None
|
1154
|
+
"""
|
1155
|
+
try:
|
1156
|
+
fromdate1=fromdate[:4]+fromdate[5:7]+fromdate[8:10]
|
1157
|
+
enddate1=enddate[:4]+enddate[5:7]+enddate[8:10]
|
1158
|
+
dft=ak.index_analysis_daily_sw(symbol=type_name,start_date=fromdate1,end_date=enddate1)
|
1159
|
+
except:
|
1160
|
+
continue
|
1161
|
+
"""
|
1162
|
+
"""
|
1163
|
+
dft的结构:
|
1164
|
+
['指数代码','指数名称','发布日期','收盘指数','成交量','涨跌幅','换手率',
|
1165
|
+
'市盈率','市净率','均价','成交额占比','流通市值','平均流通市值','股息率']
|
1166
|
+
"""
|
1167
|
+
|
1168
|
+
if not (dft is None):
|
1169
|
+
if industry_name_flag:
|
1170
|
+
dft2=dft[dft['指数名称']==industry1]
|
1171
|
+
if industry_code_flag:
|
1172
|
+
dft2=dft[dft['指数代码']==industry1]
|
1173
|
+
|
1174
|
+
if df is None:
|
1175
|
+
df=dft2
|
1176
|
+
else:
|
1177
|
+
df=pd.concat([df,dft2])
|
1178
|
+
|
1179
|
+
# 开始下一轮循环
|
1180
|
+
pdate=date_adjust(fromdate,adjust=-1)
|
1181
|
+
|
1182
|
+
df.sort_values('发布日期',ascending=True,inplace=True)
|
1183
|
+
df.drop_duplicates(inplace=True)
|
1184
|
+
|
1185
|
+
#df=df[df.index >= start1]
|
1186
|
+
#df.dropna(inplace=True)
|
1187
|
+
|
1188
|
+
df['date']=pd.to_datetime(df['发布日期'])
|
1189
|
+
df.set_index('date',inplace=True)
|
1190
|
+
df1=df[[vtypes]]
|
1191
|
+
|
1192
|
+
#筛选期间
|
1193
|
+
#df2=df1[(df1.index >= start1) & (df1.index <= end1)]
|
1194
|
+
df2=df1
|
1195
|
+
|
1196
|
+
#绘图
|
1197
|
+
if graph:
|
1198
|
+
df2['平均值']=df2[vtypes].mean()
|
1199
|
+
df2['中位数']=df2[vtypes].median()
|
1200
|
+
|
1201
|
+
titletxt="行业估值趋势:"+industry_name+','+vtypes
|
1202
|
+
|
1203
|
+
footnote0="注:申万行业分类指数,"
|
1204
|
+
footnote1=''
|
1205
|
+
import datetime
|
1206
|
+
today = datetime.date.today()
|
1207
|
+
footnote2="数据来源: 申万宏源,"+str(today)
|
1208
|
+
footnote=footnote0+footnote1+footnote2
|
1209
|
+
|
1210
|
+
colname=vtypes
|
1211
|
+
collabel=vtypes
|
1212
|
+
ylabeltxt=vtypes
|
1213
|
+
|
1214
|
+
draw_lines(df2,y_label=ylabeltxt,x_label=footnote, \
|
1215
|
+
axhline_value=0,axhline_label='', \
|
1216
|
+
title_txt=titletxt,data_label=False,resample_freq='D')
|
1217
|
+
|
1218
|
+
return df2
|
1219
|
+
|
1220
|
+
#==============================================================================
|
1221
|
+
#==============================================================================
|
1222
|
+
if __name__=='__main__':
|
1223
|
+
industries=['食品饮料','纺织服饰']
|
1224
|
+
industries=['银行','国有大型银行Ⅱ','股份制银行Ⅱ','城商行Ⅱ','农商行Ⅱ']
|
1225
|
+
start='2023-12-1'
|
1226
|
+
end='2023-12-15'
|
1227
|
+
vtypes='PE'
|
1228
|
+
|
1229
|
+
industries='纺织服饰'
|
1230
|
+
vtypes=['PE','PB']
|
1231
|
+
|
1232
|
+
graph=True
|
1233
|
+
loc1='lower left'
|
1234
|
+
loc2='upper right'
|
1235
|
+
|
1236
|
+
df5=compare_industry_valuation_sw(industries,start,end,vtypes)
|
1237
|
+
|
1238
|
+
def compare_industry_valuation_sw(industries,start,end,vtypes='PE', \
|
1239
|
+
graph=True,loc1='best',loc2='best'):
|
1240
|
+
"""
|
1241
|
+
功能:比较多个申万行业或者两个指标的历史估值趋势
|
1242
|
+
条件:若industries为列表且多个,则取vtype的第一个值;
|
1243
|
+
若industries为字符串或者列表但只有一个,则取vtype的前两个值比较,双轴
|
1244
|
+
vtypes: PE, PB, dividend
|
1245
|
+
|
1246
|
+
"""
|
1247
|
+
|
1248
|
+
#检查日期期间的合理性
|
1249
|
+
|
1250
|
+
|
1251
|
+
vtypelist=['pe','pb','dividend']
|
1252
|
+
typelist=['市盈率','市净率','股息率']
|
1253
|
+
|
1254
|
+
#检查行业个数:多个行业+单指标
|
1255
|
+
if isinstance(industries,list) & (len(industries) >= 2):
|
1256
|
+
|
1257
|
+
if isinstance(vtypes,str):
|
1258
|
+
vtype=vtypes
|
1259
|
+
elif isinstance(vtypes,list):
|
1260
|
+
vtype=vtypes[0]
|
1261
|
+
|
1262
|
+
vtypeu=vtype.lower()
|
1263
|
+
pos=vtypelist.index(vtypeu)
|
1264
|
+
vtypec=typelist[pos]
|
1265
|
+
|
1266
|
+
import pandas as pd
|
1267
|
+
df=pd.DataFrame()
|
1268
|
+
for i in industries:
|
1269
|
+
# debug
|
1270
|
+
print(" Searching valuation info for",i,'\b, which may take time ...')
|
1271
|
+
dft=industry_valuation_history_sw(i,start=start,end=end,vtype=vtype,graph=False)
|
1272
|
+
if not (dft is None):
|
1273
|
+
dft.rename(columns={vtypec:i},inplace=True)
|
1274
|
+
if len(df) == 0:
|
1275
|
+
df=dft
|
1276
|
+
else:
|
1277
|
+
df=pd.merge(df,dft,how='outer',left_index=True,right_index=True)
|
1278
|
+
else:
|
1279
|
+
continue
|
1280
|
+
|
1281
|
+
#绘图
|
1282
|
+
if graph:
|
1283
|
+
titletxt="行业估值趋势对比:"+vtypec
|
1284
|
+
|
1285
|
+
footnote0="注:申万宏源行业指数,"
|
1286
|
+
footnote1=''
|
1287
|
+
import datetime
|
1288
|
+
today = datetime.date.today()
|
1289
|
+
footnote2="数据来源: 申万宏源/韭圈儿,"+str(today)
|
1290
|
+
footnote=footnote0+footnote1+footnote2
|
1291
|
+
|
1292
|
+
ylabeltxt=vtypec
|
1293
|
+
|
1294
|
+
draw_lines(df,y_label=ylabeltxt,x_label=footnote, \
|
1295
|
+
axhline_value=0,axhline_label='', \
|
1296
|
+
title_txt=titletxt,data_label=False,resample_freq='D')
|
1297
|
+
|
1298
|
+
return df
|
1299
|
+
|
1300
|
+
#检查行业个数:一个行业+双指标
|
1301
|
+
if ((isinstance(industries,str) | (isinstance(industries,list) & (len(industries) == 1)))) \
|
1302
|
+
& (isinstance(vtypes,list) & (len(vtypes) >= 2)):
|
1303
|
+
|
1304
|
+
if isinstance(industries,str):
|
1305
|
+
industry=industries
|
1306
|
+
elif isinstance(industries,list):
|
1307
|
+
industry=industries[0]
|
1308
|
+
|
1309
|
+
if isinstance(vtypes,str):
|
1310
|
+
ivtypelist=[vtypes]
|
1311
|
+
elif isinstance(vtypes,list):
|
1312
|
+
ivtypelist=vtypes[:2]
|
1313
|
+
|
1314
|
+
import pandas as pd
|
1315
|
+
df=pd.DataFrame()
|
1316
|
+
for t in ivtypelist:
|
1317
|
+
|
1318
|
+
dft=industry_valuation_history_sw(industry,start=start,end=end,vtype=t,graph=False)
|
1319
|
+
|
1320
|
+
if len(df) == 0:
|
1321
|
+
df=dft
|
1322
|
+
else:
|
1323
|
+
df=pd.merge(df,dft,how='outer',left_index=True,right_index=True)
|
1324
|
+
|
1325
|
+
#绘图
|
1326
|
+
if graph:
|
1327
|
+
titletxt="行业估值趋势对比:"+industry
|
1328
|
+
|
1329
|
+
footnote0="注:申万宏源行业指数,"
|
1330
|
+
footnote1=''
|
1331
|
+
import datetime
|
1332
|
+
today = datetime.date.today()
|
1333
|
+
footnote2="数据来源: 申万宏源/韭圈儿,"+str(today)
|
1334
|
+
footnote=footnote0+footnote1+footnote2
|
1335
|
+
|
1336
|
+
collist=list(df)
|
1337
|
+
colname1=label1=collist[0]
|
1338
|
+
colname2=label2=collist[1]
|
1339
|
+
|
1340
|
+
plot_line2(df,'',colname1,label1, \
|
1341
|
+
df,'',colname2,label2, \
|
1342
|
+
ylabeltxt='',titletxt=titletxt,footnote=footnote, \
|
1343
|
+
twinx=True, \
|
1344
|
+
resample_freq='D',loc1=loc1,loc2=loc2, \
|
1345
|
+
color1='red',color2='blue')
|
1346
|
+
|
1347
|
+
return df
|
1348
|
+
|
1349
|
+
#检查行业个数:一个行业+一个指标
|
1350
|
+
if ((isinstance(industries,str) | (isinstance(industries,list) & (len(industries) == 1)))) \
|
1351
|
+
& ((isinstance(vtypes,str) | (isinstance(vtypes,list) & (len(vtypes) == 1)))):
|
1352
|
+
|
1353
|
+
if isinstance(industries,str):
|
1354
|
+
industry=industries
|
1355
|
+
elif isinstance(industries,list):
|
1356
|
+
industry=industries[0]
|
1357
|
+
|
1358
|
+
if isinstance(vtypes,str):
|
1359
|
+
ivtype=vtypes
|
1360
|
+
elif isinstance(vtypes,list):
|
1361
|
+
ivtype=vtypes[0]
|
1362
|
+
|
1363
|
+
df=industry_valuation_history_sw(industry,start,end,vtype=ivtype, \
|
1364
|
+
graph=graph,loc=loc1)
|
1365
|
+
return df
|
1366
|
+
|
1367
|
+
if __name__=='__main__':
|
1368
|
+
df=compare_industry_valuation_sw(industries=['纺织服饰','国防军工','食品饮料'], \
|
1369
|
+
start='2017-1-1',end='2022-11-15', \
|
1370
|
+
vtypes='PE',loc1='lower left',loc2='upper right')
|
1371
|
+
|
1372
|
+
df=compare_industry_valuation_sw(industries=['纺织服饰'], \
|
1373
|
+
start='2017-1-1',end='2022-11-15', \
|
1374
|
+
vtypes='PE',loc1='lower left',loc2='upper right')
|
1375
|
+
|
1376
|
+
df=compare_industry_valuation_sw(industries=['纺织服饰'], \
|
1377
|
+
start='2017-1-1',end='2022-11-15', \
|
1378
|
+
vtypes=['PE','PB'],loc1='lower left',loc2='upper right')
|
1379
|
+
|
1380
|
+
#==============================================================================
|
1381
|
+
#==============================================================================
|
1382
|
+
if __name__=='__main__':
|
1383
|
+
end='2022-11-18'
|
1384
|
+
start=date_adjust(end,-365*5)
|
1385
|
+
valuation=['PE','PB','dividend']
|
1386
|
+
return_delay=['Annual','Quarterly','Monthly']
|
1387
|
+
industries='all'
|
1388
|
+
|
1389
|
+
lo_est_betas_list,betas_list,idfall=valuation2return_sw(start,end,valuation=valuation,return_delay=return_delay)
|
1390
|
+
|
1391
|
+
def valuation2return_sw2(start,end,valuation=['PE','PB','dividend'], \
|
1392
|
+
return_delay=['Annual','Quarterly','Monthly'], \
|
1393
|
+
industries='all'):
|
1394
|
+
"""
|
1395
|
+
废弃!!!
|
1396
|
+
功能:测试估值指标对滞后一段时间收益率的影响。若正向(负向)影响,行业估值未低估(高估)
|
1397
|
+
start, end: 测试期间
|
1398
|
+
valuation: 估值指标,可为单项指标或列表。市盈率PE, 市净率PB, 股息率dividend
|
1399
|
+
return_delay: 滞后时间长度,可为单项指标或列表。Monthly一个月=21天,Quarterly一个季度=63天,Annual一年=252天
|
1400
|
+
"""
|
1401
|
+
# 检查日期的合理性
|
1402
|
+
|
1403
|
+
# 检查估值指标的类型
|
1404
|
+
valuationlist=['pe','pb','dividend']
|
1405
|
+
if isinstance(valuation,str):
|
1406
|
+
valuation_list=[valuation]
|
1407
|
+
elif isinstance(valuation,list):
|
1408
|
+
valuation_list=valuation
|
1409
|
+
for v in valuation_list:
|
1410
|
+
if not (v.lower() in valuationlist):
|
1411
|
+
print(" #Warning(valuation2return_sw): unsupported type of valuation:",v)
|
1412
|
+
print(" supported types of valuation:",valuationlist)
|
1413
|
+
return None
|
1414
|
+
|
1415
|
+
# 检查估值指标的类型
|
1416
|
+
return_delaylist=['annual','quarterly','monthly']
|
1417
|
+
measurelist=['Annual Ret','Quarterly Ret','Monthly Ret']
|
1418
|
+
shiftlist=[252,63,21]
|
1419
|
+
|
1420
|
+
if isinstance(return_delay,str):
|
1421
|
+
return_delay_list=[return_delay]
|
1422
|
+
elif isinstance(return_delay,list):
|
1423
|
+
return_delay_list=return_delay
|
1424
|
+
|
1425
|
+
measure_list=[]
|
1426
|
+
shift_days_list=[]
|
1427
|
+
for v in return_delay_list:
|
1428
|
+
if not (v.lower() in return_delaylist):
|
1429
|
+
print(" #Warning(valuation2return_sw): unsupported type of return delay:",v)
|
1430
|
+
print(" supported types of return delay:",return_delaylist)
|
1431
|
+
return None
|
1432
|
+
|
1433
|
+
pos=return_delaylist.index(v.lower())
|
1434
|
+
measure=measurelist[pos]
|
1435
|
+
measure_list=measure_list+[measure]
|
1436
|
+
shift_days=shiftlist[pos]
|
1437
|
+
shift_days_list=shift_days_list+[shift_days]
|
1438
|
+
|
1439
|
+
# 获取行业历史数据,本步骤所需时间较长=========================================
|
1440
|
+
industry_data=get_industry_sw('I')
|
1441
|
+
if not (industries.lower() == 'all'):
|
1442
|
+
industry_codes=industry_sw_codes(industries)
|
1443
|
+
else:
|
1444
|
+
industry_codes=list(set(list(industry_data['ticker'])))
|
1445
|
+
|
1446
|
+
# 计算基础数据,本步骤所需时间较长============================================
|
1447
|
+
idf,idfall=calc_industry_sw(industry_data,start,end)
|
1448
|
+
|
1449
|
+
# 构造回归数据,进行回归,记录回归结果
|
1450
|
+
import pandas as pd
|
1451
|
+
from scipy import stats
|
1452
|
+
|
1453
|
+
#屏蔽函数内print信息输出的类
|
1454
|
+
import os, sys
|
1455
|
+
class HiddenPrints:
|
1456
|
+
def __enter__(self):
|
1457
|
+
self._original_stdout = sys.stdout
|
1458
|
+
sys.stdout = open(os.devnull, 'w')
|
1459
|
+
|
1460
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
1461
|
+
sys.stdout.close()
|
1462
|
+
sys.stdout = self._original_stdout
|
1463
|
+
|
1464
|
+
betas_list=[]
|
1465
|
+
lo_est_betas_list=[]
|
1466
|
+
print("Calculating industry valuations, it may take great time, please wait ...")
|
1467
|
+
for m in measure_list:
|
1468
|
+
|
1469
|
+
#计算收益率
|
1470
|
+
ret_df=compare_industry_sw(idfall,industry_codes,measure=m,graph=False)
|
1471
|
+
industry_names=list(ret_df)
|
1472
|
+
pos=measure_list.index(m)
|
1473
|
+
d=shift_days_list[pos]
|
1474
|
+
|
1475
|
+
ret_collist=list(ret_df)
|
1476
|
+
ret_reg=pd.DataFrame()
|
1477
|
+
# 构造用于回归的数据结构
|
1478
|
+
for i in ret_collist:
|
1479
|
+
tmpdf=ret_df[[i]]
|
1480
|
+
tmpdf['行业']=i
|
1481
|
+
tmpdf.rename(columns={i:'ret'},inplace=True)
|
1482
|
+
|
1483
|
+
if len(ret_reg)==0:
|
1484
|
+
ret_reg=tmpdf
|
1485
|
+
else:
|
1486
|
+
try:
|
1487
|
+
ret_reg=ret_reg.append(tmpdf)
|
1488
|
+
except:
|
1489
|
+
ret_reg=ret_reg._append(tmpdf)
|
1490
|
+
|
1491
|
+
# 计算估值指标
|
1492
|
+
for val in valuation_list:
|
1493
|
+
with HiddenPrints():
|
1494
|
+
val_df=compare_industry_valuation_sw(industry_names,start=start,end=end,
|
1495
|
+
vtypes=val,graph=False)
|
1496
|
+
|
1497
|
+
# 滞后估值指标
|
1498
|
+
val_df2=val_df.shift(d)
|
1499
|
+
val_collist=list(val_df2)
|
1500
|
+
val_reg=pd.DataFrame()
|
1501
|
+
|
1502
|
+
for i in val_collist:
|
1503
|
+
tmpdf=val_df2[[i]]
|
1504
|
+
tmpdf['行业']=i
|
1505
|
+
tmpdf.rename(columns={i:val},inplace=True)
|
1506
|
+
|
1507
|
+
if len(val_reg)==0:
|
1508
|
+
val_reg=tmpdf
|
1509
|
+
else:
|
1510
|
+
try:
|
1511
|
+
val_reg=val_reg.append(tmpdf)
|
1512
|
+
except:
|
1513
|
+
val_reg=val_reg._append(tmpdf)
|
1514
|
+
|
1515
|
+
#合成
|
1516
|
+
val_reg['日期']=val_reg.index
|
1517
|
+
ret_reg['日期']=ret_reg.index
|
1518
|
+
|
1519
|
+
df_reg=val_reg.merge(ret_reg,how='inner',on=['日期','行业'])
|
1520
|
+
df_reg.set_index('日期',inplace=True)
|
1521
|
+
|
1522
|
+
df_reg.dropna(inplace=True)
|
1523
|
+
|
1524
|
+
output=stats.linregress(df_reg[val],df_reg['ret'])
|
1525
|
+
(beta,alpha,r_value,p_value,std_err)=output
|
1526
|
+
|
1527
|
+
if p_value < 0.001: siglevel='***'
|
1528
|
+
elif p_value < 0.01: siglevel='** '
|
1529
|
+
elif p_value < 0.05: siglevel='* '
|
1530
|
+
else: siglevel=' '
|
1531
|
+
r_sqr=round(r_value**2,4)
|
1532
|
+
|
1533
|
+
betas=pd.DataFrame(columns=('估值指标','收益率指标','行业','截距','估值系数','p值','显著性','R-sqr'))
|
1534
|
+
row=pd.Series({'估值指标':val,'收益率指标':m,'行业':'全行业','截距':alpha,'估值系数':beta, \
|
1535
|
+
'p值':p_value,'显著性':siglevel,'R-sqr':r_sqr})
|
1536
|
+
try:
|
1537
|
+
betas=betas.append(row,ignore_index=True)
|
1538
|
+
except:
|
1539
|
+
betas=betas._append(row,ignore_index=True)
|
1540
|
+
|
1541
|
+
industry_list=list(set(list(df_reg['行业'])))
|
1542
|
+
for i in industry_list:
|
1543
|
+
dftmp=df_reg[df_reg['行业']==i]
|
1544
|
+
|
1545
|
+
output=stats.linregress(dftmp[val],dftmp['ret'])
|
1546
|
+
(beta,alpha,r_value,p_value,std_err)=output
|
1547
|
+
|
1548
|
+
if p_value < 0.001: siglevel='***'
|
1549
|
+
elif p_value < 0.01: siglevel='** '
|
1550
|
+
elif p_value < 0.05: siglevel='* '
|
1551
|
+
else: siglevel=' '
|
1552
|
+
r_sqr=round(r_value**2,4)
|
1553
|
+
|
1554
|
+
row=pd.Series({'估值指标':val,'收益率指标':m,'行业':i,'截距':alpha,'估值系数':beta, \
|
1555
|
+
'p值':p_value,'显著性':siglevel,'R-sqr':r_sqr})
|
1556
|
+
try:
|
1557
|
+
betas=betas.append(row,ignore_index=True)
|
1558
|
+
except:
|
1559
|
+
betas=betas._append(row,ignore_index=True)
|
1560
|
+
|
1561
|
+
if val.lower() in ['pe','pb']:
|
1562
|
+
betas['估值判断']=betas['估值系数'].apply(lambda x: '可能高估' if x <0 else '可能低估')
|
1563
|
+
betas.sort_values(by=['估值系数','显著性'],ascending=[False,False],inplace=True)
|
1564
|
+
elif val.lower() in ['dividend']:
|
1565
|
+
betas['估值判断']=betas['估值系数'].apply(lambda x: '可能高估' if x >0 else '可能低估')
|
1566
|
+
betas.sort_values(by=['估值系数','显著性'],ascending=[True,False],inplace=True)
|
1567
|
+
|
1568
|
+
lo_est_betas=betas[(betas['行业']=='全行业') | (betas['估值判断']=='可能低估')]
|
1569
|
+
|
1570
|
+
betas.reset_index(drop=True,inplace=True)
|
1571
|
+
betas.index=betas.index + 1
|
1572
|
+
betas_list=betas_list+[betas]
|
1573
|
+
lo_est_betas_list=lo_est_betas_list+[lo_est_betas]
|
1574
|
+
|
1575
|
+
# 整理各个行业的综合评价
|
1576
|
+
# 合并betas
|
1577
|
+
allbetas=pd.DataFrame()
|
1578
|
+
for b in betas_list:
|
1579
|
+
try:
|
1580
|
+
allbetas=allbetas.append(b)
|
1581
|
+
except:
|
1582
|
+
allbetas=allbetas._append(b)
|
1583
|
+
|
1584
|
+
valtable=pd.DataFrame(columns=('行业', \
|
1585
|
+
('PE','Annual Ret'), \
|
1586
|
+
('PE','Quarterly Ret'), \
|
1587
|
+
('PE','Monthly Ret'), \
|
1588
|
+
('PB','Annual Ret'), \
|
1589
|
+
('PB','Quarterly Ret'), \
|
1590
|
+
('PB','Monthly Ret'), \
|
1591
|
+
('dividend','Annual Ret'), \
|
1592
|
+
('dividend','Quarterly Ret'), \
|
1593
|
+
('dividend','Monthly Ret'), \
|
1594
|
+
))
|
1595
|
+
valtable_list=list(valtable)
|
1596
|
+
valtable_list.remove('行业')
|
1597
|
+
industry_names=list(set(list(allbetas['行业'])))
|
1598
|
+
for i in industry_names:
|
1599
|
+
pos=industry_names.index(i)
|
1600
|
+
row=pd.Series({'行业':i})
|
1601
|
+
try:
|
1602
|
+
valtable=valtable.append(row,ignore_index=True)
|
1603
|
+
except:
|
1604
|
+
valtable=valtable._append(row,ignore_index=True)
|
1605
|
+
|
1606
|
+
for v in valtable_list:
|
1607
|
+
val,ret=v
|
1608
|
+
try:
|
1609
|
+
val_value=allbetas[(allbetas['估值指标']==val) & (allbetas['收益率指标']==ret) & (allbetas['行业']==i)]['估值判断'].values[0]
|
1610
|
+
if val_value == '可能低估':
|
1611
|
+
val_value1='低估'
|
1612
|
+
else:
|
1613
|
+
val_value1='高估'
|
1614
|
+
|
1615
|
+
sig_value=allbetas[(allbetas['估值指标']==val) & (allbetas['收益率指标']==ret) & (allbetas['行业']==i)]['显著性'].values[0]
|
1616
|
+
vsvalue=val_value1+sig_value
|
1617
|
+
|
1618
|
+
valtable.at[pos,v]=vsvalue
|
1619
|
+
except:
|
1620
|
+
continue
|
1621
|
+
|
1622
|
+
valtable.fillna('不确定',inplace=True)
|
1623
|
+
|
1624
|
+
# 排序,低估在前,PE优先
|
1625
|
+
fld1=valtable_list[0]
|
1626
|
+
fld2=valtable_list[1]
|
1627
|
+
fld3=valtable_list[2]
|
1628
|
+
valtable.sort_values(by=[fld1,fld2,fld3],ascending=[True,True,True],inplace=True)
|
1629
|
+
valtable.reset_index(drop=True,inplace=True)
|
1630
|
+
|
1631
|
+
print("Successfully valuated",len(valtable),'industries')
|
1632
|
+
print("Valuation completed by mixing PE/PB/dividend with Annual/Quarterly/Monthly")
|
1633
|
+
|
1634
|
+
return valtable,betas_list
|
1635
|
+
|
1636
|
+
#==============================================================================
|
1637
|
+
#==============================================================================
|
1638
|
+
if __name__=='__main__':
|
1639
|
+
end='2022-11-22'
|
1640
|
+
start=date_adjust(end,-365*5)
|
1641
|
+
itype='I'
|
1642
|
+
industries='all'
|
1643
|
+
|
1644
|
+
lo_est_betas_list,betas_list,idfall=valuation2return_sw(start,end,valuation=valuation,return_delay=return_delay)
|
1645
|
+
|
1646
|
+
def valuation2return_sw(start,end,itype='1',industries='all'):
|
1647
|
+
"""
|
1648
|
+
功能:测试三种估值指标对滞后一段时间收益率的影响。
|
1649
|
+
测试行业哑元变量对估值指标的调节作用,借此判断。若正向(负向)影响,行业估值未低估(高估)
|
1650
|
+
start, end: 测试期间
|
1651
|
+
itype: 申万指数种类,默认行业类别I, 市场表征F, 投资风格F,全部A
|
1652
|
+
industries: 指定具体的指数列表,用于节省处理时间,默认all
|
1653
|
+
"""
|
1654
|
+
#设定相关参数
|
1655
|
+
# 估值指标,市盈率PE, 市净率PB, 股息率dividend,三项合用
|
1656
|
+
valuation=['PE','PB','dividend']
|
1657
|
+
# 估值指标对收益率影响的滞后时间长度
|
1658
|
+
# Monthly一个月=21天,Quarterly一个季度=63天,Annual一年=252天。分别回归,用于观察期间长短的影响
|
1659
|
+
# 用于判断过去某个时点的估值指标能够对当前的收益率产生影响,以及何种影响
|
1660
|
+
return_delay=['Annual','Quarterly','Monthly']
|
1661
|
+
|
1662
|
+
# 检查日期的合理性
|
1663
|
+
flag,start1,end1=check_period(start,end)
|
1664
|
+
if not flag:
|
1665
|
+
print(" #Error(valuation2return_sw): invalid date period",start,end)
|
1666
|
+
return None
|
1667
|
+
|
1668
|
+
# 检查估值指标的类型
|
1669
|
+
valuationlist=['pe','pb','dividend']
|
1670
|
+
if isinstance(valuation,str):
|
1671
|
+
valuation_list=[valuation]
|
1672
|
+
elif isinstance(valuation,list):
|
1673
|
+
valuation_list=valuation
|
1674
|
+
for v in valuation_list:
|
1675
|
+
if not (v.lower() in valuationlist):
|
1676
|
+
print(" #Warning(valuation2return_sw): unsupported type of valuation:",v)
|
1677
|
+
print(" supported types of valuation:",valuationlist)
|
1678
|
+
return None
|
1679
|
+
|
1680
|
+
# 检查估值指标的类型
|
1681
|
+
return_delaylist=['annual','quarterly','monthly']
|
1682
|
+
measurelist=['Annual Ret','Quarterly Ret','Monthly Ret']
|
1683
|
+
shiftlist=[252,63,21]
|
1684
|
+
|
1685
|
+
if isinstance(return_delay,str):
|
1686
|
+
return_delay_list=[return_delay]
|
1687
|
+
elif isinstance(return_delay,list):
|
1688
|
+
return_delay_list=return_delay
|
1689
|
+
|
1690
|
+
measure_list=[]
|
1691
|
+
shift_days_list=[]
|
1692
|
+
for v in return_delay_list:
|
1693
|
+
if not (v.lower() in return_delaylist):
|
1694
|
+
print(" #Warning(valuation2return_sw): unsupported type of return delay:",v)
|
1695
|
+
print(" supported types of return delay:",return_delaylist)
|
1696
|
+
return None
|
1697
|
+
|
1698
|
+
pos=return_delaylist.index(v.lower())
|
1699
|
+
measure=measurelist[pos]
|
1700
|
+
measure_list=measure_list+[measure]
|
1701
|
+
shift_days=shiftlist[pos]
|
1702
|
+
shift_days_list=shift_days_list+[shift_days]
|
1703
|
+
|
1704
|
+
#屏蔽函数内print信息输出的类
|
1705
|
+
import os, sys
|
1706
|
+
class HiddenPrints:
|
1707
|
+
def __enter__(self):
|
1708
|
+
self._original_stdout = sys.stdout
|
1709
|
+
sys.stdout = open(os.devnull, 'w')
|
1710
|
+
|
1711
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
1712
|
+
sys.stdout.close()
|
1713
|
+
sys.stdout = self._original_stdout
|
1714
|
+
|
1715
|
+
# 步骤1:获取行业历史数据,本步骤所需时间较长==================================
|
1716
|
+
print("Step1: retrieving industry information, it may take up to hours ...")
|
1717
|
+
industry_data=get_industry_sw(itype=itype)
|
1718
|
+
if not (industries.lower() == 'all'):
|
1719
|
+
industry_codes=industry_sw_codes(industries)
|
1720
|
+
else:
|
1721
|
+
industry_codes=list(set(list(industry_data['ticker'])))
|
1722
|
+
|
1723
|
+
# 步骤2:计算基础数据,本步骤所需时间较长======================================
|
1724
|
+
print("Step2: Calculating industry valuations, it may take great time ...")
|
1725
|
+
idf,idfall=calc_industry_sw(industry_data,start,end)
|
1726
|
+
|
1727
|
+
# 步骤3:构造回归数据,进行回归,记录回归结果
|
1728
|
+
import pandas as pd
|
1729
|
+
coefdflist=[]
|
1730
|
+
print("Step3: Analyzing industry performance, it may need quite some time ...")
|
1731
|
+
|
1732
|
+
total=len(measure_list)*len(valuation_list)
|
1733
|
+
for m in measure_list:
|
1734
|
+
|
1735
|
+
# (1)计算收益率
|
1736
|
+
#print(" Processing measure",m)
|
1737
|
+
ret_df=compare_industry_sw(idfall,industry_codes,measure=m,graph=False)
|
1738
|
+
industry_names=list(ret_df)
|
1739
|
+
pos=measure_list.index(m)
|
1740
|
+
d=shift_days_list[pos]
|
1741
|
+
|
1742
|
+
ret_collist=list(ret_df)
|
1743
|
+
ret_reg=pd.DataFrame()
|
1744
|
+
obs_num=0
|
1745
|
+
# 构造用于回归的数据结构
|
1746
|
+
for i in ret_collist:
|
1747
|
+
tmpdf=ret_df[[i]]
|
1748
|
+
tmpdf['行业']=i
|
1749
|
+
tmpdf.rename(columns={i:'ret'},inplace=True)
|
1750
|
+
|
1751
|
+
if len(ret_reg)==0:
|
1752
|
+
ret_reg=tmpdf
|
1753
|
+
else:
|
1754
|
+
try:
|
1755
|
+
ret_reg=ret_reg.append(tmpdf)
|
1756
|
+
except:
|
1757
|
+
ret_reg=ret_reg._append(tmpdf)
|
1758
|
+
|
1759
|
+
# (2)处理估值指标
|
1760
|
+
df=pd.DataFrame()
|
1761
|
+
for val in valuation_list:
|
1762
|
+
|
1763
|
+
#print(" Handling valuation",val,'with measure',m)
|
1764
|
+
|
1765
|
+
# (a)计算估值指标
|
1766
|
+
with HiddenPrints():
|
1767
|
+
val_df=compare_industry_valuation_sw(industry_names,start=start,end=end,
|
1768
|
+
vtypes=val,graph=False)
|
1769
|
+
# (b)滞后估值指标
|
1770
|
+
val_df2=val_df.shift(d)
|
1771
|
+
val_collist=list(val_df2)
|
1772
|
+
val_reg=pd.DataFrame()
|
1773
|
+
|
1774
|
+
for i in val_collist:
|
1775
|
+
tmpdf=val_df2[[i]]
|
1776
|
+
tmpdf['行业']=i
|
1777
|
+
tmpdf.rename(columns={i:val},inplace=True)
|
1778
|
+
|
1779
|
+
if len(val_reg)==0:
|
1780
|
+
val_reg=tmpdf
|
1781
|
+
else:
|
1782
|
+
try:
|
1783
|
+
val_reg=val_reg.append(tmpdf)
|
1784
|
+
except:
|
1785
|
+
val_reg=val_reg._append(tmpdf)
|
1786
|
+
|
1787
|
+
# (c)合成滞后的估值指标和收益率
|
1788
|
+
val_reg['日期']=val_reg.index
|
1789
|
+
ret_reg['日期']=ret_reg.index
|
1790
|
+
|
1791
|
+
df_reg=val_reg.merge(ret_reg,how='inner',on=['日期','行业'])
|
1792
|
+
df_reg.set_index('日期',inplace=True)
|
1793
|
+
df_reg2=index2date(df_reg,date_field='date')
|
1794
|
+
|
1795
|
+
df_reg2.dropna(inplace=True)
|
1796
|
+
|
1797
|
+
if len(df)==0:
|
1798
|
+
df=df_reg2
|
1799
|
+
else:
|
1800
|
+
df=pd.merge(df,df_reg2,how='outer',on=['date','行业'])
|
1801
|
+
|
1802
|
+
# 本步骤所需时间漫长,显示当前进度
|
1803
|
+
current=measure_list.index(m)*len(valuation_list)+valuation_list.index(val)
|
1804
|
+
print_progress_percent(current,total,steps=5,leading_blanks=2)
|
1805
|
+
|
1806
|
+
# (3)增加额外的自变量:PB/PE表示净资产产生利润的能力
|
1807
|
+
#print("Model regression for the impact on industrial returns by prior valuation ...")
|
1808
|
+
# PE*PB表示利润与净资产对于股价的联合支撑作用
|
1809
|
+
df['PBdivPE']=df['PB']/df['PE']
|
1810
|
+
df['PBxPE']=df['PB']*df['PE']
|
1811
|
+
xList=['PE','PB','dividend','PBdivPE','PBxPE']
|
1812
|
+
|
1813
|
+
# (4)构造哑元变量:行业,年度
|
1814
|
+
df['year']=df['date'].apply(lambda x:x[:4])
|
1815
|
+
df2,indDummies,yDummies=df_fe2(df,industry_field="行业",year_field='year')
|
1816
|
+
obs_num=obs_num+len(df2)
|
1817
|
+
|
1818
|
+
# (5)多元回归,记录行业哑元变量的回归系数和显著性
|
1819
|
+
coefdf=multi_ols(df2,xList=xList,y='ret',industryDummies=indDummies,yearDummies=yDummies)
|
1820
|
+
coefdf2=coefdf.T[indDummies].T
|
1821
|
+
coefdf2['val']=coefdf2['coef'].apply(lambda x: '低估' if x >0 else '高估')
|
1822
|
+
coefdf2['估值判断']=coefdf2['val']+coefdf2['sig']
|
1823
|
+
coefdf2['measure']=m
|
1824
|
+
|
1825
|
+
coefdflist=coefdflist+[coefdf2]
|
1826
|
+
|
1827
|
+
# 步骤4:制作多行业多期的估值评价矩阵
|
1828
|
+
allbetas=pd.DataFrame()
|
1829
|
+
for b in coefdflist:
|
1830
|
+
b['行业0']=b.index
|
1831
|
+
b['行业']=b['行业0'].apply(lambda x:x[1:])
|
1832
|
+
if b['measure'].values[0]=='Annual Ret':
|
1833
|
+
b.rename(columns={'估值判断': '年度估值判断'}, inplace=True)
|
1834
|
+
b2=b[['行业','年度估值判断']]
|
1835
|
+
elif b['measure'].values[0]=='Quarterly Ret':
|
1836
|
+
b.rename(columns={'估值判断': '季度估值判断'},inplace=True)
|
1837
|
+
b2=b[['行业','季度估值判断']]
|
1838
|
+
else:
|
1839
|
+
b.rename(columns={'估值判断': '月度估值判断'},inplace=True)
|
1840
|
+
b2=b[['行业', '月度估值判断']]
|
1841
|
+
|
1842
|
+
if len(allbetas)==0:
|
1843
|
+
allbetas=b2
|
1844
|
+
else:
|
1845
|
+
allbetas=pd.merge(allbetas,b2,how='outer',on=['行业'])
|
1846
|
+
|
1847
|
+
allbetas.fillna('未知',inplace=True)
|
1848
|
+
|
1849
|
+
allbetas['score1']=allbetas['年度估值判断'].apply(lambda x: val_score(x))
|
1850
|
+
allbetas['score2']=allbetas['季度估值判断'].apply(lambda x: val_score(x))
|
1851
|
+
allbetas['score3']=allbetas['月度估值判断'].apply(lambda x: val_score(x))
|
1852
|
+
allbetas['score']=allbetas['score1']+allbetas['score2']+allbetas['score3']
|
1853
|
+
|
1854
|
+
loest=len(allbetas[allbetas['score']<0])
|
1855
|
+
hiest=len(allbetas[allbetas['score']>0])
|
1856
|
+
|
1857
|
+
allbetas.sort_values(by=['score','行业'],ascending=[True,True],inplace=True)
|
1858
|
+
|
1859
|
+
allbetas.reset_index(drop=True,inplace=True)
|
1860
|
+
allbetas2=allbetas[['行业', '年度估值判断', '季度估值判断', '月度估值判断']]
|
1861
|
+
allbetas2.index=allbetas2.index+1
|
1862
|
+
|
1863
|
+
print("\nResults:")
|
1864
|
+
modelstr='ret = lagged('+xList[0]
|
1865
|
+
for x in xList[1:]:
|
1866
|
+
modelstr=modelstr+' + '+x
|
1867
|
+
modelstr=modelstr+')'+' + '+'Industry/Year dummies'
|
1868
|
+
print(" Valuation model:",modelstr)
|
1869
|
+
print(" Depenbdent: using Annual/Quarterly/Monthly ret respectively")
|
1870
|
+
|
1871
|
+
print(" Sample period:",start,'to',end,'\b, total',obs_num,'observations for regression')
|
1872
|
+
|
1873
|
+
print(" ",len(allbetas2),'industries valuated,',str(loest)+'('+str(hiest)+') might be under(over) estimated from future return perspective')
|
1874
|
+
|
1875
|
+
# 在Jupyter Notebook可直接显示返回的变量,格式整齐
|
1876
|
+
return allbetas2
|
1877
|
+
|
1878
|
+
#==============================================================================
|
1879
|
+
def val_score(val_comment):
|
1880
|
+
"""
|
1881
|
+
功能:基于估值判断给出评分,仅用于排序,无其他实际意义
|
1882
|
+
"""
|
1883
|
+
#print(val_comment,len(val_comment))
|
1884
|
+
if val_comment == '未知':
|
1885
|
+
score=0
|
1886
|
+
|
1887
|
+
if val_comment == '低估 ':
|
1888
|
+
score=-1
|
1889
|
+
if val_comment == '低估* ':
|
1890
|
+
score=-2
|
1891
|
+
if val_comment == '低估** ':
|
1892
|
+
score=-3
|
1893
|
+
if val_comment == '低估***':
|
1894
|
+
score=-4
|
1895
|
+
|
1896
|
+
if val_comment == '高估 ':
|
1897
|
+
score=1
|
1898
|
+
if val_comment == '高估* ':
|
1899
|
+
score=2
|
1900
|
+
if val_comment == '高估** ':
|
1901
|
+
score=3
|
1902
|
+
if val_comment == '高估***':
|
1903
|
+
score=4
|
1904
|
+
|
1905
|
+
return score
|
1906
|
+
|
1907
|
+
|
1908
|
+
|
1909
|
+
|
1910
|
+
#==============================================================================
|
1911
|
+
if __name__ == '__main__':
|
1912
|
+
date_field='date'
|
1913
|
+
|
1914
|
+
def index2date(df,date_field='date'):
|
1915
|
+
"""
|
1916
|
+
功能:从日期型df.index取出日期,类型YYYY-MM-DD,放在新字段date_field中。
|
1917
|
+
"""
|
1918
|
+
|
1919
|
+
df[date_field+'pd']=df.index
|
1920
|
+
df[date_field]=df[date_field+'pd'].apply(lambda x: x.strftime("%Y-%m-%d"))
|
1921
|
+
del df[date_field+'pd']
|
1922
|
+
|
1923
|
+
return df
|
1924
|
+
|
1925
|
+
#==============================================================================
|
1926
|
+
|
1927
|
+
|
1928
|
+
|
1929
|
+
def df_fe2(df,industry_field,year_field):
|
1930
|
+
"""
|
1931
|
+
功能:基于df做出industry_field和year_field的哑元变量'i'+industry_field和'y'+year_field
|
1932
|
+
|
1933
|
+
"""
|
1934
|
+
ilist=[]
|
1935
|
+
ylist=[]
|
1936
|
+
|
1937
|
+
#生成行业哑元变量,全部预置为0和1
|
1938
|
+
industry_list=list(set(list(df[industry_field])))
|
1939
|
+
industry_list.sort(reverse=False)
|
1940
|
+
for i in industry_list:
|
1941
|
+
df['i'+i]=df[industry_field].apply(lambda x: 1 if x==i else 0)
|
1942
|
+
ilist=ilist+['i'+i]
|
1943
|
+
|
1944
|
+
#生成年度哑元变量,全部预置为0和1
|
1945
|
+
year_list=list(set(list(df[year_field])))
|
1946
|
+
year_list.sort(reverse=False)
|
1947
|
+
for i in year_list:
|
1948
|
+
df['y'+i]=df[year_field].apply(lambda x: 1 if x==i else 0)
|
1949
|
+
ylist=ylist+['y'+i]
|
1950
|
+
|
1951
|
+
return df,ilist,ylist
|
1952
|
+
|
1953
|
+
#==============================================================================
|
1954
|
+
def sig_level(p):
|
1955
|
+
"""
|
1956
|
+
功能:基于p值给出显著性星星个数
|
1957
|
+
|
1958
|
+
"""
|
1959
|
+
if p >=0.05:
|
1960
|
+
sig=" "
|
1961
|
+
elif 0.05 > p >= 0.01:
|
1962
|
+
sig='* '
|
1963
|
+
elif 0.01 > p >= 0.001:
|
1964
|
+
sig="** "
|
1965
|
+
else:
|
1966
|
+
sig="***"
|
1967
|
+
|
1968
|
+
return sig
|
1969
|
+
|
1970
|
+
#==============================================================================
|
1971
|
+
|
1972
|
+
def multi_ols(df,xList,y,industryDummies,yearDummies):
|
1973
|
+
"""
|
1974
|
+
功能:多元线性回归, y=f(X),需要系数和显著性
|
1975
|
+
df: 所有数据
|
1976
|
+
xList: 自变量列表,不包括行业和年度哑元变量
|
1977
|
+
y: 因变量
|
1978
|
+
industryDummies: 行业哑元变量列表
|
1979
|
+
yearDummies: 年度哑元变量列表
|
1980
|
+
"""
|
1981
|
+
import statsmodels.formula.api as smf
|
1982
|
+
|
1983
|
+
#构造模型表达式:y~x1+x2+.....
|
1984
|
+
model=''
|
1985
|
+
allXVars=xList+industryDummies+yearDummies
|
1986
|
+
for x in allXVars:
|
1987
|
+
if model == '':
|
1988
|
+
model=y+'~'+x
|
1989
|
+
else:
|
1990
|
+
model=model+'+'+x
|
1991
|
+
|
1992
|
+
#形成smf模型
|
1993
|
+
reg = smf.ols(formula=model,data=df)
|
1994
|
+
|
1995
|
+
#线性回归
|
1996
|
+
result=reg.fit()
|
1997
|
+
#print(result.summary())
|
1998
|
+
|
1999
|
+
#自变量各个系数及其显著性
|
2000
|
+
import pandas as pd
|
2001
|
+
coefMatrix=pd.DataFrame([result.params,result.pvalues],index=["coef","p"]).T
|
2002
|
+
|
2003
|
+
coefMatrix['sig']=coefMatrix['p'].apply(lambda x: sig_level(x))
|
2004
|
+
|
2005
|
+
#整个模型的显著性:F-test
|
2006
|
+
modelSig=result.f_pvalue
|
2007
|
+
|
2008
|
+
return coefMatrix
|
2009
|
+
|
2010
|
+
#==============================================================================
|
2011
|
+
#==============================================================================
|
2012
|
+
#==============================================================================
|
2013
|
+
if __name__=='__main__':
|
2014
|
+
sw_code='850831.SW'
|
2015
|
+
sw_code='801193.SW'
|
2016
|
+
indicator='PE'
|
2017
|
+
start='2023-1-1'
|
2018
|
+
end='2023-12-15'
|
2019
|
+
top=10
|
2020
|
+
|
2021
|
+
def valuation_industry_sw_generating(sw_code,indicator,start,end,top=5):
|
2022
|
+
"""
|
2023
|
+
功能:模拟申万行业指数的估值,PE/PB/股息率等
|
2024
|
+
sw_code:申万行业分类指数,各个级别
|
2025
|
+
start/end:开始/结束日期
|
2026
|
+
top:使用前几大成分股的估值进行合成
|
2027
|
+
|
2028
|
+
注意:指数模拟出的估值曲线波动过大,缺乏实用价值!
|
2029
|
+
"""
|
2030
|
+
import pandas as pd
|
2031
|
+
#查找申万行业指数成分股
|
2032
|
+
clist,cdf=industry_stock_sw(industry=sw_code,top=top)
|
2033
|
+
|
2034
|
+
#查找成分股的历史估值
|
2035
|
+
df=None
|
2036
|
+
for t in clist:
|
2037
|
+
dft=get_stock_valuation_cn_hk(ticker=t,indicators=indicator,start=start,end=end)
|
2038
|
+
dft[t]=dft[indicator]
|
2039
|
+
dft2=dft[[t]]
|
2040
|
+
if dft2 is None: continue
|
2041
|
+
|
2042
|
+
#将负数填充为0,不计入估值?整个成分股剔除?
|
2043
|
+
dft2[t]=dft2[t].apply(lambda x: 0 if x<0 else x)
|
2044
|
+
|
2045
|
+
if df is None:
|
2046
|
+
df=dft2
|
2047
|
+
else:
|
2048
|
+
df=pd.merge(df,dft2,how='outer',left_index=True,right_index=True)
|
2049
|
+
|
2050
|
+
#成分股权重
|
2051
|
+
weight=list(cdf['最新权重'])
|
2052
|
+
|
2053
|
+
#各行权重分别求和
|
2054
|
+
dfw=df.copy()
|
2055
|
+
collist=list(dfw)
|
2056
|
+
for c in collist:
|
2057
|
+
dfw[c]=dfw[c].apply(lambda x: 0 if x<=0 else 1)
|
2058
|
+
dfw['weight']=dfw.dot(weight)
|
2059
|
+
dfw2=dfw[['weight']]
|
2060
|
+
|
2061
|
+
#加权平均
|
2062
|
+
df['weighted_total']=df.dot(weight)
|
2063
|
+
df2=pd.merge(df,dfw2,left_index=True,right_index=True)
|
2064
|
+
|
2065
|
+
df2['weighted_avg']=df2['weighted_total']/df2['weight']
|
2066
|
+
df2['code']=sw_code
|
2067
|
+
df3=df2[['code','weighted_avg']]
|
2068
|
+
|
2069
|
+
#因有市盈率负数,不管如何处理都会导致加权平均后数值波动过大,不能实用
|
2070
|
+
return df3
|
2071
|
+
|
2072
|
+
|
2073
|
+
|
2074
|
+
|
2075
|
+
|
2076
|
+
#==============================================================================
|