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

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

신경 상미분방정식(Chen et al., NeurIPS 2018)은 신경망으로 hidden state의 derivative를 parameterize하고 ODE solver가 output을 계산하게 합니다. Model은 continuous-depth가 되며, 시장에는 더 유용하게 continuous-time이 됩니다. Hidden state를 data가 놓인 grid point뿐 아니라 임의의 tt에서 평가할 수 있습니다.

이 속성은 불규칙한 간격으로 도착하는 금융 data에 분명한 장점처럼 보입니다. 그러나 이 주장은 겉보기보다 훨씬 약한 내용을 숨깁니다. 일반 GRU에 이전 observation 이후 경과 시간인 log(Δt)\log(\Delta t)를 추가 input feature로 주어도 시간 경과를 "알 수" 있습니다. 진짜 질문은 continuous-time model이 irregular data를 입력할 수 있는지가 아닙니다. Observation 사이의 learned continuous dynamics가 delta-time feature만으로 이미 얻을 수 없는 무언가를 추출하는지, 그리고 그 대가가 10~100배의 inference cost를 감수할 만한지입니다.

이 글은 그 실험을 설정하고, 실행에 필요한 전체 toolchain을 제시하며, 이 machinery를 정당화하려면 비교가 무엇을 보여야 하는지 설명합니다. 거래 timing 자체가 signal을 가진다는 주장의 bar-construction 측면은 beyond time bars에서 17가지 bar type에 대한 측정 evidence로 확립되었습니다. 여기서 중요한 modeling 결과는 하나뿐입니다. Solver가 임의의 tt에서 hidden state를 평가하므로 interpolation이나 padding이 필요하지 않습니다.

검증할 주장

연속시간 시장 모델링을 위한 중첩된 세 가지 hypothesis

다음은 서로 중첩되는 세 가지 hypothesis이며, 뒤로 갈수록 더 강합니다:

  1. H1 — Raw irregular BTC trade stream에 fit한 ODE-RNN이 시간 정보가 전혀 없는 동일 stream의 GRU를 이깁니다. 거의 확실하지만 거의 의미가 없습니다. ODE-RNN이 baseline에 없는 정보를 가지고 있을 뿐입니다.
  2. H2 — GRU에 log(Δt)\log(\Delta t)를 input feature로 제공해도 ODE-RNN이 동일한 GRU를 이깁니다. 이것이 정직한 test입니다. Neural ODE literature가 보통 주장한다고 읽히는 내용이며, financial data에서는 거의 입증되지 않았습니다.
  3. H3 — H2의 advantage가 latency budget에서도 유지됩니다. 즉 adaptive dopri5가 아니라 live inference에 충분히 빠른 fixed-step solver로 ODE-RNN을 deploy했을 때도 성립해야 합니다.

실험 protocol

동일한 tick stream, 동일한 target, 양쪽에 동일한 tuning budget을 사용합니다. 1m bar로 resample하지 마십시오 — resampling은 검증 대상인 정확한 구조를 파괴합니다.

설정
Data BTC trade print, 가변 inter-arrival time, resample하지 않음
Arms ODE-RNN; GRU + log(Δt)\log(\Delta t); time feature 없는 GRU
Target 모든 arm에서 동일하며 fit과 함께 명시
Metrics RMSE, held-out log-likelihood, epoch별 wall-clock, step별 inference latency
Solver sweep dopri5 (rtol 1e-5) 대 fixed-step Euler, training accuracy를 맞춰 비교
Split Walk-forward, out-of-sample만 사용
Arm RMSE Log-lik s/epoch Step별 inference latency
GRU (time feature 없음)
GRU + log(dt)
ODE-RNN (dopri5)
ODE-RNN (fixed-step Euler)

H2에서 negative result가 나와도 충분히 publish할 수 있으며, 오히려 더 가치 있는 outcome일 수 있습니다 — 정직한 negative resultmultiple testing 아래의 deflated Sharpe에 적용된 것과 같은 기준입니다.

추가 실험: CNF 대 Student-t

