← 기사 목록으로
August 12, 2026
5분 소요

Koopman Operators and DMD: Do Market Modes Survive Out-of-Sample?

Koopman Operators and DMD: Do Market Modes Survive Out-of-Sample?
#mathematics
#Koopman
#dynamical-systems
#spectral
#prediction

동적 모드 분해는 스펙트럼을 제공합니다. 각각은 자산 단면에 걸쳐 공간 모드에 연결된 성장률과 빈도를 갖는 소수의 복잡한 고유값입니다. 구조처럼 보입니다. 전체 질문은 그것이 구조인지 아니면 노이즈의 한 창을 충실히 기억하는 선형 연산자인지 여부입니다.

해당 질문에는 테스트 가능한 두 부분이 있으며 이 문서는 이를 중심으로 구성되었습니다.

  1. 모드 지속성. tt 창과 t+1t+1 창에 DMD를 맞춥니다. 주요 모드가 동일한 하위 공간에 걸쳐 있습니까, 아니면 모든 재구성을 다시 섞습니까? 다시 섞는다면 DMD는 샘플 내 분해일 뿐이며 그 이상은 아닙니다. 그리고 분명히 말하는 것이 다른 튜토리얼보다 더 유용합니다.
  2. 주요 지표로서의 스펙트럼 반경. 최대 고유값 계수 ρ=maxjλj\rho = \max_j |\lambda_j|는 피팅된 역학이 얼마나 폭발적인지 요약하는 단일 스칼라입니다. 실현된 변동성을 이끌까요, 지연시키나요, 아니면 단순히 다시 설명하나요? 두 답변 모두 게시 가능합니다. 첫 번째 것만 거래 가능합니다.

시장이 비정상 비선형 시스템이라는 것은 여기에서 가정되며 논쟁의 여지가 없습니다. 블로그는 이미 algotrading의 어트랙터]의 위상 공간 기하학과 HMMs를 사용한 정권 감지]의 측정된 정권별 BTC 통계를 사용하여 이를 주장했습니다. 다음은 적합된 Koopman 연산자에 비정상성이 미치는 영향과 이를 측정하는 방법에 대한 더 좁은 질문입니다.

1. 핵심 아이디어: 비선형 역학 선형화

비선형 시장 움직임이 선형 잠재 흐름으로 변함

상태 공간 MRn\mathcal{M} \subseteq \mathbb{R}^n의 이산시간 동적 시스템을 고려해보세요.

xk+1=F(xk)x_{k+1} = F(x_k)

여기서 F:MMF: \mathcal{M} \to \mathcal{M}는 비선형 맵일 수 있습니다. 시장의 경우 xkx_kkk 시간 간격의 자산 수익률, 변동성 또는 주문장 불균형의 벡터입니다.

Koopman 연산자 K\mathcal{K}는 상태 xx에서 직접 작동하지 않고 스칼라 값 관찰 가능 함수 g:MCg: \mathcal{M} \to \mathbb{C}에서 작동합니다.

[Kg](x)=g(F(x))[\mathcal{K} g](x) = g(F(x))

주요 속성: FF가 선형이 아닌 경우에도 K\mathcal{K}는 선형입니다. 가격은 차원입니다. K\mathcal{K}는 무한 차원 기능 공간에서 작동합니다. 좋은 유한 차원 근사법은 선형 대수학의 다루기 쉬움과 함께 비선형 동역학의 표현력을 제공합니다. 예측은 행렬 지수화가 되며 모든 모드는 네트워크 가중치에 매몰되지 않고 검사 가능합니다.

2. 쿠프만 연산자의 스펙트럼 분해

시장 역학의 일관된 스펙트럼 모드

K\mathcal{K}에 고유값 λj\lambda_j와 고유 함수 φj\varphi_j가 있는 경우 Kφj=λjφj\mathcal{K} \varphi_j = \lambda_j \varphi_j와 이러한 고유 함수 범위에서 관찰 가능한 gg는 다음과 같이 분해됩니다.

g(xk)=j=1φj(x0)λjkvjg(x_k) = \sum_{j=1}^{\infty} \varphi_j(x_0) \, \lambda_j^{k} \, v_j

여기서 vjv_jKoopman 모드입니다. 각 고유 함수가 전체 관찰 가능한 벡터에 어떻게 기여하는지 설명하는 벡터 값 계수입니다.

각 고유값 λj=λjeiωj\lambda_j = |\lambda_j| e^{i\omega_j}는 성장률(λj|\lambda_j|)과 진동 주파수(ωj\omega_j)를 인코딩합니다.

