Mathew K Analytics

Lesson 12 · Real-World Data Analytics

Python Data Analytics #12: Portfolio Returns, Risk & the Efficient Frontier in Python

Video twelve of the hundred-video real-world data analytics series. Building a real two-asset portfolio from the real same Apple and Tesla data cleaned last…

What you'll learn

Datasets used in this lesson

Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.

📓 Full notebook

Download .ipynb

Data Analytics 100, Video 12: Portfolio Returns, Risk, and the Efficient Frontier#

  • Video twelve of the hundred-video real-world data analytics series.
  • Building a real two-asset portfolio from the real same Apple and Tesla data cleaned last video, and tracing out its real efficient frontier.
  • Let's get into it.

Part 1: Reusing the Real Cleaned Data#

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
merged = pd.read_csv('aapl_tsla_merged.csv', parse_dates=['Date'])
returns = merged[['AAPL_Return', 'TSLA_Return']].dropna()
returns.shape[0]
337

Part 2: Real Individual Asset Statistics#

mean_aapl = returns['AAPL_Return'].mean()
mean_tsla = returns['TSLA_Return'].mean()
std_aapl = returns['AAPL_Return'].std()
std_tsla = returns['TSLA_Return'].std()
round(mean_aapl, 5), round(mean_tsla, 5), round(std_aapl, 5), round(std_tsla, 5)
(np.float64(0.00067),
 np.float64(0.00088),
 np.float64(0.01477),
 np.float64(0.02447))

Part 3: Real Annualized Statistics#

annual_return_aapl = mean_aapl * 252
annual_return_tsla = mean_tsla * 252
annual_vol_aapl = std_aapl * np.sqrt(252)
annual_vol_tsla = std_tsla * np.sqrt(252)
round(annual_return_aapl * 100, 1), round(annual_return_tsla * 100, 1), round(annual_vol_aapl * 100, 1), round(annual_vol_tsla * 100, 1)
(np.float64(17.0), np.float64(22.1), np.float64(23.5), np.float64(38.8))

Part 4: Real Covariance and Correlation#

covariance = returns.cov().loc['AAPL_Return', 'TSLA_Return']
correlation = returns.corr().loc['AAPL_Return', 'TSLA_Return']
round(covariance, 6), round(correlation, 3)
(np.float64(7.8e-05), np.float64(0.217))

Part 5: Real Portfolio Return Formula#

def portfolio_return(w_aapl, r_aapl=mean_aapl, r_tsla=mean_tsla):
    return w_aapl * r_aapl + (1 - w_aapl) * r_tsla
round(portfolio_return(0.5), 5)
np.float64(0.00078)

Part 6: Real Portfolio Variance Formula#

def portfolio_variance(w_aapl, s_aapl=std_aapl, s_tsla=std_tsla, cov=covariance):
    w_tsla = 1 - w_aapl
    return (w_aapl ** 2) * s_aapl ** 2 + (w_tsla ** 2) * s_tsla ** 2 + 2 * w_aapl * w_tsla * cov
round(portfolio_variance(0.5), 6)
np.float64(0.000243)

Part 7: Real Grid of Portfolio Weights#

weights = np.linspace(0, 1, 41)
len(weights)
41

Part 8: Computing Every Real Candidate Portfolio#

results = []
for w in weights:
    ann_ret = portfolio_return(w) * 252
    ann_vol = np.sqrt(portfolio_variance(w)) * np.sqrt(252)
    results.append((w, ann_ret, ann_vol))
frontier = pd.DataFrame(results, columns=['w_aapl', 'AnnualReturn', 'AnnualVol'])
frontier.head(3).round(4)
w_aapl AnnualReturn AnnualVol
0 0.000 0.2213 0.3884
1 0.025 0.2201 0.3800
2 0.050 0.2188 0.3717

Part 9: Real Minimum-Variance Portfolio#

min_var_row = frontier.loc[frontier['AnnualVol'].idxmin()]
round(min_var_row['w_aapl'], 2), round(min_var_row['AnnualReturn'] * 100, 1), round(min_var_row['AnnualVol'] * 100, 1)
(np.float64(0.8), np.float64(18.0), np.float64(21.8))

Part 10: Real Sharpe Ratio#

frontier['Sharpe'] = frontier['AnnualReturn'] / frontier['AnnualVol']
frontier[['w_aapl', 'Sharpe']].head(3).round(3)
w_aapl Sharpe
0 0.000 0.570
1 0.025 0.579
2 0.050 0.589

Part 11: Real Maximum-Sharpe Portfolio#

