# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

#
# <h2 id="Modern-Portfolio-Theory">Modern Portfolio Theory<a class="anchor-link" href="#Modern-Portfolio-Theory">¶</a></h2>
#

#
# <p>Modern portfolio theory also popularly called as <strong><code>Mean-Variance Portofolio Theory</code> (MVP)</strong> is a major breakthrough in finance. It is based on the premises that returns are <strong>normally distributed</strong> and by looking at mean and variance, we can essentialy describe the distribution of end-of-period wealth.</p>
# <p>The basic idea of this theory is to achieve diversification by constructuing portfolio for a minimal portfolio risk or maximal portfolio returns given a certain level of risk. Accordingly, the <strong>Efficient Frontier</strong> is a set of optimal portfolios in the risk-return spectrum and portfolios located under the Efficient Frontier curve are considered sub-optimal.</p>
# <p>This means that the portfolios on the frontier offer</p>
# <ul>
# <li>Highest expected return for a given level of risk</li>
# <li>Lowest level of risk for a given level of expected returns</li>
# </ul>
# <p>In essence, the investors goal should be to select a level of risk that he/she is comfortable with and then find a portfolio that maximizes returns based on the selected risk level.</p>
#

#
# <h3 id="Import-libraries">Import libraries<a class="anchor-link" href="#Import-libraries">¶</a></h3>
#

# +
import pandas as pd
import xlwings as xw

import numpy as np
from numpy import *
from numpy.linalg import multi_dot

import matplotlib.pyplot as plt
from matplotlib.pyplot import rcParams
rcParams['figure.figsize'] = 16, 8
# -

from openpyxl import Workbook, load_workbook

#
# <p>We will use the FAANG stocks as before to build our portfolio</p>
#

# FAANG stocks
symbols = ['AAPL', 'AMZN', 'FB', 'GOOG', 'NFLX' ]
numofasset = len(symbols)
numofportfolio = 5000

#
# <h3 id="Retrive-Data">Retrive Data<a class="anchor-link" href="#Retrive-Data">¶</a></h3>
#

# +
# Load locally stored data
df = pd.read_csv('faang_stocks.csv', index_col=0, parse_dates=True)['2013':]

# Check first 5 values 
df.head()
# -

#
# <h3 id="View-Data-in-Excel">View Data in Excel<a class="anchor-link" href="#View-Data-in-Excel">¶</a></h3>
#

# View data in Excel
xw.view(df)

# Create a new Excel workbook
wb = xw.Book(r'portfolio.xlsx')     # Book by full name

#
# <h3 id="Descriptive-Statistics">Descriptive Statistics<a class="anchor-link" href="#Descriptive-Statistics">¶</a></h3>
#

summary = df.describe().T
summary

#
# <h3 id="Visualize-Data">Visualize Data<a class="anchor-link" href="#Visualize-Data">¶</a></h3>
#

# +
# Visualize the data
fig = plt.figure(figsize=(16,8))
ax = plt.axes()

ax.set_title('Normalized Price Plot')
ax.plot(df[-252:]/df.iloc[-252] * 100)
ax.legend(df.columns, loc='upper left')
ax.grid(True)
# -

#
# <h3 id="Calculate-Returns">Calculate Returns<a class="anchor-link" href="#Calculate-Returns">¶</a></h3>
#

# Calculate returns 
returns = df.pct_change().fillna(0)
returns.head()

#
# <h4 id="Annualized-Returns">Annualized Returns<a class="anchor-link" href="#Annualized-Returns">¶</a></h4><p>In <strong>MVP</strong>, the average returns play an important role as they are used to approximate the expected returns.</p>
#

# Calculate annual returns
annual_returns = (returns.mean() * 252)
annual_returns

# +
# Visualize the data
fig = plt.figure()
ax =plt.axes()

ax.bar(annual_returns.index, annual_returns*100, color='royalblue', alpha=0.75)
ax.set_title('Annualized Returns (in %)');
# -

#
# <h3 id="Calculate-Volatility">Calculate Volatility<a class="anchor-link" href="#Calculate-Volatility">¶</a></h3>
#

vols = returns.std()
vols

#
# <h4 id="Annualized-Volatilities">Annualized Volatilities<a class="anchor-link" href="#Annualized-Volatilities">¶</a></h4>
#

# Calculate annualized volatilities
annual_vols = vols*sqrt(252)
annual_vols

# +
# Visualize the data
fig = plt.figure()
ax = plt.axes()