구성요소 고유값 속성 금융통역
동향 λ1\lambda \approx 1, ω0\omega \approx 0 느린 드리프트, 추진력
사이클 λ1\lvert\lambda\rvert \approx 1, ω0\omega \neq 0 진동, 계절성
과도 현상 λ<1\lvert\lambda\rvert < 1 부패하는 충격, 단명한 움직임
불안정한 모드 λ>1\lvert\lambda\rvert > 1 성장하고 폭발적인 역학

이 테이블은 약속입니다. 섹션 4에서는 Promise를 데이터와 비교하여 확인합니다.

3. 동적 모드 분해(DMD)

다이내믹 모드 분해 및 재조립

DMD는 데이터에서 K\mathcal{K}를 근사화하기 위한 주력 알고리즘입니다. 주어진 스냅샷을 행렬로 배열하면 다음과 같습니다.

X=[x0x1xm1],X=[x1x2xm]X = \begin{bmatrix} x_0 & x_1 & \cdots & x_{m-1} \end{bmatrix}, \quad X' = \begin{bmatrix} x_1 & x_2 & \cdots & x_m \end{bmatrix}

DMD는 XAXX' \approx AX를 사용하여 가장 적합한 선형 연산자 AA를 찾습니다.

  1. SVD 계산: X=UΣVX = U \Sigma V^*
  2. 프로젝트: A~=UXVΣ1\tilde{A} = U^* X' V \Sigma^{-1}
  3. 고유분해: A~W=WΛ\tilde{A} W = W \Lambda
  4. 전체 공간 모드 복구: Φ=XVΣ1W\Phi = X' V \Sigma^{-1} W

Φ\Phi의 열은 DMD 모드입니다. Λ\Lambda의 대각선은 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

여기서는 두 가지 고의적인 선택이 있습니다. 모드는 L2-정규화됩니다. 섹션 4.2에서는 창 전체의 모드 부분 공간을 비교하고 정규화되지 않은 진폭은 비교를 압도하기 때문입니다. 진폭은 첫 번째가 아닌 마지막 스냅샷에 맞춰집니다. 이는 예측이 고유값을 큰 거듭제곱으로 올리지 않는다는 것을 의미합니다. 이는 순진한 DMD 신호가 부동 소수점 오버플로일 때 ​​동역학처럼 보이게 만드는 수치 폭발의 원인입니다.

4. 측정

상태공간 구조로 변환된 관측치

교과서에 없는 부분입니다. 위의 모든 내용은 적절한 절차입니다. 아래는 적합성이 어떤 의미인지 알아보기 위한 프로토콜입니다.

데이터. 주식 ETF의 일일 다운로드가 아닌 일관된 분 또는 틱 그리드의 BTC, ETH 및 유동 대체물의 단면인 프로젝트 자체 교환 데이터를 사용하십시오. 블로그는 암호화 우선이며 미세구조 주장은 이전되지 않습니다. 행의 자산과 열의 시간, 로그 반환으로 각 창 내에서 자산별로 품위가 저하된 XX를 빌드하세요.

4.1 고유값 스펙트럼

대표 창에 대해 보고: 단위원의 허용 오차 내에 있는 rr 고유값 수, 각각 시간 단위에 해당하는 진동 주기(기간 =2π/ωj= 2\pi / |\omega_j| 바, 변환), 상위 rr 모드가 재구성하는 반환 분산의 비율.

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)

정직한 스펙트럼 보고서는 이미 기사의 가치가 있습니다. 단위원 근처에 아무 것도 없으면 거래할 지속적인 주기가 없으며 주식 관련 문헌의 "연간 계절성" 이야기가 연중무휴 암호화폐에 적용되지 않습니다.

4.2 인접한 창 전체의 모드 안정성

결정적인 테스트. DMD를 tt 창에 맞추고 t+1t+1 창에 맞추고 지배 모드 부분 공간이 얼마나 남아 있는지 측정합니다. 올바른 통계는 모드 벡터의 순진한 상관 관계가 아니라(모드 순서 및 복소 위상은 임의적임) 두 부분 공간 사이의 주각입니다.

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)])

이 중복의 분포를 보고하고 null에 대해 보고합니다. 동일한 수익의 단계 무작위 대리자에 대해 계산된 동일한 통계입니다. 높지만 서로게이트 널(surrogate null)보다 높지 않은 중첩은 모드가 역학이 아닌 공분산 구조를 추적하고 있음을 의미합니다.

4.3 실현된 변동성에 대한 롤링 스펙트럼 반경

