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)