🇬🇧 English · 🇪🇸 Español
Para evaluación histórica de carteras, asignación, análisis técnico/fundamental y dinámica de chartistas y fundamentalistas, consulte investigación de inversiones.
Rendimientos, riesgo y pruebas de valoración de activos
puremacro.asset_pricing mide el riesgo de los rendimientos y contrasta
modelos de valoración de activos, incluidos modelos cuyos factores proceden
de datos macroeconómicos. Cubre rendimientos y estadísticos de riesgo, valor
en riesgo y sus pruebas retrospectivas, las pruebas de factores de serie
temporal (GRS) y de Fama-MacBeth, el GMM del factor de descuento estocástico
(SDF) lineal, la distancia de Hansen-Jagannathan y el CAPM de consumo de
Hansen-Singleton. Los estimadores basados en momentos usan el motor GMM
público puremacro.inference.gmm. La batería de pruebas contrasta cada
estimador con una implementación independiente (statsmodels, linearmodels,
fórmulas cerradas o una minimización directa). En tiempo de ejecución solo
se necesitan NumPy, SciPy y pandas.
Todas las entradas son rendimientos por periodo en decimales. Todos los
estimadores rechazan valores NaN o infinitos y nombran las columnas
afectadas; no se descarta nada en silencio. Se rechaza una columna de
rendimientos que parezca expresada en porcentaje (mediana de |r| mayor que
0,2, o más del 5% de sus valores mayores que 1 en valor absoluto); use
check_scale=False para rendimientos decimales genuinos de ese tamaño. Las series con DatetimeIndex se emparejan por fecha, y las
frecuencias mezcladas producen un error.
Los ejemplos siguientes se ejecutan sin conexión con datos sintéticos de semilla fija.
Rendimientos y riesgo
import numpy as np
import pandas as pd
from puremacro import asset_pricing as ap
rng = np.random.default_rng(1)
idx = pd.date_range("2000-01-01", periods=240, freq="MS")
factors = pd.DataFrame(0.005 + 0.04 * rng.standard_normal((240, 2)),
index=idx, columns=["mkt", "hml"])
loadings = rng.uniform(0.5, 1.5, (2, 6))
excess = pd.DataFrame(factors.to_numpy() @ loadings + 0.02 * rng.standard_normal((240, 6)),
index=idx, columns=[f"p{i}" for i in range(6)])
rf = pd.Series(0.002, index=idx)
prices = 100 * (1 + excess["p0"] + rf).cumprod()
r = ap.to_returns(prices) # simple returns, first row dropped
quarterly = ap.compound(excess, to="Q") # prod(1 + r) - 1 within quarters
print(round(ap.annualize_return(excess["p0"], 12), 4),
round(ap.annualize_volatility(excess["p0"], 12), 4))
risk = ap.risk_summary(excess, periods_per_year=12)
print(risk.summary())
risk.to_dataframe()[["name", "sharpe", "sharpe_se", "max_drawdown"]]
sharpe_se es el error estándar de Lo (2002) por el método delta, con una
covarianza de largo plazo de Bartlett (hac_lags=None usa
floor(4 (T/100)^(2/9)); hac_lags=0 da la fórmula iid). Use rf= para
restar una tasa libre de riesgo antes de cada estadístico.
Valor en riesgo y pruebas retrospectivas
loss = excess["p0"]
print(ap.value_at_risk(loss, alpha=0.05), # historical
ap.value_at_risk(loss, alpha=0.05, method="normal"),
ap.expected_shortfall(loss, alpha=0.05, method="garch")) # filtered historical simulation
bt = ap.var_backtest(loss, ap.value_at_risk(loss, alpha=0.05), alpha=0.05)
print(bt.summary())
El VaR y el ES son pérdidas positivas para el periodo siguiente. "garch"
filtra la serie con puremacro.garch.garch11_fit y escala el cuantil de
los residuos estandarizados por la volatilidad prevista a un paso (solo
horizonte 1). var_backtest da la prueba de cobertura incondicional de
Kupiec y las pruebas de independencia y de cobertura condicional de
Christoffersen. Sin violaciones, los estadísticos siguen siendo finitos
(0 log 0 = 0). El VaR dentro de muestra usado aquí solo ilustra la
llamada: una prueba retrospectiva real usa pronósticos formados antes de
cada periodo.
Pruebas de factores
ts = ap.time_series_test(excess, factors, hac_lags=4)
print(ts.summary()) # alphas, GRS F(N, T-N-K), HAC Wald chi2(N)
fm = ap.fama_macbeth(excess, factors)
print(fm.summary()) # lambdas with Fama-MacBeth, Shanken and Newey-West se
time_series_test regresa cada activo sobre [1, f]. El estadístico GRS
es F(N, T-N-K) exacto solo con errores normales iid y con factores que
sean excesos de rendimiento negociables. Para factores macroeconómicos use
traded=False: el GRS no se reporta, pero la prueba de Wald HAC sí.
fama_macbeth acepta factors (betas de regresiones de serie temporal con
la muestra completa) o betas=.
Factor de descuento estocástico y distancia HJ
sdf = ap.sdf_gmm(excess, factors) # m = 1 - b'(f - mu), two-step GMM
print(sdf.summary())
hj = ap.hj_distance(excess, factors, n_sim=20_000, seed=0)
print(hj.summary())
sdf_gmm estima b (y mu, con demeaned=True) mediante
inference.gmm y reporta las primas implícitas lambdas = Sigma_f b, los
errores de valoración y la prueba J de Hansen. hj_distance usa la matriz
de ponderación de segundos momentos y el valor p de chi-cuadrado ponderada
de Jagannathan-Wang (1996). Simula el valor p con la semilla seed, que
queda registrada.
La distancia HJ rechaza activos redundantes y ambos estimadores rechazan
cargas factoriales no identificadas con ValueError. El VaR y el ES históricos
componen rendimientos simples en ventanas superpuestas de varios períodos; el
método normal conserva su aproximación aditiva iid.
CAPM de consumo con datos macroeconómicos
T = 5_000
g = pd.Series(np.exp(0.005 + 0.02 * rng.standard_normal(T))) # gross consumption growth
beta, gamma = 0.98, 4.0
def moment(a): # E[g^a], lognormal growth
return np.exp(a * 0.005 + 0.5 * a ** 2 * 0.02 ** 2)
gross = pd.DataFrame({"equity": g / (beta * moment(1 - gamma)), # Lucas-tree claim on C
"levered": g ** 3 / (beta * moment(3 - gamma))}) # claim on C^3
rf_gross = pd.Series(np.full(T, 1 / (beta * moment(-gamma))))
cc = ap.ccapm(gross, g, rf=rf_gross, instruments=np.ones((T, 1)))
print(cc.summary())
ccapm estima las ecuaciones de Euler con utilidad potencial
E[(beta g^-gamma R - 1) z_t] = 0. Necesita rendimientos y crecimiento
brutos. Los instrumentos por defecto son una constante, el crecimiento
del consumo rezagado y los rendimientos rezagados. Con datos reales,
construya las entradas a partir de FRED:
# requires: network
g_q = ap.fred_consumption_growth(frequency="Q", start="1959") # per-capita real ND + services
rf_q = ap.fred_risk_free(frequency="Q", start="1959") # TB3MS / 100 / 4, quarter average
Fuentes de datos
# requires: network
from puremacro.fetch.kenfrench_factors import fetch
ff3 = fetch("ff3") # Mkt-RF, SMB, HML, RF in decimals
p25 = fetch("25_size_bm") # 25 value-weighted Size-B/M portfolios plus RF
sample = slice("1963-07-01", "2013-12-01")
ex25 = p25.loc[sample].drop(columns="RF").sub(p25.loc[sample, "RF"], axis=0)
grs = ap.time_series_test(ex25, ff3.loc[sample, ["Mkt-RF", "SMB", "HML"]], hac_lags=0)
grs.grs_stat # about 3.57 on the 2026 library; Fama and French (2015) report 3.62
- Biblioteca de datos de Ken French. Los archivos indican "Copyright
Eugene F. Fama and Kenneth R. French" y no conceden licencia de
redistribución. puremacro no incluye ninguno.
fetch.kenfrench_factors.fetchlos descarga bajo demanda en su caché HTTP de usuario (~/.cache/puremacro/http, o$PUREMACRO_HTTP_CACHE_DIR). El registro de fuentes marcakenfrench.factorscomo no redistribuible. La biblioteca se revisa después de la publicación (correcciones de CRSP, nuevos puntos de corte). Por eso los estadísticos publicados solo se reproducen de forma aproximada. La prueba de red admite un 15% y mide 3,567 frente al 3,62 publicado. - FRED (sin clave).
fred_consumption_growthsuma el gasto nominal mensual en bienes no duraderos (PCEND) y en servicios (PCES), cada uno deflactado por su propio índice de precios encadenado (DNDGRG3M086SBEA,DSERRG3M086SBEA), y divide por la población (POPTHM). Con"Q"promedia dentro de cada trimestre. Sumar dos series en dólares encadenados solo aproxima un agregado encadenado. Si falta cualquier serie, se lanzaRuntimeErroren lugar de calcular el crecimiento con una suma parcial.fred_risk_freeconvierteTB3MS(porcentaje anual, base de descuento) con una división simple, que es una aproximación.
GMM genérico
from puremacro.inference import gmm
zi = rng.standard_normal((2_000, 3))
x = zi @ np.array([0.8, 0.5, 0.3]) + rng.standard_normal(2_000)
y = 1.0 + 2.0 * x + rng.standard_normal(2_000)
Z = np.column_stack([np.ones(2_000), zi])
X = np.column_stack([np.ones(2_000), x])
fit = gmm(lambda th, d: d[2] * (d[0] - d[1] @ th)[:, None], np.zeros(2), (y, X, Z),
names=("const", "x"))
print(fit.summary())
fit.wald(np.eye(2)[[1]], [2.0])
gmm(moments, theta0, data) recibe las contribuciones de momentos por
periodo (T, q). Admite weighting="two_step" (por defecto),
"iterated", "identity" o una matriz, un jacobian analítico opcional y
una covarianza de largo plazo de Bartlett. La covarianza es el sándwich
B G'W S W G B / T, B = (G'WG)^-1, que es la forma eficiente cuando
W = S^-1. La J = T gbar'W gbar de Hansen solo se reporta con las
ponderaciones eficientes. En VI lineal, las estimaciones, los errores
estándar y J coinciden con IV2SLS/IVGMM de linearmodels y con
inference.hansen_j a 1e-8.
Supuestos y límites
- GRS es exacto solo con errores normales iid y requiere factores que sean excesos de rendimiento negociables. En otro caso, use la prueba de Wald HAC, pero tenga en cuenta que rechaza en exceso en muestras cortas (alrededor del 23% con un nivel nominal del 5%, T = 60 y N = 10): con factores negociables y pocos periodos, prefiera el GRS.
- Ancho de banda HAC. El valor por defecto de toda covarianza de largo
plazo de Bartlett es
floor(4 (T/100)^(2/9)), sin centrar.hac_lags=0es el caso iid (robusto a heterocedasticidad). - Anualización del Sharpe. Multiplica por
sqrt(periods_per_year)e ignora el factor de correlación serial eta(q) de Lo. - Distancia HJ con excesos de rendimiento. La media del SDF no está
identificada, por lo que la distancia depende de la normalización
m = 1 - b'f(Kan y Robotti 2008). - Errores estándar de Shanken. Necesitan los residuos de la primera
etapa. Si se dan
betas=, sonNaNysevuelve a Fama-MacBeth.LinearFactorModelde linearmodels reporta un error estándar robusto de GMM. Es asintóticamente igual al de Shanken con errores iid, pero es una definición distinta: la prueba mide una diferencia de alrededor del 0,2%. - Lambdas del SDF. Tratan la covarianza de los factores como conocida.
LinearFactorModelGMMde linearmodels estima la representación en betas, un estimador distinto, así que las dos primas solo coinciden de forma aproximada. - La gamma del CCAPM está débilmente identificada con datos de consumo
(Stock y Wright 2000). Reporte la prueba J y los intervalos del 95% que
imprime
summary(), no solo la estimación puntual. - Protección contra porcentajes. Es una heurística aplicada a cada
columna de rendimientos: mediana de
|r|mayor que 0,2 o más del 5% de valores con|r| > 1. Los rendimientos aislados superiores al 100% (el archivo de las 25 carteras Size-B/M en 1932, acciones individuales, criptoactivos) se aceptan. Las series volátiles que la activen pueden pasarse concheck_scale=False. Los factores solo se comprueban por finitud, salvo los factores negociables entime_series_test(traded=True). - Convenciones de GMM. La covarianza sándwich no lleva corrección de
muestra pequeña. J es
NaNcon ponderación identidad o de usuario, y exactamente cero cuando el modelo está exactamente identificado.