ax.bar(annual_vols.index, annual_vols*100, color='orange', alpha=0.5)
ax.set_title('Annualized Volatility (in %)');
# -

#
# <h2 id="Portfolio-Statistics">Portfolio Statistics<a class="anchor-link" href="#Portfolio-Statistics">¶</a></h2><p>Consider a portfolio fully invested in risky assets. Let $w$ and $\mu$ be the vector of weights and mean returns of <em>n</em> assets. <br/><br/></p>
# $$\ {w=}\left( 
# \begin{array}{c}
# w_1 \\
# w_2 \\
# \vdots \\
# w_n \\ 
# \end{array}%
# \right);
# \ \mathbf{\mu=}\left( 
# \begin{array}{ccc}
# \mu_1 \\ 
# \mu_2 \\ 
# \vdots \\
# \mu_n \\ 
# \end{array}%
# \right)$$<p></p>
# <p>where the $\sum_{i=1}^{n}w_i=1$</p>
# <p><strong>Expected Portfolio Return</strong> is then the dot product of the expected returns and their weights. <br/><br/></p>
# $$\mu_\pi = w^T\cdot\mu$$<p>which is also equivalent to the $\Sigma_{i=1}^{n}w_i\mu_i$</p>
# <p><strong>Expected Portfolio Variance</strong> is then the multidot product of weights and the covariance matrix. <br/><br/></p>
# $$\sigma^2_\pi = w^T\cdot\Sigma\cdot w $$<p>where, ${\Sigma}$ is the covariance matrix</p>
# $${\Sigma=}\left( 
# \begin{array}{ccc}
# \Sigma_{1,1} &amp; \dots &amp; \Sigma_{1,n} \\ 
# \vdots &amp; \ddots &amp; \vdots  \\ 
# \Sigma_{n,1} &amp; \dots &amp; \Sigma_{n,n} \\ %
# \end{array}%
# \right)$$
#

#
# <h3 id="Equal-Weighted-Portfolio">Equal Weighted Portfolio<a class="anchor-link" href="#Equal-Weighted-Portfolio">¶</a></h3><p>Assume a portoflio composed of all five stocks with equal weighting. We will now calculate the portfolio statistics.</p>
#

wts = numofasset * [1./numofasset]
array(wts).shape

wts = array(wts)[:,newaxis]
wts

wts.shape

#
# <h3 id="Portfolio-Return">Portfolio Return<a class="anchor-link" href="#Portfolio-Return">¶</a></h3>
#

array(returns.mean() * 252)[:,newaxis]      

array(returns.mean() * 252)[:,newaxis].shape 

# Portfolio returns
wts.T @ array(returns.mean() * 252)[:,newaxis]        

#
# <h3 id="Portfolio-Volatility">Portfolio Volatility<a class="anchor-link" href="#Portfolio-Volatility">¶</a></h3>
#

# Covariance matrix
returns.cov() * 252

# Portfolio variance
multi_dot([wts.T,returns.cov()*252,wts])

# Portfolio volatility
sqrt(multi_dot([wts.T,returns.cov()*252,wts]))


#
# <h3 id="Portfolio-statistics">Portfolio statistics<a class="anchor-link" href="#Portfolio-statistics">¶</a></h3><p>Let's subsume key statistics into a function which can be used for optimization exercise.</p>
#

def portfolio_stats(weights):
    
    weights = array(weights)[:,newaxis]
    port_rets = weights.T @ array(returns.mean() * 252)[:,newaxis]    
    port_vols = sqrt(multi_dot([weights.T, returns.cov() * 252, weights])) 
    
    return np.array([port_rets, port_vols, port_rets/port_vols]).flatten()


pip install pyfolio

import pyfolio as pf
ewp = returns@<EMAIL>
ewp.columns =['ret']
ewp.cumsum().iloc[-1]
pf.create_simple_tear_sheet(ewp['ret'])
plt.plot((1+ewp['ret']).cumprod())

#
# <h2 id="Portfolio-Simulation">Portfolio Simulation<a class="anchor-link" href="#Portfolio-Simulation">¶</a></h2><p>Now, we will implement a Monte Carlo simulation to generate random portfolio weights on a larger scale and calculate the expected portfolio return, variance and sharpe ratio for every simulated allocation. We will then identify the portfolio with a highest return for per unit of risk.</p>
#

w = random.random(numofasset)[:, newaxis]
w

w /= sum(w)
w

w.shape, sum(w)

w.flatten()

# +
# Initialize the lists
rets = []; vols = []; wts = []

