Neural ODEs: Does Continuous Time Beat a Delta-Time Feature?
Equações Diferenciais Ordinárias Neurais (Chen et al., NeurIPS 2018) parametrizam a derivada de um estado oculto com uma rede neural e permitem que um solucionador EDO calcule a saída. O modelo se torna de profundidade contínua e, mais útil para os mercados, de tempo contínuo: o estado oculto pode ser avaliado em qualquer , não apenas nos pontos da grade em que seus dados estão.
Essa propriedade tem uma tendência óbvia para dados financeiros, que chegam em intervalos aleatórios. Mas a proposta esconde uma afirmação muito mais fraca do que parece. Um GRU simples entregou um recurso de entrada extra - desde a observação anterior - também "sabe" quanto tempo passou. A verdadeira questão não é se os modelos de tempo contínuo podem ingerir dados irregulares. É se a dinâmica contínua aprendida entre observações extrai algo que um recurso de tempo delta ainda não oferece, por dez a cem vezes o custo de inferência.
Este artigo configura esse experimento, fornece o conjunto completo de ferramentas para executá-lo e relata o que a comparação teria que mostrar para justificar o maquinário. O lado da construção de barras do argumento - que o próprio timing da negociação carrega sinal - é estabelecido com evidências medidas em 17 tipos de barras em barras além do tempo; aqui a única consequência que importa é a modelagem: o solucionador avalia o estado oculto em arbitrário, portanto, nenhuma interpolação ou preenchimento é necessário.
A afirmação em teste

Três hipóteses aninhadas, cada uma estritamente mais forte que a anterior:
- H1 — Um ajuste ODE-RNN em um fluxo de negociação BTC irregular bruto supera um ajuste GRU no mesmo fluxo sem nenhuma informação de tempo. Isto é quase certo e quase inútil: a ODE-RNN simplesmente tem informação que falta à linha de base.
- H2 — O ODE-RNN supera o mesmo GRU uma vez que o GRU recebe como um recurso de entrada. Este é o teste honesto. É a afirmação que a literatura de ODE Neural costuma fazer, e é aquela que quase nunca é demonstrada em dados financeiros.
- H3 — A vantagem H2 sobrevive ao orçamento de latência, ou seja, é válida quando o ODE-RNN é implantado com um solucionador de etapa fixa rápido o suficiente para inferência ao vivo, em vez de
dopri5adaptativo.
Protocolo Experimental
O mesmo fluxo de ticks, o mesmo alvo, o mesmo orçamento de ajuste para ambos os braços. Nenhuma reamostragem para barras de 1m – a reamostragem destrói a estrutura exata em teste.
| Configuração | |
|---|---|
| Dados | Impressões comerciais BTC, tempos variáveis entre chegadas, não reamostrados |
| Armas | EDO-RNN; GRU + ; GRU sem recurso de horário |
| Alvo | O mesmo para todos os braços; especificado com o ajuste |
| Métricas | RMSE, probabilidade de log mantida, relógio de parede por época, latência de inferência por etapa |
| Varredura do solucionador | dopri5 (rtol 1e-5) vs Euler de passo fixo, combinado na precisão do treinamento |
| Dividir | Walk-forward, apenas fora da amostra |
| Braço | REMSE | Log-like | s/época | Latência/etapa de inferência |
|---|---|---|---|---|
| GRU (sem recurso de horário) | — | — | — | — |
| GRU + log(dt) | — | — | — | — |
| EDO-RNN (dopri5) | — | — | — | — |
| ODE-RNN (Euler de passo fixo) | — | — | — | — |
Um resultado negativo em H2 é totalmente publicável e sem dúvida o resultado mais valioso – o mesmo padrão aplicado em resultado negativo honesto e Sharpe deflacionado sob múltiplos testes.
Experimento Secundário: CNF vs Student-t
Se a comparação ODE-RNN for muito cara, a âncora empírica mais barata é a distributiva: ajuste um fluxo de normalização contínua nos retornos diários reais do BTC e compare sua densidade ajustada com um ajuste t de Student e com a cauda empírica, relatando erros de quantil de cauda nos níveis de 1% e 5%. Isso se conecta diretamente ao trabalho distributivo já medido em previsão de volatilidade GARCH e GARCH assimétrico.
O restante deste artigo é o histórico e o conjunto de ferramentas necessário para executar o acima.
Antecedentes: De ResNets à Dinâmica Contínua

