---
title: "0.7.2 Probabilidad, distribuciones y regresión simple"
description: "Normal y t de Student para las colas de las rentabilidades, correlación frente a causalidad, correlaciones espurias y la beta de una regresión simple."
url: "https://pythonparatrading.com/curso-python-trading/nivel-0/probabilidad-distribuciones-y-regresion/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-0/probabilidad-distribuciones-y-regresion.png"
---

# 0.7.2 · Probabilidad y distribuciones, correlación frente a causalidad y regresión simple

> ¿Cada cuánto cabe esperar un día de −3σ? ¿Se mueven juntos dos activos o solo lo parece? ¿Cuánto de TECH se explica por el mercado? Tres preguntas de trading que se responden con lo mínimo de probabilidad, correlación y regresión, y con los errores típicos de cada una.

- Curso: [Nivel 0: Python desde cero y mentalidad de datos](https://pythonparatrading.com/curso-python-trading/nivel-0/)
- Módulo: 0.7 · Matemáticas y estadística mínimas
- Duración: 55 min
- Requisitos: [0.7.1 · Rentabilidades simples y logarítmicas, interés compuesto y estadística descriptiva](https://pythonparatrading.com/curso-python-trading/nivel-0/rentabilidades-y-estadistica-descriptiva/)

## Al terminar esta lección

- Calcular con `scipy.stats` la probabilidad de un día extremo bajo una normal y compararla con la frecuencia observada.
- Ajustar una t de Student a las rentabilidades diarias y explicar por qué describe mejor las colas que la normal.
- Calcular una matriz de correlaciones con rentabilidades, no con precios, y reconocer una correlación espuria.
- Estimar con `statsmodels` la beta de un activo frente al mercado e interpretar la pendiente y el R².

## Probabilidades con una distribución

Una **distribución de probabilidad** describe qué valores puede tomar una variable y con qué frecuencia. En trading la
variable suele ser la rentabilidad de un día, y la pregunta, cuánto de probable es un valor extremo: un día de −3 %,
una semana de −10 %. La respuesta depende de la distribución que se suponga, y ahí está el riesgo.

La más usada es la **normal**, la campana de Gauss, definida por su media $\mu$ y su desviación típica $\sigma$. Cerca
del 68 % de los valores caen a menos de una $\sigma$ de la media y cerca del 95 % a menos de dos. Su función de
distribución acumulada, $F(x) = P(X \le x)$, da la probabilidad de quedar por debajo de un valor. En `scipy.stats`,
cada distribución tiene esa función (`cdf`) y la de la cola contraria (`sf`, $1 - F$).

Con la media y la desviación típica de SINT (lección 0.7.1), la probabilidad de un día por debajo de $\mu - 3\sigma$:

```python
import numpy as np
import pandas as pd
from scipy import stats

precios = pd.read_csv("sint_diario.csv", parse_dates=["fecha"],
                      index_col="fecha")
r = precios["cierre"].pct_change().dropna()
mu, sigma = r.mean(), r.std()
normal = stats.norm(loc=mu, scale=sigma)
umbral = mu - 3 * sigma
print(f"μ = {mu:.4%} · σ = {sigma:.4%} · μ − 3σ = {umbral:.2%}")
print(f"P(R < μ − 3σ) según la normal: {normal.cdf(umbral):.3%}")
```
```output
μ = 0.0342% · σ = 1.1400% · μ − 3σ = -3.39%
P(R < μ − 3σ) según la normal: 0.135%
```

Un 0,135 %: un día de cada 741, unos tres años de sesiones. Si la normal fuera una buena descripción, una caída de más
del 3,4 % en SINT sería un acontecimiento raro. Los datos dicen otra cosa.

## Lo que la normal no ve: las colas

Contar cuántos días quedaron por debajo de cada umbral y compararlo con lo que la normal espera es la prueba más
directa:

```python
n = len(r)
print(" k   umbral   normal   observada   días norm.   días obs.")
for k in (1, 2, 3, 4):
    u = mu - k * sigma
    p_norm, p_obs = normal.cdf(u), (r < u).mean()
    print(f"{k:>2} {u:8.2%} {p_norm:8.3%} {p_obs:10.3%} "
          f"{p_norm * n:11.1f} {int((r < u).sum()):10d}")
```
```output
 k   umbral   normal   observada   días norm.   días obs.
 1   -1.11%  15.866%    14.439%       206.6        188
 2   -2.25%   2.275%     1.997%        29.6         26
 3   -3.39%   0.135%     0.538%         1.8          7
 4   -4.53%   0.003%     0.077%         0.0          1
```

A una y dos desviaciones típicas, normal y datos casi coinciden (incluso hay algo menos de días de −2σ de lo
esperado). A partir de tres, la normal se queda corta: 7 días de −3σ frente a 1,8 esperados, y un día de −4σ (el peor
de la serie) cuando la normal espera 0,04, es decir, uno cada 125 años de sesiones. Es la curtosis en exceso de la lección 0.7.1 vista
como probabilidad, y la razón de que un modelo de riesgo basado en la normal subestime las pérdidas grandes.

La **t de Student** es una alternativa con colas más gruesas. Además de posición y escala tiene un parámetro, los
**grados de libertad** ($\nu$): con pocos (3, 4, 5) las colas son muy gruesas; con muchos, la t se acerca a la normal.
`stats.t.fit` estima los tres parámetros por máxima verosimilitud:

```python
nu, loc, escala = stats.t.fit(r)
t_ajustada = stats.t(nu, loc=loc, scale=escala)
print(f"Grados de libertad: {nu:.1f}")
for k in (3, 4):
    u = mu - k * sigma
    print(f"−{k}σ  normal {normal.cdf(u):.3%}  "
          f"t {t_ajustada.cdf(u):.3%}  observada {(r < u).mean():.3%}")
```
```output
Grados de libertad: 8.8
−3σ  normal 0.135%  t 0.397%  observada 0.538%
−4σ  normal 0.003%  t 0.073%  observada 0.077%
```

Con unos nueve grados de libertad, la t da a un día de −3σ una probabilidad del 0,40 %, tres veces la de la normal y
mucho más cerca de la observada (0,54 %); y a uno de −4σ, el 0,073 %, casi exactamente la frecuencia observada. No es perfecta (con siete
días en la cola, ninguna estimación lo es), pero describe el riesgo mucho mejor que la normal.

<aside class="callout" data-kind="nota">

La escala de la t no es su desviación típica: con $\nu$ grados de libertad, la varianza es
$\text{escala}^2 \cdot \nu / (\nu - 2)$. Por eso, para comparar las dos distribuciones a la misma distancia, la celda usa
siempre los umbrales $\mu - k\sigma$ calculados con los datos.

</aside>

## Correlación: cómo se mueven juntos

La **correlación** de Pearson mide si dos variables se mueven juntas en línea recta. Va de −1 (siempre en sentido
contrario) a +1 (siempre en el mismo), y 0 indica que no hay relación lineal:

$$
\rho_{XY} = \frac{\operatorname{cov}(X, Y)}{\sigma_X\,\sigma_Y}
$$

En una cartera es la cifra que decide cuánto diversifica cada activo (lección 0.7.3). Con las rentabilidades diarias
de los cuatro activos ficticios de `cartera_sint.csv`:

```python
cartera = pd.read_csv("cartera_sint.csv", parse_dates=["fecha"],
                      index_col="fecha")
rc = cartera.pct_change().dropna()
rc.corr().round(2)
```
```output
      RVAR  RFIJ   ORO  TECH
RVAR  1.00 -0.19  0.12  0.85
RFIJ -0.19  1.00  0.19 -0.14
ORO   0.12  0.19  1.00  0.02
TECH  0.85 -0.14  0.02  1.00
```

La diagonal es 1 (cada activo consigo mismo) y la matriz es simétrica. TECH y RVAR, con 0,85, se mueven casi juntos:
TECH es un activo de beta alta frente a la renta variable. RFIJ tiene correlación negativa con los dos, que es lo que
se espera de la renta fija en una cartera. ORO apenas tiene relación con nada.

La misma cuenta hecha con los **precios** en lugar de las rentabilidades da otra matriz, y es un error habitual:

```python
precios_corr = cartera.corr().loc["ORO", "RVAR"]
rent_corr = rc.corr().loc["ORO", "RVAR"]
print(f"ORO y RVAR · precios: {precios_corr:+.2f} · "
      f"rentabilidades: {rent_corr:+.2f}")
```
```output
ORO y RVAR · precios: -0.40 · rentabilidades: +0.12
```

Con los precios, ORO y RVAR parecen moverse en sentido contrario (−0,40); con las rentabilidades, en el mismo, aunque
poco (+0,12). La segunda cifra es la que importa: dice si el día en que sube uno suele subir el otro. La primera solo
dice que en estos cinco años uno acabó subiendo y el otro bajando, algo que puede pasar sin relación alguna.

## Correlaciones espurias

¿Cuánto puede engañar la correlación de los precios? La prueba más limpia es fabricar series que, por construcción,
no tienen nada que ver: pares de **paseos aleatorios** independientes, el modelo más simple de un precio (cada día, un
paso aleatorio desde el cierre anterior). Mil pares de cinco años de sesiones, con semilla:

```python
rng = np.random.default_rng(2026)
pasos = rng.normal(0, 0.01, size=(1000, 1250, 2))
niveles = 100 * np.exp(pasos.cumsum(axis=1))


def corr(x, y):
    return np.corrcoef(x, y)[0, 1]


c_niv = np.array([corr(p[:, 0], p[:, 1]) for p in niveles])
c_ren = np.array([corr(d[:, 0], d[:, 1]) for d in pasos])
print(f"|ρ| > 0,5 con precios:        {np.mean(abs(c_niv) > 0.5):.1%}")
print(f"|ρ| > 0,5 con rentabilidades: {np.mean(abs(c_ren) > 0.5):.1%}")
print(f"Mayor |ρ| con rentabilidades: {abs(c_ren).max():.3f}")
```
```output
|ρ| > 0,5 con precios:        37.6%
|ρ| > 0,5 con rentabilidades: 0.0%
Mayor |ρ| con rentabilidades: 0.104
```

Más de un tercio de los pares de precios independientes tiene una correlación de más de 0,5 en valor absoluto. Con
las rentabilidades, ninguno: la mayor no llega a 0,11. Dos series con tendencia siempre parecen relacionadas, porque
las dos se alejan de su punto de partida y la correlación lo confunde con moverse juntas. Es la versión financiera
de las correlaciones absurdas que recopila Tyler Vigen (enlace en «Recursos»): con suficientes series, siempre hay
alguna pareja que encaja.

Incluso con rentabilidades, una correlación alta no dice **por qué** dos activos se mueven juntos:

```mermaid
flowchart LR
  F[Factor común] --> A[RVAR]
  F --> B[TECH]
  A -.->|correlación| B
```

TECH y RVAR se mueven juntos porque a los dos los mueven las mismas causas (tipos de interés, crecimiento, apetito
por el riesgo), no porque uno cause al otro. Para el trading, la diferencia importa: una relación sin mecanismo puede
desaparecer en cuanto cambia el entorno, y una estrategia de pares construida sobre ella deja de funcionar sin
avisar.

<aside class="callout" data-kind="riesgo">

Las correlaciones cambian, y suelen subir en las crisis: los activos que diversificaban en calma caen juntos cuando
más falta hace la diversificación. Una correlación estimada con cinco años tranquilos no es una garantía para el
siguiente mal año.

</aside>

## Regresión simple: la beta de un activo

La **regresión lineal simple** ajusta una recta a una nube de puntos. Con las rentabilidades de TECH ($y$) frente a
las de RVAR ($x$), el modelo es:

$$
y_t = \alpha + \beta\,x_t + \varepsilon_t
$$

La pendiente $\beta$ (la **beta**) es la sensibilidad: cuánto se mueve TECH de media por cada 1 % de RVAR. $\alpha$ es
lo que queda cuando RVAR no se mueve, y $\varepsilon_t$, el residuo, lo que la recta no explica. Mínimos cuadrados
ordinarios (OLS) elige $\alpha$ y $\beta$ para que la suma de los residuos al cuadrado sea la menor posible.
`statsmodels` lo hace y da además las estadísticas del ajuste:

```python
import statsmodels.api as sm

X = sm.add_constant(rc["RVAR"])
modelo = sm.OLS(rc["TECH"], X).fit()
beta = modelo.params["RVAR"]
print(f"Beta: {beta:.2f}")
print(f"Alfa diaria: {modelo.params['const']:.4%}")
print(f"R²: {modelo.rsquared:.2f}")
beta_formula = rc["TECH"].cov(rc["RVAR"]) / rc["RVAR"].var()
print(f"cov / var: {beta_formula:.2f}")
```
```output
Beta: 1.43
Alfa diaria: -0.0128%
R²: 0.72
cov / var: 1.43
```

`sm.add_constant` añade la columna de unos que estima $\alpha$; sin ella, la recta pasaría por el origen. La beta es
1,43: un día en que RVAR sube un 1 %, TECH sube de media un 1,43 %, y lo mismo a la baja. La última línea muestra que,
en una regresión simple, la beta es la covarianza dividida entre la varianza de $x$. La alfa diaria es prácticamente
cero, y con cinco años de datos no se distingue de cero.

El **R²** (0,72) es la fracción de la varianza de TECH que la recta explica; en la regresión simple es la correlación
al cuadrado ($0{,}85^2 \approx 0{,}72$). La figura de la cabecera muestra la nube de puntos con la recta: alargada en
la diagonal, pero con bastante dispersión alrededor.

`modelo.summary()` imprime una tabla completa (errores estándar, estadísticos t, intervalos de confianza). Para
leerla bien hace falta la inferencia del nivel 2; aquí bastan la pendiente y el R².

La beta tiene un uso directo: **cubrir el riesgo de mercado**. Una posición larga de 10.000 € en TECH se mueve con
RVAR como si fueran 14.300 € de RVAR. Vender esa cantidad de RVAR deja solo la parte de TECH que no depende del
mercado:

```python
cubierta = rc["TECH"] - beta * rc["RVAR"]
def vol(s):
    return s.std() * np.sqrt(252)


print(f"Volatilidad de TECH:          {vol(rc['TECH']):.1%}")
print(f"Volatilidad de TECH cubierta: {vol(cubierta):.1%}")
print(f"Correlación cubierta-RVAR:    "
      f"{cubierta.corr(rc['RVAR']):+.2f}")
```
```output
Volatilidad de TECH:          28.3%
Volatilidad de TECH cubierta: 14.9%
Correlación cubierta-RVAR:    -0.00
```

La cobertura reduce la volatilidad de la posición a casi la mitad y deja una correlación nula con RVAR: lo que queda es
el riesgo propio de TECH, el 28 % de la varianza que el mercado no explica. Es la base de las estrategias neutrales al
mercado, y tiene la misma letra pequeña que la correlación: la beta se estima con el pasado y cambia con el tiempo.

## Ejercicios

1. Calcula con la normal ajustada a SINT la probabilidad de un día de −5 % o peor y cuántos años de 252 sesiones
   habría que esperar, de media, para ver uno. Repite con la t ajustada. ¿Cuál es más prudente para dimensionar una
   posición?

**Solución.**

```python
for nombre, dist in (("normal", normal), ("t", t_ajustada)):
    p = dist.cdf(-0.05)
    print(f"{nombre:<7} P = {p:.5%} · uno cada "
          f"{1 / p / 252:,.0f} años")
```
```output
normal  P = 0.00050% · uno cada 790 años
t       P = 0.03827% · uno cada 10 años
```

La normal hace casi imposible un día de −5 % en SINT: uno cada 790 años. La t lo pone en uno cada diez años, una
escala que un trader sí puede vivir. En los cinco años de datos no hubo ninguno (el peor fue −4,81 %), pero estuvo cerca. Para
dimensionar una posición, la distribución prudente es la que da más probabilidad a las pérdidas grandes: la t.

2. Calcula la correlación móvil de 60 sesiones entre TECH y RVAR con `rolling(60).corr()`. ¿Entre qué valores se mueve?
   ¿Qué dice eso de una correlación estimada con tres meses de datos?

**Solución.**

```python
movil = rc["TECH"].rolling(60).corr(rc["RVAR"]).dropna()
print(f"Mínima {movil.min():.2f} · máxima {movil.max():.2f} · "
      f"media {movil.mean():.2f}")
```
```output
Mínima 0.75 · máxima 0.92 · media 0.85
```

Los datos se generaron con una correlación constante, y aun así la estimación de tres meses va de 0,75 a 0,92
alrededor de su valor medio, 0,85. Esa oscilación es solo error de estimación: con 60 datos, una correlación se conoce con
poca precisión. Con datos reales, a ese ruido se suma que la correlación de verdad también cambia.

3. Estima la beta de ORO frente a RVAR y su R². ¿Serviría RVAR para cubrir una posición en oro?

**Solución.**

```python
m_oro = sm.OLS(rc["ORO"], sm.add_constant(rc["RVAR"])).fit()
print(f"Beta: {m_oro.params['RVAR']:.2f} · R²: {m_oro.rsquared:.3f}")
```
```output
Beta: 0.11 · R²: 0.015
```

Una beta pequeña y un R² cercano a cero: RVAR explica el 1,5 % de la varianza del oro. Vender RVAR contra una
posición en oro no reduciría su riesgo, solo añadiría otro. Una beta solo sirve para cubrir cuando el R² es alto.

## Autoevaluación

1. Con rentabilidades normales, un día por debajo de la media menos tres desviaciones típicas tiene una probabilidad del 0,135 %. En SINT se observa un 0,54 %. ¿Qué indica?

   - A. Que los datos tienen un error
   - B. Que las colas son más gruesas que las de la normal: los días extremos son unas cuatro veces más frecuentes de lo que predice
   - C. Que la volatilidad está mal calculada
   - D. Nada: con 1.302 días, cualquier diferencia es ruido

   *Respuesta B: Que las colas son más gruesas que las de la normal: los días extremos son unas cuatro veces más frecuentes de lo que predice.* Siete días observados frente a menos de dos esperados es una diferencia grande para ese tamaño de muestra. Es la curtosis en exceso de la lección 0.7.1 vista como probabilidad: la normal subestima los días malos.

2. ¿Qué parámetro de la t de Student controla el grosor de las colas?

   - A. La media
   - B. Los grados de libertad: cuantos menos, más gruesas; con muchos, la t se parece a la normal
   - C. La escala
   - D. El tamaño de la muestra

   *Respuesta B: Los grados de libertad: cuantos menos, más gruesas; con muchos, la t se parece a la normal.* Con pocos grados de libertad (3, 4, 5) la t da mucha más probabilidad a los extremos; a partir de unos 30 es casi una normal. El ajuste a SINT da unos 9.

3. Los precios de dos activos tienen una correlación de −0,40 en cinco años. Sus rentabilidades diarias, de 0,12. ¿Qué cifra describe cómo se mueven juntos?

   - A. La de los precios, porque usa los niveles reales
   - B. La de las rentabilidades: la de los precios mezcla las tendencias de cada serie y puede ser grande sin relación alguna
   - C. La media de las dos
   - D. Ninguna: la correlación no sirve para precios

   *Respuesta B: La de las rentabilidades: la de los precios mezcla las tendencias de cada serie y puede ser grande sin relación alguna.* Dos series con tendencia (como dos paseos aleatorios independientes) tienen a menudo correlaciones grandes en niveles. La correlación de las rentabilidades mide si los movimientos de cada día van en la misma dirección.

4. La regresión de las rentabilidades de TECH sobre las de RVAR da una pendiente de 1,43 y un R² de 0,72. ¿Qué afirmación es correcta?

   - A. RVAR causa los movimientos de TECH
   - B. Un día en que RVAR sube un 1 %, TECH sube de media un 1,43 %, y el 72 % de la varianza de TECH va con RVAR
   - C. TECH gana un 43 % más que RVAR cada año
   - D. El 72 % de los días TECH sube

   *Respuesta B: Un día en que RVAR sube un 1 %, TECH sube de media un 1,43 %, y el 72 % de la varianza de TECH va con RVAR.* La pendiente (beta) es la sensibilidad media; el R², la parte de la varianza que la recta explica. Ninguna de las dos dice nada de causas ni de rentabilidad a largo plazo.

## Ideas clave

- `scipy.stats` da para cada distribución la función de distribución (`cdf`) y la cola (`sf`): con ellas se calcula la probabilidad de un día extremo.
- Las rentabilidades tienen colas más gruesas que la normal. Un día de −3σ es bastante más frecuente de lo que la normal predice; la t de Student con pocos grados de libertad lo describe mejor.
- Las correlaciones se calculan con rentabilidades. Con precios, dos series con tendencia parecen relacionadas aunque sean independientes.
- Correlación no es causalidad: dos activos pueden moverse juntos porque los mueve un tercero, y la relación puede cambiar o desaparecer.
- La beta de una regresión simple mide la sensibilidad de un activo al mercado; el R², cuánto de su varianza explica. Sirve para cubrir el riesgo de mercado de una posición.

## Recursos

### Research

- [Procesos Estocásticos - Paseos Aleatorios](https://pythonparatrading.com/procesos-estocasticos/) · Research-0005 · 12 min

### Documentación

- [SciPy: scipy.stats.t (distribución t de Student)](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.t.html) · docs.scipy.org
- [statsmodels: regresión lineal (OLS)](https://www.statsmodels.org/stable/regression.html) · statsmodels.org
- [Tyler Vigen, Spurious Correlations](https://www.tylervigen.com/spurious-correlations) · tylervigen.com

### Datos

- [Datos del nivel 0: cierres de la cartera ficticia (CSV)](https://pythonparatrading.com/curso/datos/nivel-0/cartera_sint.csv) · Descarga · cartera_sint.csv

Lección anterior: [0.7.1 · Rentabilidades simples y logarítmicas, interés compuesto y estadística descriptiva](https://pythonparatrading.com/curso-python-trading/nivel-0/rentabilidades-y-estadistica-descriptiva/)

Lección siguiente: [0.7.3 · Álgebra lineal intuitiva: vectores y matrices como preparación para carteras](https://pythonparatrading.com/curso-python-trading/nivel-0/algebra-lineal-para-carteras/)
