siat 3.10.25__py3-none-any.whl → 3.10.125__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.
- siat/assets_liquidity.py +168 -30
- siat/bond.py +91 -30
- siat/capm_beta.py +36 -10
- siat/capm_beta2.py +34 -3
- siat/common.py +95 -8
- siat/economy.py +3 -3
- siat/economy2.py +75 -1
- siat/exchange_bond_china.pickle +0 -0
- siat/fama_french.py +292 -12
- siat/financial_statements.py +26 -85
- siat/financials.py +156 -20
- siat/financials_china.py +1 -1
- siat/fund_china.py +52 -22
- siat/future_china.py +53 -59
- siat/grafix.py +218 -155
- siat/holding_risk.py +38 -31
- siat/market_china.py +22 -11
- siat/markowitz2.py +72 -10
- siat/option_china.py +93 -31
- siat/option_pricing.py +16 -6
- siat/other_indexes.py +16 -3
- siat/risk_adjusted_return.py +283 -164
- siat/risk_adjusted_return2.py +3 -1
- siat/risk_evaluation.py +298 -25
- siat/sector_china.py +24 -11
- siat/security_price2.py +19 -4
- siat/security_prices.py +228 -28
- siat/security_trend2.py +14 -14
- siat/stock.py +38 -3
- siat/translate.py +20 -10
- siat/var_model_validation.py +59 -0
- {siat-3.10.25.dist-info → siat-3.10.125.dist-info}/METADATA +1 -1
- {siat-3.10.25.dist-info → siat-3.10.125.dist-info}/RECORD +36 -36
- {siat-3.10.25.dist-info → siat-3.10.125.dist-info}/LICENSE +0 -0
- {siat-3.10.25.dist-info → siat-3.10.125.dist-info}/WHEEL +0 -0
- {siat-3.10.25.dist-info → siat-3.10.125.dist-info}/top_level.txt +0 -0
siat/risk_adjusted_return.py
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
所属工具包:证券投资分析工具SIAT
|
5
5
|
SIAT:Security Investment Analysis Tool
|
6
6
|
创建日期:2018年10月16日
|
7
|
-
最新修订日期:
|
7
|
+
最新修订日期:2025年6月20日
|
8
8
|
作者:王德宏 (WANG Dehong, Peter)
|
9
9
|
作者单位:北京外国语大学国际商学院
|
10
10
|
作者邮件:wdehong2000@163.com
|
@@ -65,8 +65,9 @@ def calc_treynor_ratio(regdf):
|
|
65
65
|
#计算特雷诺指数
|
66
66
|
tr=ret_rf_mean/beta
|
67
67
|
|
68
|
-
ret_mean=regdf['
|
69
|
-
|
68
|
+
#ret_mean=regdf['Ret%'].mean()
|
69
|
+
rp_mean=ret_rf_mean
|
70
|
+
return tr,rp_mean,beta
|
70
71
|
|
71
72
|
#==============================================================================
|
72
73
|
def calc_alpha_ratio(regdf):
|
@@ -75,17 +76,15 @@ def calc_alpha_ratio(regdf):
|
|
75
76
|
输入:数据框,至少含有Ret-Rf和Mkt-Rf两项
|
76
77
|
输出:詹森阿尔法指数,Ret-Rf均值
|
77
78
|
"""
|
78
|
-
"""
|
79
79
|
#计算风险溢价Ret-RF均值
|
80
80
|
ret_rf_mean=regdf['Ret-RF'].mean()
|
81
|
-
"""
|
82
81
|
#使用CAPM回归计算投资组合的贝塔系数,这里得到的alpha就是Jensen's alpha
|
83
82
|
from scipy import stats
|
84
83
|
output=stats.linregress(regdf['Mkt-RF'],regdf['Ret-RF'])
|
85
84
|
(beta,alpha,r_value,p_value,std_err)=output
|
86
85
|
|
87
|
-
|
88
|
-
return alpha,
|
86
|
+
rp_mean=ret_rf_mean
|
87
|
+
return alpha,rp_mean,beta
|
89
88
|
|
90
89
|
#==============================================================================
|
91
90
|
def calc_sharpe_ratio(regdf):
|
@@ -101,8 +100,9 @@ def calc_sharpe_ratio(regdf):
|
|
101
100
|
#计算夏普指数
|
102
101
|
sr=ret_rf_mean/ret_rf_std
|
103
102
|
|
104
|
-
|
105
|
-
|
103
|
+
rp_mean=ret_rf_mean
|
104
|
+
beta=False
|
105
|
+
return sr,rp_mean,beta
|
106
106
|
|
107
107
|
if __name__=='__main__':
|
108
108
|
rfd=rf_daily_china('2021-10-1','2021-11-28',rate_period='1Y',rate_type='shibor')
|
@@ -131,15 +131,18 @@ def calc_sortino_ratio(regdf):
|
|
131
131
|
#计算索梯诺指数
|
132
132
|
sr=ret_rf_mean/ret_rf_lpsd
|
133
133
|
|
134
|
-
|
135
|
-
|
134
|
+
rp_mean=ret_rf_mean
|
135
|
+
beta=False
|
136
|
+
return sr,rp_mean,beta
|
136
137
|
|
137
138
|
#==============================================================================
|
138
|
-
def print_rar_ratio(regdf,portfolio,
|
139
|
+
def print_rar_ratio(regdf,portfolio,rp_mean,beta,ratio_name,ratio):
|
139
140
|
"""
|
140
141
|
功能:打印风险调整后的收益率
|
141
|
-
|
142
|
+
输入:数据框,投资组合构成,收益溢价均值,贝塔系数,指数名称,指数
|
142
143
|
输出:打印
|
144
|
+
|
145
|
+
注意:若贝塔系数为False则不打印
|
143
146
|
"""
|
144
147
|
|
145
148
|
#从字典中提取信息
|
@@ -150,32 +153,41 @@ def print_rar_ratio(regdf,portfolio,ret_mean,ratio_name,ratio):
|
|
150
153
|
'-'+str(regdf.index[0].day)
|
151
154
|
date_end=str(regdf.index[-1].year)+'-'+str(regdf.index[-1].month)+ \
|
152
155
|
'-'+str(regdf.index[-1].day)
|
153
|
-
print("\n
|
154
|
-
print("
|
156
|
+
print("\n======== 风险调整收益率 ========")
|
157
|
+
print("证券资产:",portfolio_name(portfolio))
|
158
|
+
#print("市场指数:",ectranslate(scope),'\b,',ticker_name(mktidx))
|
159
|
+
print("市场指数:",ticker_name(mktidx))
|
155
160
|
#print("成分股 :",ticker_name(stocklist))
|
156
161
|
#print("持仓权重:",portionlist)
|
157
|
-
print("
|
162
|
+
print("样本期间:",date_start,"至",date_end)
|
158
163
|
"""
|
159
164
|
print("日均收益率:",round(ret_mean,4),'\b%')
|
160
|
-
"""
|
161
165
|
annual_ret=(1+ret_mean/100)**252-1
|
162
166
|
print("年化收益率:",round(annual_ret,4))
|
163
|
-
|
164
|
-
|
167
|
+
"""
|
168
|
+
if not isinstance(beta,bool):
|
169
|
+
print("贝塔系数:",round(beta,4))
|
170
|
+
|
171
|
+
print("风险溢价均值%:",round(rp_mean,4))
|
165
172
|
|
173
|
+
#print(ratio_name.capitalize(),"\b比率:",round(ratio,4),'\b%')
|
174
|
+
print(ratio_name.capitalize(),"\b比率%:",round(ratio,4))
|
175
|
+
"""
|
166
176
|
print("***投资组合构成:")
|
167
177
|
print_tickerlist_sharelist(stocklist,portionlist,2)
|
168
|
-
|
178
|
+
"""
|
169
179
|
|
170
|
-
import datetime as dt;
|
171
|
-
print("
|
180
|
+
import datetime as dt; todaydt=dt.date.today()
|
181
|
+
print("数据来源:新浪/stooq, "+str(todaydt))
|
172
182
|
|
173
183
|
return
|
174
184
|
#==============================================================================
|
175
185
|
if __name__=='__main__':
|
176
|
-
|
177
|
-
|
178
|
-
|
186
|
+
portfolio={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
187
|
+
start='2024-6-1'
|
188
|
+
end='2025-5-30'
|
189
|
+
RF=0.04
|
190
|
+
printout=True
|
179
191
|
|
180
192
|
rate_period='ON'
|
181
193
|
|
@@ -200,31 +212,43 @@ def treynor_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
200
212
|
|
201
213
|
#从字典中提取信息
|
202
214
|
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
203
|
-
|
204
|
-
#第2
|
205
|
-
|
206
|
-
|
215
|
+
|
216
|
+
#第2步:获得无风险收益率/市场收益率序列
|
217
|
+
#获得期间的日无风险收益率(抓取的RF为百分比)
|
218
|
+
if isinstance(RF,bool):
|
219
|
+
print(" Searching for risk-free interest rate ...")
|
220
|
+
if scope=='China':
|
221
|
+
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
222
|
+
else:
|
223
|
+
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
224
|
+
if rf_df is None:
|
225
|
+
message=" #Error("+func_name+"): "+"no data available for rf in"
|
226
|
+
print(message,scope,start,end)
|
227
|
+
return None,None
|
228
|
+
RF=rf_df['RF'].mean()
|
229
|
+
|
230
|
+
#第3步:计算投资组合的日收益率序列
|
231
|
+
#抓取日投资组合价格:内含Mkt-RF和RF
|
232
|
+
sp=get_portfolio_prices(portfolio,start,end,RF=RF)
|
207
233
|
#计算日收益率,表示为百分比
|
234
|
+
"""
|
208
235
|
import pandas as pd
|
209
236
|
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
210
237
|
ret_pf=ret_pf.dropna()
|
211
|
-
|
212
|
-
|
213
|
-
#获得期间的日无风险收益率(抓取的RF为百分比)
|
214
|
-
print(" Searching for risk-free interest rate ...")
|
215
|
-
if scope=='China':
|
216
|
-
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
217
|
-
else:
|
218
|
-
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
219
|
-
if rf_df is None:
|
220
|
-
message=" #Error("+func_name+"): "+"no data available for rf in"
|
221
|
-
print(message,scope,start,end)
|
222
|
-
return None,None
|
238
|
+
"""
|
239
|
+
ret_pf=sp
|
223
240
|
|
224
241
|
#第4步:合并投资组合日收益率与无风险利率/市场收益率序列
|
225
|
-
|
226
|
-
|
242
|
+
"""
|
243
|
+
if isinstance(RF,bool):
|
244
|
+
#合并rf_df与ret_pf
|
245
|
+
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
246
|
+
else:
|
247
|
+
reg=ret_pf
|
248
|
+
reg['RF']=RF/365 #日度无风险收益率%
|
227
249
|
reg['Ret-RF']=reg['Close']-reg['RF']
|
250
|
+
"""
|
251
|
+
reg=ret_pf
|
228
252
|
reg=reg.dropna()
|
229
253
|
if len(reg) == 0:
|
230
254
|
message=" #Error("+func_name+"): "+"empty ret-rf data for regression"
|
@@ -233,14 +257,14 @@ def treynor_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
233
257
|
|
234
258
|
#第5步:计算风险调整后的收益率
|
235
259
|
##########风险调整后的收益率,计算开始##########
|
236
|
-
tr,
|
260
|
+
tr,rp_mean,beta=calc_treynor_ratio(reg)
|
237
261
|
##########风险调整后的收益率,计算结束##########
|
238
262
|
|
239
263
|
#第6步:打印结果
|
240
264
|
if printout == True:
|
241
|
-
print_rar_ratio(reg,portfolio,
|
265
|
+
print_rar_ratio(reg,portfolio,rp_mean,beta,ratio_name,tr)
|
242
266
|
|
243
|
-
return tr,
|
267
|
+
return tr,rp_mean
|
244
268
|
|
245
269
|
|
246
270
|
if __name__=='__main__':
|
@@ -250,79 +274,122 @@ if __name__=='__main__':
|
|
250
274
|
|
251
275
|
#==============================================================================
|
252
276
|
if __name__=='__main__':
|
253
|
-
portfolio={'Market':('US','^GSPC'),'EDU':0.
|
254
|
-
start='
|
255
|
-
end ='
|
256
|
-
|
277
|
+
portfolio={'Market':('US','^GSPC'),'EDU':0.6,'TAL':0.4}
|
278
|
+
start='2025-1-01'
|
279
|
+
end ='2025-5-30'
|
280
|
+
RF=0.04; printout=True
|
281
|
+
indicator='sharpe'
|
282
|
+
indicator='alpha'
|
257
283
|
|
258
284
|
|
259
|
-
def rar_ratio_portfolio(portfolio,start,end
|
285
|
+
def rar_ratio_portfolio(portfolio,start='MRY',end='today', \
|
286
|
+
indicator='sharpe', \
|
287
|
+
RF=0,printout=True):
|
260
288
|
"""
|
261
289
|
功能:按天计算一个投资组合的风险调整后的收益率指数
|
262
290
|
投资组合的结构:{'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
263
|
-
|
291
|
+
输入:投资组合,开始日期,结束日期,rar种类
|
264
292
|
输出:风险调整后的收益率指数
|
265
293
|
"""
|
294
|
+
ratio_name=indicator
|
266
295
|
|
267
296
|
#第1步:各种准备和检查工作
|
268
297
|
#设定错误信息的函数名
|
269
298
|
func_name='rar_ratio_portfolio'
|
270
299
|
|
300
|
+
ratio_name=ratio_name.lower()
|
271
301
|
ratio_list=['treynor','sharpe','sortino','alpha']
|
272
302
|
if ratio_name not in ratio_list:
|
273
303
|
message=" #Error("+func_name+"): "+"unsupported rar ratio type"
|
274
304
|
print(message)
|
275
|
-
return None,None
|
305
|
+
return None,None
|
306
|
+
|
307
|
+
start,end=start_end_preprocess(start,end)
|
276
308
|
result,startdate,enddate=check_period(start,end)
|
277
309
|
if not result:
|
278
310
|
message=" #Error("+func_name+"): "+"invalid start or end date"
|
279
311
|
print(message,start,end)
|
280
312
|
return None,None
|
281
313
|
|
314
|
+
print(f" Calculating {ratio_name} ratio ...")
|
282
315
|
#从字典中提取信息
|
283
316
|
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
317
|
+
|
318
|
+
#第2步:获得无风险收益率/市场收益率序列
|
319
|
+
#获得期间的日无风险收益率(抓取的RF为百分比)
|
320
|
+
rf_value_flag=True #RF以数值形式给出
|
321
|
+
if isinstance(RF,bool):
|
322
|
+
rf_value_flag=False
|
323
|
+
if RF:
|
324
|
+
print(" Searching for risk-free interest rate ...")
|
325
|
+
if scope=='China':
|
326
|
+
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
327
|
+
else:
|
328
|
+
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
329
|
+
if rf_df is None:
|
330
|
+
message=" #Error("+func_name+"): "+"no data available for rf in"
|
331
|
+
print(message,scope,start,end)
|
332
|
+
return None,None
|
333
|
+
RF=rf_df['RF'].mean()
|
334
|
+
else:
|
335
|
+
RF=0
|
336
|
+
rf_value_flag=True
|
337
|
+
|
338
|
+
#第3步:计算投资组合的日收益率序列
|
339
|
+
import os,sys
|
340
|
+
class HiddenPrints:
|
341
|
+
def __enter__(self):
|
342
|
+
self._original_stdout = sys.stdout
|
343
|
+
sys.stdout = open(os.devnull, 'w')
|
344
|
+
|
345
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
346
|
+
sys.stdout.close()
|
347
|
+
sys.stdout = self._original_stdout
|
284
348
|
|
285
|
-
#第2步:计算投资组合的日收益率序列
|
286
349
|
#抓取日投资组合价格
|
287
|
-
|
350
|
+
with HiddenPrints():
|
351
|
+
sp=get_portfolio_prices(portfolio,startdate,enddate,RF=RF)
|
352
|
+
if sp is None:
|
353
|
+
print(" #Error(rar_ratio_portfolio): failed to retrieve portfolio information")
|
354
|
+
return None,None
|
355
|
+
if len(sp) == 0:
|
356
|
+
print(" #Error(rar_ratio_portfolio): no portfolio information found during the period")
|
357
|
+
return None,None
|
358
|
+
"""
|
288
359
|
#计算日收益率,表示为百分比
|
289
360
|
import pandas as pd
|
290
361
|
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
291
362
|
ret_pf=ret_pf.dropna()
|
292
|
-
|
293
|
-
#第3步:获得无风险收益率/市场收益率序列
|
294
|
-
#获得期间的日无风险收益率(抓取的RF为百分比)
|
295
|
-
print(" Searching for risk-free interest rate ...")
|
296
|
-
if scope=='China':
|
297
|
-
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
298
|
-
else:
|
299
|
-
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
300
|
-
if rf_df is None:
|
301
|
-
message=" #Error("+func_name+"): "+"no data available for rf in"
|
302
|
-
print(message,scope,start,end)
|
303
|
-
return None,None
|
304
363
|
|
305
364
|
#第4步:合并投资组合日收益率与无风险利率/市场收益率序列
|
306
|
-
|
307
|
-
|
365
|
+
if not rf_value_flag:
|
366
|
+
#合并rf_df与ret_pf
|
367
|
+
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
368
|
+
|
369
|
+
else:
|
370
|
+
ret_pf['RF']=RF
|
371
|
+
reg=ret_pf
|
372
|
+
|
308
373
|
reg['Ret-RF']=reg['Close']-reg['RF']
|
374
|
+
"""
|
375
|
+
reg=sp
|
309
376
|
reg=reg.dropna()
|
310
377
|
if len(reg) == 0:
|
311
378
|
message=" #Error("+func_name+"): "+"empty data for ratio calculation"
|
312
379
|
print(message)
|
313
380
|
return None,None
|
314
381
|
|
315
|
-
#第
|
382
|
+
#第4步:计算风险调整后的收益率
|
316
383
|
##########风险调整后的收益率,计算开始##########
|
317
384
|
calc_func='calc_'+ratio_name+'_ratio'
|
318
|
-
rar,
|
385
|
+
rar,rp_mean,beta=eval(calc_func)(reg)
|
319
386
|
##########风险调整后的收益率,计算结束##########
|
320
387
|
|
321
|
-
#第
|
388
|
+
#第5步:打印结果
|
322
389
|
if printout == True:
|
323
|
-
print_rar_ratio(reg,portfolio,
|
390
|
+
print_rar_ratio(reg,portfolio,rp_mean,beta,ratio_name,rar)
|
324
391
|
|
325
|
-
return rar,
|
392
|
+
return rar,rp_mean
|
326
393
|
|
327
394
|
|
328
395
|
if __name__=='__main__':
|
@@ -339,7 +406,7 @@ if __name__=='__main__':
|
|
339
406
|
window=30
|
340
407
|
graph=True
|
341
408
|
|
342
|
-
def rar_ratio_rolling(portfolio,start,end,
|
409
|
+
def rar_ratio_rolling(portfolio,start='MRY',end='today',indicator='sharpe',RF=0, \
|
343
410
|
window=21,graph=True,source='auto'):
|
344
411
|
"""
|
345
412
|
功能:滚动计算一个投资组合的风险调整后的收益率指数
|
@@ -351,6 +418,9 @@ def rar_ratio_rolling(portfolio,start,end,ratio_name='treynor',RF=True, \
|
|
351
418
|
|
352
419
|
注意:当RF=False时有bug
|
353
420
|
"""
|
421
|
+
start,end=start_end_preprocess(start,end)
|
422
|
+
|
423
|
+
ratio_name=indicator.lower()
|
354
424
|
|
355
425
|
#第1步:各种准备和检查工作
|
356
426
|
print(" Start to calculate rar ratios, please wait ...")
|
@@ -369,48 +439,69 @@ def rar_ratio_rolling(portfolio,start,end,ratio_name='treynor',RF=True, \
|
|
369
439
|
print(message,start,end)
|
370
440
|
return None
|
371
441
|
#估算数据提前量,重设开始日历日期
|
372
|
-
startdate_delta=int(window/
|
442
|
+
#startdate_delta=int(window/20*30)+30
|
443
|
+
startdate_delta=int(window/20*31)
|
373
444
|
startdate1=date_adjust(startdate, adjust=-startdate_delta)
|
374
445
|
|
375
446
|
#从字典中提取信息
|
376
447
|
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
377
|
-
|
378
|
-
|
448
|
+
pname=portfolio_name(portfolio)
|
449
|
+
if pname == '': pname="投资组合"
|
450
|
+
|
451
|
+
#第2步:获得无风险收益率/市场收益率序列
|
452
|
+
#获得期间的日无风险收益率(抓取的RF为百分比)
|
453
|
+
rf_value_flag=True #RF以数值形式给出
|
454
|
+
if isinstance(RF,bool):
|
455
|
+
rf_value_flag=False
|
456
|
+
if RF:
|
457
|
+
print(" Searching for risk-free interest rate ...")
|
458
|
+
if scope=='China':
|
459
|
+
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
460
|
+
else:
|
461
|
+
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
462
|
+
if rf_df is None:
|
463
|
+
message=" #Error("+func_name+"): "+"no data available for rf in"
|
464
|
+
print(message,scope,start,end)
|
465
|
+
return None,None
|
466
|
+
RF=rf_df['RF'].mean()
|
467
|
+
else:
|
468
|
+
RF=0
|
469
|
+
rf_value_flag=True
|
470
|
+
|
471
|
+
#第3步:计算投资组合的日收益率序列
|
379
472
|
#抓取日投资组合价格
|
380
|
-
sp=get_portfolio_prices(portfolio,startdate1,enddate,
|
381
|
-
if sp is None:
|
382
|
-
|
383
|
-
|
384
|
-
|
473
|
+
sp=get_portfolio_prices(portfolio,startdate1,enddate,RF=RF)
|
474
|
+
if sp is None:
|
475
|
+
print(" #Error(rar_ratio_portfolio): failed to retrieve portfolio information")
|
476
|
+
return None,None
|
477
|
+
if len(sp) == 0:
|
478
|
+
print(" #Error(rar_ratio_portfolio): no portfolio information found during the period")
|
479
|
+
return None,None
|
480
|
+
"""
|
385
481
|
#计算日收益率,表示为百分比
|
386
482
|
import pandas as pd
|
387
483
|
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
388
484
|
ret_pf=ret_pf.dropna()
|
389
|
-
|
390
|
-
#第3步:获得无风险收益率/市场收益率序列
|
391
|
-
#获得期间的日无风险收益率(抓取的RF为百分比)
|
392
|
-
#print(" Searching for risk-free interest rate ...")
|
393
|
-
if scope=='China':
|
394
|
-
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
395
|
-
else:
|
396
|
-
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
397
|
-
if rf_df is None:
|
398
|
-
message=" #Error("+func_name+"): "+"no data available for rf in"
|
399
|
-
print(message,scope,start,end)
|
400
|
-
return None,None
|
401
485
|
|
402
486
|
#第4步:合并投资组合日收益率与无风险利率/市场收益率序列
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
487
|
+
if not rf_value_flag:
|
488
|
+
#合并rf_df与ret_pf
|
489
|
+
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
490
|
+
|
491
|
+
else:
|
492
|
+
ret_pf['RF']=RF
|
493
|
+
reg=ret_pf
|
409
494
|
|
410
495
|
reg['Ret-RF']=reg['Close']-reg['RF']
|
496
|
+
"""
|
497
|
+
reg=sp
|
411
498
|
reg=reg.dropna()
|
499
|
+
if len(reg) == 0:
|
500
|
+
message=" #Error("+func_name+"): "+"empty data for ratio calculation"
|
501
|
+
print(message)
|
502
|
+
return None,None
|
412
503
|
|
413
|
-
#第
|
504
|
+
#第4步:滚动计算风险调整后的收益率
|
414
505
|
##########风险调整后的收益率,计算开始##########
|
415
506
|
#用于保存rar和ret_rf_mean
|
416
507
|
import pandas as pd
|
@@ -447,10 +538,10 @@ def rar_ratio_rolling(portfolio,start,end,ratio_name='treynor',RF=True, \
|
|
447
538
|
rars.set_index(['Date'],inplace=True)
|
448
539
|
##########风险调整后的收益率,计算结束##########
|
449
540
|
|
450
|
-
#第
|
541
|
+
#第5步:绘图
|
451
542
|
if graph == True:
|
452
543
|
print(" Rendering graphics ...")
|
453
|
-
draw_rar_ratio(rars,portfolio,ratio_name)
|
544
|
+
draw_rar_ratio(rars,portfolio,ratio_name,pname)
|
454
545
|
|
455
546
|
return rars
|
456
547
|
|
@@ -459,7 +550,7 @@ if __name__=='__main__':
|
|
459
550
|
pf1={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
460
551
|
rars1=rar_ratio_rolling(pf1,'2020-1-1','2020-12-31',ratio_name='sharpe')
|
461
552
|
#==============================================================================
|
462
|
-
def draw_rar_ratio(rars,portfolio,ratio_name):
|
553
|
+
def draw_rar_ratio(rars,portfolio,ratio_name,pname):
|
463
554
|
"""
|
464
555
|
功能:绘制滚动窗口曲线
|
465
556
|
输入:滚动数据df,投资组合,指数名称
|
@@ -482,11 +573,13 @@ def draw_rar_ratio(rars,portfolio,ratio_name):
|
|
482
573
|
plt.plot(rars['RAR'],label=labeltxt,color='red',lw=1)
|
483
574
|
#plt.plot(rars['Mean(Ret)'],label='Stock(s) return(%)',color='blue',lw=1)
|
484
575
|
plt.axhline(y=0.0,color='black',linestyle=':')
|
485
|
-
|
576
|
+
"""
|
486
577
|
titletxt='风险调整收益的滚动趋势'+'\n'+str(ticker_name(stocklist))
|
487
578
|
if len(stocklist) > 1:
|
488
579
|
titletxt=titletxt+'\n持仓比例: '+str(portionlist)
|
489
580
|
"""
|
581
|
+
titletxt='风险调整收益的滚动趋势:'+pname
|
582
|
+
"""
|
490
583
|
if len(stocklist) == 1:
|
491
584
|
titletxt='风险调整收益的滚动趋势'+'\n('+ticker_name(stocklist)+')'
|
492
585
|
"""
|
@@ -554,35 +647,43 @@ def sharpe_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
554
647
|
return None,None
|
555
648
|
"""
|
556
649
|
|
557
|
-
|
558
|
-
|
650
|
+
#获得期间的无风险收益率
|
651
|
+
if isinstance(RF,bool):
|
652
|
+
print(" Searching for risk-free interest rate ...")
|
653
|
+
if scope=='China':
|
654
|
+
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
655
|
+
else:
|
656
|
+
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
657
|
+
if rf_df is None:
|
658
|
+
message=" #Error("+func_name+"): "+"no data available for rf in"
|
659
|
+
print(message,scope,start,end)
|
660
|
+
return None,None
|
661
|
+
RF=rf_df['RF'].mean()
|
662
|
+
|
663
|
+
#抓取日投资组合价格:内含Mkt-RF和RF
|
664
|
+
sp=get_portfolio_prices(portfolio,start,end,RF=RF)
|
559
665
|
#计算日收益率,表示为百分比
|
666
|
+
"""
|
560
667
|
import pandas as pd
|
561
668
|
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
669
|
+
"""
|
670
|
+
ret_pf=sp
|
562
671
|
ret_pf=ret_pf.dropna()
|
563
|
-
|
564
|
-
#获得期间的无风险收益率
|
565
|
-
print(" Searching for risk-free interest rate ...")
|
566
|
-
if scope=='China':
|
567
|
-
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
568
|
-
else:
|
569
|
-
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
570
|
-
if rf_df is None:
|
571
|
-
message=" #Error("+func_name+"): "+"no data available for rf in"
|
572
|
-
print(message,scope,start,end)
|
573
|
-
return None,None
|
574
672
|
|
575
673
|
#强制转换索引格式,彻底消除下面并表的潜在隐患
|
674
|
+
"""
|
576
675
|
rf_df['ffdate']=rf_df.index.astype('str')
|
577
676
|
rf_df['ffdate']=pd.to_datetime(rf_df['ffdate'])
|
578
677
|
rf_df.set_index(['ffdate'],inplace=True)
|
579
|
-
|
678
|
+
"""
|
679
|
+
"""
|
580
680
|
#合并rf_df与ret_pf
|
581
681
|
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
582
682
|
reg['Ret-RF']=reg['Close']-reg['RF']
|
583
683
|
reg=reg.dropna()
|
584
|
-
|
684
|
+
"""
|
585
685
|
#计算风险溢价Ret-RF均值和标准差
|
686
|
+
reg=ret_pf
|
586
687
|
ret_rf_mean=reg['Ret-RF'].mean()
|
587
688
|
ret_rf_std=reg['Ret-RF'].std()
|
588
689
|
|
@@ -596,20 +697,23 @@ def sharpe_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
596
697
|
date_end=str(reg.index[-1].year)+'-'+str(reg.index[-1].month)+ \
|
597
698
|
'-'+str(reg.index[-1].day)
|
598
699
|
print("\n===== 风险调整收益率 =====")
|
599
|
-
|
700
|
+
"""
|
600
701
|
_,_,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
|
601
702
|
if len(tickerlist)==1:
|
602
703
|
product=str(ticker_name(tickerlist,'bond'))
|
603
704
|
else:
|
604
705
|
product=str(ticker_name(tickerlist,'bond'))+' by '+str(sharelist)
|
605
|
-
|
606
|
-
print("
|
607
|
-
print("
|
608
|
-
print("
|
706
|
+
"""
|
707
|
+
print("证券资产:",portfolio_name(portfolio))
|
708
|
+
print("样本期间:",date_start,"至",date_end,"(可用日期)")
|
709
|
+
print("风险溢价均值%:",round(ret_rf_mean,4))
|
710
|
+
print("风险溢价标准差%:",round(ret_rf_std,4))
|
711
|
+
print("夏普比率%:",round(sr,4))
|
609
712
|
import datetime as dt; today=dt.date.today()
|
610
713
|
print("*数据来源:新浪/stooq/FRED,"+str(today))
|
611
714
|
|
612
|
-
|
715
|
+
beta=False
|
716
|
+
return sr,ret_rf_mean,beta
|
613
717
|
|
614
718
|
|
615
719
|
if __name__=='__main__':
|
@@ -649,24 +753,28 @@ def sortino_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
649
753
|
return None,None
|
650
754
|
"""
|
651
755
|
|
756
|
+
#获得期间的无风险收益率
|
757
|
+
if isinstance(RF,bool):
|
758
|
+
print(" Searching for risk-free interest rate ...")
|
759
|
+
if scope=='China':
|
760
|
+
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
761
|
+
else:
|
762
|
+
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
763
|
+
if rf_df is None:
|
764
|
+
message=" #Error("+func_name+"): "+"no data available for rf in"
|
765
|
+
print(message,scope,start,end)
|
766
|
+
return None,None
|
767
|
+
RF=rf_df['RF'].mean()
|
768
|
+
|
652
769
|
#抓取日投资组合价格
|
653
|
-
sp=get_portfolio_prices(portfolio,start,end)
|
770
|
+
sp=get_portfolio_prices(portfolio,start,end,RF=RF)
|
771
|
+
ret_pf=sp
|
772
|
+
"""
|
654
773
|
#计算日收益率,表示为百分比
|
655
774
|
import pandas as pd
|
656
775
|
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
657
776
|
ret_pf=ret_pf.dropna()
|
658
777
|
|
659
|
-
#获得期间的无风险收益率
|
660
|
-
print(" Searching for risk-free interest rate ...")
|
661
|
-
if scope=='China':
|
662
|
-
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
663
|
-
else:
|
664
|
-
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
665
|
-
if rf_df is None:
|
666
|
-
message=" #Error("+func_name+"): "+"no data available for rf in"
|
667
|
-
print(message,scope,start,end)
|
668
|
-
return None,None
|
669
|
-
|
670
778
|
#强制转换索引格式,彻底消除下面并表的潜在隐患
|
671
779
|
rf_df['ffdate']=rf_df.index.astype('str')
|
672
780
|
rf_df['ffdate']=pd.to_datetime(rf_df['ffdate'])
|
@@ -675,6 +783,8 @@ def sortino_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
675
783
|
#合并rf_df与ret_pf
|
676
784
|
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
677
785
|
reg['Ret-RF']=reg['Close']-reg['RF']
|
786
|
+
"""
|
787
|
+
reg=ret_pf
|
678
788
|
reg=reg.dropna()
|
679
789
|
|
680
790
|
#计算风险溢价Ret-RF均值和下偏标准差LPSD
|
@@ -692,17 +802,18 @@ def sortino_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
692
802
|
date_end=str(reg.index[-1].year)+'-'+str(reg.index[-1].month)+ \
|
693
803
|
'-'+str(reg.index[-1].day)
|
694
804
|
print("\n===== 风险调整收益率 =====")
|
695
|
-
|
805
|
+
"""
|
696
806
|
_,_,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
|
697
807
|
if len(tickerlist)==1:
|
698
808
|
product=str(ticker_name(tickerlist,'bond'))
|
699
809
|
else:
|
700
810
|
product=str(ticker_name(tickerlist,'bond'))+' by '+str(sharelist)
|
701
|
-
|
702
|
-
print("
|
703
|
-
|
704
|
-
print("
|
705
|
-
print("
|
811
|
+
"""
|
812
|
+
print("证券资产:",portfolio_name(portfolio))
|
813
|
+
print("样本期间:",date_start,"至",date_end,"(可用日期)")
|
814
|
+
print("风险溢价均值%:",round(ret_rf_mean,4))
|
815
|
+
print("下偏标准差%:",round(ret_rf_lpsd,4))
|
816
|
+
print("索替诺比率%:",round(sr,4))
|
706
817
|
|
707
818
|
import datetime as dt; today=dt.date.today()
|
708
819
|
print("*数据来源:新浪/stooq/FRED,"+str(today))
|
@@ -746,24 +857,28 @@ def jensen_alpha_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
746
857
|
return None,None
|
747
858
|
"""
|
748
859
|
|
749
|
-
|
750
|
-
|
860
|
+
#获得期间的无风险收益率
|
861
|
+
if isinstance(RF,bool):
|
862
|
+
print(" Searching for risk-free interest rate ...")
|
863
|
+
if scope=='China':
|
864
|
+
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
865
|
+
else:
|
866
|
+
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
867
|
+
if rf_df is None:
|
868
|
+
message=" #Error("+func_name+"): "+"no data available for rf in"
|
869
|
+
print(message,scope,start,end)
|
870
|
+
return None,None
|
871
|
+
RF=rf_df['RF'].mean()
|
872
|
+
|
873
|
+
#抓取日投资组合价格:内含Mkt-RF和RF
|
874
|
+
sp=get_portfolio_prices(portfolio,start,end,RF=RF)
|
751
875
|
#计算日收益率,表示为百分比
|
876
|
+
ret_pf=sp
|
877
|
+
"""
|
752
878
|
import pandas as pd
|
753
879
|
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
754
880
|
ret_pf=ret_pf.dropna()
|
755
881
|
|
756
|
-
#获得期间的无风险收益率
|
757
|
-
print(" Searching for risk-free interest rate ...")
|
758
|
-
if scope=='China':
|
759
|
-
rf_df=get_mkt_rf_daily_china(mktidx,start,end,rate_period='1Y',rate_type='shibor',RF=RF)
|
760
|
-
else:
|
761
|
-
rf_df=get_rf(start,end,scope=scope,freq='daily')
|
762
|
-
if rf_df is None:
|
763
|
-
message=" #Error("+func_name+"): "+"no data available for rf in"
|
764
|
-
print(message,scope,start,end)
|
765
|
-
return None,None
|
766
|
-
|
767
882
|
#强制转换索引格式,彻底消除下面并表的潜在隐患
|
768
883
|
rf_df['ffdate']=rf_df.index.astype('str')
|
769
884
|
rf_df['ffdate']=pd.to_datetime(rf_df['ffdate'])
|
@@ -779,6 +894,8 @@ def jensen_alpha_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
779
894
|
#合并rf_df与ret_pf
|
780
895
|
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
781
896
|
reg['Ret-RF']=reg['Close']-reg['RF']
|
897
|
+
"""
|
898
|
+
reg=ret_pf
|
782
899
|
reg=reg.dropna()
|
783
900
|
if len(reg) == 0:
|
784
901
|
print(" #Error(jensen_alpha_portfolio): empty data for regression.")
|
@@ -797,21 +914,23 @@ def jensen_alpha_portfolio(portfolio,start,end,RF=True,printout=True):
|
|
797
914
|
date_end=str(reg.index[-1].year)+'-'+str(reg.index[-1].month)+ \
|
798
915
|
'-'+str(reg.index[-1].day)
|
799
916
|
print("\n===== 风险调整收益率 =====")
|
800
|
-
|
917
|
+
"""
|
801
918
|
_,_,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
|
802
919
|
if len(tickerlist)==1:
|
803
920
|
product=str(ticker_name(tickerlist,'bond'))
|
804
921
|
else:
|
805
922
|
product=str(ticker_name(tickerlist,'bond'))+' by '+str(sharelist)
|
806
|
-
|
807
|
-
print("
|
808
|
-
print("
|
809
|
-
print("
|
923
|
+
"""
|
924
|
+
print("证券资产:",portfolio_name(portfolio))
|
925
|
+
print("样本期间:",date_start,"至",date_end,"(可用日期)")
|
926
|
+
print("贝塔系数:",round(beta,4))
|
927
|
+
print("风险溢价均值%:",round(ret_rf_mean,4))
|
928
|
+
print("詹森阿尔法%:",round(alpha,4))
|
810
929
|
|
811
930
|
import datetime as dt; today=dt.date.today()
|
812
931
|
print("*数据来源:新浪/stooq/FRED,"+str(today))
|
813
932
|
|
814
|
-
return alpha,ret_rf_mean
|
933
|
+
return alpha,ret_rf_mean,beta
|
815
934
|
|
816
935
|
|
817
936
|
if __name__=='__main__':
|