Synthetic Control Methods for Evaluating Trading Strategies
Esta serie ha dedicado mucho espacio a una ruta hacia una ventaja falsa: la selección. El ratio de Sharpe deflactado valora al ganador de una búsqueda. La probabilidad de sobreajuste del backtest valora la búsqueda misma. El resultado negativo honesto es lo que ocurre cuando aplicas ambos análisis a una estrategia con la que realmente querías operar. Ese aparato responde bien a una pregunta: ¿encontré esto porque busqué demasiadas veces?
Pero no toca una segunda ruta, completamente distinta: la confusión causal. Cambiaste una sola cosa — desplegaste un nuevo algoritmo de ejecución, cambiaste un parámetro, reaccionaste a un régimen — y el rendimiento mejoró. No hubo búsqueda. No hubo un problema de pruebas múltiples. Y aun así la mejora puede no tener nada que ver contigo, porque la volatilidad se duplicó la semana en que lo desplegaste. Ninguna deflación detecta esto: la deflación corrige los ensayos y tú solo ejecutaste uno.
El Método de Control Sintético (SCM), desarrollado por Abadie y Gardeazabal (2003) y perfeccionado por Abadie, Diamond y Hainmueller (2010, 2015), ataca directamente la confusión causal. Construye un contrafactual — una combinación ponderada de instrumentos que no tocaste, elegida para seguir de cerca al instrumento tratado antes de la intervención. La divergencia posterior entre ambos es la estimación del efecto. Sobre todo, SCM incorpora un criterio de falsificación que indica cuándo no puede producir un contrafactual creíble y un valor p basado en permutaciones que no exige creer en ninguna historia asintótica.
Por qué fallan las alternativas obvias

La comparación antes-después confunde la intervención con todo lo demás que cambió en esa fecha. Comparar con un único instrumento de referencia falla porque ningún activo individual sigue al tuyo — es la trampa del instrumento único vista desde el otro lado: si la estructura idiosincrásica de un instrumento puede fingir una ventaja, también puede fingir un control. El grupo de donantes de SCM es la respuesta formal a esa objeción: en lugar de elegir un activo comparable, dejas que los datos del período previo elijan una cesta ponderada de ellos.
La diferencia en diferencias merece más que una frase, porque es el casi acierto. DiD compara el cambio de la unidad tratada con el cambio de un grupo de control, e identifica el efecto solo bajo tendencias paralelas: sin la intervención, la unidad tratada y el control se habrían movido juntas. En los mercados financieros esa suposición es casi indefendible: los activos difieren en beta, régimen de volatilidad y nivel de liquidez, y sus trayectorias relativas se desvían. SCM sustituye la suposición por un objeto ajustado: en vez de suponer un control que se mueve en paralelo, lo construye y muestra qué tan bien siguió realmente a la unidad tratada. Si lo siguió mal, el método te dice que te detengas.
El método

Planteamiento y notación
Considera unidades (activos, estrategias o carteras) observadas durante períodos. La unidad 1 es la unidad tratada: el activo o la estrategia donde interveniste. Las unidades forman el grupo de donantes de unidades comparables no tratadas.
Sea el resultado de la unidad tratada en el momento con la intervención, y el resultado sin ella. El efecto del tratamiento en el momento es:
Observamos directamente. Todo el problema consiste en estimar .
Construcción del control sintético
SCM estima como un promedio ponderado de los resultados de los donantes:
donde el vector de pesos satisface para todo y .
Estas dos restricciones hacen un trabajo real. Obligan al control sintético a ser una combinación convexa de donantes, lo que evita extrapolar fuera del soporte de los datos. Una regresión sin restricciones puede entregarte un contrafactual construido con una posición larga de 3,4x en una moneda frente a una corta de 2,4x en otra: un ajuste preintervención excelente en términos numéricos, pero un contrafactual que no corresponde a ninguna cartera que alguien pudiera mantener. La restricción de convexidad hace interpretable el contrafactual y dispersos los pesos: normalmente solo un puñado de donantes recibe peso distinto de cero, y puedes ver cuáles son.
Selección óptima de pesos
Los pesos se eligen para minimizar la distancia entre la unidad tratada y el control sintético en el período previo a la intervención:
donde es un vector de características preintervención de la unidad tratada, es la matriz de esas características para el grupo de donantes y es una matriz diagonal semidefinida positiva que asigna importancia a cada predictor.
se elige mediante validación cruzada: elige de modo que el resultante minimice el error cuadrático medio de predicción (MSPE) de la variable de resultado durante el período preintervención. Es una optimización anidada: un bucle externo sobre y una resolución interna restringida para .
Variables predictoras para aplicaciones de trading
Mientras Abadie et al. usaron el PIB per cápita y la composición industrial, las aplicaciones de trading necesitan:
- Características de rentabilidad: rentabilidad media, volatilidad, asimetría y curtosis durante el período previo
- Medidas de liquidez: spread medio, volumen diario y ratio de iliquidez de Amihud
- Rasgos de microestructura: desequilibrio del flujo de órdenes, tasa de llegada de operaciones y ratio de cotizaciones a operaciones
- Exposiciones a factores: beta al mercado y a los factores de momentum, value y volatilidad
- Resultados rezagados: valores acumulados de PnL en varias fechas preintervención
Los resultados rezagados son lo más importante. Abadie et al. (2010) muestran que, si el control sintético coincide con la unidad tratada en resultados rezagados durante una ventana preintervención larga, controla implícitamente los factores de confusión no observados bajo un modelo factorial lineal — esa es precisamente la razón por la que el método merece el esfuerzo. No tienes que nombrar el factor de confusión que impulsó tu PnL. Tienes que encontrar donantes que también estuvieran expuestos a él.
Dónde se aplica

