← Zurück zu den Artikeln
August 16, 2026
5 min read

Neural ODEs: Does Continuous Time Beat a Delta-Time Feature?

Neural ODEs: Does Continuous Time Beat a Delta-Time Feature?
#deep-learning
#neural-ODE
#continuous-time
#SDE
#dynamics

Neural Ordinary Differential Equations (Chen et al., NeurIPS 2018) parametrisieren die Ableitung eines verborgenen Zustands mit einem neuronalen Netzwerk und lassen einen ODE-Löser die Ausgabe berechnen. Das Modell wird tiefenkontinuierlich und, was für Märkte nützlicher ist, zeitkontinuierlich: Der verborgene Zustand kann jederzeit ausgewertet werden tt, nicht nur an den Gitterpunkten, auf denen Ihre Daten liegen.

Diese Eigenschaft hat eine offensichtliche Anziehungskraft auf Finanzdaten, die in unregelmäßigen Abständen eingehen. Aber der Pitch verbirgt eine viel schwächere Behauptung, als er zu machen scheint. Eine einfache GRU hat eine zusätzliche Eingabefunktion bereitgestellt – log(Δt)\log(\Delta t) seit der vorherigen Beobachtung – „weiß“ auch, wie viel Zeit vergangen ist. Die eigentliche Frage ist nicht, ob zeitkontinuierliche Modelle unregelmäßige Daten aufnehmen können. Es geht darum, ob erlernte kontinuierliche Dynamiken zwischen Beobachtungen etwas extrahieren, was Ihnen ein Delta-Zeit-Feature nicht bereits liefert, und zwar zum zehn- bis hundertfachen Inferenzaufwand.

In diesem Artikel wird dieses Experiment aufgebaut, die vollständige Toolchain für die Durchführung vorgestellt und berichtet, was der Vergleich ergeben müsste, um die Maschinerie zu rechtfertigen. Die Balkenkonstruktionsseite des Arguments – dass das Timing des Handels selbst ein Signal trägt – wird durch gemessene Beweise für 17 Balkentypen in beyond time bars belegt; Hier kommt es nur auf die Modellierung an: Der Löser wertet den verborgenen Zustand nach Belieben aus tt, daher ist keine Interpolation oder Auffüllung erforderlich.

Die Behauptung im Test

Drei verschachtelte Hypothesen für die zeitkontinuierliche Marktmodellierung

Drei verschachtelte Hypothesen, jede streng stärker als die andere:

  1. H1 – Eine ODE-RNN-Anpassung auf einem rohen unregelmäßigen BTC-Handelsstrom übertrifft eine GRU-Anpassung auf demselben Strom ohne jegliche Zeitinformationen. Das ist nahezu sicher und nahezu wertlos: Das ODE-RNN verfügt lediglich über Informationen, die der Basislinie fehlen.
  2. H2 – Das ODE-RNN schlägt dieselbe GRU, sobald die GRU gegeben ist log(Δt)\log(\Delta t) als Eingabefunktion. Das ist der ehrliche Test. Es ist die Behauptung, die in der Literatur zu neuronalen ODEs üblicherweise aufgestellt wird, und sie ist diejenige, die bei Finanzdaten fast nie nachgewiesen wird.
  3. H3 – Der H2-Vorteil überdauert das Latenzbudget, d. h. er bleibt bestehen, wenn das ODE-RNN mit einem Löser mit festem Schritt schnell genug für Live-Inferenz statt mit adaptiver Lösung bereitgestellt wird dopri5.

Experimentprotokoll

Gleicher Tick-Stream, gleiches Ziel, gleiches Tuning-Budget für beide Arme. Kein Resampling auf 1-m-Stäbe – Resampling zerstört genau die zu testende Struktur.

Einstellung
Daten BTC-Handelsabzüge, variable Zwischenankunftszeiten, kein erneutes Abtasten
Waffen ODE-RNN; GRU + log(Δt)\log(\Delta t); GRU ohne Zeitfunktion
Ziel Dasselbe gilt für alle Waffen; angegeben mit der Passform
Metriken RMSE, gehaltene Protokollwahrscheinlichkeit, Wanduhr pro Epoche, Inferenzlatenz pro Schritt
Solver-Sweep dopri5 (rtol 1e-5) vs. Euler mit festem Schritt, abgestimmt auf Trainingsgenauigkeit
Geteilt Walk-forward, nur außerhalb der Stichprobe
Arm RMSE Log-lik s/epoche Inferenzlatenz/Schritt
GRU (keine Zeitfunktion)
GRU + log(dt)
ODE-RNN (dopri5)
ODE-RNN (Euler mit festem Schritt)