Uma rede residual calcula . Reduza o tamanho do passo e aumente o número de camadas e você se aproximará de um limite contínuo:
Em vez de camadas discretas com parâmetros separados, uma única rede especifica a taxa instantânea de mudança. A saída no tempo resolve um problema de valor inicial:
Um solucionador de caixa preta (Euler, Runge-Kutta, Dormand-Prince) calcula a integral numericamente, escolhendo adaptativamente os tamanhos dos passos a partir de estimativas de erros locais.
O Método Adjunto
A retropropagação através de cada etapa do solucionador armazena todos os estados intermediários, custando memória proporcional à contagem de etapas. Chen et al. em vez disso, resolva uma EDO reversa com memória . Defina o estado adjunto , que satisfaz
e acumule o gradiente do parâmetro ao longo da passagem para trás:
A passagem para trás resolve um sistema aumentado computando , e simultaneamente, executando o solucionador ao contrário de para .
A compensação: reconstruir de trás para frente acumula erros numéricos, o que é ruim para dinâmicas rígidas ou caóticas. Checkpointing é o meio termo - armazene em alguns momentos intermediários e recalcule entre eles. Os processos de preços são semimartingales contínuos e moderadamente suaves, de modo que o adjunto geralmente se mantém; a rigidez em torno dos eventos da microestrutura pode forçar solucionadores adaptativos ou híbridos.
ODE-RNN: estado oculto contínuo entre observações

A ODE-RNN é a arquitetura que o experimento principal ativa. Entre observações, o estado oculto evolui sob a EDO:
Quando a observação chega em , uma atualização discreta é acionada:
Os sobrescritos indicam o estado imediatamente antes e logo após a observação. Entre observações, aprendeu dinâmica contínua; nas observações, novas informações.
O mecanismo que a ablação H2 investiga é este: um longo intervalo significa que o estado oculto evoluiu muito sob – decaindo em direção a uma linha de base, ou divergente – e a forma dessa evolução é aprendida em vez de fornecida como um escalar. Se essa expressividade se compensa com base em dados comerciais reais é exactamente o que não pode ser medido.
Ajustes naturais além dos ticks: carteiras multiativos onde cada ativo possui seu próprio cronograma de observação e o estado latente dos ativos correlacionados continua evoluindo enquanto apenas um é observado; e sinais orientados por eventos (notícias, lucros, divulgações macro) que chegam em horários irregulares.
SDEs Neurais: Adicionando Componentes Estocásticos

As EDOs neurais são determinísticas e não podem representar o ruído em uma trajetória de preços. O tratamento clássico – movimento browniano geométrico, deriva neutra ao risco e as formas como as suposições de volatilidade constante falham contra o sorriso – é abordado em precificação de opções Black-Scholes. SDEs neurais substituem a forma paramétrica por um termo de difusão aprendido:
é a deriva, a difusão, um processo de Wiener, redes neurais e .
Arquitetura: Rede de Deriva e Rede de Difusão
- Rede de deriva : trajetória esperada — tendência, reversão à média, momentum. Treinado para minimizar erros de previsão.
- Rede de difusão : magnitude do ruído, aprendida em função do estado. Alta em regimes voláteis, baixa em regimes calmos.
A divisão reflete o financiamento quântico clássico: a deriva é a dinâmica neutra ao risco (ou medida P), a difusão é a superfície de volatilidade.
Treinamento de SDEs Neurais
A integral estocástica não é classicamente diferenciável. Três abordagens:
- Gradientes pathwise: truque de reparametrização - amostra de caminhos brownianos, diferenciada por meio do solucionador, tratando o ruído como entrada fixa.
- Correspondência de pontuação: estimar e treinar por meio de objetivos de eliminação de ruído — o mesmo maquinário usado como um modelo generativo independente em modelos de difusão para previsão de criptografia, aqui rebaixado a uma opção de treinamento para o SDE.
- Correspondência de distribuição de dimensão finita: correspondência de marginais em tempos de observação em vez de medidas de caminho completo.
Pathwise é o padrão prático. torchsde implementa Euler-Maruyama, Milstein e Runge-Kutta estocástico com suporte para autodiff.
Aprendendo a superfície da volatilidade
Os modelos clássicos (Heston, SABR) impõem formas paramétricas ao coeficiente de difusão. Um SDE Neural aprende como uma função arbitrária:
Este é um aproximador universal para processos de difusão - qualquer processo Ito com precisão arbitrária, desde que tenha capacidade suficiente. MANTER_42
EDOs latentes para dados ausentes e assíncronos

