Koopman Operators and DMD: Do Market Modes Survive Out-of-Sample?
Dynamic Mode Decomposition memberikan spektrum: sejumlah eigenvalue kompleks, masing-masing dengan laju pertumbuhan dan frekuensi, serta terikat pada mode spasial di seluruh cross-section aset. Ia tampak seperti struktur. Pertanyaan utamanya adalah apakah ia benar-benar struktur atau hanya operator linear yang menghafal satu jendela noise.
Pertanyaan itu memiliki dua bagian yang dapat diuji, dan artikel ini dibangun di sekelilingnya:
- Persistensi mode. Fit DMD pada jendela dan . Apakah mode dominan membentang pada subruang yang sama, atau berubah setiap refit? Jika berubah, DMD hanyalah dekomposisi in-sample — dan menyatakannya dengan jelas lebih berguna daripada tutorial lain.
- Jari-jari spektral sebagai indikator pendahulu. Modulus eigenvalue terbesar adalah satu skalar yang merangkum seberapa eksplosif dinamika hasil fitting. Apakah ia mendahului volatilitas terealisasi, tertinggal, atau hanya mengulanginya? Semua jawaban dapat dipublikasikan; hanya yang pertama dapat diperdagangkan.
Bahwa pasar adalah sistem nonlinear non-stasioner dianggap sebagai asumsi di sini, bukan argumen — blog ini telah membahasnya melalui geometri ruang fase dalam attractor dalam algotrading dan statistik BTC per-regime dalam deteksi regime dengan HMM. Yang dibahas berikutnya adalah pertanyaan yang lebih sempit: apa dampak non-stasioneritas terhadap operator Koopman yang di-fit, dan bagaimana mengukurnya.
1. Idea Teras: Melinierkan Dinamik Tidak Linear

Pertimbangkan sistem dinamis waktu-diskret pada ruang keadaan :
di mana adalah pemetaan yang mungkin nonlinear. Untuk pasar, adalah vektor return aset, volatilitas, atau imbalance order book pada langkah waktu .
Operator Koopman tidak bekerja langsung pada keadaan , melainkan pada fungsi observable bernilai skalar :
Sifat utamanya: linear, bahkan ketika tidak linear. Harganya adalah dimensionalitas — bekerja pada ruang fungsi berdimensi tak hingga. Aproksimasi berdimensi hingga yang baik memberi ekspresivitas dinamika nonlinear dengan kemudahan aljabar linear: prediksi menjadi eksponensiasi matriks, dan setiap mode dapat diperiksa alih-alih terkubur dalam bobot jaringan.
2. Penguraian Spektrum Operator Koopman

Jika memiliki eigenvalue dan eigenfunction , maka , dan setiap observable dalam span eigenfunction ini terurai sebagai:
di mana adalah mode Koopman — koefisien bernilai vektor yang menjelaskan kontribusi setiap eigenfunction terhadap seluruh vektor observable.
Setiap eigenvalue mengkodekan laju pertumbuhan atau peluruhan () dan frekuensi osilasi ():
| Component | Eigenvalue property | Financial interpretation |
|---|---|---|
| Trend | , | Slow drift, momentum |
| Cycles | , | Oscillations, seasonality |
| Transients | Decaying shocks, short-lived moves | |
| Unstable modes | Growing, explosive dynamics |
Tabel ini adalah janjinya. Bagian 4 adalah tempat janji itu diperiksa terhadap data.
3. Dynamic Mode Decomposition (DMD)

DMD adalah algoritme utama untuk mengaproksimasi dari data. Dengan snapshot yang disusun menjadi matriks:
DMD mencari operator linear dengan fit terbaik sehingga :
- Hitung SVD:
- Proyeksikan:
- Lakukan eigendecomposition:
- Pulihkan mode ruang penuh:
Kolom adalah mode DMD; diagonal memuat eigenvalue 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
Dua pilihan di sini disengaja. Mode dibuat L2-normalised, karena bagian 4.2 membandingkan subruang mode antarjendela dan amplitudo yang tidak dinormalisasi akan menenggelamkan perbandingan. Amplitudo di-fit pada snapshot terakhir, bukan yang pertama, sehingga forecast tidak pernah menaikkan eigenvalue ke pangkat besar — sumber ledakan numerik yang membuat sinyal DMD naif tampak seperti dinamika padahal hanya overflow floating-point.
4. Pengukuran