ODE-RNN 비교가 너무 비싸다면 더 저렴한 empirical anchor는 distributional test입니다. Real BTC daily return에 continuous normalizing flow를 fit하고, fitted density를 Student-t fit 및 empirical tail과 비교하여 1%와 5% 수준의 tail-quantile error를 보고하십시오. 이는 GARCH volatility forecastingasymmetric GARCH에서 이미 측정한 distributional work와 직접 연결됩니다.


이 글의 나머지는 위 실험을 실행하는 데 필요한 background와 toolchain입니다.

배경: ResNet에서 continuous dynamics까지

Continuous dynamical field가 되는 residual network

Residual network는 ht+1=ht+f(ht,θt)h_{t+1} = h_t + f(h_t, \theta_t)를 계산합니다. Step size를 줄이고 layer 수를 늘리면 continuous limit에 가까워집니다:

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

별도 parameter를 가진 TT개의 discrete layer 대신 단일 network ff가 instantaneous rate of change를 정의합니다. Time TT에서의 output은 initial value problem의 해입니다:

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

Black-box solver(Euler, Runge-Kutta, Dormand-Prince)가 integral을 수치적으로 계산하고 local error estimate에 따라 step size를 adaptive하게 선택합니다.

Adjoint 방법

모든 solver step을 통과해 backpropagation하면 모든 intermediate state를 저장해야 하므로 memory가 step 수에 비례해 커집니다. Chen et al.은 대신 O(1)O(1) memory로 backward ODE를 풉니다. a(t)=L/h(t)a(t) = -\partial L / \partial h(t)인 adjoint state를 정의하면 다음을 만족합니다.

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}

그리고 backward pass를 따라 parameter gradient를 누적합니다:

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

Backward pass는 h(t)h(t), a(t)a(t), dL/dθdL/d\theta를 동시에 계산하는 augmented system을 풀며, TT에서 00까지 solver를 역방향으로 실행합니다.

Trade-off는 다음과 같습니다. h(t)h(t)를 backward로 재구성하면 numerical error가 누적되며, stiff하거나 chaotic한 dynamics에서는 특히 심합니다. Checkpointing은 중간 해법입니다 — 몇 개의 intermediate time에 h(t)h(t)를 저장하고 그 사이를 다시 계산합니다. Price process는 continuous semimartingale이고 적당히 smooth하므로 adjoint는 대체로 잘 작동하지만, microstructure event 주변의 stiffness는 adaptive solver나 hybrid를 요구할 수 있습니다.

ODE-RNN: Observation 사이의 continuous hidden state

불규칙한 observation 사이에서 부드럽게 진화하는 hidden state

ODE-RNN은 본 실험이 사용하는 architecture입니다. Observation 사이에서 hidden state는 ODE에 따라 진화합니다:

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

Observation xi+1x_{i+1}ti+1t_{i+1}에 도착하면 discrete update가 실행됩니다:

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

Superscript는 observation 직전과 직후의 state를 뜻합니다. Observation 사이에는 learned continuous dynamics가, observation 시점에는 새 정보가 있습니다.

H2 ablation이 검증하는 mechanism은 다음과 같습니다. Gap이 길다는 것은 hidden state가 fθf_\theta 아래에서 긴 거리를 진화했다는 뜻입니다 — baseline을 향해 decay하거나, 반대로 diverge할 수 있습니다. 그리고 그 evolution의 shape는 scalar로 주어지는 것이 아니라 learned입니다. 이 표현력이 real trade data에서 비용을 정당화하는지는 바로 아직 측정되지 않은 부분입니다.

Tick 외에도 자연스러운 적용처가 있습니다. 각 asset이 자체 observation schedule을 가지며 correlated asset의 latent state가 한 asset만 관측되는 동안에도 계속 진화하는 multi-asset portfolio, 그리고 news·earnings·macro release처럼 불규칙한 시각에 도착하는 event-driven signal입니다.

Neural SDE: 확률적 구성 요소 추가

제어된 stochastic diffusion을 가진 continuous dynamics

Neural ODE는 deterministic이므로 price path의 noise를 표현할 수 없습니다. Geometric Brownian motion, risk-neutral drift, constant-volatility 가정이 volatility smile 앞에서 실패하는 방식에 대한 고전적 설명은 Black-Scholes options pricing에 있습니다. Neural SDE는 parameterized form을 learned diffusion term으로 바꿉니다:

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는 drift, gg는 diffusion, W(t)W(t)는 Wiener process이며, ffgg는 neural network입니다.

