!pip install statsmodels
!pip install seaborn matplotlib
!pip install pandas
!pip install yfinance==0.2.28
!pip install numpy
!pip install PyPortfolioOpt
Question 1
import pandas as pd
import yfinance as yf
import numpy as np
from scipy.optimize import minimize
import scipy.stats as stats
# Load Fama French factors data with error handling
try:
fama_french_data = pd.read_csv('fama_french_factors.csv', parse_dates=True, index_col=0) / 100 # Convert percentages to decimals
except pd.errors.ParserError as e:
print(f"Error reading CSV file: {e}")
fama_french_data = pd.DataFrame() # Create an empty DataFrame as a placeholder
# Manually set the risk-free rate (you can replace this with actual data)
risk_free_rate = 0.02 # Replace with the appropriate risk-free rate (in decimal form)
# Define the stock symbols and dates
stock_symbols = ['XOM', 'FSLR', 'JNJ', 'AMZN']
start_date = '2018-09-06'
end_date = '2023-09-06'
# Download historical stock price data
data = yf.download(stock_symbols, start=start_date, end=end_date)
# Calculate daily returns
returns = data['Adj Close'].pct_change()
returns.dropna(inplace=True)
df=pd.DataFrame(returns)
# Calculate daily returns
returns = data['Adj Close'].pct_change()
returns.dropna(inplace=True)
# Define the objective function for Sharpe ratio maximization
def sharpe_ratio(weights, returns, risk_free_rate):
portfolio_returns = np.dot(returns, weights)
mean_return = np.mean(portfolio_returns)
portfolio_stddev = np.std(portfolio_returns)
sharpe = (mean_return - risk_free_rate) / portfolio_stddev
return -sharpe # Minimize the negative Sharpe ratio
# Set initial guess for portfolio weights
initial_weights = np.array([0.25, 0.25, 0.25, 0.25])
# Set optimization constraints (sum of weights equals 1)
constraints = ({'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1})
# Set optimization bounds (each weight is between 0 and 1)
bounds = tuple((0, 1) for asset in range(len(stock_symbols)))
# Run the optimization to find the maximum Sharpe portfolio
optimized_weights = minimize(sharpe_ratio, initial_weights, args=(returns, risk_free_rate),
method='SLSQP', bounds=bounds, constraints=constraints)
optimized_weights = optimized_weights.x
# Calculate the daily returns of the maximum Sharpe portfolio
portfolio_returns_max_sharpe = np.dot(returns, optimized_weights)
# Create a DataFrame with the portfolio returns
portfolio_returns_df = pd.DataFrame({'Portfolio Returns': portfolio_returns_max_sharpe},
index=returns.index)
# Merge the portfolio returns with the Fama French factors (if available)
if not fama_french_data.empty:
merged_data = pd.concat([portfolio_returns_df, fama_french_data], axis=1, join='inner')
else:
merged_data = portfolio_returns_df
# You can further analyze and work with the merged data as needed
# Calculate statistics like mean, standard deviation, skewness, kurtosis, etc.
mean_return = np.mean(merged_data['Portfolio Returns'])
volatility = np.std(merged_data['Portfolio Returns'])
skewness = stats.skew(merged_data['Portfolio Returns'])
kurtosis = stats.kurtosis(merged_data['Portfolio Returns'])
# Print the portfolio weights, statistics, and the merged data
print("Optimal Portfolio Weights:", optimized_weights)
print("Mean Return:", mean_return)
print("Volatility (Standard Deviation):", volatility)
print("Skewness:", skewness)
print("Kurtosis:", kurtosis)
print(merged_data.head())
Question 2
import seaborn as sns
import matplotlib.pyplot as plt
# Calculate the correlation matrix between portfolio and factor returns
correlation_matrix = merged_data.corr()
# Plot the correlation matrix as a heatmap
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title("Correlation Between Portfolio and Factor Returns")
plt.show()
Question 3
import statsmodels.api as sm
for factor in fama_french_data.columns:
X = sm.add_constant(merged_data[factor])
Y = merged_data['Portfolio Returns']
model = sm.OLS(Y, X).fit()
beta = model.params[factor]
alpha = model.params['const']
print(f"Factor: {factor}")
print(f"Beta: {beta}")
print(f"Alpha: {alpha}")
print(model.summary())
Question 4
# Define the independent variables (multiple factors)
X = merged_data[fama_french_data.columns]
X = sm.add_constant(X) # Add the intercept term
Y = merged_data['Portfolio Returns']
# Fit the multi-factor regression model
model = sm.OLS(Y, X).fit()
# Obtain the regression results
factor_betas = model.params[fama_french_data.columns] # Factor betas
alpha = model.params['const'] # Intercept (alpha)
print("Multi-Factor Regression Results:")
print("Factor Betas:")
print(factor_betas)
print("Alpha (Intercept):", alpha)
print(model.summary())