siat 3.10.131__py3-none-any.whl → 3.10.132__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. build/lib/build/lib/siat/__init__.py +75 -0
  2. build/lib/build/lib/siat/allin.py +137 -0
  3. build/lib/build/lib/siat/assets_liquidity.py +915 -0
  4. build/lib/build/lib/siat/beta_adjustment.py +1058 -0
  5. build/lib/build/lib/siat/beta_adjustment_china.py +548 -0
  6. build/lib/build/lib/siat/blockchain.py +143 -0
  7. build/lib/build/lib/siat/bond.py +2900 -0
  8. build/lib/build/lib/siat/bond_base.py +992 -0
  9. build/lib/build/lib/siat/bond_china.py +100 -0
  10. build/lib/build/lib/siat/bond_zh_sina.py +143 -0
  11. build/lib/build/lib/siat/capm_beta.py +783 -0
  12. build/lib/build/lib/siat/capm_beta2.py +887 -0
  13. build/lib/build/lib/siat/common.py +5360 -0
  14. build/lib/build/lib/siat/compare_cross.py +642 -0
  15. build/lib/build/lib/siat/copyrights.py +18 -0
  16. build/lib/build/lib/siat/cryptocurrency.py +667 -0
  17. build/lib/build/lib/siat/economy.py +1471 -0
  18. build/lib/build/lib/siat/economy2.py +1853 -0
  19. build/lib/build/lib/siat/esg.py +536 -0
  20. build/lib/build/lib/siat/event_study.py +815 -0
  21. build/lib/build/lib/siat/fama_french.py +1521 -0
  22. build/lib/build/lib/siat/fin_stmt2_yahoo.py +982 -0
  23. build/lib/build/lib/siat/financial_base.py +1160 -0
  24. build/lib/build/lib/siat/financial_statements.py +598 -0
  25. build/lib/build/lib/siat/financials.py +2339 -0
  26. build/lib/build/lib/siat/financials2.py +1278 -0
  27. build/lib/build/lib/siat/financials_china.py +4433 -0
  28. build/lib/build/lib/siat/financials_china2.py +2212 -0
  29. build/lib/build/lib/siat/fund.py +629 -0
  30. build/lib/build/lib/siat/fund_china.py +3307 -0
  31. build/lib/build/lib/siat/future_china.py +551 -0
  32. build/lib/build/lib/siat/google_authenticator.py +47 -0
  33. build/lib/build/lib/siat/grafix.py +3636 -0
  34. build/lib/build/lib/siat/holding_risk.py +867 -0
  35. build/lib/build/lib/siat/luchy_draw.py +638 -0
  36. build/lib/build/lib/siat/market_china.py +1168 -0
  37. build/lib/build/lib/siat/markowitz.py +2363 -0
  38. build/lib/build/lib/siat/markowitz2.py +3150 -0
  39. build/lib/build/lib/siat/markowitz2_20250704.py +2969 -0
  40. build/lib/build/lib/siat/markowitz2_20250705.py +3158 -0
  41. build/lib/build/lib/siat/markowitz_simple.py +373 -0
  42. build/lib/build/lib/siat/ml_cases.py +2291 -0
  43. build/lib/build/lib/siat/ml_cases_example.py +60 -0
  44. build/lib/build/lib/siat/option_china.py +3069 -0
  45. build/lib/build/lib/siat/option_pricing.py +1925 -0
  46. build/lib/build/lib/siat/other_indexes.py +409 -0
  47. build/lib/build/lib/siat/risk_adjusted_return.py +1576 -0
  48. build/lib/build/lib/siat/risk_adjusted_return2.py +1900 -0
  49. build/lib/build/lib/siat/risk_evaluation.py +2218 -0
  50. build/lib/build/lib/siat/risk_free_rate.py +351 -0
  51. build/lib/build/lib/siat/sector_china.py +4140 -0
  52. build/lib/build/lib/siat/security_price2.py +727 -0
  53. build/lib/build/lib/siat/security_prices.py +3408 -0
  54. build/lib/build/lib/siat/security_trend.py +402 -0
  55. build/lib/build/lib/siat/security_trend2.py +646 -0
  56. build/lib/build/lib/siat/stock.py +4284 -0
  57. build/lib/build/lib/siat/stock_advice_linear.py +934 -0
  58. build/lib/build/lib/siat/stock_base.py +26 -0
  59. build/lib/build/lib/siat/stock_china.py +2095 -0
  60. build/lib/build/lib/siat/stock_prices_kneighbors.py +910 -0
  61. build/lib/build/lib/siat/stock_prices_linear.py +386 -0
  62. build/lib/build/lib/siat/stock_profile.py +707 -0
  63. build/lib/build/lib/siat/stock_technical.py +3305 -0
  64. build/lib/build/lib/siat/stooq.py +74 -0
  65. build/lib/build/lib/siat/transaction.py +347 -0
  66. build/lib/build/lib/siat/translate.py +5183 -0
  67. build/lib/build/lib/siat/valuation.py +1378 -0
  68. build/lib/build/lib/siat/valuation_china.py +2076 -0
  69. build/lib/build/lib/siat/var_model_validation.py +444 -0
  70. build/lib/build/lib/siat/yf_name.py +811 -0
  71. build/lib/siat/__init__.py +75 -0
  72. build/lib/siat/allin.py +137 -0
  73. build/lib/siat/assets_liquidity.py +915 -0
  74. build/lib/siat/beta_adjustment.py +1058 -0
  75. build/lib/siat/beta_adjustment_china.py +548 -0
  76. build/lib/siat/blockchain.py +143 -0
  77. build/lib/siat/bond.py +2900 -0
  78. build/lib/siat/bond_base.py +992 -0
  79. build/lib/siat/bond_china.py +100 -0
  80. build/lib/siat/bond_zh_sina.py +143 -0
  81. build/lib/siat/capm_beta.py +783 -0
  82. build/lib/siat/capm_beta2.py +887 -0
  83. build/lib/siat/common.py +5360 -0
  84. build/lib/siat/compare_cross.py +642 -0
  85. build/lib/siat/copyrights.py +18 -0
  86. build/lib/siat/cryptocurrency.py +667 -0
  87. build/lib/siat/economy.py +1471 -0
  88. build/lib/siat/economy2.py +1853 -0
  89. build/lib/siat/esg.py +536 -0
  90. build/lib/siat/event_study.py +815 -0
  91. build/lib/siat/fama_french.py +1521 -0
  92. build/lib/siat/fin_stmt2_yahoo.py +982 -0
  93. build/lib/siat/financial_base.py +1160 -0
  94. build/lib/siat/financial_statements.py +598 -0
  95. build/lib/siat/financials.py +2339 -0
  96. build/lib/siat/financials2.py +1278 -0
  97. build/lib/siat/financials_china.py +4433 -0
  98. build/lib/siat/financials_china2.py +2212 -0
  99. build/lib/siat/fund.py +629 -0
  100. build/lib/siat/fund_china.py +3307 -0
  101. build/lib/siat/future_china.py +551 -0
  102. build/lib/siat/google_authenticator.py +47 -0
  103. build/lib/siat/grafix.py +3636 -0
  104. build/lib/siat/holding_risk.py +867 -0
  105. build/lib/siat/luchy_draw.py +638 -0
  106. build/lib/siat/market_china.py +1168 -0
  107. build/lib/siat/markowitz.py +2363 -0
  108. build/lib/siat/markowitz2.py +3150 -0
  109. build/lib/siat/markowitz2_20250704.py +2969 -0
  110. build/lib/siat/markowitz2_20250705.py +3158 -0
  111. build/lib/siat/markowitz_simple.py +373 -0
  112. build/lib/siat/ml_cases.py +2291 -0
  113. build/lib/siat/ml_cases_example.py +60 -0
  114. build/lib/siat/option_china.py +3069 -0
  115. build/lib/siat/option_pricing.py +1925 -0
  116. build/lib/siat/other_indexes.py +409 -0
  117. build/lib/siat/risk_adjusted_return.py +1576 -0
  118. build/lib/siat/risk_adjusted_return2.py +1900 -0
  119. build/lib/siat/risk_evaluation.py +2218 -0
  120. build/lib/siat/risk_free_rate.py +351 -0
  121. build/lib/siat/sector_china.py +4140 -0
  122. build/lib/siat/security_price2.py +727 -0
  123. build/lib/siat/security_prices.py +3408 -0
  124. build/lib/siat/security_trend.py +402 -0
  125. build/lib/siat/security_trend2.py +646 -0
  126. build/lib/siat/stock.py +4284 -0
  127. build/lib/siat/stock_advice_linear.py +934 -0
  128. build/lib/siat/stock_base.py +26 -0
  129. build/lib/siat/stock_china.py +2095 -0
  130. build/lib/siat/stock_prices_kneighbors.py +910 -0
  131. build/lib/siat/stock_prices_linear.py +386 -0
  132. build/lib/siat/stock_profile.py +707 -0
  133. build/lib/siat/stock_technical.py +3305 -0
  134. build/lib/siat/stooq.py +74 -0
  135. build/lib/siat/transaction.py +347 -0
  136. build/lib/siat/translate.py +5183 -0
  137. build/lib/siat/valuation.py +1378 -0
  138. build/lib/siat/valuation_china.py +2076 -0
  139. build/lib/siat/var_model_validation.py +444 -0
  140. build/lib/siat/yf_name.py +811 -0
  141. siat/__init__.py +0 -0
  142. siat/allin.py +0 -0
  143. siat/assets_liquidity.py +0 -0
  144. siat/beta_adjustment.py +0 -0
  145. siat/beta_adjustment_china.py +0 -0
  146. siat/blockchain.py +0 -0
  147. siat/bond.py +0 -0
  148. siat/bond_base.py +0 -0
  149. siat/bond_china.py +0 -0
  150. siat/bond_zh_sina.py +0 -0
  151. siat/capm_beta.py +0 -0
  152. siat/capm_beta2.py +0 -0
  153. siat/common.py +136 -3
  154. siat/compare_cross.py +0 -0
  155. siat/copyrights.py +0 -0
  156. siat/cryptocurrency.py +0 -0
  157. siat/economy.py +0 -0
  158. siat/economy2.py +0 -0
  159. siat/esg.py +0 -0
  160. siat/event_study.py +0 -0
  161. siat/exchange_bond_china.pickle +0 -0
  162. siat/fama_french.py +0 -0
  163. siat/fin_stmt2_yahoo.py +0 -0
  164. siat/financial_base.py +0 -0
  165. siat/financial_statements.py +0 -0
  166. siat/financials.py +0 -0
  167. siat/financials2.py +0 -0
  168. siat/financials_china.py +0 -0
  169. siat/financials_china2.py +0 -0
  170. siat/fund.py +0 -0
  171. siat/fund_china.pickle +0 -0
  172. siat/fund_china.py +0 -0
  173. siat/future_china.py +0 -0
  174. siat/google_authenticator.py +0 -0
  175. siat/grafix.py +1 -1
  176. siat/holding_risk.py +0 -0
  177. siat/luchy_draw.py +0 -0
  178. siat/market_china.py +1 -1
  179. siat/markowitz.py +0 -0
  180. siat/markowitz2.py +240 -39
  181. siat/markowitz2_20250704.py +2969 -0
  182. siat/markowitz2_20250705.py +3158 -0
  183. siat/markowitz_simple.py +0 -0
  184. siat/ml_cases.py +0 -0
  185. siat/ml_cases_example.py +0 -0
  186. siat/option_china.py +0 -0
  187. siat/option_pricing.py +0 -0
  188. siat/other_indexes.py +0 -0
  189. siat/risk_adjusted_return.py +0 -0
  190. siat/risk_adjusted_return2.py +0 -0
  191. siat/risk_evaluation.py +0 -0
  192. siat/risk_free_rate.py +0 -0
  193. siat/sector_china.py +0 -0
  194. siat/security_price2.py +0 -0
  195. siat/security_prices.py +3 -1
  196. siat/security_trend.py +0 -0
  197. siat/security_trend2.py +1 -1
  198. siat/stock.py +4 -2
  199. siat/stock_advice_linear.py +0 -0
  200. siat/stock_base.py +0 -0
  201. siat/stock_china.py +0 -0
  202. siat/stock_info.pickle +0 -0
  203. siat/stock_prices_kneighbors.py +0 -0
  204. siat/stock_prices_linear.py +0 -0
  205. siat/stock_profile.py +0 -0
  206. siat/stock_technical.py +0 -0
  207. siat/stooq.py +0 -0
  208. siat/transaction.py +0 -0
  209. siat/translate.py +11 -11
  210. siat/valuation.py +0 -0
  211. siat/valuation_china.py +0 -0
  212. siat/var_model_validation.py +0 -0
  213. siat/yf_name.py +0 -0
  214. {siat-3.10.131.dist-info → siat-3.10.132.dist-info}/METADATA +235 -227
  215. siat-3.10.132.dist-info/RECORD +218 -0
  216. {siat-3.10.131.dist-info → siat-3.10.132.dist-info}/WHEEL +1 -1
  217. {siat-3.10.131.dist-info → siat-3.10.132.dist-info/licenses}/LICENSE +0 -0
  218. siat-3.10.132.dist-info/top_level.txt +4 -0
  219. siat-3.10.131.dist-info/RECORD +0 -76
  220. siat-3.10.131.dist-info/top_level.txt +0 -1
