Koopman Operators and DMD: Do Market Modes Survive Out-of-Sample?
La descomposición en modo dinámico le ofrece un espectro: un puñado de valores propios complejos, cada uno con una tasa de crecimiento y una frecuencia, cada uno adjunto a un modo espacial sobre la sección transversal de su activo. Parece estructura. Toda la cuestión es si es estructura o si es un operador lineal que memoriza diligentemente una ventana de ruido.
Esa pregunta tiene dos mitades comprobables, y este artículo se basa en ellas:
- Persistencia del modo. Ajustar DMD en la ventana y en la ventana . ¿Los modos dominantes abarcan el mismo subespacio o reorganizan cada remodelación? Si se reorganizan, DMD es una descomposición en la muestra y nada más, y decir eso claramente es más útil que otro tutorial.
- Radio espectral como indicador principal. El módulo de valor propio más grande es un escalar único que resume cuán explosiva es la dinámica ajustada. ¿lidera la volatilidad observada, la retrasa o simplemente la reformula? Cualquiera de las respuestas es publicable; sólo el primero es comercializable.
Aquí se supone, no se argumenta, que los mercados son sistemas no lineales no estacionarios: el blog ya lo ha argumentado, con geometría de espacio de fase en atractores en algotrading y con estadísticas BTC medidas por régimen en detección de régimen con HMM. Lo que sigue es la cuestión más concreta de qué le hace la no estacionariedad a un operador Koopman adaptado y cómo medirlo.
1. La idea central: linealizar la dinámica no lineal

Considere un sistema dinámico de tiempo discreto en un espacio de estados. :
dónde es un mapa posiblemente no lineal. Para los mercados, es un vector de rendimientos de activos, volatilidades o desequilibrios en la cartera de pedidos en el paso del tiempo .
El operador Koopman no actúa sobre el estado directamente, pero en funciones observables de valores escalares :
La propiedad clave: es lineal, incluso cuando no lo es. El precio es la dimensionalidad. actúa sobre un espacio funcional de dimensión infinita. Una buena aproximación de dimensión finita le proporciona la expresividad de la dinámica no lineal con la manejabilidad del álgebra lineal: la predicción se convierte en exponenciación matricial y cada modo es inspeccionable en lugar de estar enterrado en pesos de red.
2. Descomposición espectral del operador Koopman

si tiene valores propios y funciones propias , entonces , y cualquier observable en el lapso de estas funciones propias se descompone como:
dónde son los modos de Koopman: coeficientes con valores vectoriales que describen cómo cada función propia contribuye al vector observable completo.
Cada valor propio codifica una tasa de crecimiento o decadencia () y una frecuencia de oscilación ():
| Componente | Propiedad de valor propio | Interpretación financiera |
|---|---|---|
| Tendencia | , | Deriva lenta, impulso |
| Ciclos | , | Oscilaciones, estacionalidad |
| Transitorios | Conmociones en decadencia, movimientos de corta duración | |
| Modos inestables | Dinámica creciente y explosiva |
Esta mesa es la promesa. La sección 4 es donde se compara la promesa con los datos.
3. Descomposición en modo dinámico (DMD)

DMD es el algoritmo caballo de batalla para aproximar a partir de datos. Instantáneas dadas organizadas en matrices:
DMD busca el operador lineal que mejor se adapte con :
- Calcule la SVD:
- Proyecto:
- Descomposición propia:
- Recuperar modos de espacio completo:
las columnas de son los modos DMD; la diagonal de contiene los valores propios DMD.
import numpy as np
from numpy.linalg import svd, eig, lstsq
def dmd(X: np.ndarray, rank: int | None = None) -> tuple:
"""
Dynamic Mode Decomposition.
Parameters
----------
X : np.ndarray, shape (n_features, n_snapshots)
Data matrix where each column is a state snapshot.
rank : int or None
Truncation rank for the SVD. None = no truncation.
Returns
-------
eigenvalues : np.ndarray, shape (r,)
DMD eigenvalues (approximating Koopman eigenvalues).
modes : np.ndarray, shape (n_features, r)
DMD modes (columns), L2-normalised.
amplitudes : np.ndarray, shape (r,)
Mode amplitudes fitted to the FINAL snapshot, so that a one-step
forecast is simply modes @ (eigenvalues * amplitudes).
"""
X0 = X[:, :-1]
X1 = X[:, 1:]
U, S, Vh = svd(X0, full_matrices=False)
if rank is not None:
U = U[:, :rank]
S = S[:rank]
Vh = Vh[:rank, :]
S_inv = np.diag(1.0 / S)
A_tilde = U.conj().T @ X1 @ Vh.conj().T @ S_inv
eigenvalues, W = eig(A_tilde)
modes = X1 @ Vh.conj().T @ S_inv @ W
norms = np.linalg.norm(modes, axis=0)
norms[norms == 0] = 1.0
modes = modes / norms
amplitudes = lstsq(modes, X[:, -1].astype(complex), rcond=None)[0]
return eigenvalues, modes, amplitudes
Aquí hay dos opciones deliberadas. Los modos están normalizados en L2, porque la sección 4.2 compara subespacios de modos entre ventanas y las amplitudes no normalizadas inundarían la comparación. Las amplitudes se ajustan a la última instantánea en lugar de a la primera, lo que significa que un pronóstico nunca eleva un valor propio a una gran potencia: la fuente de la explosión numérica que hace que las ingenuas señales DMD parezcan dinámicas cuando son un desbordamiento de punto flotante.
4. La Medición