Ein negatives Ergebnis auf H2 ist vollständig veröffentlichbar und wohl das wertvollere Ergebnis – derselbe Standard gilt für das ehrliche negative Ergebnis und deflationiertes Sharpe unter Mehrfachtests.

Sekundärexperiment: CNF vs. Student-t

Wenn der ODE-RNN-Vergleich zu aufwändig ist, ist der günstigere empirische Anker ein Verteilungsanker: Passen Sie einen kontinuierlichen Normalisierungsfluss an reale BTC-Tagesrenditen an und vergleichen Sie seine angepasste Dichte mit einer Student-t-Anpassung und mit dem empirischen Tail, wobei Tail-Quantilfehler auf den Ebenen 1 % und 5 % gemeldet werden. Das hängt direkt mit der Verteilungsarbeit zusammen, die bereits in GARCH-Volatilitätsprognose und asymmetric GARCH gemessen wurde.


Der Rest dieses Artikels befasst sich mit dem Hintergrund und der Toolchain, die zum Ausführen des oben Gesagten erforderlich ist.

Hintergrund: Von ResNets zu Continuous Dynamics

Restnetzwerk wird zu einem kontinuierlichen dynamischen Feld

Ein Restnetzwerk berechnet ht+1=ht+f(ht,θt)h_{t+1} = h_t + f(h_t, \theta_t). Wenn Sie die Schrittgröße verkleinern und die Anzahl der Ebenen erhöhen, nähern Sie sich einer kontinuierlichen Grenze:

dh(t)dt=f(h(t),t,θ)\frac{dh(t)}{dt} = f(h(t), t, \theta)

Anstatt TT diskrete Schichten mit separaten Parametern, ein einziges Netzwerk ff Gibt die momentane Änderungsrate an. Die Ausgabe zum jeweiligen Zeitpunkt TT löst ein Anfangswertproblem:

h(T)=h(0)+0Tf(h(t),t,θ)dth(T) = h(0) + \int_0^T f(h(t), t, \theta) \, dt

Ein Black-Box-Löser (Euler, Runge-Kutta, Dormand-Prince) berechnet das Integral numerisch und wählt dabei adaptiv Schrittgrößen aus lokalen Fehlerschätzungen aus.

Die Adjungierte-Methode

Bei der Rückwärtsausbreitung durch jeden Solver-Schritt werden alle Zwischenzustände gespeichert, was proportional zur Schrittanzahl Speicher kostet. Chen et al. Lösen Sie stattdessen eine Rückwärts-ODE mit O(1)O(1) Erinnerung. Definieren Sie den adjungierten Zustand a(t)=L/h(t)a(t) = -\partial L / \partial h(t), was erfüllt

da(t)dt=a(t)Tf(h(t),t,θ)h\frac{da(t)}{dt} = -a(t)^T \frac{\partial f(h(t), t, \theta)}{\partial h}

und akkumulieren Sie den Parametergradienten entlang des Rückwärtsdurchlaufs:

dLdθ=T0a(t)Tf(h(t),t,θ)θdt\frac{dL}{d\theta} = -\int_T^0 a(t)^T \frac{\partial f(h(t), t, \theta)}{\partial \theta} \, dt

Der Rückwärtsdurchlauf löst ein Augmented System Computing h(t)h(t), a(t)a(t) Und dL/dθdL/d\theta Gleichzeitig wird der Solver in umgekehrter Reihenfolge ausgeführt TT Zu 00.

Der Kompromiss: Rekonstruktion h(t)h(t) Rückwärts häufen sich numerische Fehler an, was bei steifer oder chaotischer Dynamik besonders schlimm ist. Checkpointing ist der Mittelweg – Laden h(t)h(t) Berechnen Sie zwischen ihnen in einigen Zwischenzeiten neu. Preisprozesse sind kontinuierliche Semimartingale und mäßig glatt, sodass der Adjungierte im Allgemeinen Bestand hat; Steifheit um Mikrostrukturereignisse kann adaptive Löser oder Hybride erzwingen.