@@ -0,0 +1,1900 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 本模块功能:股票的风险调整收益
4
+ 所属工具包:证券投资分析工具SIAT
5
+ SIAT:Security Investment Analysis Tool
6
+ 创建日期:2024年3月16日
7
+ 最新修订日期:2024年3月19日
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.capm_beta2 import *
25
+ #from siat.fama_french import *
26
+ from siat.risk_adjusted_return import *
27
+ from siat.grafix import *
28
+
29
+ import pandas as pd
30
+ import numpy as np
31
+ #==============================================================================
32
+ #==============================================================================
33
+ #==============================================================================
34
+ if __name__=='__main__':
35
+ ticker='301161.SZ'
36
+ ticker="AAPL"
37
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
38
+
39
+ start="2024-1-1"; end="2024-9-30"
40
+ rar_name="sharpe"
41
+ ret_type="Annual Adj Ret%"
42
+ RF=0.055
43
+ source='auto'; ticker_type='auto'
44
+
45
+ sharpe1m0=get_rolling_sharpe_sortino(ticker,start,end,rar_name="sharpe",ret_type="Monthly Ret%",RF=0)
46
+ sharpe2w=get_rolling_sharpe_sortino(ticker,start,end,rar_name="sharpe",ret_type="Weekly Ret%",RF=0.01759)
47
+ sharpe2m=get_rolling_sharpe_sortino(ticker,start,end,rar_name="sharpe",ret_type="Monthly Ret%",RF=0.01759)
48
+ sharpe2q=get_rolling_sharpe_sortino(ticker,start,end,rar_name="sharpe",ret_type="Quarterly Ret%",RF=0.01759)
49
+ sharpe2y=get_rolling_sharpe_sortino(ticker,start,end,rar_name="sharpe",ret_type="Annual Ret%",RF=0.01759)
50
+
51
+ sortino1=get_rolling_sharpe_sortino(ticker,start,end,rar_name="sortino",ret_type="Monthly Ret%",RF=0)
52
+ sortino2=get_rolling_sharpe_sortino(ticker,start,end,rar_name="sortino",ret_type="Monthly Ret%",RF=0.01759)
53
+
54
+ def get_rolling_sharpe_sortino(ticker,start,end,rar_name="sharpe", \
55
+ ret_type="Monthly Adj Ret%",RF=0,source='auto', \
56
+ ticker_type='auto'):
57
+ """
58
+ 功能:获取一只股票的夏普比率或索替诺比率,基于给定的滚动收益率类型,在指定期间内
59
+ 支持股票和投资组合
60
+ RF: 年化利率,不带百分数
61
+ """
62
+
63
+ #估计滚动窗口日期的提前量
64
+ ret_type_lower=ret_type.lower()
65
+ if 'weekly' in ret_type_lower:
66
+ dateahead=7*2+7 #考虑收益率标准差和节假日
67
+ ret_period='Weekly'
68
+ period_days=5
69
+ elif 'monthly' in ret_type_lower:
70
+ dateahead=31*2+7 #考虑收益率标准差和节假日
71
+ ret_period='Monthly'
72
+ period_days=21
73
+ elif 'quarterly' in ret_type_lower:
74
+ dateahead=(31*3+7)*2 #考虑收益率标准差和节假日
75
+ ret_period='Quarterly'
76
+ period_days=63
77
+ else:
78
+ dateahead=(366+7*3)*2 #考虑收益率滚动+标准差滚动和节假日
79
+ ret_period='Annual'
80
+ period_days=252
81
+
82
+ start1=date_adjust(start,adjust=-dateahead)
83
+
84
+ #判断复权价
85
+ if ('adj' in ret_type_lower):
86
+ adjust='qfq'
87
+ else:
88
+ adjust=''
89
+
90
+ #抓取股价
91
+ #pricedf=get_price(ticker,start1,end,source=source)
92
+ #pricedf=get_price_security(ticker,start1,end,source=source)
93
+ pricedf,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start1,todate=end, \
94
+ adjust=adjust, \
95
+ source=source,ticker_type=ticker_type)
96
+
97
+ if found !='Found':
98
+ print(" #Error(get_rolling_sharpe_sortino): no records found for",ticker)
99
+ return None
100
+
101
+ #计算收益率和收益率标准差
102
+ rardf1=calc_daily_return(pricedf)
103
+ rardf2=calc_rolling_return(rardf1,period=ret_period)
104
+
105
+ if '%' in ret_type:
106
+ RF=RF*100
107
+ if ret_period=='Weekly':
108
+ RF_period=RF/52
109
+ elif ret_period=='Monthly':
110
+ RF_period=RF/12
111
+ elif ret_period=='Quarterly':
112
+ RF_period=RF/4
113
+ else:
114
+ RF_period=RF
115
+
116
+ #收益率减去一个常数其实不影响其标准差的数值,即std(ret-RF)=std(ret)
117
+ try:
118
+ rardf2[ret_type]=rardf2[ret_type] - RF_period
119
+ except:
120
+ print(" #Warning(get_rolling_sharpe_sortino): unsupported ret_type",ret_type)
121
+ return None
122
+
123
+ rardf3=rolling_ret_volatility(rardf2, period=ret_period)
124
+ rardf4=rolling_ret_lpsd(rardf3, period=ret_period)
125
+
126
+ #开始日期富余一段时间,有助于绘图时显示出期望的开始日期
127
+ startpd=pd.to_datetime(date_adjust(start,adjust=-7))
128
+ endpd=pd.to_datetime(end)
129
+
130
+ rardf4['index_tmp']=rardf4.index
131
+ rardf4['index_tmp']=rardf4['index_tmp'].apply(lambda x: pd.to_datetime(x))
132
+ rardf4.set_index(['index_tmp'],inplace=True)
133
+ #rardf4.drop(['index_tmp'],inplace=True)
134
+
135
+ rardf5=rardf4[(rardf4.index >=startpd) & (rardf4.index <=endpd)]
136
+
137
+ #确定风险字段名
138
+ pct_flag=False
139
+ if '%' in ret_type:
140
+ pct_flag=True
141
+
142
+ rar_name_lower=rar_name.lower()
143
+ ret_type_nopct=ret_type.replace('%','')
144
+ if 'sharpe' in rar_name_lower:
145
+ risk_type=ret_type_nopct+' Volatility'
146
+ if pct_flag:
147
+ risk_type=risk_type+'%'
148
+ rardf5[rar_name]=rardf5.apply(lambda x: x[ret_type]/x[risk_type],axis=1)
149
+ elif 'sortino' in rar_name_lower:
150
+ risk_type=ret_type_nopct+' LPSD'
151
+ if pct_flag:
152
+ risk_type=risk_type+'%'
153
+ rardf5[rar_name]=rardf5.apply(lambda x: x[ret_type]/x[risk_type],axis=1)
154
+
155
+ #选择返回字段
156
+ #rardf6=rardf5[['date','source','ticker','footnote',ret_type,risk_type,rar_name]]
157
+ rardf6=rardf5[[rar_name]]
158
+
159
+ return rardf6
160
+ #==============================================================================
161
+ #==============================================================================
162
+ if __name__=='__main__':
163
+ ticker="600519.SS"
164
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
165
+
166
+ start="2023-1-1"
167
+ end="2024-3-15"
168
+ rar_name="sharpe"
169
+ ret_type="Exp Ret%"
170
+ RF=0.01759
171
+ source='auto'
172
+
173
+ sharpe1=get_expanding_sharpe_sortino(ticker,start,end,rar_name="sharpe",RF=0)
174
+ sharpe2=get_expanding_sharpe_sortino(ticker,start,end,rar_name="sharpe",RF=0.01759)
175
+ sortino2=get_expanding_sharpe_sortino(ticker,start,end,rar_name="sortino",RF=0.01759)
176
+
177
+ def get_expanding_sharpe_sortino(ticker,start,end,rar_name="sharpe", \
178
+ ret_type="Exp Adj Ret%",RF=0,source='auto',ticker_type='auto'):
179
+ """
180
+ 功能:获取一只股票的夏普比率或索替诺比率,基于扩展收益率,在指定期间内
181
+ 支持股票和投资组合
182
+ RF: 年化利率,不带百分数
183
+ """
184
+
185
+ #估计扩展窗口日期的提前量
186
+ dateahead=7
187
+ start1=date_adjust(start,adjust=-dateahead)
188
+
189
+ #判断复权价
190
+ ret_type=ret_type.title()
191
+ if ('Adj' in ret_type):
192
+ adjust='qfq'
193
+ else:
194
+ adjust=''
195
+
196
+ #抓取股价
197
+ #pricedf=get_price(ticker,start1,end,source=source)
198
+ #pricedf=get_price_security(ticker,start1,end,source=source)
199
+ pricedf,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start1,todate=end, \
200
+ adjust=adjust, \
201
+ source=source,ticker_type=ticker_type)
202
+
203
+ #计算收益率和收益率标准差
204
+ rardf2=calc_expanding_return(pricedf,start)
205
+
206
+ if '%' in ret_type:
207
+ RF=RF*100
208
+ RF_daily=RF/365
209
+
210
+ #增加距离开始日期的天数
211
+ date0=pd.to_datetime(rardf2.index[0])
212
+ if 'date' not in list(rardf2):
213
+ if 'Date' in list(rardf2):
214
+ rardf2['date']=rardf2['Date']
215
+ else:
216
+ rardf2['date']=rardf2.index
217
+
218
+ rardf2['days']=rardf2['date'].apply(lambda x: days_between_dates(date0,pd.to_datetime(x)))
219
+
220
+ rardf2[ret_type]=rardf2.apply(lambda x: x[ret_type] - RF_daily*x['days'],axis=1)
221
+
222
+ #确定风险字段名,计算风险
223
+ rar_name_lower=rar_name.lower()
224
+ pct_flag=False
225
+ if '%' in ret_type:
226
+ pct_flag=True
227
+ ret_type_nopct=ret_type.replace('%','')
228
+
229
+ if 'sharpe' in rar_name_lower:
230
+ risk_type=ret_type_nopct+' Volatility'
231
+ if pct_flag:
232
+ risk_type=risk_type+'%'
233
+
234
+ #rardf2[risk_type]=rardf2[ret_type].expanding(min_periods=1).apply(lambda x: np.std(x,ddof=1)*np.sqrt(len(x)-1))
235
+ rardf2[risk_type]=rardf2[ret_type].expanding(min_periods=1).apply(lambda x: np.std(x,ddof=1))
236
+ #rardf2[risk_type]=rardf2[ret_type].expanding(min_periods=5).apply(lambda x: np.std(x,ddof=1))
237
+ elif 'sortino' in rar_name_lower:
238
+ risk_type=ret_type_nopct+' LPSD'
239
+ if pct_flag:
240
+ risk_type=risk_type+'%'
241
+
242
+ #rardf2[risk_type]=rardf2[ret_type].expanding(min_periods=1).apply(lambda x: lpsd(x)*np.sqrt(len(x)-1))
243
+ rardf2[risk_type]=rardf2[ret_type].expanding(min_periods=1).apply(lambda x: lpsd(x))
244
+ #rardf2[risk_type]=rardf2[ret_type].expanding(min_periods=5).apply(lambda x: lpsd(x))
245
+
246
+
247
+ #计算RAR
248
+ rardf2[rar_name]=rardf2.apply(lambda x: x[ret_type]/x[risk_type],axis=1)
249
+ rardf3=rardf2.replace(np.nan,0)
250
+
251
+ #选择返回字段
252
+ #rardf4=rardf3[['date','source','ticker','footnote',ret_type,risk_type,rar_name]]
253
+ rardf4=rardf3[[rar_name]]
254
+
255
+ return rardf4
256
+
257
+ #==============================================================================
258
+ if __name__=='__main__':
259
+ ticker="AAPL"
260
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
261
+
262
+ start="2024-1-1"
263
+ end="2024-6-30"
264
+ rar_name="treynor"
265
+ ret_type="Annual Adj Ret%"
266
+ RF=0.055
267
+ regression_period=365
268
+ mktidx='auto'; source='auto'; ticker_type='auto'
269
+
270
+ alpha1m0=get_rolling_treynor_alpha(ticker,start,end,rar_name="alpha",ret_type="Monthly Ret%",RF=0)
271
+ alpha2w=get_rolling_treynor_alpha(ticker,start,end,rar_name="alpha",ret_type="Weekly Ret%",RF=0.01759)
272
+ alpha2m=get_rolling_treynor_alpha(ticker,start,end,rar_name="alpha",ret_type="Monthly Ret%",RF=0.01759)
273
+ alpha2q=get_rolling_treynor_alpha(ticker,start,end,rar_name="alpha",ret_type="Quarterly Ret%",RF=0.01759)
274
+ alpha2y=get_rolling_treynor_alpha(ticker,start,end,rar_name="alpha",ret_type="Annual Ret%",RF=0.01759)
275
+
276
+ def get_rolling_treynor_alpha(ticker,start,end,rar_name="alpha", \
277
+ ret_type="Monthly Adj Ret%",RF=0, \
278
+ regression_period=365,mktidx='auto',source='auto',ticker_type='auto'):
279
+ """
280
+ 功能:获取一只股票的特雷诺比率或阿尔法指数,基于给定的滚动收益率类型,在指定期间内
281
+ 支持股票和投资组合
282
+ RF: 年化利率,不带百分数
283
+ 计算CAPM的期间:默认一年,252个交易日
284
+ ***废弃!!!指标计算有问题
285
+ """
286
+
287
+ #估计需要的日期提前量
288
+ ret_type_lower=ret_type.lower()
289
+ if 'weekly' in ret_type_lower:
290
+ dateahead=7*2+7 #考虑收益率标准差和节假日
291
+ ret_period='Weekly'
292
+ period_days=7
293
+ elif 'monthly' in ret_type_lower:
294
+ dateahead=31*2+7 #考虑收益率标准差和节假日
295
+ ret_period='Monthly'
296
+ period_days=30
297
+ elif 'quarterly' in ret_type_lower:
298
+ dateahead=(31*3+7)*2 #考虑收益率标准差和节假日
299
+ ret_period='Quarterly'
300
+ period_days=90
301
+ else:
302
+ dateahead=(366+7*3)*2 #考虑收益率标准差和节假日
303
+ ret_period='Annual'
304
+ period_days=365
305
+
306
+ #计算日历日regression_period对应的交易日数
307
+ regtrddays=int(252 / 365 * regression_period)
308
+
309
+ #计算滚动查看需要的日期提前量
310
+ start1=date_adjust(start,adjust=-dateahead)
311
+ #计算CAPM需要的日期提前量
312
+ start2=date_adjust(start1,adjust=-regression_period-7*2)
313
+
314
+ #判断复权价
315
+ ret_type=ret_type.title()
316
+ if ('Adj' in ret_type):
317
+ adjust='qfq'
318
+ else:
319
+ adjust=''
320
+
321
+ #CAPM回归,计算贝塔系数
322
+ reg_result,dretdf3=regression_capm(ticker,start2,end, \
323
+ adjust=adjust, \
324
+ RF=RF, \
325
+ regtrddays=regtrddays,mktidx=mktidx, \
326
+ source=source,ticker_type=ticker_type)
327
+
328
+ #计算股票和指数的滚动收益率
329
+ varx=ret_type+'_x' #指数收益率
330
+ vary=ret_type+'_y' #股票收益率
331
+
332
+ pretdf=dretdf3.copy()
333
+ pretdfcols=list(pretdf)
334
+ lndretx='ln_'+pretdfcols[0]
335
+ lndrety='ln_'+pretdfcols[1]
336
+
337
+ #对数法计算滚动收益率
338
+ RF_period=RF/365 * period_days
339
+
340
+ if '%' in ret_type_lower:
341
+ pretdf[lndretx]=pretdf[pretdfcols[0]].apply(lambda x: np.log(1+x/100))
342
+ pretdf[lndrety]=pretdf[pretdfcols[1]].apply(lambda x: np.log(1+x/100))
343
+
344
+ pretdf[varx]=pretdf[lndretx].rolling(window=period_days).apply(lambda x: (np.exp(sum(x))-1)*100)
345
+ pretdf[vary]=pretdf[lndrety].rolling(window=period_days).apply(lambda x: (np.exp(sum(x))-1)*100)
346
+
347
+ else:
348
+ pretdf[lndretx]=pretdf[pretdfcols[0]].apply(lambda x: np.log(1+x))
349
+ pretdf[lndrety]=pretdf[pretdfcols[1]].apply(lambda x: np.log(1+x))
350
+
351
+ pretdf[varx]=pretdf[lndretx].rolling(window=period_days).apply(lambda x: (np.exp(sum(x))-1))
352
+ pretdf[vary]=pretdf[lndrety].rolling(window=period_days).apply(lambda x: (np.exp(sum(x))-1))
353
+
354
+ #合成滚动收益率与贝塔系数
355
+ pretdf1=pd.merge(pretdf[[varx,vary]],reg_result,how='inner',left_index=True,right_index=True)
356
+
357
+ #计算特雷诺比率和阿尔法指标
358
+ if 'treynor' in rar_name.lower():
359
+ pretdf1[rar_name]=pretdf1.apply(lambda x: (x[vary]-RF_period)/x['beta'],axis=1)
360
+ elif 'alpha' in rar_name.lower():
361
+ vary_pred=vary+'_pred'
362
+ pretdf1[vary_pred]=pretdf1.apply(lambda x: RF_period+x['beta']*(x[varx]-RF_period),axis=1)
363
+ pretdf1[rar_name]=pretdf1.apply(lambda x: x[vary]-x[vary_pred],axis=1)
364
+
365
+ #开始日期富余一段时间,有助于绘图时显示出期望的开始日期
366
+ startpd=pd.to_datetime(date_adjust(start,adjust=-7))
367
+ endpd=pd.to_datetime(end)
368
+ pretdf2=pretdf1[(pretdf1.index >=startpd) & (pretdf1.index <=endpd)]
369
+
370
+ pretdf3=pretdf2[[rar_name,'beta']]
371
+
372
+ return pretdf3
373
+
374
+
375
+ def get_rolling_treynor_alpha2(ticker,start,end,rar_name="alpha", \
376
+ ret_type="Monthly Adj Ret%",RF=0, \
377
+ regression_period=365,mktidx='auto',source='auto',ticker_type='auto'):
378
+ """
379
+ 功能:获取一只股票的特雷诺比率或阿尔法指数,基于给定的滚动收益率类型,在指定期间内
380
+ 支持股票和投资组合
381
+ RF: 年化利率,不带百分数
382
+ 计算CAPM的期间:默认一年,252个交易日
383
+ """
384
+
385
+ #估计滚动窗口日期的提前量
386
+ ret_type_lower=ret_type.lower()
387
+ if 'weekly' in ret_type_lower:
388
+ dateahead=7*2+7 #考虑收益率标准差和节假日
389
+ ret_period='Weekly'
390
+ period_days=5
391
+ elif 'monthly' in ret_type_lower:
392
+ dateahead=31*2+7 #考虑收益率标准差和节假日
393
+ ret_period='Monthly'
394
+ period_days=21
395
+ elif 'quarterly' in ret_type_lower:
396
+ dateahead=(31*3+7)*2 #考虑收益率标准差和节假日
397
+ ret_period='Quarterly'
398
+ period_days=63
399
+ else:
400
+ dateahead=(366+7*3)*2 #考虑收益率标准差和节假日
401
+ ret_period='Annual'
402
+ period_days=252
403
+
404
+ #计算滚动查看需要的日期提前量
405
+ start1=date_adjust(start,adjust=-dateahead)
406
+ #计算CAPM需要的日期提前量
407
+ start2=date_adjust(start1,adjust=-regression_period-7*2)
408
+
409
+ #判断复权价
410
+ ret_type=ret_type.title()
411
+ if ('Adj' in ret_type):
412
+ adjust='qfq'
413
+ else:
414
+ adjust=''
415
+
416
+ #获取股票收益率
417
+ if '%' in ret_type:
418
+ if 'Adj' in ret_type:
419
+ dret_type="Daily Adj Ret%"
420
+ else:
421
+ dret_type="Daily Ret%"
422
+ else:
423
+ if 'Adj' in ret_type:
424
+ dret_type="Daily Adj Ret"
425
+ else:
426
+ dret_type="Daily Ret"
427
+
428
+ #抓取股价
429
+ pricedfs,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start2,todate=end, \
430
+ adjust=adjust, \
431
+ source=source,ticker_type=ticker_type)
432
+ if found !='Found':
433
+ print(" #Error(get_rolling_treynor_alpha2): no records found for",ticker)
434
+ return None
435
+
436
+ #计算股票收益率
437
+ rardf1s=calc_daily_return(pricedfs)
438
+ rardf2s=calc_rolling_return(rardf1s,period=ret_period)
439
+
440
+ #抓取指数
441
+ if isinstance(ticker,dict):
442
+ _,mktidx,pftickerlist,_,ticker_type=decompose_portfolio(ticker)
443
+ if 'auto' in mktidx.lower():
444
+ mktidx=get_market_index_code(pftickerlist[0])
445
+ else:
446
+ if 'auto' in mktidx.lower():
447
+ mktidx=get_market_index_code(ticker)
448
+
449
+ marketdf,found=get_price_1ticker_mixed(ticker=mktidx,fromdate=start2,todate=end, \
450
+ adjust=adjust, \
451
+ source=source,ticker_type=ticker_type)
452
+ if found !='Found':
453
+ print(" #Error(get_rolling_treynor_alpha2): no records found for",mktidx)
454
+ return None
455
+
456
+ #计算指数收益率
457
+ rardf1m=calc_daily_return(marketdf)
458
+ rardf2m=calc_rolling_return(rardf1m,period=ret_period)
459
+
460
+
461
+ #计算日历日regression_period对应的交易日数
462
+ regtrddays=int(252 / 365 * regression_period)
463
+
464
+ #CAPM回归,计算贝塔系数
465
+ reg_result,dretdf3=regression_capm_df(rardf1m,rardf1s,mktidx=mktidx,adjust=adjust,RF=RF,regtrddays=regtrddays)
466
+
467
+ #合成滚动收益率与贝塔系数:_x为指数收益率,_y为股票收益率
468
+ pretdfms=pd.merge(rardf2m[[ret_type]],rardf2s[[ret_type]],how='inner',left_index=True,right_index=True)
469
+ pretdf1=pd.merge(pretdfms,reg_result,how='inner',left_index=True,right_index=True)
470
+
471
+ if '%' in ret_type:
472
+ RF=RF*100
473
+ if ret_period=='Weekly':
474
+ RF_period=RF/52
475
+ elif ret_period=='Monthly':
476
+ RF_period=RF/12
477
+ elif ret_period=='Quarterly':
478
+ RF_period=RF/4
479
+ else:
480
+ RF_period=RF
481
+
482
+ #计算特雷诺比率和阿尔法指标
483
+ if 'treynor' in rar_name.lower():
484
+ pretdf1[rar_name]=pretdf1.apply(lambda x: (x[ret_type+'_y']-RF_period)/x['beta'],axis=1)
485
+
486
+ elif 'alpha' in rar_name.lower():
487
+ vary_pred=ret_type+'_pred'
488
+ pretdf1[vary_pred]=pretdf1.apply(lambda x: RF_period+x['beta']*(x[ret_type+'_x']-RF_period),axis=1)
489
+ pretdf1[rar_name]=pretdf1.apply(lambda x: x[ret_type+'_y']-x[vary_pred],axis=1)
490
+
491
+ if '%' in ret_type:
492
+ pretdf1[rar_name]=pretdf1[rar_name]/100
493
+
494
+ #开始日期富余一段时间,有助于绘图时显示出期望的开始日期
495
+ startpd=pd.to_datetime(date_adjust(start,adjust=-7))
496
+ endpd=pd.to_datetime(end)
497
+ pretdf2=pretdf1[(pretdf1.index >=startpd) & (pretdf1.index <=endpd)]
498
+
499
+ pretdf3=pretdf2[[rar_name,'beta']]
500
+
501
+ return pretdf3
502
+
503
+ #==============================================================================
504
+ if __name__=='__main__':
505
+ ticker="AAPL"
506
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
507
+
508
+ start="2024-1-1"
509
+ end="2024-6-30"
510
+ rar_name="alpha"
511
+ ret_type="Exp Adj Ret%"
512
+ RF=0.055
513
+ regression_period=365
514
+ mktidx='auto'; source='auto'
515
+
516
+ alpha1=get_expanding_treynor_alpha(ticker,start,end,rar_name="alpha",ret_type="Exp Ret%",RF=0)
517
+ alpha2=get_expanding_treynor_alpha(ticker,start,end,rar_name="alpha",ret_type="Exp Ret%",RF=0.01759)
518
+
519
+
520
+ def get_expanding_treynor_alpha(ticker,start,end,rar_name="alpha", \
521
+ ret_type="Exp Adj Ret%",RF=0, \
522
+ regression_period=365,mktidx='auto',source='auto',ticker_type='auto'):
523
+ """
524
+ 功能:获取一只股票的特雷诺比率或阿尔法指数,基于扩展收益率类型,在指定期间内
525
+ 支持股票和投资组合
526
+ RF: 年化利率,不带百分数
527
+ 计算CAPM的期间:默认一年,252个交易日=365个日历日
528
+ ***废弃!!!收益率计算有问题
529
+ """
530
+ ret_type_lower=ret_type.lower()
531
+ #计算日历日regression_period对应的交易日数
532
+ regtrddays=int(252 / 365 * regression_period)
533
+
534
+ #计算滚动查看需要的日期提前量:无滚动
535
+ start1=date_adjust(start,adjust=0)
536
+ #计算CAPM需要的日期提前量
537
+ start2=date_adjust(start1,adjust=-regression_period-7*2)
538
+
539
+ #判断复权价
540
+ ret_type=ret_type.title()
541
+ if ('Adj' in ret_type):
542
+ adjust='qfq'
543
+ else:
544
+ adjust=''
545
+
546
+ #CAPM回归,计算贝塔系数
547
+ reg_result,dretdf3=regression_capm(ticker,start2,end, \
548
+ adjust=adjust, \
549
+ RF=RF, \
550
+ regtrddays=regtrddays,mktidx=mktidx, \
551
+ source=source,ticker_type=ticker_type)
552
+
553
+ #计算股票和指数的扩展收益率
554
+ varx=ret_type+'_x'
555
+ vary=ret_type+'_y'
556
+
557
+ startpd=pd.to_datetime(start)
558
+ endpd=pd.to_datetime(end)
559
+ pretdf=dretdf3[(dretdf3.index >= startpd) & (dretdf3.index <= endpd)].copy()
560
+ date0=pd.to_datetime(pretdf.index[0])
561
+
562
+ pretdfcols=list(pretdf)
563
+ #日期首日累计收益率应该为零,先用nan代替,最后再替换为零
564
+ dretx=pretdfcols[0]
565
+ drety=pretdfcols[1]
566
+ lagdretx='lag_'+dretx
567
+ lagdrety='lag_'+drety
568
+ pretdf[lagdretx]=pretdf[dretx].shift(1)
569
+ pretdf[lagdrety]=pretdf[drety].shift(1)
570
+
571
+ pretdf=pretdf.replace(np.nan,0)
572
+
573
+ lndretx='ln_'+dretx
574
+ lndrety='ln_'+drety
575
+
576
+ #对数法计算扩展收益率
577
+ RF_daily=RF/365
578
+ if '%' in ret_type_lower:
579
+ RF_daily=RF/365 * 100
580
+ pretdf[lndretx]=pretdf[lagdretx].apply(lambda x: np.log(1+x/100))
581
+ pretdf[lndrety]=pretdf[lagdrety].apply(lambda x: np.log(1+x/100))
582
+ pretdf[varx]=pretdf[lndretx].expanding(min_periods=1).apply(lambda x: (np.exp(sum(x))-1)*100)
583
+ pretdf[vary]=pretdf[lndrety].expanding(min_periods=1).apply(lambda x: (np.exp(sum(x))-1)*100)
584
+ """
585
+ pretdf[varx]=pretdf[lndretx].expanding(min_periods=5).apply(lambda x: (np.exp(sum(x))-1)*100)
586
+ pretdf[vary]=pretdf[lndrety].expanding(min_periods=5).apply(lambda x: (np.exp(sum(x))-1)*100)
587
+ """
588
+ else:
589
+ pretdf[lndretx]=pretdf[pretdfcols[0]].apply(lambda x: np.log(1+x))
590
+ pretdf[lndrety]=pretdf[pretdfcols[1]].apply(lambda x: np.log(1+x))
591
+ pretdf[varx]=pretdf[lndretx].expanding(min_periods=1).apply(lambda x: (np.exp(sum(x))-1))
592
+ pretdf[vary]=pretdf[lndrety].expanding(min_periods=1).apply(lambda x: (np.exp(sum(x))-1))
593
+ """
594
+ pretdf[varx]=pretdf[lndretx].expanding(min_periods=5).apply(lambda x: (np.exp(sum(x))-1))
595
+ pretdf[vary]=pretdf[lndrety].expanding(min_periods=5).apply(lambda x: (np.exp(sum(x))-1))
596
+ """
597
+ pretdf['Date']=pretdf.index
598
+ pretdf['days']=pretdf['Date'].apply(lambda x: days_between_dates(date0,pd.to_datetime(x)))
599
+
600
+ #合成扩展收益率与贝塔系数
601
+ pretdf1=pd.merge(pretdf[[varx,vary,'days']],reg_result,how='inner',left_index=True,right_index=True)
602
+
603
+ #计算特雷诺比率和阿尔法指标
604
+ if 'treynor' in rar_name.lower():
605
+ pretdf1[rar_name]=pretdf1.apply(lambda x: (x[vary]-RF_daily*x['days'])/x['beta'],axis=1)
606
+ elif 'alpha' in rar_name.lower():
607
+ vary_pred=vary+'_pred'
608
+ pretdf1[vary_pred]=pretdf1.apply(lambda x: RF_daily*x['days']+x['beta']*(x[varx]-RF_daily*x['days']),axis=1)
609
+ pretdf1[rar_name]=pretdf1.apply(lambda x: x[vary]-x[vary_pred],axis=1)
610
+
611
+
612
+ pretdf3=pretdf1[[rar_name,'beta']]
613
+
614
+ return pretdf3
615
+
616
+
617
+ def get_expanding_treynor_alpha2(ticker,start,end,rar_name="alpha", \
618
+ ret_type="Exp Adj Ret%",RF=0, \
619
+ regression_period=365,mktidx='auto',source='auto',ticker_type='auto'):
620
+ """
621
+ 功能:获取一只股票的特雷诺比率或阿尔法指数,基于扩展收益率类型,在指定期间内
622
+ 支持股票和投资组合
623
+ RF: 年化利率,不带百分数
624
+ 计算CAPM的期间:默认一年,252个交易日=365个日历日
625
+ """
626
+ ret_type_lower=ret_type.lower()
627
+
628
+ #计算滚动查看需要的日期提前量:无滚动
629
+ start1=date_adjust(start,adjust=0)
630
+ #计算CAPM需要的日期提前量
631
+ start2=date_adjust(start1,adjust=-regression_period-7*2)
632
+
633
+ #判断复权价
634
+ ret_type=ret_type.title()
635
+ if ('Adj' in ret_type):
636
+ adjust='qfq'
637
+ else:
638
+ adjust=''
639
+
640
+ #抓取股价
641
+ pricedfs,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start2,todate=end, \
642
+ adjust=adjust, \
643
+ source=source,ticker_type=ticker_type)
644
+ if found !='Found':
645
+ print(" #Error(get_expanding_treynor_alpha2): no records found for",ticker)
646
+ return None
647
+
648
+ #计算股票扩展收益率
649
+ rardf1s=calc_daily_return(pricedfs)
650
+ rardf2s=calc_expanding_return(pricedfs,start)
651
+
652
+ if '%' in ret_type:
653
+ RF=RF*100
654
+ RF_daily=RF/365
655
+
656
+ #增加距离开始日期的天数
657
+ date0=pd.to_datetime(rardf2s.index[0])
658
+ if 'date' not in list(rardf2s):
659
+ if 'Date' in list(rardf2s):
660
+ rardf2s['date']=rardf2s['Date']
661
+ else:
662
+ rardf2s['date']=rardf2s.index
663
+
664
+ rardf2s['days']=rardf2s['date'].apply(lambda x: days_between_dates(date0,pd.to_datetime(x)))
665
+ rardf2s[ret_type+'_RP']=rardf2s.apply(lambda x: x[ret_type] - RF_daily*x['days'],axis=1)
666
+
667
+ #抓取指数
668
+ if isinstance(ticker,dict):
669
+ _,mktidx,pftickerlist,_,ticker_type=decompose_portfolio(ticker)
670
+ if 'auto' in mktidx.lower():
671
+ mktidx=get_market_index_code(pftickerlist[0])
672
+ else:
673
+ if 'auto' in mktidx.lower():
674
+ mktidx=get_market_index_code(ticker)
675
+
676
+ marketdf,found=get_price_1ticker_mixed(ticker=mktidx,fromdate=start2,todate=end, \
677
+ adjust=adjust, \
678
+ source=source,ticker_type=ticker_type)
679
+ if found !='Found':
680
+ print(" #Error(get_expanding_treynor_alpha2): no records found for",mktidx)
681
+ return None
682
+
683
+ #计算指数扩展收益率
684
+ rardf1m=calc_daily_return(marketdf)
685
+ rardf2m=calc_expanding_return(marketdf,start)
686
+ #增加距离开始日期的天数
687
+ date0=pd.to_datetime(rardf2m.index[0])
688
+ if 'date' not in list(rardf2m):
689
+ if 'Date' in list(rardf2m):
690
+ rardf2m['date']=rardf2m['Date']
691
+ else:
692
+ rardf2m['date']=rardf2m.index
693
+
694
+ rardf2m['days']=rardf2m['date'].apply(lambda x: days_between_dates(date0,pd.to_datetime(x)))
695
+ rardf2m[ret_type+'_RP']=rardf2m.apply(lambda x: x[ret_type] - RF_daily*x['days'],axis=1)
696
+
697
+ #计算日历日regression_period对应的交易日数
698
+ regtrddays=int(252 / 365 * regression_period)
699
+ #CAPM回归,计算贝塔系数
700
+ reg_result,dretdf3=regression_capm_df(rardf1m,rardf1s,mktidx=mktidx,adjust=adjust,RF=RF,regtrddays=regtrddays)
701
+
702
+ #合成扩展收益率与贝塔系数:_x为指数收益率,_y为股票收益率,_RP为股票风险溢价
703
+ pretdfms=pd.merge(rardf2m[[ret_type,ret_type+'_RP','days']],rardf2s[[ret_type,ret_type+'_RP']],how='inner',left_index=True,right_index=True)
704
+ pretdf1=pd.merge(pretdfms,reg_result,how='inner',left_index=True,right_index=True)
705
+
706
+ #计算特雷诺比率和阿尔法指标
707
+ if 'treynor' in rar_name.lower():
708
+ pretdf1[rar_name]=pretdf1.apply(lambda x: x[ret_type+'_RP_y']/x['beta'],axis=1)
709
+ elif 'alpha' in rar_name.lower():
710
+ vary_pred=ret_type+'_y_pred'
711
+ pretdf1[vary_pred]=pretdf1.apply(lambda x: RF_daily*x['days']+x['beta']*x[ret_type+'_RP_x'],axis=1)
712
+ pretdf1[rar_name]=pretdf1.apply(lambda x: x[ret_type+'_y']-x[vary_pred],axis=1)
713
+
714
+ if '%' in ret_type:
715
+ pretdf1[rar_name]=pretdf1[rar_name]/100
716
+
717
+ pretdf3=pretdf1[[rar_name,'beta']]
718
+
719
+ return pretdf3
720
+ #==============================================================================
721
+ if __name__=='__main__':
722
+ ticker='301161.SZ'
723
+ ticker="600519.SS"
724
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
725
+
726
+ rar_name="sharpe"
727
+ rar_name="alpha"
728
+
729
+ ret_type="Annual Adj Ret%"
730
+ ret_type="Monthly Adj Ret%"
731
+ ret_type="Exp Ret%"
732
+
733
+ start="2024-1-1"; end="2024-9-30"
734
+ RF=0.01759
735
+ regression_period=365
736
+ mktidx='auto'; source='auto'; ticker_type='auto'
737
+
738
+ alpha1=get_rar(ticker,start,end,rar_name="alpha",ret_type="Exp Ret%",RF=0)
739
+ alpha2=get_rar(ticker,start,end,rar_name="alpha",ret_type="Exp Ret%",RF=0.01759)
740
+
741
+
742
+ def get_rar(ticker,start,end,rar_name="sharpe",ret_type="Monthly Adj Ret%", \
743
+ RF=0,regression_period=365,mktidx='auto',source='auto',ticker_type='auto'):
744
+ """
745
+ 功能:获取一只股票的收益-风险性价比指标,在指定期间内,支持股票和投资组合
746
+ 支持滚动收益率和扩展收益率
747
+ 滚动收益率支持周、月、季度和年度,默认为年度
748
+ 支持特雷诺比率、夏普比率、所提诺比率和阿尔法指标
749
+
750
+ RF: 年化利率,不带百分数
751
+ 计算CAPM的期间:默认一年,252个交易日=365个日历日
752
+ """
753
+
754
+ ret_type_lower=ret_type.lower()
755
+ ret_type_title=ret_type.title() #字符串每个单词首字母大写
756
+ rar_name_lower=rar_name.lower()
757
+
758
+ rardf=None
759
+ #判断是否扩展收益率
760
+ if 'exp' not in ret_type_lower:
761
+ if ('sharpe' in rar_name_lower) or ('sortino' in rar_name_lower):
762
+ rardf=get_rolling_sharpe_sortino(ticker=ticker,start=start,end=end, \
763
+ rar_name=rar_name_lower, \
764
+ ret_type=ret_type_title,RF=RF, \
765
+ source=source,ticker_type=ticker_type)
766
+ elif ('alpha' in rar_name_lower) or ('treynor' in rar_name_lower):
767
+ rardf=get_rolling_treynor_alpha2(ticker=ticker,start=start,end=end, \
768
+ rar_name=rar_name_lower, \
769
+ ret_type=ret_type_title,RF=RF, \
770
+ regression_period=regression_period, \
771
+ mktidx=mktidx,source=source,ticker_type=ticker_type)
772
+
773
+ else:
774
+ if ('sharpe' in rar_name_lower) or ('sortino' in rar_name_lower):
775
+ rardf=get_expanding_sharpe_sortino(ticker=ticker,start=start,end=end, \
776
+ rar_name=rar_name_lower, \
777
+ ret_type=ret_type_title,RF=RF, \
778
+ source=source,ticker_type=ticker_type)
779
+ elif ('alpha' in rar_name_lower) or ('treynor' in rar_name_lower):
780
+ rardf=get_expanding_treynor_alpha2(ticker=ticker,start=start,end=end, \
781
+ rar_name=rar_name_lower, \
782
+ ret_type=ret_type_title,RF=RF, \
783
+ regression_period=regression_period, \
784
+ mktidx=mktidx,source=source,ticker_type=ticker_type)
785
+
786
+ return rardf
787
+
788
+ #==============================================================================
789
+ if __name__=='__main__':
790
+ ticker="301161.SZ"
791
+ ticker="600519.SS"
792
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
793
+
794
+ start="2024-1-1"; end="2024-9-30"
795
+ rar='sharpe'
796
+ rar=['sharpe','sortino','treynor','alpha']
797
+
798
+ ret_type="Annual Adj Ret%"
799
+ ret_type="Monthly Adj Ret%"
800
+ RF=0.01759
801
+ regression_period=365
802
+
803
+ graph=True; axhline_value=0; axhline_label=''
804
+ printout=False; sortby='tpw_mean'; trailing=20; trend_threshhold=0.001
805
+ annotate=False
806
+ mktidx='auto'; source='auto'; ticker_type='auto'
807
+
808
+ rars=compare_1ticker_mrar(ticker=ticker,start=start,end=end,rar=rar,printout=True)
809
+
810
+ def compare_1ticker_mrar(ticker,start,end,rar=['sharpe','sortino','treynor','alpha'], \
811
+ ret_type="Annual Adj Ret%",RF=0,regression_period=365, \
812
+ attention_value='',attention_value_area='', \
813
+ attention_point='',attention_point_area='', \
814
+ band_area='', \
815
+ graph=True,loc1='best', \
816
+ axhline_value=0,axhline_label='',facecolor='whitesmoke', \
817
+ printout=False,sortby='tpw_mean',trailing=7,trend_threshhold=0.01, \
818
+ annotate=False,annotate_value=False, \
819
+ mark_top=False,mark_bottom=False, \
820
+ mark_start=False,mark_end=False, \
821
+ mktidx='auto',source='auto',ticker_type='auto'):
822
+ """
823
+ 功能:一只股票,对比其多个rar,支持股票和投资组合
824
+ """
825
+
826
+ import os,sys
827
+ class HiddenPrints:
828
+ def __enter__(self):
829
+ self._original_stdout = sys.stdout
830
+ sys.stdout = open(os.devnull, 'w')
831
+
832
+ def __exit__(self, exc_type, exc_val, exc_tb):
833
+ sys.stdout.close()
834
+ sys.stdout = self._original_stdout
835
+
836
+ if isinstance(ticker,list):
837
+ ticker=ticker[0] #将列表转换为字符串
838
+ if isinstance(rar,str):
839
+ rar=[rar] #将字符串转换为列表,避免下面的循环出错
840
+ if isinstance(ret_type,list):
841
+ ret_type=ret_type[0]
842
+ if isinstance(RF,list):
843
+ RF=RF[0]
844
+ if isinstance(regression_period,list):
845
+ regression_period=regression_period[0]
846
+
847
+ tname=ticker_name(ticker,ticker_type)
848
+ print(" Working on different rars for",tname,"\b, please wait ......\n")
849
+
850
+ #预处理ticker_type
851
+ ticker_type=ticker_type_preprocess_mticker_mixed(ticker,ticker_type)
852
+
853
+ df=pd.DataFrame()
854
+ for t in rar:
855
+ #关闭print输出
856
+ with HiddenPrints():
857
+ df_tmp=get_rar(ticker,start,end,t,ret_type=ret_type, \
858
+ RF=RF,regression_period=regression_period, \
859
+ mktidx=mktidx,source=source,ticker_type=ticker_type)
860
+
861
+ if df_tmp is None:
862
+ break
863
+ else:
864
+ dft=df_tmp[[t]]
865
+
866
+ if len(df)==0:
867
+ df=dft #第一个
868
+ else:
869
+ df=pd.merge(df,dft,how='outer',left_index=True,right_index=True)
870
+
871
+ if len(df)==0:
872
+ print(" #Error(compare_1ticker_mrar): rar data inaccessible for",tname,"between",start,end)
873
+ return None
874
+
875
+ #以下仅用于绘图或制表
876
+ df1=df.copy()
877
+ for c in list(df1):
878
+ if df1[c].max() > axhline_value and df1[c].min() < axhline_value:
879
+ axhline_label='零线'
880
+
881
+ cname=ectranslate(c)
882
+ df1.rename(columns={c:cname},inplace=True)
883
+
884
+ # 将band_area中的ticker替换为tname
885
+ if band_area != '':
886
+ for index, item in enumerate(band_area):
887
+ if item == c:
888
+ band_area[index] = cname
889
+
890
+ footnote1=text_lang("评估值基于","Note: RaR based on ")+ectranslate(ret_type)
891
+ if RF !=0:
892
+ footnote2=text_lang(",年化无风险利率为",", RF = ")+str(round(RF*100,4))+text_lang('%','% pa')
893
+ else:
894
+ footnote2=text_lang(",不考虑年化无风险利率时。",", RF = 0 pa")
895
+
896
+ footnote3=''
897
+ if 'treynor' in rar or 'alpha' in rar:
898
+ if mktidx != 'auto':
899
+ mktidx_text=ticker_name(mktidx)
900
+ footnote3x=text_lang(",市场指数基于",", using ")+mktidx_text
901
+ footnote3=text_lang("\nCAPM回归期间","\nCAPM rolling ")+str(regression_period)+text_lang("个自然日"," days, ")+footnote3x
902
+ else:
903
+ footnote3=text_lang(",CAPM回归期间",", CAPM rolling ")+str(regression_period)+text_lang("个自然日"," days")
904
+
905
+
906
+ import datetime; todaydt = datetime.date.today()
907
+ footnote4=text_lang("数据来源: 综合新浪/EM/Stooq/Yahoo/SWHY,","Data source: Sina/Stooq/Yahoo, ")+str(todaydt)
908
+ if footnote3 !='':
909
+ footnotex=footnote1+footnote2+footnote3+'\n'+footnote4
910
+ else:
911
+ footnotex=footnote1+footnote2+footnote3+'\n'+footnote4
912
+
913
+ #绘图
914
+ if graph:
915
+ y_label=''
916
+ import datetime; todaydt = datetime.date.today()
917
+ x_label=text_lang("数据来源: 综合新浪/EM/Stooq/Yahoo/SWHY,","Data source: Sina/Stooq/Yahoo, ")+str(todaydt)
918
+ title_txt=text_lang("风险调整收益:","Risk-adjusted Return: ")+tname
919
+
920
+ draw_lines(df1,y_label,x_label=footnotex, \
921
+ axhline_value=axhline_value,axhline_label=axhline_label, \
922
+ title_txt=title_txt,data_label=False, \
923
+ attention_value=attention_value,attention_value_area=attention_value_area, \
924
+ attention_point=attention_point,attention_point_area=attention_point_area, \
925
+ annotate=annotate,annotate_value=annotate, \
926
+ band_area=band_area, \
927
+ mark_top=mark_top,mark_bottom=mark_bottom, \
928
+ mark_start=mark_start,mark_end=mark_end, \
929
+ facecolor=facecolor,loc=loc1)
930
+
931
+ #制表
932
+ recommenddf=pd.DataFrame()
933
+ if printout:
934
+ if sortby=='tpw_mean':
935
+ sortby_txt=text_lang('按推荐标记+近期优先加权平均值降序排列',"by Recommend + RWA, Descending")
936
+ elif sortby=='min':
937
+ sortby_txt=text_lang('按推荐标记+最小值降序排列',"by Recommend + Min, Descending")
938
+ elif sortby=='mean':
939
+ sortby_txt=text_lang('按推荐标记+平均值降序排列',"by Recommend + Mean, Descending")
940
+ elif sortby=='median':
941
+ sortby_txt=text_lang('按推荐标记+中位数值降序排列',"by Recommend + Median, Descending")
942
+ elif sortby=='trailing':
943
+ sortby_txt=text_lang('按推荐标记+短期均值走势降序排列',"by Recommend + Recent Trend, Descending")
944
+
945
+ #title_txt='***** 风险调整收益评估:'+tname+','+sortby_txt+' *****'
946
+ if isinstance(rar,list) and len(rar)==1:
947
+ rar=rar[0]
948
+ title_txt=text_lang('风险调整收益评估:',"RaR Evaluation: ")+str(ectranslate(rar))+text_lang(',',', ')+sortby_txt
949
+
950
+ footnote6=text_lang('期间:',"Period: ")+str(start)+text_lang('至'," to ")+str(end)+text_lang(";近期指近","\nRecent trend: ")+str(trailing)+text_lang("个交易日。趋势变化率阈值:", " days. Trend threshhold ")+str(trend_threshhold)
951
+ footnote7=text_lang("近期优先趋势和星号为风险调整收益指标加趋势等多项因素综合研判,最多五颗星","Recommend max 5 stars. RWA = Recent-priority Weighted Average")
952
+ footnotey=footnote6+'\n'+footnote7+'\n'+footnotex
953
+
954
+ recommenddf=descriptive_statistics2(df1,title_txt,footnotey,decimals=4, \
955
+ sortby=sortby,recommend_only=True,trailing=trailing, \
956
+ trend_threshhold=trend_threshhold,facecolor=facecolor)
957
+
958
+ return df,recommenddf
959
+
960
+ #==============================================================================
961
+ if __name__=='__main__':
962
+ ticker=["600519.SS","000858.SZ"]
963
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
964
+
965
+ ticker=['601628.SS','601319.SS','601318.SS','00966.HK']
966
+
967
+ start="2023-6-27"
968
+ end="2024-6-27"
969
+ rar='sharpe'
970
+ RF=0.01692
971
+ printout=True
972
+
973
+ ret_type="Annual Ret%"; regression_period=365
974
+ graph=True; loc1='best'
975
+ axhline_value=0; axhline_label=''
976
+ sortby='tpw_mean'; trailing=7; trend_threshhold=0.01
977
+ annotate=False; annotate_value=False
978
+ mark_top=False; mark_bottom=False; mark_end=False
979
+ mktidx='auto'; source='auto'
980
+ style_print=True; ticker_type='auto';facecolor='whitesmoke'
981
+
982
+ rars=compare_mticker_1rar(ticker=["600519.SS","000858.SZ"],start="2024-1-1",end="2024-6-16",rar='sharpe',printout=True)
983
+
984
+ def compare_mticker_1rar(ticker,start,end,rar='sharpe', \
985
+ ret_type="Annual Adj Ret%",RF=0,regression_period=365, \
986
+ attention_value='',attention_value_area='', \
987
+ attention_point='',attention_point_area='', \
988
+ band_area='', \
989
+ graph=True,loc1='best', \
990
+ axhline_value=0,axhline_label='', \
991
+ printout=False,sortby='tpw_mean',trailing=7,trend_threshhold=0.01, \
992
+ annotate=False,annotate_value=False, \
993
+ mark_top=False,mark_bottom=False, \
994
+ mark_start=False,mark_end=False, \
995
+ mktidx='auto',source='auto', \
996
+ style_print=True,ticker_type='auto',facecolor='whitesmoke'):
997
+ """
998
+ 功能:多只股票,对比其同一个rar,支持股票和投资组合
999
+ """
1000
+
1001
+ import os,sys
1002
+ class HiddenPrints:
1003
+ def __enter__(self):
1004
+ self._original_stdout = sys.stdout
1005
+ sys.stdout = open(os.devnull, 'w')
1006
+
1007
+ def __exit__(self, exc_type, exc_val, exc_tb):
1008
+ sys.stdout.close()
1009
+ sys.stdout = self._original_stdout
1010
+
1011
+ #转换字符串和列表,避免下面的循环出错
1012
+ if not isinstance(ticker,list):
1013
+ ticker=[ticker]
1014
+ if isinstance(rar,list):
1015
+ rar=rar[0]
1016
+ if isinstance(ret_type,list):
1017
+ ret_type=ret_type[0]
1018
+ if isinstance(RF,list):
1019
+ RF=RF[0]
1020
+ if isinstance(regression_period,list):
1021
+ regression_period=regression_period[0]
1022
+ print(" Working on",rar,"ratio, please wait ......\n")
1023
+
1024
+ #预处理ticker_type
1025
+ ticker_type_list=ticker_type_preprocess_mticker_mixed(ticker,ticker_type)
1026
+
1027
+ df=pd.DataFrame()
1028
+ for t in ticker:
1029
+ pos=ticker.index(t)
1030
+ tt=ticker_type_list[pos]
1031
+ #关闭print输出
1032
+ with HiddenPrints():
1033
+ df_tmp=get_rar(t,start,end,rar_name=rar,ret_type=ret_type, \
1034
+ RF=RF,regression_period=regression_period, \
1035
+ mktidx=mktidx,source=source,ticker_type=tt)
1036
+
1037
+ if df_tmp is None:
1038
+ #break
1039
+ print(" #Warning(compare_mticker_1rar): data not available for",ticker_name(t,tt),"between",start,"and",end)
1040
+ continue
1041
+ else:
1042
+ dft=df_tmp[[rar]]
1043
+ tname=ticker_name(t,tt)
1044
+ dft.rename(columns={rar:tname},inplace=True)
1045
+
1046
+ # 将band_area中的ticker替换为tname
1047
+ if band_area != '':
1048
+ for index, item in enumerate(band_area):
1049
+ if item == t:
1050
+ band_area[index] = tname
1051
+
1052
+ if len(df)==0: #第一个
1053
+ df=dft
1054
+ else:
1055
+ df=pd.merge(df,dft,how='outer',left_index=True,right_index=True)
1056
+
1057
+ if len(df)==0:
1058
+ print(" #Error(compare_mticker_1rar): data not available for",ticker,"between",start,"and",end)
1059
+ return None
1060
+
1061
+ #仅用于绘图和制表
1062
+ df1=df.copy()
1063
+ #进行空缺值填充,以便绘图连续
1064
+ df1.fillna(method='bfill',inplace=True)
1065
+ df1.fillna(method='ffill',inplace=True)
1066
+
1067
+ for c in list(df1):
1068
+ if df1[c].max() > axhline_value and df1[c].min() < axhline_value:
1069
+ axhline_label='零线' #显示零线,但不标注图例
1070
+ #df1.rename(columns={c:ticker_name(c)},inplace=True)
1071
+
1072
+ #共同脚注
1073
+ rar_text=ectranslate(rar)
1074
+ if check_language()=="English":
1075
+ if rar != "alpha":
1076
+ rar_text=rar_text.title()+" Ratio"
1077
+ else:
1078
+ #rar_text=rar_text.title()
1079
+ rar_text="Jensen Alpha"
1080
+
1081
+ footnote1=text_lang("注:","Note: ")+rar_text.capitalize()+text_lang("基于"," based on ")+ectranslate(ret_type)+text_lang("。",", ")
1082
+ """
1083
+ if RF !=0:
1084
+ footnote2=text_lang("年化无风险利率","RF = ")+str(round(RF*100,4))+text_lang('%。','% pa')
1085
+ else:
1086
+ footnote2=text_lang("假设年化无风险利率为零。","assuming RF = 0 pa.")
1087
+ """
1088
+ footnote2=text_lang("年化无风险利率","RF = ")+str(round(RF*100,4))+text_lang('%。','% pa')
1089
+
1090
+ footnote3=''
1091
+ if rar.lower() in ['treynor','alpha']:
1092
+ mktidx_text=''
1093
+ if mktidx != 'auto':
1094
+ mktidx_text=ticker_name(mktidx)
1095
+
1096
+ if mktidx != 'auto':
1097
+ footnote3=text_lang("CAPM回归期间","\nCAPM rolling ")+str(regression_period)+text_lang("个自然日,"," days, ")+ \
1098
+ text_lang("市场指数基于","using ")+mktidx_text
1099
+ else:
1100
+ footnote3=text_lang("CAPM回归期间","\nCAPM rolling ")+str(regression_period)+text_lang("个自然日"," days")
1101
+
1102
+ import datetime; todaydt = datetime.date.today()
1103
+ footnote4=text_lang("数据来源: 综合新浪/EM/Stooq/Yahoo/SWHY,","Data source: Sina/Stooq/Yahoo, ")+str(todaydt)
1104
+ if footnote3 !='':
1105
+ footnotex=footnote1+footnote2+footnote3+'\n'+footnote4
1106
+ else:
1107
+ footnotex=footnote1+footnote2+footnote3+'\n'+footnote4
1108
+
1109
+ #绘图
1110
+ if graph:
1111
+
1112
+ title_txt=text_lang("风险调整收益:","Risk-adjusted Return: ")+rar_text
1113
+ y_label=rar_text
1114
+
1115
+ draw_lines(df1,y_label,x_label=footnotex, \
1116
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1117
+ title_txt=title_txt,data_label=False, \
1118
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1119
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1120
+ band_area=band_area, \
1121
+ annotate=annotate,annotate_value=annotate, \
1122
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1123
+ mark_start=mark_start,mark_end=mark_end, \
1124
+ facecolor=facecolor,loc=loc1)
1125
+
1126
+ #制表
1127
+ recommenddf=pd.DataFrame()
1128
+ if printout:
1129
+ if sortby=='tpw_mean':
1130
+ sortby_txt=text_lang('按推荐标记+近期优先加权平均值降序排列',"by Recommend + RWA, Descending")
1131
+ elif sortby=='min':
1132
+ sortby_txt=text_lang('按推荐标记+最小值降序排列',"by Recommend + Min, Descending")
1133
+ elif sortby=='mean':
1134
+ sortby_txt=text_lang('按推荐标记+平均值降序排列',"by Recommend + Mean, Descending")
1135
+ elif sortby=='median':
1136
+ sortby_txt=text_lang('按推荐标记+中位数值降序排列',"by Recommend + Median, Descending")
1137
+ elif sortby=='trailing':
1138
+ sortby_txt=text_lang('按推荐标记+短期均值走势降序排列',"by Recommend + Recent Trend, Descending")
1139
+
1140
+ #title_txt='***** 风险调整收益评估:基于'+ectranslate(rar)+','+sortby_txt+' *****'
1141
+ title_txt=text_lang('风险调整收益评估:',"RaR Evaluation: ")+rar_text+text_lang(',',', ')+sortby_txt
1142
+
1143
+ footnote6=text_lang('期间:',"Period: ")+str(start)+text_lang('至'," to ")+str(end)+text_lang(";近期指近","\nRecent trend: ")+str(trailing)+text_lang("个交易日。趋势变化率阈值:", " trading days. Trend change threshhold: ")+str(trend_threshhold)
1144
+ footnote7=text_lang("近期优先趋势和星号为风险调整收益指标加趋势等多项因素综合研判,最多五颗星","Recommend max 5 stars. RWA = Recent-priority Weighted Average")
1145
+ footnotey=footnote6+'\n'+footnote7+'\n'+footnotex
1146
+
1147
+ #不能简单删除含有Nan的行,否则导致清空df1,应该进行填充
1148
+ #df1.dropna(inplace=True,axis=1)
1149
+ recommenddf=descriptive_statistics2(df1,title_txt,footnotey,decimals=4, \
1150
+ sortby=sortby,recommend_only=True,trailing=trailing, \
1151
+ trend_threshhold=trend_threshhold, \
1152
+ style_print=style_print,facecolor=facecolor)
1153
+
1154
+ return df,recommenddf
1155
+
1156
+ #==============================================================================
1157
+ if __name__=='__main__':
1158
+ ticker=["600519.SS","000858.SZ"]
1159
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
1160
+
1161
+ start="2024-1-1"
1162
+ end="2024-3-15"
1163
+ rar=['sharpe','alpha']
1164
+ ret_type="Monthly Ret%"
1165
+ RF=0.01759
1166
+ regression_period=365
1167
+
1168
+ graph=False; axhline_value=0; axhline_label=''
1169
+ printout=True; sortby='tpw_mean'; trailing=5; trend_threshhold=0.01
1170
+ annotate=False
1171
+ mktidx='auto'; source='auto'
1172
+
1173
+ rars=compare_mticker_mrar(ticker,start,end,rar,graph=False,printout=True)
1174
+
1175
+ def compare_mticker_mrar(ticker,start,end,rar=['sharpe','alpha','sortino','treynor'], \
1176
+ ret_type="Annual Adj Ret%",RF=0,regression_period=365, \
1177
+ attention_value='',attention_value_area='', \
1178
+ attention_point='',attention_point_area='', \
1179
+ band_area='', \
1180
+ graph=True,loc1='best', \
1181
+ axhline_value=0,axhline_label='', \
1182
+ printout=True,sortby='tpw_mean',trailing=7,trend_threshhold=0.01, \
1183
+ annotate=False,annotate_value=False, \
1184
+ mark_top=False,mark_bottom=False, \
1185
+ mark_start=False,mark_end=False, \
1186
+ mktidx='auto',source='auto', \
1187
+ ticker_type='auto',facecolor='whitesmoke'):
1188
+ """
1189
+ 功能:多只股票,多个rar,综合对比和排列。支持股票和投资组合
1190
+ """
1191
+
1192
+ #避免下面的循环出错
1193
+ if isinstance(rar,str):
1194
+ rar=[rar]
1195
+ if isinstance(ret_type,list):
1196
+ ret_type=ret_type[0]
1197
+ if isinstance(RF,list):
1198
+ RF=RF[0]
1199
+ if isinstance(regression_period,list):
1200
+ regression_period=regression_period[0]
1201
+
1202
+ #print(" Starting to compare multiple tickers with multiple RARs ......")
1203
+
1204
+ df=pd.DataFrame()
1205
+ for r in rar:
1206
+ #with HiddenPrints(): #此项将压制所有print输出,造成表头脚注不显示
1207
+ _,df_tmp=compare_mticker_1rar(ticker=ticker,start=start,end=end,rar=r, \
1208
+ ret_type=ret_type,RF=RF,regression_period=regression_period, \
1209
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1210
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1211
+ band_area=band_area, \
1212
+ graph=graph,facecolor=facecolor, \
1213
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1214
+ printout=printout,sortby=sortby, \
1215
+ trailing=trailing,trend_threshhold=trend_threshhold, \
1216
+ annotate=annotate,annotate_value=annotate, \
1217
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1218
+ mark_start=mark_start,mark_end=mark_end, \
1219
+ mktidx=mktidx,source=source,style_print=True, \
1220
+ ticker_type=ticker_type,loc1=loc1)
1221
+ if df_tmp is None:
1222
+ break
1223
+ else:
1224
+ dft=df_tmp[['比较对象','推荐标记']]
1225
+ dft.rename(columns={'推荐标记':r},inplace=True)
1226
+
1227
+ if len(df)==0: #第一个
1228
+ df=dft
1229
+ else:
1230
+ df=pd.merge(df,dft,how='left',left_on='比较对象',right_on='比较对象')
1231
+
1232
+ df['综合推荐']=df[rar].sum(axis=1)
1233
+ df.sort_values(by='综合推荐',ascending=False,inplace=True)
1234
+
1235
+ df['综合推荐']=df['综合推荐'].apply(lambda x: generate_stars(hzlen(x) / len(rar)))
1236
+ for c in list(df):
1237
+ df.rename(columns={c:ectranslate(c)},inplace=True)
1238
+
1239
+ if printout:
1240
+ # 设置显示选项为True,开启Unicode字符支持
1241
+ pd.set_option('display.unicode.ambiguous_as_wide', True)
1242
+ pd.set_option('display.unicode.east_asian_width', True)
1243
+ pd.set_option('display.width', 180) #设置打印宽度(**重要**)
1244
+
1245
+ if sortby=='tpw_mean':
1246
+ sortby_txt=text_lang('按推荐标记+近期优先加权平均值降序排列',"by Recommend + RWA, Descending")
1247
+ elif sortby=='min':
1248
+ sortby_txt=text_lang('按推荐标记+最小值降序排列',"by Recommend + Min, Descending")
1249
+ elif sortby=='mean':
1250
+ sortby_txt=text_lang('按推荐标记+平均值降序排列',"by Recommend + Mean, Descending")
1251
+ elif sortby=='median':
1252
+ sortby_txt=text_lang('按推荐标记+中位数值降序排列',"by Recommend + Median, Descending")
1253
+ elif sortby=='trailing':
1254
+ sortby_txt=text_lang('按推荐标记+短期均值走势降序排列',"by Recommend + Recent Trend, Descending")
1255
+
1256
+ df1=df.copy()
1257
+ df1.reset_index(drop=True,inplace=True)
1258
+ df1.index=df1.index + 1
1259
+
1260
+ # 处理表格标题
1261
+ #titletxt='===风险调整收益综合对比:'+sortby_txt+'==='
1262
+ titletxt=text_lang('风险调整收益综合对比:',"Risk-adjusted Return Overall Evaluation: ")+sortby_txt
1263
+ """
1264
+ #print("\n"+titletxt)
1265
+ df2=df1.style.set_caption(titletxt).set_table_styles(
1266
+ [{'selector':'caption',
1267
+ 'props':[('color','black'),('font-size','16px'),('font-weight','bold')]}])
1268
+
1269
+ df3= df2.set_properties(**{'text-align':'center'})
1270
+ from IPython.display import display
1271
+ display(df3)
1272
+ """
1273
+ """
1274
+ disph=df1.style.hide() #不显示索引列
1275
+ dispp=disph.format(precision=3) #设置带有小数点的列精度调整为小数点后3位
1276
+ #设置标题/列名
1277
+ dispt=dispp.set_caption(titletxt).set_table_styles(
1278
+ [{'selector':'caption', #设置标题
1279
+ 'props':[('color','black'),('font-size','16px'),('font-weight','bold')]}, \
1280
+ {'selector':'th.col_heading', #设置列名
1281
+ 'props':[('color','black'),('font-size','16px'),('background-color',facecolor),('text-align','center'),('margin','auto')]}])
1282
+ #设置列数值对齐
1283
+ dispt1=dispt.set_properties(**{'font-size':'16px'})
1284
+ dispf=dispt1.set_properties(**{'text-align':'center'})
1285
+ #设置前景背景颜色
1286
+ try:
1287
+ dispf2=dispf.set_properties(**{'background-color':facecolor,'color':'black'})
1288
+ except:
1289
+ print(" #Warning(compare_mticker_mrar): color",facecolor,"is unsupported, changed to default setting")
1290
+ dispf2=dispf.set_properties(**{'background-color':'whitesmoke','color':'black'})
1291
+
1292
+ from IPython.display import display
1293
+ display(dispf2)
1294
+ """
1295
+
1296
+ """
1297
+ print(df1.to_string(justify='left'))
1298
+
1299
+ justify_dict={}
1300
+ for c in df1.columns:
1301
+ if c=='比较对象':
1302
+ justify_dict[c]='left'
1303
+ else:
1304
+ justify_dict[c]='center'
1305
+ print(df1.to_string(justify=justify_dict))
1306
+ """
1307
+
1308
+ """
1309
+ alignlist=['right','left']+['center']*(len(list(df1))-3)+['center','center']
1310
+ try:
1311
+ print(df1.to_markdown(index=True,tablefmt='plain',colalign=alignlist))
1312
+ except:
1313
+ #解决汉字编码gbk出错问题
1314
+ df2=df1.to_markdown(index=True,tablefmt='plain',colalign=alignlist)
1315
+ df3=df2.encode("utf-8",errors="strict")
1316
+ print(df3)
1317
+
1318
+ print("\n$$$$$$$$ 左调节打印")
1319
+ df2=df1.copy()
1320
+ max_len=max([len(col) for col in df2.columns]) #找到最长的列名长度
1321
+ for col in df2.columns:
1322
+ df2[col]=df2[col].astype(str) #将每列的值强制转换为字符串类型
1323
+ df2[col]=df2[col].apply(lambda x: x.ljust(max_len)) #调整每列的宽度
1324
+ print(df2)
1325
+
1326
+ print("\n$$$$$$$$ tabulate打印")
1327
+ from tabulate import tabulate
1328
+ print(tabulate(df1,headers=list(df1)))
1329
+ """
1330
+
1331
+ #脚注
1332
+ footnote1=text_lang("风险调整收益基于","RaR based on ")+ectranslate(ret_type)+text_lang(",",', ')
1333
+ """
1334
+ if RF !=0:
1335
+ footnote2=text_lang("年化无风险利率","RF = ")+str(round(RF*100,4))+text_lang('%','% pa')
1336
+ else:
1337
+ footnote2=text_lang("假设年化无风险利率为零","assuming RF = 0 pa")
1338
+ """
1339
+ footnote2=text_lang("年化无风险利率","RF = ")+str(round(RF*100,4))+text_lang('%','% pa')
1340
+
1341
+ footnote3=''
1342
+ if 'treynor' in rar or 'alpha' in rar:
1343
+ if mktidx=='auto':
1344
+ mktidx=get_market_index_code(ticker[0] if isinstance(ticker,list) else ticker)
1345
+ mktidx_name=ticker_name(mktidx)
1346
+ footnote3=text_lang("CAPM基于","CAPM using ")+mktidx_name+text_lang(",回归期间",", rolling ")+str(regression_period)+text_lang("个自然日"," days")
1347
+
1348
+ import datetime; todaydt = datetime.date.today()
1349
+ footnote4=text_lang("数据来源: 综合新浪/EM/Stooq/Yahoo/SWHY,","Data source: Sina/Stooq/Yahoo, ")+str(todaydt)+text_lang("统计",'')
1350
+ if footnote3 !='':
1351
+ footnotex=footnote1+footnote2+'\n'+footnote3+'\n'+footnote4
1352
+ else:
1353
+ footnotex=footnote1+footnote2+'\n'+footnote4
1354
+
1355
+ #print("\n"+footnotex)
1356
+ #print(footnotex)
1357
+ if check_language()=="English":
1358
+ df1.rename(columns={"比较对象":"Securities","sharpe":"Sharpe Ratio","sortino":"Sortino Ratio","alpha":"Jensen Alpha","treynor":"Treynor Ratio","综合推荐":"Overall Recommend"},inplace=True)
1359
+
1360
+ df_display_CSS(df1,titletxt=titletxt,footnote=footnotex,decimals=4, \
1361
+ first_col_align='left',second_col_align='center', \
1362
+ last_col_align='center',other_col_align='center')
1363
+
1364
+ return df
1365
+
1366
+ #==============================================================================
1367
+ if __name__=='__main__':
1368
+ ticker="600519.SS"
1369
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
1370
+
1371
+ start="2024-1-1"
1372
+ end="2024-3-15"
1373
+ rar='sharpe'
1374
+ ret_type=["Monthly Ret%","Annual Ret%"]
1375
+ RF=0.01759
1376
+ regression_period=365
1377
+
1378
+ graph=True; axhline_value=0; axhline_label=''
1379
+ printout=False; sortby='tpw_mean'; trailing=5; trend_threshhold=0.001
1380
+ annotate=False
1381
+ mktidx='auto'; source='auto'
1382
+
1383
+ rars=compare_1ticker_1rar_mret(ticker,start,end,rar,ret_type,printout=True)
1384
+
1385
+ def compare_1ticker_1rar_mret(ticker,start,end,rar='sharpe', \
1386
+ ret_type=["Annual Adj Ret%","Monthly Adj Ret%"], \
1387
+ RF=0,regression_period=365, \
1388
+ attention_value='',attention_value_area='', \
1389
+ attention_point='',attention_point_area='', \
1390
+ band_area='', \
1391
+ graph=True,loc1='best', \
1392
+ axhline_value=0,axhline_label='',facecolor='whitesmoke', \
1393
+ printout=False,sortby='tpw_mean',trailing=7,trend_threshhold=0.01, \
1394
+ annotate=False,annotate_value=False, \
1395
+ mark_top=False,mark_bottom=False, \
1396
+ mark_start=False,mark_end=False, \
1397
+ mktidx='auto',source='auto',ticker_type='auto'):
1398
+ """
1399
+ 功能:一只股票,同一个rar,对比其不同的收益率类型,支持股票和投资组合
1400
+ """
1401
+
1402
+ import os,sys
1403
+ class HiddenPrints:
1404
+ def __enter__(self):
1405
+ self._original_stdout = sys.stdout
1406
+ sys.stdout = open(os.devnull, 'w')
1407
+
1408
+ def __exit__(self, exc_type, exc_val, exc_tb):
1409
+ sys.stdout.close()
1410
+ sys.stdout = self._original_stdout
1411
+
1412
+ #转换字符串和列表,避免下面的循环出错
1413
+ if isinstance(ticker,list):
1414
+ ticker=ticker[0]
1415
+ if isinstance(rar,list):
1416
+ rar=rar[0]
1417
+ if isinstance(ret_type,str):
1418
+ ret_type=[ret_type]
1419
+ if isinstance(RF,list):
1420
+ RF=RF[0]
1421
+ if isinstance(regression_period,list):
1422
+ regression_period=regression_period[0]
1423
+ print(" Working on",rar,"ratio for",ticker_name(ticker,ticker_type),"in different return types ......\n")
1424
+
1425
+ df=pd.DataFrame()
1426
+ for t in ret_type:
1427
+ #关闭print输出
1428
+ with HiddenPrints():
1429
+ df_tmp=get_rar(ticker,start,end,rar,ret_type=t, \
1430
+ RF=RF,regression_period=regression_period,mktidx=mktidx, \
1431
+ source=source,ticker_type=ticker_type)
1432
+
1433
+ if df_tmp is None:
1434
+ break
1435
+ else:
1436
+ dft=df_tmp[[rar]]
1437
+ tname=text_lang("基于","Based on ")+ectranslate(t)
1438
+ dft.rename(columns={rar:text_lang("基于","Based on ")+ectranslate(t)},inplace=True)
1439
+
1440
+ # 将band_area中的ticker替换为tname
1441
+ if band_area != '':
1442
+ for index, item in enumerate(band_area):
1443
+ if item == t:
1444
+ band_area[index] = tname
1445
+
1446
+ if len(df)==0: #第一个
1447
+ df=dft
1448
+ else:
1449
+ df=pd.merge(df,dft,how='outer',left_index=True,right_index=True)
1450
+
1451
+ if len(df)==0:
1452
+ print(" #Error(compare_mticker_1rar): rar data not available for",ticker_name(ticker,ticker_type),"between",start,end)
1453
+ return None
1454
+
1455
+ #仅用于绘图和制表
1456
+ df1=df.copy()
1457
+ for c in list(df1):
1458
+ if df1[c].max() > axhline_value and df1[c].min() < axhline_value:
1459
+ axhline_label='零线'
1460
+ #df1.rename(columns={c:"基于"+ectranslate(c)},inplace=True)
1461
+
1462
+ #共同脚注
1463
+ footnote1=text_lang("注:","Note: ")
1464
+ """
1465
+ if RF !=0:
1466
+ footnote2=text_lang("年化无风险利率为","RF = ")+str(round(RF*100,4))+text_lang('%。','% pa. ')
1467
+ else:
1468
+ footnote2=text_lang("假设年化无风险利率为零。","Assuming RF = 0 pa")
1469
+ """
1470
+ footnote2=text_lang("年化无风险利率为","RF = ")+str(round(RF*100,4))+text_lang('%。','% pa. ')
1471
+
1472
+ footnote3=''
1473
+ if rar.lower() in ['treynor','alpha']:
1474
+ footnote3=text_lang("CAPM回归期间","CAPM rolling ")+str(regression_period)+text_lang("个自然日"," days")
1475
+
1476
+ import datetime; todaydt = datetime.date.today()
1477
+ footnote4=text_lang("数据来源: 综合新浪/EM/Stooq/Yahoo/SWHY,","Data source: Sina/Stooq/Yahoo, ")+str(todaydt)
1478
+ if footnote3 !='':
1479
+ footnotex=footnote1+footnote2+footnote3+'\n'+footnote4
1480
+ else:
1481
+ footnotex=footnote1+footnote2+footnote3+'\n'+footnote4
1482
+
1483
+ #绘图
1484
+ if graph:
1485
+
1486
+ title_txt=text_lang("风险调整收益:","Risk-adjusted Return: ")+ticker_name(ticker,ticker_type)
1487
+ y_label=ectranslate(rar)
1488
+
1489
+ draw_lines(df1,y_label,x_label=footnotex, \
1490
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1491
+ title_txt=title_txt,data_label=False, \
1492
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1493
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1494
+ annotate=annotate,annotate_value=annotate, \
1495
+ band_area=band_area, \
1496
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1497
+ mark_start=mark_start,mark_end=mark_end, \
1498
+ facecolor=facecolor,loc=loc1)
1499
+
1500
+ #制表
1501
+ recommenddf=pd.DataFrame()
1502
+ if printout:
1503
+ if sortby=='tpw_mean':
1504
+ sortby_txt=text_lang('按推荐标记+近期优先加权平均值降序排列',"by Recommend + RWA, Descending")
1505
+ elif sortby=='min':
1506
+ sortby_txt=text_lang('按推荐标记+最小值降序排列',"by Recommend + Min, Descending")
1507
+ elif sortby=='mean':
1508
+ sortby_txt=text_lang('按推荐标记+平均值降序排列',"by Recommend + Mean, Descending")
1509
+ elif sortby=='median':
1510
+ sortby_txt=text_lang('按推荐标记+中位数值降序排列',"by Recommend + Median, Descending")
1511
+ elif sortby=='trailing':
1512
+ sortby_txt=text_lang('按推荐标记+短期均值走势降序排列',"by Recommend + Recent Trend, Descending")
1513
+
1514
+ #title_txt='***** 风险调整收益评估:'+'基于'+ectranslate(rar)+','+ticker_name(ticker,ticker_type)+','+sortby_txt+' *****'
1515
+ title_txt=text_lang('风险调整收益评估:',"RaR Evaluation: ")+ectranslate(rar)+text_lang(',',', ')+sortby_txt
1516
+
1517
+ footnote6=text_lang('期间:',"Period: ")+str(start)+text_lang('至'," to ")+str(end)+text_lang(";近期指近","\nRecent trend: ")+str(trailing)+text_lang("个交易日。趋势变化率阈值:", " days. Trend threshhold ")+str(trend_threshhold)
1518
+ footnote7=text_lang("近期优先趋势和星号为风险调整收益指标加趋势等多项因素综合研判,最多五颗星","Recommend max 5 stars. RWA = Recent-priority Weighted Average")
1519
+ footnotey=footnote6+'\n'+footnote7+'\n'+footnotex
1520
+
1521
+ #删除含有Nan的行
1522
+ df1.dropna(inplace=True)
1523
+
1524
+ recommenddf=descriptive_statistics2(df1,title_txt,footnotey,decimals=4, \
1525
+ sortby=sortby,recommend_only=True,trailing=trailing, \
1526
+ trend_threshhold=trend_threshhold,facecolor=facecolor)
1527
+
1528
+ return df,recommenddf
1529
+
1530
+ #==============================================================================
1531
+ if __name__=='__main__':
1532
+ ticker="600519.SS"
1533
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
1534
+ ticker={'Market':('China','000300.SS','白酒组合'),'600519.SS':0.2,'000858.SZ':0.3,'600809.SS':0.5}
1535
+
1536
+ start="2024-3-18"; end="2024-3-22"
1537
+ rar='alpha'
1538
+ rar='sharpe'
1539
+ ret_type="Annual Ret%"
1540
+ RF=[0.005,0.01759,0.05]
1541
+ regression_period=365
1542
+
1543
+ graph=True; axhline_value=0; axhline_label=''
1544
+ printout=False; sortby='tpw_mean'; trailing=5; trend_threshhold=0.001
1545
+ annotate=False
1546
+ mktidx='auto'; source='auto'
1547
+
1548
+ rars=compare_1ticker_1rar_1ret_mRF(ticker,start,end,rar,ret_type,RF)
1549
+
1550
+ def compare_1ticker_1rar_1ret_mRF(ticker,start,end,rar='sharpe', \
1551
+ ret_type="Annual Adj Ret%",RF=[0,0.02,0.05],regression_period=365, \
1552
+ attention_value='',attention_value_area='', \
1553
+ attention_point='',attention_point_area='', \
1554
+ band_area='', \
1555
+ graph=True,loc1='best', \
1556
+ axhline_value=0,axhline_label='',facecolor='whitesmoke', \
1557
+ printout=False,sortby='tpw_mean',trailing=7,trend_threshhold=0.01, \
1558
+ annotate=False,annotate_value=False, \
1559
+ mark_top=False,mark_bottom=False, \
1560
+ mark_start=False,mark_end=False, \
1561
+ mktidx='auto',source='auto',ticker_type='auto'):
1562
+ """
1563
+ 功能:一只股票,相同的rar,相同的收益率类型,不同的无风险收益率
1564
+ 支持股票和投资组合
1565
+ """
1566
+
1567
+ import os,sys
1568
+ class HiddenPrints:
1569
+ def __enter__(self):
1570
+ self._original_stdout = sys.stdout
1571
+ sys.stdout = open(os.devnull, 'w')
1572
+
1573
+ def __exit__(self, exc_type, exc_val, exc_tb):
1574
+ sys.stdout.close()
1575
+ sys.stdout = self._original_stdout
1576
+
1577
+ #转换字符串和列表,避免下面的循环出错
1578
+ if isinstance(ticker,list):
1579
+ ticker=ticker[0]
1580
+ if isinstance(rar,list):
1581
+ rar=rar[0]
1582
+ if isinstance(ret_type,list):
1583
+ ret_type=[ret_type]
1584
+ if isinstance(RF,float):
1585
+ RF=[RF]
1586
+ if isinstance(regression_period,list):
1587
+ regression_period=regression_period[0]
1588
+ print(" Working on",rar,"ratio for",ticker_name(ticker,ticker_type),"in different RF levels ......\n")
1589
+
1590
+ df=pd.DataFrame()
1591
+ for t in RF:
1592
+ #关闭print输出
1593
+ with HiddenPrints():
1594
+ df_tmp=get_rar(ticker,start,end,rar,ret_type, \
1595
+ RF=t,regression_period=regression_period,mktidx=mktidx, \
1596
+ source=source,ticker_type=ticker_type)
1597
+
1598
+ if df_tmp is None:
1599
+ break
1600
+ else:
1601
+ dft=df_tmp[[rar]]
1602
+ tname=text_lang("RF=","RF=")+str(round(t*100,4))+'%'
1603
+ dft.rename(columns={rar:tname},inplace=True)
1604
+
1605
+ # 将band_area中的ticker替换为tname
1606
+ if band_area != '':
1607
+ for index, item in enumerate(band_area):
1608
+ if item == t:
1609
+ band_area[index] = tname
1610
+
1611
+ if len(df)==0: #第一个
1612
+ df=dft
1613
+ else:
1614
+ df=pd.merge(df,dft,how='outer',left_index=True,right_index=True)
1615
+
1616
+ if len(df)==0:
1617
+ print(" #Error(compare_mticker_1rar): rar data inaccessible for",ticker_name(ticker,ticker_type),"between",start,end)
1618
+ return None
1619
+
1620
+ #仅用于绘图和制表
1621
+ df1=df.copy()
1622
+ for c in list(df1):
1623
+ if df1[c].max() > axhline_value and df1[c].min() < axhline_value:
1624
+ axhline_label='零线'
1625
+ #df1.rename(columns={c:"基于无风险利率"+c},inplace=True)
1626
+
1627
+ #共同脚注
1628
+ footnote1=text_lang("注:","Note: ")+ectranslate(rar)+text_lang("基于"," based on ")+ectranslate(ret_type)+text_lang('。','')
1629
+ footnote2=""
1630
+
1631
+ footnote3=""
1632
+ if rar.lower() in ['treynor','alpha']:
1633
+ footnote3="贝塔系数回归期间"+str(regression_period)+"个自然日"
1634
+
1635
+ import datetime; todaydt = datetime.date.today()
1636
+ footnote4=text_lang("数据来源: 综合新浪/EM/Stooq/Yahoo/SWHY,","Data source: Sina/Stooq/Yahoo, ")+str(todaydt)
1637
+ if footnote3 !='':
1638
+ footnotex=footnote1+footnote3+'\n'+footnote4
1639
+ else:
1640
+ footnotex=footnote1+footnote4
1641
+
1642
+ #绘图
1643
+ if graph:
1644
+
1645
+ title_txt=text_lang("风险调整收益:","Risk-adjusted Return: ")+ticker_name(ticker,ticker_type)
1646
+ y_label=ectranslate(rar)
1647
+
1648
+ draw_lines(df1,y_label,x_label=footnotex, \
1649
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1650
+ title_txt=title_txt,data_label=False, \
1651
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1652
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1653
+ annotate=annotate,annotate_value=annotate, \
1654
+ band_area=band_area, \
1655
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1656
+ mark_start=mark_start,mark_end=mark_end, \
1657
+ facecolor=facecolor,loc=loc1)
1658
+
1659
+ #制表
1660
+ recommenddf=pd.DataFrame()
1661
+ if printout:
1662
+ if sortby=='tpw_mean':
1663
+ sortby_txt=text_lang('按推荐标记+近期优先加权平均值降序排列',"by Recommend + RWA, Descending")
1664
+ elif sortby=='min':
1665
+ sortby_txt=text_lang('按推荐标记+最小值降序排列',"by Recommend + Min, Descending")
1666
+ elif sortby=='mean':
1667
+ sortby_txt=text_lang('按推荐标记+平均值降序排列',"by Recommend + Mean, Descending")
1668
+ elif sortby=='median':
1669
+ sortby_txt=text_lang('按推荐标记+中位数值降序排列',"by Recommend + Median, Descending")
1670
+ elif sortby=='trailing':
1671
+ sortby_txt=text_lang('按推荐标记+短期均值走势降序排列',"by Recommend + Recent Trend, Descending")
1672
+
1673
+ #title_txt='***** 风险调整收益评估:'+'基于'+ectranslate(rar)+','+ticker_name(ticker,ticker_type)+','+sortby_txt+' *****'
1674
+ title_txt=text_lang('风险调整收益评估:',"RaR Evaluation: ")+ectranslate(rar)+text_lang(',',', ')+sortby_txt
1675
+
1676
+ footnote6=text_lang('期间:',"Period: ")+str(start)+text_lang('至'," to ")+str(end)+text_lang(";近期指近","\nRecent trend: ")+str(trailing)+text_lang("个交易日。趋势变化率阈值:", " days. Trend threshhold ")+str(trend_threshhold)
1677
+ footnote7=text_lang("近期优先趋势和星号为风险调整收益指标加趋势等多项因素综合研判,最多五颗星","Recommend max 5 stars. RWA = Recent-priority Weighted Average")
1678
+ footnotey=footnote6+footnote7+'\n'+footnotex
1679
+
1680
+ #删除含有Nan的行
1681
+ df1.dropna(inplace=True)
1682
+
1683
+ recommenddf=descriptive_statistics2(df1,title_txt,footnotey,decimals=4, \
1684
+ sortby=sortby,recommend_only=True,trailing=trailing, \
1685
+ trend_threshhold=trend_threshhold,facecolor=facecolor)
1686
+
1687
+ return df,recommenddf
1688
+
1689
+ #==============================================================================
1690
+ # 合成函数
1691
+ #==============================================================================
1692
+ if __name__=='__main__':
1693
+ ticker="301161.SZ"
1694
+ ticker="600519.SS"
1695
+ ticker=["600519.SS","000858.SZ"]
1696
+ ticker={'Market':('US','^SPX','中概教培组合'),'EDU':0.7,'TAL':0.3}
1697
+
1698
+ start="2024-1-1"; end="2024-9-30"
1699
+
1700
+ rar='sharpe'
1701
+ rar='alpha'
1702
+ rar=['sharpe','alpha']
1703
+
1704
+ ret_type="Monthly Adj Ret%"
1705
+ ret_type="Annual Adj Ret%"
1706
+ ret_type=["Monthly Adj Ret%","Annual Adj Ret%"]
1707
+
1708
+ RF=0.01759
1709
+ RF=[0.005,0.01759,0.05]
1710
+
1711
+ regression_period=365
1712
+
1713
+ graph=True; axhline_value=0; axhline_label=''
1714
+ printout=False; sortby='tpw_mean'; trailing=5; trend_threshhold=0.001
1715
+ annotate=False
1716
+ mark_top=True; mark_bottom=True; mark_end=True
1717
+ mktidx='auto'; source='auto'; ticker_type='auto'
1718
+
1719
+ rars=compare_rar_security(ticker,start,end,rar,ret_type,RF,
1720
+ mark_top=True,mark_bottom=True,mark_end=True,
1721
+ printout=True)
1722
+
1723
+
1724
+ def compare_rar_security(ticker,start,end='today',indicator='sharpe', \
1725
+ ret_type="Annual Adj Ret%", \
1726
+ RF=0, \
1727
+ regression_period=365, \
1728
+ attention_value='',attention_value_area='', \
1729
+ attention_point='',attention_point_area='', \
1730
+ band_area='', \
1731
+ graph=True,loc1='best', \
1732
+ axhline_value=0,axhline_label='',facecolor='whitesmoke', \
1733
+ printout=False,sortby='tpw_mean',trailing=7,trend_threshhold=0.05, \
1734
+ annotate=False,annotate_value=False, \
1735
+ mark_top=False,mark_bottom=False, \
1736
+ mark_start=False,mark_end=False, \
1737
+ mktidx='auto',source='auto', \
1738
+ ticker_type='auto'):
1739
+ """
1740
+ 功能:组合情况,可能多只股票,多个rar,多个收益率类型,多个无风险收益率
1741
+
1742
+ 注意:trailing=7,trend_threshhold=0.05,更加贴合视觉效果
1743
+ """
1744
+ start,end=start_end_preprocess(start,end)
1745
+ rar=indicator
1746
+
1747
+ #情形1:多个证券
1748
+ if isinstance(ticker,list):
1749
+ if len(ticker) > 1:
1750
+ if isinstance(ret_type,list):
1751
+ ret_type=ret_type[0]
1752
+ if isinstance(RF,list):
1753
+ RF=RF[0]
1754
+
1755
+ rar_num=0
1756
+ if isinstance(rar,str):
1757
+ rar_num=1
1758
+ if isinstance(rar,list):
1759
+ rar_num=len(rar)
1760
+ if rar_num==1: rar=rar[0]
1761
+
1762
+ if rar_num ==1: #一个RAR
1763
+ df=compare_mticker_1rar(ticker=ticker,start=start,end=end,rar=rar, \
1764
+ ret_type=ret_type,RF=RF,regression_period=regression_period, \
1765
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1766
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1767
+ band_area=band_area, \
1768
+ graph=graph,loc1=loc1, \
1769
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1770
+ printout=printout, \
1771
+ sortby=sortby,trailing=trailing,trend_threshhold=trend_threshhold, \
1772
+ annotate=annotate,annotate_value=annotate, \
1773
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1774
+ mark_start=mark_start,mark_end=mark_end, \
1775
+ mktidx=mktidx,source=source, \
1776
+ ticker_type=ticker_type,facecolor=facecolor)
1777
+ return df
1778
+
1779
+ if rar_num >1: #多个RAR,此项的主要意图并非绘图,而是进行多指标综合推荐
1780
+ printout=True #否则无法运行descriptive_statistics2函数
1781
+ df=compare_mticker_mrar(ticker=ticker,start=start,end=end,rar=rar, \
1782
+ ret_type=ret_type,RF=RF,regression_period=regression_period, \
1783
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1784
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1785
+ band_area=band_area, \
1786
+ graph=graph,loc1=loc1, \
1787
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1788
+ printout=printout, \
1789
+ sortby=sortby,trailing=trailing,trend_threshhold=trend_threshhold, \
1790
+ annotate=annotate,annotate_value=annotate, \
1791
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1792
+ mark_start=mark_start,mark_end=mark_end, \
1793
+ mktidx=mktidx,source=source, \
1794
+ ticker_type=ticker_type,facecolor=facecolor)
1795
+ return df
1796
+ else:
1797
+ #实际上是单个证券
1798
+ ticker=ticker[0]
1799
+
1800
+ #情形2:1只证券,多个RAR
1801
+ if isinstance(rar,list):
1802
+ if len(rar) > 1:
1803
+ if isinstance(ret_type,list):
1804
+ ret_type=ret_type[0]
1805
+ if isinstance(RF,list):
1806
+ RF=RF[0]
1807
+
1808
+ df=compare_1ticker_mrar(ticker=ticker,start=start,end=end,rar=rar, \
1809
+ ret_type=ret_type,RF=RF,regression_period=regression_period, \
1810
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1811
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1812
+ band_area=band_area, \
1813
+ graph=graph,loc1=loc1, \
1814
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1815
+ printout=printout,facecolor=facecolor, \
1816
+ sortby=sortby,trailing=trailing,trend_threshhold=trend_threshhold, \
1817
+ annotate=annotate,annotate_value=annotate, \
1818
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1819
+ mark_start=mark_start,mark_end=mark_end, \
1820
+ mktidx=mktidx,source=source, \
1821
+ ticker_type=ticker_type)
1822
+ return df
1823
+ else:
1824
+ #实际上是单个RAR
1825
+ rar=rar[0]
1826
+
1827
+ #情形3:1只证券,1个RAR,多个收益率类型
1828
+ if isinstance(ret_type,list):
1829
+ if len(ret_type) > 1:
1830
+ if isinstance(RF,list):
1831
+ RF=RF[0]
1832
+
1833
+ df=compare_1ticker_1rar_mret(ticker=ticker,start=start,end=end,rar=rar, \
1834
+ ret_type=ret_type,RF=RF,regression_period=regression_period, \
1835
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1836
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1837
+ band_area=band_area, \
1838
+ graph=graph,loc1=loc1, \
1839
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1840
+ printout=printout, \
1841
+ sortby=sortby,trailing=trailing,trend_threshhold=trend_threshhold, \
1842
+ annotate=annotate,annotate_value=annotate, \
1843
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1844
+ mark_start=mark_start,mark_end=mark_end, \
1845
+ mktidx=mktidx,source=source, \
1846
+ ticker_type=ticker_type,facecolor=facecolor)
1847
+ return df
1848
+ else:
1849
+ #实际上是单个收益率类型
1850
+ ret_type=ret_type[0]
1851
+
1852
+ #情形4:1只证券,1个RAR,1个收益率类型,多个RF
1853
+ if isinstance(RF,list):
1854
+ if len(RF) > 1:
1855
+
1856
+ df=compare_1ticker_1rar_1ret_mRF(ticker=ticker,start=start,end=end,rar=rar, \
1857
+ ret_type=ret_type,RF=RF,regression_period=regression_period, \
1858
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1859
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1860
+ band_area=band_area, \
1861
+ graph=graph,loc1=loc1, \
1862
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1863
+ printout=printout,facecolor=facecolor, \
1864
+ sortby=sortby,trailing=trailing,trend_threshhold=trend_threshhold, \
1865
+ annotate=annotate,annotate_value=annotate, \
1866
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1867
+ mark_start=mark_start,mark_end=mark_end, \
1868
+ mktidx=mktidx,source=source, \
1869
+ ticker_type=ticker_type)
1870
+ return df
1871
+ else:
1872
+ #实际上是单个RF
1873
+ RF=RF[0]
1874
+
1875
+ #情形5:1只证券,1个RAR,1个收益率类型,1个RF
1876
+ df=compare_1ticker_mrar(ticker=ticker,start=start,end=end,rar=rar, \
1877
+ ret_type=ret_type,RF=RF,regression_period=regression_period, \
1878
+ attention_value=attention_value,attention_value_area=attention_value_area, \
1879
+ attention_point=attention_point,attention_point_area=attention_point_area, \
1880
+ graph=graph,loc1=loc1, \
1881
+ axhline_value=axhline_value,axhline_label=axhline_label, \
1882
+ printout=printout,sortby=sortby, \
1883
+ trailing=trailing,trend_threshhold=trend_threshhold, \
1884
+ annotate=annotate,annotate_value=annotate, \
1885
+ mark_top=mark_top,mark_bottom=mark_bottom, \
1886
+ mark_start=mark_start,mark_end=mark_end, \
1887
+ mktidx=mktidx,source=source, \
1888
+ ticker_type=ticker_type,facecolor=facecolor)
1889
+
1890
+ return df
1891
+
1892
+
1893
+ #==============================================================================
1894
+ #==============================================================================
1895
+
1896
+ #==============================================================================
1897
+ #==============================================================================
1898
+ #==============================================================================
1899
+ #==============================================================================
1900
+ #==============================================================================