Esta es la parte del artículo que no es un libro de texto. Todo lo anterior es un procedimiento adecuado; A continuación se muestra el protocolo para descubrir si el ajuste significa algo.
Datos. Utilice los datos de intercambio propios del proyecto: una sección transversal de BTC, ETH y alternativas líquidas en una cuadrícula de minutos o ticks constante, no una descarga diaria de ETF de acciones. El blog es cripto-primero y el argumento de la microestructura no se transfiere. Construir con activos en filas y tiempo en columnas, en retornos de registros, degradados por activo dentro de cada ventana.
4.1 El espectro de valores propios
Informe, para una ventana representativa: ¿cuántas de las valores propios caen dentro de una tolerancia del círculo unitario, ¿a qué período de oscilación corresponde cada uno en horas (período barras, convertidas), y qué fracción de la varianza del rendimiento la parte superior modos de reconstruir.
def spectrum_report(eigenvalues: np.ndarray, bar_minutes: float,
tol: float = 0.05) -> list[dict]:
"""
Turn a DMD spectrum into human-readable rows: modulus, period in hours,
and whether the eigenvalue sits on the unit circle within `tol`.
"""
rows = []
for lam in eigenvalues:
modulus = float(np.abs(lam))
omega = float(np.angle(lam))
period_hours = (2 * np.pi / abs(omega)) * bar_minutes / 60 if omega else np.inf
rows.append({
"modulus": modulus,
"period_hours": period_hours,
"on_unit_circle": abs(modulus - 1.0) < tol,
"regime": "unstable" if modulus > 1 + tol
else "persistent" if abs(modulus - 1.0) <= tol
else "decaying",
})
return sorted(rows, key=lambda r: -r["modulus"])
def reconstruction_r2(X: np.ndarray, modes: np.ndarray,
eigenvalues: np.ndarray, amplitudes: np.ndarray) -> float:
"""Fraction of in-window return variance captured by the truncated modes."""
n_steps = X.shape[1]
powers = eigenvalues[:, None] ** np.arange(-(n_steps - 1), 1)
X_hat = (modes @ (amplitudes[:, None] * powers)).real
resid = np.var(X - X_hat)
return 1.0 - resid / np.var(X)
Un informe honesto sobre el espectro ya merece la pena leer el artículo. Si nada se ubica cerca del círculo unitario, no hay ciclos persistentes para negociar y la historia de la "estacionalidad anual" de la literatura sobre acciones simplemente no se traslada a las criptomonedas 24 horas al día, 7 días a la semana.
4.2 Estabilidad del modo en ventanas adyacentes
La prueba decisiva. Colocar DMD en la ventana , luego en la ventana y medir qué parte del subespacio del modo dominante sobrevive. La estadística correcta no es una correlación ingenua de vectores modales (el ordenamiento de los modos y la fase compleja son arbitrarios), sino los ángulos principales entre los dos subespacios.
def subspace_stability(modes_a: np.ndarray, modes_b: np.ndarray,
k: int = 3) -> float:
"""
Overlap between the leading-k DMD mode subspaces of two adjacent windows.
Returns the mean cosine of the principal angles: 1.0 = identical subspace,
0.0 = orthogonal. Immune to mode reordering and complex phase, both of
which are arbitrary in a DMD fit.
"""
Qa, _ = np.linalg.qr(modes_a[:, :k])
Qb, _ = np.linalg.qr(modes_b[:, :k])
sing = np.linalg.svd(Qa.conj().T @ Qb, compute_uv=False)
return float(np.mean(np.clip(sing, 0.0, 1.0)))
def stability_curve(returns: np.ndarray, window: int, step: int,
rank: int, k: int = 3) -> np.ndarray:
"""Subspace overlap between every pair of adjacent windows."""
fits = []
for t_end in range(window, returns.shape[1], step):
evals, modes, _ = dmd(returns[:, t_end - window:t_end], rank=rank)
order = np.argsort(-np.abs(evals))
fits.append(modes[:, order])
return np.array([subspace_stability(fits[i], fits[i + 1], k=k)
for i in range(len(fits) - 1)])
Informe la distribución de esta superposición y compárala con un valor nulo: la misma estadística calculada sobre sustitutos aleatorizados por fases de los mismos resultados. Una superposición alta pero no mayor que el nulo sustituto significa que los modos siguen la estructura de covarianza, no la dinámica.
4.3 Radio espectral móvil frente a la volatilidad realizada
La pretensión de probar: , recalculado en ventanas móviles, se mueve antes de la volatilidad observada en lugar de junto con ella. El escalar es mecánicamente nuevo: el blog ha monitoreado escalares geométricos en tiempo real antes, en particular la curvatura de Kobayashi en [colectores complejos para el comercio algorítmico] (/en/blog/post/complex-manifolds-algorithmic-trading), pero un módulo de valores propios de Koopman es una cantidad diferente con un modo de falla diferente, y merece su propia prueba de avance-retraso en lugar de un tono heredado.
def rolling_spectral_radius(returns: np.ndarray, window: int = 1440,
step: int = 60, rank: int = 5) -> dict:
"""
Rolling DMD spectrum for regime monitoring.
returns : np.ndarray, shape (n_assets, n_timesteps)
window : rolling window length in bars
step : bars between refits
"""
idx, radii, dom_freq = [], [], []
for t_end in range(window, returns.shape[1], step):
X_win = returns[:, t_end - window:t_end]
try:
evals, _, _ = dmd(X_win, rank=rank)
except np.linalg.LinAlgError:
continue
idx.append(t_end)
radii.append(float(np.max(np.abs(evals))))
on_circle = np.abs(np.abs(evals) - 1.0) < 0.1
if on_circle.any():
sel = evals[on_circle]
dom_freq.append(float(np.abs(np.angle(sel[np.argmax(np.abs(sel))])) / (2 * np.pi)))
else:
dom_freq.append(0.0)
return {"index": np.array(idx),
"spectral_radius": np.array(radii),
"dominant_frequency": np.array(dom_freq)}
def lead_lag(signal: np.ndarray, target: np.ndarray, max_lag: int = 24) -> dict:
"""
Cross-correlation of `signal` against `target` over +/- max_lag steps.
A peak at negative lag means the signal LEADS the target.
"""
s = (signal - signal.mean()) / (signal.std() + 1e-12)
y = (target - target.mean()) / (target.std() + 1e-12)
lags = np.arange(-max_lag, max_lag + 1)
corrs = []
for L in lags:
if L < 0:
corrs.append(float(np.corrcoef(s[:L], y[-L:])[0, 1]))
elif L > 0:
corrs.append(float(np.corrcoef(s[L:], y[:-L])[0, 1]))
else:
corrs.append(float(np.corrcoef(s, y)[0, 1]))
corrs = np.array(corrs)
return {"lags": lags, "corr": corrs, "peak_lag": int(lags[np.argmax(np.abs(corrs))])}
Alinear spectral_radius a la volatilidad realizada calculada en la misma cuadrícula y leída peak_lag. Un pico en el retraso 0 significa es una reformulación de la volatilidad con pasos adicionales. Un pico de retraso negativo, estable en toda la muestra y en todos los rangos, es la única versión de este artículo que incluye un reclamo negociable.
Una nota sobre lo que viene después de un resultado positivo
si Si lidera, el siguiente paso obvio es la construcción transversal: clasificar los activos según el rendimiento del siguiente paso previsto por DMD, posicionarse en largo con los ganadores previstos y en corto con los perdedores previstos. Esa construcción no es nueva aquí: es el comercio de factor residual ya cubierto en arbitraje estadístico y comercio de pares en criptomonedas y la sección 4 de arbitraje complejo con vectores y matrices; El único giro genuinamente específico de Koopman es que las carteras propias contienen valores propios que varían en el tiempo en lugar de cargas PCA estáticas.
La sección de estrategia está deliberadamente ausente de este artículo porque no ha sido sometida a pruebas retrospectivas aquí con tarifas y deslizamientos. Cuando lo es, tiene que superar el propio listón del blog: la prueba de significancia en el índice de Sharpe deflactado y las pruebas múltiples, frente al contraejemplo permanente de el negativo honesto. Un gráfico del espectro DMD no es un resultado.
Para el pronóstico de un paso en sí, la implementación correcta predice a partir de la última instantánea en lugar de propagar un valor propio elevado a la potencia de la longitud de la ventana:
def dmd_one_step(returns: np.ndarray, rank: int = 4) -> np.ndarray:
"""
One-step-ahead prediction from the final snapshot of the window.
Never raise eigenvalues to the window length: any |lambda| != 1 then
overflows or underflows and the "signal" becomes numerical garbage.
"""
evals, modes, amplitudes = dmd(returns, rank=rank)
return (modes @ (evals * amplitudes)).real
5. DMD extendido (EDMD): observables no lineales

