Koopman Operators and DMD: Do Market Modes Survive Out-of-Sample?
La decomposizione in modalità dinamica ti fornisce uno spettro: una manciata di autovalori complessi, ciascuno con un tasso di crescita e una frequenza, ciascuno collegato a una modalità spaziale sulla sezione trasversale del tuo asset. Sembra una struttura. L'intera questione è se è una struttura o se è un operatore lineare che memorizza diligentemente una finestra di rumore.
Quella domanda ha due metà testabili e questo articolo è costruito attorno ad esse:
- Persistenza della modalità. Adatta DMD alla finestra e sulla finestra . Le modalità dominanti si estendono sullo stesso sottospazio o si rimescolano a ogni riadattamento? Se rimescolano, DMD è una scomposizione nel campione e niente di più - e dirlo chiaramente è più utile di un altro tutorial.
- Raggio spettrale come indicatore anticipatore. Il modulo di autovalore più grande è un singolo scalare che riassume quanto siano esplosive le dinamiche adattate. Conduce la volatilità realizzata, la rallenta o semplicemente la riafferma? Entrambe le risposte sono pubblicabili; solo il primo è commerciabile.
Che i mercati siano sistemi non lineari non stazionari è qui presupposto, non discusso: il blog lo ha già sostenuto, con la geometria dello spazio di fase in attrattori in algotrading e con statistiche BTC misurate per regime in rilevamento del regime con HMM. Quello che segue è la questione più ristretta di cosa fa la non stazionarietà a un operatore Koopman adattato e come misurarla.
1. L'idea centrale: linearizzazione delle dinamiche non lineari

Consideriamo un sistema dinamico a tempo discreto su uno spazio degli stati :
Dove è una mappa possibilmente non lineare. Per i mercati, è un vettore di rendimenti di asset, volatilità o squilibri del portafoglio ordini in una fase temporale .
L'operatore Koopman non agisce sullo Stato direttamente, ma su funzioni osservabili a valori scalari :
La proprietà chiave: è lineare, anche quando non lo è. Il prezzo è dimensionale — agisce su uno spazio funzionale a dimensione infinita. Una buona approssimazione a dimensione finita ti offre l’espressività della dinamica non lineare con la trattabilità dell’algebra lineare: la previsione diventa esponenziazione della matrice e ogni modalità è ispezionabile anziché sepolta nei pesi della rete.
2. Decomposizione spettrale dell'operatore Koopman

Se ha autovalori e autofunzioni , Poi e qualsiasi osservabile nell'arco di queste autofunzioni si decompone come:
Dove sono i modi di Koopman: coefficienti a valori vettoriali che descrivono come ciascuna autofunzione contribuisce all'intero vettore osservabile.
Ogni autovalore codifica un tasso di crescita o decadimento () e una frequenza di oscillazione ():
| Componente | Proprietà degli autovalori | Interpretazione finanziaria |
|---|---|---|
| Tendenza | , | Deriva lenta, slancio |
| Cicli | , | Oscillazioni, stagionalità |
| Transitori | Shock in decomposizione, mosse di breve durata | |
| Modalità instabili | Dinamica crescente, esplosiva |
Questo tavolo è la promessa. La sezione 4 è dove la promessa viene verificata rispetto ai dati.
3. Decomposizione in modalità dinamica (DMD)

DMD è l'algoritmo cavallo di battaglia per l'approssimazione dai dati. Date le istantanee organizzate in matrici:
DMD cerca l'operatore lineare più adatto con :
- Calcola la SVD:
- Progetto:
- Composizione automatica:
- Ripristina le modalità a spazio intero:
Le colonne di sono le modalità DMD; la diagonale di contiene gli autovalori 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
Due scelte deliberate qui. Le modalità sono normalizzate L2, perché la sezione 4.2 confronta i sottospazi delle modalità attraverso le finestre e le ampiezze non normalizzate ostacolerebbero il confronto. Le ampiezze vengono adattate all'ultima istantanea anziché alla prima, il che significa che una previsione non aumenta mai un autovalore a una grande potenza: la fonte dell'esplosione numerica che fa sembrare i segnali DMD ingenui come dinamici quando sono in overflow in virgola mobile.
4. La Misurazione

