siat 3.10.132__py3-none-any.whl → 3.11.1__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/__init__.py +0 -0
- siat/allin.py +8 -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 +4 -4
- siat/common.py +9 -6
- 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/exchange_bond_china.pickle +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.pickle +0 -0
- siat/fund_china.py +0 -0
- siat/future_china.py +0 -0
- siat/google_authenticator.py +0 -0
- siat/grafix.py +55 -4
- siat/holding_risk.py +0 -0
- siat/luchy_draw.py +0 -0
- siat/market_china.py +0 -0
- siat/markowitz.py +0 -0
- siat/markowitz2.py +1 -0
- siat/markowitz2_20250704.py +0 -0
- siat/markowitz2_20250705.py +0 -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 +8 -4
- siat/risk_evaluation.py +0 -0
- siat/risk_free_rate.py +0 -0
- siat/save2docx.py +345 -0
- siat/save2pdf.py +145 -0
- siat/sector_china.py +0 -0
- siat/security_price2.py +0 -0
- siat/security_prices.py +168 -6
- siat/security_trend.py +0 -0
- siat/security_trend2.py +2 -2
- siat/stock.py +11 -1
- siat/stock_advice_linear.py +0 -0
- siat/stock_base.py +0 -0
- siat/stock_china.py +0 -0
- siat/stock_info.pickle +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 +0 -0
- 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.132.dist-info/licenses → siat-3.11.1.dist-info}/LICENSE +0 -0
- {siat-3.10.132.dist-info → siat-3.11.1.dist-info}/METADATA +234 -235
- siat-3.11.1.dist-info/RECORD +80 -0
- {siat-3.10.132.dist-info → siat-3.11.1.dist-info}/WHEEL +1 -1
- {siat-3.10.132.dist-info → siat-3.11.1.dist-info}/top_level.txt +0 -1
- build/lib/build/lib/siat/__init__.py +0 -75
- build/lib/build/lib/siat/allin.py +0 -137
- build/lib/build/lib/siat/assets_liquidity.py +0 -915
- build/lib/build/lib/siat/beta_adjustment.py +0 -1058
- build/lib/build/lib/siat/beta_adjustment_china.py +0 -548
- build/lib/build/lib/siat/blockchain.py +0 -143
- build/lib/build/lib/siat/bond.py +0 -2900
- build/lib/build/lib/siat/bond_base.py +0 -992
- build/lib/build/lib/siat/bond_china.py +0 -100
- build/lib/build/lib/siat/bond_zh_sina.py +0 -143
- build/lib/build/lib/siat/capm_beta.py +0 -783
- build/lib/build/lib/siat/capm_beta2.py +0 -887
- build/lib/build/lib/siat/common.py +0 -5360
- build/lib/build/lib/siat/compare_cross.py +0 -642
- build/lib/build/lib/siat/copyrights.py +0 -18
- build/lib/build/lib/siat/cryptocurrency.py +0 -667
- build/lib/build/lib/siat/economy.py +0 -1471
- build/lib/build/lib/siat/economy2.py +0 -1853
- build/lib/build/lib/siat/esg.py +0 -536
- build/lib/build/lib/siat/event_study.py +0 -815
- build/lib/build/lib/siat/fama_french.py +0 -1521
- build/lib/build/lib/siat/fin_stmt2_yahoo.py +0 -982
- build/lib/build/lib/siat/financial_base.py +0 -1160
- build/lib/build/lib/siat/financial_statements.py +0 -598
- build/lib/build/lib/siat/financials.py +0 -2339
- build/lib/build/lib/siat/financials2.py +0 -1278
- build/lib/build/lib/siat/financials_china.py +0 -4433
- build/lib/build/lib/siat/financials_china2.py +0 -2212
- build/lib/build/lib/siat/fund.py +0 -629
- build/lib/build/lib/siat/fund_china.py +0 -3307
- build/lib/build/lib/siat/future_china.py +0 -551
- build/lib/build/lib/siat/google_authenticator.py +0 -47
- build/lib/build/lib/siat/grafix.py +0 -3636
- build/lib/build/lib/siat/holding_risk.py +0 -867
- build/lib/build/lib/siat/luchy_draw.py +0 -638
- build/lib/build/lib/siat/market_china.py +0 -1168
- build/lib/build/lib/siat/markowitz.py +0 -2363
- build/lib/build/lib/siat/markowitz2.py +0 -3150
- build/lib/build/lib/siat/markowitz2_20250704.py +0 -2969
- build/lib/build/lib/siat/markowitz2_20250705.py +0 -3158
- build/lib/build/lib/siat/markowitz_simple.py +0 -373
- build/lib/build/lib/siat/ml_cases.py +0 -2291
- build/lib/build/lib/siat/ml_cases_example.py +0 -60
- build/lib/build/lib/siat/option_china.py +0 -3069
- build/lib/build/lib/siat/option_pricing.py +0 -1925
- build/lib/build/lib/siat/other_indexes.py +0 -409
- build/lib/build/lib/siat/risk_adjusted_return.py +0 -1576
- build/lib/build/lib/siat/risk_adjusted_return2.py +0 -1900
- build/lib/build/lib/siat/risk_evaluation.py +0 -2218
- build/lib/build/lib/siat/risk_free_rate.py +0 -351
- build/lib/build/lib/siat/sector_china.py +0 -4140
- build/lib/build/lib/siat/security_price2.py +0 -727
- build/lib/build/lib/siat/security_prices.py +0 -3408
- build/lib/build/lib/siat/security_trend.py +0 -402
- build/lib/build/lib/siat/security_trend2.py +0 -646
- build/lib/build/lib/siat/stock.py +0 -4284
- build/lib/build/lib/siat/stock_advice_linear.py +0 -934
- build/lib/build/lib/siat/stock_base.py +0 -26
- build/lib/build/lib/siat/stock_china.py +0 -2095
- build/lib/build/lib/siat/stock_prices_kneighbors.py +0 -910
- build/lib/build/lib/siat/stock_prices_linear.py +0 -386
- build/lib/build/lib/siat/stock_profile.py +0 -707
- build/lib/build/lib/siat/stock_technical.py +0 -3305
- build/lib/build/lib/siat/stooq.py +0 -74
- build/lib/build/lib/siat/transaction.py +0 -347
- build/lib/build/lib/siat/translate.py +0 -5183
- build/lib/build/lib/siat/valuation.py +0 -1378
- build/lib/build/lib/siat/valuation_china.py +0 -2076
- build/lib/build/lib/siat/var_model_validation.py +0 -444
- build/lib/build/lib/siat/yf_name.py +0 -811
- build/lib/siat/__init__.py +0 -75
- build/lib/siat/allin.py +0 -137
- build/lib/siat/assets_liquidity.py +0 -915
- build/lib/siat/beta_adjustment.py +0 -1058
- build/lib/siat/beta_adjustment_china.py +0 -548
- build/lib/siat/blockchain.py +0 -143
- build/lib/siat/bond.py +0 -2900
- build/lib/siat/bond_base.py +0 -992
- build/lib/siat/bond_china.py +0 -100
- build/lib/siat/bond_zh_sina.py +0 -143
- build/lib/siat/capm_beta.py +0 -783
- build/lib/siat/capm_beta2.py +0 -887
- build/lib/siat/common.py +0 -5360
- build/lib/siat/compare_cross.py +0 -642
- build/lib/siat/copyrights.py +0 -18
- build/lib/siat/cryptocurrency.py +0 -667
- build/lib/siat/economy.py +0 -1471
- build/lib/siat/economy2.py +0 -1853
- build/lib/siat/esg.py +0 -536
- build/lib/siat/event_study.py +0 -815
- build/lib/siat/fama_french.py +0 -1521
- build/lib/siat/fin_stmt2_yahoo.py +0 -982
- build/lib/siat/financial_base.py +0 -1160
- build/lib/siat/financial_statements.py +0 -598
- build/lib/siat/financials.py +0 -2339
- build/lib/siat/financials2.py +0 -1278
- build/lib/siat/financials_china.py +0 -4433
- build/lib/siat/financials_china2.py +0 -2212
- build/lib/siat/fund.py +0 -629
- build/lib/siat/fund_china.py +0 -3307
- build/lib/siat/future_china.py +0 -551
- build/lib/siat/google_authenticator.py +0 -47
- build/lib/siat/grafix.py +0 -3636
- build/lib/siat/holding_risk.py +0 -867
- build/lib/siat/luchy_draw.py +0 -638
- build/lib/siat/market_china.py +0 -1168
- build/lib/siat/markowitz.py +0 -2363
- build/lib/siat/markowitz2.py +0 -3150
- build/lib/siat/markowitz2_20250704.py +0 -2969
- build/lib/siat/markowitz2_20250705.py +0 -3158
- build/lib/siat/markowitz_simple.py +0 -373
- build/lib/siat/ml_cases.py +0 -2291
- build/lib/siat/ml_cases_example.py +0 -60
- build/lib/siat/option_china.py +0 -3069
- build/lib/siat/option_pricing.py +0 -1925
- build/lib/siat/other_indexes.py +0 -409
- build/lib/siat/risk_adjusted_return.py +0 -1576
- build/lib/siat/risk_adjusted_return2.py +0 -1900
- build/lib/siat/risk_evaluation.py +0 -2218
- build/lib/siat/risk_free_rate.py +0 -351
- build/lib/siat/sector_china.py +0 -4140
- build/lib/siat/security_price2.py +0 -727
- build/lib/siat/security_prices.py +0 -3408
- build/lib/siat/security_trend.py +0 -402
- build/lib/siat/security_trend2.py +0 -646
- build/lib/siat/stock.py +0 -4284
- build/lib/siat/stock_advice_linear.py +0 -934
- build/lib/siat/stock_base.py +0 -26
- build/lib/siat/stock_china.py +0 -2095
- build/lib/siat/stock_prices_kneighbors.py +0 -910
- build/lib/siat/stock_prices_linear.py +0 -386
- build/lib/siat/stock_profile.py +0 -707
- build/lib/siat/stock_technical.py +0 -3305
- build/lib/siat/stooq.py +0 -74
- build/lib/siat/transaction.py +0 -347
- build/lib/siat/translate.py +0 -5183
- build/lib/siat/valuation.py +0 -1378
- build/lib/siat/valuation_china.py +0 -2076
- build/lib/siat/var_model_validation.py +0 -444
- build/lib/siat/yf_name.py +0 -811
- siat-3.10.132.dist-info/RECORD +0 -218
@@ -1,1576 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
本模块功能:投资组合的风险调整收益率教学插件(算法II)
|
4
|
-
所属工具包:证券投资分析工具SIAT
|
5
|
-
SIAT:Security Investment Analysis Tool
|
6
|
-
创建日期:2018年10月16日
|
7
|
-
最新修订日期:2025年6月20日
|
8
|
-
作者:王德宏 (WANG Dehong, Peter)
|
9
|
-
作者单位:北京外国语大学国际商学院
|
10
|
-
作者邮件:wdehong2000@163.com
|
11
|
-
版权所有:王德宏
|
12
|
-
用途限制:仅限研究与教学使用,不可商用!商用需要额外授权。
|
13
|
-
特别声明:作者不对使用本工具进行证券投资导致的任何损益负责!
|
14
|
-
"""
|
15
|
-
|
16
|
-
#==============================================================================
|
17
|
-
#关闭所有警告
|
18
|
-
import warnings; warnings.filterwarnings('ignore')
|
19
|
-
#==============================================================================
|
20
|
-
from siat.common import *
|
21
|
-
from siat.translate import *
|
22
|
-
from siat.security_prices import *
|
23
|
-
from siat.security_price2 import *
|
24
|
-
from siat.fama_french import *
|
25
|
-
from siat.grafix import *
|
26
|
-
|
27
|
-
import pandas as pd
|
28
|
-
#==============================================================================
|
29
|
-
import matplotlib.pyplot as plt
|
30
|
-
|
31
|
-
#处理绘图汉字乱码问题
|
32
|
-
import sys; czxt=sys.platform
|
33
|
-
if czxt in ['win32','win64']:
|
34
|
-
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置默认字体
|
35
|
-
mpfrc={'font.family': 'SimHei'}
|
36
|
-
|
37
|
-
if czxt in ['darwin']: #MacOSX
|
38
|
-
plt.rcParams['font.family']= ['Heiti TC']
|
39
|
-
mpfrc={'font.family': 'Heiti TC'}
|
40
|
-
|
41
|
-
if czxt in ['linux']: #website Jupyter
|
42
|
-
plt.rcParams['font.family']= ['Heiti TC']
|
43
|
-
mpfrc={'font.family':'Heiti TC'}
|
44
|
-
|
45
|
-
# 解决保存图像时'-'显示为方块的问题
|
46
|
-
plt.rcParams['axes.unicode_minus'] = False
|
47
|
-
#==============================================================================
|
48
|
-
#==============================================================================
|
49
|
-
#==============================================================================
|
50
|
-
def calc_treynor_ratio(regdf):
|
51
|
-
"""
|
52
|
-
功能:计算一项特雷诺指数
|
53
|
-
输入:数据框,至少含有Ret-Rf和Mkt-Rf两项
|
54
|
-
输出:特雷诺指数,Ret-Rf均值
|
55
|
-
"""
|
56
|
-
|
57
|
-
#计算风险溢价Ret-RF均值
|
58
|
-
ret_rf_mean=regdf['Ret-RF'].mean()
|
59
|
-
|
60
|
-
#使用CAPM回归计算投资组合的贝塔系数,这里得到的alpha就是Jensen's alpha
|
61
|
-
from scipy import stats
|
62
|
-
output=stats.linregress(regdf['Mkt-RF'],regdf['Ret-RF'])
|
63
|
-
(beta,alpha,r_value,p_value,std_err)=output
|
64
|
-
|
65
|
-
#计算特雷诺指数
|
66
|
-
tr=ret_rf_mean/beta
|
67
|
-
|
68
|
-
#ret_mean=regdf['Ret%'].mean()
|
69
|
-
rp_mean=ret_rf_mean
|
70
|
-
return tr,rp_mean,beta
|
71
|
-
|
72
|
-
#==============================================================================
|
73
|
-
def calc_alpha_ratio(regdf):
|
74
|
-
"""
|
75
|
-
功能:计算一项詹森阿尔法指数
|
76
|
-
输入:数据框,至少含有Ret-Rf和Mkt-Rf两项
|
77
|
-
输出:詹森阿尔法指数,Ret-Rf均值
|
78
|
-
"""
|
79
|
-
#计算风险溢价Ret-RF均值
|
80
|
-
ret_rf_mean=regdf['Ret-RF'].mean()
|
81
|
-
#使用CAPM回归计算投资组合的贝塔系数,这里得到的alpha就是Jensen's alpha
|
82
|
-
from scipy import stats
|
83
|
-
output=stats.linregress(regdf['Mkt-RF'],regdf['Ret-RF'])
|
84
|
-
(beta,alpha,r_value,p_value,std_err)=output
|
85
|
-
|
86
|
-
rp_mean=ret_rf_mean
|
87
|
-
return alpha,rp_mean,beta
|
88
|
-
|
89
|
-
#==============================================================================
|
90
|
-
def calc_sharpe_ratio(regdf):
|
91
|
-
"""
|
92
|
-
功能:计算一项夏普指数
|
93
|
-
输入:数据框,至少含有Ret-Rf和Mkt-Rf两项
|
94
|
-
输出:夏普指数,Ret-Rf均值
|
95
|
-
"""
|
96
|
-
#计算风险溢价Ret-RF均值和标准差
|
97
|
-
ret_rf_mean=regdf['Ret-RF'].mean()
|
98
|
-
ret_rf_std=regdf['Ret-RF'].std()
|
99
|
-
|
100
|
-
#计算夏普指数
|
101
|
-
sr=ret_rf_mean/ret_rf_std
|
102
|
-
|
103
|
-
rp_mean=ret_rf_mean
|
104
|
-
beta=False
|
105
|
-
return sr,rp_mean,beta
|
106
|
-
|
107
|
-
if __name__=='__main__':
|
108
|
-
rfd=rf_daily_china('2021-10-1','2021-11-28',rate_period='1Y',rate_type='shibor')
|
109
|
-
rfd=rf_daily_china('2021-11-1','2021-11-28',rate_period='3M',rate_type='shibor')
|
110
|
-
|
111
|
-
prices=get_prices('837344.BJ','2021-11-1','2021-11-28')
|
112
|
-
prices['ret_daily']=prices['Close'].pct_change()
|
113
|
-
rp=pd.merge(prices,rfd,how='left',left_index=True,right_index=True)
|
114
|
-
rp['r-rf']=rp['ret_daily']-rp['rf_daily']
|
115
|
-
rp.dropna(inplace=True)
|
116
|
-
sharpe1=rp['r-rf'].mean()/rp['r-rf'].std()
|
117
|
-
sharpe2=rp['ret_daily'].mean()/rp['ret_daily'].std()
|
118
|
-
#==============================================================================
|
119
|
-
def calc_sortino_ratio(regdf):
|
120
|
-
"""
|
121
|
-
功能:计算一项索替诺指数
|
122
|
-
输入:数据框,至少含有Ret-Rf和Mkt-Rf两项
|
123
|
-
输出:索替诺指数,Ret-Rf均值
|
124
|
-
"""
|
125
|
-
|
126
|
-
#计算风险溢价Ret-RF均值和下偏标准差LPSD
|
127
|
-
ret_rf_mean=regdf['Ret-RF'].mean()
|
128
|
-
reg2=regdf[regdf['Ret-RF'] < 0]
|
129
|
-
ret_rf_lpsd=reg2['Ret-RF'].std()
|
130
|
-
|
131
|
-
#计算索梯诺指数
|
132
|
-
sr=ret_rf_mean/ret_rf_lpsd
|
133
|
-
|
134
|
-
rp_mean=ret_rf_mean
|
135
|
-
beta=False
|
136
|
-
return sr,rp_mean,beta
|
137
|
-
|
138
|
-
#==============================================================================
|
139
|
-
def print_rar_ratio(regdf,portfolio,rp_mean,beta,ratio_name,ratio):
|
140
|
-
"""
|
141
|
-
功能:打印风险调整后的收益率
|
142
|
-
输入:数据框,投资组合构成,收益溢价均值,贝塔系数,指数名称,指数
|
143
|
-
输出:打印
|
144
|
-
|
145
|
-
注意:若贝塔系数为False则不打印
|
146
|
-
"""
|
147
|
-
|
148
|
-
#从字典中提取信息
|
149
|
-
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
150
|
-
stocklist1,_=cvt_yftickerlist(stocklist)
|
151
|
-
|
152
|
-
date_start=str(regdf.index[0].year)+'-'+str(regdf.index[0].month)+ \
|
153
|
-
'-'+str(regdf.index[0].day)
|
154
|
-
date_end=str(regdf.index[-1].year)+'-'+str(regdf.index[-1].month)+ \
|
155
|
-
'-'+str(regdf.index[-1].day)
|
156
|
-
print("\n======== 风险调整收益率 ========")
|
157
|
-
print("证券资产:",portfolio_name(portfolio))
|
158
|
-
#print("市场指数:",ectranslate(scope),'\b,',ticker_name(mktidx))
|
159
|
-
print("市场指数:",ticker_name(mktidx))
|
160
|
-
#print("成分股 :",ticker_name(stocklist))
|
161
|
-
#print("持仓权重:",portionlist)
|
162
|
-
print("样本期间:",date_start,"至",date_end)
|
163
|
-
"""
|
164
|
-
print("日均收益率:",round(ret_mean,4),'\b%')
|
165
|
-
annual_ret=(1+ret_mean/100)**252-1
|
166
|
-
print("年化收益率:",round(annual_ret,4))
|
167
|
-
"""
|
168
|
-
if not isinstance(beta,bool):
|
169
|
-
print("贝塔系数:",round(beta,4))
|
170
|
-
|
171
|
-
print("风险溢价均值%:",round(rp_mean,4))
|
172
|
-
|
173
|
-
#print(ratio_name.capitalize(),"\b比率:",round(ratio,4),'\b%')
|
174
|
-
print(ratio_name.capitalize(),"\b比率%:",round(ratio,4))
|
175
|
-
"""
|
176
|
-
print("***投资组合构成:")
|
177
|
-
print_tickerlist_sharelist(stocklist,portionlist,2)
|
178
|
-
"""
|
179
|
-
|
180
|
-
import datetime as dt; todaydt=dt.date.today()
|
181
|
-
print("数据来源:新浪/stooq, "+str(todaydt))
|
182
|
-
|
183
|
-
return
|
184
|
-
#==============================================================================
|
185
|
-
if __name__=='__main__':
|
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
|
191
|
-
|
192
|
-
rate_period='ON'
|
193
|
-
|
194
|
-
def treynor_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
195
|
-
"""
|
196
|
-
功能:按天计算一个投资组合的特雷诺指数
|
197
|
-
投资组合的结构:{'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
198
|
-
输入:投资组合,开始日期,结束日期
|
199
|
-
输出:特雷诺指数
|
200
|
-
"""
|
201
|
-
|
202
|
-
#第1步:各种准备和检查工作
|
203
|
-
#设定错误信息的函数名
|
204
|
-
func_name='treynor_ratio_portfolio'
|
205
|
-
#设定需要计算的指数名称
|
206
|
-
ratio_name="treynor"
|
207
|
-
result,startdate,enddate=check_period(start,end)
|
208
|
-
if not result:
|
209
|
-
message=" #Error("+func_name+"): "+"invalid start or end date:"
|
210
|
-
print(message,start,end)
|
211
|
-
return None,None
|
212
|
-
|
213
|
-
#从字典中提取信息
|
214
|
-
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
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)
|
233
|
-
#计算日收益率,表示为百分比
|
234
|
-
"""
|
235
|
-
import pandas as pd
|
236
|
-
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
237
|
-
ret_pf=ret_pf.dropna()
|
238
|
-
"""
|
239
|
-
ret_pf=sp
|
240
|
-
|
241
|
-
#第4步:合并投资组合日收益率与无风险利率/市场收益率序列
|
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 #日度无风险收益率%
|
249
|
-
reg['Ret-RF']=reg['Close']-reg['RF']
|
250
|
-
"""
|
251
|
-
reg=ret_pf
|
252
|
-
reg=reg.dropna()
|
253
|
-
if len(reg) == 0:
|
254
|
-
message=" #Error("+func_name+"): "+"empty ret-rf data for regression"
|
255
|
-
print(message)
|
256
|
-
return None,None
|
257
|
-
|
258
|
-
#第5步:计算风险调整后的收益率
|
259
|
-
##########风险调整后的收益率,计算开始##########
|
260
|
-
tr,rp_mean,beta=calc_treynor_ratio(reg)
|
261
|
-
##########风险调整后的收益率,计算结束##########
|
262
|
-
|
263
|
-
#第6步:打印结果
|
264
|
-
if printout == True:
|
265
|
-
print_rar_ratio(reg,portfolio,rp_mean,beta,ratio_name,tr)
|
266
|
-
|
267
|
-
return tr,rp_mean
|
268
|
-
|
269
|
-
|
270
|
-
if __name__=='__main__':
|
271
|
-
portfolio1={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
272
|
-
tr1,ret1=treynor_ratio_portfolio(portfolio1,'2019-01-01','2019-01-31')
|
273
|
-
|
274
|
-
|
275
|
-
#==============================================================================
|
276
|
-
if __name__=='__main__':
|
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'
|
283
|
-
|
284
|
-
|
285
|
-
def rar_ratio_portfolio(portfolio,start='MRY',end='today', \
|
286
|
-
indicator='sharpe', \
|
287
|
-
RF=0,printout=True):
|
288
|
-
"""
|
289
|
-
功能:按天计算一个投资组合的风险调整后的收益率指数
|
290
|
-
投资组合的结构:{'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
291
|
-
输入:投资组合,开始日期,结束日期,rar种类
|
292
|
-
输出:风险调整后的收益率指数
|
293
|
-
"""
|
294
|
-
ratio_name=indicator
|
295
|
-
|
296
|
-
#第1步:各种准备和检查工作
|
297
|
-
#设定错误信息的函数名
|
298
|
-
func_name='rar_ratio_portfolio'
|
299
|
-
|
300
|
-
ratio_name=ratio_name.lower()
|
301
|
-
ratio_list=['treynor','sharpe','sortino','alpha']
|
302
|
-
if ratio_name not in ratio_list:
|
303
|
-
message=" #Error("+func_name+"): "+"unsupported rar ratio type"
|
304
|
-
print(message)
|
305
|
-
return None,None
|
306
|
-
|
307
|
-
start,end=start_end_preprocess(start,end)
|
308
|
-
result,startdate,enddate=check_period(start,end)
|
309
|
-
if not result:
|
310
|
-
message=" #Error("+func_name+"): "+"invalid start or end date"
|
311
|
-
print(message,start,end)
|
312
|
-
return None,None
|
313
|
-
|
314
|
-
print(f" Calculating {ratio_name} ratio ...")
|
315
|
-
#从字典中提取信息
|
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
|
348
|
-
|
349
|
-
#抓取日投资组合价格
|
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
|
-
"""
|
359
|
-
#计算日收益率,表示为百分比
|
360
|
-
import pandas as pd
|
361
|
-
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
362
|
-
ret_pf=ret_pf.dropna()
|
363
|
-
|
364
|
-
#第4步:合并投资组合日收益率与无风险利率/市场收益率序列
|
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
|
-
|
373
|
-
reg['Ret-RF']=reg['Close']-reg['RF']
|
374
|
-
"""
|
375
|
-
reg=sp
|
376
|
-
reg=reg.dropna()
|
377
|
-
if len(reg) == 0:
|
378
|
-
message=" #Error("+func_name+"): "+"empty data for ratio calculation"
|
379
|
-
print(message)
|
380
|
-
return None,None
|
381
|
-
|
382
|
-
#第4步:计算风险调整后的收益率
|
383
|
-
##########风险调整后的收益率,计算开始##########
|
384
|
-
calc_func='calc_'+ratio_name+'_ratio'
|
385
|
-
rar,rp_mean,beta=eval(calc_func)(reg)
|
386
|
-
##########风险调整后的收益率,计算结束##########
|
387
|
-
|
388
|
-
#第5步:打印结果
|
389
|
-
if printout == True:
|
390
|
-
print_rar_ratio(reg,portfolio,rp_mean,beta,ratio_name,rar)
|
391
|
-
|
392
|
-
return rar,rp_mean
|
393
|
-
|
394
|
-
|
395
|
-
if __name__=='__main__':
|
396
|
-
pf1={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
397
|
-
tr1,rp1=rar_ratio_portfolio(pf1,'2019-01-01','2019-01-31',ratio_name='treynor')
|
398
|
-
|
399
|
-
#==============================================================================
|
400
|
-
if __name__=='__main__':
|
401
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':1}
|
402
|
-
start='2019-12-1'
|
403
|
-
end ='2021-1-31'
|
404
|
-
scope='US'
|
405
|
-
ratio_name='sharpe'
|
406
|
-
window=30
|
407
|
-
graph=True
|
408
|
-
|
409
|
-
def rar_ratio_rolling(portfolio,start='MRY',end='today',indicator='sharpe',RF=0, \
|
410
|
-
window=21,graph=True,source='auto'):
|
411
|
-
"""
|
412
|
-
功能:滚动计算一个投资组合的风险调整后的收益率指数
|
413
|
-
投资组合的结构:{'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
414
|
-
输入:投资组合,开始日期,结束日期,指数名称,滚动窗口宽度(天数)
|
415
|
-
输出:风险调整后的收益率指数序列
|
416
|
-
注意:因需要滚动计算,开始和结束日期之间需要拉开距离,提前的月数为window/21取整+1;
|
417
|
-
另外,无风率可用数据可能距离当前日期滞后约两个月
|
418
|
-
|
419
|
-
注意:当RF=False时有bug
|
420
|
-
"""
|
421
|
-
start,end=start_end_preprocess(start,end)
|
422
|
-
|
423
|
-
ratio_name=indicator.lower()
|
424
|
-
|
425
|
-
#第1步:各种准备和检查工作
|
426
|
-
print(" Start to calculate rar ratios, please wait ...")
|
427
|
-
#设定错误信息的函数名
|
428
|
-
func_name='rar_ratio_portfolio'
|
429
|
-
|
430
|
-
ratio_list=['treynor','sharpe','sortino','alpha']
|
431
|
-
if ratio_name not in ratio_list:
|
432
|
-
message=" #Error("+func_name+"): "+"unsupported rar ratio type"
|
433
|
-
print(message,ratio_name)
|
434
|
-
return None
|
435
|
-
|
436
|
-
result,startdate,enddate=check_period(start,end)
|
437
|
-
if not result:
|
438
|
-
message=" #Error("+func_name+"): "+"invalid start or end date"
|
439
|
-
print(message,start,end)
|
440
|
-
return None
|
441
|
-
#估算数据提前量,重设开始日历日期
|
442
|
-
#startdate_delta=int(window/20*30)+30
|
443
|
-
startdate_delta=int(window/20*31)
|
444
|
-
startdate1=date_adjust(startdate, adjust=-startdate_delta)
|
445
|
-
|
446
|
-
#从字典中提取信息
|
447
|
-
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
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步:计算投资组合的日收益率序列
|
472
|
-
#抓取日投资组合价格
|
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
|
-
"""
|
481
|
-
#计算日收益率,表示为百分比
|
482
|
-
import pandas as pd
|
483
|
-
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
484
|
-
ret_pf=ret_pf.dropna()
|
485
|
-
|
486
|
-
#第4步:合并投资组合日收益率与无风险利率/市场收益率序列
|
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
|
494
|
-
|
495
|
-
reg['Ret-RF']=reg['Close']-reg['RF']
|
496
|
-
"""
|
497
|
-
reg=sp
|
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
|
503
|
-
|
504
|
-
#第4步:滚动计算风险调整后的收益率
|
505
|
-
##########风险调整后的收益率,计算开始##########
|
506
|
-
#用于保存rar和ret_rf_mean
|
507
|
-
import pandas as pd
|
508
|
-
import numpy as np
|
509
|
-
datelist=reg.index.to_list()
|
510
|
-
calc_func='calc_'+ratio_name+'_ratio'
|
511
|
-
|
512
|
-
rars=pd.DataFrame(columns=('Date','RAR','Mean(Ret)'))
|
513
|
-
for i in np.arange(0,len(reg)):
|
514
|
-
i1=i+window-1
|
515
|
-
if i1 >= len(reg): break
|
516
|
-
|
517
|
-
#构造滚动窗口
|
518
|
-
windf=reg[reg.index >= datelist[i]]
|
519
|
-
windf=windf[windf.index <= datelist[i1]]
|
520
|
-
#print(i,datelist[i],i1,datelist[i1],len(windf))
|
521
|
-
|
522
|
-
#使用滚动窗口计算
|
523
|
-
try:
|
524
|
-
rar,ret_mean=eval(calc_func)(windf)
|
525
|
-
except:
|
526
|
-
print(" #Error(rar_ratio_rolling): failed in linear regression for",calc_func)
|
527
|
-
#print(" windf:\n",windf)
|
528
|
-
continue
|
529
|
-
|
530
|
-
#记录计算结果
|
531
|
-
row=pd.Series({'Date':datelist[i1],'RAR':rar,'Mean(Ret)':ret_mean})
|
532
|
-
try:
|
533
|
-
rars=rars.append(row,ignore_index=True)
|
534
|
-
except:
|
535
|
-
# 可能与Python 3.11有关,不确定
|
536
|
-
rars=rars._append(row,ignore_index=True)
|
537
|
-
|
538
|
-
rars.set_index(['Date'],inplace=True)
|
539
|
-
##########风险调整后的收益率,计算结束##########
|
540
|
-
|
541
|
-
#第5步:绘图
|
542
|
-
if graph == True:
|
543
|
-
print(" Rendering graphics ...")
|
544
|
-
draw_rar_ratio(rars,portfolio,ratio_name,pname)
|
545
|
-
|
546
|
-
return rars
|
547
|
-
|
548
|
-
|
549
|
-
if __name__=='__main__':
|
550
|
-
pf1={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
551
|
-
rars1=rar_ratio_rolling(pf1,'2020-1-1','2020-12-31',ratio_name='sharpe')
|
552
|
-
#==============================================================================
|
553
|
-
def draw_rar_ratio(rars,portfolio,ratio_name,pname):
|
554
|
-
"""
|
555
|
-
功能:绘制滚动窗口曲线
|
556
|
-
输入:滚动数据df,投资组合,指数名称
|
557
|
-
"""
|
558
|
-
|
559
|
-
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
560
|
-
stocklist1,_=cvt_yftickerlist(stocklist)
|
561
|
-
|
562
|
-
"""
|
563
|
-
#平滑处理
|
564
|
-
rars1=rars.resample('H')
|
565
|
-
#rars2=rars1.interpolate(method='pchip')
|
566
|
-
#rars2=rars1.interpolate(method='akima')
|
567
|
-
rars2=rars1.interpolate(method='cubic')
|
568
|
-
"""
|
569
|
-
|
570
|
-
#plt.figure(figsize=(12.8,6.4))
|
571
|
-
|
572
|
-
labeltxt=ratio_name.capitalize()+'指标'
|
573
|
-
plt.plot(rars['RAR'],label=labeltxt,color='red',lw=1)
|
574
|
-
#plt.plot(rars['Mean(Ret)'],label='Stock(s) return(%)',color='blue',lw=1)
|
575
|
-
plt.axhline(y=0.0,color='black',linestyle=':')
|
576
|
-
"""
|
577
|
-
titletxt='风险调整收益的滚动趋势'+'\n'+str(ticker_name(stocklist))
|
578
|
-
if len(stocklist) > 1:
|
579
|
-
titletxt=titletxt+'\n持仓比例: '+str(portionlist)
|
580
|
-
"""
|
581
|
-
titletxt='风险调整收益的滚动趋势:'+pname
|
582
|
-
"""
|
583
|
-
if len(stocklist) == 1:
|
584
|
-
titletxt='风险调整收益的滚动趋势'+'\n('+ticker_name(stocklist)+')'
|
585
|
-
"""
|
586
|
-
#plt.title(titletxt,fontsize=12,fontweight='bold')
|
587
|
-
plt.title(titletxt,fontsize=12)
|
588
|
-
|
589
|
-
#ylabeltxt="比率/指数"
|
590
|
-
#plt.ylabel(ylabeltxt,fontsize=12)
|
591
|
-
#plt.xticks(rotation=45,fontsize=9)
|
592
|
-
plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
|
593
|
-
plt.gca().set_facecolor('whitesmoke')
|
594
|
-
#plt.xticks(rotation=30,fontsize=8)
|
595
|
-
plt.legend(loc='best')
|
596
|
-
|
597
|
-
import datetime as dt; today=dt.date.today()
|
598
|
-
footnote="数据来源:新浪/stooq/FRED,"+str(today)
|
599
|
-
plt.xlabel(footnote)
|
600
|
-
plt.show()
|
601
|
-
|
602
|
-
#使用seaborn绘图
|
603
|
-
"""
|
604
|
-
import seaborn as sns
|
605
|
-
with sns.axes_style("whitegrid"):
|
606
|
-
fig, ax = plt.subplots(figsize=(12.8,6.4))
|
607
|
-
ax.plot(rars['RAR'],label=labeltxt,color='red',lw=3)
|
608
|
-
#ax.plot(rars['Mean(Ret)'],label='Stock(s) return(%)',color='blue',lw=1)
|
609
|
-
plt.axhline(y=0.0,label='Zero return',color='black',linestyle=':')
|
610
|
-
ax.set_title(titletxt)
|
611
|
-
#ax.set_ylabel(ylabeltxt)
|
612
|
-
plt.xticks(rotation=45)
|
613
|
-
ax.legend(loc='best')
|
614
|
-
ax.set_ylim([1.2*(rars['RAR'].min()), 1.1*(rars['RAR'].max())])
|
615
|
-
"""
|
616
|
-
return
|
617
|
-
#==============================================================================
|
618
|
-
if __name__=='__main__':
|
619
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':0.2,'MSFT':0.6,'IBM':0.2}
|
620
|
-
start='2019-01-01'
|
621
|
-
end ='2019-01-31'
|
622
|
-
|
623
|
-
|
624
|
-
def sharpe_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
625
|
-
"""
|
626
|
-
功能:按天计算一个投资组合的夏普指数
|
627
|
-
投资组合的结构:{'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
628
|
-
输入:投资组合,开始日期,结束日期
|
629
|
-
输出:夏普指数
|
630
|
-
"""
|
631
|
-
#设定错误信息的函数名
|
632
|
-
func_name='sharpe_ratio_portfolio'
|
633
|
-
|
634
|
-
#检查日期期间的合理性
|
635
|
-
valid,start1,end1=check_period(start,end)
|
636
|
-
if not valid:
|
637
|
-
print(" #Error(sharpe_ratio_portfolio): incorrect start/end date(s)",start,end)
|
638
|
-
return None,None
|
639
|
-
|
640
|
-
#从字典中提取信息
|
641
|
-
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
642
|
-
|
643
|
-
#检查份额配比是否合理
|
644
|
-
"""
|
645
|
-
if round(sum(portionlist),1) != 1.0:
|
646
|
-
print(" #Error(sharpe_ratio_portfolio): Incorrect total of portions")
|
647
|
-
return None,None
|
648
|
-
"""
|
649
|
-
|
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)
|
665
|
-
#计算日收益率,表示为百分比
|
666
|
-
"""
|
667
|
-
import pandas as pd
|
668
|
-
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
669
|
-
"""
|
670
|
-
ret_pf=sp
|
671
|
-
ret_pf=ret_pf.dropna()
|
672
|
-
|
673
|
-
#强制转换索引格式,彻底消除下面并表的潜在隐患
|
674
|
-
"""
|
675
|
-
rf_df['ffdate']=rf_df.index.astype('str')
|
676
|
-
rf_df['ffdate']=pd.to_datetime(rf_df['ffdate'])
|
677
|
-
rf_df.set_index(['ffdate'],inplace=True)
|
678
|
-
"""
|
679
|
-
"""
|
680
|
-
#合并rf_df与ret_pf
|
681
|
-
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
682
|
-
reg['Ret-RF']=reg['Close']-reg['RF']
|
683
|
-
reg=reg.dropna()
|
684
|
-
"""
|
685
|
-
#计算风险溢价Ret-RF均值和标准差
|
686
|
-
reg=ret_pf
|
687
|
-
ret_rf_mean=reg['Ret-RF'].mean()
|
688
|
-
ret_rf_std=reg['Ret-RF'].std()
|
689
|
-
|
690
|
-
#计算夏普指数
|
691
|
-
sr=ret_rf_mean/ret_rf_std
|
692
|
-
|
693
|
-
#打印报告
|
694
|
-
if printout == True:
|
695
|
-
date_start=str(reg.index[0].year)+'-'+str(reg.index[0].month)+ \
|
696
|
-
'-'+str(reg.index[0].day)
|
697
|
-
date_end=str(reg.index[-1].year)+'-'+str(reg.index[-1].month)+ \
|
698
|
-
'-'+str(reg.index[-1].day)
|
699
|
-
print("\n===== 风险调整收益率 =====")
|
700
|
-
"""
|
701
|
-
_,_,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
|
702
|
-
if len(tickerlist)==1:
|
703
|
-
product=str(ticker_name(tickerlist,'bond'))
|
704
|
-
else:
|
705
|
-
product=str(ticker_name(tickerlist,'bond'))+' by '+str(sharelist)
|
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))
|
712
|
-
import datetime as dt; today=dt.date.today()
|
713
|
-
print("*数据来源:新浪/stooq/FRED,"+str(today))
|
714
|
-
|
715
|
-
beta=False
|
716
|
-
return sr,ret_rf_mean,beta
|
717
|
-
|
718
|
-
|
719
|
-
if __name__=='__main__':
|
720
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':0.2,'MSFT':0.5,'IBM':0.3}
|
721
|
-
sr1,rp1=sharpe_ratio_portfolio(portfolio,'2019-01-01','2019-01-31')
|
722
|
-
|
723
|
-
|
724
|
-
#==============================================================================
|
725
|
-
if __name__=='__main__':
|
726
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
727
|
-
start='2019-01-01'
|
728
|
-
end ='2019-01-31'
|
729
|
-
|
730
|
-
def sortino_ratio_portfolio(portfolio,start,end,RF=True,printout=True):
|
731
|
-
"""
|
732
|
-
功能:按天计算一个投资组合的索梯诺指数
|
733
|
-
投资组合的结构:{'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
734
|
-
输入:投资组合,开始日期,结束日期
|
735
|
-
输出:索梯诺指数
|
736
|
-
"""
|
737
|
-
#设定错误信息的函数名
|
738
|
-
func_name='sortino_ratio_portfolio'
|
739
|
-
|
740
|
-
#检查日期期间的合理性
|
741
|
-
valid,start1,end1=check_period(start,end)
|
742
|
-
if not valid:
|
743
|
-
print(" #Error(sortino_ratio_portfolio): incorrect start/end date(s)")
|
744
|
-
return None,None
|
745
|
-
|
746
|
-
#从字典中提取信息
|
747
|
-
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
748
|
-
|
749
|
-
#检查份额配比是否合理
|
750
|
-
"""
|
751
|
-
if round(sum(portionlist),1) != 1.0:
|
752
|
-
print(" #Error(sortino_ratio_portfolio): Incorrect total of portions")
|
753
|
-
return None,None
|
754
|
-
"""
|
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
|
-
|
769
|
-
#抓取日投资组合价格
|
770
|
-
sp=get_portfolio_prices(portfolio,start,end,RF=RF)
|
771
|
-
ret_pf=sp
|
772
|
-
"""
|
773
|
-
#计算日收益率,表示为百分比
|
774
|
-
import pandas as pd
|
775
|
-
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
776
|
-
ret_pf=ret_pf.dropna()
|
777
|
-
|
778
|
-
#强制转换索引格式,彻底消除下面并表的潜在隐患
|
779
|
-
rf_df['ffdate']=rf_df.index.astype('str')
|
780
|
-
rf_df['ffdate']=pd.to_datetime(rf_df['ffdate'])
|
781
|
-
rf_df.set_index(['ffdate'],inplace=True)
|
782
|
-
|
783
|
-
#合并rf_df与ret_pf
|
784
|
-
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
785
|
-
reg['Ret-RF']=reg['Close']-reg['RF']
|
786
|
-
"""
|
787
|
-
reg=ret_pf
|
788
|
-
reg=reg.dropna()
|
789
|
-
|
790
|
-
#计算风险溢价Ret-RF均值和下偏标准差LPSD
|
791
|
-
ret_rf_mean=reg['Ret-RF'].mean()
|
792
|
-
reg2=reg[reg['Ret-RF'] < 0]
|
793
|
-
ret_rf_lpsd=reg2['Ret-RF'].std()
|
794
|
-
|
795
|
-
#计算索梯诺指数
|
796
|
-
sr=ret_rf_mean/ret_rf_lpsd
|
797
|
-
|
798
|
-
#打印报告
|
799
|
-
if printout == True:
|
800
|
-
date_start=str(reg.index[0].year)+'-'+str(reg.index[0].month)+ \
|
801
|
-
'-'+str(reg.index[0].day)
|
802
|
-
date_end=str(reg.index[-1].year)+'-'+str(reg.index[-1].month)+ \
|
803
|
-
'-'+str(reg.index[-1].day)
|
804
|
-
print("\n===== 风险调整收益率 =====")
|
805
|
-
"""
|
806
|
-
_,_,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
|
807
|
-
if len(tickerlist)==1:
|
808
|
-
product=str(ticker_name(tickerlist,'bond'))
|
809
|
-
else:
|
810
|
-
product=str(ticker_name(tickerlist,'bond'))+' by '+str(sharelist)
|
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))
|
817
|
-
|
818
|
-
import datetime as dt; today=dt.date.today()
|
819
|
-
print("*数据来源:新浪/stooq/FRED,"+str(today))
|
820
|
-
|
821
|
-
return sr,ret_rf_mean
|
822
|
-
|
823
|
-
|
824
|
-
if __name__=='__main__':
|
825
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
826
|
-
sr1,rp1=sortino_ratio_portfolio(portfolio,'2019-01-01','2019-08-03')
|
827
|
-
|
828
|
-
#==============================================================================
|
829
|
-
if __name__=='__main__':
|
830
|
-
portfolio={'Market':('China','000001.SS'),'600519.SS':1.0}
|
831
|
-
start='2019-01-01'
|
832
|
-
end ='2019-01-31'
|
833
|
-
|
834
|
-
|
835
|
-
def jensen_alpha_portfolio(portfolio,start,end,RF=True,printout=True):
|
836
|
-
"""
|
837
|
-
功能:按天计算一个投资组合的阿尔法指数
|
838
|
-
投资组合的结构:{'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
839
|
-
输入:投资组合,开始日期,结束日期
|
840
|
-
输出:阿尔法指数
|
841
|
-
"""
|
842
|
-
#设定错误信息的函数名
|
843
|
-
func_name='jensen_alpha_portfolio'
|
844
|
-
|
845
|
-
#检查日期期间的合理性
|
846
|
-
valid,start1,end1=check_period(start,end)
|
847
|
-
if not valid:
|
848
|
-
print(" #Error(jensen_alpha_portfolio): incorrect start/end date(s)")
|
849
|
-
return None,None
|
850
|
-
|
851
|
-
#从字典中提取信息
|
852
|
-
scope,mktidx,stocklist,portionlist,ticker_type=decompose_portfolio(portfolio)
|
853
|
-
#检查份额配比是否合理
|
854
|
-
"""
|
855
|
-
if round(sum(portionlist),1) != 1.0:
|
856
|
-
print(" #Error(jensen_alpha_portfolio): incorrect total of portions.")
|
857
|
-
return None,None
|
858
|
-
"""
|
859
|
-
|
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)
|
875
|
-
#计算日收益率,表示为百分比
|
876
|
-
ret_pf=sp
|
877
|
-
"""
|
878
|
-
import pandas as pd
|
879
|
-
ret_pf=pd.DataFrame(sp['Close'].pct_change())*100.0
|
880
|
-
ret_pf=ret_pf.dropna()
|
881
|
-
|
882
|
-
#强制转换索引格式,彻底消除下面并表的潜在隐患
|
883
|
-
rf_df['ffdate']=rf_df.index.astype('str')
|
884
|
-
rf_df['ffdate']=pd.to_datetime(rf_df['ffdate'])
|
885
|
-
rf_df.set_index(['ffdate'],inplace=True)
|
886
|
-
|
887
|
-
if rf_df is None:
|
888
|
-
print(" #Error(jensen_alpha_portfolio): data source did not respond.")
|
889
|
-
return None,None
|
890
|
-
if len(rf_df) == 0:
|
891
|
-
print(" #Error(jensen_alpha_portfolio): data source returned empty data.")
|
892
|
-
return None,None
|
893
|
-
|
894
|
-
#合并rf_df与ret_pf
|
895
|
-
reg=pd.merge(ret_pf,rf_df,how='inner',left_index=True,right_index=True)
|
896
|
-
reg['Ret-RF']=reg['Close']-reg['RF']
|
897
|
-
"""
|
898
|
-
reg=ret_pf
|
899
|
-
reg=reg.dropna()
|
900
|
-
if len(reg) == 0:
|
901
|
-
print(" #Error(jensen_alpha_portfolio): empty data for regression.")
|
902
|
-
return None,None
|
903
|
-
ret_rf_mean=reg['Ret-RF'].mean()
|
904
|
-
|
905
|
-
#使用CAPM回归计算投资组合的贝塔系数,这里得到的alpha就是Jensen's alpha
|
906
|
-
from scipy import stats
|
907
|
-
output=stats.linregress(reg['Mkt-RF'],reg['Ret-RF'])
|
908
|
-
(beta,alpha,r_value,p_value,std_err)=output
|
909
|
-
|
910
|
-
#打印报告
|
911
|
-
if printout == True:
|
912
|
-
date_start=str(reg.index[0].year)+'-'+str(reg.index[0].month)+ \
|
913
|
-
'-'+str(reg.index[0].day)
|
914
|
-
date_end=str(reg.index[-1].year)+'-'+str(reg.index[-1].month)+ \
|
915
|
-
'-'+str(reg.index[-1].day)
|
916
|
-
print("\n===== 风险调整收益率 =====")
|
917
|
-
"""
|
918
|
-
_,_,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
|
919
|
-
if len(tickerlist)==1:
|
920
|
-
product=str(ticker_name(tickerlist,'bond'))
|
921
|
-
else:
|
922
|
-
product=str(ticker_name(tickerlist,'bond'))+' by '+str(sharelist)
|
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))
|
929
|
-
|
930
|
-
import datetime as dt; today=dt.date.today()
|
931
|
-
print("*数据来源:新浪/stooq/FRED,"+str(today))
|
932
|
-
|
933
|
-
return alpha,ret_rf_mean,beta
|
934
|
-
|
935
|
-
|
936
|
-
if __name__=='__main__':
|
937
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':0.5,'MSFT':0.3,'IBM':0.2}
|
938
|
-
alpha1=jensen_alpha_portfolio(portfolio,'2019-01-01','2019-08-03')
|
939
|
-
|
940
|
-
#==============================================================================
|
941
|
-
def calc_monthly_date_range(start,end):
|
942
|
-
"""
|
943
|
-
功能:返回两个日期之间各个月份的开始和结束日期
|
944
|
-
输入:开始/结束日期
|
945
|
-
输出:两个日期之间各个月份的开始和结束日期元组对列表
|
946
|
-
"""
|
947
|
-
#测试用
|
948
|
-
#start='2019-01-05'
|
949
|
-
#end='2019-06-25'
|
950
|
-
|
951
|
-
import pandas as pd
|
952
|
-
startdate=pd.to_datetime(start)
|
953
|
-
enddate=pd.to_datetime(end)
|
954
|
-
|
955
|
-
mdlist=[]
|
956
|
-
#当月的结束日期
|
957
|
-
syear=startdate.year
|
958
|
-
smonth=startdate.month
|
959
|
-
import calendar
|
960
|
-
sdays=calendar.monthrange(syear,smonth)[1]
|
961
|
-
from datetime import date
|
962
|
-
slastday=pd.to_datetime(date(syear,smonth,sdays))
|
963
|
-
|
964
|
-
if slastday > enddate: slastday=enddate
|
965
|
-
|
966
|
-
#加入第一月的开始和结束日期
|
967
|
-
import bisect
|
968
|
-
bisect.insort(mdlist,(startdate,slastday))
|
969
|
-
|
970
|
-
#加入结束月的开始和结束日期
|
971
|
-
eyear=enddate.year
|
972
|
-
emonth=enddate.month
|
973
|
-
efirstday=pd.to_datetime(date(eyear,emonth,1))
|
974
|
-
if startdate < efirstday:
|
975
|
-
bisect.insort(mdlist,(efirstday,enddate))
|
976
|
-
|
977
|
-
#加入期间内各个月份的开始和结束日期
|
978
|
-
from dateutil.relativedelta import relativedelta
|
979
|
-
next=startdate+relativedelta(months=+1)
|
980
|
-
while next < efirstday:
|
981
|
-
nyear=next.year
|
982
|
-
nmonth=next.month
|
983
|
-
nextstart=pd.to_datetime(date(nyear,nmonth,1))
|
984
|
-
ndays=calendar.monthrange(nyear,nmonth)[1]
|
985
|
-
nextend=pd.to_datetime(date(nyear,nmonth,ndays))
|
986
|
-
bisect.insort(mdlist,(nextstart,nextend))
|
987
|
-
next=next+relativedelta(months=+1)
|
988
|
-
|
989
|
-
return mdlist
|
990
|
-
|
991
|
-
if __name__=='__main__':
|
992
|
-
mdp1=calc_monthly_date_range('2019-01-01','2019-06-30')
|
993
|
-
mdp2=calc_monthly_date_range('2000-01-01','2000-06-30') #闰年
|
994
|
-
mdp3=calc_monthly_date_range('2018-09-01','2019-03-31') #跨年
|
995
|
-
|
996
|
-
for i in range(0,len(mdp1)):
|
997
|
-
start=mdp1[i][0]
|
998
|
-
end=mdp1[i][1]
|
999
|
-
print("start =",start,"end =",end)
|
1000
|
-
|
1001
|
-
|
1002
|
-
#==============================================================================
|
1003
|
-
if __name__=='__main__':
|
1004
|
-
portfolio={'Market':('US','^GSPC'),'JD':0.3,'BABA':0.7}
|
1005
|
-
start='2019-01-01'
|
1006
|
-
end='2019-03-31'
|
1007
|
-
rar_type='sortino_ratio'
|
1008
|
-
|
1009
|
-
def plot_rar_monthly(portfolio,start,end,rar_type):
|
1010
|
-
"""
|
1011
|
-
功能:将风险调整收益率和风险溢价逐月绘图对比
|
1012
|
-
输入:投资组合,开始/结束日期,风险调整收益指数类别
|
1013
|
-
输出:风险调整收益率和风险溢价的逐月数据框
|
1014
|
-
显示:按月绘图投资组合的风险调整收益率和风险溢价
|
1015
|
-
"""
|
1016
|
-
|
1017
|
-
#检查日期期间的合理性
|
1018
|
-
valid,start1,end1=check_period(start,end)
|
1019
|
-
if not valid:
|
1020
|
-
print(" #Error(plot_rar_monthly): incorrect start/end date(s)",start,end)
|
1021
|
-
return None
|
1022
|
-
|
1023
|
-
#检查投资组合各个成分股份额的合理性
|
1024
|
-
"""
|
1025
|
-
if round(sum(portionlist),1) != 1.0:
|
1026
|
-
print(" #Error(plot_rar_monthly): Incorrect total of portions")
|
1027
|
-
return None
|
1028
|
-
"""
|
1029
|
-
|
1030
|
-
#检查支持的rar_type
|
1031
|
-
rar_list=['treynor_ratio','sharpe_ratio','sortino_ratio','jensen_alpha']
|
1032
|
-
if rar_type not in rar_list:
|
1033
|
-
print(" #Error(plot_rar_monthly): not supported rar type")
|
1034
|
-
print(" Supported rar type:",rar_list)
|
1035
|
-
return None
|
1036
|
-
|
1037
|
-
#拆分start/end之间的各个年份和月份
|
1038
|
-
mdlist=calc_monthly_date_range(start,end)
|
1039
|
-
if len(mdlist) == 0:
|
1040
|
-
print(" #Error(plot_rar_monthly): start/end dates inappropriate",start,end)
|
1041
|
-
return None
|
1042
|
-
|
1043
|
-
#用于保存risk premium和rar
|
1044
|
-
print("\n Calculating monthly",rar_type,"......")
|
1045
|
-
rarfunc=rar_type+'_portfolio'
|
1046
|
-
rars=pd.DataFrame(columns=('YM','rp','rar'))
|
1047
|
-
for i in range(0,len(mdlist)):
|
1048
|
-
start=mdlist[i][0]
|
1049
|
-
YM=start.strftime("%Y-%m")
|
1050
|
-
print(' ',YM,end=' ')
|
1051
|
-
end=mdlist[i][1]
|
1052
|
-
rar,rp=eval(rarfunc)(portfolio,start,end,printout=False)
|
1053
|
-
|
1054
|
-
row=pd.Series({'YM':YM,'rp':rp,'rar':rar})
|
1055
|
-
try:
|
1056
|
-
rars=rars.append(row,ignore_index=True)
|
1057
|
-
except:
|
1058
|
-
rars=rars._append(row,ignore_index=True)
|
1059
|
-
print(" Searching completed.")
|
1060
|
-
rars.set_index('YM',inplace=True)
|
1061
|
-
|
1062
|
-
#绘图
|
1063
|
-
plt.plot(rars['rp'],label='risk_premium',c='blue',marker='*',ls=':',lw=3)
|
1064
|
-
plt.plot(rars['rar'],label=rar_type,c='r',lw=3,marker='o')
|
1065
|
-
plt.axhline(y=0.0,color='black',linestyle=':',lw=1)
|
1066
|
-
titletxt="投资组合的风险调整收益"
|
1067
|
-
plt.title(titletxt)
|
1068
|
-
plt.ylabel('收益率(%)')
|
1069
|
-
|
1070
|
-
plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
|
1071
|
-
plt.gca().set_facecolor('whitesmoke')
|
1072
|
-
|
1073
|
-
#plt.xticks(rotation=30)
|
1074
|
-
plt.legend(loc='best')
|
1075
|
-
|
1076
|
-
import datetime as dt; today=dt.date.today()
|
1077
|
-
footnote="数据来源:新浪/stooq/FRED,"+str(today)
|
1078
|
-
plt.xlabel(footnote)
|
1079
|
-
|
1080
|
-
plt.show()
|
1081
|
-
|
1082
|
-
return
|
1083
|
-
|
1084
|
-
|
1085
|
-
if __name__=='__main__':
|
1086
|
-
portfolio={'Market':('US','^GSPC'),'VIPS':0.1,'PDD':0.2,'JD':0.3,'BABA':0.4}
|
1087
|
-
plot_rar_monthly(portfolio,'2019-01-01','2019-06-30','treynor_ratio')
|
1088
|
-
|
1089
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':1.0}
|
1090
|
-
plot_rar_monthly(portfolio,'2017-01-01','2017-12-31','sharpe_ratio')
|
1091
|
-
#==============================================================================
|
1092
|
-
#==============================================================================
|
1093
|
-
if __name__=='__main__':
|
1094
|
-
portfolio={'Market':('US','^GSPC'),'JD':0.3,'BABA':0.7}
|
1095
|
-
start='2013-01-01'
|
1096
|
-
end='2018-12-31'
|
1097
|
-
rar_type='sortino_ratio'
|
1098
|
-
|
1099
|
-
|
1100
|
-
def plot_rar_annual(portfolio,start,end,rar_type):
|
1101
|
-
"""
|
1102
|
-
功能:将风险调整收益率和风险溢价逐年绘图对比
|
1103
|
-
输入:投资组合,开始/结束日期,风险调整收益指数类别
|
1104
|
-
输出:风险调整收益率和风险溢价的逐年数据框
|
1105
|
-
显示:按年绘图投资组合的风险调整收益率和风险溢价
|
1106
|
-
"""
|
1107
|
-
#检查日期期间的合理性
|
1108
|
-
valid,start1,end1=check_period(start,end)
|
1109
|
-
if not valid:
|
1110
|
-
print(" #Error(plot_rar_annual): incorrect start/end date(s)",start.end)
|
1111
|
-
return None
|
1112
|
-
|
1113
|
-
#检查投资组合各个成分股份额的合理性
|
1114
|
-
"""
|
1115
|
-
if round(sum(portionlist),1) != 1.0:
|
1116
|
-
print(" #Error(plot_rar_annual): Incorrect total of portions")
|
1117
|
-
return None
|
1118
|
-
"""
|
1119
|
-
|
1120
|
-
#检查支持的rar_type
|
1121
|
-
rar_list=['treynor_ratio','sharpe_ratio','sortino_ratio','jensen_alpha']
|
1122
|
-
if rar_type not in rar_list:
|
1123
|
-
print(" #Error(plot_rar_annual): not supported rar type")
|
1124
|
-
print(" Supported rar type:",rar_list)
|
1125
|
-
return None
|
1126
|
-
|
1127
|
-
#拆分start/end之间的各个年份和月份
|
1128
|
-
mdlist=calc_yearly_date_range(start,end)
|
1129
|
-
if len(mdlist) == 0:
|
1130
|
-
print(" #Error(plot_rar_annual): start/end dates inappropriate")
|
1131
|
-
return None
|
1132
|
-
|
1133
|
-
#用于保存risk premium和rar
|
1134
|
-
print("\n Calculating yearly",rar_type,"......")
|
1135
|
-
rarfunc=rar_type+'_portfolio'
|
1136
|
-
rars=pd.DataFrame(columns=('YR','rp','rar'))
|
1137
|
-
for i in range(0,len(mdlist)):
|
1138
|
-
start=mdlist[i][0]
|
1139
|
-
YR=start.strftime("%Y")
|
1140
|
-
print(' ',YR,end=' ')
|
1141
|
-
end=mdlist[i][1]
|
1142
|
-
rar,rp=eval(rarfunc)(portfolio,start,end,printout=False)
|
1143
|
-
|
1144
|
-
row=pd.Series({'YR':YR,'rp':rp,'rar':rar})
|
1145
|
-
try:
|
1146
|
-
rars=rars.append(row,ignore_index=True)
|
1147
|
-
except:
|
1148
|
-
rars=rars._append(row,ignore_index=True)
|
1149
|
-
print(" Searching completed.")
|
1150
|
-
rars.set_index('YR',inplace=True)
|
1151
|
-
|
1152
|
-
#绘图
|
1153
|
-
plt.plot(rars['rp'],label='risk_premium',c='blue',marker='*',ls=':',lw=3)
|
1154
|
-
plt.plot(rars['rar'],label=rar_type,c='r',lw=3,marker='o')
|
1155
|
-
plt.axhline(y=0.0,color='black',linestyle=':',lw=1)
|
1156
|
-
titletxt="投资组合的风险调整收益"
|
1157
|
-
plt.title(titletxt)
|
1158
|
-
plt.ylabel('收益率(%)')
|
1159
|
-
|
1160
|
-
plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
|
1161
|
-
plt.gca().set_facecolor('whitesmoke')
|
1162
|
-
|
1163
|
-
#plt.xticks(rotation=45)
|
1164
|
-
plt.legend(loc='best')
|
1165
|
-
|
1166
|
-
import datetime as dt; today=dt.date.today()
|
1167
|
-
footnote="数据来源:新浪/stooq/FRED,"+str(today)
|
1168
|
-
plt.xlabel(footnote)
|
1169
|
-
|
1170
|
-
plt.show()
|
1171
|
-
|
1172
|
-
return
|
1173
|
-
|
1174
|
-
|
1175
|
-
if __name__=='__main__':
|
1176
|
-
portfolio={'Market':('US','^GSPC'),'VIPS':0.1,'PDD':0.2,'JD':0.3,'BABA':0.4}
|
1177
|
-
plot_rar_annual(portfolio,'2013-01-01','2019-06-30','treynor_ratio')
|
1178
|
-
|
1179
|
-
portfolio={'Market':('US','^GSPC'),'AAPL':1.0}
|
1180
|
-
plot_rar_annual(portfolio,'2015-01-01','2017-12-31','sharpe_ratio')
|
1181
|
-
#==============================================================================
|
1182
|
-
# 新加入的滚动指标对比
|
1183
|
-
#==============================================================================
|
1184
|
-
if __name__=='__main__':
|
1185
|
-
portfolio1={'Market':('US','^GSPC'),'AAPL':1}
|
1186
|
-
portfolio2={'Market':('US','^GSPC'),'MSFT':1}
|
1187
|
-
|
1188
|
-
start='2020-1-1'
|
1189
|
-
end ='2020-12-31'
|
1190
|
-
scope='US'
|
1191
|
-
ratio_name='sharpe'
|
1192
|
-
window=30
|
1193
|
-
graph=True
|
1194
|
-
|
1195
|
-
def compare_rar_portfolio(portfolio1,portfolio2,start,end,ratio_name='sharpe', \
|
1196
|
-
window=240,graph=True):
|
1197
|
-
"""
|
1198
|
-
功能:比较两个投资组合的风险调整收益率,并绘制曲线
|
1199
|
-
注意:无风险收益率有两个月的延迟
|
1200
|
-
"""
|
1201
|
-
|
1202
|
-
#检查日期的合理性
|
1203
|
-
result,startdate,enddate=check_period(start,end)
|
1204
|
-
if result is None:
|
1205
|
-
print(" #Error(compare_rar_portfolio): invalid period",start,end)
|
1206
|
-
return None
|
1207
|
-
|
1208
|
-
#检查支持的指标
|
1209
|
-
ratio_list=['treynor','sharpe','sortino','alpha']
|
1210
|
-
name_list=['特雷诺比率','夏普比率','索替诺比率','詹森阿尔法']
|
1211
|
-
if ratio_name not in ratio_list:
|
1212
|
-
message=" #Error(compare_rar_portfolio): "+"unsupported rar ratio type"
|
1213
|
-
print(message,ratio_name)
|
1214
|
-
return None
|
1215
|
-
|
1216
|
-
#计算开始日期的提前量:假定每月有20个交易日
|
1217
|
-
adjdays=int(window/20.0*30.0)+1
|
1218
|
-
import siat.common as cmn
|
1219
|
-
new_start=cmn.date_adjust(start, adjust=-adjdays)
|
1220
|
-
|
1221
|
-
#获取第一个投资组合的数据
|
1222
|
-
rars1=rar_ratio_rolling(portfolio1,new_start,end,ratio_name=ratio_name, \
|
1223
|
-
window=window,graph=False)
|
1224
|
-
if rars1 is None: return None
|
1225
|
-
#获取第二个投资组合的数据
|
1226
|
-
rars2=rar_ratio_rolling(portfolio2,new_start,end,ratio_name=ratio_name, \
|
1227
|
-
window=window,graph=False)
|
1228
|
-
if rars2 is None: return None
|
1229
|
-
|
1230
|
-
#绘制双线图
|
1231
|
-
ticker1="证券1"
|
1232
|
-
colname1='RAR'
|
1233
|
-
label1=name_list[ratio_list.index(ratio_name)]
|
1234
|
-
|
1235
|
-
ticker2="证券2"
|
1236
|
-
colname2='RAR'
|
1237
|
-
label2=label1
|
1238
|
-
|
1239
|
-
ylabeltxt=label1
|
1240
|
-
titletxt="证券风险调整收益的滚动趋势对比"
|
1241
|
-
|
1242
|
-
_,_,tickers1,shares1,ticker_type=decompose_portfolio(portfolio1)
|
1243
|
-
if len(tickers1) == 1:
|
1244
|
-
ticker1=tickers1[0]
|
1245
|
-
pf1str=tickers1[0]
|
1246
|
-
else:
|
1247
|
-
pf1str=ticker1+':成分'+str(tickers1)+',比例'+str(shares1)
|
1248
|
-
|
1249
|
-
_,_,tickers2,shares2,ticker_type=decompose_portfolio(portfolio2)
|
1250
|
-
if len(tickers2) == 1:
|
1251
|
-
ticker2=tickers2[0]
|
1252
|
-
pf2str=tickers2[0]
|
1253
|
-
else:
|
1254
|
-
pf2str=ticker2+':成分'+str(tickers2)+',比例'+str(shares2)
|
1255
|
-
|
1256
|
-
footnote="日期 -->"
|
1257
|
-
if len(tickers1) > 1:
|
1258
|
-
footnote=footnote+'\n'+pf1str
|
1259
|
-
if len(tickers2) > 1:
|
1260
|
-
footnote=footnote+'\n'+pf2str
|
1261
|
-
|
1262
|
-
import datetime as dt; today=dt.date.today()
|
1263
|
-
source="数据来源:新浪/stooq/FRED,"+str(today)
|
1264
|
-
footnote=footnote+"\n"+source
|
1265
|
-
|
1266
|
-
plot_line2(rars1,ticker1,colname1,label1, \
|
1267
|
-
rars2,ticker2,colname2,label2, \
|
1268
|
-
ylabeltxt,titletxt,footnote)
|
1269
|
-
|
1270
|
-
#合并RAR
|
1271
|
-
import pandas as pd
|
1272
|
-
rarm=pd.merge(rars1,rars2,how='inner',left_index=True,right_index=True)
|
1273
|
-
rars=rarm[['RAR_x','RAR_y']]
|
1274
|
-
rars.rename(columns={'RAR_x':ticker1+'_'+ratio_name,'RAR_y':ticker2+'_'+ratio_name},inplace=True)
|
1275
|
-
|
1276
|
-
return rars
|
1277
|
-
|
1278
|
-
|
1279
|
-
if __name__=='__main__':
|
1280
|
-
pf1={'Market':('US','^GSPC'),'AAPL':1}
|
1281
|
-
pf2={'Market':('US','^GSPC'),'MSFT':1}
|
1282
|
-
rars12=compare_rar_portfolio(pf1,pf2,'2019-11-1','2020-11-30')
|
1283
|
-
|
1284
|
-
pfA={'Market':('China','000001.SS'),'600519.SS':1}
|
1285
|
-
pfB={'Market':('China','000001.SS'),'000858.SZ':1}
|
1286
|
-
rarsAB=compare_rar_portfolio(pfA,pfB,'2019-11-1','2020-11-30')
|
1287
|
-
|
1288
|
-
pfbb={'Market':('US','^GSPC'),'BABA':1}
|
1289
|
-
pfjd={'Market':('US','^GSPC'),'JD':1}
|
1290
|
-
rarsbj=compare_rar_portfolio(pfbb,pfjd,'2019-11-1','2020-11-30')
|
1291
|
-
|
1292
|
-
pfbb={'Market':('US','^GSPC'),'BABA':1}
|
1293
|
-
pfpd={'Market':('US','^GSPC'),'PDD':1}
|
1294
|
-
rarsbj=compare_rar_portfolio(pfbb,pfpd,'2019-11-1','2020-11-30')
|
1295
|
-
|
1296
|
-
#==============================================================================
|
1297
|
-
#==============================================================================
|
1298
|
-
|
1299
|
-
if __name__=='__main__':
|
1300
|
-
tickers = ['000858.SZ','600779.SS','000596.SZ','603589.SS']
|
1301
|
-
start='2022-1-1'
|
1302
|
-
end='2022-10-31'
|
1303
|
-
rar_name="sharpe"
|
1304
|
-
market_index="000300.SS"
|
1305
|
-
market="China"
|
1306
|
-
|
1307
|
-
tickers=['AAPL','01810.HK','000063.SZ']
|
1308
|
-
start='2023-1-1'
|
1309
|
-
end='2023-7-1'
|
1310
|
-
|
1311
|
-
RF=False
|
1312
|
-
window=240
|
1313
|
-
axhline_value=0
|
1314
|
-
axhline_label=''
|
1315
|
-
graph=True
|
1316
|
-
printout=True
|
1317
|
-
sortby='tpw_mean'
|
1318
|
-
|
1319
|
-
graph=False
|
1320
|
-
source='auto'
|
1321
|
-
trailing=20
|
1322
|
-
|
1323
|
-
def compare_mrar(tickers,rar_name,start,end, \
|
1324
|
-
market="China",market_index="000300.SS",RF=False,window=63, \
|
1325
|
-
axhline_value=0,axhline_label='零线',graph=True,printout=True, \
|
1326
|
-
sortby='tpw_mean',source='auto',trailing=20,trend_threshhold=0.001, \
|
1327
|
-
annotate=False):
|
1328
|
-
"""
|
1329
|
-
功能:计算多只股票的rar比率,并绘图对比。多只股票必须处于同一个经济体的证券市场
|
1330
|
-
比率:支持夏普比率、特雷诺比率、索替诺比率、阿尔法比率
|
1331
|
-
|
1332
|
-
sortby:
|
1333
|
-
tpw_mean(近期优先加权平均值降序排列)
|
1334
|
-
min(最小值降序排列)
|
1335
|
-
mean(平均值降序排列)
|
1336
|
-
median(中位数值降序排列)
|
1337
|
-
trailing(短期趋势,最新数值与近trailing个交易日均值的差值降序排列)
|
1338
|
-
|
1339
|
-
注意:当RF=False时可能有bug
|
1340
|
-
"""
|
1341
|
-
#检查tickers是否为列表且不少于两只股票
|
1342
|
-
tickers=upper_ticker(tickers)
|
1343
|
-
|
1344
|
-
#检查rar指标的种类
|
1345
|
-
rarlist=['treynor','sharpe','sortino','alpha']
|
1346
|
-
if not (rar_name.lower() in rarlist):
|
1347
|
-
print(" #Error(compare_mrar): unsupported rar name",rar_name)
|
1348
|
-
return None
|
1349
|
-
|
1350
|
-
# 去掉重复代码
|
1351
|
-
tickers=list(set(tickers))
|
1352
|
-
|
1353
|
-
#检查支持的比率种类
|
1354
|
-
|
1355
|
-
#检查日期的合理性
|
1356
|
-
|
1357
|
-
#将开始日期提前
|
1358
|
-
start1=date_adjust(start,-(int(window/20*31)+1))
|
1359
|
-
|
1360
|
-
import os,sys
|
1361
|
-
class HiddenPrints:
|
1362
|
-
def __enter__(self):
|
1363
|
-
self._original_stdout = sys.stdout
|
1364
|
-
sys.stdout = open(os.devnull, 'w')
|
1365
|
-
|
1366
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
1367
|
-
sys.stdout.close()
|
1368
|
-
sys.stdout = self._original_stdout
|
1369
|
-
|
1370
|
-
import pandas as pd
|
1371
|
-
df=pd.DataFrame()
|
1372
|
-
print(" Starting to retrieve and calculate",rar_name,"ratio, please wait ......")
|
1373
|
-
for t in tickers:
|
1374
|
-
|
1375
|
-
pf={'Market':(market,market_index),t:1.0}
|
1376
|
-
#关闭print输出
|
1377
|
-
with HiddenPrints():
|
1378
|
-
df_tmp=rar_ratio_rolling(pf,start1,end,ratio_name=rar_name, \
|
1379
|
-
RF=RF,window=window,graph=False,source=source)
|
1380
|
-
|
1381
|
-
if df_tmp is None:
|
1382
|
-
print(" #Warning(compare_mrar): data not available for",t)
|
1383
|
-
continue
|
1384
|
-
else:
|
1385
|
-
dft=df_tmp[['RAR']]
|
1386
|
-
#dft.rename(columns={'RAR':ticker_name(t)},inplace=True)
|
1387
|
-
dft.rename(columns={'RAR':t},inplace=True)
|
1388
|
-
|
1389
|
-
if len(df)==0:
|
1390
|
-
#第一个
|
1391
|
-
df=dft
|
1392
|
-
else:
|
1393
|
-
df=pd.merge(df,dft,how='outer',left_index=True,right_index=True)
|
1394
|
-
|
1395
|
-
if len(df)==0:
|
1396
|
-
print(" #Warning(compare_mrar): no data available for the above securities between",start,end)
|
1397
|
-
return None
|
1398
|
-
|
1399
|
-
# 填充空缺值
|
1400
|
-
df.fillna(method='ffill',inplace=True) #使用前值填充
|
1401
|
-
df.fillna(method='bfill',inplace=True) #使用后值填充
|
1402
|
-
|
1403
|
-
#绘制多条曲线
|
1404
|
-
rar_list=['treynor','sortino','sharpe','alpha']
|
1405
|
-
rar_list_e=['Treynor Ratio','Sortino Ratio','Sharpe Ratio','Jensen alpha']
|
1406
|
-
#rar_list_c=['特雷诺比率','索替诺比率','夏普比率','阿尔法指数']
|
1407
|
-
rar_list_c=['特雷诺比率','索替诺比率','夏普比率','阿尔法指标']
|
1408
|
-
|
1409
|
-
pos=rar_list.index(rar_name)
|
1410
|
-
|
1411
|
-
import datetime; today = datetime.date.today()
|
1412
|
-
|
1413
|
-
lang=check_language()
|
1414
|
-
if lang == 'English':
|
1415
|
-
|
1416
|
-
y_label=rar_list_e[pos]
|
1417
|
-
x_label="Source: sina/stooq, "+str(today)
|
1418
|
-
title_txt="Compare Multiple Risk-adjusted Return Performance"
|
1419
|
-
else:
|
1420
|
-
y_label=rar_list_c[pos]
|
1421
|
-
x_label="数据来源: 新浪/stooq/Yahoo,"+str(today)
|
1422
|
-
title_txt="比较多只证券的风险调整收益滚动指标"
|
1423
|
-
|
1424
|
-
startpd=pd.to_datetime(start)
|
1425
|
-
df1=df[df.index >= startpd]
|
1426
|
-
|
1427
|
-
# 是否绘图
|
1428
|
-
if graph:
|
1429
|
-
# 翻译证券名称
|
1430
|
-
for c in list(df1):
|
1431
|
-
df1.rename(columns={c:ticker_name(c)},inplace=True)
|
1432
|
-
|
1433
|
-
draw_lines(df1,y_label,x_label, \
|
1434
|
-
axhline_value=axhline_value,axhline_label=axhline_label, \
|
1435
|
-
title_txt=title_txt,data_label=False,annotate=annotate)
|
1436
|
-
|
1437
|
-
if printout:
|
1438
|
-
|
1439
|
-
dfcols=list(df)
|
1440
|
-
for c in dfcols:
|
1441
|
-
ccn=ticker_name(c)+'('+c+')'
|
1442
|
-
df.rename(columns={c:ccn},inplace=True)
|
1443
|
-
|
1444
|
-
if sortby=='tpw_mean':
|
1445
|
-
sortby_txt='按推荐标记+近期优先加权平均值降序排列'
|
1446
|
-
elif sortby=='min':
|
1447
|
-
sortby_txt='按推荐标记+最小值降序排列'
|
1448
|
-
elif sortby=='mean':
|
1449
|
-
sortby_txt='按推荐标记+平均值降序排列'
|
1450
|
-
elif sortby=='median':
|
1451
|
-
sortby_txt='按推荐标记+中位数值降序排列'
|
1452
|
-
elif sortby=='trailing':
|
1453
|
-
sortby_txt='按推荐标记+短期均值走势降序排列'
|
1454
|
-
else:
|
1455
|
-
pass
|
1456
|
-
|
1457
|
-
title_txt='*** '+title_txt+':'+y_label+','+sortby_txt
|
1458
|
-
additional_note="*** 注:列表仅显示有星号标记或特定数量的证券。"
|
1459
|
-
footnote='期间趋势范围:'+str(start)+'至'+str(end)+";近期趋势范围:近"+str(trailing)+"个交易日"
|
1460
|
-
descriptive_statistics(df,title_txt,additional_note+footnote,decimals=4, \
|
1461
|
-
sortby=sortby,recommend_only=True,trailing=trailing, \
|
1462
|
-
trend_threshhold=trend_threshhold)
|
1463
|
-
|
1464
|
-
return df1
|
1465
|
-
|
1466
|
-
if __name__=='__main__':
|
1467
|
-
tickers = ['000858.SZ','600779.SS','000596.SZ','603589.SS','000001.SS']
|
1468
|
-
df=compare_mrar(tickers,'sharpe','2022-1-1','2022-10-31')
|
1469
|
-
df=compare_mrar(tickers,'alpha','2022-10-1','2022-10-31')
|
1470
|
-
|
1471
|
-
#==============================================================================
|
1472
|
-
|
1473
|
-
if __name__=='__main__':
|
1474
|
-
ticker = '000858.SZ'
|
1475
|
-
start='2022-1-1'
|
1476
|
-
end='2022-10-31'
|
1477
|
-
rar_names=["sharpe",'sortino','alpha']
|
1478
|
-
market_index="000300.SS"
|
1479
|
-
market="China"
|
1480
|
-
|
1481
|
-
RF=False
|
1482
|
-
window=60
|
1483
|
-
axhline_value=0
|
1484
|
-
axhline_label=''
|
1485
|
-
graph=True
|
1486
|
-
printout=False
|
1487
|
-
sortby='tpw_mean'
|
1488
|
-
|
1489
|
-
graph=False
|
1490
|
-
source='auto'
|
1491
|
-
trailing=20
|
1492
|
-
|
1493
|
-
def compare_1security_mrar(ticker,rar_names,start,end, \
|
1494
|
-
market="China",market_index="000300.SS",RF=False,window=63, \
|
1495
|
-
axhline_value=0,axhline_label='零线',graph=True,printout=False, \
|
1496
|
-
sortby='tpw_mean',source='auto',trailing=20,trend_threshhold=0.001, \
|
1497
|
-
annotate=False):
|
1498
|
-
"""
|
1499
|
-
功能:计算一只股票的多个rar比率,并绘图对比
|
1500
|
-
比率:支持夏普比率、特雷诺比率、索替诺比率、阿尔法比率等
|
1501
|
-
|
1502
|
-
sortby:
|
1503
|
-
tpw_mean(近期优先加权平均值降序排列)
|
1504
|
-
min(最小值降序排列)
|
1505
|
-
mean(平均值降序排列)
|
1506
|
-
median(中位数值降序排列)
|
1507
|
-
trailing(短期趋势,最新数值与近trailing个交易日均值的差值降序排列)
|
1508
|
-
"""
|
1509
|
-
DEBUG=True
|
1510
|
-
|
1511
|
-
rar_list=['treynor','sharpe','sortino','alpha']
|
1512
|
-
rar_list_c=['特雷诺比率','夏普比率','索替诺比率','阿尔法指标']
|
1513
|
-
|
1514
|
-
valid,start1,end1=check_period(start,end)
|
1515
|
-
if not valid:
|
1516
|
-
print(" #Error(compare_1security_mrar): invalid period from",start,'to',end)
|
1517
|
-
return None
|
1518
|
-
|
1519
|
-
if isinstance(ticker,str):
|
1520
|
-
tickers=[ticker]
|
1521
|
-
elif isinstance(ticker,list):
|
1522
|
-
tickers=[ticker[0]]
|
1523
|
-
else:
|
1524
|
-
print(" #Warning(compare_1security_mrar): unsupported ticker for",ticker)
|
1525
|
-
return None
|
1526
|
-
|
1527
|
-
import pandas as pd
|
1528
|
-
df=pd.DataFrame()
|
1529
|
-
for r in rar_names:
|
1530
|
-
if not (r in rar_list):
|
1531
|
-
print(" #Warning(compare_1security_mrar): unsupported rar indicator for",r)
|
1532
|
-
continue
|
1533
|
-
|
1534
|
-
dft=compare_mrar(tickers=tickers,rar_name=r, \
|
1535
|
-
start=start,end=end, \
|
1536
|
-
market=market,market_index=market_index, \
|
1537
|
-
RF=RF,window=window, \
|
1538
|
-
axhline_value=0,axhline_label='零线', \
|
1539
|
-
graph=False,printout=False, \
|
1540
|
-
sortby=sortby,source=source,trailing=trailing,trend_threshhold=trend_threshhold)
|
1541
|
-
if dft is None:
|
1542
|
-
print(" #Error(compare_1security_mrar): information unaccessible for",tickers[0])
|
1543
|
-
break
|
1544
|
-
if len(dft)==0:
|
1545
|
-
print(" #Error(compare_1security_mrar): information unavailable for",tickers[0],'from',start,'to',end)
|
1546
|
-
break
|
1547
|
-
|
1548
|
-
pos=rar_list.index(r)
|
1549
|
-
rcn=rar_list_c[pos]
|
1550
|
-
dft.columns=[rcn]
|
1551
|
-
|
1552
|
-
if len(df)==0:
|
1553
|
-
df=dft
|
1554
|
-
else:
|
1555
|
-
df=pd.merge(df,dft,how='inner',left_index=True,right_index=True)
|
1556
|
-
|
1557
|
-
if len(df)==0:
|
1558
|
-
return None
|
1559
|
-
|
1560
|
-
import datetime; today = datetime.date.today()
|
1561
|
-
y_label="风险调整收益指标"
|
1562
|
-
x_label="数据来源: 综合新浪/stooq/Yahoo,"+str(today)
|
1563
|
-
title_txt="证券风险调整收益滚动指标:"+ticker_name(tickers[0])
|
1564
|
-
|
1565
|
-
# 是否绘图
|
1566
|
-
if graph:
|
1567
|
-
draw_lines(df,y_label,x_label, \
|
1568
|
-
axhline_value=axhline_value,axhline_label=axhline_label, \
|
1569
|
-
title_txt=title_txt,data_label=False,annotate=annotate)
|
1570
|
-
|
1571
|
-
return df
|
1572
|
-
|
1573
|
-
|
1574
|
-
#==============================================================================
|
1575
|
-
|
1576
|
-
|