El DMD estándar opera en el vector de estado bruto. EDMD primero levanta los datos a través de un diccionario de funciones de base no lineal.
dado un diccionario de funciones escalares , defina el estado elevado:
EDMD busca con . En la convención utilizada por el código siguiente: estados como columnas, actuando por la izquierda:
| Tipo de diccionario | Funciones | Capturas |
|---|---|---|
| Polinomio | Interacciones no lineales entre activos | |
| Base radial (RBF) | Similitud local, agrupación de regímenes | |
| Incrustación con retardo de tiempo | Memoria/estructura autorregresiva | |
| Fourier | Periodicidades conocidas (intradiaria, semanal) | |
| Características de volatilidad | Heterocedasticidad, agrupación de volúmenes |
El diccionario es donde entra el conocimiento del dominio, y la fila de retardo de tiempo es la razón específica de Koopman para preocuparse por las coordenadas de retardo: no son una técnica separada, son un bloque más del mapa de elevación. La incrustación en sí: elección del retraso , dimensión de incrustación , y el teorema de reconstrucción detrás de él, ya está introducido y codificado en variedades complejas para el comercio algorítmico; tome los vectores de retraso desde allí y aliméntelos directamente a build_financial_dictionary como filas adicionales.
import numpy as np
from itertools import combinations_with_replacement
def build_financial_dictionary(X: np.ndarray, max_poly_degree: int = 2,
include_volatility: bool = True,
delay_steps: int = 0) -> np.ndarray:
"""
Build a dictionary of nonlinear observables for EDMD.
X : np.ndarray, shape (n_features, n_snapshots)
Returns Z of shape (n_dict, n_snapshots - delay_steps).
"""
n_features, n_snapshots = X.shape
offset = max(delay_steps, 0)
X_eff = X[:, offset:]
n_eff = X_eff.shape[1]
lifted = [X_eff] # degree-1 terms (identity)
if max_poly_degree >= 2:
for deg in range(2, max_poly_degree + 1):
for combo in combinations_with_replacement(range(n_features), deg):
term = np.ones(n_eff)
for idx in combo:
term *= X_eff[idx]
lifted.append(term.reshape(1, -1))
if include_volatility:
lifted.append(np.abs(X_eff)) # absolute returns
lifted.append(X_eff ** 2) # squared returns
for d in range(1, delay_steps + 1):
lifted.append(X[:, offset - d : n_snapshots - d])
return np.vstack(lifted)
def edmd(X: np.ndarray, dictionary_fn=None, reg: float = 1e-8,
**dict_kwargs) -> tuple:
"""
Extended Dynamic Mode Decomposition.
Solves Z1 ~= K @ Z0 in the least-squares sense. Uses a least-squares
solve rather than an explicit Gram inverse: `inv` on a near-singular
dictionary Gram matrix is how EDMD spectra get silently corrupted.
"""
if dictionary_fn is None:
dictionary_fn = lambda x: build_financial_dictionary(x, **dict_kwargs)
Z = dictionary_fn(X)
Z0, Z1 = Z[:, :-1], Z[:, 1:]
p = Z0.shape[0]
G = Z0 @ Z0.T + reg * np.eye(p) # regularised Gram matrix
A = Z1 @ Z0.T
K = np.linalg.solve(G, A.T).T
eigenvalues, eigenvectors = np.linalg.eig(K)
return K, eigenvalues, eigenvectors
6. Redes profundas de Koopman