Escenario 1: evaluar un nuevo algoritmo de ejecución
Despliegas un nuevo calendario TWAP en un instrumento y quieres medir su efecto sobre el implementation shortfall. Tanto la métrica como la familia de algoritmos se explican en profundidad en otros artículos: consulta implementation shortfall y TCA para la descomposición de costes y algoritmos TWAP/VWAP/POV para los planificadores. La aportación de SCM es la capa de evaluación: el grupo de donantes está formado por los mismos instrumentos que siguen ejecutando el planificador antiguo durante la misma ventana, de modo que los cambios generales del spread o del volumen afectan por igual a la unidad tratada y a los donantes.
Período preintervención: 60 días de trading antes del cambio. Período postintervención: 30 días de trading después. La estimación del efecto es la brecha:
Si el nuevo planificador reduce realmente los costes, persistirá después de y, más importante aún, la brecha de la unidad tratada quedará fuera de la nube de brechas de los donantes.
Escenario 2: impacto de un evento de mercado
Un cambio en el calendario de comisiones, un piloto de tamaño de tick, un impuesto a las transacciones o un cambio en la regla del circuit breaker afecta a un venue. Unidad tratada: tu estrategia en el venue afectado. Grupo de donantes: el mismo par en venues que no sufrieron el cambio. Los efectos de la estructura de comisiones sobre la ejecución se tratan por separado en comisiones maker-taker y rebates; SCM convierte "nuestros fills empeoraron después del cambio" en una estimación con un nulo asociado.
Escenario 3: cambio de estrategia condicionado por el régimen
Cambias de reversión a la media a momentum en un activo cuando se activa una señal de detección de régimen. ¿El cambio superó a quedarse como estabas? Grupo de donantes: activos similares donde se activó la señal pero no cambiaste. Observa la trampa: si la señal de régimen se activa en todo el mercado, tus donantes también están tratados y el diseño se derrumba. Consulta la regla de spillover más abajo.
Tests placebo e inferencia

La inferencia de SCM se basa en permutaciones. El caso general de construir un nulo empírico mediante remuestreo, en lugar de confiar en una distribución cerrada, se explica en métodos de Monte Carlo y bootstrap para backtests; lo que sigue es la construcción específica de SCM.
Placebo en el tiempo
Desplaza la fecha de intervención hacia atrás, dentro del período previo — por ejemplo, hasta la mitad — y vuelve a ejecutar el análisis. Si SCM encuentra un "efecto del tratamiento" en una fecha en la que no ocurrió nada, está captando una divergencia preexistente, no tu intervención. Un control sintético creíble no muestra efecto en fechas de intervención falsas.
Placebo en el espacio (test de permutación)
Aplica todo el procedimiento SCM a cada donante, fingiendo en cada turno que es la unidad tratada:
- Para cada donante , construye un control sintético con los donantes restantes y la unidad tratada original.
- Calcula la brecha postintervención .
- Compara la brecha de la unidad tratada con la distribución de brechas placebo.
El valor p es un rango:
donde
El ratio, y no la brecha postintervención sin procesar, es la estadística correcta: un donante que nunca se ajustó bien mostrará una gran brecha posterior por razones no relacionadas con ninguna intervención, y dividir por su MSPE preintervención penaliza exactamente eso.
Este es el mismo movimiento inferencial que hace el ratio de Sharpe deflactado: clasificar al candidato dentro de una distribución de referencia generada empíricamente, nunca contra cero. DSR clasifica al ganador frente a un nulo del mejor de N ensayos; SCM clasifica la unidad tratada frente a un nulo entre unidades. Distinto estimador, misma disciplina y la misma advertencia: con donantes, el menor valor p alcanzable es . Un test de permutación no puede resolver más allá de su propia granularidad, así que un grupo de donantes pequeño limita cuánta significación puedes afirmar, por grande que parezca el efecto.
Filtrado práctico
Abadie et al. recomiendan excluir las unidades placebo con mal ajuste preintervención — MSPE previo superior a veces el de la unidad tratada, con normalmente entre 2 y 20. Los placebos mal ajustados tienen denominadores diminutos y producen ratios MSPE enormes, contaminando la distribución de referencia y haciendo que la unidad tratada parezca normal. Informa del ratio con varios umbrales; si tu valor p solo es significativo con un concreto, has encontrado un parámetro de ajuste, no un efecto.
Implementación