Architecture: Drift net과 Diffusion net

  • Drift net fθf_\theta: expected trajectory — trend, mean reversion, momentum. Prediction error를 최소화하도록 training합니다.
  • Diffusion net gϕg_\phi: state의 함수로 학습되는 noise magnitude입니다. Volatile regime에서는 높고 calm regime에서는 낮습니다.

이 분리는 classical quant finance와 대응합니다. Drift는 risk-neutral(또는 P-measure) dynamics이고 diffusion은 volatility surface입니다.

Neural SDE 학습

Stochastic integral gdW\int g\,dW는 고전적 의미에서 differentiable하지 않습니다. 세 가지 접근이 있습니다:

  1. Pathwise gradients: reparameterization trick — Brownian path를 sample하고 noise를 fixed input으로 취급하는 solver를 통해 미분합니다.
  2. Score matching: hlogp(h)\nabla_h \log p(h)를 추정하고 denoising objective로 training합니다 — crypto prediction용 diffusion model에서 standalone generative model로 쓰인 동일한 machinery를 여기서는 SDE training option으로 사용합니다.
  3. Finite-dimensional distribution matching: full path measure 대신 observation time의 marginal을 맞춥니다.

실용적인 기본값은 pathwise 방식입니다. torchsde는 autodiff support와 함께 Euler-Maruyama, Milstein, stochastic Runge-Kutta를 구현합니다.

Volatility surface 학습

Classical model(Heston, SABR)은 diffusion coefficient에 parametric form을 부여합니다. Neural SDE는 gϕ(S,t)g_\phi(S, t)를 arbitrary function으로 학습합니다:

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)

이는 diffusion process를 위한 universal approximator입니다 — 충분한 capacity가 있으면 어떤 Ito process도 임의의 정확도로 근사할 수 있습니다.

결측 및 asynchronous data를 위한 Latent ODE

결측 비동기 data를 복원하는 continuous latent state

Latent ODE(Rubanova, Chen, Duvenaud, 2019)는 Neural ODE와 VAE를 결합합니다. Financial series는 low-dimensional latent space에서 학습되는 smooth underlying process의 noisy observation입니다.

  1. Recognition network: observation을 거꾸로 통과하는 ODE-RNN으로 q(z0x1:N)q(z_0 | x_{1:N})를 생성합니다.
  2. Latent dynamics: 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))이며 요청한 어느 time에서도 평가할 수 있습니다.

Loss는 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))

Portfolio state estimation: asset 전반의 sparse asynchronous observation으로부터 joint dynamics를 담는 continuous latent state를 추론합니다 — 본질적으로 Kalman filter의 nonlinear learned version입니다.

Missing data imputation: trading halt와 weekend gap에서도 smooth하게 interpolation된 latent trajectory를 얻고, decoder가 VAE posterior의 uncertainty와 함께 gap을 통과하는 plausible path를 생성합니다.

Multi-frequency fusion: shared time grid 없이 daily close, intraday VWAP, tick data를 하나의 model에서 처리합니다.

Return distribution을 위한 Continuous Normalizing Flow

Heavy-tailed return distribution을 형성하는 continuous flow

CNF는 Neural ODE를 사용해 simple base distribution을 complex target으로 변환합니다. Transformation은 dz(t)dt=f(z(t),t,θ)\frac{dz(t)}{dt} = f(z(t), t, \theta)이고 log-density는 instantaneous change of variables를 따릅니다:

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)

State와 log-density를 z(0)p0z(0) \sim p_0에서 forward로 적분해 z(T)z(T)logp(z(T))\log p(z(T))를 얻습니다. Jacobian trace는 scalability를 위해 Hutchinson stochastic estimator로 추정합니다.

Crypto return은 leptokurtic하고 negative skew를 가집니다 — GARCH volatility forecastingasymmetric GARCH and the leverage effect에서 real data로 측정되었습니다 — CNF는 이를 가정하지 않고 그 shape를 학습합니다. Flow를 state variable에 condition하면 regime에 따라 shape가 바뀝니다. Density가 approximate가 아니라 exact이므로 Monte Carlo 및 bootstrap backtest의 tail metric과 HRP/CVaR portfolio pipeline의 CVaR 구성에 연결할 수 있습니다. Joint tail structure는 joint risk용 copula model에서 별도로 처리합니다.