ODE-RNN: Kontinuierlicher verborgener Zustand zwischen Beobachtungen

Versteckter Zustand, der sich zwischen unregelmäßigen Beobachtungen reibungslos entwickelt

Das ODE-RNN ist die Architektur, auf der das Hauptexperiment basiert. Zwischen den Beobachtungen entwickelt sich der verborgene Zustand unter der ODE:

h(t)=ODESolve(fθ,h(ti),ti,t)h(t) = \text{ODESolve}(f_\theta, h(t_i), t_i, t)

Bei der Beobachtung xi+1x_{i+1} kommt an ti+1t_{i+1}, ein diskretes Update wird ausgelöst:

h(ti+1+)=RNNCell(h(ti+1),xi+1)h(t_{i+1}^+) = \text{RNNCell}(h(t_{i+1}^-), x_{i+1})

Die hochgestellten Zeichen bezeichnen den Zustand unmittelbar vor und unmittelbar nach der Beobachtung. Zwischen den Beobachtungen erlernte kontinuierliche Dynamik; bei Beobachtungen, neue Informationen.

Der Mechanismus, den die H2-Ablation untersucht, ist folgender: Eine lange Lücke bedeutet, dass sich der verborgene Zustand weit unten entwickelt hat fθf_\theta – in Richtung einer Grundlinie abfallend oder divergierend – und die Form dieser Entwicklung wird gelernt und nicht als Skalar bereitgestellt. Ob sich diese Aussagekraft anhand realer Handelsdaten auszahlt, ist ungewiss.

Natürliche Anpassungen jenseits von Ticks: Multi-Asset-Portfolios, bei denen jeder Vermögenswert seinen eigenen Beobachtungsplan hat und sich der latente Zustand korrelierter Vermögenswerte ständig weiterentwickelt, während nur einer beobachtet wird; und ereignisgesteuerte Signale (Nachrichten, Gewinne, Makromitteilungen), die zu unregelmäßigen Zeiten eintreffen.

Neuronale SDEs: Hinzufügen stochastischer Komponenten

Kontinuierliche Dynamik mit kontrollierter stochastischer Diffusion

Neuronale ODEs sind deterministisch und können das Rauschen in einem Preispfad nicht darstellen. Die klassische Behandlung – geometrische Brownsche Bewegung, risikoneutrale Drift und die Art und Weise, wie Annahmen einer konstanten Volatilität gegen das Lächeln versagen – wird in Black-Scholes-Optionspreise behandelt. Neuronale SDEs ersetzen die parametrische Form durch einen erlernten Diffusionsterm:

dh(t)=f(h(t),t,θ)dt+g(h(t),t,ϕ)dW(t)dh(t) = f(h(t), t, \theta) \, dt + g(h(t), t, \phi) \, dW(t)

ff ist die Drift, gg die Verbreitung, W(t)W(t) ein Wiener-Prozess, beides ff Und gg neuronale Netze.

Architektur: Treibnetz und Diffusionsnetz

  • Treibnetz fθf_\theta: erwartete Flugbahn – Trend, mittlere Umkehrung, Dynamik. Geschult, um Vorhersagefehler zu minimieren.
  • Diffusionsnetz gϕg_\phi: Rauschstärke, gelernt als Funktion des Zustands. Hoch in volatilen Regimen, niedrig in ruhigen.

Die Aufteilung spiegelt das klassische Quant Finance wider: Drift ist die risikoneutrale (oder P-Maß-)Dynamik, Diffusion ist die Volatilitätsoberfläche.

Training neuronaler SDEs

Das stochastische Integral gdW\int g\,dW ist nicht klassisch differenzierbar. Drei Ansätze:

  1. Pfadmäßige Gradienten: Reparametrisierungstrick – probieren Sie Brownsche Pfade aus, differenzieren Sie durch den Löser, der Rauschen als feste Eingabe behandelt.
  2. Score-Matching: Schätzung hlogp(h)\nabla_h \log p(h) und über Rauschunterdrückungsziele trainieren – dieselbe Maschinerie, die als eigenständiges generatives Modell in Diffusionsmodellen für die Kryptovorhersage verwendet wird, hier zu einer Trainingsoption für die SDE herabgestuft.
  3. Endlichdimensionaler Verteilungsabgleich: Passen Sie Randwerte zu Beobachtungszeitpunkten an, anstatt vollständige Pfadmaße.

