siat 3.10.132__py3-none-any.whl → 3.10.133__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 (218) hide show
  1. siat/__init__.py +0 -0
  2. siat/allin.py +0 -0
  3. siat/assets_liquidity.py +0 -0
  4. siat/beta_adjustment.py +0 -0
  5. siat/beta_adjustment_china.py +0 -0
  6. siat/blockchain.py +0 -0
  7. siat/bond.py +0 -0
  8. siat/bond_base.py +0 -0
  9. siat/bond_china.py +0 -0
  10. siat/bond_zh_sina.py +0 -0
  11. siat/capm_beta.py +0 -0
  12. siat/capm_beta2.py +0 -0
  13. siat/compare_cross.py +0 -0
  14. siat/copyrights.py +0 -0
  15. siat/cryptocurrency.py +0 -0
  16. siat/economy.py +0 -0
  17. siat/economy2.py +0 -0
  18. siat/esg.py +0 -0
  19. siat/event_study.py +0 -0
  20. siat/exchange_bond_china.pickle +0 -0
  21. siat/fama_french.py +0 -0
  22. siat/fin_stmt2_yahoo.py +0 -0
  23. siat/financial_base.py +0 -0
  24. siat/financial_statements.py +0 -0
  25. siat/financials.py +0 -0
  26. siat/financials2.py +0 -0
  27. siat/financials_china.py +0 -0
  28. siat/financials_china2.py +0 -0
  29. siat/fund.py +0 -0
  30. siat/fund_china.pickle +0 -0
  31. siat/fund_china.py +0 -0
  32. siat/future_china.py +0 -0
  33. siat/google_authenticator.py +0 -0
  34. siat/grafix.py +0 -0
  35. siat/holding_risk.py +0 -0
  36. siat/luchy_draw.py +0 -0
  37. siat/market_china.py +0 -0
  38. siat/markowitz.py +0 -0
  39. siat/markowitz2.py +0 -0
  40. siat/markowitz2_20250704.py +0 -0
  41. siat/markowitz2_20250705.py +0 -0
  42. siat/markowitz_simple.py +0 -0
  43. siat/ml_cases.py +0 -0
  44. siat/ml_cases_example.py +0 -0
  45. siat/option_china.py +0 -0
  46. siat/option_pricing.py +0 -0
  47. siat/other_indexes.py +0 -0
  48. siat/risk_adjusted_return.py +0 -0
  49. siat/risk_adjusted_return2.py +0 -0
  50. siat/risk_evaluation.py +0 -0
  51. siat/risk_free_rate.py +0 -0
  52. siat/sector_china.py +0 -0
  53. siat/security_price2.py +0 -0
  54. siat/security_prices.py +40 -2
  55. siat/security_trend.py +0 -0
  56. siat/security_trend2.py +0 -0
  57. siat/stock.py +0 -0
  58. siat/stock_advice_linear.py +0 -0
  59. siat/stock_base.py +0 -0
  60. siat/stock_china.py +0 -0
  61. siat/stock_info.pickle +0 -0
  62. siat/stock_prices_kneighbors.py +0 -0
  63. siat/stock_prices_linear.py +0 -0
  64. siat/stock_profile.py +0 -0
  65. siat/stock_technical.py +0 -0
  66. siat/stooq.py +0 -0
  67. siat/transaction.py +0 -0
  68. siat/translate.py +0 -0
  69. siat/valuation.py +0 -0
  70. siat/valuation_china.py +0 -0
  71. siat/var_model_validation.py +0 -0
  72. siat/yf_name.py +0 -0
  73. {siat-3.10.132.dist-info/licenses → siat-3.10.133.dist-info}/LICENSE +0 -0
  74. {siat-3.10.132.dist-info → siat-3.10.133.dist-info}/METADATA +232 -235
  75. siat-3.10.133.dist-info/RECORD +78 -0
  76. {siat-3.10.132.dist-info → siat-3.10.133.dist-info}/WHEEL +1 -1
  77. {siat-3.10.132.dist-info → siat-3.10.133.dist-info}/top_level.txt +0 -1
  78. build/lib/build/lib/siat/__init__.py +0 -75
  79. build/lib/build/lib/siat/allin.py +0 -137
  80. build/lib/build/lib/siat/assets_liquidity.py +0 -915
  81. build/lib/build/lib/siat/beta_adjustment.py +0 -1058
  82. build/lib/build/lib/siat/beta_adjustment_china.py +0 -548
  83. build/lib/build/lib/siat/blockchain.py +0 -143
  84. build/lib/build/lib/siat/bond.py +0 -2900
  85. build/lib/build/lib/siat/bond_base.py +0 -992
  86. build/lib/build/lib/siat/bond_china.py +0 -100
  87. build/lib/build/lib/siat/bond_zh_sina.py +0 -143
  88. build/lib/build/lib/siat/capm_beta.py +0 -783
  89. build/lib/build/lib/siat/capm_beta2.py +0 -887
  90. build/lib/build/lib/siat/common.py +0 -5360
  91. build/lib/build/lib/siat/compare_cross.py +0 -642
  92. build/lib/build/lib/siat/copyrights.py +0 -18
  93. build/lib/build/lib/siat/cryptocurrency.py +0 -667
  94. build/lib/build/lib/siat/economy.py +0 -1471
  95. build/lib/build/lib/siat/economy2.py +0 -1853
  96. build/lib/build/lib/siat/esg.py +0 -536
  97. build/lib/build/lib/siat/event_study.py +0 -815
  98. build/lib/build/lib/siat/fama_french.py +0 -1521
  99. build/lib/build/lib/siat/fin_stmt2_yahoo.py +0 -982
  100. build/lib/build/lib/siat/financial_base.py +0 -1160
  101. build/lib/build/lib/siat/financial_statements.py +0 -598
  102. build/lib/build/lib/siat/financials.py +0 -2339
  103. build/lib/build/lib/siat/financials2.py +0 -1278
  104. build/lib/build/lib/siat/financials_china.py +0 -4433
  105. build/lib/build/lib/siat/financials_china2.py +0 -2212
  106. build/lib/build/lib/siat/fund.py +0 -629
  107. build/lib/build/lib/siat/fund_china.py +0 -3307
  108. build/lib/build/lib/siat/future_china.py +0 -551
  109. build/lib/build/lib/siat/google_authenticator.py +0 -47
  110. build/lib/build/lib/siat/grafix.py +0 -3636
  111. build/lib/build/lib/siat/holding_risk.py +0 -867
  112. build/lib/build/lib/siat/luchy_draw.py +0 -638
  113. build/lib/build/lib/siat/market_china.py +0 -1168
  114. build/lib/build/lib/siat/markowitz.py +0 -2363
  115. build/lib/build/lib/siat/markowitz2.py +0 -3150
  116. build/lib/build/lib/siat/markowitz2_20250704.py +0 -2969
  117. build/lib/build/lib/siat/markowitz2_20250705.py +0 -3158
  118. build/lib/build/lib/siat/markowitz_simple.py +0 -373
  119. build/lib/build/lib/siat/ml_cases.py +0 -2291
  120. build/lib/build/lib/siat/ml_cases_example.py +0 -60
  121. build/lib/build/lib/siat/option_china.py +0 -3069
  122. build/lib/build/lib/siat/option_pricing.py +0 -1925
  123. build/lib/build/lib/siat/other_indexes.py +0 -409
  124. build/lib/build/lib/siat/risk_adjusted_return.py +0 -1576
  125. build/lib/build/lib/siat/risk_adjusted_return2.py +0 -1900
  126. build/lib/build/lib/siat/risk_evaluation.py +0 -2218
  127. build/lib/build/lib/siat/risk_free_rate.py +0 -351
  128. build/lib/build/lib/siat/sector_china.py +0 -4140
  129. build/lib/build/lib/siat/security_price2.py +0 -727
  130. build/lib/build/lib/siat/security_prices.py +0 -3408
  131. build/lib/build/lib/siat/security_trend.py +0 -402
  132. build/lib/build/lib/siat/security_trend2.py +0 -646
  133. build/lib/build/lib/siat/stock.py +0 -4284
  134. build/lib/build/lib/siat/stock_advice_linear.py +0 -934
  135. build/lib/build/lib/siat/stock_base.py +0 -26
  136. build/lib/build/lib/siat/stock_china.py +0 -2095
  137. build/lib/build/lib/siat/stock_prices_kneighbors.py +0 -910
  138. build/lib/build/lib/siat/stock_prices_linear.py +0 -386
  139. build/lib/build/lib/siat/stock_profile.py +0 -707
  140. build/lib/build/lib/siat/stock_technical.py +0 -3305
  141. build/lib/build/lib/siat/stooq.py +0 -74
  142. build/lib/build/lib/siat/transaction.py +0 -347
  143. build/lib/build/lib/siat/translate.py +0 -5183
  144. build/lib/build/lib/siat/valuation.py +0 -1378
  145. build/lib/build/lib/siat/valuation_china.py +0 -2076
  146. build/lib/build/lib/siat/var_model_validation.py +0 -444
  147. build/lib/build/lib/siat/yf_name.py +0 -811
  148. build/lib/siat/__init__.py +0 -75
  149. build/lib/siat/allin.py +0 -137
  150. build/lib/siat/assets_liquidity.py +0 -915
  151. build/lib/siat/beta_adjustment.py +0 -1058
  152. build/lib/siat/beta_adjustment_china.py +0 -548
  153. build/lib/siat/blockchain.py +0 -143
  154. build/lib/siat/bond.py +0 -2900
  155. build/lib/siat/bond_base.py +0 -992
  156. build/lib/siat/bond_china.py +0 -100
  157. build/lib/siat/bond_zh_sina.py +0 -143
  158. build/lib/siat/capm_beta.py +0 -783
  159. build/lib/siat/capm_beta2.py +0 -887
  160. build/lib/siat/common.py +0 -5360
  161. build/lib/siat/compare_cross.py +0 -642
  162. build/lib/siat/copyrights.py +0 -18
  163. build/lib/siat/cryptocurrency.py +0 -667
  164. build/lib/siat/economy.py +0 -1471
  165. build/lib/siat/economy2.py +0 -1853
  166. build/lib/siat/esg.py +0 -536
  167. build/lib/siat/event_study.py +0 -815
  168. build/lib/siat/fama_french.py +0 -1521
  169. build/lib/siat/fin_stmt2_yahoo.py +0 -982
  170. build/lib/siat/financial_base.py +0 -1160
  171. build/lib/siat/financial_statements.py +0 -598
  172. build/lib/siat/financials.py +0 -2339
  173. build/lib/siat/financials2.py +0 -1278
  174. build/lib/siat/financials_china.py +0 -4433
  175. build/lib/siat/financials_china2.py +0 -2212
  176. build/lib/siat/fund.py +0 -629
  177. build/lib/siat/fund_china.py +0 -3307
  178. build/lib/siat/future_china.py +0 -551
  179. build/lib/siat/google_authenticator.py +0 -47
  180. build/lib/siat/grafix.py +0 -3636
  181. build/lib/siat/holding_risk.py +0 -867
  182. build/lib/siat/luchy_draw.py +0 -638
  183. build/lib/siat/market_china.py +0 -1168
  184. build/lib/siat/markowitz.py +0 -2363
  185. build/lib/siat/markowitz2.py +0 -3150
  186. build/lib/siat/markowitz2_20250704.py +0 -2969
  187. build/lib/siat/markowitz2_20250705.py +0 -3158
  188. build/lib/siat/markowitz_simple.py +0 -373
  189. build/lib/siat/ml_cases.py +0 -2291
  190. build/lib/siat/ml_cases_example.py +0 -60
  191. build/lib/siat/option_china.py +0 -3069
  192. build/lib/siat/option_pricing.py +0 -1925
  193. build/lib/siat/other_indexes.py +0 -409
  194. build/lib/siat/risk_adjusted_return.py +0 -1576
  195. build/lib/siat/risk_adjusted_return2.py +0 -1900
  196. build/lib/siat/risk_evaluation.py +0 -2218
  197. build/lib/siat/risk_free_rate.py +0 -351
  198. build/lib/siat/sector_china.py +0 -4140
  199. build/lib/siat/security_price2.py +0 -727
  200. build/lib/siat/security_prices.py +0 -3408
  201. build/lib/siat/security_trend.py +0 -402
  202. build/lib/siat/security_trend2.py +0 -646
  203. build/lib/siat/stock.py +0 -4284
  204. build/lib/siat/stock_advice_linear.py +0 -934
  205. build/lib/siat/stock_base.py +0 -26
  206. build/lib/siat/stock_china.py +0 -2095
  207. build/lib/siat/stock_prices_kneighbors.py +0 -910
  208. build/lib/siat/stock_prices_linear.py +0 -386
  209. build/lib/siat/stock_profile.py +0 -707
  210. build/lib/siat/stock_technical.py +0 -3305
  211. build/lib/siat/stooq.py +0 -74
  212. build/lib/siat/transaction.py +0 -347
  213. build/lib/siat/translate.py +0 -5183
  214. build/lib/siat/valuation.py +0 -1378
  215. build/lib/siat/valuation_china.py +0 -2076
  216. build/lib/siat/var_model_validation.py +0 -444
  217. build/lib/siat/yf_name.py +0 -811
  218. siat-3.10.132.dist-info/RECORD +0 -218