조건부 density estimation

유용한 framing은 같은 문제에 대한 세 가지 published approach를 직접 대비하는 것입니다. TFT는 quantile output layer에서 fixed quantile set을 제공합니다. Conformal prediction은 coverage guarantee가 있는 calibrated interval을 제공합니다. Conditional CNF는 exact하고 differentiable한 density를 제공합니다:

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

Differentiability가 구별되는 속성입니다. Density를 downstream objective 안에 넣고 backpropagation할 수 있지만 fixed quantile과 conformal interval은 이를 지원하지 않습니다. 같은 target에서 exact density가 TFT quantile에 비해 비용을 정당화하는지는 여기서 검증하지 않았습니다.

이산형 대안과의 비교

공유 observation에서 비교한 discrete 및 continuous model

항목 LSTM/GRU Transformer Neural ODE Neural SDE
불규칙 시간 처리 낮음(padding 필요) Positional encoding Native Native
Memory (training) O(T)O(T) O(T2)O(T^2) O(1)O(1) adjoint O(1)O(1) adjoint
Uncertainty quantification 없음(deterministic) 없음(deterministic) Ensemble을 통해 Native
Observation 사이 interpolation 아니오 아니오
Continuous-time density 아니오 아니오 CNF를 통해 Path measure를 통해
Computational cost 낮음 보통 가변(solver) 높음(SDE solver)

Бұл table의 LSTM 대 attention 부분은 benchmark와 함께 TFT article에서 자세히 다룹니다 — "TFT vs LSTM vs Vanilla Transformer"와 "When LSTM Still Wins" section을 보십시오. 여기서 새로 추가된 column은 오른쪽 두 개이며, H2/H3 질문을 결정하는 row는 마지막 두 개입니다.

torchdiffeq를 사용한 Python 구현

정밀한 computational orbit로 표현한 neural differential-equation solver

torchdiffeq는 adjoint backpropagation이 포함된 ODE solver를 제공합니다.

Price dynamics를 위한 기본 Neural ODE

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)

불규칙한 tick data를 위한 ODE-RNN

이것이 실험 arm입니다. 이를 이겨야 하는 baseline은 동일 stream에서 log(t_next - t_prev)x에 concatenate한 nn.GRUCell입니다.

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)

Training loop

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

Return distribution을 위한 Continuous Normalizing Flow

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]

실용적인 참고사항

Numerical accuracy와 inference speed 사이의 균형 잡힌 trade-off

위 실험을 실행하는 동안 실제로 문제를 일으킬 항목들입니다.

Solver 선택과 속도

dopri5는 accuracy guarantee를 제공하지만 compute cost가 variable합니다. 그래서 H3는 H2와 별개의 hypothesis입니다. Adaptive solver에서만 존재하는 edge는 live latency budget에서 살아남지 못할 수 있습니다. Fixed-step solver(Euler, RK4)는 accuracy를 희생하는 대신 predictable latency를 제공합니다. 실용적인 절충안은 dopri5로 training하고, representative data에서 adaptive solver의 output과 맞도록 보정한 fixed-step solver로 deploy하는 것입니다 — gap이 작다고 가정하지 말고 직접 측정하십시오.

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

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

Stiff problem에서 discontinuous jump 근처라면 method='implicit_adams' 또는 method='scipy_solver'를 사용하십시오.

수치적 안정성

fθf_\theta가 큰 값을 output하면 state가 diverge합니다. 완화책:

  • Spectral normalization: fθf_\theta의 layer에 적용해 Lipschitz constant를 제어합니다.
  • Gradient clipping: training 중 적용합니다(위 training loop에 나와 있습니다).
  • Time normalization: timestamp를 [0,1][0, 1]로 scale합니다.
  • Dynamics norm regularization: loss에 λfθ(h,t)2\lambda \|f_\theta(h, t)\|^2를 추가합니다.

적분 구간

수개월에 걸친 data라면 t=0t = 0에서 t=10,000t = 10{,}000분까지 적분하지 마십시오:

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

