Do you want to learn how to create an optimized portfolio of stocks that maximizes returns while minimizing risk? Look no further than this Python code! The first step is to gather data on the stocks, which can be easily done using the “yfinance” library to download historical closing prices of several Indian stocks. Once you have the data, the code uses the “expected_returns” library to calculate the mean historical return of the stocks in the portfolio, as well as the “risk_models” library to calculate the sample covariance matrix of the stocks, which measures the correlation between them.
Using the “matplotlib” and “seaborn” libraries, the code creates a heatmap to visualize the covariance between the daily simple returns of the stocks in the portfolio. Then, the “EfficientFrontier” function from the “pypfopt” library is used to create an instance of the Efficient Frontier, which is a graph that shows the optimal portfolio of stocks that maximizes returns while minimizing risk. To get the portfolio weights that maximize the Sharpe ratio, the code calls the “max_sharpe” method on the Efficient Frontier instance. The Sharpe ratio is a measure of risk-adjusted returns, and maximizing it gives the most optimal portfolio.
Once the weights are obtained from the “max_sharpe” method, the code cleans them using the “clean_weights” method, and stores the cleaned weights in a dictionary called “cleaned_weights”. The code then creates a pie chart to show the allocation of stocks in the optimized portfolio.
Finally, the “DiscreteAllocation” function from the “pypfopt” library is used to allocate the available funds into the stocks in the portfolio. The “lp_portfolio” method is called on the Discrete Allocation instance to get the number of stocks to buy for each stock in the portfolio, and the results are printed in a pandas DataFrame.
In conclusion, this code provides an excellent starting point for creating an optimized portfolio of stocks. However, it’s important to remember that proper research should be done on the stocks and their respective markets before making any investment decisions. While past performance can provide valuable insights, it’s not indicative of future results, and there is always a certain level of risk involved in stock investments.
Let’s Begin Coding!
import pytz
import yfinance as yf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
- This code above imports the required libraries and modules for the program to run, including
pytz
,yfinance
,numpy
,pandas
,matplotlib
, andseaborn
.
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices
from datetime import datetime as dt
from datetime import date
from nsepy import get_history as gh
- Now let’s code imports specific modules from the
pypfopt
package, which stands for “Python Portfolio Optimization”. TheEfficientFrontier
,risk_models
,expected_returns
, andDiscreteAllocation
modules are all used to optimize a given portfolio. Thedatetime
anddate
modules are used to specify the time range of the data to download, and theget_history
module fromnsepy
is used to download the historical stock data. You will later understand, in the article, why these libraries have been imported.
plt.style.use('fivethirtyeight')
- This line sets the plotting style to ‘fivethirtyeight’. It’s just a plotting style I am using for this example.
#Setting the Time-Zone is very importanttz = pytz.timezone("Asia/Kolkata") start = tz.localize(dt(2003,4,1)) #10-Year Data end = tz.localize(dt.today())
tickers = "RELIANCE.NS,TCS.NS,HDFCBANK.NS,HINDUNILVR.NS,ICICIBANK.NS,KOTAKBANK.NS,ITC.NS,INFY.NS,LT.NS,ASIANPAINT.NS,BAJAJ-AUTO.NS,BAJAJFINSV.NS,BAJFINANCE.NS,BHARTIARTL.NS,BPCL.NS,CIPLA.NS,COALINDIA.NS,DRREDDY.NS,EICHERMOT.NS,GAIL.NS,GRASIM.NS,HEROMOTOCO.NS,HINDALCO.NS,IOC.NS,JSWSTEEL.NS,M&M.NS,MARUTI.NS,NTPC.NS,NESTLEIND.NS,ONGC.NS,POWERGRID.NS,RECLTD.NS,SBILIFE.NS,SHREECEM.NS,SUNPHARMA.NS,TATACONSUM.NS,TATAMOTORS.NS,TATASTEEL.NS,TATAPOWER.NS,TITAN.NS,ULTRACEMCO.NS,UPL.NS,WIPRO.NS".split(",")
df = yf.download(tickers,start, end, auto_adjust=True)['Close'] print (df)
- Let’s set the time zone, start date, and end date for the data to be downloaded. The code above will help you download the historical stock data for the given tickers from Yahoo Finance using the
yfinance
module, and assigns the data to a pandas DataFrame calleddf
. Theprint
statement is used to display the DataFrame.
mean = expected_returns.mean_historical_return(df)
S = risk_models.sample_cov(df) # for sample covariance matrix
print (S)
- This block of code calculates the mean historical returns and the sample covariance matrix for the stocks in the DataFrame
df
. Theprint
statement is used to display the covariance matrix.
plt.style.use('ggplot')
fig = plt.figure()
sb.heatmap(S,xticklabels=S.columns, yticklabels=S.columns,
cmap='RdBu_r', annot=True, linewidth=0.5)
print('Covariance between daily simple returns of stocks in your portfolio')
plt.show()
- This block of code creates a heatmap of the covariance matrix using the
seaborn
module, with theplt
module used to display the heatmap. Theprint
statement is used to display a message above the heatmap. Similar to the Figure below,
ef = EfficientFrontier(mean,S)
weights = ef.max_sharpe() #for maximizing the Sharpe ratio #Optimization
cleaned_weights = ef.clean_weights() #to clean the raw weights
# Get the Keys and store them in a list
labels = list(cleaned_weights.keys())
# Get the Values and store them in a list
values = list(cleaned_weights.values())
values_to_plot = [v for v in values if v > 0]
labels_to_plot = [labels[i] for i, v in enumerate(values) if v > 0]
fig, ax = plt.subplots()
ax.pie(values_to_plot, labels=labels_to_plot, autopct='%1.0f%%')
print('Portfolio Allocation')
plt.show()
- Now let’s initialize an
EfficientFrontier
object with the mean returns and covariance matrix calculated earlier, using the code above.
ef.portfolio_performance(verbose=True)
The line above calculates various portfolio performance metrics such as the expected return, volatility, and Sharpe ratio, and prints them to the console with verbose output.
# Portfolio Optimization with fixed amount
portfolio_amount = 100000 # Total Portfolio Investment
if portfolio_amount != '':
# Get discrete allocation of each share per stock
weights = portfolio['Weight'].values
allocation = np.multiply(weights, portfolio_amount)
# Use vlookup to get latest prices
latest_prices = pd.DataFrame({'Ticker': tickers})
latest_prices['Latest Price'] = latest_prices['Ticker'].apply(lambda x: data[x].iloc[-1])
# Divide allocation by latest prices
latest_prices.set_index('Ticker', inplace=True)
allocation_df = pd.DataFrame({'Ticker': tickers, 'Allocation': allocation})
allocation_df.set_index('Ticker', inplace=True)
allocation_df['Number of shares to buy'] = round(allocation_df['Allocation'] / latest_prices['Latest Price'],0)
allocation_df.reset_index(inplace=True)
# Create portfolio_df and print allocation for stocks with weight >= 1
portfolio_df = allocation_df[['Ticker', 'Number of shares to buy']]
high_allocation_df = portfolio_df[portfolio_df['Number of shares to buy'] >= 1]
if not high_allocation_df.empty:
print('Number of shares to buy with the amount of ₹', portfolio_amount)
print(high_allocation_df)
# Calculate remaining funds and print
remaining_funds = portfolio_amount - np.dot(latest_prices['Latest Price'], allocation_df['Number of shares to buy'])
print('Funds remaining: ₹', round(remaining_funds, 2))
# Filter out values less than 1
filtered_portfolio = portfolio_df[portfolio_df['Number of shares to buy'] >= 1]
# Plot pie chart
plt.pie(filtered_portfolio['Number of shares to buy'], labels=filtered_portfolio['Ticker'], autopct='%1.1f%%')
plt.title('Portfolio Allocation')
plt.show()
This section of the code is responsible for creating the final portfolio allocation. It starts by defining the amount of money to invest in the portfolio. Then it gets the latest prices for all the stocks in the portfolio, and uses the cleaned weights calculated earlier to calculate the number of shares to buy for each stock. The DiscreteAllocation
function from the pypfopt.discrete_allocation
module is used for this purpose. It takes in the weights, latest prices, and total portfolio value as inputs, and calculates the number of shares to buy for each stock to get as close as possible to the desired total portfolio value. The output of the lp_portfolio
method of the DiscreteAllocation
object is a tuple containing a dictionary with the stock ticker symbols as keys and the number of shares to buy as values, and the amount of leftover funds after buying all the shares.
The code then creates a pandas DataFrame to display the final portfolio allocation and prints it to the console along with the amount of leftover funds.
You must log in to post a comment.