CH
CalcHub
Back to Guides
Intermediate

Building a Portfolio Analyser in Python

A single stock is a bet. A portfolio is a strategy. This guide shows you how to analyse a portfolio of stocks β€” calculate weighted returns, measure risk, and find the optimal mix using Python.

Download Multiple Stocks

import yfinance as yf import pandas as pd import numpy as np import matplotlib.pyplot as plt # Our portfolio: 4 stocks tickers = ["AAPL", "MSFT", "JNJ", "JPM"] data = yf.download(tickers, start="2022-01-01", end="2024-01-01")["Close"] # Daily returns returns = data.pct_change().dropna() print(returns.describe())

Weighted Portfolio Returns

In a real portfolio, you don't own equal amounts of everything. Weights represent what percentage of your money is in each stock.

# Portfolio weights (must sum to 1.0) weights = np.array([0.30, 0.30, 0.20, 0.20])  # 30% AAPL, 30% MSFT, 20% JNJ, 20% JPM # Daily portfolio return = sum of (weight * stock return) portfolio_daily = returns.dot(weights) # Cumulative portfolio value starting at $10,000 initial = 10000 portfolio_value = initial * (1 + portfolio_daily).cumprod() plt.figure(figsize=(12, 5)) plt.plot(portfolio_value.index, portfolio_value, color="mediumseagreen", linewidth=1.5) plt.title("Portfolio Value Over Time") plt.xlabel("Date") plt.ylabel("Value ($)") plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() print(f"Final value: ${{portfolio_value.iloc[-1]:,.2f}}") print(f"Total return: {{(portfolio_value.iloc[-1] / initial - 1):.2%}}")

Correlation Matrix

Correlation tells you which stocks move together. If all your stocks are highly correlated, you're not actually diversified β€” they'll all drop at the same time.

# Correlation matrix corr = returns.corr() print(corr.round(2)) # A correlation of 1.0 = move in perfect lockstep # A correlation of 0.0 = no relationship # A correlation of -1.0 = move in opposite directions # AAPL and MSFT will be highly correlated (both big tech) # JNJ (healthcare) should be less correlated with tech

Diversification = Low Correlation

The whole point of diversification is owning assets that do not all move together. If stock A drops 5% but stock B goes up 3%, the portfolio cushions the blow. Correlation tells you how much cushion you actually have.

Portfolio Risk (Variance & Standard Deviation)

# Annualised portfolio return annual_return = returns.mean().dot(weights) * 252  # 252 trading days # Portfolio variance (accounts for correlations between stocks) cov_matrix = returns.cov() * 252  # annualised covariance matrix portfolio_variance = weights.T.dot(cov_matrix).dot(weights) portfolio_std = np.sqrt(portfolio_variance) print(f"Expected annual return: {{annual_return:.2%}}") print(f"Annual volatility (risk): {{portfolio_std:.2%}}") print(f"Sharpe ratio: {{(annual_return - 0.05) / portfolio_std:.2f}}") # Risk-free rate assumed at 5% (typical for current environment)

Sharpe Ratio

Sharpe Ratio = Return Per Unit of Risk

Sharpe = (Portfolio Return - Risk Free Rate) / Portfolio Volatility. A Sharpe above 1.0 is good. Above 2.0 is excellent. It answers: am I being compensated enough for the risk I am taking?

Simple Efficient Frontier

The efficient frontier shows every possible portfolio you could build from these stocks, plotted by risk vs return. The β€œfrontier” is the top edge β€” the best return you can get for each level of risk.

# Generate 10,000 random portfolios num_portfolios = 10000 results = np.zeros((num_portfolios, 3))  # return, risk, sharpe for i in range(num_portfolios): # Random weights that sum to 1 w = np.random.random(len(tickers)) w /= w.sum() # Portfolio return and risk ret = returns.mean().dot(w) * 252 risk = np.sqrt(w.T.dot(cov_matrix).dot(w)) sharpe = (ret - 0.05) / risk results[i] = [ret, risk, sharpe] # Plot the efficient frontier plt.figure(figsize=(10, 6)) scatter = plt.scatter( results[:, 1], results[:, 0], c=results[:, 2], cmap="RdYlGn", s=3, alpha=0.5 ) plt.colorbar(scatter, label="Sharpe Ratio") plt.title("Efficient Frontier (10,000 Random Portfolios)") plt.xlabel("Risk (Volatility)") plt.ylabel("Expected Return") plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # Find the portfolio with the best Sharpe ratio best_idx = results[:, 2].argmax() print(f"Best Sharpe: {{results[best_idx, 2]:.2f}}") print(f"Return: {{results[best_idx, 0]:.2%}}, Risk: {{results[best_idx, 1]:.2%}}")