@@ -1,3150 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- 本模块功能:证券投资组合理论优化分析,手动输入RF版
4
- 所属工具包:证券投资分析工具SIAT
5
- SIAT:Security Investment Analysis Tool
6
- 创建日期:2024年4月19日
7
- 最新修订日期:2024年4月19日
8
- 作者:王德宏 (WANG Dehong, Peter)
9
- 作者单位:北京外国语大学国际商学院
10
- 作者邮件:wdehong2000@163.com
11
- 版权所有:王德宏
12
- 用途限制:仅限研究与教学使用,不可商用!商用需要额外授权。
13
- 特别声明:作者不对使用本工具进行证券投资导致的任何损益负责!
14
- """
15
- #==============================================================================
16
- #统一屏蔽一般性警告
17
- import warnings; warnings.filterwarnings("ignore")
18
- #==============================================================================
19
-
20
- from siat.common import *
21
- from siat.translate import *
22
- from siat.security_prices import *
23
- from siat.security_price2 import *
24
- from siat.grafix import *
25
- #from siat.fama_french import *
26
-
27
- import pandas as pd
28
- import numpy as np
29
- import datetime
30
- #==============================================================================
31
- import seaborn as sns
32
- import matplotlib.pyplot as plt
33
- #统一设定绘制的图片大小:数值为英寸,1英寸=100像素
34
- #plt.rcParams['figure.figsize']=(12.8,7.2)
35
- plt.rcParams['figure.figsize']=(12.8,6.4)
36
- plt.rcParams['figure.dpi']=300
37
- plt.rcParams['font.size'] = 13
38
- plt.rcParams['xtick.labelsize']=11 #横轴字体大小
39
- plt.rcParams['ytick.labelsize']=11 #纵轴字体大小
40
-
41
- title_txt_size=16
42
- ylabel_txt_size=14
43
- xlabel_txt_size=14
44
- legend_txt_size=14
45
-
46
- #设置绘图风格:网格虚线
47
- plt.rcParams['axes.grid']=True
48
- #plt.rcParams['grid.color']='steelblue'
49
- #plt.rcParams['grid.linestyle']='dashed'
50
- #plt.rcParams['grid.linewidth']=0.5
51
- #plt.rcParams['axes.facecolor']='papayawhip'
52
-
53
- #处理绘图汉字乱码问题
54
- import sys; czxt=sys.platform
55
- if czxt in ['win32','win64']:
56
- plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置默认字体
57
- mpfrc={'font.family': 'SimHei'}
58
- sns.set_style('whitegrid',{'font.sans-serif':['simhei','Arial']})
59
-
60
- if czxt in ['darwin','linux']: #MacOSX
61
- #plt.rcParams['font.family'] = ['Arial Unicode MS'] #用来正常显示中文标签
62
- plt.rcParams['font.family']= ['Heiti TC']
63
- mpfrc={'font.family': 'Heiti TC'}
64
- sns.set_style('whitegrid',{'font.sans-serif':['Arial Unicode MS','Arial']})
65
-
66
-
67
- # 解决保存图像时'-'显示为方块的问题
68
- plt.rcParams['axes.unicode_minus'] = False
69
- #==============================================================================
70
- #全局变量定义
71
- RANDOM_SEED=1234567890
72
-
73
- #==============================================================================
74
- def portfolio_config(tickerlist,sharelist):
75
- """
76
- 将股票列表tickerlist和份额列表sharelist合成为一个字典
77
- """
78
- #整理sharelist的小数点
79
- ratiolist=[]
80
- for s in sharelist:
81
- ss=round(s,4); ratiolist=ratiolist+[ss]
82
- #合成字典
83
- new_dict=dict(zip(tickerlist,ratiolist))
84
- return new_dict
85
-
86
- #==============================================================================
87
- def ratiolist_round(sharelist,num=4):
88
- """
89
- 将股票份额列表sharelist中的数值四舍五入
90
- """
91
- #整理sharelist的小数点
92
- ratiolist=[]
93
- for s in sharelist:
94
- ss=round(s,num); ratiolist=ratiolist+[ss]
95
- return ratiolist
96
-
97
- #==============================================================================
98
- def varname(p):
99
- """
100
- 功能:获得变量的名字本身。
101
- """
102
- import inspect
103
- import re
104
- for line in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
105
- m = re.search(r'\bvarname\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)', line)
106
- if m:
107
- return m.group(1)
108
-
109
- #==============================================================================
110
- if __name__=='__main__':
111
- end_date='2021-12-3'
112
- pastyears=3
113
-
114
- def get_start_date(end_date,pastyears=1):
115
- """
116
- 输入参数:一个日期,年数
117
- 输出参数:几年前的日期
118
- start_date, end_date是datetime类型
119
- """
120
- import pandas as pd
121
- try:
122
- end_date=pd.to_datetime(end_date)
123
- except:
124
- print(" #Error(get_start_date): invalid date,",end_date)
125
- return None
126
-
127
- from datetime import datetime,timedelta
128
- start_date=datetime(end_date.year-pastyears,end_date.month,end_date.day)
129
- start_date=start_date-timedelta(days=1)
130
- # 日期-1是为了保证计算收益率时得到足够的样本数量
131
-
132
- start=start_date.strftime("%Y-%m-%d")
133
-
134
- return start
135
-
136
- #==============================================================================
137
- #==============================================================================
138
- #==============================================================================
139
- if __name__=='__main__':
140
- retgroup=StockReturns
141
-
142
- def cumulative_returns_plot(retgroup,name_list="",titletxt="投资组合策略:业绩比较", \
143
- ylabeltxt="持有收益率",xlabeltxt="", \
144
- label_list=[],facecolor='papayawhip'):
145
- """
146
- 功能:基于传入的name_list绘制多条持有收益率曲线,并从label_list中取出曲线标记
147
- 注意:最多绘制四条曲线,否则在黑白印刷时无法区分曲线,以此标记为实线、点虚线、划虚线和点划虚线四种
148
- """
149
- if name_list=="":
150
- name_list=list(retgroup)
151
-
152
- if len(label_list) < len(name_list):
153
- label_list=name_list
154
-
155
- if xlabeltxt=="":
156
- #取出观察期
157
- hstart0=retgroup.index[0]
158
- #hstart=str(hstart0.date())
159
- hstart=str(hstart0.strftime("%Y-%m-%d"))
160
- hend0=retgroup.index[-1]
161
- #hend=str(hend0.date())
162
- hend=str(hend0.strftime("%Y-%m-%d"))
163
-
164
- lang = check_language()
165
- import datetime as dt; stoday=dt.date.today()
166
- if lang == 'Chinese':
167
- footnote1="观察期间: "+hstart+'至'+hend
168
- footnote2="\n数据来源:Sina/EM/Stooq/Yahoo,"+str(stoday)
169
- else:
170
- footnote1="Period of sample: "+hstart+' to '+hend
171
- footnote2="\nData source: Sina/EM/Stooq/Yahoo, "+str(stoday)
172
-
173
- xlabeltxt=footnote1+footnote2
174
-
175
- # 持有收益曲线绘制函数
176
- """
177
- lslist=['-','--',':','-.']
178
- markerlist=['.','h','+','x','4','3','2','1']
179
- for name in name_list:
180
- pos=name_list.index(name)
181
- rlabel=label_list[pos]
182
- if pos < len(lslist):
183
- thisls=lslist[pos]
184
- else:
185
- thisls=(45,(55,20))
186
-
187
- # 计算持有收益率
188
- CumulativeReturns = ((1+retgroup[name]).cumprod()-1)
189
- if pos-len(lslist) < 0:
190
- CumulativeReturns.plot(label=ectranslate(rlabel),ls=thisls)
191
- else:
192
- thismarker=markerlist[pos-len(lslist)]
193
- CumulativeReturns.plot(label=ectranslate(rlabel),ls=thisls,marker=thismarker,markersize=4)
194
-
195
- plt.axhline(y=0,ls=":",c="red")
196
- plt.legend(loc='best')
197
- plt.title(titletxt); plt.ylabel(ylabeltxt); plt.xlabel(xlabeltxt)
198
-
199
- plt.gca().set_facecolor(facecolor)
200
- plt.show()
201
- """
202
- import pandas as pd
203
- df=pd.DataFrame()
204
- for name in name_list:
205
- # 计算持有收益率
206
- CumulativeReturns = ((1+retgroup[name]).cumprod()-1)
207
-
208
- df[name]=CumulativeReturns
209
-
210
- draw_lines(df,y_label=ylabeltxt,x_label=xlabeltxt, \
211
- axhline_value=0,axhline_label='', \
212
- title_txt=titletxt, \
213
- annotate=True, \
214
- annotate_value=False, \
215
- facecolor=facecolor, \
216
- )
217
-
218
- return
219
-
220
- if __name__=='__main__':
221
- retgroup=StockReturns
222
- cumulative_returns_plot(retgroup,name_list,titletxt,ylabeltxt,xlabeltxt, \
223
- label_list=[])
224
-
225
- def portfolio_expret_plot(retgroup,name_list="",titletxt="投资组合策略:业绩比较", \
226
- ylabeltxt="持有收益率",xlabeltxt="", \
227
- label_list=[]):
228
- """
229
- 功能:套壳函数cumulative_returns_plot
230
- """
231
-
232
- cumulative_returns_plot(retgroup,name_list,titletxt,ylabeltxt,xlabeltxt,label_list)
233
-
234
- return
235
-
236
- #==============================================================================
237
- def portfolio_hpr(portfolio,thedate,pastyears=1, \
238
- RF=0, \
239
- printout=True,graph=True):
240
- """
241
- 功能:套壳函数portfolio_build
242
- """
243
- dflist=portfolio_build(portfolio=portfolio,thedate=thedate,pastyears=pastyears, \
244
- printout=printout,graph=graph)
245
-
246
- return dflist
247
-
248
- #==============================================================================
249
- if __name__=='__main__':
250
- #测试1
251
- Market={'Market':('US','^GSPC')}
252
- Market={'Market':('US','^GSPC','我的组合001')}
253
- Stocks1={'AAPL':.3,'MSFT':.15,'AMZN':.15,'GOOG':.01}
254
- Stocks2={'XOM':.02,'JNJ':.02,'JPM':.01,'TSLA':.3,'SBUX':.03}
255
- portfolio=dict(Market,**Stocks1,**Stocks2)
256
-
257
- #测试2
258
- Market={'Market':('China','000300.SS','养猪1号组合')}
259
- porkbig={'000876.SZ':0.20,#新希望
260
- '300498.SZ':0.15,#温氏股份
261
- }
262
- porksmall={'002124.SZ':0.10,#天邦股份
263
- '600975.SS':0.10,#新五丰
264
- '603477.SS':0.10,#巨星股份
265
- '000735.SZ':0.07,#罗牛山
266
- }
267
- portfolio=dict(Market,**porkbig,**porksmall)
268
-
269
- #测试3
270
- Market={'Market':('China','000300.SS','股债基组合')}
271
- Stocks={'600519.SS':0.3,#股票:贵州茅台
272
- 'sh010504':[0.5,'bond'],#05国债⑷
273
- '010504.SS':('fund',0.2),#招商稳兴混合C基金
274
- }
275
- portfolio=dict(Market,**Stocks)
276
-
277
- printout=True
278
- graph=False
279
-
280
- indicator='Adj Close'
281
- adjust='qfq'; source='auto'; ticker_type='bond'
282
- thedate='2024-6-19'
283
- pastyears=2
284
-
285
-
286
- #测试3
287
- Market={'Market':('China','000300.SS','股债基组合')}
288
- Stocks={'600519.SS':0.3,#股票:贵州茅台
289
- 'sh010504':[0.5,'bond'],#05国债⑷
290
- '010504.SS':('fund',0.2),#招商稳兴混合C基金
291
- }
292
- portfolio=dict(Market,**Stocks)
293
-
294
- indicator='Close'
295
- adjust=''; source='auto'; ticker_type='auto'
296
- thedate='2024-6-19'
297
- pastyears=1
298
- printout=True
299
- graph=False
300
-
301
-
302
- pf_info=portfolio_build(portfolio,thedate,pastyears,printout,graph)
303
-
304
- """
305
- def portfolio_cumret(portfolio,thedate,pastyears=1, \
306
- RF=0, \
307
- printout=True,graph=True):
308
- """
309
- def portfolio_build(portfolio,thedate='default',pastyears=3, \
310
- indicator='Adj Close', \
311
- adjust='qfq',source='auto',ticker_type='auto', \
312
- printout=False,graph=False,facecolor='papayawhip'):
313
- """
314
- 功能:收集投资组合成份股数据,绘制收益率趋势图,并与等权和期间内交易额加权策略组合比较
315
- 注意:
316
- 1. 此处无需RF,待到优化策略时再指定
317
- 2. printout=True控制下列内容是否显示:
318
- 获取股价时的信息
319
- 是否显示原始组合、等权重组合和交易金额加权组合的成分股构成
320
- 是否显示原始组合、等权重组合和交易金额加权组合的收益风险排名
321
- 3. pastyears=3更有可能生成斜向上的椭圆形可行集,短于3形状不佳,长于3改善形状有限。
322
- 需要与不同行业的证券搭配。同行业证券相关性较强,不易生成斜向上的椭圆形可行集。
323
- 4. 若ticker_type='fund'可能导致无法处理股票的复权价!
324
- 5. 若要指定特定的证券为债券,则需要使用列表逐一指定证券的类型(股票,债券,基金)
325
- 6. 默认采用前复权计算收益率,更加平稳
326
- """
327
- #判断复权标志
328
- indicator_list=['Close','Adj Close']
329
- if indicator not in indicator_list:
330
- print(" Warning(portfolio_build): invalid indicator",indicator)
331
- print(" Supported indicator:",indicator_list)
332
- indicator='Adj Close'
333
-
334
- adjust_list=['','qfq','hfq']
335
- if adjust not in adjust_list:
336
- print(" Warning(portfolio_build): invalid indicator",adjust)
337
- print(" Supported adjust:",adjust_list)
338
- adjust='qfq'
339
-
340
- import datetime
341
- stoday = datetime.date.today()
342
- if thedate=='default':
343
- thedate=str(stoday)
344
- else:
345
- if not check_date(thedate):
346
- print(" #Warning(portfolio_build): invalid date",thedate)
347
- return None
348
-
349
- print(f" Searching portfolio info for recent {pastyears} years ...")
350
- # 解构投资组合
351
- scope,_,tickerlist,sharelist0,ticker_type=decompose_portfolio(portfolio)
352
- pname=portfolio_name(portfolio)
353
-
354
- #如果持仓份额总数不为1,则将其转换为总份额为1
355
- import numpy as np
356
- totalshares=np.sum(sharelist0)
357
- if abs(totalshares - 1) >= 0.00001:
358
- print(" #Warning(portfolio_build): total weights is",totalshares,"\b, expecting 1.0 here")
359
- print(" Action taken: automatically converted into total weights 1.0")
360
- sharelist=list(sharelist0/totalshares)
361
- else:
362
- sharelist=sharelist0
363
-
364
- #..........................................................................
365
- # 计算历史数据的开始日期
366
- start=get_start_date(thedate,pastyears)
367
-
368
- #处理无风险利率,不再需要,但为兼容考虑仍保留,根据手动输入的RF构造rf_df以便后续改动量较小
369
- import pandas as pd
370
- date_series = pd.date_range(start=start,end=thedate,freq='D')
371
- rf_df=pd.DataFrame(index=date_series)
372
- rf_df['date']=rf_df.index
373
- rf_df['date']=rf_df['date'].apply(lambda x: x.strftime('%Y-%m-%d'))
374
- rf_df['RF']=RF=0
375
- rf_df['rf_daily']=RF/365
376
- """
377
- #一次性获得无风险利率,传递给后续函数,避免后续每次获取,耗费时间
378
- if RF:
379
- rf_df=get_rf_daily(start,thedate,scope,rate_period,rate_type)
380
- #结果字段中,RF是日利率百分比,rf_daily是日利率数值
381
- if rf_df is None:
382
- #print(" #Error(portfolio_build): failed to retrieve risk-free interest rate in",scope)
383
- print(" #Warning: all subsequent portfolio optimizations cannot proceed")
384
- print(" Solution1: try again after until success to include risk-free interest rate in calculation")
385
- print(" Solution2: use RF=False in script command to ignore risk-free interest rate in calculation")
386
- return None
387
- else:
388
- rf_df=None
389
- """
390
- #..........................................................................
391
- import os, sys
392
- class HiddenPrints:
393
- def __enter__(self):
394
- self._original_stdout = sys.stdout
395
- sys.stdout = open(os.devnull, 'w')
396
-
397
- def __exit__(self, exc_type, exc_val, exc_tb):
398
- sys.stdout.close()
399
- sys.stdout = self._original_stdout
400
-
401
- # 抓取投资组合股价
402
- #prices=get_prices(tickerlist,start,thedate)
403
- #判断是否赚取复权价
404
- if indicator == 'Adj Close' and adjust == '':
405
- adjust='qfq'
406
- if indicator == 'Close' and adjust != '':
407
- indicator = 'Adj Close'
408
-
409
- if printout:
410
- #prices=get_prices_simple(tickerlist,start,thedate) #有待改造?
411
- #债券优先
412
- prices,found=get_price_mticker(tickerlist,start,thedate, \
413
- adjust=adjust,source=source,ticker_type=ticker_type,fill=False)
414
- else:
415
- with HiddenPrints():
416
- #prices=get_prices_simple(tickerlist,start,thedate) #有待改造?
417
- prices,found=get_price_mticker(tickerlist,start,thedate, \
418
- adjust=adjust,source=source,ticker_type=ticker_type,fill=False)
419
-
420
- if found == 'Found':
421
- ntickers=len(list(prices['Close']))
422
- nrecords=len(prices)
423
- #print(" Successfully retrieved",ntickers,"stocks with",nrecords,"record(s) respectively")
424
- print(" Successfully retrieved prices of",ntickers,"securities for",pname)
425
-
426
- if ntickers != len(tickerlist):
427
- print(" However, failed to access some securities, unable to build portfolio",pname)
428
- return None
429
-
430
- #if prices is None:
431
- if found == 'None':
432
- print(" #Error(portfolio_build): failed to get portfolio prices",pname)
433
- return None
434
- #if len(prices) == 0:
435
- if found == 'Empty':
436
- print(" #Error(portfolio_build): retrieved empty prices for",pname)
437
- return None
438
- #..........................................................................
439
- #判断是否使用复权价:若是,使用Adj Close直接覆盖Close。方法最简单,且兼容后续处理!
440
- if (indicator =='Adj Close') or (adjust !=''):
441
- prices_collist=list(prices)
442
- for pc in prices_collist:
443
- pc1=pc[0]; pc2=pc[1]
444
- if pc1=='Close':
445
- pc_adj=('Adj Close',pc2)
446
- prices[pc]=prices[pc_adj]
447
-
448
- # 取各个成份股的收盘价
449
- aclose=prices['Close']
450
- member_prices=aclose
451
- # 计算各个成份股的日收益率,并丢弃缺失值
452
- StockReturns = aclose.pct_change().dropna()
453
- if len(StockReturns) == 0:
454
- print("\n #Error(portfolio_build): retrieved empty returns for",pname)
455
- return None
456
-
457
- # 保存各个成份股的收益率数据,为了后续调用的方便
458
- stock_return = StockReturns.copy()
459
-
460
- # 将原投资组合的权重存储为numpy数组类型,为了合成投资组合计算方便
461
- import numpy as np
462
- portfolio_weights = np.array(sharelist)
463
- # 合成portfolio的日收益率
464
- WeightedReturns = stock_return.mul(portfolio_weights, axis=1)
465
- # 原投资组合的收益率
466
- StockReturns['Portfolio'] = WeightedReturns.sum(axis=1)
467
- #..........................................................................
468
- #lang = check_language()
469
- #..........................................................................
470
-
471
- # 绘制原投资组合的收益率曲线,以便使用收益率%来显示
472
- if graph:
473
- plotsr = StockReturns['Portfolio']
474
- plotsr.plot(label=pname)
475
- plt.axhline(y=0,ls=":",c="red")
476
-
477
- title_txt=text_lang("投资组合: 日收益率的变化趋势","Investment Portfolio: Daily Return")
478
- ylabel_txt=text_lang("日收益率","Daily Return")
479
- source_txt=text_lang("来源: 综合新浪/东方财富/Stooq/雅虎等, ","Data source: Sina/EM/Stooq/Yahoo, ")
480
-
481
- plt.title(title_txt)
482
- plt.ylabel(ylabel_txt)
483
-
484
- stoday = datetime.date.today()
485
- plt.xlabel(source_txt+str(stoday))
486
-
487
- plt.gca().set_facecolor(facecolor)
488
-
489
- plt.legend(); plt.show(); plt.close()
490
- #..........................................................................
491
-
492
- # 计算原投资组合的持有收益率,并绘图
493
- name_list=["Portfolio"]
494
- label_list=[pname]
495
-
496
-
497
- titletxt=text_lang("投资组合: 持有收益率的变化趋势","Investment Portfolio: Holding Period Return%")
498
- ylabeltxt=text_lang("持有收益率","Holding Period Return%")
499
- xlabeltxt1=text_lang("数据来源: 综合新浪/东方财富/Stooq/雅虎等, ","Data source: Sina/EM/Stooq/Yahoo, ")
500
- xlabeltxt=xlabeltxt1+str(stoday)
501
-
502
- #绘制持有收益率曲线
503
- if graph:
504
- cumulative_returns_plot(StockReturns,name_list,titletxt,ylabeltxt,xlabeltxt,label_list,facecolor=facecolor)
505
- #..........................................................................
506
-
507
- # 构造等权重组合Portfolio_EW的持有收益率
508
- numstocks = len(tickerlist)
509
- # 平均分配每一项的权重
510
- portfolio_weights_ew = np.repeat(1/numstocks, numstocks)
511
- # 合成等权重组合的收益,按行横向加总
512
- StockReturns['Portfolio_EW']=stock_return.mul(portfolio_weights_ew,axis=1).sum(axis=1)
513
- #..........................................................................
514
-
515
- # 创建交易额加权组合:按照成交金额计算期间内交易额均值。债券和基金信息中无交易量!
516
- if ('bond' not in ticker_type) and ('fund' not in ticker_type):
517
- tamount=prices['Close']*prices['Volume']
518
- tamountlist=tamount.mean(axis=0) #求列的均值
519
- tamountlist_array = np.array(tamountlist)
520
- # 计算成交金额权重
521
- portfolio_weights_lw = tamountlist_array / np.sum(tamountlist_array)
522
- # 计算成交金额加权的组合收益
523
- StockReturns['Portfolio_LW'] = stock_return.mul(portfolio_weights_lw, axis=1).sum(axis=1)
524
-
525
- #绘制累计收益率对比曲线
526
- title_txt=text_lang("投资组合策略:业绩对比","Portfolio Strategies: Performance")
527
- Portfolio_EW_txt=text_lang("等权重策略","Equal-weighted")
528
- if ('bond' not in ticker_type) and ('fund' not in ticker_type):
529
- Portfolio_LW_txt=text_lang("交易额加权策略","Amount-weighted")
530
-
531
- name_list=['Portfolio', 'Portfolio_EW', 'Portfolio_LW']
532
- label_list=[pname, Portfolio_EW_txt, Portfolio_LW_txt]
533
- else:
534
- name_list=['Portfolio', 'Portfolio_EW']
535
- label_list=[pname, Portfolio_EW_txt]
536
-
537
-
538
- titletxt=title_txt
539
-
540
- #绘制各个投资组合的持有收益率曲线
541
- if graph:
542
- cumulative_returns_plot(StockReturns,name_list,titletxt,ylabeltxt,xlabeltxt,label_list)
543
-
544
- #打印各个投资组合的持股比例
545
- member_returns=stock_return
546
- if printout:
547
- portfolio_expectation_universal(pname,member_returns,portfolio_weights,member_prices,ticker_type)
548
- portfolio_expectation_universal(Portfolio_EW_txt,member_returns,portfolio_weights_ew,member_prices,ticker_type)
549
-
550
- if ('bond' not in ticker_type) and ('fund' not in ticker_type):
551
- portfolio_expectation_universal(Portfolio_LW_txt,member_returns,portfolio_weights_lw,member_prices,ticker_type)
552
-
553
- #返回投资组合的综合信息
554
- member_returns=stock_return
555
- portfolio_returns=StockReturns[name_list]
556
-
557
- #投资组合名称改名
558
- portfolio_returns=cvt_portfolio_name(pname,portfolio_returns)
559
-
560
- #打印现有投资组合策略的排名
561
- if printout:
562
- portfolio_ranks(portfolio_returns,pname)
563
-
564
- #
565
- if ('bond' not in ticker_type) and ('fund' not in ticker_type):
566
- return [[portfolio,thedate,member_returns,rf_df,member_prices], \
567
- [portfolio_returns,portfolio_weights,portfolio_weights_ew,portfolio_weights_lw]]
568
- else:
569
- return [[portfolio,thedate,member_returns,rf_df,member_prices], \
570
- [portfolio_returns,portfolio_weights,portfolio_weights_ew,None]]
571
-
572
-
573
- if __name__=='__main__':
574
- X=portfolio_build(portfolio,'2021-9-30')
575
-
576
- if __name__=='__main__':
577
- pf_info=portfolio_build(portfolio,'2021-9-30')
578
-
579
- #==============================================================================
580
-
581
- def portfolio_expret(portfolio,today,pastyears=1, \
582
- RF=0,printout=True,graph=True):
583
- """
584
- 功能:绘制投资组合的持有期收益率趋势图,并与等权和期间内交易额加权组合比较
585
- 套壳原来的portfolio_build函数,以维持兼容性
586
- expret: expanding return,以维持与前述章节名词的一致性
587
- hpr: holding period return, 持有(期)收益率
588
- 注意:实验发现RF对于结果的影响极其微小难以观察,默认设为不使用无风险利率调整收益,以加快运行速度
589
- """
590
- #处理失败的返回值
591
- results=portfolio_build(portfolio,today,pastyears, \
592
- rate_period,rate_type,RF,printout,graph)
593
- if results is None: return None
594
-
595
- [[portfolio,thedate,member_returns,rf_df,member_prices], \
596
- [portfolio_returns,portfolio_weights,portfolio_weights_ew,portfolio_weights_lw]] = results
597
-
598
- return [[portfolio,thedate,member_returns,rf_df,member_prices], \
599
- [portfolio_returns,portfolio_weights,portfolio_weights_ew,portfolio_weights_lw]]
600
-
601
- if __name__=='__main__':
602
- pf_info=portfolio_expret(portfolio,'2021-9-30')
603
-
604
- #==============================================================================
605
- def portfolio_correlate(pf_info):
606
- """
607
- 功能:绘制投资组合成份股之间相关关系的热力图
608
- """
609
- [[portfolio,thedate,stock_return,_,_],_]=pf_info
610
- pname=portfolio_name(portfolio)
611
-
612
- #取出观察期
613
- hstart0=stock_return.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
614
- hend0=stock_return.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
615
-
616
- sr=stock_return.copy()
617
- collist=list(sr)
618
- for col in collist:
619
- #投资组合中名称翻译以债券优先处理,因此几乎没有人把基金作为成分股
620
- sr.rename(columns={col:ticker_name(col,'bond')},inplace=True)
621
-
622
- # 计算相关矩阵
623
- correlation_matrix = sr.corr()
624
-
625
- # 导入seaborn
626
- import seaborn as sns
627
- # 创建热图
628
- sns.heatmap(correlation_matrix,annot=True,cmap="YlGnBu",linewidths=0.3,
629
- annot_kws={"size": 16})
630
- plt.title(pname+": 成份股收益率之间的相关系数")
631
- plt.ylabel("成份股票")
632
-
633
- footnote1="观察期间: "+hstart+'至'+hend
634
- import datetime as dt; stoday=dt.date.today()
635
- footnote2="\n来源:Sina/EM/stooq,"+str(stoday)
636
- plt.xlabel(footnote1+footnote2)
637
- plt.xticks(rotation=90); plt.yticks(rotation=0)
638
-
639
- plt.gca().set_facecolor('papayawhip')
640
- plt.show()
641
-
642
- return
643
-
644
- if __name__=='__main__':
645
- Market={'Market':('US','^GSPC','我的组合001')}
646
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
647
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
648
- portfolio=dict(Market,**Stocks1,**Stocks2)
649
- pf_info=portfolio_expret(portfolio,'2019-12-31')
650
-
651
- portfolio_correlate(pf_info)
652
- #==============================================================================
653
- def portfolio_covar(pf_info):
654
- """
655
- 功能:计算投资组合成份股之间的协方差
656
- """
657
- [[portfolio,thedate,stock_return,_,_],_]=pf_info
658
- pname=portfolio_name(portfolio)
659
-
660
- #取出观察期
661
- hstart0=stock_return.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
662
- hend0=stock_return.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
663
-
664
- # 计算协方差矩阵
665
- cov_mat = stock_return.cov()
666
- # 年化协方差矩阵,252个交易日
667
- cov_mat_annual = cov_mat * 252
668
-
669
- # 导入seaborn
670
- import seaborn as sns
671
- # 创建热图
672
- sns.heatmap(cov_mat_annual,annot=True,cmap="YlGnBu",linewidths=0.3,
673
- annot_kws={"size": 8})
674
- plt.title(pname+": 成份股之间的协方差")
675
- plt.ylabel("成份股票")
676
-
677
- footnote1="观察期间: "+hstart+'至'+hend
678
- import datetime as dt; stoday=dt.date.today()
679
- footnote2="\n来源:Sina/EM/stooq,"+str(stoday)
680
- plt.xlabel(footnote1+footnote2)
681
- plt.xticks(rotation=90)
682
- plt.yticks(rotation=0)
683
-
684
- plt.gca().set_facecolor('papayawhip')
685
- plt.show()
686
-
687
- return
688
-
689
- #==============================================================================
690
- def portfolio_expectation_original(pf_info):
691
- """
692
- 功能:计算原始投资组合的年均收益率和标准差
693
- 输入:pf_info
694
- 输出:年化收益率和标准差
695
- """
696
- [[portfolio,_,member_returns,_,member_prices],[_,portfolio_weights,_,_]]=pf_info
697
- pname=portfolio_name(portfolio)
698
-
699
- portfolio_expectation_universal(pname,member_returns,portfolio_weights,member_prices)
700
-
701
- return
702
-
703
- if __name__=='__main__':
704
- Market={'Market':('US','^GSPC','我的组合001')}
705
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
706
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
707
- portfolio=dict(Market,**Stocks1,**Stocks2)
708
- pf_info=portfolio_expret(portfolio,'2019-12-31')
709
-
710
- portfolio_expectation_original(pf_info)
711
-
712
- #==============================================================================
713
- def portfolio_expectation_universal(pname,member_returns,portfolio_weights,member_prices,ticker_type):
714
- """
715
- 功能:计算给定成份股收益率和持股权重的投资组合年均收益率和标准差
716
- 输入:投资组合名称,成份股历史收益率数据表,投资组合权重series
717
- 输出:年化收益率和标准差
718
- 用途:求出MSR、GMV等持仓策略后计算投资组合的年化收益率和标准差
719
- """
720
-
721
- #观察期
722
- hstart0=member_returns.index[0]
723
- #hstart=str(hstart0.date())
724
- hstart=str(hstart0.strftime("%Y-%m-%d"))
725
- hend0=member_returns.index[-1]
726
- #hend=str(hend0.date())
727
- hend=str(hend0.strftime("%Y-%m-%d"))
728
- tickerlist=list(member_returns)
729
-
730
- #合成投资组合的历史收益率,按行横向加权求和
731
- preturns=member_returns.copy() #避免改变输入的数据
732
- preturns['Portfolio']=preturns.mul(portfolio_weights,axis=1).sum(axis=1)
733
-
734
- #计算一手投资组合的价格,最小持股份额的股票需要100股
735
- import numpy as np
736
- min_weight=np.min(portfolio_weights)
737
- # 将最少持股的股票份额转换为1
738
- portfolio_weights_1=portfolio_weights / min_weight * 1
739
- portfolio_values=member_prices.mul(portfolio_weights_1,axis=1).sum(axis=1)
740
- portfolio_value_thedate=portfolio_values[-1:].values[0]
741
-
742
- #计算年化收益率:按列求均值,需要有选项:滚动的年化收益率或月度收益率?
743
- mean_return=preturns['Portfolio'].mean(axis=0)
744
- annual_return = (1 + mean_return)**252 - 1
745
-
746
- #计算年化标准差
747
- std_return=preturns['Portfolio'].std(axis=0)
748
- import numpy as np
749
- annual_std = std_return*np.sqrt(252)
750
-
751
- lang=check_language()
752
- import datetime as dt; stoday=dt.date.today()
753
- if lang == 'Chinese':
754
- print("\n ======= 投资组合的收益与风险 =======")
755
- print(" 投资组合:",pname)
756
- print(" 分析日期:",str(hend))
757
- # 投资组合中即使持股比例最低的股票每次交易最少也需要1手(100股)
758
- print(" 1手组合单位价值:","约"+str(round(portfolio_value_thedate/10000*100,2))+"万")
759
- print(" 观察期间:",hstart+'至'+hend)
760
- print(" 年化收益率:",round(annual_return,4))
761
- print(" 年化标准差:",round(annual_std,4))
762
- print(" ***投资组合持仓策略***")
763
- print_tickerlist_sharelist(tickerlist,portfolio_weights,leading_blanks=4,ticker_type=ticker_type)
764
-
765
- print(" *数据来源:Sina/EM/Stooq/Yahoo,"+str(stoday)+"统计")
766
- else:
767
- print("\n ======= Investment Portfolio: Return and Risk =======")
768
- print(" Investment portfolio:",pname)
769
- print(" Date of analysis:",str(hend))
770
- print(" Value of portfolio:","about "+str(round(portfolio_value_thedate/1000,2))+"K/portfolio unit")
771
- print(" Period of sample:",hstart+' to '+hend)
772
- print(" Annualized return:",round(annual_return,4))
773
- print(" Annualized std of return:",round(annual_std,4))
774
- print(" ***Portfolio Constructing Strategy***")
775
- print_tickerlist_sharelist(tickerlist,portfolio_weights,4)
776
-
777
- print(" *Data source: Sina/EM/Stooq/Yahoo, "+str(stoday))
778
-
779
- return
780
-
781
- if __name__=='__main__':
782
- Market={'Market':('US','^GSPC','我的组合001')}
783
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
784
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
785
- portfolio=dict(Market,**Stocks1,**Stocks2)
786
- pf_info=portfolio_expret(portfolio,'2019-12-31')
787
-
788
- [[portfolio,thedate,member_returns,_,_],[_,portfolio_weights,_,_]]=pf_info
789
- pname=portfolio_name(portfolio)
790
-
791
- portfolio_expectation2(pname,member_returns, portfolio_weights)
792
-
793
- #==============================================================================
794
- def portfolio_expectation(pname,pf_info,portfolio_weights,ticker_type):
795
- """
796
- 功能:计算给定pf_info和持仓权重的投资组合年均收益率和标准差
797
- 输入:投资组合名称,pf_info,投资组合权重series
798
- 输出:年化收益率和标准差
799
- 用途:求出持仓策略后计算投资组合的年化收益率和标准差,为外部独立使用方便
800
- """
801
- [[_,_,member_returns,_,member_prices],_]=pf_info
802
-
803
- portfolio_expectation_universal(pname,member_returns,portfolio_weights,member_prices,ticker_type)
804
-
805
- return
806
-
807
- if __name__=='__main__':
808
- Market={'Market':('US','^GSPC','我的组合001')}
809
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
810
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
811
- portfolio=dict(Market,**Stocks1,**Stocks2)
812
- pf_info=portfolio_expret(portfolio,'2019-12-31')
813
-
814
- [[portfolio,thedate,member_returns,_,_],[_,portfolio_weights,_,_]]=pf_info
815
- pname=portfolio_name(portfolio)
816
-
817
- portfolio_expectation2(pname,member_returns, portfolio_weights)
818
-
819
-
820
- #==============================================================================
821
-
822
-
823
- def portfolio_ranks(portfolio_returns,pname,facecolor='papayawhip'):
824
- """
825
- 功能:区分中英文
826
- """
827
- """
828
- lang = check_language()
829
- if lang == 'Chinese':
830
- df=portfolio_ranks_cn(portfolio_returns=portfolio_returns,pname=pname,facecolor=facecolor)
831
- else:
832
- df=portfolio_ranks_en(portfolio_returns=portfolio_returns,pname=pname)
833
- """
834
- df=portfolio_ranks_cn(portfolio_returns=portfolio_returns,pname=pname,facecolor=facecolor)
835
-
836
- return df
837
-
838
- #==============================================================================
839
-
840
- def portfolio_ranks_cn(portfolio_returns,pname,facecolor='papayawhip'):
841
- """
842
- 功能:打印现有投资组合的收益率、标准差排名,收益率降序,标准差升序,中文/英文
843
- """
844
- #临时保存,避免影响原值
845
- pr=portfolio_returns.copy()
846
-
847
- #统一核定小数位数
848
- ndecimals=2
849
-
850
- #以pname组合作为基准
851
- import numpy as np
852
- mean_return_pname=pr[pname].mean(axis=0)
853
- annual_return_pname=round(((1 + mean_return_pname)**252 - 1)*100,ndecimals)
854
- """
855
- if annual_return_pname > 0:
856
- pct_style=True #百分比模式
857
- else: #数值模式,直接加减
858
- pct_style=False
859
- """
860
- pct_style=False
861
-
862
- std_return_pname=pr[pname].std(axis=0)
863
- annual_std_pname= round((std_return_pname*np.sqrt(252))*100,ndecimals)
864
-
865
- import pandas as pd
866
- #prr=pd.DataFrame(columns=["名称","年化收益率","收益率变化","年化标准差","标准差变化","收益/风险"])
867
- #prr=pd.DataFrame(columns=["名称","年化收益率%","收益率变化","年化标准差%","标准差变化","收益/风险"])
868
- prr=pd.DataFrame(columns=["名称","年化收益率%","收益率变化","年化标准差%","标准差变化","收益率/标准差"])
869
- cols=list(pr)
870
- for c in cols:
871
-
872
- #年化收益率:按列求均值
873
- mean_return=pr[c].mean(axis=0)
874
- annual_return = round(((1 + mean_return)**252 - 1)*100,ndecimals)
875
-
876
- if pct_style:
877
- return_chg=round((annual_return - annual_return_pname) / annual_return_pname * 100,ndecimals)
878
- else:
879
- return_chg=round((annual_return - annual_return_pname),ndecimals)
880
-
881
- #收益率变化
882
- if return_chg==0:
883
- return_chg_str=text_lang("基准","Benchmark")
884
- elif return_chg > 0:
885
- if pct_style:
886
- return_chg_str='+'+str(return_chg)+'%'
887
- else:
888
- return_chg_str='+'+str(return_chg)
889
- else:
890
- if pct_style:
891
- return_chg_str='-'+str(-return_chg)+'%'
892
- else:
893
- return_chg_str='-'+str(-return_chg)
894
-
895
- #年化标准差
896
- std_return=pr[c].std(axis=0)
897
- annual_std = round((std_return*np.sqrt(252))*100,ndecimals)
898
-
899
- #sharpe_ratio=round(annual_return / annual_std,2)
900
- sharpe_ratio=round((annual_return) / annual_std,ndecimals)
901
-
902
- if pct_style:
903
- std_chg=round((annual_std - annual_std_pname) / annual_std_pname * 100,ndecimals)
904
- else:
905
- std_chg=round((annual_std - annual_std_pname),ndecimals)
906
-
907
- #标准差变化
908
- if std_chg==0:
909
- std_chg_str=text_lang("基准","Benchmark")
910
- elif std_chg > 0:
911
- if pct_style:
912
- std_chg_str='+'+str(std_chg)+'%'
913
- else:
914
- std_chg_str='+'+str(std_chg)
915
- else:
916
- if pct_style:
917
- std_chg_str='-'+str(-std_chg)+'%'
918
- else:
919
- std_chg_str='-'+str(-std_chg)
920
-
921
- row=pd.Series({"名称":c,"年化收益率%":annual_return, \
922
- "收益率变化":return_chg_str, \
923
- "年化标准差%":annual_std,"标准差变化":std_chg_str,"收益率/标准差":sharpe_ratio})
924
- try:
925
- prr=prr.append(row,ignore_index=True)
926
- except:
927
- prr=prr._append(row,ignore_index=True)
928
-
929
- #先按风险降序排名,高者排前面
930
- prr.sort_values(by="年化标准差%",ascending=False,inplace=True)
931
- prr.reset_index(inplace=True)
932
- prr['风险排名']=prr.index+1
933
-
934
- #再按收益降序排名,高者排前面
935
- prr.sort_values(by="年化收益率%",ascending=False,inplace=True)
936
- prr.reset_index(inplace=True)
937
- prr['收益排名']=prr.index+1
938
-
939
- #prr2=prr[["名称","收益排名","风险排名","年化收益率","年化标准差","收益率变化","标准差变化","收益/风险"]]
940
- prr2=prr[["名称","收益排名","年化收益率%","收益率变化", \
941
- "风险排名","年化标准差%","标准差变化", \
942
- "收益率/标准差"]]
943
- prr2.sort_values(by="年化收益率%",ascending=False,inplace=True)
944
- #prr2.reset_index(inplace=True)
945
-
946
- #打印
947
- """
948
- print("\n========= 投资组合策略排名:平衡收益与风险 =========\n")
949
- #打印对齐
950
- pd.set_option('display.max_columns', 1000)
951
- pd.set_option('display.width', 1000)
952
- pd.set_option('display.max_colwidth', 1000)
953
- pd.set_option('display.unicode.ambiguous_as_wide', True)
954
- pd.set_option('display.unicode.east_asian_width', True)
955
-
956
- #print(prr2.to_string(index=False,header=False))
957
- #print(prr2.to_string(index=False))
958
-
959
- alignlist=['left']+['center']*(len(list(prr2))-2)+['right']
960
- print(prr2.to_markdown(index=False,tablefmt='plain',colalign=alignlist))
961
- """
962
- #一点改造
963
- print('') #空一行
964
- prr2.index=prr2.index + 1
965
- prr2.rename(columns={'名称':'投资组合名称/策略'},inplace=True)
966
- for c in list(prr2):
967
- try:
968
- prr2[c]=prr2[c].apply(lambda x: str(round(x,4)) if isinstance(x,float) else str(x))
969
- except: pass
970
-
971
- titletxt=text_lang('投资组合策略排名:平衡收益与风险','Investment Portfolio Strategies: Performance, Balancing Return and Risk')
972
- """
973
- dispt=prr2.style.set_caption(titletxt).set_table_styles(
974
- [{'selector':'caption',
975
- 'props':[('color','black'),('font-size','16px'),('font-weight','bold')]}])
976
- dispf=dispt.set_properties(**{'text-align':'center'})
977
-
978
- from IPython.display import display
979
- display(dispf)
980
- """
981
-
982
- prr2.rename(columns={"投资组合名称/策略":text_lang("投资组合名称/策略","Strategy"), \
983
- "收益排名":text_lang("收益排名","Return#"), \
984
- "年化收益率%":text_lang("年化收益率%","pa Return%"), \
985
- "收益率变化":text_lang("收益率变化","Return%+/-"), \
986
- "风险排名":text_lang("风险排名","Risk#"), \
987
- "年化标准差%":text_lang("年化标准差%","pa Std%"), \
988
- "标准差变化":text_lang("标准差变化","Std%+/-"), \
989
- "收益率/标准差":text_lang("收益/风险性价比","Return/Std")}, \
990
- inplace=True)
991
-
992
- #重新排名:相同的值赋予相同的序号
993
- prr2[text_lang("年化收益率%","pa Return%")]=prr2[text_lang("年化收益率%","pa Return%")].apply(lambda x: round(float(x),ndecimals))
994
- prr2[text_lang("收益排名","Return#")]=prr2[text_lang("年化收益率%","pa Return%")].rank(ascending=False,method='dense')
995
- prr2[text_lang("收益排名","Return#")]=prr2[text_lang("收益排名","Return#")].apply(lambda x: int(x))
996
-
997
- prr2[text_lang("年化标准差%","pa Std%")]=prr2[text_lang("年化标准差%","pa Std%")].apply(lambda x: round(float(x),ndecimals))
998
- prr2[text_lang("风险排名","Risk#")]=prr2[text_lang("年化标准差%","pa Std%")].rank(ascending=False,method='dense')
999
- prr2[text_lang("风险排名","Risk#")]=prr2[text_lang("风险排名","Risk#")].apply(lambda x: int(x))
1000
-
1001
- prr2[text_lang("收益/风险性价比","Return/Std")]=prr2[text_lang("收益/风险性价比","Return/Std")].apply(lambda x: round(float(x),ndecimals))
1002
- prr2[text_lang("性价比排名","Ret/Std#")]=prr2[text_lang("收益/风险性价比","Return/Std")].rank(ascending=False,method='dense')
1003
- prr2[text_lang("性价比排名","Ret/Std#")]=prr2[text_lang("性价比排名","Ret/Std#")].apply(lambda x: int(x))
1004
-
1005
- df_display_CSS(prr2,titletxt=titletxt,footnote='',facecolor='papayawhip',decimals=ndecimals, \
1006
- first_col_align='left',second_col_align='center', \
1007
- last_col_align='center',other_col_align='center', \
1008
- titile_font_size='15px',heading_font_size='13px', \
1009
- data_font_size='13px')
1010
-
1011
- """
1012
- print(' ') #空一行
1013
-
1014
- disph=prr2.style.hide() #不显示索引列
1015
- dispp=disph.format(precision=2) #设置带有小数点的列精度调整为小数点后2位
1016
- #设置标题/列名
1017
- dispt=dispp.set_caption(titletxt).set_table_styles(
1018
- [{'selector':'caption', #设置标题属性
1019
- 'props':[('color','black'),('font-size','18px'),('font-weight','bold')]}, \
1020
- {'selector':'th.col_heading', #设置列名属性
1021
- 'props':[('color','black'),('font-size','17px'),('background-color',facecolor),('text-align','center'),('margin','auto')]}])
1022
- #设置列数值对齐
1023
- dispt1=dispt.set_properties(**{'font-size':'17px'})
1024
- dispf=dispt1.set_properties(**{'text-align':'center'})
1025
- #设置前景背景颜色
1026
- try:
1027
- dispf2=dispf.set_properties(**{'background-color':facecolor,'color':'black'})
1028
- except:
1029
- print(" #Warning(portfolio_ranks_cn): color",facecolor,"is unsupported, changed to default setting")
1030
- dispf2=dispf.set_properties(**{'background-color':'papayawhip','color':'black'})
1031
-
1032
- from IPython.display import display
1033
- display(dispf2)
1034
-
1035
- print('') #空一行
1036
- """
1037
- return prr2
1038
-
1039
- if __name__=='__main__':
1040
- portfolio_ranks(portfolio_returns,pname)
1041
-
1042
- #==============================================================================
1043
-
1044
- def portfolio_ranks_en(portfolio_returns,pname):
1045
- """
1046
- 功能:打印现有投资组合的收益率、标准差排名,收益率降序,标准差升序,英文
1047
- 废弃!!!
1048
- """
1049
- #临时保存,避免影响原值
1050
- pr=portfolio_returns.copy()
1051
-
1052
- #以pname组合作为基准
1053
- import numpy as np
1054
- mean_return_pname=pr[pname].mean(axis=0)
1055
- annual_return_pname=(1 + mean_return_pname)**252 - 1
1056
- if annual_return_pname > 0:
1057
- pct_style=True
1058
- else:
1059
- pct_style=False
1060
-
1061
- std_return_pname=pr[pname].std(axis=0)
1062
- annual_std_pname= std_return_pname*np.sqrt(252)
1063
-
1064
- import pandas as pd
1065
- prr=pd.DataFrame(columns=["Portfolio","Annualized Return","Change of Return","Annualized Std","Change of Std","Return/Risk"])
1066
- cols=list(pr)
1067
- for c in cols:
1068
- #计算年化收益率:按列求均值
1069
- mean_return=pr[c].mean(axis=0)
1070
- annual_return = (1 + mean_return)**252 - 1
1071
-
1072
- if pct_style:
1073
- return_chg=round((annual_return - annual_return_pname) / annual_return_pname *100,1)
1074
- else:
1075
- return_chg=round((annual_return - annual_return_pname),5)
1076
-
1077
- if return_chg==0:
1078
- return_chg_str="base"
1079
- elif return_chg > 0:
1080
- if pct_style:
1081
- return_chg_str='+'+str(return_chg)+'%'
1082
- else:
1083
- return_chg_str='+'+str(return_chg)
1084
- else:
1085
- if pct_style:
1086
- return_chg_str='-'+str(-return_chg)+'%'
1087
- else:
1088
- return_chg_str='-'+str(-return_chg)
1089
-
1090
- #计算年化标准差
1091
- std_return=pr[c].std(axis=0)
1092
- annual_std = std_return*np.sqrt(252)
1093
-
1094
- sharpe_ratio=round(annual_return / annual_std,4)
1095
-
1096
- if pct_style:
1097
- std_chg=round((annual_std - annual_std_pname) / annual_std_pname *100,4)
1098
- else:
1099
- std_chg=round((annual_std - annual_std_pname),4)
1100
- if std_chg==0:
1101
- std_chg_str="base"
1102
- elif std_chg > 0:
1103
- if pct_style:
1104
- std_chg_str='+'+str(std_chg)+'%'
1105
- else:
1106
- std_chg_str='+'+str(std_chg)
1107
- else:
1108
- if pct_style:
1109
- std_chg_str='-'+str(-std_chg)+'%'
1110
- else:
1111
- std_chg_str='-'+str(-std_chg)
1112
-
1113
- row=pd.Series({"Portfolio":c,"Annualized Return":annual_return,"Change of Return":return_chg_str, \
1114
- "Annualized Std":annual_std,"Change of Std":std_chg_str,"Return/Risk":sharpe_ratio})
1115
- try:
1116
- prr=prr.append(row,ignore_index=True)
1117
- except:
1118
- prr=prr._append(row,ignore_index=True)
1119
-
1120
- #处理小数位数,以便与其他地方的小数位数一致
1121
- prr['Annualized Return']=round(prr['Annualized Return'],4)
1122
- prr['Annualized Std']=round(prr['Annualized Std'],4)
1123
-
1124
- #先按风险降序排名,高者排前面
1125
- prr.sort_values(by="Annualized Std",ascending=False,inplace=True)
1126
- prr.reset_index(inplace=True)
1127
- prr['Risk Rank']=prr.index+1
1128
-
1129
- #再按收益降序排名,高者排前面
1130
- prr.sort_values(by="Annualized Return",ascending=False,inplace=True)
1131
- prr.reset_index(inplace=True)
1132
- prr['Return Rank']=prr.index+1
1133
-
1134
- prr2=prr[["Portfolio","Return Rank","Risk Rank","Annualized Return","Annualized Std","Change of Return","Change of Std","Return/Risk"]]
1135
- prr2.sort_values(by="Annualized Return",ascending=False,inplace=True)
1136
- #prr2.reset_index(inplace=True)
1137
-
1138
- #打印
1139
- print("\n========= Investment Portfolio Strategy Ranking: Balancing Return & Risk =========\n")
1140
- #打印对齐
1141
- pd.set_option('display.max_columns', 1000)
1142
- pd.set_option('display.width', 1000)
1143
- pd.set_option('display.max_colwidth', 1000)
1144
- pd.set_option('display.unicode.ambiguous_as_wide', True)
1145
- pd.set_option('display.unicode.east_asian_width', True)
1146
-
1147
- #print(prr2.to_string(index=False,header=False))
1148
- print(prr2.to_string(index=False))
1149
-
1150
- return prr2
1151
-
1152
- #==============================================================================
1153
- if __name__=='__main__':
1154
- #Define market information and name of the portfolio: Banking #1
1155
- Market={'Market':('China','000300.SS','Banking #1')}
1156
- #First batch of component stocks
1157
- Stocks1={'601939.SS':.3,#CCB Bank
1158
- '600000.SS':.3, #SPDB Bank
1159
- '600036.SS':.2, #CMB Bank
1160
- '601166.SS':.2,#Industrial Bank
1161
- }
1162
- portfolio=dict(Market,**Stocks1)
1163
-
1164
- pf_info=portfolio_build(portfolio,thedate='2024-7-17')
1165
-
1166
- simulation=50000
1167
- convex_hull=True; frontier="both"; facecolor='papayawhip'
1168
-
1169
- portfolio_eset(pf_info,simulation=50000)
1170
-
1171
- def portfolio_feset(pf_info,simulation=10000,convex_hull=True,frontier="both",facecolor='papayawhip'):
1172
- """
1173
- 功能:套壳函数portfolio_eset
1174
- 当frontier不在列表['efficient','inefficient','both']中时,绘制可行集
1175
- 当frontier == 'efficient'时绘制有效边界
1176
- 当frontier == 'inefficient'时绘制无效边界
1177
- 当frontier == 'both'时同时绘制有效边界和无效边界
1178
- 当绘制有效/无效边界时,默认使用凸包绘制(convex_hull=True)
1179
- 注:若可行集形状不佳,首先尝试pastyears=3,再尝试增加simulation数量
1180
- """
1181
- if frontier is None:
1182
- convex_hull=False
1183
- elif isinstance(frontier,str):
1184
- frontier=frontier.lower()
1185
-
1186
- frontier_list=['efficient','inefficient','both']
1187
- if not any(element in frontier for element in frontier_list):
1188
- convex_hull=False
1189
- else:
1190
- convex_hull=True
1191
- else:
1192
- convex_hull=False
1193
-
1194
- results=portfolio_eset(pf_info,simulation=simulation,convex_hull=convex_hull,frontier=frontier,facecolor=facecolor)
1195
-
1196
- return results
1197
-
1198
-
1199
- def portfolio_eset(pf_info,simulation=50000,convex_hull=False,frontier="both",facecolor='papayawhip'):
1200
- """
1201
- 功能:基于随机数,生成大量可能的投资组合,计算各个投资组合的年均收益率和标准差,绘制投资组合的可行集
1202
- 默认绘制散点图凸包:convex_hull=True
1203
- """
1204
- DEBUG=True; MORE_DETAIL=False
1205
-
1206
- frontier_list=['efficient','inefficient','both']
1207
- if isinstance(frontier,str):
1208
- if any(element in frontier for element in frontier_list):
1209
- efficient_set=True
1210
- else:
1211
- efficient_set=False
1212
- else:
1213
- efficient_set=False
1214
-
1215
- [[portfolio,thedate,stock_return,_,_],_]=pf_info
1216
- pname=portfolio_name(portfolio)
1217
- _,_,tickerlist,_,ticker_type=decompose_portfolio(portfolio)
1218
-
1219
- #取出观察期
1220
- hstart0=stock_return.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
1221
- hend0=stock_return.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
1222
-
1223
- #获得成份股个数
1224
- numstocks=len(tickerlist)
1225
-
1226
- # 设置空的numpy数组,用于存储每次模拟得到的成份股权重、投资组合的收益率和标准差
1227
- import numpy as np
1228
- random_p = np.empty((simulation,numstocks+2))
1229
- # 设置随机数种子,这里是为了结果可重复
1230
- np.random.seed(RANDOM_SEED)
1231
-
1232
- # 循环模拟n次随机的投资组合
1233
- print(f" Simulating {simulation} feasible sets of portfolios ...")
1234
- for i in range(simulation):
1235
- # 生成numstocks个随机数,并归一化,得到一组随机的权重数据
1236
- random9 = np.random.random(numstocks)
1237
- random_weight = random9 / np.sum(random9)
1238
-
1239
- # 计算随机投资组合的年化平均收益率
1240
- mean_return=stock_return.mul(random_weight,axis=1).sum(axis=1).mean(axis=0)
1241
- annual_return = (1 + mean_return)**252 - 1
1242
-
1243
- # 计算随机投资组合的年化平均标准差
1244
- std_return=stock_return.mul(random_weight,axis=1).sum(axis=1).std(axis=0)
1245
- annual_std = std_return*np.sqrt(252)
1246
-
1247
- # 将上面生成的权重,和计算得到的收益率、标准差存入数组random_p中
1248
- # 数组矩阵的前numstocks为随机权重,其后为年均收益率,再后为年均标准差
1249
- random_p[i][:numstocks] = random_weight
1250
- random_p[i][numstocks] = annual_return
1251
- random_p[i][numstocks+1] = annual_std
1252
-
1253
- #显示完成进度
1254
- print_progress_percent(i,simulation,steps=10,leading_blanks=2)
1255
-
1256
- # 将numpy数组转化成DataFrame数据框
1257
- import pandas as pd
1258
- RandomPortfolios = pd.DataFrame(random_p)
1259
- # 设置数据框RandomPortfolios每一列的名称
1260
- RandomPortfolios.columns = [ticker + "_weight" for ticker in tickerlist] \
1261
- + ['Returns', 'Volatility']
1262
-
1263
- # 绘制散点图
1264
- """
1265
- RandomPortfolios.plot('Volatility','Returns',kind='scatter',color='y',edgecolors='k')
1266
- """
1267
- #RandomPortfolios['Returns_Volatility']=RandomPortfolios['Returns'] / RandomPortfolios['Volatility']
1268
- #pf_ratio = np.array(RandomPortfolios['Returns_Volatility'])
1269
- pf_ratio = np.array(RandomPortfolios['Returns'] / RandomPortfolios['Volatility'])
1270
- pf_returns = np.array(RandomPortfolios['Returns'])
1271
- pf_volatilities = np.array(RandomPortfolios['Volatility'])
1272
-
1273
- #plt.style.use('seaborn-dark') #不支持中文
1274
- #plt.figure(figsize=(12.8,6.4))
1275
- plt.scatter(pf_volatilities,pf_returns,c=pf_ratio,cmap='RdYlGn',edgecolors='black',marker='o')
1276
- #plt.grid(True)
1277
-
1278
- #绘制散点图轮廓线凸包(convex hull)
1279
- if convex_hull:
1280
- print(" Calculating convex hull ...")
1281
-
1282
- from scipy.spatial import ConvexHull
1283
-
1284
- #构造散点对的列表
1285
- pf_volatilities_list=list(pf_volatilities)
1286
- pf_returns_list=list(pf_returns)
1287
- points=[]
1288
- for x in pf_volatilities_list:
1289
- print_progress_percent2(x,pf_volatilities_list,steps=5,leading_blanks=4)
1290
-
1291
- pos=pf_volatilities_list.index(x)
1292
- y=pf_returns_list[pos]
1293
- points=points+[[x,y]]
1294
-
1295
- #寻找最左侧的坐标:在x最小的同时,y要最大
1296
- points_df = pd.DataFrame(points, columns=['x', 'y'])
1297
- points_df.sort_values(by=['x','y'],ascending=[True,False],inplace=True)
1298
- x1,y1=points_df.head(1).values[0]
1299
- if DEBUG and MORE_DETAIL:
1300
- print("\n*** Leftmost point (x1,y1):",x1,y1)
1301
-
1302
- #寻找最高点的坐标:在y最大的同时,x要最小
1303
- points_df.sort_values(by=['y','x'],ascending=[False,True],inplace=True)
1304
- x2,y2=points_df.head(1).values[0]
1305
- if DEBUG and MORE_DETAIL:
1306
- print("*** Highest point (x2,y2):",x2,y2)
1307
-
1308
- if DEBUG:
1309
- plt.plot([x1,x2],[y1,y2],ls=':',lw=2,alpha=0.5)
1310
-
1311
- #建立最左侧和最高点之间的拟合直线方程y=a+bx
1312
- a=(x1*y2-x2*y1)/(x1-x2); b=(y1-y2)/(x1-x2)
1313
- def y_bar(x_bar):
1314
- return a+b*x_bar
1315
-
1316
- # 计算散点集的外轮廓
1317
- hull = ConvexHull(points)
1318
-
1319
- # 绘制外轮廓线
1320
- firsttime_efficient=True; firsttime_inefficient=True
1321
- for simplex in hull.simplices:
1322
- #p1中是一条线段起点和终点的横坐标
1323
- p1=[points[simplex[0]][0], points[simplex[1]][0]]
1324
- px1=p1[0];px2=p1[1]
1325
- #p2中是一条线段起点和终点的纵坐标
1326
- p2=[points[simplex[0]][1], points[simplex[1]][1]]
1327
- py1=p2[0]; py2=p2[1]
1328
-
1329
- if DEBUG and MORE_DETAIL:
1330
- print("\n*** Hull line start (px1,py1):",px1,py1)
1331
- print("*** Hull line end (px2,py2):",px2,py2)
1332
-
1333
- """
1334
- plt.plot([points[simplex[0]][0], points[simplex[1]][0]],
1335
- [points[simplex[0]][1], points[simplex[1]][1]], 'k-.')
1336
- """
1337
-
1338
- #线段起点:px1,py1;线段终点:px2,py2
1339
- if DEBUG and MORE_DETAIL:
1340
- is_efficient=(py1>=y_bar(px1) or py1==y1) and (py2>=y_bar(px2) or py2==y2)
1341
- print("\n*** is_efficient:",is_efficient)
1342
- print("py1=",py1,"y_bar1",y_bar(px1),"y1=",y1,"py2=",py2,"ybar2=",y_bar(px2),"y2=",y2)
1343
- if px1==x1 and py1==y1:
1344
- print("====== This is the least risk point !")
1345
- if px2==x2 and py2==y2:
1346
- print("====== This is the highest return point !")
1347
-
1348
- #坐标对[px1,py1]既可能作为开始点,也可能作为结束点,[px2,py2]同样
1349
- if ((py1>=y_bar(px1) or py1==y1) and (py2>=y_bar(px2) or py2==y2)) or \
1350
- ((py1>=y_bar(px2) or py1==y2) and (py2>=y_bar(px1) or py2==y1)):
1351
-
1352
- #有效边界
1353
- if frontier.lower() in ['both','efficient']:
1354
- if firsttime_efficient:
1355
- plt.plot(p1,p2, 'r--',label=text_lang("有效边界","Efficient Frontier"),lw=3,alpha=0.5)
1356
- firsttime_efficient=False
1357
- else:
1358
- plt.plot(p1,p2, 'r--',lw=3,alpha=0.5)
1359
- else:
1360
- pass
1361
- else:
1362
- #其余边沿
1363
- if frontier.lower() in ['both','inefficient']:
1364
- if firsttime_inefficient:
1365
- plt.plot(p1,p2, 'k-.',label=text_lang("无效边界","Inefficient Frontier"),alpha=0.5)
1366
- firsttime_inefficient=False
1367
- else:
1368
- plt.plot(p1,p2, 'k-.',alpha=0.5)
1369
- else:
1370
- pass
1371
- else:
1372
- pass
1373
-
1374
- # 空一行
1375
- print('')
1376
-
1377
-
1378
- import datetime as dt; stoday=dt.date.today()
1379
- lang = check_language()
1380
- if lang == 'Chinese':
1381
- if pname == '': pname='投资组合'
1382
-
1383
- plt.colorbar(label='收益率/标准差')
1384
-
1385
- if efficient_set:
1386
- if frontier == 'efficient':
1387
- titletxt0=": 马科维茨有效集(有效边界)"
1388
- elif frontier == 'inefficient':
1389
- titletxt0=": 马科维茨无效集(无效边界)"
1390
- elif frontier == 'both':
1391
- titletxt0=": 马科维茨有效边界与无效边界"
1392
- else:
1393
- titletxt0=": 马科维茨可行集"
1394
- else:
1395
- titletxt0=": 马科维茨可行集"
1396
-
1397
- plt.title(pname+titletxt0,fontsize=title_txt_size)
1398
- plt.ylabel("年化收益率",fontsize=ylabel_txt_size)
1399
-
1400
- footnote1="年化收益率标准差-->"
1401
- footnote2="\n\n基于给定的成份证券构造"+str(simulation)+"个投资组合"
1402
- footnote3="\n观察期间:"+hstart+"至"+hend
1403
- footnote4="\n数据来源: Sina/EM/Stooq/Yahoo, "+str(stoday)
1404
- else:
1405
- if pname == '': pname='Investment Portfolio'
1406
-
1407
- if efficient_set:
1408
- if frontier == 'efficient':
1409
- titletxt0=": Markowitz Efficient Set (Efficient Frontier)"
1410
- elif frontier == 'inefficient':
1411
- titletxt0=": Markowitz Inefficient Set (Inefficient Frontier)"
1412
- elif frontier == 'both':
1413
- titletxt0=": Markowitz Efficient & Inefficient Frontier"
1414
- else:
1415
- titletxt0=": Markowitz Feasible Set"
1416
- else:
1417
- titletxt0=": Markowitz Feasible Set"
1418
-
1419
- plt.colorbar(label='Return/Std')
1420
- plt.title(pname+titletxt0,fontsize=title_txt_size)
1421
- plt.ylabel("Annualized Return",fontsize=ylabel_txt_size)
1422
-
1423
- footnote1="Annualized Std -->\n\n"
1424
- footnote2="Built "+str(simulation)+" portfolios of given securities\n"
1425
- footnote3="Period of sample: "+hstart+" to "+hend
1426
- footnote4="\nData source: Sina/EM/Stooq/Yahoo, "+str(stoday)
1427
-
1428
- plt.xlabel(footnote1+footnote2+footnote3+footnote4,fontsize=xlabel_txt_size)
1429
-
1430
- plt.gca().set_facecolor(facecolor)
1431
- if efficient_set:
1432
- plt.legend(loc='best')
1433
- plt.show()
1434
-
1435
- return [pf_info,RandomPortfolios]
1436
-
1437
- if __name__=='__main__':
1438
- Market={'Market':('US','^GSPC','我的组合001')}
1439
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
1440
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
1441
- portfolio=dict(Market,**Stocks1,**Stocks2)
1442
- pf_info=portfolio_expret(portfolio,'2019-12-31')
1443
-
1444
- es=portfolio_eset(pf_info,simulation=50000)
1445
-
1446
- #==============================================================================
1447
- if __name__=='__main__':
1448
- simulation=1000
1449
- rate_period='1Y'
1450
- rate_type='treasury'
1451
-
1452
- def portfolio_es_sharpe(pf_info,simulation=50000,RF=0):
1453
- """
1454
- 功能:基于随机数,生成大量可能的投资组合,计算各个投资组合的年均风险溢价及其标准差,绘制投资组合的可行集
1455
- """
1456
- print(f" Calculating {simulation} portfolio combinations ...")
1457
-
1458
- [[portfolio,thedate,stock_return0,rf_df,_],_]=pf_info
1459
- pname=portfolio_name(portfolio)
1460
- scope,_,tickerlist,_,ticker_type=decompose_portfolio(portfolio)
1461
-
1462
- #取出观察期
1463
- hstart0=stock_return0.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
1464
- hend0=stock_return0.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
1465
-
1466
- import pandas as pd
1467
- #处理无风险利率
1468
- """
1469
- if RF:
1470
- #rf_df=get_rf_daily(hstart,hend,scope,rate_period,rate_type)
1471
- if not (rf_df is None):
1472
- stock_return1=pd.merge(stock_return0,rf_df,how='inner',left_index=True,right_index=True)
1473
- for t in tickerlist:
1474
- #计算风险溢价
1475
- stock_return1[t]=stock_return1[t]-stock_return1['rf_daily']
1476
-
1477
- stock_return=stock_return1[tickerlist]
1478
- else:
1479
- print(" #Error(portfolio_es_sharpe): failed to retrieve risk-free interest rate, please try again")
1480
- return None
1481
- else:
1482
- #不考虑RF
1483
- stock_return=stock_return0
1484
- """
1485
- rf_daily=RF/365
1486
- for t in tickerlist:
1487
- #计算风险溢价
1488
- stock_return0[t]=stock_return0[t]-rf_daily
1489
- stock_return=stock_return0[tickerlist]
1490
-
1491
- #获得成份股个数
1492
- numstocks=len(tickerlist)
1493
-
1494
- # 设置空的numpy数组,用于存储每次模拟得到的成份股权重、组合的收益率和标准差
1495
- import numpy as np
1496
- random_p = np.empty((simulation,numstocks+2))
1497
- # 设置随机数种子,这里是为了结果可重复
1498
- np.random.seed(RANDOM_SEED)
1499
-
1500
- # 循环模拟n次随机的投资组合
1501
- for i in range(simulation):
1502
- # 生成numstocks个随机数,并归一化,得到一组随机的权重数据
1503
- random9 = np.random.random(numstocks)
1504
- random_weight = random9 / np.sum(random9)
1505
-
1506
- # 计算随机投资组合的年化平均收益率
1507
- mean_return=stock_return.mul(random_weight,axis=1).sum(axis=1).mean(axis=0)
1508
- annual_return = (1 + mean_return)**252 - 1
1509
-
1510
- # 计算随机投资组合的年化平均标准差
1511
- std_return=stock_return.mul(random_weight,axis=1).sum(axis=1).std(axis=0)
1512
- annual_std = std_return*np.sqrt(252)
1513
-
1514
- # 将上面生成的权重,和计算得到的收益率、标准差存入数组random_p中
1515
- # 数组矩阵的前numstocks为随机权重,其后为年均收益率,再后为年均标准差
1516
- random_p[i][:numstocks] = random_weight
1517
- random_p[i][numstocks] = annual_return
1518
- random_p[i][numstocks+1] = annual_std
1519
-
1520
- #显示完成进度
1521
- print_progress_percent(i,simulation,steps=10,leading_blanks=2)
1522
-
1523
- # 将numpy数组转化成DataFrame数据框
1524
- RandomPortfolios = pd.DataFrame(random_p)
1525
- # 设置数据框RandomPortfolios每一列的名称
1526
- RandomPortfolios.columns = [ticker + "_weight" for ticker in tickerlist] \
1527
- + ['Risk premium', 'Risk premium volatility']
1528
-
1529
- return [pf_info,RandomPortfolios]
1530
-
1531
- if __name__=='__main__':
1532
- Market={'Market':('US','^GSPC','我的组合001')}
1533
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
1534
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
1535
- portfolio=dict(Market,**Stocks1,**Stocks2)
1536
- pf_info=portfolio_expret(portfolio,'2019-12-31')
1537
-
1538
- es_sharpe=portfolio_es_sharpe(pf_info,simulation=50000)
1539
-
1540
- #==============================================================================
1541
- if __name__=='__main__':
1542
- simulation=1000
1543
- rate_period='1Y'
1544
- rate_type='treasury'
1545
-
1546
- def portfolio_es_sortino(pf_info,simulation=50000,RF=0):
1547
- """
1548
- 功能:基于随机数,生成大量可能的投资组合,计算各个投资组合的年均风险溢价及其下偏标准差,绘制投资组合的可行集
1549
- """
1550
- print(f" Calculating {simulation} portfolio combinations ...")
1551
-
1552
- [[portfolio,thedate,stock_return0,rf_df,_],_]=pf_info
1553
- pname=portfolio_name(portfolio)
1554
- scope,_,tickerlist,_,ticker_type=decompose_portfolio(portfolio)
1555
-
1556
- #取出观察期
1557
- hstart0=stock_return0.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
1558
- hend0=stock_return0.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
1559
-
1560
- import pandas as pd
1561
- #处理无风险利率
1562
- """
1563
- if RF:
1564
- #rf_df=get_rf_daily(hstart,hend,scope,rate_period,rate_type)
1565
- if not (rf_df is None):
1566
- stock_return1=pd.merge(stock_return0,rf_df,how='inner',left_index=True,right_index=True)
1567
- for t in tickerlist:
1568
- #计算风险溢价
1569
- stock_return1[t]=stock_return1[t]-stock_return1['rf_daily']
1570
-
1571
- stock_return=stock_return1[tickerlist]
1572
- else:
1573
- print(" #Error(portfolio_es_sortino): failed to retrieve risk-free interest rate, please try again")
1574
- return None
1575
- else:
1576
- #不考虑RF
1577
- stock_return=stock_return0
1578
- """
1579
- rf_daily=RF/365
1580
- for t in tickerlist:
1581
- #计算风险溢价
1582
- stock_return0[t]=stock_return0[t]-rf_daily
1583
- stock_return=stock_return0[tickerlist]
1584
-
1585
- #获得成份股个数
1586
- numstocks=len(tickerlist)
1587
-
1588
- # 设置空的numpy数组,用于存储每次模拟得到的成份股权重、组合的收益率和标准差
1589
- import numpy as np
1590
- random_p = np.empty((simulation,numstocks+2))
1591
- # 设置随机数种子,这里是为了结果可重复
1592
- np.random.seed(RANDOM_SEED)
1593
- # 与其他比率设置不同的随机数种子,意在产生多样性的随机组合
1594
-
1595
- # 循环模拟n次随机的投资组合
1596
- for i in range(simulation):
1597
- # 生成numstocks个随机数,并归一化,得到一组随机的权重数据
1598
- random9 = np.random.random(numstocks)
1599
- random_weight = random9 / np.sum(random9)
1600
-
1601
- # 计算随机投资组合的年化平均收益率
1602
- mean_return=stock_return.mul(random_weight,axis=1).sum(axis=1).mean(axis=0)
1603
- annual_return = (1 + mean_return)**252 - 1
1604
-
1605
- # 计算随机投资组合的年化平均下偏标准差
1606
- sr_temp0=stock_return.copy()
1607
- sr_temp0['Portfolio Ret']=sr_temp0.mul(random_weight,axis=1).sum(axis=1)
1608
- sr_temp1=sr_temp0[sr_temp0['Portfolio Ret'] < mean_return]
1609
- sr_temp2=sr_temp1[tickerlist]
1610
- lpsd_return=sr_temp2.mul(random_weight,axis=1).sum(axis=1).std(axis=0)
1611
- annual_lpsd = lpsd_return*np.sqrt(252)
1612
-
1613
- # 将上面生成的权重,和计算得到的收益率、标准差存入数组random_p中
1614
- # 数组矩阵的前numstocks为随机权重,其后为年均收益率,再后为年均标准差
1615
- random_p[i][:numstocks] = random_weight
1616
- random_p[i][numstocks] = annual_return
1617
- random_p[i][numstocks+1] = annual_lpsd
1618
-
1619
- #显示完成进度
1620
- print_progress_percent(i,simulation,steps=10,leading_blanks=2)
1621
-
1622
- # 将numpy数组转化成DataFrame数据框
1623
- RandomPortfolios = pd.DataFrame(random_p)
1624
- # 设置数据框RandomPortfolios每一列的名称
1625
- RandomPortfolios.columns = [ticker + "_weight" for ticker in tickerlist] \
1626
- + ['Risk premium', 'Risk premium LPSD']
1627
-
1628
- return [pf_info,RandomPortfolios]
1629
-
1630
- if __name__=='__main__':
1631
- Market={'Market':('US','^GSPC','我的组合001')}
1632
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
1633
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
1634
- portfolio=dict(Market,**Stocks1,**Stocks2)
1635
- pf_info=portfolio_expret(portfolio,'2019-12-31')
1636
-
1637
- es_sortino=portfolio_es_sortino(pf_info,simulation=50000)
1638
-
1639
- #==============================================================================
1640
- #==============================================================================
1641
- if __name__=='__main__':
1642
- simulation=1000
1643
- rate_period='1Y'
1644
- rate_type='treasury'
1645
-
1646
- def portfolio_es_alpha(pf_info,simulation=50000,RF=0):
1647
- """
1648
- 功能:基于随机数,生成大量可能的投资组合,计算各个投资组合的年化标准差和阿尔法指数,
1649
- 绘制投资组合的可行集
1650
- """
1651
- print(f" Calculating {simulation} portfolio combinations ...")
1652
-
1653
- [[portfolio,thedate,stock_return0,rf_df,_],_]=pf_info
1654
- pname=portfolio_name(portfolio)
1655
- scope,mktidx,tickerlist,_,ticker_type=decompose_portfolio(portfolio)
1656
-
1657
- #取出观察期
1658
- hstart0=stock_return0.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
1659
- hend0=stock_return0.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
1660
-
1661
- #计算市场指数的收益率
1662
- import pandas as pd
1663
- start1=date_adjust(hstart,adjust=-30)
1664
-
1665
- import os, sys
1666
- class HiddenPrints:
1667
- def __enter__(self):
1668
- self._original_stdout = sys.stdout
1669
- sys.stdout = open(os.devnull, 'w')
1670
-
1671
- def __exit__(self, exc_type, exc_val, exc_tb):
1672
- sys.stdout.close()
1673
- sys.stdout = self._original_stdout
1674
- with HiddenPrints():
1675
- mkt=get_prices(mktidx,start1,hend)
1676
- mkt['Mkt']=mkt['Close'].pct_change()
1677
- mkt.dropna(inplace=True)
1678
- mkt1=pd.DataFrame(mkt['Mkt'])
1679
-
1680
- stock_return0m=pd.merge(stock_return0,mkt1,how='left',left_index=True,right_index=True)
1681
- #必须舍弃空值,否则下面的回归将得不到系数!!!
1682
- stock_return0m.dropna(inplace=True)
1683
-
1684
- #处理期间内无风险利率
1685
- """
1686
- if RF:
1687
- #rf_df=get_rf_daily(hstart,hend,scope,rate_period,rate_type)
1688
- if not (rf_df is None):
1689
- stock_return1=pd.merge(stock_return0m,rf_df,how='inner',left_index=True,right_index=True)
1690
- for t in tickerlist:
1691
- #计算风险溢价
1692
- stock_return1[t]=stock_return1[t]-stock_return1['rf_daily']
1693
-
1694
- stock_return1['Mkt']=stock_return1['Mkt']-stock_return1['rf_daily']
1695
- stock_return=stock_return1[tickerlist+['Mkt']]
1696
- else:
1697
- print(" #Error(portfolio_es_alpha): failed to retrieve risk-free interest rate, please try again")
1698
- return None
1699
- else:
1700
- #不考虑RF
1701
- stock_return=stock_return0m[tickerlist+['Mkt']]
1702
- """
1703
- rf_daily=RF/365
1704
- for t in tickerlist:
1705
- #计算风险溢价
1706
- stock_return0m[t]=stock_return0m[t]-rf_daily
1707
- stock_return0m['Mkt']=stock_return0m['Mkt']-rf_daily
1708
- stock_return=stock_return0m[tickerlist+['Mkt']]
1709
-
1710
- #获得成份股个数
1711
- numstocks=len(tickerlist)
1712
-
1713
- # 设置空的numpy数组,用于存储每次模拟得到的成份股权重、组合的收益率和标准差
1714
- import numpy as np
1715
- random_p = np.empty((simulation,numstocks+2))
1716
- # 设置随机数种子,这里是为了结果可重复
1717
- np.random.seed(RANDOM_SEED)
1718
- # 与其他比率设置不同的随机数种子,意在产生多样性的随机组合
1719
-
1720
- # 循环模拟n次随机的投资组合
1721
- from scipy import stats
1722
- for i in range(simulation):
1723
- # 生成numstocks个随机数,并归一化,得到一组随机的权重数据
1724
- random9 = np.random.random(numstocks)
1725
- random_weight = random9 / np.sum(random9)
1726
-
1727
- # 计算随机投资组合的历史收益率
1728
- stock_return['pRet']=stock_return[tickerlist].mul(random_weight,axis=1).sum(axis=1)
1729
- """
1730
- #使用年化收益率,便于得到具有可比性的纵轴数据刻度
1731
- stock_return['pReta']=(1+stock_return['pRet'])**252 - 1
1732
- stock_return['Mkta']=(1+stock_return['Mkt'])**252 - 1
1733
- """
1734
- #回归求截距项作为阿尔法指数:参与回归的变量不能有空值,否则回归系数将为空值!!!
1735
- (beta,alpha,_,_,_)=stats.linregress(stock_return['Mkt'],stock_return['pRet'])
1736
- """
1737
- mean_return=stock_return[tickerlist].mul(random_weight,axis=1).sum(axis=1).mean(axis=0)
1738
- annual_return = (1 + mean_return)**252 - 1
1739
-
1740
- # 计算随机投资组合的年化平均标准差
1741
- std_return=stock_return[tickerlist].mul(random_weight,axis=1).sum(axis=1).std(axis=0)
1742
- annual_std = std_return*np.sqrt(252)
1743
- """
1744
- # 将上面生成的权重,和计算得到的阿尔法指数、贝塔系数存入数组random_p中
1745
- # 数组矩阵的前numstocks为随机权重,其后为收益指标,再后为风险指标
1746
- random_p[i][:numstocks] = random_weight
1747
- random_p[i][numstocks] = alpha
1748
- random_p[i][numstocks+1] = beta
1749
-
1750
- #显示完成进度
1751
- print_progress_percent(i,simulation,steps=10,leading_blanks=2)
1752
-
1753
- # 将numpy数组转化成DataFrame数据框
1754
- RandomPortfolios = pd.DataFrame(random_p)
1755
- # 设置数据框RandomPortfolios每一列的名称
1756
- RandomPortfolios.columns = [ticker + "_weight" for ticker in tickerlist] \
1757
- + ['alpha', 'beta']
1758
-
1759
- return [pf_info,RandomPortfolios]
1760
-
1761
- if __name__=='__main__':
1762
- Market={'Market':('US','^GSPC','我的组合001')}
1763
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
1764
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
1765
- portfolio=dict(Market,**Stocks1,**Stocks2)
1766
- pf_info=portfolio_expret(portfolio,'2019-12-31')
1767
-
1768
- es_alpha=portfolio_es_alpha(pf_info,simulation=50000)
1769
-
1770
- #==============================================================================
1771
- if __name__=='__main__':
1772
- simulation=1000
1773
- rate_period='1Y'
1774
- rate_type='treasury'
1775
-
1776
- def portfolio_es_treynor(pf_info,simulation=50000,RF=0):
1777
- """
1778
- 功能:基于随机数,生成大量可能的投资组合,计算各个投资组合的风险溢价和贝塔系数,绘制投资组合的可行集
1779
- """
1780
- print(f" Calculating {simulation} portfolio combinations ...")
1781
-
1782
- [[portfolio,_,stock_return0,rf_df,_],_]=pf_info
1783
- pname=portfolio_name(portfolio)
1784
- scope,mktidx,tickerlist,_,ticker_type=decompose_portfolio(portfolio)
1785
-
1786
- #取出观察期
1787
- hstart0=stock_return0.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
1788
- hend0=stock_return0.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
1789
-
1790
- #计算市场指数的收益率
1791
- import pandas as pd
1792
- start1=date_adjust(hstart,adjust=-30)
1793
-
1794
- import os, sys
1795
- class HiddenPrints:
1796
- def __enter__(self):
1797
- self._original_stdout = sys.stdout
1798
- sys.stdout = open(os.devnull, 'w')
1799
-
1800
- def __exit__(self, exc_type, exc_val, exc_tb):
1801
- sys.stdout.close()
1802
- sys.stdout = self._original_stdout
1803
- with HiddenPrints():
1804
- mkt=get_prices(mktidx,start1,hend)
1805
- mkt['Mkt']=mkt['Close'].pct_change()
1806
- mkt.dropna(inplace=True)
1807
- mkt1=pd.DataFrame(mkt['Mkt'])
1808
-
1809
- stock_return0m=pd.merge(stock_return0,mkt1,how='left',left_index=True,right_index=True)
1810
- #处理无风险利率
1811
- """
1812
- if RF:
1813
- #rf_df=get_rf_daily(hstart,hend,scope,rate_period,rate_type)
1814
- if not (rf_df is None):
1815
- stock_return1=pd.merge(stock_return0m,rf_df,how='inner',left_index=True,right_index=True)
1816
- for t in tickerlist:
1817
- #计算风险溢价
1818
- stock_return1[t]=stock_return1[t]-stock_return1['rf_daily']
1819
-
1820
- stock_return1['Mkt']=stock_return1['Mkt']-stock_return1['rf_daily']
1821
- stock_return=stock_return1[tickerlist+['Mkt']]
1822
- else:
1823
- print(" #Error(portfolio_es_treynor): failed to retrieve risk-free interest rate, please try again")
1824
- return None
1825
- else:
1826
- #不考虑RF
1827
- stock_return=stock_return0m[tickerlist+['Mkt']]
1828
- """
1829
- rf_daily=RF/365
1830
- for t in tickerlist:
1831
- #计算风险溢价
1832
- stock_return0m[t]=stock_return0m[t]-rf_daily
1833
- stock_return0m['Mkt']=stock_return0m['Mkt']-rf_daily
1834
- stock_return=stock_return0m[tickerlist+['Mkt']]
1835
-
1836
-
1837
- #获得成份股个数
1838
- numstocks=len(tickerlist)
1839
-
1840
- # 设置空的numpy数组,用于存储每次模拟得到的成份股权重、组合的收益率和标准差
1841
- import numpy as np
1842
- random_p = np.empty((simulation,numstocks+2))
1843
- # 设置随机数种子,这里是为了结果可重复
1844
- np.random.seed(RANDOM_SEED)
1845
- # 与其他比率设置不同的随机数种子,意在产生多样性的随机组合
1846
-
1847
- # 循环模拟simulation次随机的投资组合
1848
- from scipy import stats
1849
- for i in range(simulation):
1850
- # 生成numstocks个随机数放入random9,计算成份股持仓比例放入random_weight,得到一组随机的权重数据
1851
- random9 = np.random.random(numstocks)
1852
- random_weight = random9 / np.sum(random9)
1853
-
1854
- # 计算随机投资组合的历史收益率
1855
- stock_return['pRet']=stock_return[tickerlist].mul(random_weight,axis=1).sum(axis=1)
1856
-
1857
- #回归求贝塔系数作为指数分母
1858
- (beta,alpha,_,_,_)=stats.linregress(stock_return['Mkt'],stock_return['pRet'])
1859
-
1860
- #计算年化风险溢价
1861
- mean_return=stock_return[tickerlist].mul(random_weight,axis=1).sum(axis=1).mean(axis=0)
1862
- annual_return = (1 + mean_return)**252 - 1
1863
- """
1864
- # 计算随机投资组合的年化平均标准差
1865
- std_return=stock_return.mul(random_weight,axis=1).sum(axis=1).std(axis=0)
1866
- annual_std = std_return*np.sqrt(252)
1867
- """
1868
- # 将上面生成的权重,和计算得到的风险溢价、贝塔系数存入数组random_p中
1869
- # 数组矩阵的前numstocks为随机权重,其后为收益指标,再后为风险指标
1870
- random_p[i][:numstocks] = random_weight
1871
- random_p[i][numstocks] = annual_return
1872
- random_p[i][numstocks+1] = beta
1873
-
1874
- #显示完成进度
1875
- print_progress_percent(i,simulation,steps=10,leading_blanks=2)
1876
-
1877
- # 将numpy数组转化成DataFrame数据框
1878
- RandomPortfolios = pd.DataFrame(random_p)
1879
-
1880
- # 设置数据框RandomPortfolios每一列的名称
1881
- RandomPortfolios.columns = [ticker + "_weight" for ticker in tickerlist] \
1882
- + ['Risk premium', 'beta']
1883
- # 新增
1884
- RandomPortfolios['treynor']=RandomPortfolios['Risk premium']/RandomPortfolios['beta']
1885
-
1886
- return [pf_info,RandomPortfolios]
1887
-
1888
- if __name__=='__main__':
1889
- Market={'Market':('US','^GSPC','我的组合001')}
1890
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
1891
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
1892
- portfolio=dict(Market,**Stocks1,**Stocks2)
1893
- pf_info=portfolio_expret(portfolio,'2019-12-31')
1894
-
1895
- es_treynor=portfolio_es_treynor(pf_info,simulation=50000)
1896
-
1897
- #==============================================================================
1898
- def RandomPortfolios_plot(RandomPortfolios,col_x,col_y,colorbartxt,title_ext, \
1899
- ylabeltxt,x_axis_name,pname,simulation,hstart,hend, \
1900
- hiret_point,lorisk_point,convex_hull=True,frontier='efficient', \
1901
- facecolor="papayawhip"):
1902
- """
1903
- 功能:将生成的马科维茨可行集RandomPortfolios绘制成彩色散点图
1904
- """
1905
-
1906
- """
1907
- #特雷诺比率,对照用
1908
- #RandomPortfolios.plot('beta','Risk premium',kind='scatter',color='y',edgecolors='k')
1909
- pf_ratio = np.array(RandomPortfolios['Risk premium'] / RandomPortfolios['beta'])
1910
- pf_returns = np.array(RandomPortfolios['Risk premium'])
1911
- pf_volatilities = np.array(RandomPortfolios['beta'])
1912
-
1913
- plt.figure(figsize=(12.8,6.4))
1914
- plt.scatter(pf_volatilities, pf_returns, c=pf_ratio,cmap='RdYlGn', edgecolors='black',marker='o')
1915
- plt.colorbar(label='特雷诺比率')
1916
-
1917
- plt.title("投资组合: 马科维茨可行集,基于特雷诺比率")
1918
- plt.ylabel("年化风险溢价")
1919
-
1920
- import datetime as dt; stoday=dt.date.today()
1921
- footnote1="贝塔系数-->"
1922
- footnote2="\n\n基于"+pname+"之成份股构造"+str(simulation)+"个投资组合"
1923
- footnote3="\n观察期间:"+hstart+"至"+hend
1924
- footnote4="\n来源: Sina/EM/stooq/fred, "+str(stoday)
1925
- plt.xlabel(footnote1+footnote2+footnote3+footnote4)
1926
- plt.show()
1927
- """
1928
- DEBUG=False
1929
-
1930
- #RandomPortfolios.plot(col_x,col_y,kind='scatter',color='y',edgecolors='k')
1931
-
1932
- pf_ratio = np.array(RandomPortfolios[col_y] / RandomPortfolios[col_x])
1933
- pf_returns = np.array(RandomPortfolios[col_y])
1934
- pf_volatilities = np.array(RandomPortfolios[col_x])
1935
-
1936
- #plt.figure(figsize=(12.8,6.4))
1937
- plt.scatter(pf_volatilities, pf_returns, c=pf_ratio,cmap='RdYlGn', edgecolors='black',marker='o')
1938
- plt.colorbar(label=colorbartxt)
1939
-
1940
- #绘制散点图轮廓线凸包(convex hull)
1941
- if convex_hull:
1942
- print(" Calculating convex hull ...")
1943
- from scipy.spatial import ConvexHull
1944
-
1945
- #构造散点对的列表
1946
- pf_volatilities_list=list(pf_volatilities)
1947
- pf_returns_list=list(pf_returns)
1948
- points=[]
1949
- for x in pf_volatilities_list:
1950
- print_progress_percent2(x,pf_volatilities_list,steps=5,leading_blanks=4)
1951
-
1952
- pos=pf_volatilities_list.index(x)
1953
- y=pf_returns_list[pos]
1954
- points=points+[[x,y]]
1955
-
1956
- #寻找最左侧的坐标:在x最小的同时,y要最大
1957
- points_df = pd.DataFrame(points, columns=['x', 'y'])
1958
- points_df.sort_values(by=['x','y'],ascending=[True,False],inplace=True)
1959
- x1,y1=points_df.head(1).values[0]
1960
-
1961
- #寻找最高点的坐标:在y最大的同时,x要最小
1962
- points_df.sort_values(by=['y','x'],ascending=[False,True],inplace=True)
1963
- x2,y2=points_df.head(1).values[0]
1964
-
1965
- if DEBUG:
1966
- plt.plot([x1,x2],[y1,y2],ls=':',lw=2,alpha=0.5)
1967
-
1968
- #建立最左侧和最高点之间的拟合直线方程y=a+bx
1969
- a=(x1*y2-x2*y1)/(x1-x2); b=(y1-y2)/(x1-x2)
1970
- def y_bar(x_bar):
1971
- return a+b*x_bar
1972
-
1973
- # 计算散点集的外轮廓
1974
- hull = ConvexHull(points)
1975
-
1976
- # 绘制外轮廓线
1977
- firsttime_efficient=True; firsttime_inefficient=True
1978
- for simplex in hull.simplices:
1979
- #p1中是一条线段起点和终点的横坐标
1980
- p1=[points[simplex[0]][0], points[simplex[1]][0]]
1981
- px1=p1[0];px2=p1[1]
1982
- #p2中是一条线段起点和终点的纵坐标
1983
- p2=[points[simplex[0]][1], points[simplex[1]][1]]
1984
- py1=p2[0]; py2=p2[1]
1985
-
1986
- """
1987
- plt.plot([points[simplex[0]][0], points[simplex[1]][0]],
1988
- [points[simplex[0]][1], points[simplex[1]][1]], 'k-.')
1989
- """
1990
- #线段起点:px1,py1;线段终点:px2,py2。但也可能互换起点和终点
1991
- if ((py1>=y_bar(px1) or py1==y1) and (py2>=y_bar(px2) or py2==y2)) or \
1992
- ((py1>=y_bar(px2) or py1==y2) and (py2>=y_bar(px1) or py2==y1)) :
1993
- #有效边界
1994
- if frontier.lower() in ['both','efficient']:
1995
- if firsttime_efficient:
1996
- plt.plot(p1,p2, 'r--',label=text_lang("有效边界","Efficient Frontier"),lw=3,alpha=0.5)
1997
- firsttime_efficient=False
1998
- else:
1999
- plt.plot(p1,p2, 'r--',lw=3,alpha=0.5)
2000
- else:
2001
- pass
2002
- else:
2003
- #其余边沿
2004
- if frontier.lower() in ['both','inefficient']:
2005
- if firsttime_inefficient:
2006
- plt.plot(p1,p2, 'k-.',label=text_lang("无效边界","Inefficient Frontier"),alpha=0.5)
2007
- firsttime_inefficient=False
2008
- else:
2009
- plt.plot(p1,p2, 'k-.',alpha=0.5)
2010
- else:
2011
- pass
2012
- else:
2013
- #无convex hull
2014
- pass
2015
-
2016
- # 空一行
2017
- print('')
2018
-
2019
- lang = check_language()
2020
- if lang == 'Chinese':
2021
- if pname == '': pname='投资组合'
2022
-
2023
- plt.title(pname+": 投资组合优化策略,基于"+title_ext,fontsize=title_txt_size)
2024
- plt.ylabel(ylabeltxt,fontsize=ylabel_txt_size)
2025
-
2026
- import datetime as dt; stoday=dt.date.today()
2027
- footnote1=x_axis_name+" -->\n\n"
2028
- footnote2="基于给定证券构造"+str(simulation)+"个投资组合"
2029
- footnote3="\n观察期间:"+hstart+"至"+hend
2030
- footnote4="\n数据来源: Sina/EM/Stooq/Yahoo, "+str(stoday)
2031
- else:
2032
- if pname == '': pname='Investment Portfolio'
2033
-
2034
- plt.title(pname+": Portfolio Optimization, Based on "+title_ext,fontsize=title_txt_size)
2035
- plt.ylabel(ylabeltxt,fontsize=ylabel_txt_size)
2036
-
2037
- import datetime as dt; stoday=dt.date.today()
2038
- footnote1=x_axis_name+" -->\n\n"
2039
- footnote2="Built "+str(simulation)+" portfolios of given securities"
2040
- footnote3="\nPeriod of sample: "+hstart+" to "+hend
2041
- footnote4="\nData source: Sina/EM/Stooq/Yahoo, "+str(stoday)
2042
-
2043
- plt.xlabel(footnote1+footnote2+footnote3+footnote4,fontsize=xlabel_txt_size)
2044
-
2045
- #解析最大比率点和最低风险点信息,并绘点
2046
- [hiret_x,hiret_y,name_hiret]=hiret_point
2047
- #plt.scatter(hiret_x, hiret_y, color='red',marker='*',s=150,label=name_hiret)
2048
- plt.scatter(hiret_x, hiret_y, color='red',marker='*',s=300,label=name_hiret,alpha=0.5)
2049
-
2050
- [lorisk_x,lorisk_y,name_lorisk]=lorisk_point
2051
- #plt.scatter(lorisk_x, lorisk_y, color='m',marker='8',s=100,label=name_lorisk)
2052
- plt.scatter(lorisk_x, lorisk_y, color='blue',marker='8',s=150,label=name_lorisk,alpha=0.5)
2053
-
2054
- plt.legend(loc='best')
2055
-
2056
- plt.gca().set_facecolor(facecolor)
2057
- plt.show()
2058
-
2059
- return
2060
- #==============================================================================
2061
- #==============================================================================
2062
- if __name__=='__main__':
2063
- pname="MSR组合"
2064
- modify_portfolio_name(pname)
2065
-
2066
- def modify_portfolio_name(pname):
2067
- """
2068
- 功能:将原来的类似于MSR组合修改为更易懂的名称,仅供打印时使用
2069
- """
2070
- pclist=['等权重组合','交易额加权组合','MSR组合','GMVS组合','MSO组合','GML组合', \
2071
- 'MAR组合','GMBA组合', 'MTR组合','GMBT组合']
2072
-
2073
- pclist1=['等权重组合','交易额加权组合', \
2074
- '最佳夏普比率组合(MSR)','夏普比率最小风险组合(GMVS)', \
2075
- '最佳索替诺比率组合(MSO)','索替诺比率最小风险组合(GML)', \
2076
- '最佳阿尔法指标组合(MAR)','阿尔法指标最小风险组合(GMBA)', \
2077
- '最佳特雷诺比率组合(MTR)','特雷诺比率最小风险组合(GMBT)']
2078
-
2079
- if pname not in pclist:
2080
- return pname
2081
-
2082
- pos=pclist.index(pname)
2083
-
2084
- return pclist1[pos]
2085
-
2086
- #==============================================================================
2087
- def cvt_portfolio_name(pname,portfolio_returns):
2088
- """
2089
- 功能:将结果数据表中投资组合策略的名字从英文改为中文
2090
- 将原各处portfolio_optimize函数中的过程统一起来
2091
- """
2092
-
2093
- pelist=['Portfolio','Portfolio_EW','Portfolio_LW','Portfolio_MSR','Portfolio_GMVS', \
2094
- 'Portfolio_MSO','Portfolio_GML','Portfolio_MAR','Portfolio_GMBA', \
2095
- 'Portfolio_MTR','Portfolio_GMBT']
2096
-
2097
- lang=check_language()
2098
- if lang == "Chinese":
2099
- """
2100
- pclist=[pname,'等权重组合','交易额加权组合','MSR组合','GMVS组合','MSO组合','GML组合', \
2101
- 'MAR组合','GMBA组合', 'MTR组合','GMBT组合']
2102
- """
2103
- pclist=[pname,'等权重组合','交易额加权组合', \
2104
- '最佳夏普比率组合(MSR)','夏普比率最小风险组合(GMVS)', \
2105
- '最佳索替诺比率组合(MSO)','索替诺比率最小风险组合(GML)', \
2106
- '最佳阿尔法指标组合(MAR)','阿尔法指标最小风险组合(GMBA)', \
2107
- '最佳特雷诺比率组合(MTR)','特雷诺比率最小风险组合(GMBT)']
2108
-
2109
- else:
2110
- #"""
2111
- pclist=[pname,'Equal-weighted','Amount-weighted','MSR','GMVS','MSO','GML', \
2112
- 'MAR','GMBA', 'MTR','GMBT']
2113
- """
2114
- pclist=[pname,'Equal-weighted','Amount-weighted','Max Sharpe Ratio(MSR)', \
2115
- 'Min Risk in Sharpe Ratio(GMVS)','Max Sortino Ratio(MSO)', \
2116
- 'Min Risk in Sortino Ratio(GML)','Max Alpha(MAR)','Min Risk in Alpha(GMBA)', \
2117
- 'Max Treynor Ratio(MTR)','Min Risk in Treynor Ratio(GMBT)']
2118
- """
2119
-
2120
- pecols=list(portfolio_returns)
2121
- for p in pecols:
2122
- try:
2123
- ppos=pelist.index(p)
2124
- except:
2125
- continue
2126
- else:
2127
- pc=pclist[ppos]
2128
- portfolio_returns.rename(columns={p:pc},inplace=True)
2129
-
2130
- return portfolio_returns
2131
-
2132
- #==============================================================================
2133
-
2134
- def portfolio_optimize_sharpe(es_info,graph=True,convex_hull=False,frontier='efficient',facecolor='papayawhip'):
2135
- """
2136
- 功能:计算投资组合的最高夏普比率组合,并绘图
2137
- MSR: Maximium Sharpe Rate, 最高夏普指数方案
2138
- GMVS: Global Minimum Volatility by Sharpe, 全局最小波动方案
2139
- """
2140
-
2141
- #需要定制:定义名称变量......................................................
2142
- col_ratio='Sharpe' #指数名称
2143
- col_y='Risk premium' #指数分子
2144
- col_x='Risk premium volatility' #指数分母
2145
-
2146
- name_hiret='MSR' #Maximum Sharpe Ratio,指数最高点
2147
- name_lorisk='GMVS' #Global Minimum Volatility by Sharpe,风险最低点
2148
-
2149
- lang = check_language()
2150
- if lang == 'Chinese':
2151
- title_ext="夏普比率" #用于标题区别
2152
- """
2153
- if RF:
2154
- colorbartxt='夏普比率(经无风险利率调整后)' #用于彩色棒标签
2155
- ylabeltxt="年化风险溢价" #用于纵轴名称
2156
- x_axis_name="年化风险溢价标准差" #用于横轴名称
2157
- else:
2158
- colorbartxt='夏普比率(未经无风险利率调整)' #用于彩色棒标签
2159
- ylabeltxt="年化收益率" #用于纵轴名称
2160
- x_axis_name="年化标准差" #用于横轴名称
2161
- """
2162
- colorbartxt='夏普比率' #用于彩色棒标签
2163
- ylabeltxt="年化风险溢价" #用于纵轴名称
2164
- x_axis_name="年化风险溢价标准差" #用于横轴名称
2165
-
2166
- else:
2167
- title_ext="Sharpe Ratio" #用于标题区别
2168
- """
2169
- if RF:
2170
- colorbartxt='Sharpe Ratio(Rf adjusted)' #用于彩色棒标签
2171
- ylabeltxt="Annualized Risk Premium" #用于纵轴名称
2172
- x_axis_name="Annualized Std of Risk Premium" #用于横轴名称
2173
- else:
2174
- colorbartxt='Sharpe Ratio(Rf unadjusted)' #用于彩色棒标签
2175
- ylabeltxt="Annualized Return" #用于纵轴名称
2176
- x_axis_name="Annualized Std" #用于横轴名称
2177
- """
2178
- colorbartxt='Sharpe Ratio' #用于彩色棒标签
2179
- ylabeltxt="Annualized Risk Premium" #用于纵轴名称
2180
- x_axis_name="Annualized Risk Premium STD" #用于横轴名称
2181
-
2182
- #定制部分结束...............................................................
2183
-
2184
- #计算指数,寻找最大指数点和风险最低点,并绘图标注两个点
2185
- hiret_weights,lorisk_weights,portfolio_returns = \
2186
- portfolio_optimize_rar(es_info,col_ratio,col_y,col_x,name_hiret,name_lorisk, \
2187
- colorbartxt,title_ext,ylabeltxt,x_axis_name,graph=graph, \
2188
- convex_hull=convex_hull,frontier=frontier,facecolor=facecolor)
2189
-
2190
- print(text_lang("【注释】","[Notes]"))
2191
- print("★MSR :Maximized Sharpe Ratio"+text_lang(",最大夏普比率点",''))
2192
- print("◍GMVS:Global Minimized Volatility by Sharpe Ratio"+text_lang(",全局最小夏普比率波动点",''))
2193
-
2194
- return name_hiret,hiret_weights,name_lorisk,lorisk_weights,portfolio_returns
2195
-
2196
-
2197
- if __name__=='__main__':
2198
- Market={'Market':('US','^GSPC','我的组合001')}
2199
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
2200
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
2201
- portfolio=dict(Market,**Stocks1,**Stocks2)
2202
-
2203
- pf_info=portfolio_expret(portfolio,'2019-12-31')
2204
- es_sharpe=portfolio_es_sharpe(pf_info,simulation=50000)
2205
-
2206
- MSR_weights,GMV_weights,portfolio_returns=portfolio_optimize_sharpe(es_sharpe)
2207
-
2208
-
2209
- #==============================================================================
2210
-
2211
- def portfolio_optimize_sortino(es_info,graph=True,convex_hull=False,frontier='efficient',facecolor="papayawhip"):
2212
- """
2213
- 功能:计算投资组合的最高索替诺比率组合,并绘图
2214
- MSO: Maximium Sortino ratio, 最高索替诺比率方案
2215
- GML: Global Minimum LPSD volatility, 全局最小LPSD下偏标准差方案
2216
- """
2217
-
2218
- #需要定制:定义名称变量......................................................
2219
- col_ratio='Sortino' #指数名称
2220
- col_y='Risk premium' #指数分子
2221
- col_x='Risk premium LPSD' #指数分母
2222
-
2223
- name_hiret='MSO' #Maximum SOrtino ratio,指数最高点
2224
- name_lorisk='GML' #Global Minimum LPSD,风险最低点
2225
-
2226
- title_ext=text_lang("索替诺比率","Sortino Ratio") #用于标题区别
2227
- colorbartxt=text_lang("索替诺比率","Sortino Ratio") #用于彩色棒标签
2228
- ylabeltxt=text_lang("年化风险溢价","Annualized Risk Premium") #用于纵轴名称
2229
- x_axis_name=text_lang("年化风险溢价之下偏标准差","Annualized Risk Premium LPSD") #用于横轴名称
2230
-
2231
- #定制部分结束...............................................................
2232
-
2233
- #计算指数,寻找最大指数点和风险最低点,并绘图标注两个点
2234
- hiret_weights,lorisk_weights,portfolio_returns = \
2235
- portfolio_optimize_rar(es_info,col_ratio,col_y,col_x,name_hiret,name_lorisk, \
2236
- colorbartxt,title_ext,ylabeltxt,x_axis_name,graph=graph, \
2237
- convex_hull=convex_hull,frontier=frontier,facecolor=facecolor)
2238
-
2239
- print(text_lang("【注释】","[Notes]"))
2240
- print("★MSO:Maximum SOrtino ratio"+text_lang(",最大索替诺比率点",''))
2241
- print("◍GML:Global Minimum LPSD"+text_lang(",全局最小LPSD点",''))
2242
-
2243
- return name_hiret,hiret_weights,name_lorisk,lorisk_weights,portfolio_returns
2244
-
2245
-
2246
- if __name__=='__main__':
2247
- Market={'Market':('US','^GSPC','我的组合001')}
2248
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
2249
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
2250
- portfolio=dict(Market,**Stocks1,**Stocks2)
2251
-
2252
- pf_info=portfolio_expret(portfolio,'2019-12-31')
2253
- es_sortino=portfolio_es_sortino(pf_info,simulation=50000)
2254
-
2255
- MSO_weights,GML_weights,portfolio_returns=portfolio_optimize_sortino(es_Sortino)
2256
-
2257
-
2258
- #==============================================================================
2259
- if __name__=='__main__':
2260
- graph=True; convex_hull=False
2261
-
2262
- def portfolio_optimize_alpha(es_info,graph=True,convex_hull=False,frontier='efficient',facecolor='papayawhip'):
2263
- """
2264
- 功能:计算投资组合的最高詹森阿尔法组合,并绘图
2265
- MAR: Maximium Alpha Ratio, 最高阿尔法指数方案
2266
- GMBA: Global Minimum Beta by Alpha, 全局最小贝塔系数方案
2267
- """
2268
-
2269
- #需要定制:定义名称变量......................................................
2270
- col_ratio='Alpha' #指数名称
2271
- col_y='alpha' #指数分子
2272
- #col_y='Risk premium' #指数分子
2273
- col_x='beta' #指数分母
2274
-
2275
- name_hiret='MAR' #Maximum Alpha Ratio,指数最高点
2276
- name_lorisk='GMBA' #Global Minimum Beta by Alpha,风险最低点
2277
-
2278
- title_ext=text_lang("阿尔法指数","Jensen Alpha") #用于标题区别
2279
- colorbartxt=text_lang("阿尔法指数","Jensen Alpha") #用于彩色棒标签
2280
- ylabeltxt=text_lang("阿尔法指数","Jensen Alpha") #用于纵轴名称
2281
- x_axis_name=text_lang("贝塔系数","Beta") #用于横轴名称
2282
- #定制部分结束...............................................................
2283
-
2284
- #计算指数,寻找最大指数点和风险最低点,并绘图标注两个点
2285
- hiret_weights,lorisk_weights,portfolio_returns = \
2286
- portfolio_optimize_rar(es_info,col_ratio,col_y,col_x,name_hiret,name_lorisk, \
2287
- colorbartxt,title_ext,ylabeltxt,x_axis_name,graph=graph, \
2288
- convex_hull=convex_hull,frontier=frontier,facecolor=facecolor)
2289
-
2290
- print(text_lang("【注释】","[Notes]"))
2291
- print("★MAR :Maximum Alpha Ratio"+text_lang(",最大阿尔法点",''))
2292
- print("◍GMBA:Global Minimum Beta by Alpha"+text_lang(",全局最小阿尔法-贝塔点",''))
2293
-
2294
- return name_hiret,hiret_weights,name_lorisk,lorisk_weights,portfolio_returns
2295
-
2296
-
2297
- if __name__=='__main__':
2298
- Market={'Market':('US','^GSPC','我的组合001')}
2299
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
2300
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
2301
- portfolio=dict(Market,**Stocks1,**Stocks2)
2302
-
2303
- pf_info=portfolio_expret(portfolio,'2019-12-31')
2304
- es_alpha=portfolio_es_alpha(pf_info,simulation=50000)
2305
-
2306
- MAR_weights,GMB_weights,portfolio_returns=portfolio_optimize_alpha(es_alpha)
2307
-
2308
- #==============================================================================
2309
-
2310
- def portfolio_optimize_treynor(es_info,graph=True,convex_hull=False,frontier='efficient',facecolor='papayawhip'):
2311
- """
2312
- 功能:计算投资组合的最高特雷诺比率组合,并绘图
2313
- MTR: Maximium Treynor Ratio, 最高特雷诺指数方案
2314
- GMBT: Global Minimum Beta by Treynor, 全局最小贝塔系数方案
2315
- """
2316
-
2317
- #需要定制:定义名称变量......................................................
2318
- col_ratio='Treynor' #指数名称
2319
- col_y='treynor' #指数:直接做纵轴
2320
- col_x='beta' #做横轴
2321
-
2322
- name_hiret='MTR' #Maximum Treynor Ratio,指数最高点
2323
- name_lorisk='GMBT' #Global Minimum Beta in Treynor,风险最低点
2324
-
2325
- title_ext=text_lang("特雷诺比率","Treynor Ratio") #用于标题区别
2326
- colorbartxt=text_lang("特雷诺比率","Treynor Ratio") #用于彩色棒标签
2327
- #ylabeltxt=text_lang("年化风险溢价","Annualized Risk Premium") #用于纵轴名称
2328
- ylabeltxt=text_lang("特雷诺比率·","Treynor Ratio")
2329
- x_axis_name=text_lang("贝塔系数","Beta") #用于横轴名称
2330
- #定制部分结束...............................................................
2331
-
2332
- #计算指数,寻找最大指数点和风险最低点,并绘图标注两个点
2333
- hiret_weights,lorisk_weights,portfolio_returns = \
2334
- portfolio_optimize_rar(es_info,col_ratio,col_y,col_x,name_hiret,name_lorisk, \
2335
- colorbartxt,title_ext,ylabeltxt,x_axis_name,graph=graph, \
2336
- convex_hull=convex_hull,frontier=frontier,facecolor=facecolor)
2337
-
2338
- print(text_lang("【注释】","[Notes]"))
2339
- print("★MTR :Maximum Treynor Ratio"+text_lang(",最大特雷诺比率点",''))
2340
- print("◍GMBT:Global Minimum Beta in Treynor"+text_lang(",全局最小特雷诺-贝塔点",''))
2341
-
2342
- return name_hiret,hiret_weights,name_lorisk,lorisk_weights,portfolio_returns
2343
-
2344
- #==============================================================================
2345
- if __name__=='__main__':
2346
- col_ratio,col_y,col_x,name_hiret,name_lorisk,colorbartxt,title_ext,ylabeltxt,x_axis_name= \
2347
- ('Sharpe', 'alpha', 'beta', 'MAR', 'GMBA', '阿尔法指数', '阿尔法指数', '阿尔法指数', '贝塔系数')
2348
-
2349
- def portfolio_optimize_rar(es_info,col_ratio,col_y,col_x,name_hiret,name_lorisk, \
2350
- colorbartxt,title_ext,ylabeltxt,x_axis_name,graph=True, \
2351
- convex_hull=False,frontier='efficient',facecolor='papayawhip'):
2352
- """
2353
- 功能:提供rar比率优化的共同处理部分
2354
- 基于RandomPortfolios中的随机投资组合,计算相应的指数,寻找最大指数点和风险最小点,并绘图标注两个点
2355
- 输入:以特雷诺比率为例
2356
- col_ratio='Treynor' #指数名称
2357
- col_y='Risk premium' #指数分子
2358
- col_x='beta' #指数分母
2359
- name_hiret='MTR' #Maximum Treynor Ratio,指数最高点
2360
- name_lorisk='GMBT' #Global Minimum Beta in Treynor,风险最低点
2361
-
2362
- colorbartxt='特雷诺比率' #用于彩色棒标签
2363
- title_ext="特雷诺比率" #用于标题区别
2364
- ylabeltxt="年化风险溢价" #用于纵轴名称
2365
- x_axis_name="贝塔系数" #用于横轴名称
2366
-
2367
- """
2368
- #解析传入的数据
2369
- [[[portfolio,thedate,stock_return,_,_],[StockReturns,_,_,_]],RandomPortfolios]=es_info
2370
- _,_,tickerlist,_,ticker_type=decompose_portfolio(portfolio)
2371
- numstocks=len(tickerlist)
2372
- pname=portfolio_name(portfolio)
2373
-
2374
- #取出观察期
2375
- hstart0=StockReturns.index[0]; hstart=str(hstart0.strftime("%Y-%m-%d"))
2376
- hend0=StockReturns.index[-1]; hend=str(hend0.strftime("%Y-%m-%d"))
2377
-
2378
- #识别并计算指数..........................................................
2379
- if col_ratio.title() in ['Treynor','Alpha']:
2380
- RandomPortfolios[col_ratio] = RandomPortfolios[col_y]
2381
- elif col_ratio.title() in ['Sharpe','Sortino']:
2382
- RandomPortfolios[col_ratio] = RandomPortfolios[col_y] / RandomPortfolios[col_x]
2383
- else:
2384
- print(" #Error(portfolio_optimize_rar): invalid rar",col_ratio)
2385
- print(" Supported rar(risk-adjusted-return): Treynor, Sharpe, Sortino, Alpha")
2386
- return None
2387
-
2388
- # 找到指数最大数据对应的索引值
2389
- max_index = RandomPortfolios[col_ratio].idxmax()
2390
- # 找出指数最大的点坐标并绘制该点
2391
- hiret_x = RandomPortfolios.loc[max_index,col_x]
2392
- hiret_y = RandomPortfolios.loc[max_index,col_y]
2393
-
2394
- # 提取最高指数组合对应的权重,并转化为numpy数组
2395
- import numpy as np
2396
- hiret_weights = np.array(RandomPortfolios.iloc[max_index, 0:numstocks])
2397
- # 计算最高指数组合的收益率
2398
- StockReturns['Portfolio_'+name_hiret] = stock_return[tickerlist].mul(hiret_weights, axis=1).sum(axis=1)
2399
-
2400
- # 找到风险最小组合的索引值
2401
- min_index = RandomPortfolios[col_x].idxmin()
2402
- # 提取最小风险组合对应的权重, 并转换成Numpy数组
2403
- # 找出风险最小的点坐标并绘制该点
2404
- lorisk_x = RandomPortfolios.loc[min_index,col_x]
2405
- lorisk_y = RandomPortfolios.loc[min_index,col_y]
2406
-
2407
- # 提取最小风险组合对应的权重,并转化为numpy数组
2408
- lorisk_weights = np.array(RandomPortfolios.iloc[min_index, 0:numstocks])
2409
- # 计算风险最小组合的收益率
2410
- StockReturns['Portfolio_'+name_lorisk] = stock_return[tickerlist].mul(lorisk_weights, axis=1).sum(axis=1)
2411
-
2412
- #绘制散点图
2413
- simulation=len(RandomPortfolios)
2414
-
2415
- lang = check_language()
2416
- if lang == 'Chinese':
2417
- point_txt="点"
2418
- else:
2419
- point_txt=" Point"
2420
-
2421
- hiret_point=[hiret_x,hiret_y,name_hiret+point_txt]
2422
- lorisk_point=[lorisk_x,lorisk_y,name_lorisk+point_txt]
2423
- if graph:
2424
- RandomPortfolios_plot(RandomPortfolios,col_x,col_y,colorbartxt,title_ext, \
2425
- ylabeltxt,x_axis_name,pname,simulation,hstart,hend, \
2426
- hiret_point,lorisk_point,convex_hull=convex_hull, \
2427
- frontier=frontier,facecolor=facecolor)
2428
-
2429
- #返回数据,供进一步分析
2430
- portfolio_returns=StockReturns.copy()
2431
-
2432
- #将投资组合策略改为中文
2433
- portfolio_returns=cvt_portfolio_name(pname,portfolio_returns)
2434
-
2435
- return hiret_weights,lorisk_weights,portfolio_returns
2436
-
2437
-
2438
- if __name__=='__main__':
2439
- Market={'Market':('US','^GSPC','我的组合001')}
2440
- Stocks1={'AAPL':.1,'MSFT':.13,'XOM':.09,'JNJ':.09,'JPM':.09}
2441
- Stocks2={'AMZN':.15,'GE':.08,'FB':.13,'T':.14}
2442
- portfolio=dict(Market,**Stocks1,**Stocks2)
2443
-
2444
- pf_info=portfolio_expret(portfolio,'2019-12-31')
2445
- es_treynor=portfolio_es_treynor(pf_info,simulation=50000)
2446
-
2447
- MTR_weights,GMB2_weights,portfolio_returns=portfolio_optimize_treynor(es_treynor)
2448
-
2449
- #==============================================================================
2450
- #==============================================================================
2451
- if __name__=='__main__':
2452
- ratio='sharpe'
2453
- ratio='alpha'
2454
- ratio='treynor'
2455
- simulation=1000
2456
- simulation=50000
2457
-
2458
- pf_info0=pf_info
2459
- ratio='treynor'
2460
-
2461
- simulation=10000
2462
- RF=0.046
2463
- graph=True
2464
- hirar_return=True; lorisk=True
2465
- convex_hull=True; frontier='efficient'; facecolor='papayawhip'
2466
-
2467
-
2468
-
2469
- def portfolio_optimize(pf_info0,ratio='sharpe',simulation=10000,RF=0, \
2470
- graph=True,hirar_return=False,lorisk=True, \
2471
- convex_hull=True,frontier='efficient',facecolor='papayawhip'):
2472
- """
2473
- 功能:集成式投资组合优化策略
2474
- 注意:实验发现RF较小时对于结果的影响极其微小难以观察,默认设为不使用无风险利率调整收益
2475
- 但RF较大时对于结果的影响明显变大,已经不能忽略!
2476
- 若可行集形状不佳,优先尝试pastyears=3,再尝试增加simulation次数。
2477
- simulation数值过大时将导致速度太慢。
2478
- """
2479
- # 防止原始数据被修改
2480
- import copy
2481
- pf_info=copy.deepcopy(pf_info0)
2482
-
2483
- ratio_list=['treynor','sharpe','sortino','alpha']
2484
- ratio=ratio.lower()
2485
- if not (ratio in ratio_list):
2486
- print(" #Error(portfolio_optimize_strategy): invalid strategy ratio",ratio)
2487
- print(" Supported strategy ratios",ratio_list)
2488
- return
2489
-
2490
- print(" Optimizing portfolio configuration by",ratio,"ratio ...")
2491
-
2492
- [[portfolio,_,_,_,_],_]=pf_info
2493
- pname=portfolio_name(portfolio)
2494
- _,_,_,_,ticker_type=decompose_portfolio(portfolio)
2495
-
2496
- #观察马科维茨可行集:风险溢价-标准差,用于夏普比率优化
2497
- func_es="portfolio_es_"+ratio
2498
- es_info=eval(func_es)(pf_info=pf_info,simulation=simulation,RF=RF)
2499
-
2500
-
2501
- #寻找比率最优点:最大比率策略和最小风险策略
2502
- func_optimize="portfolio_optimize_"+ratio
2503
- """
2504
- name_hiret,hiret_weights,name_lorisk,lorisk_weights,portfolio_returns= \
2505
- eval(func_optimize)(es_info=es_info,RF=RF,graph=graph)
2506
- """
2507
- name_hiret,hiret_weights,name_lorisk,lorisk_weights,portfolio_returns= \
2508
- eval(func_optimize)(es_info=es_info,graph=graph,convex_hull=convex_hull, \
2509
- frontier=frontier,facecolor=facecolor)
2510
-
2511
-
2512
- lang = check_language()
2513
- if lang == 'Chinese':
2514
- zhuhe_txt='组合'
2515
- mingcheng_txt='投资组合名称/策略'
2516
- titletxt="投资组合策略:业绩比较"
2517
- ylabeltxt="持有期收益率"
2518
- else:
2519
- zhuhe_txt=''
2520
- mingcheng_txt='Strategy'
2521
- titletxt="Investment Portfolio Strategy: Performance Comparison"
2522
- ylabeltxt="Holding Period Return"
2523
-
2524
- #打印投资组合构造和业绩表现
2525
- hi_name=modify_portfolio_name(name_hiret+zhuhe_txt)
2526
- lo_name=modify_portfolio_name(name_lorisk+zhuhe_txt)
2527
- portfolio_expectation(hi_name,pf_info,hiret_weights,ticker_type)
2528
-
2529
- if hirar_return:
2530
- scope,mktidx,tickerlist,_,ticker_type=decompose_portfolio(portfolio)
2531
- hwdf=pd.DataFrame(hiret_weights)
2532
- hwdft=hwdf.T
2533
- hwdft.columns=tickerlist
2534
- hwdftt=hwdft.T
2535
- hwdftt.sort_values(by=[0],ascending=False,inplace=True)
2536
- hwdftt['ticker']=hwdftt.index
2537
- hwdftt['weight']=hwdftt[0].apply(lambda x:round(x,4))
2538
- stocks_new=hwdftt.set_index(['ticker'])['weight'].to_dict()
2539
- pname=portfolio_name(portfolio)
2540
-
2541
- Market={'Market':(scope,mktidx,pname)}
2542
- portfolio_new=dict(Market,**stocks_new)
2543
-
2544
- if lorisk:
2545
- portfolio_expectation(lo_name,pf_info,lorisk_weights,ticker_type)
2546
-
2547
- #现有投资组合的排名
2548
- ranks=portfolio_ranks(portfolio_returns,pname)
2549
-
2550
- #绘制投资组合策略业绩比较曲线:最多显示4条曲线,否则黑白打印时无法区分
2551
- top4=list(ranks[mingcheng_txt])[:4]
2552
- for p in top4:
2553
- if p in [pname,hi_name,lo_name]:
2554
- continue
2555
- else:
2556
- break
2557
- name_list=[pname,hi_name,lo_name,p]
2558
-
2559
- if graph:
2560
- portfolio_expret_plot(portfolio_returns,name_list,titletxt=titletxt,ylabeltxt=ylabeltxt)
2561
-
2562
- if hirar_return:
2563
- return portfolio_new
2564
- else:
2565
- return
2566
-
2567
- #==============================================================================
2568
- #==============================================================================
2569
- #==============================================================================
2570
- #==============================================================================
2571
- #==============================================================================
2572
- #==============================================================================
2573
-
2574
- def translate_tickerlist(tickerlist):
2575
- newlist=[]
2576
- for t in tickerlist:
2577
- name=ticker_name(t,'bond')
2578
- newlist=newlist+[name]
2579
-
2580
- return newlist
2581
- #==============================================================================
2582
- # 绘制马科维茨有效边界
2583
- #==============================================================================
2584
- def ret_monthly(ticker,prices):
2585
- """
2586
- 功能:
2587
- """
2588
- price=prices['Adj Close'][ticker]
2589
-
2590
- import numpy as np
2591
- div=price.pct_change()+1
2592
- logret=np.log(div)
2593
- import pandas as pd
2594
- lrdf=pd.DataFrame(logret)
2595
- lrdf['ymd']=lrdf.index.astype("str")
2596
- lrdf['ym']=lrdf['ymd'].apply(lambda x:x[0:7])
2597
- lrdf.dropna(inplace=True)
2598
-
2599
- mret=lrdf.groupby(by=['ym'])[ticker].sum()
2600
-
2601
- return mret
2602
-
2603
- if __name__=='__main__':
2604
- ticker='MSFT'
2605
- fromdate,todate='2019-1-1','2020-8-1'
2606
-
2607
- #==============================================================================
2608
- def objFunction(W,R,target_ret):
2609
-
2610
- import numpy as np
2611
- stock_mean=np.mean(R,axis=0)
2612
- port_mean=np.dot(W,stock_mean) # portfolio mean
2613
-
2614
- cov=np.cov(R.T) # var-cov matrix
2615
- port_var=np.dot(np.dot(W,cov),W.T) # portfolio variance
2616
- penalty = 2000*abs(port_mean-target_ret)# penalty 4 deviation
2617
-
2618
- objfunc=np.sqrt(port_var) + penalty # objective function
2619
-
2620
- return objfunc
2621
-
2622
- #==============================================================================
2623
- def portfolio_ef_0(stocks,fromdate,todate):
2624
- """
2625
- 功能:绘制马科维茨有效前沿,不区分上半沿和下半沿
2626
- 问题:很可能出现上下边界折叠的情况,难以解释,弃用!!!
2627
- """
2628
- #Code for getting stock prices
2629
- prices=get_prices(stocks,fromdate,todate)
2630
-
2631
- #Code for generating a return matrix R
2632
- R0=ret_monthly(stocks[0],prices) # starting from 1st stock
2633
- n_stock=len(stocks) # number of stocks
2634
- import pandas as pd
2635
- import numpy as np
2636
- for i in range(1,n_stock): # merge with other stocks
2637
- x=ret_monthly(stocks[i],prices)
2638
- R0=pd.merge(R0,x,left_index=True,right_index=True)
2639
- R=np.array(R0)
2640
-
2641
- #Code for estimating optimal portfolios for a given return
2642
- out_mean,out_std,out_weight=[],[],[]
2643
- import numpy as np
2644
- stockMean=np.mean(R,axis=0)
2645
-
2646
- from scipy.optimize import minimize
2647
- for r in np.linspace(np.min(stockMean),np.max(stockMean),num=100):
2648
- W = np.ones([n_stock])/n_stock # starting from equal weights
2649
- b_ = [(0,1) for i in range(n_stock)] # bounds, here no short
2650
- c_ = ({'type':'eq', 'fun': lambda W: sum(W)-1. }) #constraint
2651
- result=minimize(objFunction,W,(R,r),method='SLSQP'
2652
- ,constraints=c_, bounds=b_)
2653
- if not result.success: # handle error raise
2654
- BaseException(result.message)
2655
-
2656
- try:
2657
- out_mean.append(round(r,4)) # 4 decimal places
2658
- except:
2659
- out_mean._append(round(r,4))
2660
-
2661
- std_=round(np.std(np.sum(R*result.x,axis=1)),6)
2662
- try:
2663
- out_std.append(std_)
2664
- out_weight.append(result.x)
2665
- except:
2666
- out_std._append(std_)
2667
- out_weight._append(result.x)
2668
-
2669
- #Code for plotting the efficient frontier
2670
-
2671
- plt.title('Efficient Frontier of Portfolio')
2672
- plt.xlabel('Standard Deviation of portfolio (Risk))')
2673
- plt.ylabel('Return of portfolio')
2674
-
2675
- out_std_min=min(out_std)
2676
- pos=out_std.index(out_std_min)
2677
- out_mean_min=out_mean[pos]
2678
- x_left=out_std_min+0.25
2679
- y_left=out_mean_min+0.5
2680
-
2681
- #plt.figtext(x_left,y_left,str(n_stock)+' stock are used: ')
2682
- plt.figtext(x_left,y_left,"投资组合由"+str(n_stock)+'种证券构成: ')
2683
- plt.figtext(x_left,y_left-0.05,' '+str(stocks))
2684
- plt.figtext(x_left,y_left-0.1,'观察期间:'+str(fromdate)+'至'+str(todate))
2685
- plt.plot(out_std,out_mean,color='r',ls=':',lw=4)
2686
-
2687
- plt.gca().set_facecolor('papayawhip')
2688
- plt.show()
2689
-
2690
- return
2691
-
2692
- if __name__=='__main__':
2693
- stocks=['IBM','WMT','AAPL','C','MSFT']
2694
- fromdate,todate='2019-1-1','2020-8-1'
2695
- portfolio_ef_0(stocks,fromdate,todate)
2696
-
2697
- #==============================================================================
2698
- def portfolio_ef(stocks,fromdate,todate):
2699
- """
2700
- 功能:多只股票的马科维茨有效边界,区分上半沿和下半沿,标记风险极小点
2701
- 问题:很可能出现上下边界折叠的情况,难以解释,弃用!!!
2702
- """
2703
- print("\n Searching for portfolio information, please wait...")
2704
- #Code for getting stock prices
2705
- prices=get_prices(stocks,fromdate,todate)
2706
-
2707
- #Code for generating a return matrix R
2708
- R0=ret_monthly(stocks[0],prices) # starting from 1st stock
2709
- n_stock=len(stocks) # number of stocks
2710
-
2711
- import pandas as pd
2712
- import numpy as np
2713
- for i in range(1,n_stock): # merge with other stocks
2714
- x=ret_monthly(stocks[i],prices)
2715
- R0=pd.merge(R0,x,left_index=True,right_index=True)
2716
- R=np.array(R0)
2717
-
2718
- #Code for estimating optimal portfolios for a given return
2719
- out_mean,out_std,out_weight=[],[],[]
2720
- stockMean=np.mean(R,axis=0)
2721
-
2722
- from scipy.optimize import minimize
2723
- for r in np.linspace(np.min(stockMean),np.max(stockMean),num=100):
2724
- W = np.ones([n_stock])/n_stock # starting from equal weights
2725
- b_ = [(0,1) for i in range(n_stock)] # bounds, here no short
2726
- c_ = ({'type':'eq', 'fun': lambda W: sum(W)-1. }) #constraint
2727
- result=minimize(objFunction,W,(R,r),method='SLSQP'
2728
- ,constraints=c_, bounds=b_)
2729
- if not result.success: # handle error raise
2730
- BaseException(result.message)
2731
-
2732
- try:
2733
- out_mean.append(round(r,4)) # 4 decimal places
2734
- std_=round(np.std(np.sum(R*result.x,axis=1)),6)
2735
- out_std.append(std_)
2736
- out_weight.append(result.x)
2737
- except:
2738
- out_mean._append(round(r,4)) # 4 decimal places
2739
- std_=round(np.std(np.sum(R*result.x,axis=1)),6)
2740
- out_std._append(std_)
2741
- out_weight._append(result.x)
2742
-
2743
- #Code for positioning
2744
- out_std_min=min(out_std)
2745
- pos=out_std.index(out_std_min)
2746
- out_mean_min=out_mean[pos]
2747
- x_left=out_std_min+0.25
2748
- y_left=out_mean_min+0.5
2749
-
2750
- import pandas as pd
2751
- out_df=pd.DataFrame(out_mean,out_std,columns=['mean'])
2752
- out_df_ef=out_df[out_df['mean']>=out_mean_min]
2753
- out_df_ief=out_df[out_df['mean']<out_mean_min]
2754
-
2755
- #Code for plotting the efficient frontier
2756
-
2757
- plt.title('投资组合:马科维茨有效边界(理想图)')
2758
-
2759
- import datetime as dt; stoday=dt.date.today()
2760
- plt.xlabel('收益率标准差-->'+"\n数据来源:新浪/EM/stooq, "+str(stoday))
2761
- plt.ylabel('收益率')
2762
-
2763
- plt.figtext(x_left,y_left,"投资组合由"+str(n_stock)+'种证券构成: ')
2764
- plt.figtext(x_left,y_left-0.05,' '+str(stocks))
2765
- plt.figtext(x_left,y_left-0.1,'观察期间:'+str(fromdate)+'至'+str(todate))
2766
- plt.plot(out_df_ef.index,out_df_ef['mean'],color='r',ls='--',lw=2,label='有效边界')
2767
- plt.plot(out_df_ief.index,out_df_ief['mean'],color='k',ls=':',lw=2,label='无效边界')
2768
- plt.plot(out_std_min,out_mean_min,'g*-',markersize=16,label='风险最低点')
2769
-
2770
- plt.legend(loc='best')
2771
- plt.gca().set_facecolor('papayawhip')
2772
- plt.show()
2773
-
2774
- return
2775
-
2776
- if __name__=='__main__':
2777
- stocks=['IBM','WMT','AAPL','C','MSFT']
2778
- fromdate,todate='2019-1-1','2020-8-1'
2779
- df=portfolio_ef(stocks,fromdate,todate)
2780
-
2781
- #==============================================================================
2782
- if __name__=='__main__':
2783
- tickers=['^GSPC','000001.SS','^HSI','^N225','^BSESN']
2784
- start='2023-1-1'
2785
- end='2023-3-22'
2786
- info_type='Volume'
2787
- df=security_correlation(tickers,start,end,info_type='Close')
2788
-
2789
-
2790
- def cm2inch(x,y):
2791
- return x/2.54,y/2.54
2792
-
2793
- def security_correlation(tickers,start='L5Y',end='today',info_type='Close', \
2794
- facecolor='white'):
2795
- """
2796
- ===========================================================================
2797
- 功能:股票/指数收盘价之间的相关性
2798
- 参数:
2799
- tickers:指标列表,至少两个
2800
- start:起始日期,格式YYYY-MM-DD,支持简易格式
2801
- end:截止日期
2802
- info_type:指标的数值类型,默认'Close', 还可为Open/High/Low/Volume
2803
- facecolor:背景颜色,默认'papayawhip'
2804
-
2805
- 返回:相关系数df
2806
- """
2807
-
2808
- start,end=start_end_preprocess(start,end)
2809
-
2810
- info_types=['Close','Open','High','Low','Volume']
2811
- info_types_cn=['收盘价','开盘价','最高价','最低价','成交量']
2812
- if not(info_type in info_types):
2813
- print(" #Error(security_correlation): invalid information type",info_type)
2814
- print(" Supported information type:",info_types)
2815
- return None
2816
- pos=info_types.index(info_type)
2817
- info_type_cn=info_types_cn[pos]
2818
-
2819
- #屏蔽函数内print信息输出的类
2820
- import os, sys
2821
- class HiddenPrints:
2822
- def __enter__(self):
2823
- self._original_stdout = sys.stdout
2824
- sys.stdout = open(os.devnull, 'w')
2825
-
2826
- def __exit__(self, exc_type, exc_val, exc_tb):
2827
- sys.stdout.close()
2828
- sys.stdout = self._original_stdout
2829
-
2830
- print(" Searching for security prices, please wait ...\n")
2831
- with HiddenPrints():
2832
- prices=get_prices_simple(tickers,start,end)
2833
- df=prices[info_type]
2834
- df.dropna(axis=0,inplace=True)
2835
-
2836
- # here put the import lib
2837
- import seaborn as sns
2838
- sns.set(font='SimHei') # 解决Seaborn中文显示问题
2839
- #sns.set_style('whitegrid',{'font.sans-serif':['SimHei','Arial']})
2840
- #sns.set_style('whitegrid',{'font.sans-serif':['FangSong']})
2841
-
2842
- import numpy as np
2843
- from scipy.stats import pearsonr
2844
-
2845
- collist=list(df)
2846
- for col in collist:
2847
- df.rename(columns={col:ticker_name(col,'bond')},inplace=True)
2848
- df_coor = df.corr()
2849
-
2850
-
2851
- #fig = plt.figure(figsize=(cm2inch(16,12)))
2852
- #fig = plt.figure(figsize=(cm2inch(12,8)))
2853
- #fig = plt.figure(figsize=(12.8,7.2))
2854
- fig = plt.figure(figsize=(12.8,6.4))
2855
- ax1 = plt.gca()
2856
-
2857
- #构造mask,去除重复数据显示
2858
- mask = np.zeros_like(df_coor)
2859
- mask[np.triu_indices_from(mask)] = True
2860
- mask2 = mask
2861
- mask = (np.flipud(mask)-1)*(-1)
2862
- mask = np.rot90(mask,k = -1)
2863
-
2864
- im1 = sns.heatmap(df_coor,annot=True,cmap="YlGnBu"
2865
- , mask=mask#构造mask,去除重复数据显示
2866
- , vmax=1,vmin=-1
2867
- , cbar=False
2868
- , fmt='.2f',ax = ax1,annot_kws={"size": 16})
2869
-
2870
- ax1.tick_params(axis = 'both', length=0)
2871
-
2872
- #计算相关性显著性并显示
2873
- rlist = []
2874
- plist = []
2875
- for i in df.columns.values:
2876
- for j in df.columns.values:
2877
- r,p = pearsonr(df[i],df[j])
2878
- try:
2879
- rlist.append(r)
2880
- plist.append(p)
2881
- except:
2882
- rlist._append(r)
2883
- plist._append(p)
2884
-
2885
- rarr = np.asarray(rlist).reshape(len(df.columns.values),len(df.columns.values))
2886
- parr = np.asarray(plist).reshape(len(df.columns.values),len(df.columns.values))
2887
- xlist = ax1.get_xticks()
2888
- ylist = ax1.get_yticks()
2889
-
2890
- widthx = 0
2891
- widthy = -0.15
2892
-
2893
- # 星号的大小
2894
- font_dict={'size':10}
2895
-
2896
- for m in ax1.get_xticks():
2897
- for n in ax1.get_yticks():
2898
- pv = (parr[int(m),int(n)])
2899
- rv = (rarr[int(m),int(n)])
2900
- if mask2[int(m),int(n)]<1.:
2901
- if abs(rv) > 0.5:
2902
- if pv< 0.05 and pv>= 0.01:
2903
- ax1.text(n+widthx,m+widthy,'*',ha = 'center',color = 'white',fontdict=font_dict)
2904
- if pv< 0.01 and pv>= 0.001:
2905
- ax1.text(n+widthx,m+widthy,'**',ha = 'center',color = 'white',fontdict=font_dict)
2906
- if pv< 0.001:
2907
- #print([int(m),int(n)])
2908
- ax1.text(n+widthx,m+widthy,'***',ha = 'center',color = 'white',fontdict=font_dict)
2909
- else:
2910
- if pv< 0.05 and pv>= 0.01:
2911
- ax1.text(n+widthx,m+widthy,'*',ha = 'center',color = 'k',fontdict=font_dict)
2912
- elif pv< 0.01 and pv>= 0.001:
2913
- ax1.text(n+widthx,m+widthy,'**',ha = 'center',color = 'k',fontdict=font_dict)
2914
- elif pv< 0.001:
2915
- ax1.text(n+widthx,m+widthy,'***',ha = 'center',color = 'k',fontdict=font_dict)
2916
-
2917
- #plt.title(text_lang("时间序列相关性分析:","Time Series Correlation Analysis: ")+text_lang(info_type_cn,info_type),fontsize=16)
2918
- plt.title(text_lang("时间序列相关性分析","Time Series Correlation Analysis"),fontsize=16)
2919
- plt.tick_params(labelsize=10)
2920
-
2921
- footnote1=text_lang("\n显著性数值:***非常显著(<0.001),**很显著(<0.01),*显著(<0.05),其余为不显著", \
2922
- "\nSig level: *** Extremely sig(p<0.001), ** Very sig(<0.01), * Sig(<0.05), others unsig")
2923
- footnote2=text_lang("\n系数绝对值:>=0.8极强相关,0.6-0.8强相关,0.4-0.6相关,0.2-0.4弱相关,否则为极弱(不)相关", \
2924
- "\nCoef. abs: >=0.8 Extreme corr, 0.6-0.8 Strong corr, 0.4-0.6 Corr, <0.4 Weak or uncorr")
2925
-
2926
- footnote3=text_lang("\n观察期间: ","\nPeriod of sample: ")+start+text_lang('至',' to ')+end
2927
- import datetime as dt; stoday=dt.date.today()
2928
- footnote4=text_lang(";数据来源:Sina/EM/Stooq/Yahoo,",". Data source: Sina/EM/Stooq/Yahoo, ")+str(stoday)
2929
-
2930
- fontxlabel={'size':8}
2931
- plt.xlabel(footnote1+footnote2+footnote3+footnote4,fontxlabel)
2932
- #plt.xticks(rotation=45)
2933
-
2934
- plt.gca().set_facecolor(facecolor)
2935
-
2936
- #plt.xticks(fontsize=10, rotation=90)
2937
- plt.xticks(fontsize=10, rotation=30)
2938
- plt.yticks(fontsize=10, rotation=0)
2939
-
2940
- plt.show()
2941
-
2942
- return df_coor
2943
-
2944
- #==============================================================================
2945
- if __name__ =="__main__":
2946
- portfolio={'Market':('US','^GSPC','Test 1'),'EDU':0.4,'TAL':0.3,'TEDU':0.2}
2947
-
2948
- def portfolio_describe(portfolio):
2949
- describe_portfolio(portfolio)
2950
- return
2951
-
2952
- def describe_portfolio(portfolio):
2953
- """
2954
- 功能:描述投资组合的信息
2955
- 输入:投资组合
2956
- 输出:市场,市场指数,股票代码列表和份额列表
2957
- """
2958
-
2959
- scope,mktidx,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
2960
- pname=portfolio_name(portfolio)
2961
-
2962
- print(text_lang("*** 投资组合名称:","*** Portfolio name:"),pname)
2963
- print(text_lang("所在市场:","Market:"),ectranslate(scope))
2964
- print(text_lang("市场指数:","Market index:"),ticker_name(mktidx,'bond')+'('+mktidx+')')
2965
- print(text_lang("\n*** 成分股及其份额:","\n*** Members and shares:"))
2966
-
2967
- num=len(tickerlist)
2968
- #seqlist=[]
2969
- tickerlist1=[]
2970
- sharelist1=[]
2971
- totalshares=0
2972
- for t in range(num):
2973
- #seqlist=seqlist+[t+1]
2974
- tickerlist1=tickerlist1+[ticker_name(tickerlist[t],'bond')+'('+tickerlist[t]+')']
2975
- sharelist1=sharelist1+[str(round(sharelist[t]*100,2))+'%']
2976
-
2977
- totalshares=totalshares+sharelist[t]
2978
-
2979
- import pandas as pd
2980
- #df=pd.DataFrame({'序号':seqlist,'成分股':tickerlist1,'份额':sharelist1})
2981
- df=pd.DataFrame({text_lang('成分股','Members'):tickerlist1,text_lang('份额','Shares'):sharelist1})
2982
- df.index=df.index+1
2983
-
2984
- alignlist=['center','left','right']
2985
- print(df.to_markdown(index=True,tablefmt='plain',colalign=alignlist))
2986
-
2987
- print("*** "+text_lang("成分股份额总和:","Total shares: ")+str(totalshares*100)+'%')
2988
- if totalshares != 1:
2989
- print(" #Warning: total shares is expecting to be 100%")
2990
-
2991
- return
2992
-
2993
- #==============================================================================
2994
- def portfolio_drop(portfolio,last=0,droplist=[],new_name=''):
2995
- """
2996
- 功能:删除最后几个成分股
2997
- """
2998
- scope,mktidx,tickerlist,sharelist,ticker_type=decompose_portfolio(portfolio)
2999
- pname=portfolio_name(portfolio)
3000
-
3001
- if not (last ==0):
3002
- for i in range(last):
3003
- #print(i)
3004
- tmp=tickerlist.pop()
3005
- tmp=sharelist.pop()
3006
-
3007
- if not (droplist==[]):
3008
- for d in droplist:
3009
- pos=tickerlist.index(d)
3010
- tmp=tickerlist.pop(pos)
3011
- tmp=sharelist.pop(pos)
3012
-
3013
- stocks_new=dict(zip(tickerlist,sharelist))
3014
-
3015
- if new_name=='':
3016
- new_name=pname
3017
-
3018
- Market={'Market':(scope,mktidx,new_name)}
3019
- portfolio_new=dict(Market,**stocks_new)
3020
-
3021
- return portfolio_new
3022
-
3023
- #==============================================================================
3024
- def portfolio_define(name='My Portfolio', \
3025
- market='CN', \
3026
- market_index='000001.SS', \
3027
- members={}, \
3028
- check=False):
3029
- """
3030
- 功能:定义一个投资组合
3031
- 参数:
3032
- name: 投资组合的名字
3033
- economy_entity: 投资组合的成分股所在的经济体
3034
- market_index: 经济体的代表性市场指数
3035
- members: 数据字典,投资组合的各个成分股代码及其所占的股数份额
3036
-
3037
- 返回值:投资组合的字典型描述
3038
- """
3039
-
3040
- # 检查市场名称
3041
- market=market.upper()
3042
- if len(market) != 2:
3043
- print(" #Warning(portfolio_define): need a country code of 2 letters")
3044
- return None
3045
-
3046
- #屏蔽函数内print信息输出的类
3047
- import os, sys
3048
- class HiddenPrints:
3049
- def __enter__(self):
3050
- self._original_stdout = sys.stdout
3051
- sys.stdout = open(os.devnull, 'w')
3052
-
3053
- def __exit__(self, exc_type, exc_val, exc_tb):
3054
- sys.stdout.close()
3055
- sys.stdout = self._original_stdout
3056
-
3057
- error_flag=False
3058
- govt_bond='1Y'+market+'Y.B'
3059
- with HiddenPrints():
3060
- rf_df=get_price_stooq(govt_bond,start='MRW')
3061
- if not(rf_df is None):
3062
- RF=round(rf_df['Close'].mean() / 100.0,6)
3063
- print(f" Notice: recent annualized RF for {market} market is {RF} (or {round(RF*100,4)}%)")
3064
- else:
3065
- error_flag=True
3066
- print(f" #Warning(portfolio_define): no RF info found for market {market}")
3067
- print(" Solution: manually define annualized RF value without %")
3068
-
3069
- # 检查是否存在成分股
3070
- if not isinstance(members,dict):
3071
- print(" #Warning(portfolio_define): invalid structure for portfolio members")
3072
- return None
3073
-
3074
- if len(members) == 0:
3075
- print(" #Warning(portfolio_define): no members found in the portfolio")
3076
- return None
3077
-
3078
- try:
3079
- keys=members.keys()
3080
- values=members.values()
3081
- except:
3082
- print(" #Warning(portfolio_define): invalid dict for portfolio members")
3083
- return None
3084
- if len(keys) != len(values):
3085
- print(" #Warning(portfolio_define): number of members and their portions mismatch")
3086
- return None
3087
-
3088
- marketdict={'Market':(market,market_index,name)}
3089
- portfolio=dict(marketdict,**members)
3090
-
3091
- if check:
3092
- print(" Checking portfolio information ...")
3093
- df=None
3094
- with HiddenPrints():
3095
- df=security_indicator(market_index,fromdate='MRW',graph=False)
3096
- if df is None:
3097
- error_flag=True
3098
- print(f" #Warning(portfolio_define): market index {market_index} not found")
3099
-
3100
- for t in keys:
3101
- with HiddenPrints():
3102
- df=security_indicator(t,fromdate='MRW',graph=False)
3103
- if df is None:
3104
- error_flag=True
3105
- print(f" #Warning(portfolio_define): portfolio member {t} not found")
3106
-
3107
- if not check:
3108
- print(f" Notice: portfolio information not fully checked")
3109
- else:
3110
- if not error_flag:
3111
- print(f" Congratulations! Portfolio is ready to go")
3112
- else:
3113
- print(f" #Warning(portfolio_define): there are issues in portfolio definition")
3114
-
3115
- return portfolio,RF
3116
-
3117
-
3118
- #==============================================================================
3119
-
3120
- def portfolio_feasible(pf_info,simulation=10000,facecolor='papayawhip'):
3121
- """
3122
- 功能:绘制投资组合的可行集散点图,仅供教学演示,无实际用途
3123
- """
3124
- fset=portfolio_feset(pf_info,frontier=None, \
3125
- simulation=simulation,facecolor=facecolor)
3126
- return
3127
- #==============================================================================
3128
-
3129
- def portfolio_efficient(pf_info,frontier='Both',simulation=10000,facecolor='papayawhip'):
3130
- """
3131
- 功能:绘制投资组合的有效边界散点图,仅供教学演示,无实际用途
3132
- """
3133
- frontier=frontier.title()
3134
- frontier_list=['Efficient','Inefficient','Both']
3135
- if not (frontier in frontier_list):
3136
- print(f" #Warning: invalid frontier {frontier}")
3137
- print(f" Valid options for frontier: {frontier_list}")
3138
- return
3139
-
3140
- eset=portfolio_feset(pf_info,frontier=frontier, \
3141
- simulation=simulation,facecolor=facecolor)
3142
-
3143
- return
3144
-
3145
- #==============================================================================
3146
- #==============================================================================
3147
- #==============================================================================
3148
- #==============================================================================
3149
-
3150
-