Questa è la parte dell'articolo che non è un libro di testo. Tutto quanto sopra è una procedura appropriata; di seguito è riportato il protocollo per verificare se l'adattamento significa qualcosa.
Dati. Utilizza i dati di scambio del progetto: una sezione trasversale di BTC, ETH e alternative liquide su una griglia coerente di minuti o tick, non un download giornaliero di ETF azionari. Il blog è cripto-first e l'argomento della microstruttura non è trasferibile. Costruire con risorse su righe e tempo su colonne, sui rendimenti dei log, svalutati per risorsa all'interno di ciascuna finestra.
4.1 Lo spettro degli autovalori
Report, per una finestra rappresentativa: quanti dei Gli autovalori rientrano in una tolleranza del cerchio unitario, a quale periodo di oscillazione corrisponde ciascuno in ore (period barre, convertite) e quale frazione della varianza del rendimento è superiore le modalità ricostruiscono.
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 rapporto onesto sullo spettro vale già l’articolo. Se nulla si trova vicino al cerchio unitario, non ci sono cicli persistenti da scambiare e la storia della "stagionalità annuale" della letteratura azionaria semplicemente non si applica alle criptovalute 24 ore su 24, 7 giorni su 7.
4.2 Stabilità della modalità tra finestre adiacenti
La prova decisiva. Adatta DMD alla finestra , quindi su finestra e misurare quanto del sottospazio di modo dominante sopravvive. La statistica giusta non è una correlazione ingenua dei vettori dei modi (l'ordinamento dei modi e la fase complessa sono arbitrari) ma gli angoli principali tra i due sottospazi.
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)])
Riportare la distribuzione di questa sovrapposizione e segnalarla rispetto a un valore nullo: la stessa statistica calcolata su surrogati randomizzati in fase degli stessi rendimenti. Una sovrapposizione elevata ma non superiore al valore nullo surrogato significa che le modalità seguono la struttura della covarianza, non la dinamica.
4.3 Raggio spettrale mobile rispetto alla volatilità realizzata
La pretesa di testare: , ricalcolato su finestre variabili, si muove prima della volatilità realizzata anziché con essa. Lo scalare è meccanicamente nuovo - il blog ha già monitorato scalari geometrici in tempo reale in precedenza, in particolare la curvatura di Kobayashi in varietà complesse per il trading algoritmico - ma un modulo agli autovalori di Koopman è una quantità diversa con una diversa modalità di fallimento e merita un proprio test lead-lag piuttosto che un tono ereditato.
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))])}
Allineare spectral_radius alla volatilità realizzata calcolata sulla stessa griglia e letta peak_lag. Un picco con ritardo 0 significa è una riaffermazione della volatilità con passaggi aggiuntivi. Un picco con ritardo negativo, stabile in tutto il campione e tra i ranghi, è l'unica versione di questo articolo che contiene un'affermazione negoziabile.
Una nota su cosa succede dopo un risultato positivo
Se porta, l’ovvia mossa successiva è la costruzione trasversale: classificare gli asset in base al rendimento successivo previsto da DMD, andare long sui vincitori previsti e short sui perdenti previsti. Questa costruzione non è nuova in questo caso: si tratta del commercio di fattori residui già trattato in arbitraggio statistico e trading di coppie di criptovalute e nella sezione 4 di arbitraggio complesso con vettori e matrici; l'unica svolta veramente specifica di Koopman è che gli autoportfolio portano autovalori variabili nel tempo piuttosto che caricamenti PCA statici.
La sezione relativa alla strategia è deliberatamente assente da questo articolo perché non è stata sottoposta a backtesting con commissioni e slittamenti. Quando lo è, deve superare il limite stesso del blog: il test di significatività in il Deflated Sharpe Ratio e i test multipli, contro il controesempio permanente di l'onesto negativo. Il risultato non è un grafico dello spettro DMD.
Per la previsione in un solo passaggio, l'implementazione corretta prevede dall'ultima istantanea anziché propagare un autovalore alla potenza della lunghezza della finestra:
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 estesa (EDMD): osservabili non lineari