Pathwise ist die praktische Standardeinstellung. torchsde implementiert Euler-Maruyama, Milstein und stochastisches Runge-Kutta mit Autodiff-Unterstützung.

Lernen der Volatilitätsoberfläche

Klassische Modelle (Heston, SABR) legen parametrische Formen für den Diffusionskoeffizienten fest. Eine neuronale SDE lernt gϕ(S,t)g_\phi(S, t) als beliebige Funktion:

dS(t)=μθ(S(t),t)dt+σϕ(S(t),t)S(t)dW(t)dS(t) = \mu_\theta(S(t), t) \, dt + \sigma_\phi(S(t), t) \, S(t) \, dW(t)

Dies ist ein universeller Näherungswert für Diffusionsprozesse – jeden Ito-Prozess mit beliebiger Genauigkeit bei ausreichender Kapazität.

Latente ODEs für fehlende und asynchrone Daten

Latenter kontinuierlicher Zustand, der fehlende asynchrone Daten rekonstruiert

Die latente ODE (Rubanova, Chen, Duvenaud, 2019) kombiniert eine neuronale ODE mit einer VAE: Finanzreihen sind verrauschte Beobachtungen eines reibungslosen zugrunde liegenden Prozesses, gelernt in einem niedrigdimensionalen latenten Raum.

  1. Erkennungsnetzwerk: ein ODE-RNN, das rückwärts durch Beobachtungen läuft, um zu produzieren q(z0x1:N)q(z_0 | x_{1:N}).
  2. Latente Dynamik: z(t)=z(t0)+t0tfθ(z(s),s)dsz(t) = z(t_0) + \int_{t_0}^{t} f_\theta(z(s), s)\, ds.
  3. Decoder: x^(t)=Decoder(z(t))\hat{x}(t) = \text{Decoder}(z(t)), jederzeit auswertbar.

Der Verlust ist der Standard-ELBO:

L=Eq(z0)[i=1Nlogp(xiz(ti))]KL(q(z0x1:N)p(z0))\mathcal{L} = \mathbb{E}_{q(z_0)} \left[ \sum_{i=1}^{N} \log p(x_i | z(t_i)) \right] - \text{KL}(q(z_0 | x_{1:N}) \| p(z_0))

Schätzung des Portfoliozustands: Aus spärlichen asynchronen Beobachtungen über Assets hinweg einen kontinuierlichen latenten Zustand ableiten, der die gemeinsame Dynamik erfasst – im Wesentlichen eine nichtlineare, erlernte Version des Kalman-Filters.

Fehlende Datenimputation: Unterbrechungen und Wochenendlücken erhalten eine reibungslos interpolierte latente Flugbahn; Der Decoder erzeugt plausible Pfade durch die Lücke mit Unsicherheit aus dem VAE-Posterior.

Mehrfrequenzfusion: Tagesabschlüsse, Intraday-VWAP und Tick-Daten in einem Modell ohne gemeinsames Zeitraster.

Kontinuierliche Normalisierungsflüsse für Rückgabeverteilungen

Kontinuierlicher Fluss, der eine stark ausgeprägte Renditeverteilung formt

CNFs verwenden eine neuronale ODE, um eine einfache Basisverteilung in ein komplexes Ziel umzuwandeln. Die Transformation ist dz(t)dt=f(z(t),t,θ)\frac{dz(t)}{dt} = f(z(t), t, \theta), und die Log-Dichte folgt der momentanen Änderung der Variablen:

logp(z(t))t=tr(fz(t))\frac{\partial \log p(z(t))}{\partial t} = -\text{tr}\left(\frac{\partial f}{\partial z(t)}\right)

Integrieren Sie Status und Protokolldichte vorwärts von z(0)p0z(0) \sim p_0 zu bekommen z(T)z(T) Und logp(z(T))\log p(z(T)). Die Jacobi-Spur wird aus Gründen der Skalierbarkeit mit dem stochastischen Schätzer von Hutchinson geschätzt.

