{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"execute": {
"enabled": false
}
},
"cells": [
{
"cell_type": "markdown",
"source": [
"## Notebook: Financial Analytics\n",
"*PADS - Programa Avançado em Data Science*\n",
"\n",
"Insper\n",
"\n",
"**Paloma Vaissman Uribe**"
],
"metadata": {
"id": "lUgLs1xYW9tJ"
}
},
{
"cell_type": "code",
"source": [
"import yfinance as yf\n",
"import numpy as np\n",
"from scipy.stats import linregress\n",
"import pandas as pd\n",
"from scipy.optimize import minimize"
],
"metadata": {
"id": "Wkl4cclXvUMd"
},
"execution_count": 23,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"We will start by picking up some data from Yahoo Finance APIs"
],
"metadata": {
"id": "3s94HTYNXKoc"
}
},
{
"cell_type": "code",
"source": [
"#getting historic stock data from yfinance\n",
"stocks_list = ['BBDC4.SA', 'ITUB4.SA','SANB4.SA', 'BBAS3.SA']\n",
"data = yf.download(stocks_list, period='5y')['Close']\n",
"data"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 509
},
"id": "Azb3AzsbXHn_",
"outputId": "7cc0b6e2-b701-43e8-98e2-1bb2b3d29279"
},
"execution_count": 7,
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"/tmp/ipython-input-2534288318.py:3: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
" data = yf.download(stocks_list, period='5y')['Close']\n",
"[*********************100%***********************] 4 of 4 completed\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Ticker BBAS3.SA BBDC4.SA ITUB4.SA SANB4.SA\n",
"Date \n",
"2020-11-24 11.897976 14.993035 20.721100 14.784431\n",
"2020-11-25 11.864409 14.799624 20.476990 15.099722\n",
"2020-11-26 11.676400 14.559301 20.044577 15.264225\n",
"2020-11-27 11.619330 14.442082 20.198013 15.181971\n",
"2020-11-30 11.367538 14.242801 19.919031 14.434868\n",
"... ... ... ... ...\n",
"2025-11-17 22.500000 19.320000 40.299999 17.270000\n",
"2025-11-18 21.879999 19.090000 40.099998 17.330000\n",
"2025-11-19 21.580000 18.900000 39.849998 17.129999\n",
"2025-11-21 22.000000 18.790001 39.970001 17.150000\n",
"2025-11-24 22.100000 18.809999 39.860001 17.250000\n",
"\n",
"[1247 rows x 4 columns]"
],
"text/html": [
"\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | Ticker | \n",
" BBAS3.SA | \n",
" BBDC4.SA | \n",
" ITUB4.SA | \n",
" SANB4.SA | \n",
"
\n",
" \n",
" | Date | \n",
" | \n",
" | \n",
" | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" | 2020-11-24 | \n",
" 11.897976 | \n",
" 14.993035 | \n",
" 20.721100 | \n",
" 14.784431 | \n",
"
\n",
" \n",
" | 2020-11-25 | \n",
" 11.864409 | \n",
" 14.799624 | \n",
" 20.476990 | \n",
" 15.099722 | \n",
"
\n",
" \n",
" | 2020-11-26 | \n",
" 11.676400 | \n",
" 14.559301 | \n",
" 20.044577 | \n",
" 15.264225 | \n",
"
\n",
" \n",
" | 2020-11-27 | \n",
" 11.619330 | \n",
" 14.442082 | \n",
" 20.198013 | \n",
" 15.181971 | \n",
"
\n",
" \n",
" | 2020-11-30 | \n",
" 11.367538 | \n",
" 14.242801 | \n",
" 19.919031 | \n",
" 14.434868 | \n",
"
\n",
" \n",
" | ... | \n",
" ... | \n",
" ... | \n",
" ... | \n",
" ... | \n",
"
\n",
" \n",
" | 2025-11-17 | \n",
" 22.500000 | \n",
" 19.320000 | \n",
" 40.299999 | \n",
" 17.270000 | \n",
"
\n",
" \n",
" | 2025-11-18 | \n",
" 21.879999 | \n",
" 19.090000 | \n",
" 40.099998 | \n",
" 17.330000 | \n",
"
\n",
" \n",
" | 2025-11-19 | \n",
" 21.580000 | \n",
" 18.900000 | \n",
" 39.849998 | \n",
" 17.129999 | \n",
"
\n",
" \n",
" | 2025-11-21 | \n",
" 22.000000 | \n",
" 18.790001 | \n",
" 39.970001 | \n",
" 17.150000 | \n",
"
\n",
" \n",
" | 2025-11-24 | \n",
" 22.100000 | \n",
" 18.809999 | \n",
" 39.860001 | \n",
" 17.250000 | \n",
"
\n",
" \n",
"
\n",
"
1247 rows × 4 columns
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"variable_name": "data",
"summary": "{\n \"name\": \"data\",\n \"rows\": 1247,\n \"fields\": [\n {\n \"column\": \"Date\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2020-11-24 00:00:00\",\n \"max\": \"2025-11-24 00:00:00\",\n \"num_unique_values\": 1247,\n \"samples\": [\n \"2023-08-15 00:00:00\",\n \"2025-06-16 00:00:00\",\n \"2024-07-05 00:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"BBAS3.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5.789805887300614,\n \"min\": 9.525737762451172,\n \"max\": 29.217079162597656,\n \"num_unique_values\": 1154,\n \"samples\": [\n 25.59922981262207,\n 13.872206687927246,\n 25.231386184692383\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"BBDC4.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2.013164669891024,\n \"min\": 10.105661392211914,\n \"max\": 19.489999771118164,\n \"num_unique_values\": 1167,\n \"samples\": [\n 11.485569953918457,\n 16.5145206451416,\n 14.018441200256348\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ITUB4.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 6.286357978287929,\n \"min\": 15.170910835266113,\n \"max\": 41.220001220703125,\n \"num_unique_values\": 1192,\n \"samples\": [\n 29.621286392211914,\n 15.61988353729248,\n 15.589557647705078\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"SANB4.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1.117841217112526,\n \"min\": 11.342205047607422,\n \"max\": 17.5,\n \"num_unique_values\": 1067,\n \"samples\": [\n 12.959424018859863,\n 14.298778533935547,\n 15.04734992980957\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {},
"execution_count": 7
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "09daae80"
},
"source": [
"Calculate the daily percentage returns for each stock using the `.pct_change()` method on the `data` DataFrame and store the result in a new DataFrame called `daily_returns`.\n",
"\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 238
},
"id": "e5ff7624",
"outputId": "7e737417-4382-4169-a5bd-adb3b94762fe"
},
"source": [
"daily_returns = data.pct_change()\n",
"daily_returns.head()"
],
"execution_count": 8,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Ticker BBAS3.SA BBDC4.SA ITUB4.SA SANB4.SA\n",
"Date \n",
"2020-11-24 NaN NaN NaN NaN\n",
"2020-11-25 -0.002821 -0.012900 -0.011781 0.021326\n",
"2020-11-26 -0.015846 -0.016238 -0.021117 0.010894\n",
"2020-11-27 -0.004888 -0.008051 0.007655 -0.005389\n",
"2020-11-30 -0.021670 -0.013799 -0.013812 -0.049210"
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | Ticker | \n",
" BBAS3.SA | \n",
" BBDC4.SA | \n",
" ITUB4.SA | \n",
" SANB4.SA | \n",
"
\n",
" \n",
" | Date | \n",
" | \n",
" | \n",
" | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" | 2020-11-24 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
" | 2020-11-25 | \n",
" -0.002821 | \n",
" -0.012900 | \n",
" -0.011781 | \n",
" 0.021326 | \n",
"
\n",
" \n",
" | 2020-11-26 | \n",
" -0.015846 | \n",
" -0.016238 | \n",
" -0.021117 | \n",
" 0.010894 | \n",
"
\n",
" \n",
" | 2020-11-27 | \n",
" -0.004888 | \n",
" -0.008051 | \n",
" 0.007655 | \n",
" -0.005389 | \n",
"
\n",
" \n",
" | 2020-11-30 | \n",
" -0.021670 | \n",
" -0.013799 | \n",
" -0.013812 | \n",
" -0.049210 | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"variable_name": "daily_returns",
"summary": "{\n \"name\": \"daily_returns\",\n \"rows\": 1247,\n \"fields\": [\n {\n \"column\": \"Date\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2020-11-24 00:00:00\",\n \"max\": \"2025-11-24 00:00:00\",\n \"num_unique_values\": 1247,\n \"samples\": [\n \"2023-08-15 00:00:00\",\n \"2025-06-16 00:00:00\",\n \"2024-07-05 00:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"BBAS3.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.017248877670399024,\n \"min\": -0.1268707051666298,\n \"max\": 0.07632371362239843,\n \"num_unique_values\": 1233,\n \"samples\": [\n 0.025371813290653522,\n 0.023399037822915414,\n 0.001036928498128642\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"BBDC4.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.018936169276738024,\n \"min\": -0.173842816643902,\n \"max\": 0.1564417629190129,\n \"num_unique_values\": 1230,\n \"samples\": [\n 0.00914638370009091,\n 0.03378895519299441,\n -0.03996254325010218\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ITUB4.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.016065904865480523,\n \"min\": -0.17964312107233382,\n \"max\": 0.0826879208453708,\n \"num_unique_values\": 1231,\n \"samples\": [\n -0.018601238960503808,\n 0.0018517894083580622,\n -0.01892533356431858\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"SANB4.SA\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.017008531407023703,\n \"min\": -0.0788442899265086,\n \"max\": 0.11633426831930316,\n \"num_unique_values\": 1216,\n \"samples\": [\n -0.006527378704842213,\n -0.002359931431251572,\n 0.035996539200348066\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {},
"execution_count": 8
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "45c28b20"
},
"source": [
"## Define equal weights and calculate portfolio returns\n",
"\n",
"Create a vector of equal weights for the stocks and use it to calculate the daily returns of the portfolio.\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 272
},
"id": "63f8c390",
"outputId": "4cdfa206-d9fc-441a-ae87-3e960df12747"
},
"source": [
"num_stocks = len(daily_returns.columns)\n",
"weights = np.array([1/num_stocks] * num_stocks)\n",
"\n",
"portfolio_returns = (daily_returns * weights).sum(axis=1)\n",
"portfolio_returns.head()"
],
"execution_count": 9,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Date\n",
"2020-11-24 0.000000\n",
"2020-11-25 -0.001544\n",
"2020-11-26 -0.010577\n",
"2020-11-27 -0.002668\n",
"2020-11-30 -0.024623\n",
"dtype: float64"
],
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" 0 | \n",
"
\n",
" \n",
" | Date | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" | 2020-11-24 | \n",
" 0.000000 | \n",
"
\n",
" \n",
" | 2020-11-25 | \n",
" -0.001544 | \n",
"
\n",
" \n",
" | 2020-11-26 | \n",
" -0.010577 | \n",
"
\n",
" \n",
" | 2020-11-27 | \n",
" -0.002668 | \n",
"
\n",
" \n",
" | 2020-11-30 | \n",
" -0.024623 | \n",
"
\n",
" \n",
"
\n",
"
"
]
},
"metadata": {},
"execution_count": 9
}
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "c33922b5",
"outputId": "fb7a1396-5cea-445d-e49c-98dfc1cca8c6"
},
"source": [
"weights"
],
"execution_count": 10,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"array([0.25, 0.25, 0.25, 0.25])"
]
},
"metadata": {},
"execution_count": 10
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "57482436"
},
"source": [
"## Calculate Cumulative Portfolio Returns\n",
"\n",
"Compute the cumulative returns of the portfolio using the `portfolio_returns` DataFrame.\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 272
},
"id": "cfa12191",
"outputId": "56d88524-33bc-4ccb-e252-887f988aa98d"
},
"source": [
"cumulative_returns = (1 + portfolio_returns).cumprod() - 1\n",
"cumulative_returns.head()"
],
"execution_count": 11,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Date\n",
"2020-11-24 0.000000\n",
"2020-11-25 -0.001544\n",
"2020-11-26 -0.012105\n",
"2020-11-27 -0.014740\n",
"2020-11-30 -0.039000\n",
"dtype: float64"
],
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" 0 | \n",
"
\n",
" \n",
" | Date | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" | 2020-11-24 | \n",
" 0.000000 | \n",
"
\n",
" \n",
" | 2020-11-25 | \n",
" -0.001544 | \n",
"
\n",
" \n",
" | 2020-11-26 | \n",
" -0.012105 | \n",
"
\n",
" \n",
" | 2020-11-27 | \n",
" -0.014740 | \n",
"
\n",
" \n",
" | 2020-11-30 | \n",
" -0.039000 | \n",
"
\n",
" \n",
"
\n",
"
"
]
},
"metadata": {},
"execution_count": 11
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "30f6a91a"
},
"source": [
"## Obtain Market Data and Risk-Free Rate\n",
"\n",
"Fetch historical data for the Ibovespa index (market data) and define a suitable risk-free rate for the corresponding period. Ensure the data is aligned by date with the portfolio returns.\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 291
},
"id": "849a06d2",
"outputId": "976cb285-2a78-4d50-a362-7c114edf6931"
},
"source": [
"ibovespa_data = yf.download('^BVSP', period='5y')['Close']\n",
"ibovespa_daily_returns = ibovespa_data.pct_change()\n",
"ibovespa_daily_returns.head()"
],
"execution_count": 12,
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"/tmp/ipython-input-1883129585.py:1: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
" ibovespa_data = yf.download('^BVSP', period='5y')['Close']\n",
"[*********************100%***********************] 1 of 1 completed\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
"Ticker ^BVSP\n",
"Date \n",
"2020-11-24 NaN\n",
"2020-11-25 0.003161\n",
"2020-11-26 0.000854\n",
"2020-11-27 0.003157\n",
"2020-11-30 -0.015257"
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | Ticker | \n",
" ^BVSP | \n",
"
\n",
" \n",
" | Date | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" | 2020-11-24 | \n",
" NaN | \n",
"
\n",
" \n",
" | 2020-11-25 | \n",
" 0.003161 | \n",
"
\n",
" \n",
" | 2020-11-26 | \n",
" 0.000854 | \n",
"
\n",
" \n",
" | 2020-11-27 | \n",
" 0.003157 | \n",
"
\n",
" \n",
" | 2020-11-30 | \n",
" -0.015257 | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"variable_name": "ibovespa_daily_returns",
"summary": "{\n \"name\": \"ibovespa_daily_returns\",\n \"rows\": 1247,\n \"fields\": [\n {\n \"column\": \"Date\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2020-11-24 00:00:00\",\n \"max\": \"2025-11-24 00:00:00\",\n \"num_unique_values\": 1247,\n \"samples\": [\n \"2023-08-15 00:00:00\",\n \"2025-06-16 00:00:00\",\n \"2024-07-05 00:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"^BVSP\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.011121357385243894,\n \"min\": -0.05120086233031296,\n \"max\": 0.05540863527722495,\n \"num_unique_values\": 1246,\n \"samples\": [\n -0.0007304847227936895,\n -0.021737386700890315,\n 0.002263756675179973\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {},
"execution_count": 12
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "89952938"
},
"source": [
"Define the annual and daily risk-free rates and align the portfolio and Ibovespa daily returns to ensure they cover the same dates for subsequent calculations.\n",
"\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "34a097f5",
"outputId": "8667face-7daa-4ffd-9c85-2b7697052a07"
},
"source": [
"annual_risk_free_rate = 0.12 # change as you wish\n",
"trading_days_per_year = 252\n",
"daily_risk_free_rate = annual_risk_free_rate / trading_days_per_year\n",
"\n",
"# Align data by dropping NaNs and ensuring common dates\n",
"# First, drop any initial NaNs from pct_change\n",
"portfolio_returns_cleaned = portfolio_returns.dropna()\n",
"ibovespa_daily_returns_cleaned = ibovespa_daily_returns.dropna()\n",
"\n",
"# Ensure both series have the exact same dates after dropping NaNs\n",
"common_index = portfolio_returns_cleaned.index.intersection(ibovespa_daily_returns_cleaned.index)\n",
"portfolio_returns = portfolio_returns_cleaned.reindex(common_index)\n",
"ibovespa_daily_returns = ibovespa_daily_returns_cleaned.reindex(common_index)\n",
"\n",
"print(f\"Annual Risk-Free Rate: {annual_risk_free_rate:.2%}\")\n",
"print(f\"Daily Risk-Free Rate: {daily_risk_free_rate:.4%}\")\n",
"print(\"\\nAligned portfolio_returns head:\")\n",
"print(portfolio_returns.head())\n",
"print(\"\\nAligned ibovespa_daily_returns head:\")\n",
"print(ibovespa_daily_returns.head())\n",
"print(f\"\\nLengths after alignment: Portfolio returns = {len(portfolio_returns)}, Ibovespa daily returns = {len(ibovespa_daily_returns)}\")"
],
"execution_count": 13,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Annual Risk-Free Rate: 12.00%\n",
"Daily Risk-Free Rate: 0.0476%\n",
"\n",
"Aligned portfolio_returns head:\n",
"Date\n",
"2020-11-25 -0.001544\n",
"2020-11-26 -0.010577\n",
"2020-11-27 -0.002668\n",
"2020-11-30 -0.024623\n",
"2020-12-01 0.060496\n",
"dtype: float64\n",
"\n",
"Aligned ibovespa_daily_returns head:\n",
"Ticker ^BVSP\n",
"Date \n",
"2020-11-25 0.003161\n",
"2020-11-26 0.000854\n",
"2020-11-27 0.003157\n",
"2020-11-30 -0.015257\n",
"2020-12-01 0.022473\n",
"\n",
"Lengths after alignment: Portfolio returns = 1246, Ibovespa daily returns = 1246\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "edd912b0"
},
"source": [
"## Calculate Portfolio Volatility\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "50fe1a52"
},
"source": [
"To calculate the portfolio historical volatility, I will first compute the daily standard deviation of the `portfolio_returns` Series. Then, I will annualize this daily volatility by multiplying it by the square root of `trading_days_per_year`.\n",
"\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "3b05b046",
"outputId": "548a58ee-305c-4fcf-8a56-a35f15cd8cbc"
},
"source": [
"portfolio_volatility_daily = portfolio_returns.std()\n",
"portfolio_volatility_annualized = portfolio_volatility_daily * np.sqrt(trading_days_per_year)\n",
"\n",
"print(f\"Annualized Portfolio Volatility: {portfolio_volatility_annualized:.2%}\")"
],
"execution_count": 14,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Annualized Portfolio Volatility: 22.92%\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "429eaa71"
},
"source": [
"## Calculate Sharpe Ratio\n",
"\n",
"Compute the Sharpe Ratio using the portfolio's average daily return, the risk-free rate, and the portfolio's volatility. Annualize the Sharpe Ratio.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c5c99bdd"
},
"source": [
"**Reasoning**:\n",
"To calculate the Sharpe Ratio, I will first calculate the average daily portfolio return, then compute the daily excess return by subtracting the daily risk-free rate. After that, I'll calculate the daily Sharpe Ratio and finally annualize it and print the result.\n",
"\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "3e088cdc",
"outputId": "cce905c9-97ae-487e-81c5-015db6a84e85"
},
"source": [
"average_daily_portfolio_return = portfolio_returns.mean()\n",
"daily_excess_return = average_daily_portfolio_return - daily_risk_free_rate\n",
"daily_sharpe_ratio = daily_excess_return / portfolio_volatility_daily\n",
"annualized_sharpe_ratio = daily_sharpe_ratio * np.sqrt(trading_days_per_year)\n",
"\n",
"print(f\"Annualized Sharpe Ratio: {annualized_sharpe_ratio:.2f}\")"
],
"execution_count": 15,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Annualized Sharpe Ratio: 0.01\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "2c71104e"
},
"source": [
"## Calculate CAPM Beta\n",
"\n",
"### Subtask:\n",
"Perform a linear regression of the portfolio's daily returns against the daily returns of the Ibovespa index to calculate the Beta coefficient.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4971f263"
},
"source": [
"**Reasoning**:\n",
"The previous code failed because 'pd' was not defined. I need to import the pandas library to use `pd.DataFrame` for type checking. Also, based on the kernel state, `ibovespa_daily_returns` is already a pandas DataFrame, so I will ensure that it is correctly accessed as a Series for the `linregress` function.\n",
"\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "c53a7371",
"outputId": "254e3c6e-abdc-4988-d25f-a85ef750f7c5"
},
"source": [
"# Perform linear regression\n",
"# Ensure ibovespa_daily_returns is a 1D array/series for linregress\n",
"# If it's a DataFrame with a single column, access the column\n",
"if isinstance(ibovespa_daily_returns, pd.DataFrame):\n",
" market_returns = ibovespa_daily_returns.iloc[:, 0] # Get the first (and likely only) column\n",
"else:\n",
" market_returns = ibovespa_daily_returns\n",
"\n",
"slope, intercept, r_value, p_value, std_err = linregress(market_returns, portfolio_returns)\n",
"\n",
"beta = slope\n",
"\n",
"print(f\"CAPM Beta: {beta:.2f}\")"
],
"execution_count": 17,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"CAPM Beta: 0.99\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b07fa3a9"
},
"source": [
"## Optimization\n",
"Define a Python function `portfolio_performance` that takes portfolio weights as input and calculates the annualized portfolio volatility and annualized Sharpe Ratio, using the `daily_returns`, `trading_days_per_year`, and `daily_risk_free_rate` variables from the current kernel state. The function should return both these metrics."
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "24abda87",
"outputId": "a57bfb77-fa5c-4ae6-cdbe-ea70a6005b68"
},
"source": [
"def portfolio_performance(weights):\n",
" # 2. Calculate portfolio_daily_returns\n",
" portfolio_daily_returns = (daily_returns * weights).sum(axis=1)\n",
"\n",
" # 3. Calculate daily standard deviation\n",
" portfolio_volatility_daily = portfolio_daily_returns.std()\n",
"\n",
" # 4. Annualize the portfolio volatility\n",
" annualized_volatility = portfolio_volatility_daily * np.sqrt(trading_days_per_year)\n",
"\n",
" # 5. Calculate average daily portfolio return\n",
" average_daily_portfolio_return = portfolio_daily_returns.mean()\n",
"\n",
" # 6. Compute daily excess return\n",
" daily_excess_return = average_daily_portfolio_return - daily_risk_free_rate\n",
"\n",
" # 7. Calculate daily Sharpe Ratio\n",
" daily_sharpe_ratio = daily_excess_return / portfolio_volatility_daily\n",
"\n",
" # 8. Annualize the daily Sharpe Ratio\n",
" annualized_sharpe_ratio = daily_sharpe_ratio * np.sqrt(trading_days_per_year)\n",
"\n",
" # 9. Return both metrics\n",
" return annualized_volatility, annualized_sharpe_ratio\n",
"\n",
"print(\"Function 'portfolio_performance' defined successfully.\")"
],
"execution_count": 18,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Function 'portfolio_performance' defined successfully.\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "d0e7f0ab"
},
"source": [
"## Set up Optimization Constraints and Bounds\n",
"\n",
"### Subtask:\n",
"Define the constraints for the optimization (e.g., sum of weights equals 1) and the bounds for individual weights (e.g., between 0 and 1 for no short-selling).\n",
"\n",
"**Reasoning**:\n",
"I will define the bounds for each stock weight, ensuring they are between 0 and 1, and then define the equality constraint that the sum of all weights must equal 1, preparing these for use in an optimization function."
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "dc993443",
"outputId": "82f3b9e1-dd5d-4dc2-f59a-1f49cb1ffdd6"
},
"source": [
"num_stocks = len(stocks_list)\n",
"\n",
"# Define bounds for individual weights (no short-selling, no leverage)\n",
"bounds = tuple((0, 1) for _ in range(num_stocks))\n",
"\n",
"# Define constraints: sum of weights must be 1\n",
"constraints = ({'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1})\n",
"\n",
"print(f\"Number of stocks: {num_stocks}\")\n",
"print(f\"Bounds for each weight: {bounds[0]}\")\n",
"print(\"Constraints defined: Sum of weights = 1\")"
],
"execution_count": 19,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Number of stocks: 4\n",
"Bounds for each weight: (0, 1)\n",
"Constraints defined: Sum of weights = 1\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "651ee596"
},
"source": [
"## Run Optimization to Maximize Sharpe Ratio\n",
"\n",
"### Subtask:\n",
"Use a numerical optimization library (like `scipy.optimize.minimize`) to find the set of weights that maximizes the Sharpe Ratio, subject to the defined constraints and bounds.\n",
"\n",
"**Reasoning**:\n",
"I will import the `minimize` function, define the objective function `neg_sharpe_ratio`, set initial equal weights, and then run the optimization using `scipy.optimize.minimize` to find the weights that maximize the Sharpe Ratio, subject to the previously defined constraints and bounds. The result will be stored in `optimized_result`.\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "e4073a80",
"outputId": "45825d0d-213e-427b-98af-b412f9e0f3fb"
},
"source": [
"# Objective function to minimize (negative Sharpe Ratio)\n",
"def neg_sharpe_ratio(weights):\n",
" # `portfolio_performance` returns (annualized_volatility, annualized_sharpe_ratio)\n",
" volatility, sharpe_ratio = portfolio_performance(weights)\n",
" return -sharpe_ratio\n",
"\n",
"# Initial guess (equal weights)\n",
"initial_weights = np.array([1/num_stocks] * num_stocks)\n",
"\n",
"# Run the optimization\n",
"optimized_result = minimize(neg_sharpe_ratio, initial_weights, method='SLSQP', bounds=bounds, constraints=constraints)\n",
"\n",
"print(\"Optimization complete. Optimized result stored in 'optimized_result'.\")"
],
"execution_count": 24,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Optimization complete. Optimized result stored in 'optimized_result'.\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "6ce23625"
},
"source": [
"## Extract and Display Optimal Portfolio Metrics\n",
"\n",
"### Subtask:\n",
"Retrieve the optimal weights from the optimization result and calculate the corresponding optimal annualized portfolio return, annualized volatility, and annualized Sharpe Ratio. Then, display these optimized metrics.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "d74fed11"
},
"source": [
"**Reasoning**:\n",
"First, I need to extract the optimal weights from the `optimized_result` object. Then, I will use these optimal weights to calculate the optimal annualized volatility and Sharpe Ratio using the previously defined `portfolio_performance` function. After that, I will calculate the optimal annualized portfolio return. Finally, I will display all these optimal metrics in a formatted way.\n",
"\n"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "a452fc60",
"outputId": "3d15bce3-214a-4169-9442-a76d11df2fc2"
},
"source": [
"optimal_weights = optimized_result.x\n",
"\n",
"# Calculate optimal annualized volatility and Sharpe Ratio using the portfolio_performance function\n",
"optimal_annualized_volatility, optimal_annualized_sharpe_ratio = portfolio_performance(optimal_weights)\n",
"\n",
"# Calculate optimal annualized portfolio return\n",
"optimal_portfolio_daily_returns = (daily_returns * optimal_weights).sum(axis=1)\n",
"optimal_annualized_portfolio_return = optimal_portfolio_daily_returns.mean() * trading_days_per_year\n",
"\n",
"print(\"--- Optimal Portfolio Metrics ---\")\n",
"print(f\"Optimal Weights: {np.round(optimal_weights, 4)}\")\n",
"print(f\"Optimal Annualized Portfolio Return: {optimal_annualized_portfolio_return:.2%}\")\n",
"print(f\"Optimal Annualized Volatility: {optimal_annualized_volatility:.2%}\")\n",
"print(f\"Optimal Annualized Sharpe Ratio: {optimal_annualized_sharpe_ratio:.2f}\")"
],
"execution_count": 25,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"--- Optimal Portfolio Metrics ---\n",
"Optimal Weights: [0.3632 0. 0.6368 0. ]\n",
"Optimal Annualized Portfolio Return: 16.43%\n",
"Optimal Annualized Volatility: 23.55%\n",
"Optimal Annualized Sharpe Ratio: 0.19\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"np.round(optimal_weights,4)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "oHQF44y9xl_x",
"outputId": "90ab6129-510a-4f5a-c4c3-42b01f1a3533"
},
"execution_count": 30,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"array([0.3632, 0. , 0.6368, 0. ])"
]
},
"metadata": {},
"execution_count": 30
}
]
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "BytsUnWGxrye"
},
"execution_count": null,
"outputs": []
}
]
}