테스트할 주장: 롤링 윈도우에서 다시 계산된 ρt=maxjλj\rho_t = \max_j |\lambda_j|는 변동성이 실현되기 전에 *이동합니다. 스칼라는 기계적으로 새로운 것입니다. 블로그는 이전에 실시간으로 기하 스칼라를 모니터링한 적이 있으며, 특히 알고리즘 거래를 위한 복잡한 다양체]에서 고바야시 곡률을 모니터링했습니다. 그러나 Koopman 고유값 모듈러스는 다른 실패 모드를 가진 다른 수량이며 상속된 피치보다는 자체 리드-지연 테스트를 받을 가치가 있습니다.

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))])}

동일한 그리드에서 계산된 실현된 변동성을 위해 spectral_radius를 정렬하고 peak_lag를 읽습니다. 지연 0의 피크는 ρt\rho_t가 추가 단계를 통한 변동성 재기술임을 의미합니다. 샘플 전체와 순위 전체에서 안정적인 음수 지연의 피크는 거래 가능한 주장이 있는 이 기사의 유일한 버전입니다.

긍정적인 결과가 나온 후의 참고사항

ρt\rho_t가 앞서는 경우, 분명한 다음 조치는 단면적 구성입니다. 즉, DMD가 예측한 다음 단계 수익률을 기준으로 자산 순위를 매기고, 예상 승자를 롱으로 두고, 예상 패자를 매도합니다. 이러한 구성은 여기서 새로운 것이 아닙니다. 이는 crypto의 통계 차익거래 및 쌍 거래]와 벡터 및 행렬을 사용한 복잡한 차익거래; Koopman에 특화된 유일한 변형은 고유 포트폴리오가 정적 PCA 로딩이 아닌 시간에 따라 변하는 고유값을 전달한다는 것입니다.

The strategy section is deliberately absent from this article because it has not been backtested here with fees and slippage. When it is, it has to clear the blog's own bar: the significance test in the Deflated Sharpe Ratio and multiple testing, against the standing counter-example of the honest negative. A DMD spectrum plot is not a result.

1단계 예측 자체의 경우 올바른 구현은 고유값을 창 길이의 거듭제곱으로 전파하는 대신 마지막 스냅샷에서 예측합니다.

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(EDMD): 비선형 관측 가능 항목

동적 모드를 확장하는 비선형 특성 필드

표준 DMD는 원시 상태 벡터에서 작동합니다. EDMD는 먼저 비선형 기반 함수 사전을 통해 데이터를 리프트합니다.

스칼라 함수 di:RnRd_i: \mathbb{R}^n \to \mathbb{R}의 사전 D={d1,,dp}\mathbf{D} = \{d_1, \ldots, d_p\}가 주어지면 리프트 상태를 정의합니다.

zk=[d1(xk)d2(xk)dp(xk)]Rpz_k = \begin{bmatrix} d_1(x_k) \\ d_2(x_k) \\ \vdots \\ d_p(x_k) \end{bmatrix} \in \mathbb{R}^p

EDMD는 zk+1Kzkz_{k+1} \approx K z_k를 사용하여 KRp×pK \in \mathbb{R}^{p \times p}를 찾습니다. 아래 코드에서 사용되는 규칙에서는 로 표시되며 KK는 왼쪽에서 작동합니다.

K=AG1,G=k=0m1zkzkT,A=k=0m1zk+1zkTK = A G^{-1}, \quad G = \sum_{k=0}^{m-1} z_k z_k^{T}, \quad A = \sum_{k=0}^{m-1} z_{k+1} z_k^{T}

사전 유형 기능 캡처
다항식 xi, xixj, xi2,x_i,\ x_i x_j,\ x_i^2, \ldots 비선형 자산 간 상호 작용
방사형 기준(RBF) exp(γxck2)\exp(-\gamma \lVert x - c_k \rVert^2) 지역 유사성, 정권 클러스터링
시간 지연 임베딩 xk, xk1,, xkτx_k,\ x_{k-1}, \ldots,\ x_{k-\tau} 메모리/자동회귀 구조
푸리에 sin(2πfjt), cos(2πfjt)\sin(2\pi f_j t),\ \cos(2\pi f_j t) 알려진 주기(일중, 주별)
변동성 기능 rt, rt2\lvert r_t \rvert,\ r_t^2 이분산성, vol 클러스터링