Inilah bagian artikel yang bukan textbook. Semua di atas adalah prosedur fitting; bagian di bawah adalah protokol untuk mengetahui apakah fit tersebut berarti.
Data. Gunakan data exchange milik proyek — cross-section BTC, ETH dan alt likuid pada grid menit atau tick yang konsisten, bukan unduhan harian ETF saham. Blog ini crypto-first dan argumen microstructure tidak dapat dipindahkan begitu saja. Bangun dengan aset pada baris dan waktu pada kolom, memakai log return yang di-demean per aset dalam setiap jendela.
4.1 Spektrum eigenvalue
Untuk jendela yang representatif, laporkan berapa banyak dari eigenvalue yang berada dalam toleransi lingkaran satuan, periode osilasi masing-masing dalam jam (periode bar yang telah dikonversi), dan fraksi varians return yang direkonstruksi oleh mode teratas.
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)
Laporan spektrum yang jujur saja sudah bernilai sebagai artikel. Jika tidak ada yang dekat dengan lingkaran satuan, tidak ada siklus persisten untuk diperdagangkan dan cerita "musiman tahunan" dari literatur saham tidak berlaku untuk crypto 24/7.
4.2 Kestabilan mod antara tetingkap bersebelahan
Inilah tes penentu. Fit DMD pada jendela , lalu pada , dan ukur berapa banyak subruang mode dominan yang bertahan. Statistik yang tepat bukan korelasi naif vektor mode — urutan mode dan fase kompleks bersifat arbitrer — melainkan sudut utama antara kedua subruang.
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)])
Laporkan distribusi overlap ini dan bandingkan dengan null: statistik yang sama dihitung pada surrogate fase-acak dari return yang sama. Overlap yang tinggi tetapi tidak lebih tinggi daripada null surrogate berarti mode mengikuti struktur kovarians, bukan dinamika.
4.3 Jejari spektrum bergulir berbanding turun naik terealisasi
Klaim yang diuji: , dihitung ulang pada jendela bergulir, bergerak sebelum volatilitas terealisasi dan bukan bersamaan dengannya. Skalar ini memang baru secara mekanis — blog ini pernah memantau skalar geometris secara real time, terutama kurvatur Kobayashi dalam manifold kompleks untuk algorithmic trading — tetapi modulus eigenvalue Koopman adalah kuantitas yang berbeda dengan mode kegagalan berbeda, sehingga layak mendapat tes lead-lag sendiri, bukan pitch warisan.
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))])}
Selaraskan spectral_radius dengan volatilitas terealisasi yang dihitung pada grid yang sama lalu baca peak_lag. Peak pada lag 0 berarti hanyalah pengulangan volatilitas dengan langkah tambahan. Peak pada lag negatif yang stabil di seluruh sampel dan rank adalah satu-satunya versi artikel ini yang memiliki klaim dapat diperdagangkan.
Nota tentang perkara selepas keputusan positif
Jika memang mendahului, langkah berikutnya yang jelas adalah konstruksi cross-sectional: urutkan aset berdasarkan return langkah berikutnya yang diprediksi DMD, long pada pemenang yang diprediksi dan short pada pecundang yang diprediksi. Konstruksi ini bukan hal baru — ia adalah trade factor-residual yang sudah dibahas dalam arbitrase statistik dan pairs trading dalam crypto dan bagian 4 dari arbitrase kompleks dengan vektor dan matriks; satu-satunya twist khusus Koopman adalah eigenportfolio membawa eigenvalue yang berubah terhadap waktu, bukan loading PCA statis.
Bagian strategi sengaja tidak ada dalam artikel ini karena belum di-backtest dengan fee dan slippage. Ketika dilakukan, strategi itu harus melewati standar blog sendiri: tes signifikansi dalam Deflated Sharpe Ratio dan multiple testing, melawan contoh tandingan tetap berupa hasil negatif yang jujur. Plot spektrum DMD bukan hasil.
Untuk forecast satu langkah itu sendiri, implementasi yang benar memprediksi dari snapshot terakhir, bukan merambatkan eigenvalue ke pangkat panjang jendela:
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. Extended DMD (EDMD): Observable Tidak Linear