# Simulate 5,000 portfolios
for i in range (5000):
    
    # Generate random weights
    weights = random.random(numofasset)[:, newaxis]
    
    # Set weights such that sum of weights equals 1
    weights /= sum(weights)
    
    # Portfolio statistics
    rets.append(weights.T @ array(returns.mean() * 252)[:, newaxis])        
    vols.append(sqrt(multi_dot([weights.T, returns.cov()*252, weights])))
    wts.append(weights.flatten())

# Record values     
port_rets = array(rets).flatten()
port_vols = array(vols).flatten()
port_wts = array(wts)
# -

port_rets

port_vols

port_wts

port_rets.shape, port_vols.shape, port_wts.shape

# Create a dataframe for analysis
mc_df = pd.DataFrame({'returns': port_rets,
                      'volatility': port_vols,
                      'sharpe_ratio': port_rets/port_vols,
                      'weights': list(port_wts)})
mc_df.head()

#
# <h3 id="Summary-Statistics">Summary Statistics<a class="anchor-link" href="#Summary-Statistics">¶</a></h3>
#

# Summary Statistics
mc_df.describe().T

#
# <h3 id="Maximum-Sharpe-Ratio-Portfolio">Maximum Sharpe Ratio Portfolio<a class="anchor-link" href="#Maximum-Sharpe-Ratio-Portfolio">¶</a></h3>
#

# Max sharpe ratio portfolio 
msrp = mc_df.iloc[mc_df['sharpe_ratio'].idxmax()]
msrp

# +
# Max sharpe ratio portfolio weights
max_sharpe_port_wts = mc_df['weights'][mc_df['sharpe_ratio'].idxmax()]

# Allocation to achieve max sharpe ratio portfolio
dict(zip(symbols,np.around(max_sharpe_port_wts*100,2)))
# -

#
# <h3 id="Visulaize-Simulated-Portfolio">Visulaize Simulated Portfolio<a class="anchor-link" href="#Visulaize-Simulated-Portfolio">¶</a></h3>
#

# +
# Visualize the simulated portfolio for risk and return
fig = plt.figure()
ax = plt.axes()

ax.set_title('Monte Carlo Simulated Allocation')

# Simulated portfolios
fig.colorbar(ax.scatter(port_vols, port_rets, c=port_rets / port_vols, 
                        marker='o', cmap='RdYlGn', edgecolors='black'), label='Sharpe Ratio') 

# Maximum sharpe ratio portfolio
ax.scatter(msrp['volatility'], msrp['returns'], c='red', marker='*', s = 300, label='Max Sharpe Ratio')

ax.set_xlabel('Expected Volatility')
ax.set_ylabel('Expected Return')
ax.grid(True)
# -

#
# <h2 id="Efficient-Frontier">Efficient Frontier<a class="anchor-link" href="#Efficient-Frontier">¶</a></h2>
#

#
# <p>The Efficient Frontier is formed by a set of portfolios offering the highest expected portfolio return for a certain volatility or offering the lowest volatility for a certain level of expected returns.</p>
# <p><strong>Return objective</strong>:</p>
# $$\underset{w_1,w_2,\dots,w_n}{minimize} \space\space \sigma^2_{p}(w_1,w_2,\dots,w_n)$$<p>subject to,</p>
# $$E[R_p] = m$$<p><strong>Risk constraint</strong>:</p>
# $$\underset{w_1,w_2,\dots,w_n}{maximize} \space\space E[R_p(w_1,w_2,\dots,w_n)]$$<p>subject to,</p>
# $$\sigma^2_{p}(w_1,w_2,\dots,w_n)=v^2$$<p>where, $\sum_{i=1}^{n}w_i=1$ for the above objectives.</p>
# <p>We can use numerical optimization to achieve this objective. The goal of optimization is to find the optimal value of the objective function by adjusting the target variables operating withing some boundary conditions and constraints.</p>
#

#
# <h3 id="Constrained-Optimization">Constrained Optimization<a class="anchor-link" href="#Constrained-Optimization">¶</a></h3><p>Construction of optimal portfolios is a constrained optimization problem where we specify some boundary conditions and constraints. The objective function here is a function returning maximum sharpe ratio, minimum variance (volatility) and the target variables are portfolio weights. We will use the <em><code>minimize</code></em> function from <code>scipy</code> optimization module to achieve our objective.</p>
# <blockquote><div class="highlight"><pre><span></span><span class="n">sco</span><span class="o">.</span><span class="n">minimize</span><span class="p">(</span><span class="n">fun</span><span class="p">,</span> <span class="n">x0</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(),</span> <span class="n">method</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">jac</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">hess</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">hessp</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> 
#              <span class="n">bounds</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">constraints</span><span class="o">=</span><span class="p">(),</span> <span class="n">tol</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">callback</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
# </pre></div>
# </blockquote>
#