사전은 도메인 지식이 입력되는 곳이며 시간 지연 행은 지연 좌표에 전혀 관심을 두는 Koopman 특유의 이유입니다. 이는 별도의 기술이 추가된 것이 아니라 리프팅 맵의 또 하나의 블록입니다. 임베딩 자체(지연 τ\tau 선택, 임베딩 차원 dd 및 그 뒤에 있는 재구성 정리)는 이미 알고리즘 거래를 위한 복잡한 다양체; 거기에서 지연 벡터를 가져와 build_financial_dictionary에 추가 행으로 바로 공급합니다.

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. 딥 쿠프만 네트워크

깊은 네트워크 학습 잠재 선형 역학

EDMD 사전은 손으로 제작되는데, 이는 Koopman 불변 부분공간을 알 수 없는 경우 실제 제한 사항입니다. Deep Koopman 네트워크는 리프팅과 작업자를 공동으로 학습합니다.

아키텍처는 알고트레이딩의 이상 탐지]에 소개된 인코더, 잠재 코드, 디코더, 재구성 손실과 같은 자동 인코더입니다. 섹션의 전체 요점인 선형성 손실이 추가되어 단일 학습된 행렬 KK를 통해 잠재 역학을 강제합니다.

x_k  -->  [Encoder φ]  -->  z_k  -->  [Linear K]  -->  z_{k+1}  -->  [Decoder ψ]  -->  x̂_{k+1}
                              |                            |
                              +--- Linearity loss: ‖z_{k+1} - K z_k‖ ---+

L=xk+1ψ(Kφ(xk))2prediction+αφ(xk+1)Kφ(xk)2linearity+βxkψ(φ(xk))2reconstruction\mathcal{L} = \underbrace{\lVert x_{k+1} - \psi(K \varphi(x_k)) \rVert^2}_{\text{prediction}} + \alpha \underbrace{\lVert \varphi(x_{k+1}) - K \varphi(x_k) \rVert^2}_{\text{linearity}} + \beta \underbrace{\lVert x_k - \psi(\varphi(x_k)) \rVert^2}_{\text{reconstruction}}

α\alpha 용어가 없으면 잠재 공간 뒤에 행렬 곱셈이 따라오는 일반 자동 인코더가 있습니다. 이를 통해 네트워크는 진화가 선형이 아닌 모든 잠재 표현에 대해 불이익을 받습니다. 이는 학습된 KK를 Koopman 근사치로 만들고 해당 고유값을 섹션 4의 DMD 스펙트럼과 비교할 수 있게 만듭니다.

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는 시퀀스 모델에 도달하는 것보다 이것을 훈련할 가치가 있게 만드는 것입니다. 학습된 연산자는 여전히 행렬이므로 섹션 4.2 안정성 테스트와 섹션 4.3 리드-지연 테스트는 심층 모델에 변경 없이 적용됩니다.

7. 실제 고려 사항 및 함정

모델 드리프트 및 희소 관측 탐색

순위 선택. 잘림 순위 rr는 편향 분산 다이얼입니다. 너무 낮으면 역학이 누락되고, 너무 높으면 노이즈에 적합합니다. 단일 값 팔꿈치를 눈으로 보지 마십시오. 블로그는 이미 벡터 및 행렬을 사용한 복잡한 차익 거래]의 Random Matrix Theory에서 바인딩된 Marchenko-Pastur를 사용하여 "노이즈를 맞추기 전의 구성 요소 수"에 대해 올바르게 대답합니다. 창 모양에 대한 특이값이 Marchenko-Pastur 가장자리를 초과하는 구성 요소를 유지하고 게시하는 모든 스펙트럼 옆에 결과 rr를 명시적으로 명시합니다.

창 길이. Koopman 이론은 고정된 FF를 가정합니다. 시장은 하나를 공급하지 않습니다. 롤링 수리는 필수이며 섹션 4.2의 안정성 곡선은 선택한 창이 추정할 수 있을 만큼 길고 한 체제 내에 머물 수 있을 만큼 짧은지 여부를 정확하게 진단합니다.

노이즈 민감도. 금융 데이터는 신호 대 노이즈 비율이 낮으며 표준 DMD는 XX에서 노이즈에 의해 편향됩니다. 모드가 불안정하다고 결론을 내리기 전에 시도해 볼 만한 해결 방법은 다음과 같습니다.

  • 총 DMD(TDMD) — 총 최소 제곱을 통해 XXXX'를 모두 노이즈로 처리합니다.
  • 최적화된 DMD — 잔차 Frobenius 표준에 대해 고유값 모드 분해를 직접 최적화합니다.
  • 커널 EDMD — 사전을 구축하지 않고 고차원 기능 공간에서 암시적으로 작동합니다.

TDMD에서 모드 안정성이 실질적으로 증가하면 불안정성은 측정 노이즈 때문입니다. 그렇지 않다면 그것은 시장이었습니다.

