Predicción conforme para el dimensionamiento de posiciones consciente del riesgo
Toda fórmula de dimensionamiento de posiciones necesita una estimación de la incertidumbre. El criterio de Kelly necesita una probabilidad de ganancia y una relación de pago (ver El criterio de Kelly para estrategias). La optimización media-varianza necesita una matriz de covarianza. El VaR necesita una distribución de rendimientos. Todo esto requiere supuestos sobre el proceso generador de datos — supuestos que los mercados financieros violan de forma rutinaria.
La predicción conforme ofrece algo distinto: intervalos de predicción con garantías de cobertura de muestra finita, sin ningún supuesto de distribución paramétrica. Si pides una cobertura del 90%, obtienes al menos un 90% de cobertura, sin importar si los rendimientos son gaussianos, de colas pesadas, asimétricos o heterocedásticos. El único requisito es la intercambiabilidad (o condiciones más débiles, como veremos).
Este artículo cubre la teoría, las variantes clave y una implementación práctica para el dimensionamiento de posiciones en Python.
La idea central: puntuaciones de no conformidad

La predicción conforme funciona midiendo cuán "extraña" es una nueva observación en relación con los datos pasados. Esa rareza se cuantifica mediante una puntuación de no conformidad: cualquier función que mida cuán mal se ajusta un punto de datos al patrón observado en el resto de los datos.
Para regresión (predicción de un valor continuo como los rendimientos), la puntuación de no conformidad más simple es el residuo absoluto:
donde es cualquier predictor puntual (regresión lineal, random forest, red neuronal — no importa cuál) y es un punto de datos.
La observación clave: si los puntos de datos son intercambiables, entonces el rango de entre se distribuye uniformemente sobre . Este es un hecho puramente combinatorio — no requiere ningún supuesto sobre la distribución de o .
A partir de esta uniformidad de rango, podemos construir intervalos de predicción con cobertura de muestra finita.
Predicción conforme split (split conformal)

