Inspired by many specialized ETFs (Exchange-Traded Funds: An investment vehicle that pools a group of securities into a fund. It can be traded like an individual stock on an exchange). Creating a basket of stocks with weights for specific investment strategies or weighting criteria beyond traditional market-capitalization indexes and treating them all as a unit.
A portfolio, if selected properly, is less vulnerable to extreme highs and lows, and provides the benefits of diversification.
This project offers optimization and modeling tools for investment strategies. Leveraging Efficient Frontier modeling, Monte Carlo simulation, risk metrics (Sharpe ratio, VaR), and Fama French factor models w/ python libraries like pandas, yfinance, and PyPortfolioOpt.
Utilized yfinance
for data retrieval of NSE stocks, obtaining historical price data for a specified date range.
'df' is the DataFrame that contains daily prices of stocks.
'weights' is the array that contains portfolio weights.
returns = df.pct_change()
returns.dropna(inplace = True)
Portfolio construction w/ stock returns
returns_pf = returns.dot(weights)
Portfolio construction w/ stock values
pf_AUM = df.dot(weights)
- To account for compounding effect
- To compare portfolios with different time periods
- Average return can be deceiving (example: if portfolio loses all its value in the end the average may be > 0%, but final return is 0%)
Annualizing returns:
total_return = (pf_AUM[-1] - pf_AUM[0]) / pf_AUM[0]
annualized_return = ((1 + total_return) * * (12 / months)) - 1
pf_returns = pf_AUM.pct_change()
pf_vol = pf_returns.std()
pf_vol = pf_vol * np.sqrt(250)
250: trading days in a year (annual)
sqrt: variance annualized = 250 * variance daily.
Therefore, standard deviation annualized = sqrt(250) * standard deviation daily.
sharpe_ratio = ((annualized_return - rfr) / pf_vol)
cov_matrix = (returns.cov())*250
port_variance = np.dot(weights.T, np.dot(cov_matrix, weights))
port_standard_dev = np.sqrt(port_variance)
View Portfolio Variance derivation
so that we calculate the annualized volatility later.
Precision: As stocks are correlated to each other, we need to account for correlations b/w stocks. This gives more precision in measuring volatility.
It can be forward looking if covariance matrix is estimated or forecasted.
Historical data method can include rebalancing.
Skewness measures the asymmetry of the distribution of data around its mean.
pf_returns.skew()
Risk management: Investors should seek positive skew as in the long run, few positive bets should create a positive expectancy
Kurtosis measures the "tailedness" of the data distribution, indicating the presence of outliers.
pf_returns.kurtosis()
k>3: FAT (leptokurtic) | high risk-high reward
Finding var95 and cvar95:
var = np.percentile(returns_pf, 5)
cvar = returns_pf [returns_pf <= var].mean()
View Complete Risk Analysis w/ 15 Portfolio Risk metrics including 5 VaR metrics
The Fama-French 3 factor model tells us what drives portfolio returns and quantifies their contributions.
FamaFrench_model = smf.ols(formula='Portfolio_Excess ~ Market_Excess + SMB + HML', data=FamaFrenchData_final)
Risk management:
- Identifies exposure to specific factors (size, value)
- Optimize portfolio by adjusting exposures
- Evaluate performance
because there might be a high correlation with their levels but not with their changes so correlation of % change or differences will be close to 0.
The efficient frontier is the set of portfolios that achieve the highest return for a given risk or the lowest risk for a given return, representing optimal diversification.
View Portfolio Optimization maths
View Complete Portfolio Optimization of NSE stocks
hope you find it helpful, and encourage you to forward any suggestions for improvements