8. DMD가 있는 곳

모델링 접근 방식 중 동적 모드 분해

방법 선형성 해석 가능 다단계 예측
DMD 상태 공간에서 선형 예(모드 + 고유값) 안정(매트릭스 전력)
EDMD 들어 올려진 공간에서 선형 예, 사전이 주어지면 안정(매트릭스 전력)
딥 쿠프만 학습된 공간의 선형 보통(잠재 K 검사) 안정(매트릭스 전력)

변동성별 예측의 경우 비교 지점은 GARCH 제품군입니다. crypto에 대한 GARCH 변동성 예측을 참조하세요. 완전 비선형 시퀀스 모델과 해석 가능성 및 오류 누적 트레이드오프에 대해서는 trading의 Temporal Fusion Transformer]를 참조하세요.

DMD가 차지하는 틈새 시장은 좁지만 현실적입니다. 즉, 자동 회귀 롤아웃이 아닌 단일 매트릭스 전력으로 생성된 다단계 예측이며 모든 모드를 검사할 수 있습니다. 틈새 시장에 알파가 포함되어 있는지 여부는 이 테이블의 문제가 아니라 섹션 4의 문제입니다.

결론

안정적인 모드로 해석되는 난류 궤적

Koopman 이론은 시장 역학을 보는 진정으로 우아한 방법이며, 우아함은 바로 가장 가혹한 테스트가 필요한 이유입니다. 시사점:

  1. DMD를 맞춘 후 즉시 적합성을 테스트합니다. 위상 무작위 널에 대해 측정된 인접한 창 사이의 부분 공간 중첩은 구조를 찾았는지 또는 창을 기억했는지를 오후 내에 알려줍니다.
  2. **롤링 스펙트럼 반경은 모니터링할 가치가 있는 단일 스칼라이며, 그 값은 전적으로 리드-지연 결과에 따라 달라집니다. 지연 0에서는 변동성 프록시입니다. 마이너스 시차에서는 정권 경고입니다.
  3. 마지막 스냅샷에 진폭을 고정하고 고유값을 창 길이로 올리지 마세요. 실제 DMD "신호"의 상당 부분은 부동 소수점 인공물입니다.
  4. 팔꿈치를 쳐다보지 않고 Marchenko-Pastur로 순위를 선택하고 모든 스펙트럼에 순위를 게시합니다.
  5. 스펙트럼 플롯은 결과가 아닙니다. 이를 기반으로 구축된 모든 전략은 계산되기 전에 수수료, 슬리피지 및 하락한 샤프 테스트에서 살아남아야 합니다.

이를 넘어서는 구현을 위해 PyDMD 라이브러리는 DMD 변형을 포괄적으로 다루고 Mallen et al.의 참조 코드는 깊은 Koopman 측면을 다룹니다.

시장은 혼란스럽고 고정적이지 않으며 부분적으로 관찰될 것입니다. Koopman 이론은 다음 주에도 구조가 여전히 존재하는지 확인한다면 혼란스러운 구조에서 구조를 추출하기 위한 원칙적인 렌즈를 제공합니다.


참고자료 및 추가 자료:

  • B. O. Koopman, "힐베르트 공간의 해밀턴 시스템 및 변환", 국립과학원 회보, 1931.
  • J. H. Tu 외, "동적 모드 분해: 이론 및 응용", Journal of Computational Dynamics, 2014.
  • M. O. Williams, I. G. Kevrekidis, C. W. Rowley, "Koopman 연산자의 데이터 기반 근사: 동적 모드 분해 확장", Journal of Nonlinear Science, 2015.
  • B. Lusch, J. N. Kutz, S. L. Brunton, "비선형 역학의 범용 선형 임베딩을 위한 딥 러닝", Nature Communications, 2018.
  • J. Mann 및 J. N. Kutz, "금융 거래 전략을 위한 동적 모드 분해", 정량 금융, 2016.
  • A. Mallen 외, "시간적 분포 변화가 있는 시계열에 대한 Koopman 신경 예측기", ICML, 2023.
  • E. Gonzalez 및 M. Generelo, "Koopman 연산자, EDMD, Takens의 정리 및 기계 학습을 통한 혼란스러운 경제 모델 분석", 금융 및 경제의 데이터 과학, 2022.
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

시장에서 앞서 나가세요

뉴스레터를 구독하여 독점적인 AI 트레이딩 통찰력, 시장 분석 및 플랫폼 업데이트를 받아보세요.

귀하의 개인정보를 존중합니다. 언제든지 구독을 취소할 수 있습니다.