El diccionario EDMD está elaborado a mano, lo que supone una limitación real cuando se desconoce el subespacio invariante de Koopman. Las redes profundas de Koopman aprenden conjuntamente el levantamiento y el operador.
La arquitectura es un codificador automático: codificador, código latente, decodificador, pérdida de reconstrucción, todo como se presenta en detección de anomalías en algotrading, con una adición que es el punto central de la sección: una pérdida de linealidad que fuerza la dinámica latente a través de una única matriz aprendida. .
x_k --> [Encoder φ] --> z_k --> [Linear K] --> z_{k+1} --> [Decoder ψ] --> x̂_{k+1}
| |
+--- Linearity loss: ‖z_{k+1} - K z_k‖ ---+
sin el término, tiene un codificador automático ordinario cuyo espacio latente va seguido de una matriz multiplicada. Con ello, la red es penalizada por cualquier representación latente cuya evolución no sea lineal, que es lo que hace que lo aprendido una aproximación de Koopman y sus valores propios comparables al espectro DMD de la sección 4.
import torch
import torch.nn as nn
class DeepKoopman(nn.Module):
"""Deep Koopman autoencoder: learned lifting + linear latent dynamics."""
def __init__(self, input_dim: int, latent_dim: int, hidden_dim: int = 128):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, latent_dim),
)
self.decoder = nn.Sequential(
nn.Linear(latent_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, input_dim),
)
self.K = nn.Linear(latent_dim, latent_dim, bias=False)
def encode(self, x: torch.Tensor) -> torch.Tensor:
return self.encoder(x)
def decode(self, z: torch.Tensor) -> torch.Tensor:
return self.decoder(z)
def forward(self, x_k: torch.Tensor) -> dict:
z_k = self.encode(x_k)
z_k1_pred = self.K(z_k)
return {
"z_k": z_k,
"z_k1_pred": z_k1_pred,
"x_k1_pred": self.decode(z_k1_pred),
"x_k_recon": self.decode(z_k),
}
def multi_step_predict(self, x_0: torch.Tensor, n_steps: int) -> torch.Tensor:
"""Roll out by repeated application of the linear operator."""
z = self.encode(x_0)
preds = []
for _ in range(n_steps):
z = self.K(z)
preds.append(self.decode(z))
return torch.stack(preds, dim=1)
def latent_spectrum(self) -> np.ndarray:
"""Eigenvalues of the learned K — directly comparable to DMD's."""
return np.linalg.eigvals(self.K.weight.detach().cpu().numpy())
def koopman_loss(model: DeepKoopman, x_k: torch.Tensor, x_k1: torch.Tensor,
alpha: float = 1.0, beta: float = 0.5) -> torch.Tensor:
out = model(x_k)
z_k1_true = model.encode(x_k1)
prediction = nn.functional.mse_loss(out["x_k1_pred"], x_k1)
linearity = nn.functional.mse_loss(out["z_k1_pred"], z_k1_true)
reconstruction = nn.functional.mse_loss(out["x_k_recon"], x_k)
return prediction + alpha * linearity + beta * reconstruction
latent_spectrum Esto es lo que hace que valga la pena entrenar esto en lugar de buscar un modelo de secuencia: el operador aprendido sigue siendo una matriz, por lo que la prueba de estabilidad de la sección 4.2 y la prueba de adelanto y retraso de la sección 4.3 se aplican sin cambios a un modelo profundo.
7. Consideraciones prácticas y dificultades