El código siguiente usa solo numpy y scipy para que la mecánica sea visible. Las alternativas de producción — SparseSC (Microsoft Research), pysyncon, SyntheticControlMethods — gestionan los casos límite y escalan mejor.
Construcción de la matriz de predictores
import numpy as np
import pandas as pd
from scipy.optimize import minimize
def build_predictors(series: pd.Series, T0: int) -> np.ndarray:
"""
Extract predictor vector from pre-intervention data.
Features: mean return, volatility, skewness, kurtosis,
plus lagged outcome values at regular intervals.
"""
pre = series.iloc[:T0]
daily_ret = pre.diff().dropna()
stats = [
daily_ret.mean(),
daily_ret.std(),
daily_ret.skew(),
daily_ret.kurtosis(),
]
lag_indices = np.linspace(0, T0 - 1, 5, dtype=int)
lags = pre.iloc[lag_indices].values.tolist()
return np.array(stats + lags)
Aquí df es un panel de rentabilidades acumuladas: la unidad tratada está en la columna 0, los donantes en el resto y cada fila corresponde a una barra. Usa rentabilidades acumuladas, no niveles de precios.
X1 = build_predictors(df["Treated"], T0).reshape(-1, 1) # (k x 1)
X0 = np.column_stack([ # (k x J)
build_predictors(df[col], T0) for col in df.columns[1:]
])
Optimización de pesos
def solve_weights(
X1: np.ndarray,
X0: np.ndarray,
Y1_pre: np.ndarray,
Y0_pre: np.ndarray,
) -> np.ndarray:
"""
Solve for optimal synthetic control weights.
Nested optimization:
Outer: optimize V (predictor importance)
Inner: optimize w (donor weights) given V
"""
k = X1.shape[0]
J = X0.shape[1]
def solve_w_given_V(V_diag: np.ndarray) -> np.ndarray:
"""Inner: find w minimizing weighted predictor distance."""
V = np.diag(V_diag)
def objective(w):
gap = X1.flatten() - X0 @ w
return gap @ V @ gap
constraints = {"type": "eq", "fun": lambda w: np.sum(w) - 1.0}
bounds = [(0, 1)] * J
w0 = np.ones(J) / J
result = minimize(objective, w0, method="SLSQP",
bounds=bounds, constraints=constraints,
options={"maxiter": 1000, "ftol": 1e-12})
return result.x
def outer_objective(V_diag: np.ndarray) -> float:
"""Outer: minimize pre-period MSPE given V."""
w = solve_w_given_V(np.abs(V_diag))
return np.mean((Y1_pre - Y0_pre @ w) ** 2)
V0 = np.ones(k)
result = minimize(outer_objective, V0, method="Nelder-Mead",
options={"maxiter": 5000, "xatol": 1e-8})
return solve_w_given_V(np.abs(result.x))
Y1_pre = df["Treated"].iloc[:T0].values
Y0_pre = df.iloc[:T0, 1:].values
weights = solve_weights(X1, X0, Y1_pre, Y0_pre)
for col, w in zip(df.columns[1:], weights):
if w > 0.01:
print(f" {col}: {w:.4f}")
La serie de brechas es entonces df["Treated"].values - df.iloc[:, 1:].values @ weights; se grafica frente a la fecha de intervención junto con las brechas de los donantes del placebo que aparece abajo.
Tests placebo
def run_placebo_tests(df: pd.DataFrame, T0: int) -> pd.DataFrame:
"""In-space placebo: treat each unit in turn as if intervened upon."""
results = []
all_columns = df.columns.tolist()
for placebo_col in all_columns:
donor_cols = [c for c in all_columns if c != placebo_col]
donor_df = df[donor_cols]
X1_p = build_predictors(df[placebo_col], T0).reshape(-1, 1)
X0_p = np.column_stack([
build_predictors(donor_df[c], T0) for c in donor_cols
])
w_p = solve_weights(
X1_p, X0_p,
df[placebo_col].iloc[:T0].values,
donor_df.iloc[:T0].values,
)
gap_p = df[placebo_col].values - donor_df.values @ w_p
mspe_pre = np.mean(gap_p[:T0] ** 2)
mspe_post = np.mean(gap_p[T0:] ** 2)
results.append({
"unit": placebo_col,
"mspe_pre": mspe_pre,
"mspe_post": mspe_post,
"mspe_ratio": mspe_post / mspe_pre if mspe_pre > 1e-10 else np.inf,
"gap_series": gap_p,
"is_treated": placebo_col == "Treated",
})
return pd.DataFrame(results)
placebo_results = run_placebo_tests(df, T0)
treated_pre = placebo_results.loc[placebo_results["is_treated"], "mspe_pre"].values[0]
filtered = placebo_results[placebo_results["mspe_pre"] <= 5 * treated_pre]
treated_ratio = filtered.loc[filtered["is_treated"], "mspe_ratio"].values[0]
p_value = (filtered["mspe_ratio"] >= treated_ratio).sum() / len(filtered)
print(f"MSPE ratio (treated): {treated_ratio:.2f} p = {p_value:.3f}")
Calibración del estimador
Hay una demostración que merece la pena ejecutar y otra que merece la pena rechazar. Generar datos de panel sintéticos, inyectar un efecto de +0,3%/día, recuperar aproximadamente +0,3%/día y presentarlo como evidencia solo demuestra que SLSQP converge. Es circular, y este artículo no lo publica.
Los datos sintéticos sí sirven para calibrar: el mismo estándar aplicado en el artículo sobre DSR y PBO, donde el nulo conocido es lo que hace fiable al estadístico. Ejecuta dos comprobaciones con datos generados por un modelo factorial lineal y con el número de donantes y la duración del período previo que realmente tienes:
- Potencia con un tamaño de efecto conocido. Inyecta un efecto de magnitud conocida. ¿La brecha estimada del período posterior lo recupera y con qué error? Reduce el tamaño del efecto hasta que la recuperación falle: ese umbral es el efecto mínimo que puede detectar la geometría de tu panel, y cualquier resultado real inferior es ruido independientemente de su valor p.
- Tamaño bajo un nulo verdadero. No inyectes nada. Ejecuta todo el flujo placebo. El valor p debería ser aproximadamente uniforme en , por lo que debería caer por debajo de 0,10 cerca del 10% de las veces en muestras repetidas. Si se activa mucho más a menudo, tu conjunto de predictores o el umbral de filtrado están fabricando significación y el flujo está roto antes de tocar datos reales.
La comprobación 2 es la que la gente omite y la que importa. Ejecútala primero.
Consideraciones prácticas

