diff --git a/magic-formula/magic_formula.ipynb b/magic-formula/magic_formula.ipynb new file mode 100644 index 0000000..ad39e19 --- /dev/null +++ b/magic-formula/magic_formula.ipynb @@ -0,0 +1,721 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# Estratégia Magic Formula de Joel Greenblatt\n", + "\n", + "A Magic Formula é uma estratégia de investimento que classifica ações com base em dois critérios principais:\n", + "\n", + "- **Qualidade**: medida pelo Retorno sobre o Capital Investido (ROIC)\n", + "- **Valor**: medida pelo Rendimento de Lucros (Earnings Yield)" + ], + "id": "f0f9bbafd34ec9e" + }, + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2025-04-13T20:27:04.058395Z", + "start_time": "2025-04-13T20:26:52.206786Z" + } + }, + "source": "!pip install pandas numpy requests beautifulsoup4", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/usr/bin/bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory\r\n", + "Collecting pandas\r\n", + " Using cached pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (89 kB)\r\n", + "Collecting numpy\r\n", + " Using cached numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (62 kB)\r\n", + "Collecting yfinance\r\n", + " Using cached yfinance-0.2.55-py2.py3-none-any.whl.metadata (5.8 kB)\r\n", + "Requirement already satisfied: requests in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (2.32.3)\r\n", + "Requirement already satisfied: beautifulsoup4 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (4.13.3)\r\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from pandas) (2.9.0.post0)\r\n", + "Collecting pytz>=2020.1 (from pandas)\r\n", + " Using cached pytz-2025.2-py2.py3-none-any.whl.metadata (22 kB)\r\n", + "Collecting tzdata>=2022.7 (from pandas)\r\n", + " Using cached tzdata-2025.2-py2.py3-none-any.whl.metadata (1.4 kB)\r\n", + "Collecting multitasking>=0.0.7 (from yfinance)\r\n", + " Using cached multitasking-0.0.11-py3-none-any.whl.metadata (5.5 kB)\r\n", + "Requirement already satisfied: platformdirs>=2.0.0 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from yfinance) (4.3.7)\r\n", + "Collecting frozendict>=2.3.4 (from yfinance)\r\n", + " Using cached frozendict-2.4.6-py313-none-any.whl.metadata (23 kB)\r\n", + "Collecting peewee>=3.16.2 (from yfinance)\r\n", + " Using cached peewee-3.17.9-cp313-cp313-linux_x86_64.whl\r\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from requests) (3.4.1)\r\n", + "Requirement already satisfied: idna<4,>=2.5 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from requests) (3.10)\r\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from requests) (2.4.0)\r\n", + "Requirement already satisfied: certifi>=2017.4.17 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from requests) (2025.1.31)\r\n", + "Requirement already satisfied: soupsieve>1.2 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from beautifulsoup4) (2.6)\r\n", + "Requirement already satisfied: typing-extensions>=4.0.0 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from beautifulsoup4) (4.13.2)\r\n", + "Requirement already satisfied: six>=1.5 in /home/geron/Projects/python-for-finances/.venv/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\r\n", + "Using cached pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.7 MB)\r\n", + "Using cached numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (16.1 MB)\r\n", + "Using cached yfinance-0.2.55-py2.py3-none-any.whl (109 kB)\r\n", + "Using cached frozendict-2.4.6-py313-none-any.whl (16 kB)\r\n", + "Using cached multitasking-0.0.11-py3-none-any.whl (8.5 kB)\r\n", + "Using cached pytz-2025.2-py2.py3-none-any.whl (509 kB)\r\n", + "Using cached tzdata-2025.2-py2.py3-none-any.whl (347 kB)\r\n", + "Installing collected packages: pytz, peewee, multitasking, tzdata, numpy, frozendict, pandas, yfinance\r\n", + "Successfully installed frozendict-2.4.6 multitasking-0.0.11 numpy-2.2.4 pandas-2.2.3 peewee-3.17.9 pytz-2025.2 tzdata-2025.2 yfinance-0.2.55\r\n" + ] + } + ], + "execution_count": 1 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:27:40.137379Z", + "start_time": "2025-04-13T20:27:39.725248Z" + } + }, + "cell_type": "code", + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import requests\n", + "from bs4 import BeautifulSoup" + ], + "id": "737ea007e532eace", + "outputs": [], + "execution_count": 2 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:55:39.461968Z", + "start_time": "2025-04-13T20:55:39.453184Z" + } + }, + "cell_type": "code", + "source": [ + "def obter_acoes_b3():\n", + " url = \"https://www.fundamentus.com.br/resultado.php\"\n", + " headers = {\n", + " 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0'\n", + " }\n", + "\n", + " response = requests.get(url, headers=headers)\n", + " soup = BeautifulSoup(response.text, 'html.parser')\n", + "\n", + " tabela = soup.find('table', {'id': 'resultado'})\n", + " dados = []\n", + "\n", + " for linha in tabela.find_all('tr')[1:]:\n", + " colunas = linha.find_all('td')\n", + " ticker = colunas[0].text.strip()\n", + "\n", + " # Extrair métricas fundamentalistas\n", + " ev_ebit = colunas[10].text.strip().replace('.', '').replace(',', '.')\n", + " roic = colunas[16].text.strip().replace('.', '').replace(',', '.')\n", + " liquidez = colunas[18].text.strip().replace('.', '').replace(',', '.')\n", + " divida_pl = colunas[19].text.strip().replace('.', '').replace(',', '.')\n", + "\n", + " # Converter para float, tratando valores inválidos\n", + " try:\n", + " ev_ebit = float(ev_ebit) if ev_ebit != '-' else np.nan\n", + " roic = float(roic[:-1]) if '%' in roic else float(roic)\n", + " liquidez = float(liquidez)\n", + " divida_pl = float(divida_pl)\n", + "\n", + " # Calcular Earnings Yield (inverso do EV/EBIT)\n", + " earnings_yield = 1 / ev_ebit if ev_ebit > 0 else np.nan\n", + "\n", + " dados.append({\n", + " 'Ticker': ticker,\n", + " 'ROIC': roic,\n", + " 'EV/EBIT': ev_ebit,\n", + " 'Earnings Yield': earnings_yield,\n", + " 'Liquidez Media': liquidez,\n", + " 'Divida Bruta/PL': divida_pl\n", + " })\n", + " except (ValueError, ZeroDivisionError):\n", + " continue\n", + "\n", + " return pd.DataFrame(dados)" + ], + "id": "5a0af3f299da432", + "outputs": [], + "execution_count": 33 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:55:40.849860Z", + "start_time": "2025-04-13T20:55:40.092717Z" + } + }, + "cell_type": "code", + "source": [ + "acoes_df = obter_acoes_b3()\n", + "acoes_df" + ], + "id": "eee764781bd7c385", + "outputs": [ + { + "data": { + "text/plain": [ + " Ticker ROIC EV/EBIT Earnings Yield Liquidez Media Divida Bruta/PL\n", + "0 PORP4 -2.08 0.00 NaN 2.239900e+07 0.00\n", + "1 POPR4 19.93 0.00 NaN 5.458030e+08 0.82\n", + "2 MNSA3 145.70 0.00 NaN -9.105000e+06 -6.52\n", + "3 CFLU4 32.15 0.00 NaN 6.035100e+07 0.06\n", + "4 CSTB4 20.11 0.00 NaN 8.420670e+09 0.14\n", + ".. ... ... ... ... ... ...\n", + "986 UBBR4 0.33 0.00 NaN 1.031720e+10 0.00\n", + "987 VSTE3 0.06 25.50 0.039216 1.043280e+09 0.33\n", + "988 UBBR11 0.33 0.00 NaN 1.031720e+10 0.00\n", + "989 UBBR3 0.33 0.00 NaN 1.031720e+10 0.00\n", + "990 LJQQ3 0.03 4.86 0.205761 5.459970e+08 0.98\n", + "\n", + "[991 rows x 6 columns]" + ], + "text/html": [ + "
| \n", + " | Ticker | \n", + "ROIC | \n", + "EV/EBIT | \n", + "Earnings Yield | \n", + "Liquidez Media | \n", + "Divida Bruta/PL | \n", + "
|---|---|---|---|---|---|---|
| 0 | \n", + "PORP4 | \n", + "-2.08 | \n", + "0.00 | \n", + "NaN | \n", + "2.239900e+07 | \n", + "0.00 | \n", + "
| 1 | \n", + "POPR4 | \n", + "19.93 | \n", + "0.00 | \n", + "NaN | \n", + "5.458030e+08 | \n", + "0.82 | \n", + "
| 2 | \n", + "MNSA3 | \n", + "145.70 | \n", + "0.00 | \n", + "NaN | \n", + "-9.105000e+06 | \n", + "-6.52 | \n", + "
| 3 | \n", + "CFLU4 | \n", + "32.15 | \n", + "0.00 | \n", + "NaN | \n", + "6.035100e+07 | \n", + "0.06 | \n", + "
| 4 | \n", + "CSTB4 | \n", + "20.11 | \n", + "0.00 | \n", + "NaN | \n", + "8.420670e+09 | \n", + "0.14 | \n", + "
| ... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "
| 986 | \n", + "UBBR4 | \n", + "0.33 | \n", + "0.00 | \n", + "NaN | \n", + "1.031720e+10 | \n", + "0.00 | \n", + "
| 987 | \n", + "VSTE3 | \n", + "0.06 | \n", + "25.50 | \n", + "0.039216 | \n", + "1.043280e+09 | \n", + "0.33 | \n", + "
| 988 | \n", + "UBBR11 | \n", + "0.33 | \n", + "0.00 | \n", + "NaN | \n", + "1.031720e+10 | \n", + "0.00 | \n", + "
| 989 | \n", + "UBBR3 | \n", + "0.33 | \n", + "0.00 | \n", + "NaN | \n", + "1.031720e+10 | \n", + "0.00 | \n", + "
| 990 | \n", + "LJQQ3 | \n", + "0.03 | \n", + "4.86 | \n", + "0.205761 | \n", + "5.459970e+08 | \n", + "0.98 | \n", + "
991 rows × 6 columns
\n", + "| \n", + " | Ticker | \n", + "ROIC | \n", + "EV/EBIT | \n", + "Earnings Yield | \n", + "Liquidez Media | \n", + "Divida Bruta/PL | \n", + "ROIC_Rank | \n", + "EY_Rank | \n", + "Magic_Rank | \n", + "
|---|---|---|---|---|---|---|---|---|---|
| 453 | \n", + "AHEB3 | \n", + "61.47 | \n", + "2.17 | \n", + "0.460829 | \n", + "2.444130e+08 | \n", + "0.02 | \n", + "7.0 | \n", + "25.0 | \n", + "32.0 | \n", + "
| 455 | \n", + "AHEB5 | \n", + "61.47 | \n", + "2.48 | \n", + "0.403226 | \n", + "2.444130e+08 | \n", + "0.02 | \n", + "7.0 | \n", + "30.0 | \n", + "37.0 | \n", + "
| 459 | \n", + "AHEB6 | \n", + "61.47 | \n", + "2.80 | \n", + "0.357143 | \n", + "2.444130e+08 | \n", + "0.02 | \n", + "7.0 | \n", + "32.0 | \n", + "39.0 | \n", + "
| 367 | \n", + "CSPC4 | \n", + "45.33 | \n", + "1.43 | \n", + "0.699301 | \n", + "2.126670e+09 | \n", + "1.48 | \n", + "23.5 | \n", + "16.5 | \n", + "40.0 | \n", + "
| 371 | \n", + "CSPC3 | \n", + "45.33 | \n", + "1.43 | \n", + "0.699301 | \n", + "2.126670e+09 | \n", + "1.48 | \n", + "23.5 | \n", + "16.5 | \n", + "40.0 | \n", + "
| 433 | \n", + "SYNE3 | \n", + "50.87 | \n", + "2.41 | \n", + "0.414938 | \n", + "1.073780e+09 | \n", + "0.76 | \n", + "16.0 | \n", + "28.0 | \n", + "44.0 | \n", + "
| 435 | \n", + "CPFG4 | \n", + "36.99 | \n", + "0.95 | \n", + "1.052632 | \n", + "3.614430e+09 | \n", + "0.42 | \n", + "37.5 | \n", + "7.0 | \n", + "44.5 | \n", + "
| 437 | \n", + "CPFG3 | \n", + "36.99 | \n", + "0.97 | \n", + "1.030928 | \n", + "3.614430e+09 | \n", + "0.42 | \n", + "37.5 | \n", + "8.0 | \n", + "45.5 | \n", + "
| 396 | \n", + "PALF11 | \n", + "46.56 | \n", + "2.84 | \n", + "0.352113 | \n", + "3.055110e+09 | \n", + "3.00 | \n", + "19.0 | \n", + "33.0 | \n", + "52.0 | \n", + "
| 403 | \n", + "PALF5 | \n", + "46.56 | \n", + "2.87 | \n", + "0.348432 | \n", + "3.055110e+09 | \n", + "3.00 | \n", + "19.0 | \n", + "34.0 | \n", + "53.0 | \n", + "
| 405 | \n", + "PALF3 | \n", + "46.56 | \n", + "2.88 | \n", + "0.347222 | \n", + "3.055110e+09 | \n", + "3.00 | \n", + "19.0 | \n", + "35.0 | \n", + "54.0 | \n", + "
| 380 | \n", + "ENER3 | \n", + "51.72 | \n", + "3.09 | \n", + "0.323625 | \n", + "1.167120e+09 | \n", + "3.89 | \n", + "11.0 | \n", + "44.0 | \n", + "55.0 | \n", + "
| 381 | \n", + "ENER6 | \n", + "51.72 | \n", + "3.09 | \n", + "0.323625 | \n", + "1.167120e+09 | \n", + "3.89 | \n", + "11.0 | \n", + "44.0 | \n", + "55.0 | \n", + "
| 379 | \n", + "ENER5 | \n", + "51.72 | \n", + "3.09 | \n", + "0.323625 | \n", + "1.167120e+09 | \n", + "3.89 | \n", + "11.0 | \n", + "44.0 | \n", + "55.0 | \n", + "
| 382 | \n", + "TEFC11 | \n", + "46.72 | \n", + "3.45 | \n", + "0.289855 | \n", + "4.585600e+10 | \n", + "2.95 | \n", + "17.0 | \n", + "52.0 | \n", + "69.0 | \n", + "