La predicción conforme split (Papadopoulos et al., 2002; Lei et al., 2018) es la variante más práctica. El algoritmo es simple:
Paso 1. Divide los datos en un conjunto de entrenamiento y un conjunto de calibración .
Paso 2. Ajusta cualquier modelo sobre .
Paso 3. Calcula las puntuaciones de no conformidad en el conjunto de calibración:
Paso 4. Para un nivel de no cobertura deseado , toma como el cuantil empírico de . Concretamente, es el residuo -ésimo más pequeño (y siempre que , es decir, para muy pequeño).
Paso 5. El intervalo de predicción para un nuevo punto es:
La garantía de cobertura
Bajo la intercambiabilidad de los datos de calibración y del nuevo punto de prueba:
Esta es una garantía de muestra finita, no una aproximación asintótica. Se cumple para cualquier modelo , cualquier distribución de los datos y cualquier tamaño de muestra . Si es un predictor pésimo, los intervalos simplemente serán más anchos. La garantía de cobertura se mantiene de todos modos.
También existe un límite superior cuando las puntuaciones no tienen empates: , de modo que la cobertura no es inútilmente conservadora.
Por qué esto importa para el trading
Los intervalos de predicción tradicionales, digamos de una regresión lineal, asumen errores gaussianos. Un intervalo gaussiano calibrado sobre la mayor parte de los datos puede juzgar mal las colas cuando los residuos reales son de colas pesadas (por ejemplo, una t de Student con pocos grados de libertad): la masa central es más delgada que la gaussiana, así que un intervalo gaussiano ajustado por varianza sobrecubre cerca del centro pero subcubre en las colas, y uno ajustado a las colas hace lo contrario. El punto no es un único número mágico — es que la cobertura realizada de un intervalo paramétrico depende de un supuesto de distribución que no has verificado.
Los intervalos de predicción conformes evitan esto. Se ensanchan automáticamente cuando el modelo está incierto y mantienen la cobertura marginal independientemente de la distribución real del error. Para un trader, esto significa:
- Si dimensionas las posiciones de forma inversamente proporcional al ancho del intervalo, reduces automáticamente la exposición cuando el modelo está incierto.
- La garantía de cobertura significa que tus estimaciones de riesgo son honestas: si dices "el 90% de los rendimientos realizados caerán dentro de este intervalo", esa afirmación es estadísticamente válida (marginalmente, bajo intercambiabilidad).
Conformal completo y jackknife+
La conformidad split es simple pero desperdicia datos: el conjunto de calibración no puede usarse para entrenar. Dos alternativas abordan esto.
Predicción conforme completa (full conformal)
La predicción conforme completa (Vovk et al., 2005) usa todos los datos tanto para entrenamiento como para calibración. Para cada valor candidato de :
- Aumenta el conjunto de datos con .
- Reajusta el modelo sobre el conjunto de datos aumentado.
- Calcula todas las puntuaciones de no conformidad.
- Incluye en el conjunto de predicción si la puntuación de no es demasiado extrema.
El conjunto de predicción es:
donde son las puntuaciones de no conformidad calculadas con el conjunto de datos aumentado.
El conformal completo ofrece los intervalos más ajustados, pero es computacionalmente prohibitivo para la mayoría de los modelos — hay que reajustar el modelo para cada candidato en una malla. Para una predicción de rendimiento, esto podría significar miles de reajustes por predicción.
Jackknife+ (Barber et al., 2021)
El jackknife+ logra un equilibrio. Utiliza residuos de dejar-uno-fuera (LOO) pero tiene en cuenta la variabilidad del modelo ajustado a través de los pliegues LOO.
Sea el modelo entrenado con todos los datos excepto el punto . Define la puntuación de no conformidad LOO con el residuo absoluto único:
El intervalo de predicción jackknife+ se construye entonces a partir de las predicciones LOO en el punto de prueba, ensanchado por estos residuos:
Aquí denota el valor -ésimo más pequeño del conjunto , y el valor -ésimo más pequeño. El límite inferior resta el residuo de cada predicción LOO; el límite superior lo suma. Esa asimetría es todo el punto — colapsar ambos límites a pondría el límite inferior por encima de la predicción, lo cual es incorrecto.
El jackknife+ ofrece una garantía de cobertura de al menos (ligeramente más débil que el de la conformidad split), pero usa todos los datos tanto para entrenamiento como para calibración. En la práctica, la cobertura suele acercarse a .
Para modelos de trading entrenados con datos limitados (por ejemplo, modelos específicos de régimen con solo unos cientos de observaciones), el jackknife+ suele ser la mejor opción: no sacrifica datos escasos para la calibración. El costo son reajustes del modelo.
El problema con las series temporales financieras: no intercambiabilidad

La garantía conforme estándar requiere intercambiabilidad: la distribución conjunta de es invariante bajo permutaciones. Para datos i.i.d., esto se cumple trivialmente.
Las series temporales financieras no son intercambiables. Los rendimientos exhiben:
- Agrupamiento de volatilidad (volatility clustering): Los periodos de alta volatilidad siguen a periodos de alta volatilidad (efectos GARCH).
- Momentum y reversión a la media: Autocorrelación en los rendimientos o en los rendimientos al cuadrado.
- Cambios de régimen: La distribución se desplaza con el tiempo (mercados alcistas frente a bajistas).
Si aplicas ingenuamente la conformidad split a una serie temporal — usando una división de calibración aleatoria —, violas la estructura temporal. Las puntuaciones de calibración de un tranquilo 2017 no reflejarán la incertidumbre de un volátil 2020. Tu garantía de cobertura se rompe.
Inferencia conforme adaptativa (ACI)