Kryptorenditen sind leptokurtisch und negativ verzerrt – gemessen an realen Daten in GARCH-Volatilitätsprognose und asymmetrisches GARCH und der Leverage-Effekt – und ein CNF lernt diese Form, anstatt sie anzunehmen. Durch die Konditionierung des Flusses anhand von Zustandsvariablen kann sich die Form je nach Regime ändern. Da die Dichte exakt und nicht ungefähr ist, kann sie die in Monte-Carlo- und Bootstrap-Backtests berechneten Tail-Metriken und die CVaR-Konstruktion in der HRP/CVaR-Portfolio-Pipeline speisen. Die Struktur des Gelenkschwanzes wird in [Copula-Modellen für Gelenkrisiken] (/en/blog/post/copula-models-joint-risk-crypto) separat behandelt.

Bedingte Dichteschätzung

Der nützliche Rahmen ist ein direkter Kontrast zwischen drei veröffentlichten Ansätzen für dasselbe Problem. TFT bietet Ihnen einen festen Satz von Quantilen aus einer Quantil-Ausgabeschicht. Konforme Vorhersage bietet Ihnen kalibrierte Intervalle mit einer Abdeckungsgarantie. Eine bedingte CNF liefert Ihnen eine exakte, differenzierbare Dichte:

dz(t)dt=f(z(t),t,featurest,θ)\frac{dz(t)}{dt} = f(z(t), t, \text{features}_t, \theta)

Differenzierbarkeit ist die Unterscheidungseigenschaft: Die Dichte kann innerhalb eines nachgeschalteten Ziels liegen und durch dieses zurückpropagiert werden, was weder feste Quantile noch konforme Intervalle unterstützen. Ob sich die exakte Dichte im Vergleich zu TFT-Quantilen auf demselben Ziel lohnt, ist hier ungetestet.

Vergleich mit diskreten Alternativen

Diskrete und kontinuierliche Modelle im Vergleich anhand gemeinsamer Beobachtungen

Eigentum LSTM/GRU Transformator Neuronale ODE Neuronale SDE
Unregelmäßige Zeitabwicklung Schlecht (muss gepolstert werden) Positionskodierung Einheimisch Einheimisch
Gedächtnis (Training) O(T)O(T) O(T2)O(T^2) O(1)O(1) adjunkt O(1)O(1) adjunkt
Unsicherheitsquantifizierung Nein (deterministisch) Nein (deterministisch) Über Ensembles Einheimisch
Interpolation zwischen Beobachtungen Nein Nein Ja Ja
Zeitkontinuierliche Dichte Nein Nein Über CNF Über Wegmaß
Rechenaufwand Niedrig Mäßig Variable (Löser) Hoch (SDE-Löser)

Die LSTM-versus-Aufmerksamkeitshälfte dieser Tabelle wird im TFT-Artikel ausführlich und mit Benchmarks erläutert – siehe die Abschnitte „TFT vs. LSTM vs. Vanilla Transformer“ und „Wenn LSTM immer noch gewinnt“. Die Spalten, die hier neu sind, sind die beiden rechten, und die Zeilen, die über die H2/H3-Fragen entscheiden, sind die letzten beiden.

Python-Implementierung mit Torchdiffeq

Neuronaler Differentialgleichungslöser als präziser Rechenorbit

torchdiffeq bietet ODE-Lösern adjungierte Backpropagation.

Grundlegende neuronale ODE für Preisdynamik

import torch
import torch.nn as nn
from torchdiffeq import odeint_adjoint as odeint

class PriceDynamics(nn.Module):
    """Neural network defining dh/dt = f(h, t)."""

    def __init__(self, hidden_dim: int = 64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(hidden_dim, 128),
            nn.Tanh(),
            nn.Linear(128, 128),
            nn.Tanh(),
            nn.Linear(128, hidden_dim),
        )

    def forward(self, t, h):
        return self.net(h)


class NeuralODEPredictor(nn.Module):
    """
    Encode observed features -> latent state,
    evolve via Neural ODE,
    decode to price prediction.
    """

    def __init__(self, input_dim: int, hidden_dim: int = 64):
        super().__init__()
        self.encoder = nn.Linear(input_dim, hidden_dim)
        self.dynamics = PriceDynamics(hidden_dim)
        self.decoder = nn.Linear(hidden_dim, 1)

    def forward(self, x0, eval_times):
        """
        x0:         (batch, input_dim) features at t=0
        eval_times: (T,) times at which to evaluate the ODE
        Returns:    (T, batch, 1) predictions
        """
        h0 = self.encoder(x0)                          # (batch, hidden_dim)
        h_traj = odeint(self.dynamics, h0, eval_times,
                        method='dopri5', rtol=1e-5, atol=1e-7)
        return self.decoder(h_traj)