max_sharpe_row = frontier.loc[frontier['Sharpe'].idxmax()]
round(max_sharpe_row['w_aapl'], 2), round(max_sharpe_row['AnnualReturn'] * 100, 1), round(max_sharpe_row['AnnualVol'] * 100, 1), round(max_sharpe_row['Sharpe'], 3)
(np.float64(0.7), np.float64(18.5), np.float64(22.1), np.float64(0.839))

Part 12: Visualizing the Real Efficient Frontier#

plt.figure(figsize=(9, 7))
plt.plot(frontier['AnnualVol'], frontier['AnnualReturn'], color='steelblue')
plt.scatter([annual_vol_aapl], [annual_return_aapl], color='green', s=100, label='Real 100% Apple', zorder=5)
plt.scatter([annual_vol_tsla], [annual_return_tsla], color='red', s=100, label='Real 100% Tesla', zorder=5)
plt.scatter([min_var_row['AnnualVol']], [min_var_row['AnnualReturn']], color='black', s=120, marker='*', label='Real min-variance portfolio', zorder=5)
plt.scatter([max_sharpe_row['AnnualVol']], [max_sharpe_row['AnnualReturn']], color='gold', s=120, marker='*', edgecolor='black', label='Real max-Sharpe portfolio', zorder=5)
plt.xlabel('Real Annualized Volatility')
plt.ylabel('Real Annualized Return')
plt.title('Real Efficient Frontier, Apple and Tesla')
plt.legend()
plt.tight_layout()
plt.savefig('efficient_frontier.png', dpi=120)
plt.close()

Part 13: Real Diversification Benefit#

naive_avg_vol = min_var_row['w_aapl'] * annual_vol_aapl + (1 - min_var_row['w_aapl']) * annual_vol_tsla
actual_vol = min_var_row['AnnualVol']
round((naive_avg_vol - actual_vol) * 100, 2)
np.float64(4.72)

Part 14: Real Equal-Weight Portfolio#

equal_weight_row = frontier.iloc[(frontier['w_aapl'] - 0.5).abs().idxmin()]
round(equal_weight_row['AnnualReturn'] * 100, 1), round(equal_weight_row['AnnualVol'] * 100, 1)
(np.float64(19.6), np.float64(24.8))

Part 15: Real Backtest, Three Portfolios Over Time#

min_var_w = min_var_row['w_aapl']
returns['MinVarPort'] = min_var_w * returns['AAPL_Return'] + (1 - min_var_w) * returns['TSLA_Return']
returns['MinVarCumulative'] = (1 + returns['MinVarPort']).cumprod()
returns['AAPLCumulative'] = (1 + returns['AAPL_Return']).cumprod()
returns['TSLACumulative'] = (1 + returns['TSLA_Return']).cumprod()
round(returns['MinVarCumulative'].iloc[-1], 3), round(returns['AAPLCumulative'].iloc[-1], 3), round(returns['TSLACumulative'].iloc[-1], 3)
(np.float64(1.233), np.float64(1.21), np.float64(1.215))

Part 16: Visualizing the Real Backtest#

plt.figure(figsize=(10, 6))
plt.plot(merged['Date'].iloc[1:], returns['MinVarCumulative'], label='Real Min-Variance Portfolio')
plt.plot(merged['Date'].iloc[1:], returns['AAPLCumulative'], label='Real 100% Apple')
plt.plot(merged['Date'].iloc[1:], returns['TSLACumulative'], label='Real 100% Tesla')
plt.xlabel('Real Date')
plt.ylabel('Real Growth of $1 Invested')
plt.title('Real Backtest, Three Portfolio Strategies')
plt.legend()
plt.tight_layout()
plt.savefig('portfolio_backtest.png', dpi=120)
plt.close()

Part 17: Real Sanity Check on the Variance Formula#

manual_var_full_aapl = portfolio_variance(1.0)
abs(manual_var_full_aapl - std_aapl ** 2) < 1e-10
np.True_

Part 18: Saving the Real Frontier Table#

frontier.round(5).to_csv('efficient_frontier_table.csv', index=False)
reloaded = pd.read_csv('efficient_frontier_table.csv')
reloaded.shape[0] == frontier.shape[0]
True

Part 19: Real Recap Print#

print(f'Minimum-variance portfolio: {round(min_var_row["w_aapl"]*100)}% Apple, {round((1-min_var_row["w_aapl"])*100)}% Tesla, {round(min_var_row["AnnualVol"]*100,1)}% annual volatility.')
Minimum-variance portfolio: 80% Apple, 20% Tesla, 21.8% annual volatility.

Part 20: Real Rolling Correlation Over Time#