Elección del grupo de donantes
Los donantes deben ser plausiblemente comparables con la unidad tratada y estar no afectados por la intervención. Para trabajos de ejecución: el mismo algoritmo sobre instrumentos similares — mismo sector, decil de capitalización bursátil y nivel de liquidez. La taxonomía de niveles de validación mult símbolo se puede reutilizar directamente: las propiedades que hacen de los instrumentos una prueba justa de robustez también los convierten en un grupo de donantes justo. Para eventos de venue: el mismo instrumento en venues a los que no llegó el cambio.
Excluye unidades con spillover. Si cambiar tu algoritmo en un instrumento modificó tus cotizaciones en instrumentos correlacionados, esos instrumentos están parcialmente tratados. Dejarlos dentro sesga el control sintético hacia la trayectoria postintervención de la unidad tratada y reduce tu estimación hacia cero — un defecto de diseño que fabrica resultados nulos con la misma facilidad con que un control malo fabrica resultados positivos.
El ajuste preintervención es todo el argumento
La credibilidad de SCM depende por completo del ajuste previo. Reporta el MSPE preintervención y muestra el ajuste. Un preajuste malo invalida todo lo que ocurre después de .
Abadie et al. (2015) expresan la regla con claridad: si ninguna combinación convexa de donantes reproduce la trayectoria preintervención de la unidad tratada, no apliques el método. Es un criterio honesto de falsificación, y aparece menos de lo que debería: la mayoría de los estimadores devuelven un número con independencia de lo que les des. SCM te dice cuándo no puede producir un contrafactual creíble, y la respuesta correcta es publicar el fallo, no ampliar el grupo de donantes hasta mejorar el ajuste. Ampliar el grupo para perseguir el preajuste es el mismo sobreajuste del que trata el resto de esta serie, trasladado al paso de construir el control.
Sobreajuste en paneles cortos
La cantidad específica de SCM que debes vigilar es el ratio de donantes respecto a las observaciones preintervención . Con pesos libres ajustados sobre puntos, el preajuste perfecto se vuelve alcanzable por construcción cuando se acerca a , y un preajuste perfecto obtenido así no contiene información. Mantén bastante mayor que ; si una estrategia funcionó durante 30 días antes del cambio y tienes 20 donantes candidatos, no tienes un problema de SCM, sino una interpolación. Mitigaciones:
- Regularización: SparseSC añade penalizaciones y a la optimización de pesos.
- Restringir el grupo de donantes: menos donantes mejor justificados son preferibles a más donantes — a costa de una cuadrícula de permutación más basta, ya que también fija tu valor p mínimo. Esa tensión es real y no tiene una solución limpia.
- SCM aumentado: Ben-Michael, Feller y Rothstein (2021) usan un modelo de resultados para corregir el sesgo cuando no se puede alcanzar un preajuste perfecto.
No estacionariedad
Ejecuta SCM sobre rentabilidades acumuladas, nunca sobre niveles de precios: los niveles invitan a ajustes espurios. Confirma que la brecha preintervención tenga media cero y sea estacionaria antes de confiar en la divergencia posterior; la maquinaria ADF y Engle-Granger para ello, incluidos los valores críticos corregidos necesarios al probar un residuo ajustado en lugar de una serie observada, se explica en arbitraje estadístico y pairs trading.
Varias unidades tratadas
Desplegar la intervención en varios activos a la vez rompe el diseño con una sola unidad tratada. Extensiones: SCM agrupado (efectos medios entre unidades tratadas) y diseños de adopción escalonada (Ben-Michael et al., 2022) para unidades tratadas en momentos distintos.
Cuándo no usar SCM