A EDO Latente (Rubanova, Chen, Duvenaud, 2019) emparelha uma EDO Neural com um VAE: séries financeiras são observações ruidosas de um processo subjacente suave, aprendido em um espaço latente de baixa dimensão.
- Rede de reconhecimento: uma ODE-RNN retrocedendo nas observações para produzir .
- Dinâmica latente: .
- Decodificador: , avaliável a qualquer momento solicitado.
A perda é o ELBO padrão:
Estimativa do estado do portfólio: a partir de observações assíncronas esparsas entre ativos, infere um estado latente contínuo que captura a dinâmica conjunta — essencialmente uma versão não linear e aprendida do filtro de Kalman.
Imputação de dados ausentes: paradas e intervalos de fim de semana obtêm uma trajetória latente interpolada suavemente; o decodificador produz caminhos plausíveis através da lacuna com incerteza do VAE posterior.
Fusão multifrequencial: fechamentos diários, VWAP intradiário e dados de ticks em um modelo sem grade de tempo compartilhada.
Fluxos de normalização contínua para distribuições de retorno

Os CNFs usam uma EDO Neural para transformar uma distribuição de base simples em um alvo complexo. A transformação é , e a densidade logarítmica segue a mudança instantânea de variáveis:
Integre o estado e a densidade de log de para obter e . O traço Jacobiano é estimado com o estimador estocástico de Hutchinson para escalabilidade.
Os retornos criptográficos são leptocúrticos e distorcidos negativamente – medidos em dados reais em previsão de volatilidade GARCH e GARCH assimétrico e o efeito de alavancagem – e um CNF aprende essa forma em vez de assumi-la. Condicionar o fluxo em variáveis de estado permite que a forma mude com o regime. Como a densidade é exata e não aproximada, ela pode alimentar as métricas finais calculadas em Monte Carlo e backtests de bootstrap e a construção de CVaR em pipeline de portfólio HRP/CVaR; a estrutura da cauda conjunta é tratada separadamente em modelos de cópula para risco conjunto.
Estimativa de densidade condicional
O enquadramento útil é um contraste direto entre três abordagens publicadas para o mesmo problema. TFT fornece um conjunto fixo de quantis de uma camada de saída de quantil. Previsão conforme oferece intervalos calibrados com garantia de cobertura. Um CNF condicional fornece uma densidade exata e diferenciável:
A diferenciabilidade é a propriedade distintiva: a densidade pode ficar dentro de um objetivo posterior e ser retropropagada, o que nem quantis fixos nem intervalos conformes suportam. Se a densidade exata vale seu custo em relação aos quantis de TFT no mesmo alvo, não foi testado aqui. MANTER_43
Comparação com alternativas discretas

| Propriedade | LSTM/GRU | Transformador | EDO neural | SDE neural |
|---|---|---|---|---|
| Tratamento irregular de horas | Fraco (precisa de preenchimento) | Codificação posicional | Nativo | Nativo |
| Memória (treinamento) | MANTER_108 | MANTER_109 | adjunto | adjunto |
| Quantificação da incerteza | Não (determinístico) | Não (determinístico) | Através de conjuntos | Nativo |
| Interpolação entre observações | Não | Não | Sim | Sim |
| Densidade de tempo contínuo | Não | Não | Via CNF | Via medida de caminho |
| Custo computacional | Baixo | Moderado | Variável (solucionador) | Alto (solucionador SDE) |
A metade LSTM versus atenção desta tabela é discutida em detalhes, com benchmarks, no artigo do TFT - consulte as seções "TFT vs LSTM vs Vanilla Transformer" e "Quando LSTM ainda vence". As colunas novas aqui são as duas da direita, e as linhas que decidem as questões H2/H3 são as duas últimas.
Implementação Python com torchdiffeq

torchdiffeq fornece solucionadores de EDO com retropropagação adjunta.
EDO Neural Básico para Dinâmica de Preços
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 para dados de ticks irregulares
Este é o braço experimental. A linha de base que deve bater é nn.GRUCell no mesmo fluxo com log(t_next - t_prev) concatenado a 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)
Ciclo de treinamento
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}")
Fluxo de normalização contínua para distribuições de retorno
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]
Notas Práticas