Il DMD standard opera sul vettore dello stato grezzo. EDMD preleva prima i dati attraverso un dizionario di funzioni di base non lineari.
Dato un dizionario di funzioni scalari , definire lo stato revocato:
EDMD cerca con . Nella convenzione utilizzata dal codice seguente — stati come colonne, agendo a sinistra:
| Tipo dizionario | Funzioni | Cattura |
|---|---|---|
| Polinomio | Interazioni non lineari tra asset | |
| Base radiale (RBF) | Somiglianza locale, clustering di regimi | |
| Incorporamento ritardato | Memoria/struttura autoregressiva | |
| Fourier | Periodicità note (intraday, settimanale) | |
| Caratteristiche della volatilità | Eteroschedasticità, clustering vol |
Il dizionario è dove entra in gioco la conoscenza del dominio, e la riga del ritardo temporale è la ragione specifica di Koopman per preoccuparsi delle coordinate del ritardo: non sono una tecnica separata, sono un ulteriore blocco della mappa di sollevamento. L'inclusione stessa: scelta del ritardo , dimensione di incorporamento , e il teorema di ricostruzione alla base di esso - è già introdotto e codificato in varietà complesse per il trading algoritmico; prendi i vettori di ritardo da lì e inseriscili direttamente build_financial_dictionary come righe aggiuntive.
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. Reti profonde di Koopman

Il dizionario EDMD è realizzato a mano, il che rappresenta un vero limite quando il sottospazio invariante di Koopman è sconosciuto. Le reti Deep Koopman apprendono congiuntamente il sollevamento e l'operatore.
L'architettura è un autoencoder — codificatore, codice latente, decodificatore, perdita di ricostruzione, tutto come introdotto in rilevamento delle anomalie in algotrading — con un'aggiunta che è il punto centrale della sezione: una perdita di linearità che forza le dinamiche latenti attraverso un'unica matrice appresa .
x_k --> [Encoder φ] --> z_k --> [Linear K] --> z_{k+1} --> [Decoder ψ] --> x̂_{k+1}
| |
+--- Linearity loss: ‖z_{k+1} - K z_k‖ ---+
Senza il termine si ha un normale autocodificatore il cui spazio latente sembra essere seguito da una matrice moltiplicata. Con esso, la rete viene penalizzata per ogni rappresentazione latente la cui evoluzione non è lineare – che è ciò che rende i sapienti un'approssimazione di Koopman e i suoi autovalori paragonabili allo spettro DMD della sezione 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 è ciò che rende questo vale la pena addestrarlo piuttosto che raggiungere un modello di sequenza: l'operatore appreso è ancora una matrice, quindi il test di stabilità della sezione 4.2 e il test lead-lag della sezione 4.3 si applicano invariati a un modello profondo.
7. Considerazioni pratiche e insidie

Selezione del rango. Il rango di troncamento è un quadrante della varianza del bias: troppo basso manca la dinamica, troppo alto si adatta al rumore. Non guardare a occhio un gomito a valore singolare; il blog risponde già correttamente a "quanti componenti prima di adattare il rumore", con il limite di Marchenko-Pastur dalla teoria della matrice casuale in arbitraggio complesso con vettori e matrici. Mantieni i componenti i cui valori singolari superano il bordo Marchenko-Pastur per la forma della tua finestra e dichiara il risultato esplicitamente accanto a ogni spettro che pubblichi.
Lunghezza della finestra. La teoria di Koopman presuppone un valore fisso ; i mercati non ne forniscono uno. I refitting progressivi sono obbligatori e la curva di stabilità della sezione 4.2 è proprio la diagnosi per stabilire se la finestra scelta è sufficientemente lunga per stimare e sufficientemente breve per rimanere all'interno di un regime.
Sensibilità al rumore. I dati finanziari hanno un basso rapporto segnale-rumore e il DMD standard è distorto dal rumore in . Rimedi che vale la pena provare prima di concludere che le modalità sono instabili:
- DMD totale (TDMD): tratta entrambi E altrettanto rumoroso tramite i minimi quadrati totali.
- DMD ottimizzato: ottimizza direttamente la scomposizione della modalità degli autovalori rispetto alla norma di Frobenius residua.
- Kernel EDMD: funziona implicitamente in uno spazio di funzionalità ad alta dimensione senza creare il dizionario.
Se la stabilità della modalità aumenta materialmente sotto TDMD, l'instabilità era il rumore di misurazione. Se così non fosse, sarebbe stato il mercato.
8. Dove si trova DMD

