ml-finance-python
python scripts for finance machine learning
git clone https://9o.is/git/ml-finance-python.git
mean_variance_opt.py
(6823B)
1 # coding: utf-8
2 import pandas as pd
3 import numpy as np
4 import matplotlib.pyplot as plt
5 from scipy.optimize import minimize
6 from numpy.random import dirichlet
7
8 plt.style.use('fivethirtyeight')
9 np.random.seed(42)
10 pd.set_option('display.expand_frame_repr', False)
11
12 N_PORTFOLIOS = 2500000 # number of pf to simulate
13 RF_RATE = 0.0178
14 TRADING_DAYS = 252
15
16
17 with pd.HDFStore('mv.h5') as store:
18 factor = store.get('factor').loc['2016':]
19 prices = store.get('prices').loc['2010': '2015']
20
21 print(factor.info())
22 print(prices.info())
23
24 baseline = factor.iloc[0].dropna()
25 print(baseline.describe())
26 start = baseline.name
27 print(start)
28 base_pf = baseline.div(baseline.abs().sum())
29 print(base_pf.abs().sum())
30
31
32 assets = baseline.index.tolist()
33 n_assets = len(assets) # number of assets to allocate
34
35 returns = prices.loc[:, assets].pct_change()
36 x0 = np.full(n_assets, 1 / n_assets)
37 mean_asset_ret = returns.mean()
38 asset_cov = returns.cov()
39
40
41 def pf_vol(weights, cov):
42 return np.sqrt(weights.T @ (cov @ weights) * TRADING_DAYS)
43
44
45 def pf_ret(weights, mean_ret):
46 return (weights @ mean_ret + 1) ** TRADING_DAYS - 1
47
48
49 def pf_performance(weights, mean_ret, cov):
50 r = pf_ret(weights, mean_ret)
51 sd = pf_vol(weights, cov)
52 return r, sd
53
54
55 def simulate_pf(mean_ret, cov):
56 perf, weights = [], []
57 for i in range(N_PORTFOLIOS):
58 if i % 50000 == 0:
59 print(i)
60 weights = dirichlet([.08] * n_assets)
61 weights /= np.sum(weights)
62
63 r, sd = pf_performance(weights, mean_ret, cov)
64 perf.append([r, sd, (r - RF_RATE) / sd])
65 perf_df = pd.DataFrame(perf, columns=['ret', 'vol', 'sharpe'])
66 return perf_df, weights
67
68
69 def get_ret_vol(perf, idx):
70 r = perf.loc[idx, 'ret']
71 std = perf.loc[idx, 'vol']
72 return r, std
73
74
75 def simulate_alloc(mean_ret, cov):
76 perf, weights = simulate_pf(mean_ret, cov)
77
78 df = pd.DataFrame()
79 alloc = pd.DataFrame()
80 max_sharpe_ix = perf.sharpe.idxmax()
81 df['Max Sharpe'] = perf.loc[max_sharpe_ix, ['ret', 'vol']]
82 alloc['Max Sharpe'] = pd.Series(weights[max_sharpe_ix], index=assets)
83
84 min_std_idx = perf.vol.idxmin()
85 df['Min Vol'] = perf.loc[min_std_idx, ['ret', 'vol']]
86 alloc['Min Vol'] = pd.Series(weights[min_std_idx], index=assets)
87 return perf, alloc, df
88
89
90 def simulate_efficient_frontier(mean_ret, cov):
91 perf, alloc, df = simulate_alloc(mean_ret, cov)
92
93 perf.plot.scatter(x='vol', y='ret', c='sharpe',
94 cmap='YlGnBu', marker='o', s=10,
95 alpha=0.3, figsize=(10, 7), colorbar=True,
96 title='PF Simulation')
97
98 r, sd = df['Max Sharpe'].values
99 plt.scatter(sd, r, marker='*', color='r', s=500, label='Maximum Sharpe ratio')
100 r, sd = df['Min Vol'].values
101 plt.scatter(sd, r, marker='*', color='g', s=500, label='Minimum volatility')
102 plt.xlabel('Annualised Volatility')
103 plt.ylabel('Annualised Returns')
104 plt.legend(labelspacing=0.8)
105 plt.savefig('Simulated EF.png')
106 plt.close()
107
108 alloc.sort_values('Max Sharpe', ascending=False).plot.bar(figsize=(12, 6))
109 plt.savefig('allocations.png')
110
111
112 def neg_sharpe_ratio(weights, mean_ret, cov):
113 r, sd = pf_performance(weights, mean_ret, cov)
114 return -(r - RF_RATE) / sd
115
116
117 def max_sharpe_ratio(mean_ret, cov):
118 args = (mean_ret, cov)
119 constraints = {'type': 'eq', 'fun': lambda x: np.sum(x) - 1}
120 bounds = ((0.0, 1.0),) * n_assets
121 return minimize(fun=neg_sharpe_ratio,
122 x0=x0,
123 args=args,
124 method='SLSQP',
125 bounds=bounds,
126 constraints=constraints)
127
128
129 def pf_volatility(w, r, c):
130 return pf_performance(w, r, c)[1]
131
132
133 def min_variance(mean_ret, cov):
134 args = (mean_ret, cov)
135 constraints = {'type': 'eq', 'fun': lambda x: np.sum(x) - 1}
136 bounds = ((0.0, 1.0),) * n_assets
137
138 return minimize(fun=pf_volatility,
139 x0=x0,
140 args=args,
141 method='SLSQP',
142 bounds=bounds,
143 constraints=constraints)
144
145
146 def efficient_return(mean_ret, cov, target):
147 args = (mean_ret, cov)
148
149 def ret_(weights):
150 return pf_ret(weights, mean_ret)
151
152 constraints = [{'type': 'eq', 'fun': lambda x: ret_(x) - target},
153 {'type': 'eq', 'fun': lambda x: np.sum(x) - 1}]
154
155 bounds = ((0.0, 1.0),) * n_assets
156
157 # noinspection PyTypeChecker
158 return minimize(pf_volatility,
159 x0=x0,
160 args=args, method='SLSQP',
161 bounds=bounds,
162 constraints=constraints)
163
164
165 def efficient_frontier(mean_ret, cov, ret_range):
166 efficient_pf = []
167 for ret in ret_range:
168 efficient_pf.append(efficient_return(mean_ret, cov, ret))
169 return efficient_pf
170
171
172 def calculate_efficient_frontier(mean_ret, cov):
173 perf, wt = simulate_pf(mean_ret, cov)
174 print(pd.DataFrame(wt).stack().describe())
175
176 max_sharpe = max_sharpe_ratio(mean_ret, cov)
177 max_sharpe_perf = pf_performance(max_sharpe.x, mean_ret, cov)
178 wmax = max_sharpe.x
179 print(np.sum(wmax))
180
181 min_vol = min_variance(mean_ret, cov)
182 min_vol_perf = pf_performance(min_vol['x'], mean_ret, cov)
183
184 pf = ['Max Sharpe', 'Min Vol']
185 alloc = pd.DataFrame(dict(zip(pf, [max_sharpe.x, min_vol.x])), index=assets)
186 selected_pf = pd.DataFrame(dict(zip(pf, [max_sharpe_perf, min_vol_perf])),
187 index=['ret', 'vol'])
188
189 print(selected_pf)
190 print(perf.describe())
191
192 perf.plot.scatter(x='vol', y='ret', c='sharpe',
193 cmap='YlGnBu', marker='o', s=10,
194 alpha=0.3, figsize=(10, 7), colorbar=True,
195 title='PF Simulation')
196
197 r, sd = selected_pf['Max Sharpe'].values
198 plt.scatter(sd, r, marker='*', color='r', s=500, label='Max Sharpe Ratio')
199 r, sd = selected_pf['Min Vol'].values
200 plt.scatter(sd, r, marker='*', color='g', s=500, label='Min volatility')
201 plt.xlabel('Annualised Volatility')
202 plt.ylabel('Annualised Returns')
203 plt.legend(labelspacing=0.8)
204
205 rmin = selected_pf.loc['ret', 'Min Vol']
206 rmax = returns.add(1).prod().pow(1 / len(returns)).pow(TRADING_DAYS).sub(1).max()
207 ret_range = np.linspace(rmin, rmax, 50)
208 # ret_range = np.linspace(rmin, .22, 50)
209 efficient_portfolios = efficient_frontier(mean_asset_ret, cov, ret_range)
210
211 plt.plot([p['fun'] for p in efficient_portfolios], ret_range, linestyle='-.', color='black',
212 label='efficient frontier')
213 plt.title('Calculated Portfolio Optimization based on Efficient Frontier')
214 plt.xlabel('annualised volatility')
215 plt.ylabel('annualised returns')
216 plt.legend(labelspacing=0.8)
217 plt.tight_layout()
218 plt.savefig('Calculated EF.png')
219
220
221 calculate_efficient_frontier(mean_asset_ret, asset_cov)