Python para finanzas: volatilidad, ratio de Sharpe y drawdown | Quantum Club

Etapa 2 de 4 · Fundamentos en Python

Python para finanzas: rentabilidad, volatilidad, Sharpe y drawdown

Tu primer análisis cuantitativo en Python: descarga precios reales, calcula rentabilidades, volatilidad anualizada, ratio de Sharpe y máximo drawdown con pandas, paso a paso.

En este artículo
  1. Preparar el entorno
  2. Descargar precios reales
  3. Rentabilidades: simples y logarítmicas
  4. Rentabilidad anualizada
  5. Volatilidad anualizada
  6. Ratio de Sharpe
  7. Máximo drawdown: la métrica que de verdad duele
  8. Todo junto: tu primer tearsheet
  9. Una cartera en lugar de activos sueltos
  10. Qué has aprendido y qué viene después

La mejor manera de empezar en finanzas cuantitativas no es leer teoría: es abrir Python y medir. En este artículo vas a descargar precios reales de bolsa y calcular las cuatro métricas que aparecen en cualquier informe de un fondo: rentabilidad, volatilidad, ratio de Sharpe y máximo drawdown.

Todo el código funciona tal cual en Google Colab, sin instalar nada en tu ordenador.

Preparar el entorno

Necesitas cuatro librerías: numpy y pandas para los cálculos, matplotlib para los gráficos y yfinance para descargar precios de Yahoo Finance. En Colab solo hay que instalar la última:

Python
# En Colab o en una terminal
!pip install yfinance

Descargar precios reales

Vamos a trabajar con tres activos: Inditex y Banco Santander (cotizan en Madrid, de ahí el sufijo .MC) y el SPY, un fondo que replica el S&P 500.

Python
import numpy as np
import pandas as pd
import yfinance as yf

tickers = ["ITX.MC", "SAN.MC", "SPY"]
precios = yf.download(tickers, start="2016-01-01", end="2025-12-31",
                      auto_adjust=True)["Close"].dropna()
print(precios.tail())

Con auto_adjust=True los precios vienen ajustados por dividendos y splits. Es importante: si usas el precio sin ajustar, el día que una acción reparte dividendo parece que cae, y tus métricas salen mal.

Rentabilidades: simples y logarítmicas

Los quants casi nunca trabajan con precios, sino con rentabilidades, porque los precios de activos distintos no son comparables y las rentabilidades sí. Hay dos formas de medirlas:

\[ r_t^{\text{simple}} = \frac{P_t - P_{t-1}}{P_{t-1}}, \qquad r_t^{\log} = \ln\frac{P_t}{P_{t-1}} \]

La simple es la que entiende todo el mundo («ha subido un 2%»). La logarítmica tiene una propiedad muy cómoda: se suma en el tiempo. La rentabilidad logarítmica de un año es la suma de las diarias, cosa que no ocurre con las simples. Para días normales ambas son casi idénticas.

Python
rent = precios.pct_change().dropna()            # rentabilidades simples diarias
rent_log = np.log(precios / precios.shift(1)).dropna()

print(rent.describe().round(4))

Rentabilidad anualizada

Para comparar activos se habla en términos anuales. La forma honesta de hacerlo es la tasa de crecimiento anual compuesta (CAGR): qué rentabilidad constante cada año te habría llevado del precio inicial al final.

\[ \text{CAGR} = \left(\frac{P_{\text{final}}}{P_{\text{inicial}}}\right)^{1/\text{años}} - 1 \]

Python
años = len(rent) / 252                          # ~252 sesiones de bolsa al año
cagr = (precios.iloc[-1] / precios.iloc[0]) ** (1 / años) - 1
print((cagr * 100).round(2))

¿Por qué 252? Es el número aproximado de días en que abre la bolsa en un año. Lo usaremos para pasar de métricas diarias a anuales.

Volatilidad anualizada

La volatilidad es la desviación típica de las rentabilidades: cuánto se mueve el activo alrededor de su media. Es la medida de riesgo más básica. Para anualizarla se multiplica por la raíz de 252:

\[ \sigma_{\text{anual}} = \sigma_{\text{diaria}} \cdot \sqrt{252} \]

La raíz sale de que, si las rentabilidades diarias son independientes, las varianzas se suman: la varianza de un año es 252 veces la diaria, y la desviación típica es su raíz.

Python
vol = rent.std() * np.sqrt(252)
print((vol * 100).round(2))

Un índice amplio como el S&P 500 suele rondar un 15–20% anual; una acción individual, bastante más, porque no está diversificada.

Ratio de Sharpe

Rentabilidad sola no dice nada: un 20% con un riesgo enorme puede ser peor que un 10% muy estable. El ratio de Sharpe mide la rentabilidad por unidad de riesgo:

