codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2004/iii/paper-35-frontiers.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2004/iii/paper-35-frontiers.py
"""Mean-variance geometry for Paper 35; Python 3.14, numpy 2.3.5, matplotlib 3.10.7.
Writes only paper-35-frontiers.png in the caller's current directory.
The caller may supply MPLCONFIGDIR; this script does not modify it.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

mu=np.array([10.,17.,24.])
cov=np.array([[4.,1.,1.],[1.,9.,2.],[1.,2.,16.]])
one=np.ones(3)
a=one@np.linalg.solve(cov,one)
b=one@np.linalg.solve(cov,mu)
c=mu@np.linalg.solve(cov,mu)
d=a*c-b*b
mean_min=b/a
means=np.linspace(-25,75,1600)
sigma_min=np.sqrt((a*means*means-2*b*means+c)/d)
fig,axs=plt.subplots(1,2,figsize=(12,6),sharex=True,sharey=True,layout='constrained')
for ax,r in zip(axs,[3.,25.]):
    ax.fill_betweenx(means,sigma_min,6,where=sigma_min<=6,color='#d8eaf1',label='Risky opportunity set')
    upper=means>=mean_min
    ax.plot(sigma_min[upper],means[upper],color='#237498',lw=2,label='Efficient risky frontier')
    ax.plot(sigma_min[~upper],means[~upper],color='#237498',ls='--',lw=1.5,label='Inefficient risky boundary')
    z=np.linalg.solve(cov,mu-r*one)
    weights=z/(one@z)
    tangent_mean=mu@weights
    tangent_sigma=np.sqrt(weights@cov@weights)
    theta=np.sqrt((mu-r*one)@z)
    x=np.linspace(0,6,400)
    ax.plot(x,r+theta*x,color='#ab3b27',lw=2,label='Efficient bank + risky ray')
    ax.scatter([0],[r],color='black',s=35,zorder=4)
    ax.annotate('Risk-free asset',(0,r),xytext=(12,4),textcoords='offset points',fontsize=9)
    ax.scatter([1/np.sqrt(a)],[mean_min],color='#237498',s=30,zorder=4)
    ax.scatter([tangent_sigma],[tangent_mean],color='#6f348c',s=35,zorder=5)
    if r==3:
        ax.annotate('Market portfolio',(tangent_sigma,tangent_mean),xytext=(15,-23),textcoords='offset points',fontsize=9,arrowprops={'arrowstyle':'-','color':'#6f348c'})
    else:
        ax.plot(x,r-theta*x,color='#6f348c',ls=':',lw=1.5,label='Negative-premium tangent ray')
        ax.annotate('Normalized tangency portfolio',(tangent_sigma,tangent_mean),xytext=(10,-29),textcoords='offset points',fontsize=9,arrowprops={'arrowstyle':'-','color':'#6f348c'})
        reflected=2*r-tangent_mean
        ax.scatter([tangent_sigma],[reflected],color='#ab3b27',s=35,zorder=5)
        ax.annotate('Short one tangency portfolio;\nlend twice initial wealth',(tangent_sigma,reflected),xytext=(8,14),textcoords='offset points',fontsize=9,arrowprops={'arrowstyle':'-','color':'#ab3b27'})
    ax.set(xlim=(0,6),ylim=(-25,75),xlabel='Return standard deviation (percentage points)',title=f'Risk-free return = {r:g}%')
    ax.grid(alpha=.18)
    ax.legend(loc='lower right',fontsize=7,framealpha=.95)
axs[0].set_ylabel('Expected return (%)')
fig.suptitle('Risky opportunity sets and efficient risk-free combinations')
fig.savefig(Path.cwd()/'paper-35-frontiers.png',dpi=120,facecolor='white',transparent=False)
plt.close(fig)