Python for Finance-6: Constant Proportion Portfolio Insurance (CPPI)
What are the risks involved with your current portfolio? Well, the two important risks that investors should be concerned about are idiosyncratic and Systematic risks.
Idiosyncratic risks (Specific or unsystematic risk): It’s a type of investment risk that is endemic (characteristic) to an individual asset (a particular stock) or a group of assets (a particular sector) or in rare cases a specific asset class (like collateralized mortgage obligation). It refers to the inherent factors that can negatively impact securities. Certain securities naturally have higher idiosyncratic risk than others. Idiosyncratic risk can generally be mitigated in an investment portfolio by diversification.
Systematic risk: The risk associated with the market itself and cannot be mitigated with diversification. The most popular solution is to hedge against such situations using derivatives using options. Hedging limits the portfolio’s upside because it’s complex and expensive to implement.
Systematic vs. Unsystematic Risk — Risk Management — YouTube
Don’t worry there is a solution that doesn’t involve derivatives. This opens us to the world of Portfolio Insurance Strategies.
Constant Proportion Portfolio Insurance (CPPI) is a type of portfolio insurance in which investors sets a floor on the value of their portfolio, then structure the asset allocation around that decision. The two asset classes used in CPPI are risky assets (usually equities) and conservative assets of either cash or bonds. The percentage allotted to each depends on the ‘cushion value’ which is the current portfolio value minus a multiplier co-efficient, where a higher number denotes a more aggressive strategy.
For example, given a multiplier M=4 and assuming your current asset value (AV) is at 100%, and you want to keep its value at a minimum 85% (floor F=85%). You can invest 4x($100-$85)=4x$15=$60 into your risky assets and the rest of $40 to safe assets.
We start with our investment (CPPI) and define your floor (F) as a certain % of our investment. Each investor’s F is different depending on their risk appetite. We then calculate the cushion of our portfolio’s downside as
Cushion (C ) = CPPI — Floor (F)
The risky asset’s (E) exposure is then this cushion multiplied by M. M is calculated as the inverse of the maximum downside that is expected in a given period of time. Think of it as the inverse of the value at risk for your risky asset.
The final CPPI is the sum of the returns of the risky asset (E) and the stable asset (B). This is an iterative calculation done in given intervals (day, week, month, quarter or year) to readjust your portfolio.
Implementation in Python:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
#Here RELIANCE is our large cap stable asset and SUZLON is our small cap risky asset
stocks = ['RELIANCE.NS','SUZLON.NS']
prices = pd.DataFrame([])
#Date range
start = '2018-01-01'
end = '2022-11-27'
for i in stocks:
prices[i] = yf.download(i,start=start,end=end)['Adj Close']
prices
#Let's calculate value at risk for our portfolio for this time period (Q1) with confidence level of 99%
def portfolio_var(r,level=5):
return round(-np.percentile(r,level,axis=0)*100,2)
'''Rember that Var at a certain confidence gives the worst return from the remaining
samples after eliminating the lowest % values below the confidence internal
In our case for 99% confidence, the output is the worst return in 99% of the times.
'''
stock_returns = prices.pct_change().dropna()
floor_value = stock_returns.aggregate(portfolio_var)
safe_returns = stock_returns[['RELIANCE.NS']]
risky_returns = stock_returns[['SUZLON.NS']]
#Create a risky and safe allocation of same size as their respective returns using reindex_like() function
risky_alloc = pd.DataFrame().reindex_like(risky_returns)
safe_alloc = pd.DataFrame().reindex_like(safe_returns)
account_history = pd.DataFrame().reindex_like(safe_returns)
#Use Var to define floor of your portfolio
#Absolute worst case considering a drop across all your portfolio stocks in the given time period
print("Worst case drop across portfolio is:", round(floor_value.sum(),2),"% at 95% confidence")
Worst case drop across portfolio is: 7.82% at 95% confidence
#We can either take this as the floor for our portfolio or define our floor as a certain value above this.
#Let's treat our floor as 20%, i.e. F = 20%
#CPPI basic values definition
investment = 100000
my_floor = investment*0.8
my_M = 5.62
#Traditional CPPI
dates = risky_returns.index
date_index = len(dates) #Calculating the length of date index so you can it
for step in range(date_index):
cushion = (investment - my_floor)/investment #Calculating cushion
risky_w = my_M*cushion #Calculating the weight of the risky asset
risky_w = np.minimum(risky_w,1) #Defining the weight such that it is less than 1 (100%)
risky_w = np.maximum(risky_w,0) #Defining the weight such that it is at least 0 (weight should not go negative)
safe_w = 1 - risky_w #Defining the weight of the stable asset
risky_alloc.iloc[step] = (investment*risky_w)*(1+risky_returns.iloc[step]) #Allocation to the risky asset
safe_alloc.iloc[step] = (investment*safe_w)*(1+safe_returns.iloc[step]) #Allocation to the safe asset
investment = risky_alloc.iloc[step].sum()+safe_alloc.iloc[step].sum() #CPPI which is your investment to the next iteration
account_history.iloc[step] = investment
#Wealth with CPPI
account_history.columns = ['With CPPI']
#Plot the risky and safe allocations
axis1 = risky_alloc.plot(figsize = (16,8))
safe_alloc.plot(ax=axis1)
#Wealth without CPPI
risky_wealth = (10000)*(1+risky_returns).cumprod()
risky_wealth.columns = ['No CPPI']
axis2 = risky_wealth.plot(figsize=(16,8))
account_history.plot(ax=axis2)
CPPI is very valuable if you want to safeguard your investment against dropping below a lower threshold so that you can cut your losses and move out anytime you wish.
Thank you for reading this article :)
Code is avaliable on my git profile: @Pratham2012
Don’t forget to follow and share it with your Investor friends.