Gibbs y Candes (2021, NeurIPS) introdujeron la inferencia conforme adaptativa (ACI) para manejar el desplazamiento de distribución y los datos no intercambiables. La idea es elegante: en lugar de usar un nivel de cobertura fijo, adaptar en línea el nivel objetivo de no cobertura según si los intervalos recientes cubrieron el resultado real, y re-derivar el cuantil a partir de la distribución de puntuaciones en cada paso.
El algoritmo ACI
ACI no ajusta directamente el ancho del intervalo. Mantiene un parámetro de no cobertura adaptativo y recalcula el cuantil conforme a partir de él. En cada paso de tiempo :
- Calcula el umbral conforme como el cuantil empírico del conjunto de residuos actual (las puntuaciones de calibración más cualquier puntuación realizada hasta el momento): .
- Observa las características , produce el intervalo .
- Observa el valor real y calcula el indicador de error .
- Actualiza el nivel (no el ancho):
donde es un tamaño de paso y es la no cobertura objetivo. Si un intervalo falló (), se reduce, lo que empuja el siguiente cuantil hacia arriba y ensancha el intervalo; si cubrió, crece y los intervalos se estrechan. Fundamentalmente, aquí está en unidades de probabilidad — ajusta un nivel en , no el umbral en unidades de rendimiento crudas —, de modo que el mismo se comporta razonablemente ya sea que los residuos estén del orden de o no.
Garantía de cobertura para ACI
ACI ofrece una garantía de cobertura a largo plazo que no depende de un modelo de distribución:
Dado que está acotado a , el numerador está acotado por por una constante, así que el lado derecho es y la frecuencia empírica de no cobertura converge a . La afirmación precisa: ACI garantiza que la frecuencia empírica de no cobertura a largo plazo converge a para secuencias arbitrarias (incluso adversariales), siempre que los niveles adaptados permanezcan acotados, lo cual el clip impone. Es una garantía sobre la frecuencia de cobertura, no sobre la informatividad del intervalo: bajo una secuencia verdaderamente adversarial, los intervalos pueden crecer de forma no informativa mientras siguen alcanzando el objetivo de cobertura.
ACI de ajuste dinámico (DtACI)
Gibbs y Candes (2024, JMLR) refinaron ACI con ajuste dinámico del tamaño de paso . En lugar de fijar , mantienen un conjunto de candidatos y los combinan mediante una regla de agregación de expertos, favoreciendo el cuya cobertura reciente esté más cerca del objetivo.
Esto aborda un problema práctico: un grande se adapta rápidamente a los cambios de régimen pero produce anchos de intervalo volátiles; un pequeño es estable pero lento para adaptarse. DtACI equilibra esto automáticamente.
Por qué esto importa para el trading
Considera una estrategia de creación de mercado que usa un modelo de predicción de rendimientos. Durante mercados tranquilos, los intervalos conformes son estrechos: el modelo está seguro, y puedes tomar posiciones más grandes. Cuando la volatilidad se dispara (temporada de resultados, anuncios de la FOMC, choques geopolíticos), el nivel ACI se adapta y los intervalos se ensanchan en pocos pasos de tiempo. Tu dimensionamiento de posiciones se reduce en respuesta, sin ningún modelo de volatilidad explícito ni lógica de detección de régimen.
Esto es cuantificación de incertidumbre como señal de primera clase, no como una idea de último momento.
Dimensionamiento de posiciones con intervalos conformes