ODE-RNN für unregelmäßige Tick-Daten

Dies ist der experimentelle Arm. Die Grundlinie, die es schlagen muss, ist nn.GRUCell im selben Stream mit log(t_next - t_prev) verkettet zu x.

class ODERNNCell(nn.Module):
    """Single step: ODE-evolve, then RNN-update."""

    def __init__(self, input_dim: int, hidden_dim: int = 64):
        super().__init__()
        self.dynamics = PriceDynamics(hidden_dim)
        self.gru_cell = nn.GRUCell(input_dim, hidden_dim)

    def forward(self, h, x, t_prev, t_next):
        times = torch.tensor([t_prev, t_next], dtype=torch.float32)
        h_evolved = odeint(self.dynamics, h, times,
                           method='dopri5')[-1]  # state at t_next
        h_updated = self.gru_cell(x, h_evolved)
        return h_updated


class ODERNN(nn.Module):
    """Process irregularly-sampled sequence."""

    def __init__(self, input_dim: int, hidden_dim: int = 64):
        super().__init__()
        self.cell = ODERNNCell(input_dim, hidden_dim)
        self.decoder = nn.Linear(hidden_dim, 1)
        self.hidden_dim = hidden_dim

    def forward(self, observations, times):
        """
        observations: list of (batch, input_dim) tensors
        times:        list of floats, observation timestamps
        """
        batch_size = observations[0].shape[0]
        h = torch.zeros(batch_size, self.hidden_dim)

        outputs = []
        for i in range(len(observations)):
            t_prev = 0.0 if i == 0 else times[i - 1]
            h = self.cell(h, observations[i], t_prev, times[i])
            outputs.append(self.decoder(h))

        return torch.stack(outputs)  # (seq_len, batch, 1)

Trainingsschleife

def train_neural_ode(model, train_loader, epochs=100, lr=1e-3):
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
        optimizer, T_max=epochs
    )

    for epoch in range(epochs):
        epoch_loss = 0.0
        for batch in train_loader:
            features, times, targets = batch
            optimizer.zero_grad()

            predictions = model(features, times)
            loss = torch.nn.functional.mse_loss(predictions, targets)

            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
            optimizer.step()

            epoch_loss += loss.item()

        scheduler.step()

        if (epoch + 1) % 10 == 0:
            avg_loss = epoch_loss / len(train_loader)
            print(f"Epoch {epoch+1}/{epochs}, Loss: {avg_loss:.6f}")

Kontinuierlicher Normalisierungsfluss für Rückgabeverteilungen

from torchdiffeq import odeint

class CNFDynamics(nn.Module):
    """Dynamics for continuous normalizing flow."""

    def __init__(self, dim: int = 1, hidden_dim: int = 64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(dim + 1, hidden_dim),  # +1 for time
            nn.Tanh(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, dim),
        )
        self.dim = dim

    def forward(self, t, state):
        z = state[..., :self.dim]
        t_expand = t.expand(z.shape[0], 1)
        zt = torch.cat([z, t_expand], dim=-1)
        dz = self.net(zt)

        e = torch.randn_like(z)
        e_dz = torch.autograd.grad(
            dz, z, e, create_graph=True
        )[0]
        trace_jac = (e_dz * e).sum(dim=-1, keepdim=True)

        return torch.cat([dz, -trace_jac], dim=-1)


