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,2339 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
本模块功能:计算财务报表比例,应用层
|
4
|
+
所属工具包:证券投资分析工具SIAT
|
5
|
+
SIAT:Security Investment Analysis Tool
|
6
|
+
创建日期:2020年9月8日
|
7
|
+
最新修订日期:2020年9月15日
|
8
|
+
作者:王德宏 (WANG Dehong, Peter)
|
9
|
+
作者单位:北京外国语大学国际商学院
|
10
|
+
作者邮件:wdehong2000@163.com
|
11
|
+
版权所有:王德宏
|
12
|
+
用途限制:仅限研究与教学使用,不可商用!商用需要额外授权。
|
13
|
+
特别声明:作者不对使用本工具进行证券投资导致的任何损益负责!
|
14
|
+
"""
|
15
|
+
#==============================================================================
|
16
|
+
#关闭所有警告
|
17
|
+
import warnings; warnings.filterwarnings('ignore')
|
18
|
+
#==============================================================================
|
19
|
+
#本模块的公共引用
|
20
|
+
from siat.common import *
|
21
|
+
from siat.translate import *
|
22
|
+
from siat.financial_statements import *
|
23
|
+
from siat.grafix import *
|
24
|
+
#==============================================================================
|
25
|
+
import matplotlib.pyplot as plt
|
26
|
+
|
27
|
+
#plt.rcParams['figure.figsize']=(12.8,7.2)
|
28
|
+
plt.rcParams['figure.figsize']=(12.8,6.4)
|
29
|
+
plt.rcParams['figure.dpi']=300
|
30
|
+
plt.rcParams['font.size'] = 13
|
31
|
+
plt.rcParams['xtick.labelsize']=11 #横轴字体大小
|
32
|
+
plt.rcParams['ytick.labelsize']=11 #纵轴字体大小
|
33
|
+
|
34
|
+
title_txt_size=16
|
35
|
+
ylabel_txt_size=14
|
36
|
+
xlabel_txt_size=14
|
37
|
+
legend_txt_size=14
|
38
|
+
|
39
|
+
#设置绘图风格:网格虚线
|
40
|
+
plt.rcParams['axes.grid']=True
|
41
|
+
#plt.rcParams['grid.color']='steelblue'
|
42
|
+
#plt.rcParams['grid.linestyle']='dashed'
|
43
|
+
#plt.rcParams['grid.linewidth']=0.5
|
44
|
+
#plt.rcParams['axes.facecolor']='whitesmoke'
|
45
|
+
|
46
|
+
#处理绘图汉字乱码问题
|
47
|
+
import sys; czxt=sys.platform
|
48
|
+
if czxt in ['win32','win64']:
|
49
|
+
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置默认字体
|
50
|
+
mpfrc={'font.family': 'SimHei'}
|
51
|
+
|
52
|
+
if czxt in ['darwin']: #MacOSX
|
53
|
+
plt.rcParams['font.family']= ['Heiti TC']
|
54
|
+
mpfrc={'font.family': 'Heiti TC'}
|
55
|
+
|
56
|
+
if czxt in ['linux']: #website Jupyter
|
57
|
+
plt.rcParams['font.family']= ['Heiti TC']
|
58
|
+
mpfrc={'font.family':'Heiti TC'}
|
59
|
+
|
60
|
+
# 解决保存图像时'-'显示为方块的问题
|
61
|
+
plt.rcParams['axes.unicode_minus'] = False
|
62
|
+
#==============================================================================
|
63
|
+
if __name__ == '__main__':
|
64
|
+
ticker=['AAPL','MSFT']
|
65
|
+
indicator=['Current Ratio','Quick Ratio']
|
66
|
+
|
67
|
+
ticker=['BABA','JD']
|
68
|
+
indicator='Cashflow per Share'
|
69
|
+
|
70
|
+
datatag=False
|
71
|
+
power=0
|
72
|
+
zeroline=False
|
73
|
+
twinx=False
|
74
|
+
loc1=loc2='best'
|
75
|
+
|
76
|
+
def compare_history(ticker,indicator, \
|
77
|
+
datatag=False,power=0,zeroline=False,twinx=False, \
|
78
|
+
loc1='best',loc2='best',graph=True):
|
79
|
+
"""
|
80
|
+
功能:比较多个股票的时序数据,绘制折线图
|
81
|
+
datatag=False: 不将数值标记在图形旁
|
82
|
+
zeroline=False:不绘制水平零线
|
83
|
+
twinx=False:单纵轴
|
84
|
+
"""
|
85
|
+
tickers=ticker
|
86
|
+
items=indicator
|
87
|
+
|
88
|
+
#检查power的范围是否合理
|
89
|
+
if not (power in range(0,80)):
|
90
|
+
print(" #Error(compare_history): invalid parameter, power =",power)
|
91
|
+
return None
|
92
|
+
|
93
|
+
#检查股票个数
|
94
|
+
ticker_num=1
|
95
|
+
if isinstance(tickers,list):
|
96
|
+
if len(tickers) >= 1: ticker1=tickers[0]
|
97
|
+
if len(tickers) >= 2:
|
98
|
+
ticker2=tickers[1]
|
99
|
+
ticker_num=2
|
100
|
+
if len(tickers) == 0:
|
101
|
+
print(" #Error(compare_history): no stock code found",tickers)
|
102
|
+
return None,None
|
103
|
+
else:
|
104
|
+
ticker1=tickers
|
105
|
+
|
106
|
+
#检查指标个数
|
107
|
+
item_num=1
|
108
|
+
if isinstance(items,list):
|
109
|
+
if len(items) >= 1: item1=items[0]
|
110
|
+
if len(items) >= 2:
|
111
|
+
item2=items[1]
|
112
|
+
item_num=2
|
113
|
+
if len(items) == 0:
|
114
|
+
print(" #Error(compare_history): no analytical item found",items)
|
115
|
+
return None,None
|
116
|
+
else:
|
117
|
+
item1=items
|
118
|
+
|
119
|
+
#判断比较模式
|
120
|
+
if (ticker_num == 1) and (item_num == 1): mode='T1I1'
|
121
|
+
if (ticker_num == 1) and (item_num == 2): mode='T1I2'
|
122
|
+
if (ticker_num == 2): mode='T2I1'
|
123
|
+
|
124
|
+
#检查指标是否支持
|
125
|
+
itemlist=[
|
126
|
+
#短期偿债能力
|
127
|
+
'Current Ratio','Quick Ratio','Cash Ratio','Cash Flow Ratio', \
|
128
|
+
#长期偿债能力
|
129
|
+
'Debt to Asset','Equity to Asset','Equity Multiplier','Debt to Equity', \
|
130
|
+
#'Debt to Tangible Net Asset', \
|
131
|
+
'Debt Service Coverage','Times Interest Earned', \
|
132
|
+
#营运能力
|
133
|
+
'Inventory Turnover','Receivable Turnover','Current Asset Turnover', \
|
134
|
+
'Fixed Asset Turnover','Total Asset Turnover', \
|
135
|
+
#盈利能力
|
136
|
+
'Operating Margin','Gross Margin','Profit Margin', \
|
137
|
+
'Net Profit on Costs','ROA','ROIC','ROE', \
|
138
|
+
#股东持股
|
139
|
+
'Payout Ratio','Cashflow per Share','CFPS','Dividend per Share','DPS', \
|
140
|
+
'Net Asset per Share','BasicEPS','DilutedEPS', \
|
141
|
+
#发展潜力
|
142
|
+
'Revenue Growth','Capital Accumulation','Total Asset Growth','PPE Residual' \
|
143
|
+
]
|
144
|
+
|
145
|
+
if item1 not in itemlist:
|
146
|
+
print(" #Error(compare_history): unsupported item for",item1)
|
147
|
+
print(" Supported items are as follows:\n",itemlist)
|
148
|
+
return None,None
|
149
|
+
if mode=='T1I2':
|
150
|
+
if item2 not in itemlist:
|
151
|
+
print(" #Error(compare_history): unsupported item for",item2)
|
152
|
+
print(" Supported items are as follows:\n",itemlist)
|
153
|
+
return None,None
|
154
|
+
|
155
|
+
#抓取数据
|
156
|
+
info1=get_financial_rates(ticker1)
|
157
|
+
if info1 is None:
|
158
|
+
print(f" #Warning(compare_history): unable to get data for {ticker1}, retrying ...")
|
159
|
+
sleep_random(max_sleep=30)
|
160
|
+
info1=get_financial_rates(ticker1)
|
161
|
+
if info1 is None:
|
162
|
+
print(" #Error(compare_history): failed to retrieved financials for",ticker1)
|
163
|
+
return None,None
|
164
|
+
|
165
|
+
cols1=['ticker','endDate','periodType',item1]
|
166
|
+
df1=info1[cols1]
|
167
|
+
df1['date']=df1['endDate']
|
168
|
+
df1.set_index('date',inplace=True)
|
169
|
+
|
170
|
+
if mode == 'T1I2':
|
171
|
+
ticker2=ticker1
|
172
|
+
cols2=['ticker','endDate','periodType',item2]
|
173
|
+
df2=info1[cols2]
|
174
|
+
df2['date']=df2['endDate']
|
175
|
+
df2.set_index('date',inplace=True)
|
176
|
+
|
177
|
+
if mode == 'T2I1':
|
178
|
+
item2=item1
|
179
|
+
info2=get_financial_rates(ticker2)
|
180
|
+
if info2 is None:
|
181
|
+
print(f" #Warning(compare_history): unable to get data for {ticker2}, retrying ...")
|
182
|
+
sleep_random(max_sleep=30)
|
183
|
+
info2=get_financial_rates(ticker2)
|
184
|
+
if info2 is None:
|
185
|
+
print(" #Error(compare_history): failed to retrieved financials for",ticker2)
|
186
|
+
return None,None
|
187
|
+
|
188
|
+
df2=info2[cols1]
|
189
|
+
df2['date']=df2['endDate']
|
190
|
+
df2.set_index('date',inplace=True)
|
191
|
+
|
192
|
+
import datetime; todaydt=datetime.date.today()
|
193
|
+
#绘图:T1I1,单折线
|
194
|
+
if mode == 'T1I1'and graph:
|
195
|
+
df=df1
|
196
|
+
colname=item1
|
197
|
+
collabel=ectranslate(item1)
|
198
|
+
ylabeltxt=''
|
199
|
+
#titletxt=ticker_name(ticker1)+texttranslate(": 基于年(季)报的业绩历史")
|
200
|
+
titletxt=ticker_name(ticker1)+": 财报业绩历史"
|
201
|
+
#footnote=texttranslate("数据来源: 雅虎财经,")+' '+str(today)
|
202
|
+
footnote="数据来源: 雅虎财经,"+' '+str(todaydt)
|
203
|
+
|
204
|
+
plot_line(df,colname,collabel,ylabeltxt,titletxt,footnote, \
|
205
|
+
datatag=datatag,power=power,zeroline=zeroline,resample_freq='3M', \
|
206
|
+
loc=loc1)
|
207
|
+
return df1,None
|
208
|
+
elif mode == 'T1I1'and not graph:
|
209
|
+
return df1,None
|
210
|
+
else:
|
211
|
+
pass
|
212
|
+
|
213
|
+
if not graph:
|
214
|
+
return df1,df2
|
215
|
+
|
216
|
+
#绘图:T1I2,单股票双折线
|
217
|
+
if mode == 'T1I2':
|
218
|
+
colname1=item1
|
219
|
+
label1=ectranslate(item1)
|
220
|
+
colname2=item2
|
221
|
+
label2=ectranslate(item2)
|
222
|
+
ylabeltxt=''
|
223
|
+
#titletxt=ticker_name(ticker1)+texttranslate(": 基于年(季)报的业绩历史对比")
|
224
|
+
titletxt=ticker_name(ticker1)+": 财报业绩历史对比"
|
225
|
+
#footnote=texttranslate("数据来源: 雅虎财经,")+' '+str(today)
|
226
|
+
footnote="数据来源: 雅虎财经,"+' '+str(today)
|
227
|
+
|
228
|
+
plot_line2(df1,ticker1,colname1,label1, \
|
229
|
+
df2,ticker2,colname2,label2, \
|
230
|
+
ylabeltxt,titletxt,footnote, \
|
231
|
+
power=power,zeroline=zeroline,twinx=twinx,resample_freq='3M', \
|
232
|
+
loc1=loc1,loc2=loc2)
|
233
|
+
return df1,df2
|
234
|
+
|
235
|
+
#绘图:T2I1,双股票双折线
|
236
|
+
if mode == 'T2I1':
|
237
|
+
df1=df1.fillna(method='ffill').fillna(method='bfill')
|
238
|
+
df2=df2.fillna(method='ffill').fillna(method='bfill')
|
239
|
+
"""
|
240
|
+
#日期可能不一致,并表
|
241
|
+
import pandas as pd
|
242
|
+
df=pd.merge(df1,df2,how="outer",on="endDate")
|
243
|
+
#df=df.fillna(method='ffill').fillna(method='bfill')
|
244
|
+
df.dropna(inplace=True)
|
245
|
+
dfx=df[['ticker_x','endDate','periodType_x',item1+'_x']]
|
246
|
+
dfx=dfx.rename(columns={'ticker_x':'ticker','periodType_x':'periodType',item1+'_x':item1})
|
247
|
+
dfx['date']=dfx['endDate']
|
248
|
+
dfx.set_index('date',inplace=True)
|
249
|
+
|
250
|
+
dfy=df[['ticker_y','endDate','periodType_y',item1+'_y']]
|
251
|
+
dfy=dfy.rename(columns={'ticker_y':'ticker','periodType_y':'periodType',item1+'_y':item1})
|
252
|
+
dfy['date']=dfy['endDate']
|
253
|
+
dfy.set_index('date',inplace=True)
|
254
|
+
"""
|
255
|
+
|
256
|
+
colname1=item1
|
257
|
+
label1=ectranslate(item1)
|
258
|
+
colname2=item2
|
259
|
+
label2=ectranslate(item2)
|
260
|
+
ylabeltxt=''
|
261
|
+
#titletxt=ticker_name(ticker1)+" vs "+ticker_name(ticker2)+texttranslate(": 基于年(季)报的业绩历史对比")
|
262
|
+
titletxt=ticker_name(ticker1)+" vs "+ticker_name(ticker2)+": 财报业绩历史对比"
|
263
|
+
#footnote=texttranslate("数据来源: 雅虎财经,")+' '+str(today)
|
264
|
+
footnote="数据来源: 雅虎财经,"+' '+str(todaydt)
|
265
|
+
|
266
|
+
#克服双线绘制时第2条线错乱问题:两个df日期强制取齐,能解决问题,但原因不明
|
267
|
+
tname1=ticker_name(ticker1)
|
268
|
+
df1.rename(columns={item1:tname1},inplace=True)
|
269
|
+
tname2=ticker_name(ticker2)
|
270
|
+
df2.rename(columns={item2:ticker_name(ticker2)},inplace=True)
|
271
|
+
df12=pd.merge(df1,df2,how='inner',left_index=True,right_index=True)
|
272
|
+
df1t=df12[[tname1]]
|
273
|
+
df2t=df12[[tname2]]
|
274
|
+
|
275
|
+
plot_line2(df1t,ticker1,tname1,label1, \
|
276
|
+
df2t,ticker2,tname2,label2, \
|
277
|
+
ylabeltxt,titletxt,footnote, \
|
278
|
+
power=power,zeroline=zeroline,twinx=twinx,resample_freq='3M', \
|
279
|
+
loc1=loc1,loc2=loc2)
|
280
|
+
|
281
|
+
return df1,df2
|
282
|
+
|
283
|
+
if __name__ == '__main__':
|
284
|
+
df1,df2=compare_history(tickers,items)
|
285
|
+
|
286
|
+
#==============================================================================
|
287
|
+
if __name__ == '__main__':
|
288
|
+
ticker=['AAPL','MSFT','WMT']
|
289
|
+
itemk='Current Ratio'
|
290
|
+
itemk='Employees'
|
291
|
+
indicator=itemk='PEG'
|
292
|
+
|
293
|
+
multicolor=False
|
294
|
+
|
295
|
+
tickers=['AMZN','EBAY']
|
296
|
+
itemk='IGR'
|
297
|
+
|
298
|
+
datatag=True
|
299
|
+
tag_offset=0.01
|
300
|
+
graph=True
|
301
|
+
axisamp=1.3
|
302
|
+
|
303
|
+
|
304
|
+
def compare_snapshot(ticker,indicator, \
|
305
|
+
facecolor='lightblue',
|
306
|
+
datatag=True,tag_offset=0.01, \
|
307
|
+
graph=True,axisamp=1.2,px=True, \
|
308
|
+
printout=True,numberPerLine=10):
|
309
|
+
"""
|
310
|
+
功能:比较多个股票的快照数据,绘制水平柱状图
|
311
|
+
itemk需要通过对照表转换为内部的item
|
312
|
+
datatag=True: 将数值标记在图形旁
|
313
|
+
tag_offset=0.01:标记的数值距离图形的距离,若不理想可以手动调节,可为最大值1%-5%
|
314
|
+
graph:是否将结果绘图,默认True
|
315
|
+
axisamp:绘图时横轴放大系数,默认1.2。若标记数值超出右边界则增加数值,也可能需要负数数值
|
316
|
+
px:是否使用plotly-express工具绘图,默认True。
|
317
|
+
其优点是无需调整axisamp,缺点是无法保存绘图结果在Jupyter Notebook中。
|
318
|
+
printout:是否显示哪些股票找到或未找到相关数据,默认True
|
319
|
+
numberPerLine:在显示相关数据时,每行显示的股票代码或名称个数,默认10
|
320
|
+
"""
|
321
|
+
tickers=ticker; itemk=indicator
|
322
|
+
|
323
|
+
#检查股票代码列表
|
324
|
+
if not isinstance(tickers,list):
|
325
|
+
print(" #Error(compare_snapshot): need more stock codes in",tickers)
|
326
|
+
return None
|
327
|
+
if len(tickers) < 2:
|
328
|
+
print(" #Error(compare_snapshot): need more stock codes in",tickers)
|
329
|
+
return None
|
330
|
+
|
331
|
+
#检查指标
|
332
|
+
if isinstance(itemk,list):
|
333
|
+
print(" #Error(compare_snapshot): only 1 item allowed here",itemk)
|
334
|
+
return None
|
335
|
+
|
336
|
+
itemdict={
|
337
|
+
#员工与ESG
|
338
|
+
'Employees':'fullTimeEmployees', \
|
339
|
+
'Total ESG':'totalEsg','Environment Score':'environmentScore', \
|
340
|
+
'Social Score':'socialScore','Governance Score':'governanceScore', \
|
341
|
+
#偿债能力
|
342
|
+
'Current Ratio':'currentRatio','Quick Ratio':'quickRatio', \
|
343
|
+
'Debt to Equity':'debtToEquity', \
|
344
|
+
#盈利能力
|
345
|
+
'EBITDA Margin':'ebitdaMargins','Operating Margin':'operatingMargins', \
|
346
|
+
'Gross Margin':'grossMargins','Profit Margin':'profitMargins', \
|
347
|
+
'ROA':'returnOnAssets','ROE':'returnOnEquity', \
|
348
|
+
#股东持股
|
349
|
+
'Held Percent Insiders':'heldPercentInsiders', \
|
350
|
+
'Held Percent Institutions':'heldPercentInstitutions', \
|
351
|
+
#股东回报
|
352
|
+
'Payout Ratio':'payoutRatio','Revenue per Share':'revenuePerShare', \
|
353
|
+
'Cashflow per Share':'totalCashPerShare', \
|
354
|
+
'Dividend Rate':'dividendRate','TTM Dividend Rate':'trailingAnnualDividendRate', \
|
355
|
+
'Dividend Yield':'dividendYield', \
|
356
|
+
'TTM Dividend Yield':'trailingAnnualDividendYield', \
|
357
|
+
'5-Year Avg Dividend Yield':'fiveYearAvgDividendYield', \
|
358
|
+
'Trailing EPS':'trailingEps','Forward EPS':'forwardEps', \
|
359
|
+
#发展潜力
|
360
|
+
'Revenue Growth':'revenueGrowth','Earnings Growth':'earningsGrowth', \
|
361
|
+
'Earnings Quarterly Growth':'earningsQuarterlyGrowth', \
|
362
|
+
'EV to Revenue':'enterpriseToRevenue','EV to EBITDA':'enterpriseToEbitda', \
|
363
|
+
#市场看法
|
364
|
+
'Current Price':'currentPrice','Price to Book':'priceToBook', \
|
365
|
+
'TTM Price to Sales':'priceToSalesTrailing12Months', \
|
366
|
+
'beta':'beta','52-Week Change':'52WeekChange', \
|
367
|
+
'Trailing PE':'trailingPE','Forward PE':'forwardPE', \
|
368
|
+
#'PEG':'pegRatio',#经常取不到数据
|
369
|
+
#'IGR':'IGR','SGR':'SGR',#另起其他命令处理
|
370
|
+
}
|
371
|
+
|
372
|
+
itemlist=list(itemdict.keys())
|
373
|
+
if itemk not in itemlist:
|
374
|
+
print(" #Error(compare_snapshot): unsupported indicator",itemk)
|
375
|
+
#print(" Supported indicators:\n",itemlist)
|
376
|
+
print(" Supported indicators:")
|
377
|
+
#printInLine(itemlist,numberPerLine=5,leadingBlanks=2)
|
378
|
+
printInLine_md(itemlist,numberPerLine=5,colalign='left',font_size='16px')
|
379
|
+
|
380
|
+
return None
|
381
|
+
|
382
|
+
item=itemdict[itemk]
|
383
|
+
import pandas as pd
|
384
|
+
df=pd.DataFrame(columns=('ticker','item','value','name'))
|
385
|
+
proxydict={'trailingPE':'forwardPE','forwardPE':'trailingPE', \
|
386
|
+
'trailingEps':'forwardEps','forwardEps':'trailingEps',}
|
387
|
+
|
388
|
+
notfoundlist=[]
|
389
|
+
total0=len(tickers)
|
390
|
+
print(" Searching",itemk,"for designated companies ...")
|
391
|
+
for t in tickers:
|
392
|
+
|
393
|
+
current=tickers.index(t)
|
394
|
+
total=total0 - len(notfoundlist)
|
395
|
+
print_progress_percent(current,total,steps=10,leading_blanks=2)
|
396
|
+
|
397
|
+
try:
|
398
|
+
info=stock_info(t)
|
399
|
+
except:
|
400
|
+
notfoundlist=notfoundlist+[t]
|
401
|
+
#print(" #Error(compare_snapshot): stock info not available for",t)
|
402
|
+
continue
|
403
|
+
if (info is None) or (len(info)==0):
|
404
|
+
notfoundlist=notfoundlist+[t]
|
405
|
+
#print(" #Error(compare_snapshot): failed to get info for",t,"\b, try later!")
|
406
|
+
continue
|
407
|
+
try:
|
408
|
+
value=info[info.index == item]['Value'][0]
|
409
|
+
except:
|
410
|
+
try:
|
411
|
+
itemp=proxydict[item]
|
412
|
+
value=info[info.index == itemp]['Value'][0]
|
413
|
+
notfoundlist=notfoundlist+[t]
|
414
|
+
#print(" #Warning(compare_snapshot):",item,"unavailable for",t,'\b, using proxy',itemp)
|
415
|
+
except:
|
416
|
+
notfoundlist=notfoundlist+[t]
|
417
|
+
#print(" #Error(compare_snapshot): failed to get info of",item,"for",t)
|
418
|
+
continue
|
419
|
+
|
420
|
+
name=ticker_name(t)
|
421
|
+
row=pd.Series({'ticker':t,'item':item,'value':value,'name':name})
|
422
|
+
try:
|
423
|
+
df=df.append(row,ignore_index=True)
|
424
|
+
except:
|
425
|
+
df=df._append(row,ignore_index=True)
|
426
|
+
|
427
|
+
# 尝试恢复失败的股票信息 1
|
428
|
+
if len(notfoundlist) > 0:
|
429
|
+
print("\n Recovering info of",itemk,"for",notfoundlist,"...")
|
430
|
+
total0=len(notfoundlist)
|
431
|
+
tickers2=notfoundlist.copy(); notfoundlist=[]
|
432
|
+
for t in tickers2:
|
433
|
+
|
434
|
+
current=tickers2.index(t)
|
435
|
+
total=total0 - len(notfoundlist)
|
436
|
+
print_progress_percent(current,total,steps=10,leading_blanks=2)
|
437
|
+
|
438
|
+
try:
|
439
|
+
info=stock_info(t)
|
440
|
+
except:
|
441
|
+
notfoundlist=notfoundlist+[t]
|
442
|
+
continue
|
443
|
+
if (info is None) or (len(info)==0):
|
444
|
+
notfoundlist=notfoundlist+[t]
|
445
|
+
continue
|
446
|
+
try:
|
447
|
+
value=info[info.index == item]['Value'][0]
|
448
|
+
except:
|
449
|
+
try:
|
450
|
+
itemp=proxydict[item]
|
451
|
+
value=info[info.index == itemp]['Value'][0]
|
452
|
+
notfoundlist=notfoundlist+[t]
|
453
|
+
except:
|
454
|
+
notfoundlist=notfoundlist+[t]
|
455
|
+
continue
|
456
|
+
|
457
|
+
name=ticker_name(t)
|
458
|
+
row=pd.Series({'ticker':t,'item':item,'value':value,'name':name})
|
459
|
+
try:
|
460
|
+
df=df.append(row,ignore_index=True)
|
461
|
+
except:
|
462
|
+
df=df._append(row,ignore_index=True)
|
463
|
+
|
464
|
+
|
465
|
+
# 尝试恢复失败的股票信息 2
|
466
|
+
if len(notfoundlist) > 0:
|
467
|
+
print("\n Recovering info of",itemk,"for",notfoundlist,"...")
|
468
|
+
total0=len(notfoundlist)
|
469
|
+
tickers3=notfoundlist.copy(); notfoundlist=[]
|
470
|
+
for t in tickers3:
|
471
|
+
|
472
|
+
current=tickers3.index(t)
|
473
|
+
total=total0 - len(notfoundlist)
|
474
|
+
print_progress_percent(current,total,steps=10,leading_blanks=2)
|
475
|
+
|
476
|
+
try:
|
477
|
+
info=stock_info(t)
|
478
|
+
except:
|
479
|
+
notfoundlist=notfoundlist+[t]
|
480
|
+
continue
|
481
|
+
if (info is None) or (len(info)==0):
|
482
|
+
notfoundlist=notfoundlist+[t]
|
483
|
+
continue
|
484
|
+
try:
|
485
|
+
value=info[info.index == item]['Value'][0]
|
486
|
+
except:
|
487
|
+
try:
|
488
|
+
itemp=proxydict[item]
|
489
|
+
value=info[info.index == itemp]['Value'][0]
|
490
|
+
notfoundlist=notfoundlist+[t]
|
491
|
+
except:
|
492
|
+
notfoundlist=notfoundlist+[t]
|
493
|
+
continue
|
494
|
+
|
495
|
+
name=ticker_name(t)
|
496
|
+
row=pd.Series({'ticker':t,'item':item,'value':value,'name':name})
|
497
|
+
try:
|
498
|
+
df=df.append(row,ignore_index=True)
|
499
|
+
except:
|
500
|
+
df=df._append(row,ignore_index=True)
|
501
|
+
|
502
|
+
# 未找到任何股票信息
|
503
|
+
if len(df) == 0:
|
504
|
+
print(f"\n #Warning(compare_snapshot): no {indicator} found for specified stocks")
|
505
|
+
print(" Reasons: wrong codes, failed to access to or fetch info from Yahoo Finance")
|
506
|
+
print(" Feel weired? upgrade yahooquery, which may need certain versions of lxml")
|
507
|
+
return None
|
508
|
+
|
509
|
+
#处理小数点
|
510
|
+
try:
|
511
|
+
df['value']=round(df['value'],3)
|
512
|
+
except:
|
513
|
+
pass
|
514
|
+
df.sort_values(by='value',ascending=True,inplace=True)
|
515
|
+
df['key']=df['name']
|
516
|
+
df.set_index('key',inplace=True)
|
517
|
+
|
518
|
+
#绘图
|
519
|
+
if graph:
|
520
|
+
print(" Calculating and rendering graph, please wait ...")
|
521
|
+
colname='value'
|
522
|
+
|
523
|
+
lang=check_language()
|
524
|
+
if lang == 'Chinese':
|
525
|
+
titletxt="企业对比: 指标快照"
|
526
|
+
notestxt="注:财务指标为TTM数值"
|
527
|
+
else:
|
528
|
+
titletxt="Company Snapshot Comparison"
|
529
|
+
notestxt="Note: TTM values for financial ratios"
|
530
|
+
|
531
|
+
import datetime; today=datetime.date.today()
|
532
|
+
lang=check_language()
|
533
|
+
if lang=='English':
|
534
|
+
footnote1="Source: Yahoo Finance, "
|
535
|
+
else:
|
536
|
+
footnote1="数据来源: 雅虎财经,"
|
537
|
+
footnote=ectranslate(itemk)+" -->\n"+notestxt+'\n'+footnote1+str(today)
|
538
|
+
|
539
|
+
df.rename(columns={'value':itemk},inplace=True)
|
540
|
+
colname=itemk
|
541
|
+
|
542
|
+
if not px:
|
543
|
+
footnote=ectranslate(itemk)+" -->\n"+notestxt+'\n'+footnote1+str(today)
|
544
|
+
plot_barh(df,colname,titletxt,footnote,datatag=datatag,tag_offset=tag_offset,axisamp=axisamp)
|
545
|
+
else:
|
546
|
+
#在Spyder中可能无法显示
|
547
|
+
titletxt="企业快照:"+ectranslate(itemk)
|
548
|
+
footnote=notestxt+','+footnote1+str(today)
|
549
|
+
plot_barh2(df,colname,titletxt,footnote,facecolor=facecolor)
|
550
|
+
|
551
|
+
if (len(notfoundlist) > 0):
|
552
|
+
foundlist=[]
|
553
|
+
for t in tickers:
|
554
|
+
if not (t in notfoundlist):
|
555
|
+
foundlist=foundlist+[t]
|
556
|
+
else:
|
557
|
+
foundlist=tickers
|
558
|
+
|
559
|
+
"""
|
560
|
+
if len(foundlist) > 0:
|
561
|
+
foundlist_names=ticker_name(foundlist)
|
562
|
+
print("Results:",itemk,"info found for the stocks below")
|
563
|
+
printInLine(foundlist_names,numberPerLine=numberPerLine,leadingBlanks=2)
|
564
|
+
printInLine(foundlist,numberPerLine=numberPerLine,leadingBlanks=2)
|
565
|
+
"""
|
566
|
+
if (len(notfoundlist) > 0):
|
567
|
+
print(" [Warning]",itemk,"info not found for the stocks below:")
|
568
|
+
notfoundlist_names=ticker_name(notfoundlist)
|
569
|
+
printInLine(notfoundlist_names,numberPerLine=numberPerLine,leadingBlanks=2)
|
570
|
+
print(" [Solution] re-run the command with more stable internet connection")
|
571
|
+
|
572
|
+
return df
|
573
|
+
|
574
|
+
if __name__ == '__main__':
|
575
|
+
df=compare_snapshot(tickers,itemk)
|
576
|
+
|
577
|
+
#==============================================================================
|
578
|
+
def compare_snapshot2(ticker,indicator,graph=True):
|
579
|
+
"""
|
580
|
+
功能:比较多个股票的快照数据,绘制水平柱状图
|
581
|
+
itemk需要通过对照表转换为内部的item
|
582
|
+
|
583
|
+
特点:与compare_snapshot相比如何?
|
584
|
+
"""
|
585
|
+
tickers=ticker; itemk=indicator
|
586
|
+
|
587
|
+
#检查股票代码列表
|
588
|
+
if not isinstance(tickers,list):
|
589
|
+
print(" #Error(compare_snapshot2): need more stock codes in",tickers)
|
590
|
+
return None
|
591
|
+
if len(tickers) < 2:
|
592
|
+
print(" #Error(compare_snapshot2): need more stock codes in",tickers)
|
593
|
+
return None
|
594
|
+
|
595
|
+
#检查指标
|
596
|
+
if isinstance(itemk,list):
|
597
|
+
print(" #Error(compare_snapshot2): only 1 item allowed here",itemk)
|
598
|
+
return None
|
599
|
+
|
600
|
+
itemdict={
|
601
|
+
#员工与ESG
|
602
|
+
'Employees':'fullTimeEmployees', \
|
603
|
+
'Total ESG':'totalEsg','Environment Score':'environmentScore', \
|
604
|
+
'Social Score':'socialScore','Governance Score':'governanceScore', \
|
605
|
+
#偿债能力
|
606
|
+
'Current Ratio':'currentRatio','Quick Ratio':'quickRatio', \
|
607
|
+
'Debt to Equity':'debtToEquity', \
|
608
|
+
#盈利能力
|
609
|
+
'EBITDA Margin':'ebitdaMargins','Operating Margin':'operatingMargins', \
|
610
|
+
'Gross Margin':'grossMargins','Profit Margin':'profitMargins', \
|
611
|
+
'ROA':'returnOnAssets','ROE':'returnOnEquity', \
|
612
|
+
#股东持股
|
613
|
+
'Held Percent Insiders':'heldPercentInsiders', \
|
614
|
+
'Held Percent Institutions':'heldPercentInstitutions', \
|
615
|
+
#股东回报
|
616
|
+
'Payout Ratio':'payoutRatio','Revenue per Share':'revenuePerShare', \
|
617
|
+
'Cashflow per Share':'totalCashPerShare', \
|
618
|
+
'Dividend Rate':'dividendRate','TTM Dividend Rate':'trailingAnnualDividendRate', \
|
619
|
+
'Dividend Yield':'dividendYield', \
|
620
|
+
'TTM Dividend Yield':'trailingAnnualDividendYield', \
|
621
|
+
'5-Year Avg Dividend Yield':'fiveYearAvgDividendYield', \
|
622
|
+
'Trailing EPS':'trailingEps','Forward EPS':'forwardEps', \
|
623
|
+
#发展潜力
|
624
|
+
'Revenue Growth':'revenueGrowth','Earnings Growth':'earningsGrowth', \
|
625
|
+
'Earnings Quarterly Growth':'earningsQuarterlyGrowth', \
|
626
|
+
'EV to Revenue':'enterpriseToRevenue','EV to EBITDA':'enterpriseToEbitda', \
|
627
|
+
#市场看法
|
628
|
+
'Current Price':'currentPrice','Price to Book':'priceToBook', \
|
629
|
+
#'TTM Price to Sales':'priceToSalesTrailing12Months', \
|
630
|
+
'Price to Sales':'priceToSalesTrailing12Months', \
|
631
|
+
'beta':'beta','52-Week Change':'52WeekChange', \
|
632
|
+
'Trailing PE':'trailingPE','Forward PE':'forwardPE', \
|
633
|
+
#'PEG':'pegRatio',
|
634
|
+
#'IGR':'IGR','SGR':'SGR'
|
635
|
+
}
|
636
|
+
itemlist=list(itemdict.keys())
|
637
|
+
if itemk not in itemlist:
|
638
|
+
print(" #Error(compare_snapshot): unsupported rate for",itemk)
|
639
|
+
print(" Supported rates are as follows:\n",itemlist)
|
640
|
+
return None
|
641
|
+
|
642
|
+
item=itemdict[itemk]
|
643
|
+
import pandas as pd
|
644
|
+
#import siat.stock_base as sb
|
645
|
+
df=pd.DataFrame(columns=('ticker','item','value','name'))
|
646
|
+
print(f" Working on {indicator} for specified stocks ...")
|
647
|
+
for t in tickers:
|
648
|
+
print_progress_percent2(t,tickers,steps=5,leading_blanks=4)
|
649
|
+
|
650
|
+
try:
|
651
|
+
info=stock_info(t)
|
652
|
+
except:
|
653
|
+
print(" #Error(compare_snapshot): stock info not available for",t)
|
654
|
+
continue
|
655
|
+
if (info is None) or (len(info)==0):
|
656
|
+
print(" #Error(compare_snapshot): failed to get info for",t,"\b, try later!")
|
657
|
+
continue
|
658
|
+
try:
|
659
|
+
value=info[info.index == item]['Value'][0]
|
660
|
+
except:
|
661
|
+
print(" #Error(compare_snapshot): failed to get info of",item,"for",t)
|
662
|
+
continue
|
663
|
+
"""
|
664
|
+
name=info[info.index == 'shortName']['Value'][0]
|
665
|
+
name1=name.split(' ',1)[0] #取空格分隔字符串的第一个单词
|
666
|
+
name2=name1.split(',',1)[0]
|
667
|
+
name3=name2.split('.',1)[0]
|
668
|
+
"""
|
669
|
+
name=ticker_name(t)
|
670
|
+
row=pd.Series({'ticker':t,'item':item,'value':value,'name':name})
|
671
|
+
try:
|
672
|
+
df=df.append(row,ignore_index=True)
|
673
|
+
except:
|
674
|
+
df=df._append(row,ignore_index=True)
|
675
|
+
|
676
|
+
if len(df) == 0:
|
677
|
+
print(" #Error(compare_snapshot): stock info not found in",tickers)
|
678
|
+
return None
|
679
|
+
|
680
|
+
#处理小数点
|
681
|
+
try:
|
682
|
+
df['value']=round(df['value'],3)
|
683
|
+
except:
|
684
|
+
pass
|
685
|
+
df.sort_values(by='value',ascending=True,inplace=True)
|
686
|
+
df['key']=df['name']
|
687
|
+
df.set_index('key',inplace=True)
|
688
|
+
|
689
|
+
#绘图
|
690
|
+
if graph:
|
691
|
+
print(" Calculating and rendering graph, please wait ...")
|
692
|
+
|
693
|
+
df.rename(columns={'value':itemk},inplace=True)
|
694
|
+
colname=itemk
|
695
|
+
#titletxt="企业横向对比: "+ectranslate(itemk)+"(TTM)"
|
696
|
+
titletxt=text_lang("企业对比: ","Comparing Company: ")+ectranslate(itemk)
|
697
|
+
import datetime; today=datetime.date.today()
|
698
|
+
footnote=text_lang("注:财务比率为TTM,数据来源: 雅虎财经, ","Note: TTM data, source: Yahoo Finance, ")+str(today)
|
699
|
+
plot_barh2(df,colname,titletxt,footnote)
|
700
|
+
|
701
|
+
return df
|
702
|
+
|
703
|
+
if __name__ == '__main__':
|
704
|
+
df=compare_snapshot(tickers,itemk)
|
705
|
+
|
706
|
+
#==============================================================================
|
707
|
+
|
708
|
+
if __name__ == '__main__':
|
709
|
+
tickers=["0883.HK","0857.HK","0386.HK",'XOM','2222.SR','OXY','BP','RDSA.AS']
|
710
|
+
graph=True
|
711
|
+
|
712
|
+
def compare_tax(ticker,graph=True,axisamp=1.3,px=True):
|
713
|
+
"""
|
714
|
+
功能:比较公司最新的实际所得税率
|
715
|
+
"""
|
716
|
+
tickers=ticker
|
717
|
+
|
718
|
+
#检查股票代码列表
|
719
|
+
if not isinstance(tickers,list):
|
720
|
+
print(" #Error(compare_tax): need more stock codes in",tickers)
|
721
|
+
return None
|
722
|
+
if len(tickers) < 2:
|
723
|
+
print(" #Error(compare_tax): need more stock codes in",tickers)
|
724
|
+
return None
|
725
|
+
|
726
|
+
import siat.beta_adjustment as badj
|
727
|
+
import pandas as pd
|
728
|
+
df=pd.DataFrame(columns=('ticker','name','date','tax rate'))
|
729
|
+
print(" Working on tax info for specified stocks ...")
|
730
|
+
for t in tickers:
|
731
|
+
print_progress_percent2(t,tickers,steps=5,leading_blanks=4)
|
732
|
+
|
733
|
+
try:
|
734
|
+
df0=badj.prepare_hamada_yahoo(t)
|
735
|
+
except:
|
736
|
+
print(" #Warning(compare_tax): stock info not available for",t)
|
737
|
+
continue
|
738
|
+
df1=df0.tail(1)
|
739
|
+
name=ticker_name(t)
|
740
|
+
reportdate=df1.index[0]
|
741
|
+
taxrate=df1['tax rate'][0]
|
742
|
+
row=pd.Series({'ticker':t,'name':name,'date':reportdate,'tax rate':round(taxrate,3)})
|
743
|
+
try:
|
744
|
+
df=df.append(row,ignore_index=True)
|
745
|
+
except:
|
746
|
+
df=df._append(row,ignore_index=True)
|
747
|
+
|
748
|
+
df.sort_values(by='tax rate',ascending=True,inplace=True)
|
749
|
+
df['key']=df['name']
|
750
|
+
df.set_index('key',inplace=True)
|
751
|
+
#绘图
|
752
|
+
if graph:
|
753
|
+
print(" Calculating and rendering graph, please wait ...")
|
754
|
+
lang=check_language()
|
755
|
+
colname='tax rate'
|
756
|
+
if lang == 'Chinese':
|
757
|
+
titletxt="企业对比: 实际所得税率"
|
758
|
+
itemk="实际所得税率"
|
759
|
+
source_txt="数据来源: 雅虎财经,"
|
760
|
+
else:
|
761
|
+
titletxt=texttranslate("企业对比: 实际税率")
|
762
|
+
itemk=texttranslate("实际所得税率")
|
763
|
+
source_txt=texttranslate("数据来源: 雅虎财经,")
|
764
|
+
|
765
|
+
import datetime; today=datetime.date.today()
|
766
|
+
if not px:
|
767
|
+
footnote=itemk+" -->\n"+source_txt+" "+str(today)
|
768
|
+
plot_barh(df,colname,titletxt,footnote,axisamp=axisamp)
|
769
|
+
else:
|
770
|
+
footnote=source_txt+" "+str(today)
|
771
|
+
plot_barh2(df,colname,titletxt,footnote)
|
772
|
+
|
773
|
+
return df
|
774
|
+
#==============================================================================
|
775
|
+
if __name__ == '__main__':
|
776
|
+
ticker='EBAY'
|
777
|
+
|
778
|
+
def calc_igr_sgr(ticker):
|
779
|
+
|
780
|
+
|
781
|
+
import siat.stock as stk
|
782
|
+
sub_info=stk.get_stock_profile(ticker,info_type='fin_rates',printout=False)
|
783
|
+
"""
|
784
|
+
#应对各种出错情形:执行出错,返回NoneType,返回空值
|
785
|
+
try:
|
786
|
+
info=stk.stock_info(ticker)
|
787
|
+
except:
|
788
|
+
print(" #Warning(calc_igr_sgr): failed to retrieve info of",ticker,"\b, recovering...")
|
789
|
+
import time; time.sleep(5)
|
790
|
+
try:
|
791
|
+
info=stock_info(ticker)
|
792
|
+
except:
|
793
|
+
print(" #Error(calc_igr_sgr): failed to retrieve info of",ticker)
|
794
|
+
return None
|
795
|
+
if info is None:
|
796
|
+
print(" #Error(calc_igr_sgr): retrieved none info of",ticker)
|
797
|
+
return None
|
798
|
+
if len(info) == 0:
|
799
|
+
print(" #Error(calc_igr_sgr): retrieved empty info of",ticker)
|
800
|
+
return None
|
801
|
+
sub_info=stock_fin_rates(info)
|
802
|
+
"""
|
803
|
+
if sub_info is None:
|
804
|
+
return None,None
|
805
|
+
|
806
|
+
roa=list(sub_info[sub_info.index=='returnOnAssets']['Value'])[0]
|
807
|
+
roe=list(sub_info[sub_info.index=='returnOnEquity']['Value'])[0]
|
808
|
+
try:
|
809
|
+
b=1-list(sub_info[sub_info.index=='payoutRatio']['Value'])[0]
|
810
|
+
except:
|
811
|
+
b=1-0
|
812
|
+
|
813
|
+
igr=round(roa*b/(1-roa*b),4)
|
814
|
+
sgr=round(roe*b/(1-roe*b),4)
|
815
|
+
|
816
|
+
return igr,sgr
|
817
|
+
|
818
|
+
def compare_igr_sgr(ticker,graph=True,axisamp=1.0,px=True):
|
819
|
+
"""
|
820
|
+
功能:比较公司TTM的IGR和SGR
|
821
|
+
"""
|
822
|
+
tickers=ticker
|
823
|
+
|
824
|
+
#检查股票代码列表
|
825
|
+
if not isinstance(tickers,list):
|
826
|
+
print(" #Error(compare_igr_sgr): need more stock codes in",tickers)
|
827
|
+
return None
|
828
|
+
if len(tickers) < 2:
|
829
|
+
print(" #Error(compare_igr_sgr): need more stock codes in",tickers)
|
830
|
+
return None
|
831
|
+
|
832
|
+
import pandas as pd
|
833
|
+
df=pd.DataFrame(columns=('ticker','name','date','IGR','SGR'))
|
834
|
+
print(" Working on IGR & SGR for specified stocks ...")
|
835
|
+
for t in tickers:
|
836
|
+
print_progress_percent2(t,tickers,steps=5,leading_blanks=4)
|
837
|
+
|
838
|
+
try:
|
839
|
+
igr,sgr=calc_igr_sgr(t)
|
840
|
+
except:
|
841
|
+
print(" #Warning(compare_igr_sgr): stock info not available for",t)
|
842
|
+
continue
|
843
|
+
if igr is None or sgr is None:
|
844
|
+
print(" #Warning(compare_igr_sgr): stock info not available for",t)
|
845
|
+
continue
|
846
|
+
name=ticker_name(t)
|
847
|
+
row=pd.Series({'ticker':t,'name':name,'IGR':round(igr,3),'SGR':round(sgr,3)})
|
848
|
+
try:
|
849
|
+
df=df.append(row,ignore_index=True)
|
850
|
+
except:
|
851
|
+
df=df._append(row,ignore_index=True)
|
852
|
+
|
853
|
+
#绘制IGR
|
854
|
+
df.sort_values(by='IGR',ascending=True,inplace=True)
|
855
|
+
df['key']=df['name']
|
856
|
+
df.set_index('key',inplace=True)
|
857
|
+
#绘图
|
858
|
+
lang=check_language()
|
859
|
+
if graph:
|
860
|
+
print("\n Calculating and rendering graph, please wait ...")
|
861
|
+
|
862
|
+
colname='IGR'
|
863
|
+
if lang == "Chinese":
|
864
|
+
titletxt="企业对比: 内部增长率IGR"
|
865
|
+
itemk="内部增长率(IGR)"
|
866
|
+
source_txt="数据来源: 雅虎财经,"
|
867
|
+
else:
|
868
|
+
titletxt="Company Internal Growth Rate (IGR TTM)"
|
869
|
+
itemk="Internal growth rate"
|
870
|
+
source_txt="Source: Yahoo Finance,"
|
871
|
+
|
872
|
+
import datetime; today=datetime.date.today()
|
873
|
+
if not px:
|
874
|
+
footnote=ectranslate(itemk)+" -->\n"+source_txt+" "+str(today)
|
875
|
+
plot_barh(df,colname,titletxt,footnote,axisamp=axisamp)
|
876
|
+
else:
|
877
|
+
footnote=source_txt+" "+str(today)
|
878
|
+
plot_barh2(df,colname,titletxt,footnote)
|
879
|
+
|
880
|
+
#绘制SGR
|
881
|
+
df.sort_values(by='SGR',ascending=True,inplace=True)
|
882
|
+
df['key']=df['name']
|
883
|
+
df.set_index('key',inplace=True)
|
884
|
+
#绘图
|
885
|
+
if graph:
|
886
|
+
colname='SGR'
|
887
|
+
if lang == 'Chinese':
|
888
|
+
titletxt="企业对比: 可持续增长率SGR"
|
889
|
+
itemk="可持续增长率(SGR)"
|
890
|
+
else:
|
891
|
+
titletxt="Company Sustainable Growth Rate (SGR TTM)"
|
892
|
+
itemk="Sustainable growth rate"
|
893
|
+
|
894
|
+
if not px:
|
895
|
+
footnote=ectranslate(itemk)+" -->\n"+source_txt+" "+str(today)
|
896
|
+
plot_barh(df,colname,titletxt,footnote,axisamp=axisamp)
|
897
|
+
else:
|
898
|
+
footnote=source_txt+" "+str(today)
|
899
|
+
plot_barh2(df,colname,titletxt,footnote)
|
900
|
+
|
901
|
+
return df
|
902
|
+
|
903
|
+
|
904
|
+
#==============================================================================
|
905
|
+
if __name__ == '__main__':
|
906
|
+
fsdf=get_financial_statements('AAPL')
|
907
|
+
fst=fsdf.T #查看科目名称更加方便
|
908
|
+
|
909
|
+
def get_PE(fsdf):
|
910
|
+
"""
|
911
|
+
功能:计算PE
|
912
|
+
"""
|
913
|
+
dateymd=lambda x:x.strftime('%Y-%m-%d')
|
914
|
+
fsdf['endDate']=fsdf['asOfDate'].apply(dateymd)
|
915
|
+
|
916
|
+
#获得各个报表的日期范围,适当扩大日期范围以规避非交易日
|
917
|
+
start=min(list(fsdf['endDate']))
|
918
|
+
fromdate=date_adjust(start,adjust=-30)
|
919
|
+
end=max(list(fsdf['endDate']))
|
920
|
+
todate=date_adjust(end,adjust=30)
|
921
|
+
|
922
|
+
#获取股价
|
923
|
+
ticker=list(fsdf['ticker'])[0]
|
924
|
+
import siat.security_prices as ssp
|
925
|
+
prices=ssp.get_price(ticker, fromdate, todate)
|
926
|
+
if prices is None:
|
927
|
+
print(" #Error(get_PE): retrieving stock price failed for",ticker,fromdate,todate,"\b, recovering...")
|
928
|
+
import time; time.sleep(5)
|
929
|
+
prices=ssp.get_price(ticker, fromdate, todate)
|
930
|
+
if prices is None:
|
931
|
+
print(" #Error(get_PE): failed retrieving stock price, retrying stopped")
|
932
|
+
import numpy as np
|
933
|
+
fsdf['BasicPE']=np.nan
|
934
|
+
fsdf['DilutedPE']=np.nan
|
935
|
+
return fsdf
|
936
|
+
|
937
|
+
prices['datedt']=prices.index.date
|
938
|
+
datecvt=lambda x: str(x)[0:10]
|
939
|
+
prices['Date']=prices['datedt'].apply(datecvt)
|
940
|
+
|
941
|
+
#报表日期列表
|
942
|
+
datelist_fs=list(fsdf['endDate'])
|
943
|
+
#价格日期列表
|
944
|
+
datelist_price=list(prices['Date'])
|
945
|
+
date_price_min=min(datelist_price)
|
946
|
+
date_price_max=max(datelist_price)
|
947
|
+
|
948
|
+
#股价列表
|
949
|
+
pricelist=list(prices['Close'])
|
950
|
+
|
951
|
+
import pandas as pd
|
952
|
+
pricedf=pd.DataFrame(columns=('endDate','actualDate','Price'))
|
953
|
+
for d in datelist_fs:
|
954
|
+
found=False
|
955
|
+
d1=d
|
956
|
+
if d in datelist_price:
|
957
|
+
found=True
|
958
|
+
pos=datelist_price.index(d)
|
959
|
+
p=pricelist[pos]
|
960
|
+
else:
|
961
|
+
while (d1 >= date_price_min) and not found:
|
962
|
+
d1=date_adjust(d1,adjust=-1)
|
963
|
+
if d1 in datelist_price:
|
964
|
+
found=True
|
965
|
+
pos=datelist_price.index(d1)
|
966
|
+
p=pricelist[pos]
|
967
|
+
while (d1 <= date_price_max) and not found:
|
968
|
+
d1=date_adjust(d1,adjust=1)
|
969
|
+
if d1 in datelist_price:
|
970
|
+
found=True
|
971
|
+
pos=datelist_price.index(d1)
|
972
|
+
p=pricelist[pos]
|
973
|
+
#记录股价
|
974
|
+
row=pd.Series({'endDate':d,'actualDate':d1,'Price':p})
|
975
|
+
try:
|
976
|
+
pricedf=pricedf.append(row,ignore_index=True)
|
977
|
+
except:
|
978
|
+
pricedf=pricedf._append(row,ignore_index=True)
|
979
|
+
|
980
|
+
#合成表
|
981
|
+
fsdf1=pd.merge(fsdf,pricedf,on='endDate')
|
982
|
+
fsdf1['BasicPE']=fsdf1['Price']/fsdf1['BasicEPS']
|
983
|
+
fsdf1['DilutedPE']=fsdf1['Price']/fsdf1['DilutedEPS']
|
984
|
+
|
985
|
+
return fsdf1
|
986
|
+
|
987
|
+
if __name__ == '__main__':
|
988
|
+
fsdf1=get_PE(fsdf)
|
989
|
+
|
990
|
+
#==============================================================================
|
991
|
+
if __name__ == '__main__':
|
992
|
+
fsdf=get_financial_statements('AAPL')
|
993
|
+
fst=fsdf.T #查看科目名称更加方便
|
994
|
+
|
995
|
+
def calc_DebtToAsset(fsdf):
|
996
|
+
"""
|
997
|
+
功能:计算资产负债率
|
998
|
+
"""
|
999
|
+
|
1000
|
+
fsdf1=fsdf.copy()
|
1001
|
+
|
1002
|
+
#计算Debt to Asset
|
1003
|
+
try:
|
1004
|
+
fsdf1['Debt to Asset']=round(fsdf1['TotalLiabilities']/fsdf1['TotalAssets'],4)
|
1005
|
+
except:
|
1006
|
+
print(" #Error(get_DebtToAsset): failed in calculating DebtToAsset")
|
1007
|
+
|
1008
|
+
#计算Debt to Equity
|
1009
|
+
try:
|
1010
|
+
fsdf1['Debt to Equity']=round(fsdf1['TotalLiabilities']/fsdf1['TotalEquities'],4)
|
1011
|
+
except:
|
1012
|
+
print(" #Error(get_DebtToAsset): failed in calculating DebtToEquity")
|
1013
|
+
|
1014
|
+
return fsdf1
|
1015
|
+
|
1016
|
+
if __name__ == '__main__':
|
1017
|
+
fsdf1=get_DebtToAsset(fsdf)
|
1018
|
+
|
1019
|
+
|
1020
|
+
#==============================================================================
|
1021
|
+
if __name__ == '__main__':
|
1022
|
+
fsdf=get_financial_statements('AAPL')
|
1023
|
+
fst=fsdf.T #查看科目名称更加方便
|
1024
|
+
|
1025
|
+
fsdf=get_financial_statements('3333.HK')
|
1026
|
+
fsdf=get_financial_statements('601398.SS')
|
1027
|
+
|
1028
|
+
def calc_fin_rates(fsdf):
|
1029
|
+
"""
|
1030
|
+
功能:基于财报计算各种指标
|
1031
|
+
注意:ROA/ROE/EM/turnover比率基于期初期末均值计算,其余仅基于期末数据计算!
|
1032
|
+
"""
|
1033
|
+
#####前后填充缺失值
|
1034
|
+
if fsdf is None: return None
|
1035
|
+
fs = fsdf.fillna(method='ffill').fillna(method='bfill')
|
1036
|
+
|
1037
|
+
"""
|
1038
|
+
#期初期末平均数
|
1039
|
+
fs['avgInventory']=(fs['Inventory']+fs['Inventory'].shift(1))/2.0
|
1040
|
+
fs['avgReceivables']=(fs['AccountsReceivable']+fs['AccountsReceivable'].shift(1))/2.0
|
1041
|
+
fs['avgCurrentAsset']=(fs['CurrentAssets']+fs['CurrentAssets'].shift(1))/2.0
|
1042
|
+
fs['avgPPE']=(fs['NetPPE']+fs['NetPPE'].shift(1))/2.0
|
1043
|
+
fs['avgTotalAsset']=(fs['TotalAssets']+fs['TotalAssets'].shift(1))/2.0
|
1044
|
+
fs['avgNetPPE']=(fs['NetPPE']+fs['NetPPE'].shift(1))/2.0
|
1045
|
+
fs['avgGrossPPE']=(fs['GrossPPE']+fs['GrossPPE'].shift(1))/2.0
|
1046
|
+
fs['avgTotalEquity']=(fs['TotalEquities']+fs['TotalEquities'].shift(1))/2.0
|
1047
|
+
"""
|
1048
|
+
|
1049
|
+
#短期偿债能力指标
|
1050
|
+
#流动比率:流动资产 / 流动负债
|
1051
|
+
fs['Current Ratio']=fs['CurrentAssets']/fs['CurrentLiabilities']
|
1052
|
+
#速动比率:(流动资产-存货) / 流动负债
|
1053
|
+
fs['Quick Ratio']=(fs['CurrentAssets']-fs['Inventory'])/fs['CurrentLiabilities']
|
1054
|
+
#现金比率: (现金+现金等价物) / 流动负债
|
1055
|
+
fs['Cash Ratio']=fs['CashAndCashEquivalents']/fs['CurrentLiabilities']
|
1056
|
+
#现金流量比率:经营活动现金流量 / 流动负债
|
1057
|
+
fs['Cash Flow Ratio']=fs['OperatingCashFlow']/fs['CurrentLiabilities']
|
1058
|
+
|
1059
|
+
#####长期偿债能力指标
|
1060
|
+
#资产负债率:负债总额 / 资产总额
|
1061
|
+
fs['Debt to Asset']=fs['TotalLiabilities']/fs['TotalAssets']
|
1062
|
+
#股东权益比率:股东权益总额 / 资产总额
|
1063
|
+
fs['Equity to Asset']=fs['TotalEquities']/fs['TotalAssets']
|
1064
|
+
|
1065
|
+
#权益乘数:资产总额 / 股东权益总额,使用期初期末均值*****
|
1066
|
+
fs=fs_entry_begin(fs,account_entry='TotalAssets',suffix='_begin')
|
1067
|
+
fs=fs_entry_begin(fs,account_entry='TotalEquities',suffix='_begin')
|
1068
|
+
fs['Equity Multiplier']=((fs['TotalAssets']+fs['TotalAssets_begin'])/2)/((fs['TotalEquities']+fs['TotalEquities_begin'])/2)
|
1069
|
+
#fs['Equity Multiplier']=fs['avgTotalAsset']/fs['avgTotalEquity']
|
1070
|
+
|
1071
|
+
#负债股权比率:负债总额 / 股东权益总额
|
1072
|
+
fs['Debt to Equity']=fs['TotalLiabilities']/fs['TotalEquities']
|
1073
|
+
#有形净值债务率:负债总额 / (股东权益-无形资产净额)
|
1074
|
+
fs['netIntangibleAsset']=fs['TotalAssets']-fs['NetTangibleAssets']
|
1075
|
+
fs['Debt to Tangible Net Asset']=fs['TotalLiabilities']/(fs['TotalEquities']-fs['netIntangibleAsset'])
|
1076
|
+
#偿债保障比率:负债总额 / 经营活动现金净流量
|
1077
|
+
fs['Debt Service Coverage']=fs['TotalLiabilities']/fs['OperatingCashFlow']
|
1078
|
+
#利息保障倍数:(税前利润+利息费用)/ 利息费用
|
1079
|
+
fs['Times Interest Earned']=fs['PretaxIncome']/fs['InterestExpense']+1
|
1080
|
+
|
1081
|
+
#营运能力指标
|
1082
|
+
#存货周转率:销售收入 / 期末存货,平均存货计算困难
|
1083
|
+
#fs['Inventory Turnover']=fs['CostOfRevenue']/fs['avgInventory']
|
1084
|
+
#fs['Inventory Turnover']=fs['CostOfRevenue']/fs['Inventory']
|
1085
|
+
fs=fs_entry_begin(fs,account_entry='Inventory',suffix='_begin')
|
1086
|
+
fs['Inventory Turnover']=fs['TotalRevenue']/((fs['Inventory']+fs['Inventory_begin'])/2)
|
1087
|
+
#应收账款周转率:赊销收入净额 / 平均应收账款余额
|
1088
|
+
#fs['Receivable Turnover']=fs['TotalRevenue']/fs['avgReceivables']
|
1089
|
+
fs=fs_entry_begin(fs,account_entry='AccountsReceivable',suffix='_begin')
|
1090
|
+
fs['Receivable Turnover']=fs['TotalRevenue']/((fs['AccountsReceivable']+fs['AccountsReceivable_begin'])/2)
|
1091
|
+
#流动资产周转率:销售收入 / 平均流动资产余额
|
1092
|
+
#fs['Current Asset Turnover']=fs['TotalRevenue']/fs['avgCurrentAsset']
|
1093
|
+
fs=fs_entry_begin(fs,account_entry='CurrentAssets',suffix='_begin')
|
1094
|
+
fs['Current Asset Turnover']=fs['TotalRevenue']/((fs['CurrentAssets']+fs['CurrentAssets_begin'])/2)
|
1095
|
+
#固定资产周转率:销售收入 / 平均固定资产净额
|
1096
|
+
#fs['Fixed Asset Turnover']=fs['TotalRevenue']/fs['avgPPE']
|
1097
|
+
fs=fs_entry_begin(fs,account_entry='NetPPE',suffix='_begin')
|
1098
|
+
fs['Fixed Asset Turnover']=fs['TotalRevenue']/((fs['NetPPE']+fs['NetPPE_begin'])/2)
|
1099
|
+
#总资产周转率:销售收入 / 平均资产总额
|
1100
|
+
#fs['Total Asset Turnover']=fs['TotalRevenue']/fs['avgTotalAsset']
|
1101
|
+
fs['Total Asset Turnover']=fs['TotalRevenue']/((fs['TotalAssets']+fs['TotalAssets_begin'])/2)
|
1102
|
+
|
1103
|
+
#主营业务利润率=主营业务利润/主营业务收入
|
1104
|
+
fs['Operating Margin']=fs['OperatingIncome']/fs['OperatingRevenue']
|
1105
|
+
|
1106
|
+
#发展潜力指标
|
1107
|
+
#营业收入增长率:本期营业收入增长额 / 上年同期营业收入总额
|
1108
|
+
fs['Revenue Growth']=fs['OperatingRevenue'].pct_change()
|
1109
|
+
#资本积累率:本期所有者权益增长额 / 年初所有者权益
|
1110
|
+
fs['Capital Accumulation']=fs['TotalEquities'].pct_change()
|
1111
|
+
#总资产增长率:本期总资产增长额 / 年初资产总额
|
1112
|
+
fs['Total Asset Growth']=fs['TotalAssets'].pct_change()
|
1113
|
+
#固定资产成新率:平均固定资产净值 / 平均固定资产原值。又称“固定资产净值率”或“有用系数”
|
1114
|
+
#fs['PPE Residual']=fs['avgNetPPE']/fs['avgGrossPPE']
|
1115
|
+
if ('NetPPE' in list(fs)) and ('GrossPPE' in list(fs)):
|
1116
|
+
fs['PPE Residual']=fs['NetPPE']/fs['GrossPPE']
|
1117
|
+
|
1118
|
+
#其他指标
|
1119
|
+
#盈利能力指标
|
1120
|
+
#资产报酬率:净利润 / 期末资产总额,平均总资产计算困难
|
1121
|
+
#fs['Return on Asset']=(fs['NetIncome']+fs['InterestExpense'])/fs['avgTotalAsset']
|
1122
|
+
#fs['Return on Asset']=(fs['NetIncome']+fs['InterestExpense'])/fs['TotalAssets']
|
1123
|
+
#fs=fs_entry_begin(fs,account_entry='TotalAssets',suffix='_begin')
|
1124
|
+
fs['Return on Asset']=(fs['NetIncome'])/((fs['TotalAssets']+fs['TotalAssets_begin'])/2)
|
1125
|
+
fs['ROA']=fs['Return on Asset']
|
1126
|
+
#(投入)资本回报率(Return on Invested Capital,简称ROIC)
|
1127
|
+
#ROIC=NOPLAT(息前税后经营利润)/IC(投入资本)
|
1128
|
+
#NOPLAT=EBIT×(1-T)=(营业利润+财务费用-非经常性投资损益) ×(1-所得税率)
|
1129
|
+
#IC=有息负债+净资产-超额现金-非经营性资产
|
1130
|
+
#fs['Return on Invested Capital']=(fs['OperatingIncome']+fs['InterestExpense'])*(1-fs['TaxRateForCalcs'])/fs['InvestedCapital']
|
1131
|
+
fs=fs_entry_begin(fs,account_entry='InvestedCapital',suffix='_begin')
|
1132
|
+
fs['Return on Invested Capital']=(fs['OperatingIncome'])*(1-fs['TaxRateForCalcs'])/((fs['InvestedCapital']+fs['InvestedCapital_begin'])/2)
|
1133
|
+
#fs['Return on Invested Capital']=fs['Return on Invested Capital']
|
1134
|
+
fs['ROIC']=fs['Return on Invested Capital']
|
1135
|
+
#净资产报酬率:净利润 / 平均净资产
|
1136
|
+
#fs['Return on Net Asset']=fs['NetIncome']/fs['avgTotalEquity']
|
1137
|
+
|
1138
|
+
fs['Return on Net Asset']=fs['NetIncome']/((fs['TotalEquities']+fs['TotalEquities_begin'])/2)
|
1139
|
+
#股东权益报酬率:净利润 / 平均股东权益总额
|
1140
|
+
fs['Return on Equity']=fs['Return on Net Asset']
|
1141
|
+
fs['ROE']=fs['Return on Equity']
|
1142
|
+
#毛利率:销售毛利 / 销售收入净额
|
1143
|
+
fs['Gross Margin']=fs['GrossProfit']/fs['TotalRevenue']
|
1144
|
+
#销售净利率:净利润 / 销售收入净额
|
1145
|
+
fs['Profit Margin']=fs['NetIncome']/fs['TotalRevenue']
|
1146
|
+
#成本费用净利率:净利润 / 成本费用总额
|
1147
|
+
fs['Net Profit on Costs']=fs['NetIncome']/fs['CostOfRevenue']
|
1148
|
+
#股利发放率:每股股利 / 每股利润
|
1149
|
+
fs['Payout Ratio']=fs['CashDividendsPaid']/fs['NetIncome']
|
1150
|
+
|
1151
|
+
###每股指标,受EPS可用性影响
|
1152
|
+
#每股利润:(净利润-优先股股利) / 加权流通在外股数。基本EPS
|
1153
|
+
#注意:流通股股数=期初commonStock-treasuryStock,加本年增加的股数issuanceOfStock*月份占比-本年减少的股数repurchaseOfStock*月份占比
|
1154
|
+
import numpy as np
|
1155
|
+
fs['outstandingStock']=np.floor(fs['NetIncomeCommonStockholders']/fs['BasicEPS'])
|
1156
|
+
#每股现金流量:(经营活动现金净流量-优先股股利) / 流通在外股数
|
1157
|
+
fs['Cashflow per Share']=fs['OperatingCashFlow']/fs['outstandingStock']
|
1158
|
+
fs['CFPS']=fs['Cashflow per Share']
|
1159
|
+
#每股股利:(现金股利总额-优先股股利) /流通在外股数
|
1160
|
+
fs['Dividend per Share']=fs['CashDividendsPaid']/fs['outstandingStock']
|
1161
|
+
fs['DPS']=fs['Dividend per Share']
|
1162
|
+
#每股净资产:股东权益总额 / 流通在外股数
|
1163
|
+
fs['Net Asset per Share']=fs['CommonStockEquity']/fs['outstandingStock']
|
1164
|
+
|
1165
|
+
#市盈率:每股市价 / 每股利润,依赖EPS反推出的流通股数量
|
1166
|
+
#fs=get_PE(fs)
|
1167
|
+
dateymd=lambda x:x.strftime('%Y-%m-%d')
|
1168
|
+
fs['endDate']=fs['asOfDate'].apply(dateymd)
|
1169
|
+
|
1170
|
+
fs['date']=fs['endDate']
|
1171
|
+
fs.set_index('date',inplace=True)
|
1172
|
+
|
1173
|
+
# 删除起初_begin字段
|
1174
|
+
list_begin=[]
|
1175
|
+
for b in list(fs):
|
1176
|
+
if '_begin' in b:
|
1177
|
+
list_begin=list_begin+[b]
|
1178
|
+
fs.drop(list_begin,axis=1,inplace=True)
|
1179
|
+
|
1180
|
+
return fs
|
1181
|
+
|
1182
|
+
|
1183
|
+
if __name__ == '__main__':
|
1184
|
+
fs=calc_fin_rates(fsdf)
|
1185
|
+
|
1186
|
+
#==============================================================================
|
1187
|
+
if __name__ == '__main__':
|
1188
|
+
ticker='AAPL'
|
1189
|
+
ticker='00700.HK'
|
1190
|
+
ticker='601398.SS'
|
1191
|
+
|
1192
|
+
fsr=get_financial_rates(ticker)
|
1193
|
+
|
1194
|
+
def get_financial_rates(ticker):
|
1195
|
+
"""
|
1196
|
+
功能:获得股票的财务报表和财务比率
|
1197
|
+
财务报表:资产负债表,利润表,现金流量表
|
1198
|
+
财务比率:短期还债能力,长期还债能力,营运能力,盈利能力,发展能力
|
1199
|
+
返回:报表+比率
|
1200
|
+
"""
|
1201
|
+
print("\n Analyzing financial rates of",ticker,"......")
|
1202
|
+
|
1203
|
+
# 变换港股代码5位-->4位
|
1204
|
+
result,prefix,suffix=split_prefix_suffix(ticker)
|
1205
|
+
if result & (suffix=='HK'):
|
1206
|
+
if len(prefix)==5:
|
1207
|
+
ticker=ticker[1:]
|
1208
|
+
|
1209
|
+
#抓取股票的财务报表
|
1210
|
+
try:
|
1211
|
+
fsdf=get_financial_statements(ticker)
|
1212
|
+
except:
|
1213
|
+
print(" Failed to get financial statements of",ticker,"\b, recovering")
|
1214
|
+
sleep_random(max_sleep=60)
|
1215
|
+
try:
|
1216
|
+
fsdf=get_financial_statements(ticker)
|
1217
|
+
except:
|
1218
|
+
print(" Failed to get financial statements of",ticker,"\b!")
|
1219
|
+
print(" If the stock code",ticker,"\b is correct, please try a few minutes later.")
|
1220
|
+
return None
|
1221
|
+
|
1222
|
+
#抓取股票的稀释后EPS,计算财务比率
|
1223
|
+
fsr=calc_fin_rates(fsdf)
|
1224
|
+
if fsr is None: return None
|
1225
|
+
"""
|
1226
|
+
try:
|
1227
|
+
fsr=calc_fin_rates(fsdf)
|
1228
|
+
except:
|
1229
|
+
print("......Failed to calculate some financial rates of",ticker,"\b!")
|
1230
|
+
return None
|
1231
|
+
"""
|
1232
|
+
#整理列名:将股票代码、截止日期、报表类型排在开头
|
1233
|
+
cols=list(fsr)
|
1234
|
+
cols.remove('endDate')
|
1235
|
+
cols.remove('ticker')
|
1236
|
+
cols.remove('periodType')
|
1237
|
+
fsr2=fsr[['ticker','endDate','periodType']+cols]
|
1238
|
+
|
1239
|
+
return fsr2
|
1240
|
+
|
1241
|
+
"""
|
1242
|
+
短期偿债能力分析:
|
1243
|
+
1、流动比率,计算公式: 流动资产 / 流动负债
|
1244
|
+
2、速动比率,计算公式: (流动资产-存货) / 流动负债
|
1245
|
+
3、现金比率,计算公式: (现金+现金等价物) / 流动负债
|
1246
|
+
4、现金流量比率,计算公式: 经营活动现金流量 / 流动负债
|
1247
|
+
|
1248
|
+
长期偿债能力分析:
|
1249
|
+
1、资产负债率,计算公式: 负债总额 / 资产总额
|
1250
|
+
2、股东权益比率,计算公式: 股东权益总额 / 资产总额
|
1251
|
+
3、权益乘数,计算公式: 资产总额 / 股东权益总额
|
1252
|
+
4、负债股权比率,计算公式: 负债总额 / 股东权益总额
|
1253
|
+
5、有形净值债务率,计算公式: 负债总额 / (股东权益-无形资产净额)
|
1254
|
+
6、偿债保障比率,计算公式: 负债总额 / 经营活动现金净流量
|
1255
|
+
7、利息保障倍数,计算公式: (税前利润+利息费用)/ 利息费用
|
1256
|
+
|
1257
|
+
营运分析
|
1258
|
+
1、存货周转率,计算公式: 销售成本 / 平均存货
|
1259
|
+
2、应收账款周转率,计算公式: 赊销收入净额 / 平均应收账款余额
|
1260
|
+
3、流动资产周转率,计算公式: 销售收入 / 平均流动资产余额
|
1261
|
+
4、固定资产周转率,计算公式: 销售收入 / 平均固定资产净额
|
1262
|
+
5、总资产周转率,计算公式: 销售收入 / 平均资产总额
|
1263
|
+
|
1264
|
+
盈利分析
|
1265
|
+
1、资产报酬率,计算公式: 利润总额+利息支出 / 平均资产总额
|
1266
|
+
2、净资产报酬率,计算公式: 净利润 / 平均净资产
|
1267
|
+
3、股东权益报酬率,计算公式: 净利润 / 平均股东权益总额
|
1268
|
+
4、毛利率,计算公式: 销售毛利 / 销售收入净额
|
1269
|
+
5、销售净利率,计算公式: 净利润 / 销售收入净额
|
1270
|
+
6、成本费用净利率,计算公式: 净利润 / 成本费用总额
|
1271
|
+
7、每股利润,计算公式: (净利润-优先股股利) / 流通在外股数
|
1272
|
+
8、每股现金流量,计算公式: (经营活动现金净流量-优先股股利) / 流通在外股数
|
1273
|
+
9、每股股利,计算公式: (现金股利总额-优先股股利) /流通在外股数
|
1274
|
+
10、股利发放率,计算公式: 每股股利 / 每股利润
|
1275
|
+
11、每股净资产,计算公式: 股东权益总额 / 流通在外股数
|
1276
|
+
12、市盈率,计算公式: 每股市价 / 每股利润
|
1277
|
+
13、主营业务利润率=主营业务利润/主营业务收入*100%
|
1278
|
+
|
1279
|
+
发展分析
|
1280
|
+
1、营业增长率,计算公式: 本期营业增长额 / 上年同期营业收入总额
|
1281
|
+
2、资本积累率,计算公式: 本期所有者权益增长额 / 年初所有者权益
|
1282
|
+
3、总资产增长率,计算公式: 本期总资产增长额 / 年初资产总额
|
1283
|
+
4、固定资产成新率,计算公式: 平均固定资产净值 / 平均固定资产原值
|
1284
|
+
"""
|
1285
|
+
#==============================================================================
|
1286
|
+
#==============================================================================
|
1287
|
+
#==============================================================================
|
1288
|
+
#####以上的指标为时间序列;以下的指标为非时间序列,类似于快照信息
|
1289
|
+
#==============================================================================
|
1290
|
+
if __name__=='__main__':
|
1291
|
+
symbol='JD'
|
1292
|
+
symbol='AAPL'
|
1293
|
+
symbol='BABA'
|
1294
|
+
symbol='2883.HK'
|
1295
|
+
symbol='EBAY'
|
1296
|
+
|
1297
|
+
stock_info(symbol)
|
1298
|
+
|
1299
|
+
def stock_info(symbol):
|
1300
|
+
"""
|
1301
|
+
功能:返回静态信息
|
1302
|
+
"""
|
1303
|
+
DEBUG=False
|
1304
|
+
if DEBUG:
|
1305
|
+
print(" DEBUG: in stock_info, symbol={}".format(symbol))
|
1306
|
+
|
1307
|
+
from yahooquery import Ticker
|
1308
|
+
stock = Ticker(symbol)
|
1309
|
+
|
1310
|
+
"""
|
1311
|
+
Asset Profile:
|
1312
|
+
Head office address/zip/country, Officers, Employees, industry/sector, phone/fax,
|
1313
|
+
web site,
|
1314
|
+
Risk ranks: auditRisk, boardRisk, compensationRisk, overallRisk, shareHolderRightRisk,
|
1315
|
+
compensationRisk: 薪酬风险。Jensen 及 Meckling (1976)的研究指出薪酬與管理者的風險承擔
|
1316
|
+
具有關連性,站在管理者的立場來看,創新支出的投入使管理者承受更大的薪酬風險(compensation risk),
|
1317
|
+
管理者自然地要求更高的薪酬來補貼所面臨的風險,因此企業創新投資對管理者薪酬成正相關。
|
1318
|
+
boardRisk: 董事会风险
|
1319
|
+
shareHolderRightRisk:股权风险
|
1320
|
+
"""
|
1321
|
+
adict=stock.asset_profile
|
1322
|
+
keylist=list(adict[symbol].keys())
|
1323
|
+
import pandas as pd
|
1324
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1325
|
+
ainfo=aframe.T
|
1326
|
+
info=ainfo.copy()
|
1327
|
+
|
1328
|
+
|
1329
|
+
"""
|
1330
|
+
ESG Scores: Risk measurements
|
1331
|
+
peerGroup, ratingYear,
|
1332
|
+
environmentScore, governanceScore, socialScore, totalEsg
|
1333
|
+
dict: peerEnvironmentPerformance, peerGovernancePerformance, peerSocialPerformance,
|
1334
|
+
peerEsgScorePerformance
|
1335
|
+
"""
|
1336
|
+
adict=stock.esg_scores
|
1337
|
+
try: #一些企业无此信息
|
1338
|
+
keylist=list(adict[symbol].keys())
|
1339
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1340
|
+
ainfo=aframe.T
|
1341
|
+
info=pd.concat([info,ainfo])
|
1342
|
+
except:
|
1343
|
+
pass
|
1344
|
+
|
1345
|
+
"""
|
1346
|
+
Financial Data: TTM???
|
1347
|
+
currentPrice, targetHighPrice, targetLowPrice, targetMeanPrice, targetMedianPrice,
|
1348
|
+
currentRatio, debtToEquity, earningsGrowth, ebitda, ebitdaMargins, financialCurrency,
|
1349
|
+
freeCashflow, grossMargins, grossProfits,
|
1350
|
+
operatingCashflow, operatingMargins, profitMargins,
|
1351
|
+
quickRatio, returnOnAssets, returnOnEquity, revenueGrowth, revenuePerShare,
|
1352
|
+
totalCash, totalCashPerShare, totalDebt, totalRevenue,
|
1353
|
+
"""
|
1354
|
+
adict=stock.financial_data
|
1355
|
+
keylist=list(adict[symbol].keys())
|
1356
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1357
|
+
ainfo=aframe.T
|
1358
|
+
info=pd.concat([info,ainfo])
|
1359
|
+
|
1360
|
+
|
1361
|
+
"""
|
1362
|
+
Key Statistics: TTM???
|
1363
|
+
52WeekChang, SandP52WeekChang, beta, floatShares, sharesOutstanding,
|
1364
|
+
bookValue, earningsQuarterlyGrowth, enterpriseToEbitda, enterpriseToRevenue,
|
1365
|
+
enterpriseValue, netIncomeToCommon, priceToBook, profitMargins,
|
1366
|
+
forwardEps, trailingEps,
|
1367
|
+
heldPercentInsiders, heldPercentInstitutions,
|
1368
|
+
lastFiscalYearEnd, lastSplitDate, lastSplitFactor, mostRecentQuarter, nextFiscalYearEnd,
|
1369
|
+
"""
|
1370
|
+
adict=stock.key_stats
|
1371
|
+
keylist=list(adict[symbol].keys())
|
1372
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1373
|
+
ainfo=aframe.T
|
1374
|
+
info=pd.concat([info,ainfo])
|
1375
|
+
|
1376
|
+
|
1377
|
+
"""
|
1378
|
+
Price Information:
|
1379
|
+
currency, currencySymbol, exchange, exchangeName, shortName,
|
1380
|
+
longName,
|
1381
|
+
marketCap, marketState, quoteType,
|
1382
|
+
regularMarketChange, regularMarketChangPercent, regularMarketHigh, regularMarketLow,
|
1383
|
+
regularMarketOpen, regularMarketPreviousClose, regularMarketPrice, regularMarketTime,
|
1384
|
+
regularMarketVolume,
|
1385
|
+
"""
|
1386
|
+
adict=stock.price
|
1387
|
+
keylist=list(adict[symbol].keys())
|
1388
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1389
|
+
ainfo=aframe.T
|
1390
|
+
info=pd.concat([info,ainfo])
|
1391
|
+
|
1392
|
+
|
1393
|
+
"""
|
1394
|
+
Quote Type:
|
1395
|
+
exchange, firstTradeDateEpocUtc(上市日期), longName, quoteType(证券类型:股票),
|
1396
|
+
shortName, symbol(当前代码), timeZoneFullName, timeZoneShortName, underlyingSymbol(原始代码),
|
1397
|
+
"""
|
1398
|
+
adict=stock.quote_type
|
1399
|
+
keylist=list(adict[symbol].keys())
|
1400
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1401
|
+
ainfo=aframe.T
|
1402
|
+
info=pd.concat([info,ainfo])
|
1403
|
+
|
1404
|
+
|
1405
|
+
"""
|
1406
|
+
Share Purchase Activity
|
1407
|
+
period(6m), totalInsiderShares
|
1408
|
+
"""
|
1409
|
+
adict=stock.share_purchase_activity
|
1410
|
+
keylist=list(adict[symbol].keys())
|
1411
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1412
|
+
ainfo=aframe.T
|
1413
|
+
info=pd.concat([info,ainfo])
|
1414
|
+
|
1415
|
+
|
1416
|
+
"""
|
1417
|
+
# Summary detail
|
1418
|
+
averageDailyVolume10Day, averageVolume, averageVolume10days, beta, currency,
|
1419
|
+
dayHigh, dayLow, fiftyDayAverage, fiftyTwoWeekHigh, fiftyTwoWeekLow, open, previousClose,
|
1420
|
+
regularMarketDayHigh, regularMarketDayLow, regularMarketOpen, regularMarketPreviousClose,
|
1421
|
+
regularMarketVolume, twoHundredDayAverage, volume,
|
1422
|
+
forwardPE, marketCap, priceToSalesTrailing12Months,
|
1423
|
+
dividendRate, dividendYield, exDividendDate, payoutRatio, trailingAnnualDividendRate,
|
1424
|
+
trailingAnnualDividendYield, trailingPE,
|
1425
|
+
"""
|
1426
|
+
adict=stock.summary_detail
|
1427
|
+
keylist=list(adict[symbol].keys())
|
1428
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1429
|
+
ainfo=aframe.T
|
1430
|
+
info=pd.concat([info,ainfo])
|
1431
|
+
|
1432
|
+
|
1433
|
+
"""
|
1434
|
+
summary_profile
|
1435
|
+
address/city/country/zip, phone/fax, sector/industry, website/longBusinessSummary,
|
1436
|
+
fullTimeEmployees,
|
1437
|
+
"""
|
1438
|
+
adict=stock.summary_profile
|
1439
|
+
keylist=list(adict[symbol].keys())
|
1440
|
+
aframe=pd.DataFrame.from_dict(adict, orient='index', columns=keylist)
|
1441
|
+
ainfo=aframe.T
|
1442
|
+
info=pd.concat([info,ainfo])
|
1443
|
+
|
1444
|
+
# 清洗数据项目
|
1445
|
+
info.sort_index(inplace=True) #排序
|
1446
|
+
info.dropna(inplace=True) #去掉空值
|
1447
|
+
#去重
|
1448
|
+
info['Item']=info.index
|
1449
|
+
info.drop_duplicates(subset=['Item'],keep='last',inplace=True)
|
1450
|
+
|
1451
|
+
#删除不需要的项目
|
1452
|
+
delrows=['adult','alcoholic','animalTesting','ask','askSize','bid','bidSize', \
|
1453
|
+
'catholic','coal','controversialWeapons','furLeather','gambling', \
|
1454
|
+
'gmo','gmtOffSetMilliseconds','militaryContract','messageBoardId', \
|
1455
|
+
'nuclear','palmOil','pesticides','tobacco','uuid','maxAge']
|
1456
|
+
for r in delrows:
|
1457
|
+
info.drop(info[info['Item']==r].index,inplace=True)
|
1458
|
+
|
1459
|
+
#修改列名
|
1460
|
+
info.rename(columns={symbol:'Value'}, inplace=True)
|
1461
|
+
del info['Item']
|
1462
|
+
|
1463
|
+
infot=info.T
|
1464
|
+
#有的股票信息中缺失returnOnAssets或payoutRatio
|
1465
|
+
try:
|
1466
|
+
#增加IGR
|
1467
|
+
infot['IGR']=infot['returnOnAssets']*(1-infot['payoutRatio'])/(1-infot['returnOnAssets']*(1-infot['payoutRatio']))
|
1468
|
+
except:
|
1469
|
+
pass
|
1470
|
+
|
1471
|
+
#增加SGR
|
1472
|
+
try:
|
1473
|
+
infot['SGR']=infot['returnOnEquity']*(1-infot['payoutRatio'])/(1-infot['returnOnEquity']*(1-infot['payoutRatio']))
|
1474
|
+
except:
|
1475
|
+
pass
|
1476
|
+
|
1477
|
+
infott=infot.T
|
1478
|
+
|
1479
|
+
return infott
|
1480
|
+
|
1481
|
+
|
1482
|
+
if __name__=='__main__':
|
1483
|
+
info=stock_info('AAPL')
|
1484
|
+
info=stock_info('BABA')
|
1485
|
+
|
1486
|
+
#==============================================================================
|
1487
|
+
if __name__=='__main__':
|
1488
|
+
info=stock_info('AAPL')
|
1489
|
+
|
1490
|
+
def stock_basic(info):
|
1491
|
+
|
1492
|
+
wishlist=['symbol','shortName','sector','industry', \
|
1493
|
+
|
1494
|
+
#公司名称,业务
|
1495
|
+
'underlyingSymbol','longName', \
|
1496
|
+
|
1497
|
+
#公司地址,网站
|
1498
|
+
'address1','address2','city','state','country','zip','phone','fax', \
|
1499
|
+
'timeZoneShortName','timeZoneFullName','website', \
|
1500
|
+
|
1501
|
+
#员工人数
|
1502
|
+
'fullTimeEmployees', \
|
1503
|
+
|
1504
|
+
#上市与交易所
|
1505
|
+
'exchange','exchangeName','quoteType', \
|
1506
|
+
|
1507
|
+
#其他
|
1508
|
+
'beta','currency','currentPrice','marketCap','trailingPE', \
|
1509
|
+
|
1510
|
+
'ratingYear','ratingMonth']
|
1511
|
+
|
1512
|
+
#按照wishlist的顺序从info中取值
|
1513
|
+
rowlist=list(info.index)
|
1514
|
+
import pandas as pd
|
1515
|
+
info_sub=pd.DataFrame(columns=['Item','Value'])
|
1516
|
+
infot=info.T
|
1517
|
+
for w in wishlist:
|
1518
|
+
if w in rowlist:
|
1519
|
+
v=infot[w][0]
|
1520
|
+
s=pd.Series({'Item':w,'Value':v})
|
1521
|
+
try:
|
1522
|
+
info_sub=info_sub.append(s,ignore_index=True)
|
1523
|
+
except:
|
1524
|
+
info_sub=info_sub._append(s,ignore_index=True)
|
1525
|
+
|
1526
|
+
return info_sub
|
1527
|
+
|
1528
|
+
if __name__=='__main__':
|
1529
|
+
basic=stock_basic(info)
|
1530
|
+
|
1531
|
+
#==============================================================================
|
1532
|
+
if __name__=='__main__':
|
1533
|
+
info=stock_info('AAPL')
|
1534
|
+
|
1535
|
+
def stock_officers(info):
|
1536
|
+
|
1537
|
+
wishlist=['symbol','shortName','sector','industry', \
|
1538
|
+
|
1539
|
+
#公司高管
|
1540
|
+
'currency','companyOfficers', \
|
1541
|
+
|
1542
|
+
]
|
1543
|
+
|
1544
|
+
#按照wishlist的顺序从info中取值
|
1545
|
+
rowlist=list(info.index)
|
1546
|
+
import pandas as pd
|
1547
|
+
info_sub=pd.DataFrame(columns=['Item','Value'])
|
1548
|
+
infot=info.T
|
1549
|
+
for w in wishlist:
|
1550
|
+
if w in rowlist:
|
1551
|
+
v=infot[w][0]
|
1552
|
+
s=pd.Series({'Item':w,'Value':v})
|
1553
|
+
try:
|
1554
|
+
info_sub=info_sub.append(s,ignore_index=True)
|
1555
|
+
except:
|
1556
|
+
info_sub=info_sub._append(s,ignore_index=True)
|
1557
|
+
|
1558
|
+
return info_sub
|
1559
|
+
|
1560
|
+
if __name__=='__main__':
|
1561
|
+
sub_info=stock_officers(info)
|
1562
|
+
|
1563
|
+
#==============================================================================
|
1564
|
+
def stock_risk_general(info):
|
1565
|
+
|
1566
|
+
wishlist=['symbol','shortName','sector','industry', \
|
1567
|
+
|
1568
|
+
'overallRisk','boardRisk','compensationRisk', \
|
1569
|
+
'shareHolderRightsRisk','auditRisk', \
|
1570
|
+
|
1571
|
+
'ratingYear','ratingMonth']
|
1572
|
+
|
1573
|
+
#按照wishlist的顺序从info中取值
|
1574
|
+
rowlist=list(info.index)
|
1575
|
+
import pandas as pd
|
1576
|
+
info_sub=pd.DataFrame(columns=['Item','Value'])
|
1577
|
+
infot=info.T
|
1578
|
+
for w in wishlist:
|
1579
|
+
if w in rowlist:
|
1580
|
+
v=infot[w][0]
|
1581
|
+
s=pd.Series({'Item':w,'Value':v})
|
1582
|
+
try:
|
1583
|
+
info_sub=info_sub.append(s,ignore_index=True)
|
1584
|
+
except:
|
1585
|
+
info_sub=info_sub._append(s,ignore_index=True)
|
1586
|
+
|
1587
|
+
return info_sub
|
1588
|
+
|
1589
|
+
if __name__=='__main__':
|
1590
|
+
risk_general=stock_risk_general(info)
|
1591
|
+
|
1592
|
+
#==============================================================================
|
1593
|
+
def stock_risk_esg(info):
|
1594
|
+
|
1595
|
+
wishlist=['symbol','shortName','sector','industry', \
|
1596
|
+
|
1597
|
+
'totalEsg','esgPerformance','peerEsgScorePerformance', \
|
1598
|
+
'environmentScore','peerEnvironmentPerformance', \
|
1599
|
+
'socialScore','peerSocialPerformance', \
|
1600
|
+
'governanceScore','peerGovernancePerformance', \
|
1601
|
+
'peerGroup','relatedControversy','peerCount','percentile', \
|
1602
|
+
|
1603
|
+
'ratingYear','ratingMonth']
|
1604
|
+
|
1605
|
+
#按照wishlist的顺序从info中取值
|
1606
|
+
rowlist=list(info.index)
|
1607
|
+
import pandas as pd
|
1608
|
+
info_sub=pd.DataFrame(columns=['Item','Value'])
|
1609
|
+
infot=info.T
|
1610
|
+
for w in wishlist:
|
1611
|
+
if w in rowlist:
|
1612
|
+
v=infot[w][0]
|
1613
|
+
s=pd.Series({'Item':w,'Value':v})
|
1614
|
+
try:
|
1615
|
+
info_sub=info_sub.append(s,ignore_index=True)
|
1616
|
+
except:
|
1617
|
+
info_sub=info_sub._append(s,ignore_index=True)
|
1618
|
+
|
1619
|
+
return info_sub
|
1620
|
+
|
1621
|
+
if __name__=='__main__':
|
1622
|
+
risk_esg=stock_risk_esg(info)
|
1623
|
+
|
1624
|
+
#==============================================================================
|
1625
|
+
def stock_fin_rates(info):
|
1626
|
+
|
1627
|
+
wishlist=['symbol','shortName','sector','industry', \
|
1628
|
+
|
1629
|
+
'financialCurrency', \
|
1630
|
+
|
1631
|
+
#偿债能力
|
1632
|
+
'currentRatio','quickRatio','debtToEquity', \
|
1633
|
+
|
1634
|
+
#盈利能力
|
1635
|
+
'ebitdaMargins','operatingMargins','grossMargins','profitMargins', \
|
1636
|
+
|
1637
|
+
#股东回报率
|
1638
|
+
'returnOnAssets','returnOnEquity', \
|
1639
|
+
'dividendRate','trailingAnnualDividendRate','trailingEps', \
|
1640
|
+
'payoutRatio','revenuePerShare','totalCashPerShare', \
|
1641
|
+
|
1642
|
+
#业务发展能力
|
1643
|
+
'revenueGrowth','earningsGrowth','earningsQuarterlyGrowth', \
|
1644
|
+
'enterpriseToRevenue','enterpriseToEbitda', \
|
1645
|
+
|
1646
|
+
'ratingYear','ratingMonth']
|
1647
|
+
|
1648
|
+
#按照wishlist的顺序从info中取值
|
1649
|
+
rowlist=list(info.index)
|
1650
|
+
import pandas as pd
|
1651
|
+
info_sub=pd.DataFrame(columns=['Item','Value'])
|
1652
|
+
infot=info.T
|
1653
|
+
for w in wishlist:
|
1654
|
+
if w in rowlist:
|
1655
|
+
v=infot[w][0]
|
1656
|
+
s=pd.Series({'Item':w,'Value':v})
|
1657
|
+
try:
|
1658
|
+
info_sub=info_sub.append(s,ignore_index=True)
|
1659
|
+
except:
|
1660
|
+
info_sub=info_sub._append(s,ignore_index=True)
|
1661
|
+
|
1662
|
+
return info_sub
|
1663
|
+
|
1664
|
+
if __name__=='__main__':
|
1665
|
+
fin_rates=stock_fin_rates(info)
|
1666
|
+
|
1667
|
+
#==============================================================================
|
1668
|
+
def stock_fin_statements(info):
|
1669
|
+
|
1670
|
+
wishlist=['symbol','shortName','sector','industry', \
|
1671
|
+
|
1672
|
+
'financialCurrency','lastFiscalYearEnd','mostRecentQuarter','nextFiscalYearEnd', \
|
1673
|
+
|
1674
|
+
#资产负债
|
1675
|
+
'enterpriseValue','totalDebt','marketCap', \
|
1676
|
+
|
1677
|
+
#利润表
|
1678
|
+
'totalRevenue','grossProfits','ebitda','netIncomeToCommon', \
|
1679
|
+
|
1680
|
+
#现金流量
|
1681
|
+
'operatingCashflow','freeCashflow','totalCash', \
|
1682
|
+
|
1683
|
+
#股票数量
|
1684
|
+
'sharesOutstanding','floatShares','totalInsiderShares', \
|
1685
|
+
|
1686
|
+
'ratingYear','ratingMonth']
|
1687
|
+
|
1688
|
+
#按照wishlist的顺序从info中取值
|
1689
|
+
rowlist=list(info.index)
|
1690
|
+
import pandas as pd
|
1691
|
+
info_sub=pd.DataFrame(columns=['Item','Value'])
|
1692
|
+
infot=info.T
|
1693
|
+
for w in wishlist:
|
1694
|
+
if w in rowlist:
|
1695
|
+
v=infot[w][0]
|
1696
|
+
s=pd.Series({'Item':w,'Value':v})
|
1697
|
+
try:
|
1698
|
+
info_sub=info_sub.append(s,ignore_index=True)
|
1699
|
+
except:
|
1700
|
+
info_sub=info_sub._append(s,ignore_index=True)
|
1701
|
+
|
1702
|
+
return info_sub
|
1703
|
+
|
1704
|
+
if __name__=='__main__':
|
1705
|
+
fin_statements=stock_fin_statements(info)
|
1706
|
+
|
1707
|
+
#==============================================================================
|
1708
|
+
def stock_market_rates(info):
|
1709
|
+
|
1710
|
+
wishlist=['symbol','shortName','sector','industry', \
|
1711
|
+
|
1712
|
+
'currency','currencySymbol', \
|
1713
|
+
|
1714
|
+
#市场观察
|
1715
|
+
'priceToBook','priceToSalesTrailing12Months','recommendationKey', \
|
1716
|
+
|
1717
|
+
#市场风险与收益
|
1718
|
+
'beta','52WeekChange','SandP52WeekChange', \
|
1719
|
+
'trailingEps','forwardEps','trailingPE','forwardPE','pegRatio', \
|
1720
|
+
|
1721
|
+
#分红
|
1722
|
+
'dividendYield','fiveYearAvgDividendYield','trailingAnnualDividendYield', \
|
1723
|
+
|
1724
|
+
#持股
|
1725
|
+
'heldPercentInsiders','heldPercentInstitutions', \
|
1726
|
+
|
1727
|
+
#股票流通
|
1728
|
+
'sharesOutstanding','totalInsiderShares','floatShares', \
|
1729
|
+
'sharesPercentSharesOut','shortPercentOfFloat','shortRatio', \
|
1730
|
+
|
1731
|
+
'ratingYear','ratingMonth']
|
1732
|
+
|
1733
|
+
#按照wishlist的顺序从info中取值
|
1734
|
+
rowlist=list(info.index)
|
1735
|
+
import pandas as pd
|
1736
|
+
info_sub=pd.DataFrame(columns=['Item','Value'])
|
1737
|
+
infot=info.T
|
1738
|
+
for w in wishlist:
|
1739
|
+
if w in rowlist:
|
1740
|
+
v=infot[w][0]
|
1741
|
+
s=pd.Series({'Item':w,'Value':v})
|
1742
|
+
try:
|
1743
|
+
info_sub=info_sub.append(s,ignore_index=True)
|
1744
|
+
except:
|
1745
|
+
info_sub=info_sub._append(s,ignore_index=True)
|
1746
|
+
|
1747
|
+
return info_sub
|
1748
|
+
|
1749
|
+
if __name__=='__main__':
|
1750
|
+
market_rates=stock_market_rates(info)
|
1751
|
+
|
1752
|
+
#==============================================================================
|
1753
|
+
if __name__=='__main__':
|
1754
|
+
ticker='01810.HK'
|
1755
|
+
ticker='AAPL'
|
1756
|
+
info_type='fin_rates'
|
1757
|
+
|
1758
|
+
def get_stock_profile(ticker,info_type='basic',graph=True):
|
1759
|
+
"""
|
1760
|
+
功能:抓取和获得股票的信息
|
1761
|
+
basic: 基本信息
|
1762
|
+
fin_rates: 财务比率快照
|
1763
|
+
fin_statements: 财务报表快照
|
1764
|
+
market_rates: 市场比率快照
|
1765
|
+
risk_general: 一般风险快照
|
1766
|
+
risk_esg: 可持续发展风险快照(有些股票无此信息)
|
1767
|
+
"""
|
1768
|
+
#print("\nSearching for snapshot info of",ticker,"\b, please wait...")
|
1769
|
+
|
1770
|
+
typelist=['basic','officers','fin_rates','fin_statements','market_rates','risk_general','risk_esg','all']
|
1771
|
+
if info_type not in typelist:
|
1772
|
+
print(" #Sorry, info_type not supported for",info_type)
|
1773
|
+
print(" Supported info_type:\n",typelist)
|
1774
|
+
return None
|
1775
|
+
|
1776
|
+
#改变港股代码,去掉前导的0或8
|
1777
|
+
if '.HK' in ticker.upper():
|
1778
|
+
ticker=ticker[-7:]
|
1779
|
+
|
1780
|
+
info=stock_info(ticker)
|
1781
|
+
if not graph: return info
|
1782
|
+
|
1783
|
+
name=info.T['shortName'][0]
|
1784
|
+
if info_type in ['basic','all']:
|
1785
|
+
sub_info=stock_basic(info)
|
1786
|
+
titletxt="***** "+name+": Basic Information *****"
|
1787
|
+
printdf(sub_info,titletxt)
|
1788
|
+
|
1789
|
+
if info_type in ['officers','all']:
|
1790
|
+
sub_info=stock_officers(info)
|
1791
|
+
titletxt="***** "+name+": Company Senior Management *****"
|
1792
|
+
printdf(sub_info,titletxt)
|
1793
|
+
|
1794
|
+
if info_type in ['fin_rates','all']:
|
1795
|
+
sub_info=stock_fin_rates(info)
|
1796
|
+
titletxt="***** "+name+": Fundamental Rates *****"
|
1797
|
+
printdf(sub_info,titletxt)
|
1798
|
+
|
1799
|
+
if info_type in ['fin_statements','all']:
|
1800
|
+
sub_info=stock_fin_statements(info)
|
1801
|
+
titletxt="***** "+name+": Financial Statements *****"
|
1802
|
+
printdf(sub_info,titletxt)
|
1803
|
+
|
1804
|
+
if info_type in ['market_rates','all']:
|
1805
|
+
sub_info=stock_market_rates(info)
|
1806
|
+
titletxt="***** "+name+": Market Rates *****"
|
1807
|
+
printdf(sub_info,titletxt)
|
1808
|
+
|
1809
|
+
if info_type in ['risk_general','all']:
|
1810
|
+
sub_info=stock_risk_general(info)
|
1811
|
+
titletxt="***** "+name+": Risk General *****"+ \
|
1812
|
+
"\n(Bigger number means higher risk)"
|
1813
|
+
printdf(sub_info,titletxt)
|
1814
|
+
|
1815
|
+
if info_type in ['risk_esg','all']:
|
1816
|
+
sub_info=stock_risk_esg(info)
|
1817
|
+
if len(sub_info)==0:
|
1818
|
+
print("#Error(get_stock_profile): esg info not available for",ticker)
|
1819
|
+
else:
|
1820
|
+
titletxt="***** "+name+": Sustainability Risk *****"+ \
|
1821
|
+
"\n(Smaller number means less risky)"
|
1822
|
+
printdf(sub_info,titletxt)
|
1823
|
+
|
1824
|
+
return info
|
1825
|
+
|
1826
|
+
if __name__=='__main__':
|
1827
|
+
info=get_stock_profile(ticker,info_type='basic')
|
1828
|
+
info=get_stock_profile(ticker,info_type='officers')
|
1829
|
+
info=get_stock_profile(ticker,info_type='fin_rates')
|
1830
|
+
info=get_stock_profile(ticker,info_type='fin_statements')
|
1831
|
+
info=get_stock_profile(ticker,info_type='market_rates')
|
1832
|
+
info=get_stock_profile(ticker,info_type='risk_general')
|
1833
|
+
info=get_stock_profile(ticker,info_type='risk_esg')
|
1834
|
+
|
1835
|
+
#==============================================================================
|
1836
|
+
if __name__=='__main__':
|
1837
|
+
ticker='AAPL'
|
1838
|
+
info=stock_info(ticker)
|
1839
|
+
sub_info=stock_officers(info)
|
1840
|
+
titletxt="***** "+ticker+": Snr Management *****"
|
1841
|
+
|
1842
|
+
def printdf(sub_info,titletxt):
|
1843
|
+
"""
|
1844
|
+
功能:整齐显示股票信息快照
|
1845
|
+
"""
|
1846
|
+
print("\n"+titletxt)
|
1847
|
+
|
1848
|
+
maxlen=0
|
1849
|
+
for index,row in sub_info.iterrows():
|
1850
|
+
if len(row['Item']) > maxlen: maxlen=len(row['Item'])
|
1851
|
+
|
1852
|
+
for index,row in sub_info.iterrows():
|
1853
|
+
|
1854
|
+
#特殊打印:高管信息
|
1855
|
+
if row['Item']=="companyOfficers":
|
1856
|
+
print_companyOfficers(sub_info)
|
1857
|
+
continue
|
1858
|
+
|
1859
|
+
#特殊打印:ESG同行状况
|
1860
|
+
peerlist=["peerEsgScorePerformance","peerEnvironmentPerformance", \
|
1861
|
+
"peerSocialPerformance","peerGovernancePerformance"]
|
1862
|
+
if row['Item'] in peerlist:
|
1863
|
+
print_peerPerformance(sub_info,row['Item'])
|
1864
|
+
continue
|
1865
|
+
|
1866
|
+
#特殊打印:ESG Social风险内容
|
1867
|
+
if row['Item']=="relatedControversy":
|
1868
|
+
print_controversy(sub_info,row['Item'])
|
1869
|
+
continue
|
1870
|
+
|
1871
|
+
thislen=maxlen-len(row['Item'])
|
1872
|
+
print(row['Item'],'.'*thislen,'\b:',row['Value'])
|
1873
|
+
|
1874
|
+
import datetime
|
1875
|
+
today=datetime.date.today()
|
1876
|
+
print("*** Source: Yahoo Finance,",today)
|
1877
|
+
|
1878
|
+
return
|
1879
|
+
|
1880
|
+
if __name__=='__main__':
|
1881
|
+
printdf(sub_info,titletxt)
|
1882
|
+
|
1883
|
+
#==============================================================================
|
1884
|
+
if __name__=='__main__':
|
1885
|
+
info=stock_info('AAPL')
|
1886
|
+
sub_info=stock_officers(info)
|
1887
|
+
|
1888
|
+
def print_companyOfficers(sub_info):
|
1889
|
+
"""
|
1890
|
+
功能:打印公司高管信息
|
1891
|
+
"""
|
1892
|
+
item='companyOfficers'
|
1893
|
+
itemtxt='Company officers:'
|
1894
|
+
key1='name'
|
1895
|
+
key2='title'
|
1896
|
+
key3='yearBorn'
|
1897
|
+
key4='age'
|
1898
|
+
key6='totalPay'
|
1899
|
+
key7='fiscalYear'
|
1900
|
+
currency=list(sub_info[sub_info['Item'] == 'currency']['Value'])[0]
|
1901
|
+
alist=list(sub_info[sub_info['Item'] == item]['Value'])[0]
|
1902
|
+
|
1903
|
+
print(itemtxt)
|
1904
|
+
for i in alist:
|
1905
|
+
print(' '*4,i[key1])
|
1906
|
+
print(' '*8,i[key2],'\b,',i[key4],'years old (born',i[key3],'\b)')
|
1907
|
+
print(' '*8,'Total paid',currency+str(format(i[key6],',')),'@'+str(i[key7]))
|
1908
|
+
|
1909
|
+
return
|
1910
|
+
|
1911
|
+
if __name__=='__main__':
|
1912
|
+
print_companyOfficers(sub_info)
|
1913
|
+
|
1914
|
+
#==============================================================================
|
1915
|
+
if __name__=='__main__':
|
1916
|
+
info=stock_info('AAPL')
|
1917
|
+
sub_info=stock_risk_esg(info)
|
1918
|
+
item="peerEsgScorePerformance"
|
1919
|
+
|
1920
|
+
def print_peerPerformance(sub_info,item):
|
1921
|
+
"""
|
1922
|
+
功能:打印ESG信息
|
1923
|
+
"""
|
1924
|
+
maxlen=0
|
1925
|
+
for index,row in sub_info.iterrows():
|
1926
|
+
if len(row['Item']) > maxlen: maxlen=len(row['Item'])
|
1927
|
+
thislen=maxlen-len(item)+2
|
1928
|
+
itemtxt=item+'.'*thislen+'\b:'
|
1929
|
+
|
1930
|
+
key1='min'
|
1931
|
+
key2='avg'
|
1932
|
+
key3='max'
|
1933
|
+
i=list(sub_info[sub_info['Item'] == item]['Value'])[0]
|
1934
|
+
|
1935
|
+
print(itemtxt)
|
1936
|
+
print(' '*4,key1+':',i[key1],'\b,',key2+':',round(i[key2],2),'\b,',key3+':',i[key3])
|
1937
|
+
|
1938
|
+
return
|
1939
|
+
|
1940
|
+
if __name__=='__main__':
|
1941
|
+
print_peerPerformance(sub_info,item)
|
1942
|
+
|
1943
|
+
#==============================================================================
|
1944
|
+
if __name__=='__main__':
|
1945
|
+
info=stock_info('AAPL')
|
1946
|
+
sub_info=stock_risk_esg(info)
|
1947
|
+
item='relatedControversy'
|
1948
|
+
|
1949
|
+
def print_controversy(sub_info,item):
|
1950
|
+
"""
|
1951
|
+
功能:打印ESG Social风险内容
|
1952
|
+
"""
|
1953
|
+
maxlen=0
|
1954
|
+
for index,row in sub_info.iterrows():
|
1955
|
+
if len(row['Item']) > maxlen: maxlen=len(row['Item'])
|
1956
|
+
thislen=maxlen-len(item)+2
|
1957
|
+
itemtxt=item+'.'*thislen+'\b:'
|
1958
|
+
|
1959
|
+
alist=list(sub_info[sub_info['Item'] == item]['Value'])[0]
|
1960
|
+
|
1961
|
+
print(itemtxt)
|
1962
|
+
for i in alist:
|
1963
|
+
print(' '*4,i)
|
1964
|
+
|
1965
|
+
return
|
1966
|
+
|
1967
|
+
if __name__=='__main__':
|
1968
|
+
print_controversy(sub_info,item)
|
1969
|
+
|
1970
|
+
#==============================================================================
|
1971
|
+
def calc_dupont(ticker):
|
1972
|
+
"""
|
1973
|
+
功能:计算股票ticker的杜邦分析项目
|
1974
|
+
"""
|
1975
|
+
fsr2=get_financial_rates(ticker)
|
1976
|
+
if fsr2 is None:
|
1977
|
+
print(" #Error(calc_dupont): failed to retrieved info for",ticker)
|
1978
|
+
return None
|
1979
|
+
|
1980
|
+
dpidf=fsr2[['ticker','endDate','periodType','ROE','Profit Margin','Total Asset Turnover','Equity Multiplier']]
|
1981
|
+
dpidf['pROE']=dpidf['Profit Margin']*dpidf['Total Asset Turnover']*dpidf['Equity Multiplier']
|
1982
|
+
|
1983
|
+
return dpidf
|
1984
|
+
#==============================================================================
|
1985
|
+
if __name__=='__main__':
|
1986
|
+
tickerlist=['AAPL','MSFT','FB']
|
1987
|
+
fsdate='latest'
|
1988
|
+
scale1 = 10
|
1989
|
+
scale2 = 10
|
1990
|
+
hatchlist=['.', 'o', '\\']
|
1991
|
+
|
1992
|
+
def compare_dupont(tickerlist,fsdate='latest', \
|
1993
|
+
sort='PM',facecolor='whitesmoke',font_size='16px', \
|
1994
|
+
loc1='best',retry=10, \
|
1995
|
+
scale1 = 10,scale2 = 10,hatchlist=['.', 'o', '\\']):
|
1996
|
+
"""
|
1997
|
+
功能:获得tickerlist中每只股票的杜邦分析项目,绘制柱状叠加比较图
|
1998
|
+
tickerlist:股票代码列表,建议在10只以内
|
1999
|
+
fsdate:财报日期,默认为最新一期季报/年报,或具体日期,格式:YYYY-MM-DD
|
2000
|
+
scale1:用于放大销售净利率,避免与权益乘数数量级不一致导致绘图难看问题,可自行调整
|
2001
|
+
scale2:用于放大总资产周转率,避免与权益乘数数量级不一致导致绘图难看问题,可自行调整
|
2002
|
+
hatchlist:绘制柱状图的纹理,用于黑白打印时区分,可自定义,
|
2003
|
+
可用的符号:'-', '+', 'x', '\\', '*', 'o', 'O', '.'
|
2004
|
+
"""
|
2005
|
+
import pandas as pd
|
2006
|
+
|
2007
|
+
lang=check_language()
|
2008
|
+
"""
|
2009
|
+
ticker ='Ticker'
|
2010
|
+
name1 = 'Profit Margin'
|
2011
|
+
name2 = 'Asset Turnover'
|
2012
|
+
name3 = 'Equity Multiplier'
|
2013
|
+
name4 = 'ROE'
|
2014
|
+
name5 = 'Report Date'
|
2015
|
+
name6 = 'Report Type'
|
2016
|
+
"""
|
2017
|
+
ticker = '公司'
|
2018
|
+
name1 = '销售净利率'
|
2019
|
+
name2 = '总资产周转率'
|
2020
|
+
name3 = '权益乘数'
|
2021
|
+
name4 = '净资产收益率'
|
2022
|
+
name5 = '财报日期'
|
2023
|
+
name6 = '财报类型'
|
2024
|
+
|
2025
|
+
import os, sys
|
2026
|
+
class HiddenPrints:
|
2027
|
+
def __enter__(self):
|
2028
|
+
self._original_stdout = sys.stdout
|
2029
|
+
sys.stdout = open(os.devnull, 'w')
|
2030
|
+
|
2031
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
2032
|
+
sys.stdout.close()
|
2033
|
+
sys.stdout = self._original_stdout
|
2034
|
+
|
2035
|
+
dpidflist,dpilist,fsdatelist,fstypelist=[],[],[],[]
|
2036
|
+
name1list,name2list,name3list,name4list,name5list,name6list=[],[],[],[],[],[]
|
2037
|
+
newtickerlist=[]
|
2038
|
+
print("Working on DuPont factsheet, it takes a very long time, take a breather ...")
|
2039
|
+
|
2040
|
+
#第1次尝试
|
2041
|
+
faillist=[]
|
2042
|
+
for t in tickerlist:
|
2043
|
+
try:
|
2044
|
+
with HiddenPrints():
|
2045
|
+
dpidf=calc_dupont(t)
|
2046
|
+
except:
|
2047
|
+
print(" #Warning(compare_dupont): found errors in accounting items, ignore",t)
|
2048
|
+
continue
|
2049
|
+
|
2050
|
+
#未出错,但未抓取到数据,再试
|
2051
|
+
if dpidf is None:
|
2052
|
+
faillist=faillist+[t]
|
2053
|
+
#sleep_random(max_sleep=30)
|
2054
|
+
continue
|
2055
|
+
|
2056
|
+
if fsdate == 'latest':
|
2057
|
+
try:
|
2058
|
+
dpi=dpidf.tail(1)
|
2059
|
+
except:
|
2060
|
+
print(f" #Warning(compare_dupont): got empty data for {t} @ {fsdate} financials")
|
2061
|
+
faillist=faillist+[t]
|
2062
|
+
#sleep_random(max_sleep=30)
|
2063
|
+
continue
|
2064
|
+
elif fsdate == 'annual':
|
2065
|
+
dpidf_tmp=dpidf[dpidf['periodType']=="12M"]
|
2066
|
+
try:
|
2067
|
+
dpi=dpidf_tmp.tail(1)
|
2068
|
+
except:
|
2069
|
+
print(f" #Warning(compare_dupont): got empty data for {t} @ {fsdate} financials")
|
2070
|
+
faillist=faillist+[t]
|
2071
|
+
#sleep_random(max_sleep=30)
|
2072
|
+
continue
|
2073
|
+
|
2074
|
+
elif fsdate == 'quarterly':
|
2075
|
+
dpidf_tmp=dpidf[dpidf['periodType']=="3M"]
|
2076
|
+
try:
|
2077
|
+
dpi=dpidf_tmp.tail(1)
|
2078
|
+
except:
|
2079
|
+
print(f" #Warning(compare_dupont): got empty data for {t} @ {fsdate} financials")
|
2080
|
+
faillist=faillist+[t]
|
2081
|
+
#sleep_random(max_sleep=30)
|
2082
|
+
continue
|
2083
|
+
else: dpi=dpidf[dpidf['endDate']==fsdate]
|
2084
|
+
if len(dpi) == 0:
|
2085
|
+
print(" #Warning(compare_dupont): financial statements not found for",t,'@',fsdate)
|
2086
|
+
faillist=faillist+[t]
|
2087
|
+
#sleep_random(max_sleep=30)
|
2088
|
+
continue
|
2089
|
+
|
2090
|
+
newtickerlist=newtickerlist+[t]
|
2091
|
+
dpidflist=dpidflist+[dpidf]
|
2092
|
+
dpilist=dpilist+[dpi]
|
2093
|
+
fsdatelist=fsdatelist+[dpi['endDate'][0]]
|
2094
|
+
fstypelist=fstypelist+[dpi['periodType'][0]]
|
2095
|
+
|
2096
|
+
name1list=name1list+[dpi['Profit Margin'][0]*scale1]
|
2097
|
+
name2list=name2list+[dpi['Total Asset Turnover'][0]*scale2]
|
2098
|
+
name3list=name3list+[dpi['Equity Multiplier'][0]]
|
2099
|
+
name4list=name4list+[dpi['ROE'][0]]
|
2100
|
+
name5list=name5list+[dpi['endDate'][0]]
|
2101
|
+
name6list=name6list+[dpi['periodType'][0]]
|
2102
|
+
|
2103
|
+
#显示进度
|
2104
|
+
#print_progress_percent2(t,tickerlist,steps=5,leading_blanks=4)
|
2105
|
+
print(f" *** Successfully obtained financial information for {t}")
|
2106
|
+
|
2107
|
+
#第2次尝试
|
2108
|
+
for i in range(retry):
|
2109
|
+
if len(faillist) == 0: break
|
2110
|
+
|
2111
|
+
tickerlist=faillist
|
2112
|
+
faillist=[]
|
2113
|
+
|
2114
|
+
for t in tickerlist:
|
2115
|
+
try:
|
2116
|
+
with HiddenPrints():
|
2117
|
+
dpidf=calc_dupont(t)
|
2118
|
+
except:
|
2119
|
+
print(" #Warning(compare_dupont): found errors in accounting items, ignore",t)
|
2120
|
+
continue
|
2121
|
+
|
2122
|
+
#未出错,但未抓取到数据,再试
|
2123
|
+
if dpidf is None:
|
2124
|
+
faillist=faillist+[t]
|
2125
|
+
#sleep_random(max_sleep=30)
|
2126
|
+
continue
|
2127
|
+
|
2128
|
+
if fsdate == 'latest':
|
2129
|
+
try:
|
2130
|
+
dpi=dpidf.tail(1)
|
2131
|
+
except:
|
2132
|
+
print(f" #Warning(compare_dupont): got empty data for {t} @ {fsdate} financials")
|
2133
|
+
faillist=faillist+[t]
|
2134
|
+
#sleep_random(max_sleep=30)
|
2135
|
+
continue
|
2136
|
+
elif fsdate == 'annual':
|
2137
|
+
dpidf_tmp=dpidf[dpidf['periodType']=="12M"]
|
2138
|
+
try:
|
2139
|
+
dpi=dpidf_tmp.tail(1)
|
2140
|
+
except:
|
2141
|
+
print(f" #Warning(compare_dupont): got empty data for {t} @ {fsdate} financials")
|
2142
|
+
faillist=faillist+[t]
|
2143
|
+
#sleep_random(max_sleep=30)
|
2144
|
+
continue
|
2145
|
+
|
2146
|
+
elif fsdate == 'quarterly':
|
2147
|
+
dpidf_tmp=dpidf[dpidf['periodType']=="3M"]
|
2148
|
+
try:
|
2149
|
+
dpi=dpidf_tmp.tail(1)
|
2150
|
+
except:
|
2151
|
+
print(f" #Warning(compare_dupont): got empty data for {t} @ {fsdate} financials")
|
2152
|
+
faillist=faillist+[t]
|
2153
|
+
#sleep_random(max_sleep=30)
|
2154
|
+
continue
|
2155
|
+
else: dpi=dpidf[dpidf['endDate']==fsdate]
|
2156
|
+
if len(dpi) == 0:
|
2157
|
+
print(" #Warning(compare_dupont): financial statements not found for",t,'@',fsdate)
|
2158
|
+
faillist=faillist+[t]
|
2159
|
+
#sleep_random(max_sleep=30)
|
2160
|
+
continue
|
2161
|
+
|
2162
|
+
newtickerlist=newtickerlist+[t]
|
2163
|
+
dpidflist=dpidflist+[dpidf]
|
2164
|
+
dpilist=dpilist+[dpi]
|
2165
|
+
fsdatelist=fsdatelist+[dpi['endDate'][0]]
|
2166
|
+
fstypelist=fstypelist+[dpi['periodType'][0]]
|
2167
|
+
|
2168
|
+
name1list=name1list+[dpi['Profit Margin'][0]*scale1]
|
2169
|
+
name2list=name2list+[dpi['Total Asset Turnover'][0]*scale2]
|
2170
|
+
name3list=name3list+[dpi['Equity Multiplier'][0]]
|
2171
|
+
name4list=name4list+[dpi['ROE'][0]]
|
2172
|
+
name5list=name5list+[dpi['endDate'][0]]
|
2173
|
+
name6list=name6list+[dpi['periodType'][0]]
|
2174
|
+
|
2175
|
+
#显示进度
|
2176
|
+
#print_progress_percent2(t,tickerlist,steps=5,leading_blanks=4)
|
2177
|
+
print(f" *** Successfully obtained financial information for {t}")
|
2178
|
+
|
2179
|
+
|
2180
|
+
if len(faillist) > 0:
|
2181
|
+
print(f" ~~~ Pity: failed to fetch financials for {faillist}")
|
2182
|
+
|
2183
|
+
tickerlist=newtickerlist
|
2184
|
+
raw_data = {ticker:tickerlist,
|
2185
|
+
name1:name1list,
|
2186
|
+
name2:name2list,
|
2187
|
+
name3:name3list,
|
2188
|
+
name4:name4list,
|
2189
|
+
name5:name5list,
|
2190
|
+
name6:name6list}
|
2191
|
+
|
2192
|
+
df = pd.DataFrame(raw_data,columns=[ticker,name1,name2,name3,name4,name5,name6])
|
2193
|
+
num=len(df['公司'])
|
2194
|
+
for i in range(num):
|
2195
|
+
code=df.loc[i,'公司']
|
2196
|
+
df.loc[i,'公司']=ticker_name(code)
|
2197
|
+
|
2198
|
+
# 排序
|
2199
|
+
if sort=='PM':
|
2200
|
+
df.sort_values(name1,ascending=False,inplace=True)
|
2201
|
+
sorttxt=text_lang(":按照"+name1+"降序排列",": By Descending"+name1)
|
2202
|
+
elif sort=='TAT':
|
2203
|
+
df.sort_values(name2,ascending=False,inplace=True)
|
2204
|
+
sorttxt=text_lang(":按照"+name2+"降序排列",": By Descending"+name2)
|
2205
|
+
elif sort=='EM':
|
2206
|
+
df.sort_values(name3,ascending=False,inplace=True)
|
2207
|
+
sorttxt=text_lang(":按照"+name3+"降序排列",": By Descending"+name3)
|
2208
|
+
else:
|
2209
|
+
df.sort_values(name1,ascending=False,inplace=True)
|
2210
|
+
sorttxt=text_lang(":按照"+name1+"降序排列",": By Descending"+name1)
|
2211
|
+
|
2212
|
+
# 绘图
|
2213
|
+
#f,ax1 = plt.subplots(1,figsize=(10,5))
|
2214
|
+
f,ax1 = plt.subplots(1,figsize=(12.8,6.4))
|
2215
|
+
w = 0.75
|
2216
|
+
x = [i+1 for i in range(len(df[name1]))]
|
2217
|
+
#tick_pos = [i+(w/2.) for i in x]
|
2218
|
+
tick_pos = [i for i in x]
|
2219
|
+
|
2220
|
+
ax1.bar(x,df[name3],width=w,bottom=[i+j for i,j in zip(df[name1],df[name2])], \
|
2221
|
+
label=ectranslate(name3),alpha=0.5,color='green',hatch=hatchlist[0], \
|
2222
|
+
edgecolor='black',align='center')
|
2223
|
+
ax1.bar(x,df[name2],width=w,bottom=df[name1],label=ectranslate(name2),alpha=0.5,color='red', \
|
2224
|
+
hatch=hatchlist[1], edgecolor='black',align='center')
|
2225
|
+
ax1.bar(x,df[name1],width=w,label=ectranslate(name1),alpha=0.5,color='blue', \
|
2226
|
+
hatch=hatchlist[2], edgecolor='black',align='center')
|
2227
|
+
|
2228
|
+
plt.xticks(tick_pos,df[ticker])
|
2229
|
+
if lang == 'English':
|
2230
|
+
plt.ylabel(texttranslate("杜邦分析分解项目"),fontsize=ylabel_txt_size)
|
2231
|
+
else:
|
2232
|
+
plt.ylabel("杜邦分析分解项目",fontsize=ylabel_txt_size)
|
2233
|
+
|
2234
|
+
tickernamelist,fstypenamelist=[],[]
|
2235
|
+
for i in range(num):
|
2236
|
+
tickernamelist=tickernamelist+[ticker_name(tickerlist[i])]
|
2237
|
+
if fstypelist[i]=='3M': fsname='季报'
|
2238
|
+
else: fsname='年报'
|
2239
|
+
fstypenamelist=fstypenamelist+[fsname]
|
2240
|
+
|
2241
|
+
if lang == 'Chinese':
|
2242
|
+
footnote='【'+'财报日期及类型'+'】'
|
2243
|
+
else:
|
2244
|
+
footnote='【'+texttranslate('财报日期及类型')+'】'
|
2245
|
+
|
2246
|
+
#检查财报类型是否一致
|
2247
|
+
name5types=len(df.groupby(name5).count())
|
2248
|
+
if name5types > 1:
|
2249
|
+
#财报类型不一致
|
2250
|
+
linenum=0
|
2251
|
+
for i in range(num):
|
2252
|
+
if linenum % 4 == 3:
|
2253
|
+
footnote=footnote+'\n'
|
2254
|
+
footnote=footnote+ticker_name(tickerlist[i])+":"+fsdatelist[i]+","+fstypenamelist[i]
|
2255
|
+
if linenum < num -1:
|
2256
|
+
footnote=footnote+';'
|
2257
|
+
linenum=linenum + 1
|
2258
|
+
else:
|
2259
|
+
footnote=footnote+fsdatelist[0]+","+fstypenamelist[0]
|
2260
|
+
|
2261
|
+
import datetime; today=datetime.date.today()
|
2262
|
+
#footnote1=footnote+'\n'+"【图示放大比例】"+name1+':x'+str(scale1)+','+name2+':x'+str(scale2)
|
2263
|
+
if lang == 'Chinese':
|
2264
|
+
footnote1="【图示放大比例】"+name1+':x'+str(scale1)+','+name2+':x'+str(scale2)
|
2265
|
+
footnote2=footnote1+'\n'+"数据来源:雅虎财经,"+' '+str(today)
|
2266
|
+
else:
|
2267
|
+
footnote1=texttranslate("【图示放大比例】")+ectranslate(name1)+':x'+str(scale1)+','+ectranslate(name2)+':x'+str(scale2)
|
2268
|
+
footnote2=footnote1+'\n'+texttranslate("数据来源: 雅虎财经,")+' '+str(today)
|
2269
|
+
plt.xlabel(footnote2,fontsize=xlabel_txt_size)
|
2270
|
+
|
2271
|
+
plt.legend(loc=loc1,fontsize=legend_txt_size)
|
2272
|
+
if lang == 'Chinese':
|
2273
|
+
plt.title("杜邦分析对比图"+sorttxt,fontsize=title_txt_size,fontweight='bold')
|
2274
|
+
else:
|
2275
|
+
plt.title(texttranslate("杜邦分析对比图")+sorttxt,fontsize=title_txt_size,fontweight='bold')
|
2276
|
+
plt.xlim([min(tick_pos)-w,max(tick_pos)+w])
|
2277
|
+
|
2278
|
+
plt.gca().set_facecolor('whitesmoke')
|
2279
|
+
plt.show()
|
2280
|
+
|
2281
|
+
#设置打印对齐
|
2282
|
+
pd.set_option('display.max_columns', 1000)
|
2283
|
+
pd.set_option('display.width', 1000)
|
2284
|
+
pd.set_option('display.max_colwidth', 1000)
|
2285
|
+
pd.set_option('display.unicode.ambiguous_as_wide', True)
|
2286
|
+
pd.set_option('display.unicode.east_asian_width', True)
|
2287
|
+
|
2288
|
+
df[name1]=df[name1]/scale1
|
2289
|
+
df[name2]=df[name2]/scale2
|
2290
|
+
|
2291
|
+
for i in range(num):
|
2292
|
+
code=df.loc[i,'财报类型']
|
2293
|
+
if code == '3M': df.loc[i,'财报类型']='季报'
|
2294
|
+
else: df.loc[i,'财报类型']='年报'
|
2295
|
+
|
2296
|
+
dfcols=list(df)
|
2297
|
+
dfecols=[]
|
2298
|
+
for c in dfcols:
|
2299
|
+
ce=ectranslate(c)
|
2300
|
+
dfecols=dfecols+[ce]
|
2301
|
+
df[ce]=df[c]
|
2302
|
+
df[ectranslate('财报类型')]=df['财报类型'].apply(lambda x:'Quarterly' if x=='季报' else 'Annual')
|
2303
|
+
dfe=df[dfecols]
|
2304
|
+
|
2305
|
+
titletxt=text_lang("杜邦分析分项数据表","Du Pont Identity Fact Sheet")
|
2306
|
+
footnote=text_lang("数据来源: 雅虎财经","Data source: Yahoo Finance")+', '+str(today)
|
2307
|
+
#确定表格字体大小
|
2308
|
+
titile_font_size=font_size
|
2309
|
+
heading_font_size=data_font_size=str(int(font_size.replace('px',''))-1)+'px'
|
2310
|
+
|
2311
|
+
df_display_CSS(df=df,titletxt=titletxt,footnote=footnote, \
|
2312
|
+
facecolor=facecolor,decimals=4, \
|
2313
|
+
titile_font_size=titile_font_size,heading_font_size=heading_font_size, \
|
2314
|
+
data_font_size=data_font_size)
|
2315
|
+
|
2316
|
+
|
2317
|
+
#合并所有历史记录
|
2318
|
+
alldf=pd.concat(dpidflist)
|
2319
|
+
alldf.dropna(inplace=True)
|
2320
|
+
del alldf['pROE']
|
2321
|
+
|
2322
|
+
"""
|
2323
|
+
allnum=len(alldf)
|
2324
|
+
for i in range(allnum):
|
2325
|
+
code=alldf.loc[i,'periodType']
|
2326
|
+
if code == '3M': alldf.loc[i,'periodType']='Quarterly'
|
2327
|
+
else: alldf.loc[i,'periodType']='Annual'
|
2328
|
+
"""
|
2329
|
+
return alldf
|
2330
|
+
|
2331
|
+
if __name__=='__main__':
|
2332
|
+
tickerlist=['IBM','DELL','WMT']
|
2333
|
+
df=compare_dupont(tickerlist,fsdate='latest',scale1 = 100,scale2 = 10)
|
2334
|
+
#==============================================================================
|
2335
|
+
|
2336
|
+
|
2337
|
+
|
2338
|
+
#==============================================================================
|
2339
|
+
#==============================================================================
|