Selección de rango. El rango de truncamiento es un dial de variación de sesgo: demasiado bajo pierde dinámica, demasiado alto ajusta el ruido. No mires un codo de valor singular; el blog ya responde correctamente "cuántos componentes antes de ajustar el ruido", con Marchenko-Pastur vinculado desde la teoría de matrices aleatorias en [arbitraje complejo con vectores y matrices] (/en/blog/post/complex-arbitrage-vector-matrix). Mantenga los componentes cuyos valores singulares excedan el borde de Marchenko-Pastur para la forma de su ventana e indique el resultado explícitamente junto con cada espectro que publique.
Longitud de la ventana. La teoría de Koopman supone una longitud fija. ; los mercados no ofrecen uno. Los reajustes continuos son obligatorios, y la curva de estabilidad de la sección 4.2 es precisamente el diagnóstico para determinar si la ventana elegida es lo suficientemente larga para estimarla y lo suficientemente corta para permanecer dentro de un régimen.
Sensibilidad al ruido. Los datos financieros tienen una relación señal-ruido baja y el DMD estándar está sesgado por el ruido en . Remedios que vale la pena probar antes de concluir que los modos son inestables:
- DMD total (TDMD): trata ambos y como ruidoso mediante mínimos cuadrados totales.
- DMD optimizado: optimiza directamente la descomposición del modo de valor propio frente a la norma residual de Frobenius.
- Kernel EDMD: funciona implícitamente en un espacio de características de alta dimensión sin crear el diccionario.
Si la estabilidad del modo aumenta materialmente bajo TDMD, la inestabilidad fue ruido de medición. Si no es así, fue el mercado.
8. Dónde se encuentra DMD