# Import optimization module from scipy
import scipy.optimize as sco


#
# <h4 id="Maximum-sharpe-ratio-portfolio">Maximum sharpe ratio portfolio<a class="anchor-link" href="#Maximum-sharpe-ratio-portfolio">¶</a></h4>
#

# Maximizing sharpe ratio
def min_sharpe_ratio(weights):
    return -portfolio_stats(weights)[2] # asigning minus 1


cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bnds = tuple((0, 1) for x in range(numofasset))
initial_wts = numofasset*[1./numofasset]

# Optimizing for maximum sharpe ratio
opt_sharpe = sco.minimize(min_sharpe_ratio, initial_wts, method='SLSQP', bounds=bnds, constraints=cons)

opt_sharpe

# Portfolio weights
list(zip(symbols,np.around(opt_sharpe['x']*100,2)))

# Portfolio stats
stats = ['Returns', 'Volatility', 'Sharpe Ratio']
list(zip(stats,np.around(portfolio_stats(opt_sharpe['x']),4)))


#
# <h4 id="Minumum-variance-portfolio">Minumum variance portfolio<a class="anchor-link" href="#Minumum-variance-portfolio">¶</a></h4>
#

# Minimize the variance
def min_variance(weights):
    return portfolio_stats(weights)[1]**2


# Optimizing for minimum variance
opt_var = sco.minimize(min_variance, initial_wts, method='SLSQP', bounds=bnds, constraints=cons)

opt_var

# Portfolio weights
list(zip(symbols,np.around(opt_var['x']*100,2)))

# Portfolio stats
list(zip(stats,np.around(portfolio_stats(opt_var['x']),4)))


#
# <h4 id="Efficient-Frontier-portfolio">Efficient Frontier portfolio<a class="anchor-link" href="#Efficient-Frontier-portfolio">¶</a></h4><p>For efficient frontier portfolios, we fix a target return and derive for objective function.</p>
#

# Minimize the volatility
def min_volatility(weights):
    return portfolio_stats(weights)[1]


# +
targetrets = linspace(0.22,0.50,100)
tvols = []

for tr in targetrets:
    
    ef_cons = ({'type': 'eq', 'fun': lambda x: portfolio_stats(x)[0] - tr},
               {'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    
    opt_ef = sco.minimize(min_volatility, initial_wts, method='SLSQP', bounds=bnds, constraints=ef_cons)
    
    tvols.append(opt_ef['fun'])

targetvols = array(tvols)

# +
# Visualize the simulated portfolio for risk and return
fig = plt.figure()
ax = plt.axes()

ax.set_title('Efficient Frontier Portfolio')

# Efficient Frontier
fig.colorbar(ax.scatter(targetvols, targetrets, c=targetrets / targetvols, 
                        marker='x', cmap='RdYlGn', edgecolors='black'), label='Sharpe Ratio') 

# Maximum Sharpe Portfolio
ax.plot(portfolio_stats(opt_sharpe['x'])[1], portfolio_stats(opt_sharpe['x'])[0], 'r*', markersize =15.0)

# Minimum Variance Portfolio
ax.plot(portfolio_stats(opt_var['x'])[1], portfolio_stats(opt_var['x'])[0], 'b*', markersize =15.0)

ax.set_xlabel('Expected Volatility')
ax.set_ylabel('Expected Return')
ax.grid(True)
# -

#
# <h1 id="References">References<a class="anchor-link" href="#References">¶</a></h1><ul>
# <li><p>Numpy linear algebra documentation <a href="https://numpy.org/doc/stable/reference/routines.linalg.html">https://numpy.org/doc/stable/reference/routines.linalg.html</a></p>
# </li>
# <li><p>Scipy optimization function documentation <a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html">https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html</a></p>
# </li>
# <li><p>Xlwings documentation <a href="https://docs.xlwings.org/en/stable/index.html">https://docs.xlwings.org/en/stable/index.html</a></p>
# </li>
# <li><p><NAME> (2018), Python For Finance: Analyze Big Financial Data</p>
# </li>
# </ul>
#

StarcoderdataJupyterScriptsDedupFiltered