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", + "\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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TickerROICEV/EBITEarnings YieldLiquidez MediaDivida Bruta/PL
0PORP4-2.080.00NaN2.239900e+070.00
1POPR419.930.00NaN5.458030e+080.82
2MNSA3145.700.00NaN-9.105000e+06-6.52
3CFLU432.150.00NaN6.035100e+070.06
4CSTB420.110.00NaN8.420670e+090.14
.....................
986UBBR40.330.00NaN1.031720e+100.00
987VSTE30.0625.500.0392161.043280e+090.33
988UBBR110.330.00NaN1.031720e+100.00
989UBBR30.330.00NaN1.031720e+100.00
990LJQQ30.034.860.2057615.459970e+080.98
\n", + "

991 rows × 6 columns

\n", + "
" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 34 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:55:41.458678Z", + "start_time": "2025-04-13T20:55:41.454935Z" + } + }, + "cell_type": "code", + "source": [ + "def filtrar_acoes(df):\n", + " \"\"\"Filtra ações conforme recomendação de Greenblatt\"\"\"\n", + "\n", + " # Remover empresas com baixa liquidez (menor que 1M)\n", + " df_filtrado = df[df['Liquidez Media'] > 1_000_000]\n", + "\n", + " # Remover empresas com dívida muito alta\n", + " df_filtrado = df_filtrado[(df_filtrado['Divida Bruta/PL'] > 0) & (df_filtrado['Divida Bruta/PL'] < 4)]\n", + "\n", + " return df_filtrado" + ], + "id": "f91f75a047ff57bf", + "outputs": [], + "execution_count": 35 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:55:42.184602Z", + "start_time": "2025-04-13T20:55:42.181491Z" + } + }, + "cell_type": "code", + "source": "acoes_df = filtrar_acoes(acoes_df)", + "id": "17276b69637e2cbe", + "outputs": [], + "execution_count": 36 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:55:42.796804Z", + "start_time": "2025-04-13T20:55:42.793964Z" + } + }, + "cell_type": "code", + "source": [ + "def magic_formula(df):\n", + " # Remover dados inválidos\n", + " df = df[df['Earnings Yield'].notna()].copy()\n", + "\n", + " # Classificar as ações (ranking)\n", + " df['ROIC_Rank'] = df['ROIC'].rank(ascending=False)\n", + " df['EY_Rank'] = df['Earnings Yield'].rank(ascending=False)\n", + "\n", + " # Calcular o ranking combinado\n", + " df['Magic_Rank'] = df['ROIC_Rank'] + df['EY_Rank']\n", + "\n", + " # Ordenar pelo ranking combinado (do menor para o maior)\n", + " df_result = df.sort_values('Magic_Rank')\n", + "\n", + " return df_result" + ], + "id": "32611e00190bf868", + "outputs": [], + "execution_count": 37 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:55:43.475901Z", + "start_time": "2025-04-13T20:55:43.454407Z" + } + }, + "cell_type": "code", + "source": [ + "resultado = magic_formula(acoes_df)\n", + "\n", + "# Selecionar as 15 melhores ações\n", + "top_acoes = resultado.head(15)\n", + "top_acoes" + ], + "id": "625cd15e9bf429eb", + "outputs": [ + { + "data": { + "text/plain": [ + " Ticker ROIC EV/EBIT Earnings Yield Liquidez Media Divida Bruta/PL \\\n", + "453 AHEB3 61.47 2.17 0.460829 2.444130e+08 0.02 \n", + "455 AHEB5 61.47 2.48 0.403226 2.444130e+08 0.02 \n", + "459 AHEB6 61.47 2.80 0.357143 2.444130e+08 0.02 \n", + "367 CSPC4 45.33 1.43 0.699301 2.126670e+09 1.48 \n", + "371 CSPC3 45.33 1.43 0.699301 2.126670e+09 1.48 \n", + "433 SYNE3 50.87 2.41 0.414938 1.073780e+09 0.76 \n", + "435 CPFG4 36.99 0.95 1.052632 3.614430e+09 0.42 \n", + "437 CPFG3 36.99 0.97 1.030928 3.614430e+09 0.42 \n", + "396 PALF11 46.56 2.84 0.352113 3.055110e+09 3.00 \n", + "403 PALF5 46.56 2.87 0.348432 3.055110e+09 3.00 \n", + "405 PALF3 46.56 2.88 0.347222 3.055110e+09 3.00 \n", + "380 ENER3 51.72 3.09 0.323625 1.167120e+09 3.89 \n", + "381 ENER6 51.72 3.09 0.323625 1.167120e+09 3.89 \n", + "379 ENER5 51.72 3.09 0.323625 1.167120e+09 3.89 \n", + "382 TEFC11 46.72 3.45 0.289855 4.585600e+10 2.95 \n", + "\n", + " ROIC_Rank EY_Rank Magic_Rank \n", + "453 7.0 25.0 32.0 \n", + "455 7.0 30.0 37.0 \n", + "459 7.0 32.0 39.0 \n", + "367 23.5 16.5 40.0 \n", + "371 23.5 16.5 40.0 \n", + "433 16.0 28.0 44.0 \n", + "435 37.5 7.0 44.5 \n", + "437 37.5 8.0 45.5 \n", + "396 19.0 33.0 52.0 \n", + "403 19.0 34.0 53.0 \n", + "405 19.0 35.0 54.0 \n", + "380 11.0 44.0 55.0 \n", + "381 11.0 44.0 55.0 \n", + "379 11.0 44.0 55.0 \n", + "382 17.0 52.0 69.0 " + ], + "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", + " \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", + "
TickerROICEV/EBITEarnings YieldLiquidez MediaDivida Bruta/PLROIC_RankEY_RankMagic_Rank
453AHEB361.472.170.4608292.444130e+080.027.025.032.0
455AHEB561.472.480.4032262.444130e+080.027.030.037.0
459AHEB661.472.800.3571432.444130e+080.027.032.039.0
367CSPC445.331.430.6993012.126670e+091.4823.516.540.0
371CSPC345.331.430.6993012.126670e+091.4823.516.540.0
433SYNE350.872.410.4149381.073780e+090.7616.028.044.0
435CPFG436.990.951.0526323.614430e+090.4237.57.044.5
437CPFG336.990.971.0309283.614430e+090.4237.58.045.5
396PALF1146.562.840.3521133.055110e+093.0019.033.052.0
403PALF546.562.870.3484323.055110e+093.0019.034.053.0
405PALF346.562.880.3472223.055110e+093.0019.035.054.0
380ENER351.723.090.3236251.167120e+093.8911.044.055.0
381ENER651.723.090.3236251.167120e+093.8911.044.055.0
379ENER551.723.090.3236251.167120e+093.8911.044.055.0
382TEFC1146.723.450.2898554.585600e+102.9517.052.069.0
\n", + "
" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 38 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-04-13T20:55:47.816488Z", + "start_time": "2025-04-13T20:55:47.812631Z" + } + }, + "cell_type": "code", + "source": [ + "# Salvar resultados em um CSV\n", + "top_acoes.to_csv('magic_formula_b3_resultado.csv', index=False)\n", + "print(\"\\nResultados salvos em 'magic_formula_b3_resultado.csv'\")" + ], + "id": "605685dca910a47c", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Resultados salvos em 'magic_formula_b3_resultado.csv'\n" + ] + } + ], + "execution_count": 39 + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "", + "id": "cdcab25ed5f48c3b" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/magic-formula/magic_formula_b3_resultado.csv b/magic-formula/magic_formula_b3_resultado.csv new file mode 100644 index 0000000..50afd9c --- /dev/null +++ b/magic-formula/magic_formula_b3_resultado.csv @@ -0,0 +1,16 @@ +Ticker,ROIC,EV/EBIT,Earnings Yield,Liquidez Media,Divida Bruta/PL,ROIC_Rank,EY_Rank,Magic_Rank +AHEB3,61.47,2.17,0.4608294930875576,244413000.0,0.02,7.0,25.0,32.0 +AHEB5,61.47,2.48,0.40322580645161293,244413000.0,0.02,7.0,30.0,37.0 +AHEB6,61.47,2.8,0.35714285714285715,244413000.0,0.02,7.0,32.0,39.0 +CSPC4,45.33,1.43,0.6993006993006994,2126670000.0,1.48,23.5,16.5,40.0 +CSPC3,45.33,1.43,0.6993006993006994,2126670000.0,1.48,23.5,16.5,40.0 +SYNE3,50.87,2.41,0.41493775933609955,1073780000.0,0.76,16.0,28.0,44.0 +CPFG4,36.99,0.95,1.0526315789473684,3614430000.0,0.42,37.5,7.0,44.5 +CPFG3,36.99,0.97,1.0309278350515465,3614430000.0,0.42,37.5,8.0,45.5 +PALF11,46.56,2.84,0.35211267605633806,3055110000.0,3.0,19.0,33.0,52.0 +PALF5,46.56,2.87,0.3484320557491289,3055110000.0,3.0,19.0,34.0,53.0 +PALF3,46.56,2.88,0.3472222222222222,3055110000.0,3.0,19.0,35.0,54.0 +ENER3,51.72,3.09,0.3236245954692557,1167120000.0,3.89,11.0,44.0,55.0 +ENER6,51.72,3.09,0.3236245954692557,1167120000.0,3.89,11.0,44.0,55.0 +ENER5,51.72,3.09,0.3236245954692557,1167120000.0,3.89,11.0,44.0,55.0 +TEFC11,46.72,3.45,0.2898550724637681,45856000000.0,2.95,17.0,52.0,69.0