이렇게 하면 solver가 수치적으로 안정적인 regime에서 동작하고 learned dynamics가 scale-invariant해집니다. 비교에서도 중요합니다. Unnormalized ODE-RNN은 순전히 numerical reason으로 GRU baseline에 질 수 있으며, 이는 finding이 아니라 measurement artifact입니다.

Multiple time scale 처리

Market은 microsecond, second, minute, daily scale의 dynamics를 동시에 가지므로 단일 Neural ODE가 모두를 유지하기 어려울 수 있습니다. 선택지는 서로 다른 time scale에 여러 ODE block을 stack하거나, fast/slow capacity를 위해 hidden dimension을 늘리거나, frequency band별로 별도 Neural ODE를 실행해 output을 fuse하는 것입니다. (이는 adaptive resolution drill-down의 backtest fidelity 문제와 다른 model-capacity 문제입니다.)

열린 연구 방향

하나의 model에서 뻗어 나오는 open continuous-time research trajectory

Neural Jump SDEs: 갑작스러운 dislocation(flash crash, earnings surprise)을 위한 learned jump component를 추가합니다:

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

여기서 N~\tilde{N}은 compensated Poisson random measure이고 γ\gamma는 learned jump kernel입니다.

Neural Controlled Differential Equations (Neural CDEs): Wiener process를 general driving signal로 바꾸어 observed data stream이 dynamics를 drive하게 합니다. Trade와 quote stream이 latent market state를 drive하는 order flow에 자연스럽습니다.

Differentiable Market Simulation: Neural SDE를 differentiable simulator 내부의 generative model로 사용하고 adjoint를 통해 backpropagation하여 strategy를 end-to-end로 training합니다. Strategy와 market model이 함께 진화합니다.

Physics-Informed Neural ODEs: PINN은 loss에 differential-equation residual을 넣습니다 — 이 기법 자체는 Navier-Stokes article에서 소개됩니다. 금융 적용에서는 no-arbitrage, put-call parity, martingale condition을 penalty term이나 learned dynamics에 대한 hard constraint로 부과합니다.

결론

측정된 conclusion으로 수렴하는 smooth continuous dynamics

Continuous-time framing은 구조적으로 옳습니다. Market은 discrete하고 irregular한 시각에 관측되는 continuous process이며, model은 이를 존중해야 합니다. Toolchain은 성숙했습니다 — torchdiffeq, torchsde, 나머지는 plain PyTorch — 알려진 cost는 긴 integration interval에서의 solver speed와 numerical stability입니다.

확립되지 않은 것은 이 모든 것이 production stack에 들어갈 가치가 있는지 결정하는 부분입니다. Structural correctness는 predictive advantage의 evidence가 아니며, log(Δt)\log(\Delta t) feature에 대한 continuous dynamics의 구체적 advantage도 여기서 real trade data로 측정되지 않았습니다. 위 H2 table이 채워질 때까지 fold 아래의 내용은 working implementation이 붙은 well-specified hypothesis로 취급하십시오. result로 취급하면 안 됩니다.


참고문헌

  • Chen, R.T.Q., Rubanova, Y., Bettencourt, J., Duvenaud, D. (2018). 신경 상미분방정식. NeurIPS 2018. arXiv:1806.07366
  • Rubanova, Y., Chen, R.T.Q., Duvenaud, D. (2019). 불규칙하게 샘플링된 시계열을 위한 Latent ODE. NeurIPS 2019. arXiv:1907.03907
  • Jia, J., Benson, A.R. (2019). Neural Jump Stochastic Differential Equations. NeurIPS 2019.
  • Kidger, P., Morrill, J., Foster, J., Lyons, T. (2020). 불규칙 시계열을 위한 Neural Controlled Differential Equations. NeurIPS 2020.
  • Hasan, A., Pereira, J.M., Farsiu, S., Carin, L. (2021). 금융 data 예측에 적용한 Neural Network Stochastic Differential Equation Models. arXiv:2111.13164
  • torchdiffeq: github.com/rtqichen/torchdiffeq
  • UvA Deep Learning Tutorials — Neural 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

시장에서 앞서 나가세요

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

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