\[ \text{Sharpe} = \frac{\mu_{\text{anual}} - r_f}{\sigma_{\text{anual}}} \]

donde \( r_f \) es el tipo sin riesgo (lo que te daría una letra del Tesoro). Como referencia, un Sharpe de 0,5 es normal para un índice a largo plazo, 1 es bueno y por encima de 2 de forma sostenida es excepcional… o un error en el backtest.

Python
rf = 0.02                                        # tipo sin riesgo anual (ajústalo)
mu = rent.mean() * 252
sharpe = (mu - rf) / vol
print(sharpe.round(2))

Cuidado: aquí \( \mu \) es la media aritmética anualizada, que siempre es algo mayor que la CAGR. Es la convención habitual para el Sharpe, pero conviene no mezclar ambas cifras en un mismo informe sin decirlo.

Máximo drawdown: la métrica que de verdad duele

La volatilidad trata igual las subidas y las bajadas. El drawdown mira solo lo que importa a quien invierte: cuánto ha caído desde el último máximo. El máximo drawdown es la peor de esas caídas en todo el periodo.

Python
riqueza = (1 + rent).cumprod()                   # valor de 1 € invertido
maximo = riqueza.cummax()                        # máximo alcanzado hasta cada día
drawdown = riqueza / maximo - 1
max_dd = drawdown.min()
print((max_dd * 100).round(2))

Es la métrica que explica por qué la gente abandona las estrategias: un −40% significa que necesitas un +67% solo para volver al punto de partida.

2,0 4,0 4,38 € Valor de 1 € invertido 0 2 4 6 8 10 Años (datos simulados) -20 % 0 % Máximo drawdown: -30 % Drawdown: caída desde el último máximo
Lo que ven la rentabilidad y el drawdown sobre la misma serie (simulada). Arriba todo parece una subida; abajo se ve lo que hubo que aguantar por el camino.

Todo junto: tu primer tearsheet

Los fondos resumen una estrategia en una tabla de métricas llamada tearsheet. Esta función hace una versión mínima para cualquier serie de precios:

Python
def metricas(precios, rf=0.02):
    r = precios.pct_change().dropna()
    años = len(r) / 252
    riqueza = (1 + r).cumprod()
    vol = r.std() * np.sqrt(252)
    return pd.DataFrame({
        "CAGR %": ((precios.iloc[-1] / precios.iloc[0]) ** (1 / años) - 1) * 100,
        "Volatilidad %": vol * 100,
        "Sharpe": (r.mean() * 252 - rf) / vol,
        "Máx. drawdown %": (riqueza / riqueza.cummax() - 1).min() * 100,
    }).round(2)

print(metricas(precios))

Y un gráfico para verlo, porque un número sin gráfico esconde cosas:

Python
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
riqueza.plot(ax=ax1, title="Valor de 1 € invertido")
drawdown.plot(ax=ax2, title="Drawdown")
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f"{y:.0%}"))
plt.tight_layout()
plt.show()

Una cartera en lugar de activos sueltos

Si repartes el dinero a partes iguales entre los tres activos y rebalanceas cada día, la rentabilidad de la cartera es la media ponderada de las rentabilidades:

Python
pesos = np.array([1/3, 1/3, 1/3])
rent_cartera = rent @ pesos
precio_cartera = (1 + rent_cartera).cumprod()
print(metricas(precio_cartera.to_frame("Cartera")))

Fíjate en la volatilidad: normalmente sale menor que la media de las volatilidades individuales. Es la diversificación en acción, y es exactamente lo que Markowitz convirtió en un problema de optimización. Lo veremos en optimización de carteras de Markowitz.

Qué has aprendido y qué viene después

  • Trabajar con rentabilidades, no con precios, y usar precios ajustados.
  • Anualizar: la rentabilidad con CAGR, la volatilidad con \( \sqrt{252} \).
  • Medir el riesgo de dos formas complementarias: volatilidad y drawdown.
  • Comparar activos con el ratio de Sharpe.

Hasta aquí has mirado al pasado. El siguiente paso es simular el futuro: miles de caminos posibles para un precio con simulación de Monte Carlo en Python.

Quantum Pablo

Sobre Quantum Pablo

Físico de partículas y quant. Enseño finanzas cuantitativas en español desde la intuición y con código: más de 400 alumnos en más de 25 países se han formado en Quantum Club. También en YouTube e Instagram.

Etapa 2 de 4 · Fundamentos en Python

  1. 06Python para finanzas: rentabilidad, volatilidad, Sharpe y drawdown
  2. 07Simulación de Monte Carlo en finanzas con Python
  3. 08El modelo de Black-Scholes explicado desde la intuición (con Python)
  4. 09Value at Risk (VaR) y Expected Shortfall: los 3 métodos en Python
Ver el recorrido completo →