class ReturnDistributionCNF(nn.Module):
    """Model return distributions with continuous normalizing flows."""

    def __init__(self, dim: int = 1):
        super().__init__()
        self.dynamics = CNFDynamics(dim)
        self.dim = dim

    def log_prob(self, x):
        """Compute log probability of observed returns."""
        log_p0 = torch.zeros(x.shape[0], 1)
        state0 = torch.cat([x, log_p0], dim=-1)
        state0.requires_grad_(True)

        times = torch.tensor([1.0, 0.0])  # backward
        state_T = odeint(self.dynamics, state0, times,
                         method='dopri5')[-1]

        z_T = state_T[..., :self.dim]
        delta_log_p = state_T[..., self.dim:]

        log_p_base = -0.5 * (z_T ** 2 + torch.log(
            torch.tensor(2 * torch.pi)
        )).sum(dim=-1, keepdim=True)

        return log_p_base + delta_log_p

    def sample(self, n_samples: int):
        """Generate samples from learned distribution."""
        z0 = torch.randn(n_samples, self.dim)
        log_p0 = torch.zeros(n_samples, 1)
        state0 = torch.cat([z0, log_p0], dim=-1)

        times = torch.tensor([0.0, 1.0])  # forward
        state_T = odeint(self.dynamics, state0, times,
                         method='dopri5')[-1]

        return state_T[..., :self.dim]

Praktische Hinweise

Ausgewogener Kompromiss zwischen numerischer Genauigkeit und Inferenzgeschwindigkeit

Dies sind die Dinge, die Ihnen bei der Durchführung des obigen Experiments auffallen werden.

Lösungsauswahl und Geschwindigkeit

dopri5 gibt Genauigkeitsgarantien, aber variable Rechenkosten, weshalb H3 eine von H2 getrennte Hypothese ist: Eine Kante, die nur unter einem adaptiven Löser existiert, überlebt möglicherweise ein Live-Latenzbudget nicht. Löser mit festem Schritt (Euler, RK4) bieten eine vorhersehbare Latenz auf Kosten der Genauigkeit. Praktischer Kompromiss: Trainieren Sie mit dopri5, mit einem Löser mit fester Schrittweite einsetzen, der so kalibriert ist, dass er mit der Ausgabe des adaptiven Lösers für repräsentative Daten übereinstimmt – und die Lücke messen, anstatt davon auszugehen, dass sie klein ist.

odeint(f, h0, t, method='dopri5', rtol=1e-6, atol=1e-8)

odeint(f, h0, t, method='euler', options={'step_size': 0.1})

Für steife Probleme in der Nähe diskontinuierlicher Sprünge, method='implicit_adams' oder method='scipy_solver'.

Numerische Stabilität

Wenn fθf_\theta Gibt große Werte aus, der Zustand divergiert. Abhilfemaßnahmen:

  • Spektrale Normalisierung auf Schichten von fθf_\theta um die Lipschitz-Konstante zu kontrollieren.
  • Verlaufsausschnitt während des Trainings (in der Trainingsschleife oben gezeigt).
  • Zeitnormalisierung: Zeitstempel skalieren auf [0,1][0, 1].
  • Regularisierung der Dynamiknorm: hinzufügen λfθ(h,t)2\lambda \|f_\theta(h, t)\|^2 zum Verlust.

Integrationsintervall

Bei Daten, die sich über mehrere Monate erstrecken, nicht integrieren t=0t = 0 Zu t=10,000t = 10{,}000 Minuten:

t_normalized = (timestamps - timestamps[0]) / (timestamps[-1] - timestamps[0])

Dies hält den Löser in einem numerisch freundlichen Regime und macht die gelernte Dynamik skaleninvariant. Für den Vergleich ist es auch wichtig: Ein nicht normalisiertes ODE-RNN kann aus rein numerischen Gründen gegenüber der GRU-Basislinie verlieren, was eher ein Messartefakt als ein Befund wäre.

Umgang mit mehreren Zeitskalen

Märkte haben gleichzeitig Dynamiken im Mikrosekunden-, Sekunden-, Minuten- und Tagesmaßstab, und ein einziges neuronales ODE könnte Schwierigkeiten haben, sie alle zu beherrschen. Optionen: Mehrere ODE-Blöcke in unterschiedlichen Zeitskalen stapeln; Erweitern Sie die verborgene Dimension sowohl für die schnelle als auch für die langsame Kapazität. oder führen Sie separate neuronale ODEs pro Frequenzband und Sicherungsausgängen aus. (Dies ist eine Frage zur Modellkapazität, die sich von der Backtest-Fidelity-Frage in Drilldown zur adaptiven Auflösung unterscheidet.)

Offene Forschungsanweisungen

Offene zeitkontinuierliche Forschungsverläufe, die von einem Modell ausgehen