rolling_corr = returns['AAPL_Return'].rolling(30).corr(returns['TSLA_Return'])
rolling_corr.dropna().describe().round(3)
count    308.000
mean       0.210
std        0.160
min       -0.259
25%        0.149
50%        0.242
75%        0.300
max        0.476
dtype: float64

Part 21: Visualizing Real Rolling Correlation#

plt.figure(figsize=(10, 5))
plt.plot(merged['Date'].iloc[1:], rolling_corr, color='purple')
plt.axhline(correlation, color='gray', linestyle='--', label='Real full-period correlation')
plt.xlabel('Real Date')
plt.ylabel('Real 30-Day Rolling Correlation')
plt.title('Real Rolling Correlation, Apple and Tesla')
plt.legend()
plt.tight_layout()
plt.savefig('rolling_correlation.png', dpi=120)
plt.close()

Part 22: Real Sanity Check at Full Tesla Weight#

manual_var_full_tsla = portfolio_variance(0.0)
abs(manual_var_full_tsla - std_tsla ** 2) < 1e-10
np.True_

Part 23: What If These Two Stocks Were Uncorrelated#

hypothetical_var = (0.5 ** 2) * std_aapl ** 2 + (0.5 ** 2) * std_tsla ** 2
actual_var_50 = portfolio_variance(0.5)
round(np.sqrt(hypothetical_var) * np.sqrt(252) * 100, 2), round(np.sqrt(actual_var_50) * np.sqrt(252) * 100, 2)
(np.float64(22.69), np.float64(24.77))

Part 24: Real Total Return Comparison Over the Full Window#

total_return_aapl = returns['AAPLCumulative'].iloc[-1] - 1
total_return_tsla = returns['TSLACumulative'].iloc[-1] - 1
round(total_return_aapl * 100, 1), round(total_return_tsla * 100, 1)
(np.float64(21.0), np.float64(21.5))

Part 25: Real Days Each Stock Outperformed the Other#

aapl_wins = (returns['AAPL_Return'] > returns['TSLA_Return']).sum()
tsla_wins = (returns['TSLA_Return'] > returns['AAPL_Return']).sum()
aapl_wins, tsla_wins
(np.int64(164), np.int64(172))

Part 26: Real Downside-Only Risk, the Sortino Idea#

downside_aapl = returns['AAPL_Return'][returns['AAPL_Return'] < 0].std()
downside_tsla = returns['TSLA_Return'][returns['TSLA_Return'] < 0].std()
round(downside_aapl * np.sqrt(252) * 100, 1), round(downside_tsla * np.sqrt(252) * 100, 1)
(np.float64(16.9), np.float64(28.0))

Part 27: Real Min-Variance vs Real Max-Sharpe Side by Side#

comparison = pd.DataFrame({'Portfolio': ['Min-Variance', 'Max-Sharpe'], 'AppleWeight': [min_var_row['w_aapl'], max_sharpe_row['w_aapl']], 'AnnualReturn': [min_var_row['AnnualReturn'], max_sharpe_row['AnnualReturn']], 'AnnualVol': [min_var_row['AnnualVol'], max_sharpe_row['AnnualVol']]})
comparison.round(3)
Portfolio AppleWeight AnnualReturn AnnualVol
0 Min-Variance 0.8 0.180 0.218
1 Max-Sharpe 0.7 0.185 0.221

Part 28: Real Range of Achievable Portfolio Returns#

frontier['AnnualReturn'].min(), frontier['AnnualReturn'].max()
(np.float64(0.16989436201780417), np.float64(0.2213412462908012))

Part 29: Real Volatility Reduction from Min-Variance vs Naive 50/50#

vol_50_50 = frontier.loc[(frontier['w_aapl'] - 0.5).abs().idxmin(), 'AnnualVol']
round((vol_50_50 - min_var_row['AnnualVol']) * 100, 2)
np.float64(2.96)

Part 30: Real Final Recap Numbers#

print(f'Tested {len(frontier)} real candidate portfolios; correlation between Apple and Tesla over this window was {round(correlation, 2)}.')
Tested 41 real candidate portfolios; correlation between Apple and Tesla over this window was 0.22.

Wrap-Up: What You Learned#

  • Portfolio return is genuinely just a weighted average of the real individual asset returns.
  • Portfolio variance is not a plain weighted average; the real covariance term is exactly what captures diversification benefit.
  • Sweeping a real grid of weights traces out the real efficient frontier, every achievable risk-return combination between two assets.
  • The real minimum-variance portfolio and the real maximum-Sharpe portfolio are two different, both genuinely useful, optimal points on that real frontier.
  • A real backtest turns the theory concrete: tracking how one real dollar would have actually grown under each real strategy.
  • Next video: real technical indicators, moving averages and momentum signals computed directly on these real price series.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.