| Metodo | Linearità | Interpretabile | Previsioni a più fasi |
|---|---|---|---|
| DMD | Lineare nello spazio degli stati | Sì (modi + autovalori) | Stabile (potenza della matrice) |
| EDDM | Lineare nello spazio sollevato | Sì, dato il dizionario | Stabile (potenza della matrice) |
| Profondo Koopman | Lineare nello spazio appreso | Moderato (ispezionare K latente) | Stabile (potenza della matrice) |
Per le previsioni specifiche sulla volatilità, il punto di confronto è la famiglia GARCH: vedi Previsione della volatilità GARCH per le criptovalute. Per i modelli di sequenza completamente non lineari e i compromessi in termini di interpretabilità e accumulo di errori che comportano, vedere Temporal Fusion Transformer nel trading.
La nicchia occupata da DMD è ristretta ma reale: previsioni in più fasi generate da un unico potere di matrice piuttosto che da un'implementazione autoregressiva, con ogni modalità ispezionabile. Se quella nicchia contenga alfa è la domanda della sezione 4, non di questa tabella.
Conclusione

La teoria di Koopman è un modo veramente elegante di guardare alle dinamiche del mercato, e l’eleganza è esattamente il motivo per cui necessita del test più severo disponibile. I takeaway:
- Adatta DMD, quindi testa immediatamente l'adattamento. La sovrapposizione del sottospazio tra finestre adiacenti, misurata rispetto a un valore nullo randomizzato in fase, ti dice entro un pomeriggio se hai trovato la struttura o memorizzato una finestra.
- Il raggio spettrale mobile è l'unico scalare che vale la pena monitorare e il suo valore dipende interamente dal risultato anticipo-ritardo. Al lag 0 è un proxy di volatilità; con un ritardo negativo è un avvertimento del regime.
- Ancora le ampiezze all'ultima istantanea e non aumenta mai gli autovalori alla lunghezza della finestra. Una grande parte dei "segnali" DMD in natura sono artefatti in virgola mobile.
- Scegli la classifica in base a Marchenko-Pastur, non guardando un gomito, e pubblica la classifica con ogni spettro.
- Un grafico dello spettro non è un risultato. Qualsiasi strategia costruita su questo deve sopravvivere a commissioni, slittamenti e un test di Sharpe sgonfio prima di poter contare.
Per implementazioni oltre a queste, la libreria PyDMD copre le varianti DMD in modo completo e il codice di riferimento di Mallen et al. copre il lato profondo di Koopman.
I mercati rimarranno disordinati, non stazionari e parzialmente osservati. La teoria di Koopman offre una lente di principio per estrarre la struttura da quel pasticcio, a patto di verificare che la struttura sia ancora lì la prossima settimana.
Riferimenti e approfondimenti:- B. O. Koopman, "Sistemi hamiltoniani e trasformazione nello spazio di Hilbert", Atti dell'Accademia nazionale delle scienze, 1931.
- J. H. Tu et al., "Sulla decomposizione in modalità dinamica: teoria e applicazioni", Journal of Computational Dynamics, 2014.
- M. O. Williams, I. G. Kevrekidis, C. W. Rowley, "Un'approssimazione basata sui dati dell'operatore Koopman: estensione della decomposizione in modalità dinamica", Journal of Nonlinear Science, 2015.
- B. Lusch, J. N. Kutz, S. L. Brunton, "Apprendimento profondo per incorporamenti lineari universali di dinamiche non lineari", Nature Communications, 2018.
- J. Mann e J. N. Kutz, "Scomposizione in modalità dinamica per strategie di trading finanziario", Finanza quantitativa, 2016.
- A. Mallen et al., "Koopman Neural Forecaster for Time Series with Timeral Distribution Shifts", ICML, 2023.
- E. Gonzalez e M. Generelo, "Analisi di modelli economici caotici attraverso operatori di Koopman, EDMD, teorema di Takens e Machine Learning", Data Science in Finance and Economics, 2022.
Autori
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.