%%html
<head>
<link rel="stylesheet" href="css_jupyter.css">
</head>
Pensémos cómo hacen dinero las empresas que manejan los fondos de pensiones o de inversión.
En general, la gran mayoría carga una comisión ad valorem calculada sobre el porcentaje del valor de los activos que manejan.
Esto significa que los managers estarán más preocupados por aumentar el volumen del fondo que por su performance: cuanto más grande sea el fondo en global más dinero se llevan en comisiones.
En este escenario, los economistas académicos han intentado proponer mecanismos que permitan alinear el interés de los managers con el interés de los inversores.
Por ejemplo, podría ser interesante obligar a los managers a tener dinero propio en el fondo. En este caso no solo estarían interesados en aumentar el volumen total de activos del fondo sino que también su rendimiento, alineando el interés del managers al del inversionista
Resulta entonces natural que un analista-inversionista esté interesado en analizar y caracterizar la rentabilidad de los fondos de inversión. Y, para ello, necesita obtener datos relacionados con los fondos de pensiones.
Mostrar cómo es posible obtener, de manera sencilla, datos de la Web para trabajar en aquellos aspectos que nos interesen.
En particular, los objetivos específicos son:
Lo que aquí se presenta, de manera resumida, son algunos de los conceptos que hemos introducido en el curso de Obtención de datos de la Web. Este curso tiene como objetivo presentar distintos módulos de Python, e.g., request-html o selenium, para extraer información de la Web
# Importamos las librerias-paquetes que vamos a utilizar
from requests_html import HTML, HTMLSession
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme()
import warnings
warnings.filterwarnings('ignore')
# iniciamos session
session = HTMLSession()
Vamos a ver la página web de donde queremos sacar los fondos
Vamos a buscar el ranking de los fondos y dentro de estos, empezaremos por los que miran la rentabilidad a 10 años
Esto es una web estática: miremos cómo funciona el url de la web para ver si podemos sacar la más información:
https://www.quefondos.com/es/fondos/ranking/decenio/
url Depués
https://www.quefondos.com/es/fondos/ranking/decenio/index.html?cardCount=2&cardSize=20
Que sucede si en el cardSize
ponemos el valor máximo: 100
https://www.quefondos.com/es/fondos/ranking/decenio/index.html?cardCount=1&cardSize=100
# Vamos a obtener los datos de esa primera página
url = "https://www.quefondos.com/es/fondos/ranking/decenio/index.html?cardCount=1&cardSize=100"
source = session.get(url)
type(source)
print(source.html)
print(source.html.text)
tabla = source.html.find("tbody", first = True)
print(tabla.text)
fondos = tabla.find("tr")
print(fondos)
print(fondos[0].text)
print(fondos[2].text)
# cual es el úlitmo fondo
print(fondos[100].text)
¿Por qué da error?
table_df =[]
for row in range(len(fondos)):
fondo = fondos[row].text.split("\n")
table_df.append(fondo)
table_df
tabla_pensiones = pd.DataFrame(table_df, columns = ['plan', 'categoria', 'un', 'tres', 'cinco', 'diez'])
tabla_pensiones.head()
tabla_pensiones.tail()
tabla_pensiones.info()
tabla_pensiones['un']
type(tabla_pensiones['un'])
tabla_pensiones['un'] = tabla_pensiones['un'].str.replace('%', '')
tabla_pensiones['un']
tabla_pensiones.columns
for col in tabla_pensiones.columns:
tabla_pensiones[col] = tabla_pensiones[col].str.replace('%', '')
tabla_pensiones[col] = tabla_pensiones[col].str.replace(',', '.')
tabla_pensiones.head()
import locale
#from locale import atof
#locale.setlocale(locale.LC_NUMERIC, '')
#'en_GB.UTF-8'
#tabla_pensiones.set_index("plan", inplace = True).applymap(atof)
#tabla_pensiones.head()
tabla_pensiones.dtypes
tabla_pensiones['un'] = tabla_pensiones['un'].astype(float)
tabla_pensiones.dtypes
for col in ['un', 'tres', 'cinco', 'diez']:
tabla_pensiones[col] = tabla_pensiones[col].astype(float)
tabla_pensiones.replace('.', np.nan)
for col in ['un', 'tres', 'cinco', 'diez']:
tabla_pensiones[col] = tabla_pensiones[col].astype(float)
# tengo que buscar este punto
tabla_pensiones[tabla_pensiones["plan"] == "ISHARES NASDAQ 100 UCITS ETF USD (ACC)"]
tabla_pensiones[tabla_pensiones["plan"] == "ISHARES NASDAQ 100 UCITS ETF USD (ACC)"]["cinco"]
tabla_pensiones.set_index("plan", inplace = True)
tabla_pensiones
tabla_pensiones.loc["ISHARES NASDAQ 100 UCITS ETF USD (ACC)"][3]
missing = tabla_pensiones.loc["ISHARES NASDAQ 100 UCITS ETF USD (ACC)"][3]
ord(missing)
ord('.')
tabla_pensiones = tabla_pensiones.replace(chr(183), np.nan)
tabla_pensiones.loc["ISHARES NASDAQ 100 UCITS ETF USD (ACC)"]
for col in ['un', 'tres', 'cinco', 'diez']:
tabla_pensiones[col] = tabla_pensiones[col].astype(float)
tabla_pensiones.iloc[0,4].replace(".","")
tabla_pensiones.iloc[0,4] = tabla_pensiones.iloc[0,4].replace(".","")
for col in ['un', 'tres', 'cinco', 'diez']:
tabla_pensiones[col] = tabla_pensiones[col].astype(float)
tabla_pensiones.info()
tabla_pensiones.iloc[0,4] = tabla_pensiones.iloc[0,4]/100
tabla_pensiones.iloc[0,4]
# Leemos los datos de los planes de pensiones, que son menos de la web y los guardamos en un dataframe
table_df =[]
i = 1
while True:
url = f"https://www.quefondos.com/es/planes/ranking/decenio/index.html?cardCount={i}&cardSize=100"
source = session.get(url)
# Buscamos el tag que nos interesa
tabla = source.html.find("tbody", first = True)
fondos = tabla.find("tr")
# Quebramos para que no tarde mucho
if fondos == []:
break
for row in range(len(fondos)):
fondo = fondos[row].text.split("\n")
table_df.append(fondo)
i = i + 1 # cambiamos la página
time.sleep(5)
tabla_pensiones = pd.DataFrame(table_df, columns = ['plan', 'categoria', 'un', 'tres', 'cinco', 'diez'])
# Vemos lo que hemos leido
tabla_pensiones.head()
# Limpiamos los datos para poder trabajar con ellos,
tabla_pensiones = tabla_pensiones.replace(chr(183), np.nan)
for col in tabla_pensiones.columns:
tabla_pensiones[col] = tabla_pensiones[col].str.replace('%', '')
tabla_pensiones[col] = tabla_pensiones[col].str.replace(',', '.')
for col in ['un', 'tres', 'cinco', 'diez']:
tabla_pensiones[col] = tabla_pensiones[col].astype(float)
# Vemos los datos con los cuales vamos a trabajar
tabla_pensiones.head()
# Veamos las rentabilidades medianas por categoría,
# ordenadas en base a la rentabilidad mediana a diez años
(tabla_pensiones.groupby("categoria").agg(["median"])
.sort_values(by = [("diez", "median")], ascending = False))
# Veamos las rentabilidades máximas y mínimas por categoría a diez años
(tabla_pensiones.groupby("categoria").agg({"diez":["min", "max"]}).dropna().
sort_values(by=[("diez", "max")], ascending = False))
# Veamos cuales son, dentro de cada categoría, los mejores planes medidos por la rentabilidad a 10 años
categoria_diez = tabla_pensiones.groupby("categoria")
(categoria_diez.apply(lambda x: x.sort_values(["diez"], ascending = False))
.reset_index(drop=True).groupby("categoria").head(1).
sort_values(by=["diez"], ascending = False)
)
# Análisis de un plan en particular, e.g., plan Naranja: usampos regex en pandas
(
tabla_pensiones[tabla_pensiones["plan"].str.match(r'(^NAR.*)')==True]
.sort_values(by=["diez"], ascending = False)
)
#Extraer los 10 mejores RVI a diez años y ordenarlos por rentabilidad
rvi = (
tabla_pensiones[tabla_pensiones["categoria"].str.match(r'(^RVI.*)')==True]
.sort_values(by=["diez"], ascending = False).head(10)
)
# Veamos estos mejores planes
rvi
# Analisis de la distribución de las rentabilidades a 10 años
sns.set(style="whitegrid")
fig, ax = plt.subplots()
sns.ecdfplot(data=tabla_pensiones, x="diez")
# Agrego la mediana
ax.axvline(x = tabla_pensiones["diez"].median(), ymin = 0, ymax = 0.5, linestyle = "dotted", alpha = 0.75, color = "green" )
ax.axhline(y = 0.5, xmin = 0, xmax = 0.15, linestyle = "dotted", alpha = 0.75, color = "green" )
# Flecha y texto
ax.annotate(f'Rentabilidad mediana: {tabla_pensiones["diez"].median()} ', xycoords='data', xy=(30, .0), xytext=(-125, -.35),
arrowprops=dict(facecolor='black', arrowstyle = "->", color = "blue"),
bbox=dict(boxstyle="round", alpha=0.1),
)
ax.set_xlabel("Rentabilidad a 10 años: porcentaje")
# Agrego el rug
sns.rugplot(data=tabla_pensiones, x="diez", hue = "categoria", height = 0.15, linewidth = 2.5)
# Stackoverflow sobre legends
# https://stackoverflow.com/questions/66623746/no-access-to-legend-of-sns-histplot-used-with-data-normalisation
ax.legend(handles=ax.legend_.legendHandles, labels=[t.get_text() for t in ax.legend_.texts],
title=ax.legend_.get_title().get_text(),
bbox_to_anchor=(1.05, 1), loc='upper left', handleheight = .85, fontsize = 10)
# Ensancho las handels de la leggend
for i in range(0,len(ax.legend_.legendHandles)):
ax.legend_.legendHandles[i]._linewidth = 4
plt.title("Distribución acumulada de la Rentabilidad a 10 años")
plt.show()
# Relación entre las medidas temporales de rentabilidad
sns.pairplot(data=tabla_pensiones, hue = "categoria",height=2.5, diag_kind= "hist", corner = True)
plt.show()
# Analizar la relación entre fondos en renta variable y rentabilidad a 10 años
# importamos statmodels para hacer regresin
import statsmodels.api as sm
# Creamos una dummy para renta variable
tabla_pensiones["Dummy_RentaV"] = (tabla_pensiones["categoria"].str.match(r'(^RV.*)')==True).astype(int)
# Veamos que información contiene esta dummy
tabla_pensiones["Dummy_RentaV"].describe()
# El describe no es más que un dataframe
media = tabla_pensiones["Dummy_RentaV"].describe()["mean"]
print(f"Porcentaje de fondos en renta variable es de {media:.4f}")
# Defino varibles y hago la regresión
tabla_pensiones['constante'] = 1
X = tabla_pensiones[["constante","Dummy_RentaV"]]
y = tabla_pensiones["diez"]
reg = sm.OLS(endog=y, exog= X, missing='drop')
results = reg.fit()
print(results.summary())
# Obsevar que el valor del coeficiente de la renta variable es equivalente a la difencia de medias
(
tabla_pensiones.groupby("Dummy_RentaV")["diez"].mean().iloc[1] # media rentabilidades variables
-
tabla_pensiones.groupby("Dummy_RentaV")["diez"].mean().iloc[0] # media rentabilidades otros tipo de fondos
)
# Relación rentabilidad a 5 y diez años
#Creación de nombres cortos por categoría
tabla_pensiones["label"] =tabla_pensiones["categoria"].str.split(" ", n = 1, expand = True)[0]
# Relación rentabilidades a cinco y diez años
from numpy.polynomial import polynomial as pol
from numpy.polynomial import Polynomial as Pol
tabla_pensiones = tabla_pensiones.dropna()
X = tabla_pensiones["cinco"]
y = tabla_pensiones["diez"]
labels = tabla_pensiones["label"]
# Obtener coeficientes
c = pol.polyfit(X,y, deg = 1)
# Alternativa: obener funcion
c1= Pol.fit(X,y, deg = 1)
# Para evaluar esta funcion polinómica c1(X)
# Vamos a sustituir los puntos por los labels
fig, ax = plt.subplots()
ax.scatter(X, y, marker = " ")
for i, label in enumerate(labels):
ax.annotate(label, (X.iloc[i], y.iloc[i]), fontsize = 10, color = "green", alpha = 0.5)
# Ajustamos una regresión lineal utilizando polinomios
ax.plot(np.unique(X),
np.poly1d(np.polyfit(X, y, 1))(np.unique(X)),
color='blue', alpha = 0.5, linewidth = 4)
ax.set_xlim([-50,150])
ax.set_ylim([-60,350])
ax.set_xlabel('Rentabilidad a 5 años')
ax.set_ylabel('Rentabilidad a 10 años')
ax.set_title('Relación entre la rentabilidad a 5 y 10 años')
plt.show()
Ejercicio: intentar lo mismo con los fondos de inversión.