Conectemos ahora la predicción conforme con el dimensionamiento concreto de posiciones. La variable clave es la media anchura del intervalo de predicción relativa al caso simétrico de residuo absoluto. Con el intervalo simétrico , el ancho total es . Para mantener las fórmulas y el código coherentes, medimos todo frente al ancho total a lo largo del texto.
Dimensionamiento por ancho inverso
El enfoque más simple: dimensionar de forma inversamente proporcional al ancho del intervalo.
donde es una constante de escala calibrada a tu presupuesto de riesgo. Cuando el modelo está seguro (intervalo estrecho), tomas una posición más grande. Cuando está incierto (intervalo ancho), tomas una más pequeña.
Esto es análogo al volatility targeting (), pero con una diferencia crucial: el ancho del intervalo conforme es una medida de incertidumbre libre de distribución, no una estimación paramétrica de volatilidad. Captura la incertidumbre predictiva bajo la garantía de cobertura, no solo la varianza de los rendimientos.
Dimensionamiento por edge ratio y el filtro de no operar
El dimensionamiento por ancho inverso puro ignora la fuerza de la propia señal. Un refinamiento natural escala por el edge ratio — la predicción puntual relativa al ancho del intervalo:
Este es un análogo conforme de una relación señal-ruido: rendimiento esperado dividido por una medida de incertidumbre libre de distribución. Lo usamos tanto para el dimensionamiento como para un filtro de no operar.
El filtro es principiado. Si el intervalo abarca el cero,
entonces el intervalo de predicción incluye tanto rendimientos positivos como negativos: el rendimiento realizado puede plausiblemente tener el signo opuesto a tu predicción. Define un umbral mínimo de edge y opera solo cuando . Ten en cuenta que el contenido geométrico de " suficientemente grande como para que el intervalo ya no abarque el cero" es exactamente (ya que el intervalo despeja el cero cuando ). Elige en la escala real de mediante backtesting; para residuos de rendimientos diarios, suele estar muy por debajo de , así que un diminuto puede admitir casi todas las operaciones y uno grande puede no admitir ninguna. Calíbralo con tus datos.
Sobre el "Kelly conforme"
Es tentador acoplar intervalos conformes a la fracción de Kelly . Pero ya es una fracción completa y acotada derivada de una probabilidad de ganancia y una relación de pago ; multiplicarla por una relación no acotada como no tiene justificación en teoría de la decisión — puede superar 1 o invertir el signo independientemente de , y cuenta dos veces el edge que ya codifica. Por eso no presentamos un multiplicador de "Kelly conforme".
Si quieres derivar Kelly a partir del intervalo, debes obtener realmente y de él, lo que requiere un supuesto explícito sobre la distribución dentro del intervalo (los intervalos conformes deliberadamente no dicen nada sobre eso — ver Limitaciones). Por ejemplo, bajo una forma supuesta dentro del intervalo, puedes aproximar y una relación de pago a partir de la geometría del intervalo — pero señala ese supuesto en voz alta, porque reintroduce exactamente el compromiso paramétrico que la predicción conforme pretendía evitar.
La alternativa honesta y con pocos supuestos es usar el edge ratio como reducción fraccional de Kelly: aumentar el tamaño cuando el rendimiento esperado es grande en relación con el intervalo, reducirlo cuando es pequeño, y aplicar esto sobre un límite máximo estricto — explícitamente como una heurística, no como "la fracción de Kelly".
Implementación en Python
Aquí una implementación práctica. Mostramos tanto la ruta split/prefit como la ruta temporal (EnbPI), ya que todo el propósito de este artículo es que los datos financieros no son intercambiables.
Configuración y preparación de datos
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import KFold
from mapie.regression import MapieRegressor, MapieTimeSeriesRegressor
from mapie.subsample import BlockBootstrap
def prepare_features(prices: pd.Series, lookback: int = 20) -> pd.DataFrame:
"""Create features from a price series."""
df = pd.DataFrame()
returns = prices.pct_change()
for lag in range(1, lookback + 1):
df[f"ret_lag_{lag}"] = returns.shift(lag)
for window in [5, 10, 20]:
df[f"ret_mean_{window}"] = returns.rolling(window).mean().shift(1)
df[f"ret_std_{window}"] = returns.rolling(window).std().shift(1)
df[f"ret_skew_{window}"] = returns.rolling(window).skew().shift(1)
df["target"] = returns.shift(-1)
return df.dropna()
Conformal split con MAPIE (prefit)
Para conformal split/prefit, cv="prefit" requiere method="base" (el estimador conforme split ingenuo). La opción method="plus" es el estimador CV+/jackknife+ y es incompatible con cv="prefit" — necesita un objeto de validación cruzada en su lugar. Aquí usamos la combinación correcta y estandarizamos el dimensionamiento sobre el ancho total.
def split_conformal_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3, # scaling constant, in width units
max_position: float = 1.0,
min_edge: float = 0.05, # threshold on |pred| / width
) -> pd.DataFrame:
"""
Position sizing using split (prefit) conformal prediction intervals.
Sizing rule (consistent with the prose):
edge_t = |pred_t| / width_t
size_t = clip(k / width_t, 0, max_position) # inverse-width
size_t = 0 if edge_t < min_edge # no-trade filter
"""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.6)
n_cal = int(len(X) * 0.2)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_cal, y_cal = X.iloc[n_train:n_train + n_cal], y.iloc[n_train:n_train + n_cal]
X_test, y_test = X.iloc[n_train + n_cal:], y.iloc[n_train + n_cal:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
base_model.fit(X_train, y_train)
mapie = MapieRegressor(estimator=base_model, cv="prefit", method="base")
mapie.fit(X_cal, y_cal)
y_pred, y_intervals = mapie.predict(X_test, alpha=alpha)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred,
"lower": lower,
"upper": upper,
"width": width,
"edge_ratio": edge_ratio,
"position_size": position_size,
"actual": y_test.values,
}, index=X_test.index)
Conformal de series temporales con EnbPI
Dado que los rendimientos no son intercambiables, la división aleatoria/prefit anterior es solo una línea base. El MapieTimeSeriesRegressor de MAPIE con method="enbpi" (Xu & Xie, 2021) usa bootstrap por bloques y actualización de residuos diseñados para la dependencia temporal. Esta es la herramienta que coincide con el propio argumento de este artículo.
def enbpi_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3,
max_position: float = 1.0,
min_edge: float = 0.05,
) -> pd.DataFrame:
"""Position sizing with EnbPI (block-bootstrap, time-series conformal)."""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.7)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_test, y_test = X.iloc[n_train:], y.iloc[n_train:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
cv = BlockBootstrap(n_resamplings=30, length=20, overlapping=False, random_state=42)
mapie_ts = MapieTimeSeriesRegressor(base_model, method="enbpi", cv=cv, agg_function="mean")
mapie_ts.fit(X_train, y_train)
y_pred, y_intervals = mapie_ts.predict(X_test, alpha=alpha, ensemble=True)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": position_size, "actual": y_test.values,
}, index=X_test.index)
Inferencia conforme adaptativa (en línea)
Para el trading en vivo implementamos ACI real desde cero: mantener el nivel de no cobertura , actualizarlo de forma aditiva y re-derivar el cuantil a partir del conjunto de residuos en cada paso. Dos detalles de muestra finita importan:
- Usa el estadístico de orden, no un cuantil interpolado.
np.quantileinterpola por defecto, lo que puede caer justo por debajo del rango requerido y causar subcobertura; pasamethod="higher"(equivalente a"inverted_cdf"). - Cuando el rango requerido excede ( pequeño, cobertura objetivo alta), el umbral correcto es (intervalo = toda la recta), no un recorte al residuo más grande. Recortar rompe silenciosamente la garantía .
class AdaptiveConformalSizer:
"""
Online position sizing with Adaptive Conformal Inference (Gibbs & Candes,
2021). Updates the miscoverage LEVEL alpha_t and re-derives the quantile
from the residual set each step -- gamma is in probability units.
"""
def __init__(self, base_model, alpha=0.1, gamma=0.02,
max_position=1.0, min_edge=0.05, k=1e-3):
self.base_model = base_model
self.alpha_target = alpha # target miscoverage
self.alpha_t = alpha # adaptive miscoverage level
self.gamma = gamma # step size, in [0, 1] units
self.max_position = max_position
self.min_edge = min_edge
self.k = k
self.residuals = []
self.q_hat = np.inf
self.coverage_history = []
@staticmethod
def _conformal_quantile(residuals, alpha_t):
"""(1 - alpha_t) conformal quantile via the order statistic."""
n = len(residuals)
if n == 0:
return np.inf
rank = int(np.ceil((1.0 - alpha_t) * (n + 1)))
if rank > n: # required order statistic does not exist
return np.inf # -> interval is the whole line
level = rank / n
return float(np.quantile(residuals, min(level, 1.0), method="higher"))
def calibrate(self, X_cal, y_cal):
preds = self.base_model.predict(X_cal)
self.residuals = list(np.abs(np.asarray(y_cal) - preds))
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
def predict_and_size(self, X_t) -> dict:
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
lower, upper = mu_hat - self.q_hat, mu_hat + self.q_hat
width = upper - lower # = 2 * q_hat
edge_ratio = abs(mu_hat) / width if np.isfinite(width) and width > 0 else 0.0
if edge_ratio < self.min_edge:
size = 0.0
else:
size = min(self.k / width, self.max_position) if width > 0 else 0.0
return {
"prediction": mu_hat, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": size * np.sign(mu_hat),
"alpha_t": self.alpha_t, "q_hat": self.q_hat,
}
def update(self, X_t, y_t: float):
"""ACI update: adapt the LEVEL, then re-derive the quantile."""
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
residual = abs(y_t - mu_hat)
covered = int(residual <= self.q_hat)
err_t = 1 - covered
self.alpha_t = float(np.clip(
self.alpha_t + self.gamma * (self.alpha_target - err_t), 0.0, 1.0
))
self.residuals.append(residual)
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
self.coverage_history.append(covered)
@property
def running_coverage(self) -> float:
if not self.coverage_history:
return float("nan")
return float(np.mean(self.coverage_history))
Uniéndolo todo: bucle de backtest
def backtest_aci_sizing(prices: pd.Series, alpha=0.1, gamma=0.02) -> pd.DataFrame:
"""Backtest position sizing with Adaptive Conformal Inference."""
df = prepare_features(prices)
X = df.drop(columns=["target"]).values
y = df["target"].values
index = df.index
n_train = int(len(X) * 0.5)
n_cal = int(len(X) * 0.2)
X_train, y_train = X[:n_train], y[:n_train]
X_cal, y_cal = X[n_train:n_train + n_cal], y[n_train:n_train + n_cal]
X_test, y_test = X[n_train + n_cal:], y[n_train + n_cal:]
test_index = index[n_train + n_cal:]
model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
model.fit(X_train, y_train)
sizer = AdaptiveConformalSizer(base_model=model, alpha=alpha, gamma=gamma)
sizer.calibrate(X_cal, y_cal)
records = []
for i in range(len(X_test)):
result = sizer.predict_and_size(X_test[i])
result["actual"] = y_test[i]
result["pnl"] = result["position_size"] * y_test[i]
records.append(result)
sizer.update(X_test[i], y_test[i]) # online residual + level update
results = pd.DataFrame(records, index=test_index)
results["cumulative_pnl"] = results["pnl"].cumsum()
results["running_coverage"] = (
((results["lower"] <= results["actual"]) &
(results["actual"] <= results["upper"])).expanding().mean()
)
return results
Evaluación de los resultados
def evaluate(results: pd.DataFrame, alpha: float = 0.1):
"""Print evaluation metrics for conformal position sizing."""
covered = ((results["actual"] >= results["lower"]) &
(results["actual"] <= results["upper"]))
print(f"Target coverage: {1 - alpha:.1%}")
print(f"Empirical coverage: {covered.mean():.1%}")
print(f"Mean interval width: {results['width'].mean():.6f}")
print(f"Median position size: {results['position_size'].abs().median():.4f}")
print(f"Fraction no-trade: {(results['position_size'] == 0).mean():.1%}")
print(f"Total PnL (bps): {results['pnl'].sum() * 10000:.1f}")
sd = results['pnl'].std()
sharpe = results['pnl'].mean() / sd * np.sqrt(252) if sd > 0 else float('nan')
print(f"Sharpe (annualized): {sharpe:.2f}")
Consideraciones prácticas
Elección de la puntuación de no conformidad
El residuo absoluto es el predeterminado, pero asume que el intervalo de predicción debe ser simétrico alrededor de la predicción puntual. Para rendimientos financieros, los intervalos asimétricos a menudo tienen más sentido:
- Regresión cuantílica conformalizada (CQR): Ajusta regresores cuantílicos en los niveles y , luego conformaliza (Romano et al., 2019). Los intervalos adaptan su forma a la distribución local — más anchos a la baja durante drawdowns, más anchos al alza durante rallies. (Con CQR el intervalo ya no es simétrico, así que es el ancho genuino superior-menos-inferior — sigue usando como denominador en todas partes.)
- Puntuaciones normalizadas: , donde es una estimación de volatilidad local. Esto produce intervalos condicionalmente adaptativos — más estrechos durante regímenes de baja volatilidad, más anchos durante los de alta volatilidad — manteniendo la cobertura marginal.
Tamaño del conjunto de calibración
La garantía de cobertura de la conformidad split se cumple para cualquier tamaño de conjunto de calibración , pero el ancho del intervalo disminuye a medida que aumenta. Para un muy pequeño, el estadístico de orden requerido puede no existir, en cuyo caso el umbral honesto es (un intervalo no informativo pero válido) — recortar al residuo más grande anula silenciosamente la garantía. Para trading práctico:
- puntos de calibración da intervalos razonablemente ajustados.
- es preferible para una estimación estable de cuantiles.
- Con ACI, el conjunto de calibración es solo para inicialización; las actualizaciones de nivel en línea se encargan del resto.
Frecuencia de reentrenamiento
El modelo base puede quedar obsoleto. Dos enfoques:
- Reentrenar periódicamente (por ejemplo, mensualmente) y recalibrar el cuantil conforme.
- Usar ACI y dejar que el nivel adaptativo compense la obsolescencia del modelo — los intervalos se ensanchan automáticamente a medida que crecen los residuos del modelo.
La opción 2 es más simple y sorprendentemente efectiva. La capa conforme actúa como una red de seguridad: incluso si el modelo se degrada, la frecuencia de cobertura ACI a largo plazo sigue convergiendo hacia el objetivo.
Costos de transacción
Los intervalos conformes interactúan con los costos de transacción de una manera útil. Cuando los intervalos son anchos (alta incertidumbre), las posiciones son pequeñas, por lo que el turnover es bajo. Cuando los intervalos se estrechan (el modelo está seguro), las posiciones crecen — pero es más probable que el modelo acierte, así que vale la pena pagar el turnover.
También puedes incorporar los costos de transacción directamente en el filtro de no operar:
Esto asegura que solo operas cuando el edge neto esperado supera un umbral escalado por el ancho conforme — usando el mismo denominador que en todas partes.
Comparación con métodos tradicionales
| Propiedad | IC gaussiano | IC bootstrap | IP conforme |
|---|---|---|---|
| Supuesto de distribución | Errores normales | i.i.d. + asintótico | Ninguno (intercambiabilidad) |
| Garantía de muestra finita | No | No | Sí |
| Funciona con cualquier modelo | No | Sí | Sí |
| Se adapta a la heterocedasticidad | No | Parcialmente | Con CQR / puntuaciones normalizadas |
| Maneja el desplazamiento de distribución | No | No | Variante ACI / EnbPI |
| Costo computacional | Bajo | Medio | Split: bajo; jackknife+: reajustes; completo: prohibitivo |
El bootstrap es "asintótico" solo en su garantía; sigue asumiendo datos i.i.d./intercambiables y suavidad, por lo que su celda de supuesto de distribución no es "libre de supuestos". Y la única columna "conforme" oculta costos muy diferentes entre las variantes, lo que la fila de costos ahora detalla.
Limitaciones
La predicción conforme no es magia. Limitaciones clave para el trading:
-
Cobertura marginal, no condicional. La garantía es que se cumple de forma marginal — promediada sobre la aleatoriedad tanto en como en . Para un valor de condicionamiento específico , la cobertura local puede estar por encima o por debajo de . La regresión cuantílica conformalizada aborda esto parcialmente.
-
La intercambiabilidad es un requisito real. Para la conformidad split, los datos de calibración y el punto de prueba deben ser intercambiables. Los datos financieros no lo son. ACI y EnbPI relajan esto a una garantía a largo plazo, pero la cobertura a corto plazo puede desviarse.
-
El ancho del intervalo no es una densidad de probabilidad. Un intervalo conforme te dice dónde caerá con probabilidad , pero no dice nada sobre la distribución dentro del intervalo. No es un sustituto de una distribución predictiva completa — precisamente por eso derivar una y una de Kelly a partir de él requiere un supuesto adicional y explícito.
-
Basura entra, más ancho sale. Un mal modelo base produce intervalos anchos. La predicción conforme garantiza cobertura, pero no utilidad. Si tu modelo no tiene poder predictivo, los intervalos serán tan anchos que el dimensionador de posiciones nunca operará.
Resumen
La predicción conforme ofrece un marco riguroso y libre de distribución para la cuantificación de la incertidumbre que encaja de forma natural en el dimensionamiento de posiciones:
- Conformidad split para calibración estática, sin conexión, con cobertura de muestra finita.
- Jackknife+ cuando los datos de calibración son escasos y quieres usar todas las observaciones (al costo de reajustes).
- Inferencia conforme adaptativa / EnbPI para trading en línea con mercados no estacionarios.
- Dimensionamiento de posiciones mediante el ancho inverso del intervalo y un filtro de no operar por edge ratio — y, si te decantas por Kelly, solo después de derivar y honestamente, no mediante un multiplicador injustificado.
La ventaja clave frente a las alternativas paramétricas: nunca necesitas especificar ni validar un supuesto de distribución paramétrico. Los intervalos son honestos por construcción (marginalmente, bajo intercambiabilidad). Para un trader sistemático, esto significa una fuente menos de riesgo de modelo — y en un negocio donde el riesgo de modelo es existencial, eso importa.
Referencias:
- Papadopoulos, H., Proedrou, K., Vovk, V., Gammerman, A. (2002). Inductive confidence machines for regression. ECML.
- Vovk, V., Gammerman, A., Shafer, G. (2005). Algorithmic Learning in a Random World. Springer.
- Lei, J., G'Sell, M., Rinaldo, A., Tibshirani, R.J., Wasserman, L. (2018). Distribution-free predictive inference for regression. JASA.
- Xu, C., Xie, Y. (2021). Conformal prediction interval for dynamic time-series (EnbPI). ICML.
- Barber, R.F., Candes, E.J., Ramdas, A., Tibshirani, R.J. (2021). Predictive inference with the jackknife+. Annals of Statistics.
- Gibbs, I., Candes, E.J. (2021). Adaptive conformal inference under distribution shift. NeurIPS.
- Gibbs, I., Candes, E.J. (2024). Conformal inference for online prediction with arbitrary distribution shifts. JMLR.
- Romano, Y., Patterson, E., Candes, E.J. (2019). Conformalized quantile regression. NeurIPS.
- Cordier, T. et al. (2022). MAPIE: an open-source library for distribution-free uncertainty quantification. arXiv:2207.12274.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.