| Método | Linealidad | Interpretables | Previsión de varios pasos |
|---|---|---|---|
| DMD | Lineal en el espacio de estados | Sí (modos + valores propios) | Estable (potencia de matriz) |
| EDDM | Lineal en espacio elevado | Sí, dado el diccionario | Estable (potencia de matriz) |
| Koopman profundo | Lineal en el espacio aprendido | Moderado (inspeccionar K latente) | Estable (potencia de matriz) |
Para pronósticos específicos de volatilidad, el punto de comparación es la familia GARCH; consulte pronóstico de volatilidad GARCH para criptomonedas. Para conocer modelos de secuencia completamente no lineales y las compensaciones entre interpretabilidad y acumulación de errores que conllevan, consulte el transformador de fusión temporal en el comercio.
El nicho que ocupa DMD es estrecho pero real: pronósticos de varios pasos generados por una sola matriz de poder en lugar de un despliegue autorregresivo, con cada modo inspeccionable. Si ese nicho contiene alfa es la pregunta de la sección 4, no la de esta tabla.
Conclusión

La teoría de Koopman es una forma genuinamente elegante de observar la dinámica del mercado, y la elegancia es exactamente la razón por la que necesita la prueba más dura disponible. Las conclusiones:
- Ajuste DMD, luego pruebe inmediatamente el ajuste. La superposición del subespacio entre ventanas adyacentes, medida contra un valor nulo de fase aleatoria, le indica en una tarde si ha encontrado una estructura o ha memorizado una ventana.
- El radio espectral móvil es el único escalar que vale la pena monitorear, y su valor depende completamente del resultado de adelanto-retraso. En el retraso 0 es un indicador de la volatilidad; con un retraso negativo es una advertencia del régimen.
- Ancle las amplitudes en la última instantánea y nunca aumente los valores propios a la longitud de la ventana. Una gran fracción de las "señales" DMD que existen en la naturaleza son artefactos de punto flotante.
- Elija el rango según Marchenko-Pastur, no mirando con el codo, y publique el rango con cada espectro.
- Un gráfico de espectro no es un resultado. Cualquier estrategia construida sobre esto tiene que sobrevivir a tarifas, deslizamientos y una prueba de Sharpe desinflada antes de que cuente.
Para implementaciones más allá de estas, la biblioteca PyDMD cubre las variantes de DMD de manera integral, y el código de referencia de Mallen et al. cubre el lado profundo de Koopman.
Los mercados seguirán desordenados, no estacionarios y parcialmente observados. La teoría de Koopman ofrece una lente basada en principios para extraer estructura de ese desastre, siempre que se compruebe que la estructura sigue ahí la próxima semana.
Referencias y lecturas adicionales:- B. O. Koopman, "Sistemas hamiltonianos y transformación en el espacio de Hilbert", Actas de la Academia Nacional de Ciencias, 1931.
- J. H. Tu et al., "Sobre la descomposición en modo dinámico: teoría y aplicaciones", Journal of Computational Dynamics, 2014.
- M. O. Williams, I. G. Kevrekidis, C. W. Rowley, "Una aproximación basada en datos del operador Koopman: ampliación de la descomposición en modo dinámico", Journal of Nonlinear Science, 2015.
- B. Lusch, J. N. Kutz, S. L. Brunton, "Aprendizaje profundo para incrustaciones lineales universales de dinámicas no lineales", Nature Communications, 2018.
- J. Mann y J. N. Kutz, "Descomposición en modo dinámico para estrategias comerciales financieras", Finanzas cuantitativas, 2016.
- A. Mallen et al., "Koopman Neural Forecaster for Time Series with Temporal Distribution Shifts", ICML, 2023.
- E. González y M. Generelo, "Análisis de modelos económicos caóticos mediante operadores de Koopman, EDMD, teorema de Takens y Machine Learning", Ciencia de datos en Finanzas y Economía, 2022.
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.