Estas são as coisas que irão morder você durante a execução do experimento acima.
Escolha e velocidade do solucionador
dopri5 oferece garantias de precisão, mas custo de computação variável, e é por isso que H3 é uma hipótese separada de H2: uma vantagem que existe apenas sob um solucionador adaptativo pode não sobreviver a um orçamento de latência ativo. Solucionadores de etapa fixa (Euler, RK4) fornecem latência previsível em detrimento da precisão. Compromisso prático: treine com dopri5, implante com um solucionador de etapa fixa calibrado para corresponder à saída do solucionador adaptativo em dados representativos — e meça a lacuna em vez de presumir que ela é pequena.
odeint(f, h0, t, method='dopri5', rtol=1e-6, atol=1e-8)
odeint(f, h0, t, method='euler', options={'step_size': 0.1})
Para problemas rígidos próximos a saltos descontínuos, method='implicit_adams' ou method='scipy_solver'.
Estabilidade Numérica
Se gerar valores grandes, o estado diverge. Mitigações:
- Normalização espectral nas camadas de para controlar a constante de Lipschitz.
- Recorte de gradiente durante o treinamento (mostrado no loop de treinamento acima).
- Normalização de tempo: dimensione os carimbos de data/hora para .
- Regularização na norma dinâmica: adicionar à perda.
Intervalo de Integração
Para dados que abrangem meses, não integre de a minutos:
t_normalized = (timestamps - timestamps[0]) / (timestamps[-1] - timestamps[0])
Isso mantém o solucionador em um regime numericamente amigável e torna a dinâmica aprendida invariante em escala. Também é importante para a comparação: uma ODE-RNN não normalizada pode perder para a linha de base do GRU por razões puramente numéricas, o que seria um artefato de medição e não uma descoberta.
Lidando com múltiplas escalas de tempo
Os mercados têm dinâmicas em escalas de microssegundos, segundos, minutos e diárias ao mesmo tempo, e uma única EDO Neural pode ter dificuldade para manter todas elas. Opções: empilhar vários blocos EDO em diferentes escalas de tempo; expandir a dimensão oculta para capacidade rápida e lenta; ou execute ODEs neurais separadas por banda de frequência e saídas de fusíveis. (Esta é uma questão de capacidade do modelo, diferente da questão de fidelidade de backtest em detalhamento da resolução adaptativa.)
Direções de pesquisa abertas

SDEs de salto neural: adicione um componente de salto aprendido para deslocamentos repentinos (falhas repentinas, surpresas de ganhos):
onde é uma medida aleatória de Poisson compensada e um kernel de salto aprendido.
Equações diferenciais controladas neurais (CDEs neurais): substitua o processo de Wiener por um sinal de acionamento geral, permitindo que os fluxos de dados observados conduzam a dinâmica. Natural para o fluxo de pedidos, onde o fluxo de negociações e cotações impulsiona o estado latente do mercado.
Simulação de mercado diferenciável: use um SDE Neural como modelo generativo dentro de um simulador diferenciável e treine estratégias de ponta a ponta retropropagando através dele por meio do adjunto. Estratégia e modelo de mercado co-evoluem.
EDOs neurais informados pela física: PINNs incorporam um resíduo de equação diferencial na perda — a técnica em si é introduzida no artigo de Navier-Stokes. A aplicação financeira é impor condições de não arbitragem, paridade put-call e martingale como termos de penalidade ou restrições rígidas à dinâmica aprendida.
Conclusão

O enquadramento temporal contínuo é estruturalmente correto: os mercados são processos contínuos observados em tempos discretos e irregulares, e os modelos devem respeitar isso. O conjunto de ferramentas está maduro – torchdiffeq, torchsde, PyTorch simples para o resto – e os custos conhecidos são a velocidade do solucionador e a estabilidade numérica em longos intervalos de integração.
O que não está estabelecido é a parte que decide se algo disso pertence a uma pilha de produção. A correção estrutural não é evidência de vantagem preditiva, e a vantagem específica reivindicada para a dinâmica contínua sobre uma característica não foi medida aqui em dados comerciais reais. Até que a tabela H2 acima seja preenchida, trate tudo abaixo da dobra como uma hipótese bem especificada com uma implementação funcional anexada, e não como um resultado.
Referências
- Chen, RTQ, Rubanova, Y., Bettencourt, J., Duvenaud, D. (2018). Equações Diferenciais Ordinárias Neurais. NeurIPS 2018. arXiv:1806.07366
- Rubanova, Y., Chen, RTQ, Duvenaud, D. (2019). EDOs latentes para séries temporais com amostragem irregular. NeurIPS 2019. arXiv:1907.03907
- Jia, J., Benson, AR (2019). Equações Diferenciais Estocásticas de Salto Neural. NeuroIPS 2019.
- Kidger, P., Morrill, J., Foster, J., Lyons, T. (2020). Equações diferenciais controladas neurais para séries temporais irregulares. NeuroIPS 2020.
- Hasan, A., Pereira, JM, Farsiu, S., Carin, L. (2021). Modelos de equações diferenciais estocásticas de redes neurais com aplicações para previsão de dados financeiros. arXiv:2111.13164
- torchdiffeq: github.com/rtqichen/torchdiffeq
- Tutoriais de aprendizado profundo UvA - ODEs neurais: uvadlc-notebooks.readthedocs.io
Authors
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.