DMD standar bekerja pada vektor keadaan mentah. EDMD terlebih dahulu mengangkat data melalui dictionary fungsi basis nonlinear.
Dengan dictionary yang terdiri dari fungsi skalar , definisikan keadaan terangkat:
EDMD mencari dengan . Dalam konvensi yang digunakan kode di bawah — keadaan sebagai kolom, bekerja dari kiri:
| Dictionary type | Functions | Captures |
|---|---|---|
| Polynomial | Nonlinear cross-asset interactions | |
| Radial basis (RBF) | Local similarity, regime clustering | |
| Time-delay embedding | Memory / autoregressive structure | |
| Fourier | Known periodicities (intraday, weekly) | |
| Volatility features | Heteroskedasticity, vol clustering |
Dictionary adalah tempat pengetahuan domain masuk, dan baris time-delay adalah alasan khusus Koopman untuk peduli pada koordinat delay: ini bukan teknik terpisah yang ditempelkan, melainkan satu blok tambahan dari lifting map. Embedding itu sendiri — pilihan delay , dimensi embedding , dan teorema rekonstruksi di belakangnya — sudah diperkenalkan dan dikodekan dalam manifold kompleks untuk algorithmic trading; ambil vektor delay dari sana dan masukkan langsung ke build_financial_dictionary sebagai baris tambahan.
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. Deep Koopman Networks

Dictionary EDMD dibuat dengan tangan, dan ini merupakan keterbatasan nyata ketika subruang invariant Koopman tidak diketahui. Deep Koopman networks mempelajari lifting dan operator secara bersama-sama.
Arsitekturnya adalah autoencoder — encoder, kode laten, decoder, reconstruction loss, semuanya diperkenalkan dalam deteksi anomali pada algotrading — dengan satu tambahan yang merupakan inti bagian ini: linearity loss yang memaksa dinamika laten melalui satu matriks terpelajar .
x_k --> [Encoder φ] --> z_k --> [Linear K] --> z_{k+1} --> [Decoder ψ] --> x̂_{k+1}
| |
+--- Linearity loss: ‖z_{k+1} - K z_k‖ ---+
Tanpa suku , Anda memiliki autoencoder biasa yang ruang latennya kebetulan diikuti perkalian matriks. Dengan suku itu, jaringan dihukum untuk setiap representasi laten yang evolusinya tidak linear — inilah yang membuat terpelajar menjadi aproksimasi Koopman dan eigenvalue-nya dapat dibandingkan dengan spektrum DMD bagian 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 adalah alasan untuk melatih model ini alih-alih memakai sequence model: operator yang dipelajari masih berupa matriks, sehingga tes stabilitas bagian 4.2 dan tes lead-lag bagian 4.3 berlaku tanpa perubahan pada model deep.
7. Pertimbangan Praktikal dan Perangkap

Pemilihan rank. Rank truncation adalah dial bias-variance — terlalu rendah melewatkan dinamika, terlalu tinggi mem-fit noise. Jangan menebak elbow singular-value dengan mata; blog ini sudah menjawab dengan tepat "berapa banyak komponen sebelum Anda mem-fit noise", menggunakan batas Marchenko-Pastur dari Random Matrix Theory dalam arbitrase kompleks dengan vektor dan matriks. Pertahankan komponen dengan singular value di atas tepi Marchenko-Pastur untuk bentuk jendela Anda, dan nyatakan yang dihasilkan secara eksplisit bersama setiap spektrum yang dipublikasikan.
Panjang jendela. Teori Koopman mengasumsikan tetap; pasar tidak menyediakannya. Refit bergulir wajib dilakukan, dan kurva stabilitas bagian 4.2 tepat merupakan diagnostik apakah jendela yang dipilih cukup panjang untuk mengestimasi dan cukup pendek untuk tetap berada dalam satu regime.
Sensitivitas terhadap noise. Data finansial memiliki signal-to-noise ratio rendah dan DMD standar bias oleh noise dalam . Sebelum menyimpulkan bahwa mode tidak stabil, cobalah perbaikan berikut:
- Total DMD (TDMD) — treats both and as noisy via total least squares.
- Optimized DMD — mengoptimalkan langsung dekomposisi eigenvalue-mode terhadap norma residual Frobenius.
- Kernel EDMD — bekerja secara implisit dalam ruang fitur berdimensi tinggi tanpa membangun dictionary.
Jika stabilitas mode meningkat secara material di bawah TDMD, ketidakstabilan itu adalah measurement noise. Jika tidak, penyebabnya adalah pasar.
8. Kedudukan DMD