Adecuado para: una intervención discreta y bien definida en una fecha conocida; un grupo de donantes razonable de unidades comparables no tratadas; intervenciones no anticipadas, porque la anticipación mueve la unidad tratada antes de y viola la identificación.
Poco adecuado para: un tratamiento continuo, como un parámetro que deriva durante semanas; contextos sin comparables realmente no tratados; evaluación de alta frecuencia donde la relación señal-ruido impide estimar los pesos.
Conclusión

La selección y la confusión causal son modos de fallo distintos y necesitan instrumentos distintos. DSR y PBO valoran la búsqueda. SCM valora el contrafactual y es la única herramienta de esta serie que aborda el caso en que ejecutaste exactamente un experimento y aun así no puedes confiar en el resultado.
La razón para preferirlo a un benchmark ad hoc no es que produzca efectos mayores — con frecuencia produce efectos menores — sino que puede negarse. Si ninguna combinación convexa de donantes sigue a tu instrumento antes de la intervención, SCM lo dice y te detienes. Si la brecha tratada queda dentro de la nube de donantes, el valor p placebo lo dice y reportas un nulo. Esas dos salidas son el valor del método, y una explicación de SCM sin un MSPE preintervención reportado y un valor p placebo ha omitido las únicas partes que lo convierten en algo más que un gráfico de dos líneas divergentes.
Lecturas adicionales
- Abadie, A., Diamond, A., & Hainmueller, J. (2010). "Synthetic Control Methods for Comparative Case Studies." Journal of the American Statistical Association, 105(490), 493-505.
- Abadie, A. (2021). "Using Synthetic Controls: Feasibility, Data Requirements, and Methodological Aspects." Journal of Economic Literature, 59(2), 391-425.
- Ben-Michael, E., Feller, A., & Rothstein, J. (2021). "The Augmented Synthetic Control Method." Journal of the American Statistical Association, 116(536), 1789-1803.
- Cunningham, S. (2021). Causal Inference: The Mixtape. Chapter 10: Synthetic Control. Available at mixtape.scunning.com.
- Microsoft Research. SparseSC: Sparse Synthetic Controls. github.com/microsoft/SparseSC.
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.