{ "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TickerBBAS3.SABBDC4.SAITUB4.SASANB4.SA
Date
2020-11-2411.89797614.99303520.72110014.784431
2020-11-2511.86440914.79962420.47699015.099722
2020-11-2611.67640014.55930120.04457715.264225
2020-11-2711.61933014.44208220.19801315.181971
2020-11-3011.36753814.24280119.91903114.434868
...............
2025-11-1722.50000019.32000040.29999917.270000
2025-11-1821.87999919.09000040.09999817.330000
2025-11-1921.58000018.90000039.84999817.129999
2025-11-2122.00000018.79000139.97000117.150000
2025-11-2422.10000018.80999939.86000117.250000
\n", "

1247 rows × 4 columns

\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\n", " \n", "
\n", "\n", "\n", "
\n", " \n", "\n", "\n", "\n", " \n", "
\n", "\n", "
\n", " \n", " \n", " \n", "
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TickerBBAS3.SABBDC4.SAITUB4.SASANB4.SA
Date
2020-11-24NaNNaNNaNNaN
2020-11-25-0.002821-0.012900-0.0117810.021326
2020-11-26-0.015846-0.016238-0.0211170.010894
2020-11-27-0.004888-0.0080510.007655-0.005389
2020-11-30-0.021670-0.013799-0.013812-0.049210
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\n", " \n", "
\n", "\n", "\n", "
\n", " \n", "\n", "\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
0
Date
2020-11-240.000000
2020-11-25-0.001544
2020-11-26-0.010577
2020-11-27-0.002668
2020-11-30-0.024623
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
0
Date
2020-11-240.000000
2020-11-25-0.001544
2020-11-26-0.012105
2020-11-27-0.014740
2020-11-30-0.039000
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Ticker^BVSP
Date
2020-11-24NaN
2020-11-250.003161
2020-11-260.000854
2020-11-270.003157
2020-11-30-0.015257
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\n", " \n", "
\n", "\n", "\n", "
\n", " \n", "\n", "\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": [] } ] }