| Method | Linearity | Interpretable | Multi-step forecast |
|---|---|---|---|
| DMD | Linear dalam ruang keadaan | Ya (mode + eigenvalue) | Stabil (pangkat matriks) |
| EDMD | Linear dalam ruang terangkat | Ya, dengan dictionary | Stabil (pangkat matriks) |
| Deep Koopman | Linear dalam ruang terpelajar | Sedang (periksa K laten) | Stabil (pangkat matriks) |
Untuk forecasting khusus volatilitas, titik perbandingannya adalah keluarga GARCH — lihat forecasting volatilitas GARCH untuk crypto. Untuk sequence model yang sepenuhnya nonlinear serta trade-off interpretabilitas dan akumulasi error yang dibawanya, lihat Temporal Fusion Transformer dalam trading.
Niche yang ditempati DMD sempit tetapi nyata: forecast multi-langkah yang dihasilkan oleh satu pangkat matriks, bukan rollout autoregresif, dengan setiap mode dapat diperiksa. Apakah niche itu berisi alpha adalah pertanyaan bagian 4, bukan pertanyaan tabel ini.
Kesimpulan

Teori Koopman adalah cara yang benar-benar elegan untuk melihat dinamika pasar, dan justru karena elegan ia memerlukan tes paling keras yang tersedia. Kesimpulannya:
- Fit DMD, lalu segera uji fit tersebut. Overlap subruang antarjendela, dibandingkan dengan null fase-acak, memberi tahu dalam satu sore apakah Anda menemukan struktur atau menghafal satu jendela.
- Jari-jari spektral bergulir adalah satu-satunya skalar yang layak dipantau, dan nilainya sepenuhnya bergantung pada hasil lead-lag. Pada lag 0 ia adalah proksi volatilitas; pada lag negatif ia adalah peringatan regime.
- Jangkar amplitudo pada snapshot terakhir dan jangan pernah menaikkan eigenvalue ke pangkat panjang jendela. Sebagian besar "sinyal" DMD di lapangan adalah artefak floating-point.
- Pilih rank dengan Marchenko-Pastur, bukan dengan melihat elbow, dan publikasikan rank bersama setiap spektrum.
- Plot spektrum bukan hasil. Strategi apa pun yang dibangun di atasnya harus bertahan dari fee, slippage dan tes Sharpe yang dideflasi sebelum dianggap berhasil.
Untuk implementasi di luar ini, library PyDMD mencakup varian DMD secara komprehensif, dan kode referensi Mallen et al. mencakup sisi deep Koopman.
Pasar akan tetap berantakan, non-stasioner dan hanya teramati sebagian. Teori Koopman menawarkan lensa berprinsip untuk mengekstrak struktur dari kekacauan itu — asalkan Anda memeriksa bahwa struktur tersebut masih ada minggu depan.
Rujukan dan bacaan lanjut:
- B. O. Koopman, "Hamiltonian Systems and Transformation in Hilbert Space," Proceedings of the National Academy of Sciences, 1931.
- J. H. Tu et al., "On Dynamic Mode Decomposition: Theory and Applications," Journal of Computational Dynamics, 2014.
- M. O. Williams, I. G. Kevrekidis, C. W. Rowley, "A Data-Driven Approximation of the Koopman Operator: Extending Dynamic Mode Decomposition," Journal of Nonlinear Science, 2015.
- B. Lusch, J. N. Kutz, S. L. Brunton, "Deep Learning for Universal Linear Embeddings of Nonlinear Dynamics," Nature Communications, 2018.
- J. Mann and J. N. Kutz, "Dynamic Mode Decomposition for Financial Trading Strategies," Quantitative Finance, 2016.
- A. Mallen et al., "Koopman Neural Forecaster for Time Series with Temporal Distribution Shifts," ICML, 2023.
- E. Gonzalez and M. Generelo, "Analysis of chaotic economic models through Koopman operators, EDMD, Takens' theorem and Machine Learning," Data Science in Finance and Economics, 2022.
Pengarang
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.