Neuronale Sprung-SDEs: Fügen Sie eine erlernte Sprungkomponente für plötzliche Störungen (Flash-Abstürze, Gewinnüberraschungen) hinzu:

dh=fdt+gdW+Rγ(h,z)N~(dt,dz)dh = f \, dt + g \, dW + \int_{\mathbb{R}} \gamma(h, z) \, \tilde{N}(dt, dz)

Wo N~\tilde{N} ist ein kompensiertes Poisson-Zufallsmaß und γ\gamma ein gelernter Jump-Kernel.

Neuronale kontrollierte Differentialgleichungen (Neuronale CDEs): Ersetzen Sie den Wiener-Prozess durch ein allgemeines Antriebssignal und lassen Sie beobachtete Datenströme die Dynamik steuern. Natürlich für den Auftragsfluss, bei dem der Strom von Geschäften und Kursen die latente Marktlage bestimmt.

Differenzierbare Marktsimulation: Verwenden Sie eine neuronale SDE als generatives Modell in einem differenzierbaren Simulator und trainieren Sie Strategien Ende-zu-Ende, indem Sie sie über den Adjungierten zurückpropagieren. Strategie und Marktmodell entwickeln sich gemeinsam.

Physik-informierte neuronale ODEs: PINNs betten ein Differentialgleichungsresiduum in den Verlust ein – die Technik selbst wird im Navier-Stokes-Artikel vorgestellt. Die finanzielle Anwendung besteht darin, der erlernten Dynamik No-Arbitrage-, Put-Call-Paritäts- und Martingalbedingungen als Strafbedingungen oder harte Einschränkungen aufzuerlegen.

Fazit

Glatte kontinuierliche Dynamik, die sich in eine gemessene Schlussfolgerung auflöst

Der zeitkontinuierliche Rahmen ist strukturell korrekt: Märkte sind kontinuierliche Prozesse, die zu diskreten, unregelmäßigen Zeiten beobachtet werden, und Modelle sollten dies berücksichtigen. Die Toolchain ist ausgereift – torchdiffeq, torchsde, für den Rest einfach PyTorch – und die bekannten Kosten sind Lösergeschwindigkeit und numerische Stabilität über lange Integrationsintervalle.

Was nicht festgelegt ist, ist der Teil, der entscheidet, ob etwas davon in einen Produktionsstapel gehört. Strukturelle Korrektheit ist kein Beweis für einen Vorhersagevorteil, sondern für den spezifischen Vorteil, der für kontinuierliche Dynamik gegenüber a beansprucht wird log(Δt)\log(\Delta t) Die Funktion wurde hier nicht anhand realer Handelsdaten gemessen. Bis die H2-Tabelle oben ausgefüllt ist, betrachten Sie alles unterhalb der Falte als eine genau spezifizierte Hypothese mit einer angehängten funktionierenden Implementierung, nicht als Ergebnis.


Referenzen

  • Chen, R.T.Q., Rubanova, Y., Bettencourt, J., Duvenaud, D. (2018). Neuronale gewöhnliche Differentialgleichungen. NeurIPS 2018. arXiv:1806.07366
  • Rubanova, Y., Chen, R.T.Q., Duvenaud, D. (2019). Latente ODEs für unregelmäßig abgetastete Zeitreihen. NeurIPS 2019. arXiv:1907.03907
  • Jia, J., Benson, A.R. (2019). Neuronale Sprung-Stochastische Differentialgleichungen. NeurIPS 2019.
  • Kidger, P., Morrill, J., Foster, J., Lyons, T. (2020). Neuronal kontrollierte Differentialgleichungen für unregelmäßige Zeitreihen. NeurIPS 2020.
  • Hasan, A., Pereira, J.M., Farsiu, S., Carin, L. (2021). Stochastische Differentialgleichungsmodelle neuronaler Netze mit Anwendungen für die Finanzdatenprognose. arXiv:2111.13164
  • Torchdiffeq: github.com/rtqichen/torchdiffeq
  • UvA Deep Learning Tutorials – Neuronale ODEs: uvadlc-notebooks.readthedocs.io
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

Dem Markt einen Schritt voraus

Abonniere unseren Newsletter für exklusive KI-Trading-Einblicke, Marktanalysen und Plattform-Updates.

Wir respektieren deine Privatsphäre. Jederzeit abbestellbar.