Tiempo irregular en modelos de ticks: codificaciones de tiempo continuo versus incrustaciones posicionales simples
Las barras de tiempo son una compresión arbitraria que destruye la sincronización de eventos, y la actividad debería definir los límites de las barras; ese argumento se presenta en su totalidad, con 17 tipos de barras y generadores en funcionamiento, en Tipos de barras y métodos de agregación para el comercio algorítmico. Este artículo comienza un paso después, en la parte que nadie resolvió: incluso una vez que hayas cambiado a barras de tick, volumen o desequilibrio, el modelo con el que las alimentas todavía asume un espaciado regular. Un nn.TransformerEncoder con una incrustación posicional aprendida trata la posición 7 como "la séptima ranura", ya sea que ese tick llegue 200 microsegundos o 40 segundos después de la posición 6. El tiempo entre llegadas (lo que las barras de actividad fueron construidas para preservar) se desperdicia nuevamente en la capa de entrada.
Entonces, la pregunta de la que trata este artículo es estrecha y comprobable: ¿cómo se debe saber en un modelo de secuencia cuándo ocurrió realmente un tic? ¿La respuesta sofisticada supera a la trivial?
Antecedentes asumidos

Los siguientes son requisitos previos, todos ya tratados en este blog y ninguno de ellos se vuelve a derivar aquí:
- Ruido de microestructura y rebote de oferta y demanda. El modelo de diferencial implícito de Roll deriva la covarianza serial negativa en los rendimientos de ticks de los primeros principios, en unidades de precio, con el error de implementación común denominado: Modelado de diferencial de oferta y demanda con ML.
- Características de la cartera de pedidos. El desequilibrio contable, el precio medio ponderado, la relación de profundidad en los niveles 1-5, la presión contable y la relación diferencial/tick se tabulan en el mismo artículo; el OBI y las derivaciones de la media ponderada por volumen, incluida la advertencia de que la media ponderada no es el microprecio de Stoikov, se encuentran en DeepLOB: Deep Learning on Limit Order Books.
- Flujo de pedidos e intensidad comercial. El desequilibrio comercial, VPIN y lambda de Kyle se encuentran en el artículo sobre modelos de diferenciales; el intervalo entre pedidos y la intensidad de la autoexcitación de Hawkes como características de sincronización se encuentran en Huella digital: identificación del comerciante. el crudo
1/dtLa intensidad que este borrador propuso originalmente es una versión más débil de la formulación de Hawkes allí. - No estacionariedad intradiaria. El patrón de volumen en forma de U, la ampliación del diferencial en períodos tranquilos y la codificación sin/cos de la hora del día se tratan en el modelado de diferenciales como características del régimen de mercado.
- Etiquetas. Ambas convenciones de suavizado (media futura versus precio actual, media futura versus media anterior), la discretización de tres clases con umbral y la advertencia de LOBFrame sobre la sensibilidad de la etiqueta a y se encuentran en el artículo de DeepLOB.
- Desequilibrio de clases. Con una zona muerta, la clase FLAT domina, razón por la cual F1, en lugar de la precisión, es la métrica que se debe informar: el mismo artículo.
- Duración del llenado de la barra. El tiempo de reloj de pared que tarda una barra de volumen en llenarse es una característica utilizable para un modelo de tick; los generadores que lo producen se encuentran en Tipos de barras y métodos de agregación.
Una cosa que vale la pena señalar explícitamente porque la versión original de este borrador se equivocó: un salto del 50,0% al 50,5% de precisión direccional no es evidentemente rentable. El punto central de LOBFrame, discutido en el artículo de DeepLOB, es que un movimiento previsto debe ser lo suficientemente grande como para eliminar la dispersión antes de que la precisión signifique algo, y el resultado negativo honesto en este blog es lo que sucede cuando se supone lo contrario.
Línea de base

Supongamos un CNN-Inception-LSTM estilo DeepLOB como codificador de referencia; la arquitectura, los números verificados de Configuración 2 FI-2010 (F1 83.40 / precisión 84.47 en ), las advertencias de LOBFrame y una reimplementación funcional de PyTorch se encuentran en DeepLOB: Deep Learning on Limit Order Books. Nada de lo siguiente cambia ese codificador; toda la cuestión es qué se agrega a su representación de entrada.
Tres formas de codificar el tiempo irregular

Opción A: delta_t como característica simple
La respuesta trivial. Añadir (transformada logarítmicamente, puntuación z) como una columna más en el vector de características, junto con OFI y desequilibrio de libros, y mantiene la incrustación posicional aprendida estándar. No cuesta nada, agrega una dimensión de entrada y es lo que realmente hacen la mayoría de los modelos de producción.
Este es el brazo que toda propuesta más sofisticada debe superar, y es el brazo que se omite en los artículos que proponen alternativas más sofisticadas.
Opción B: codificación posicional en tiempo continuo
Reemplace la incrustación posicional discreta con una función sinusoidal de tiempo transcurrido, en escalas de tiempo que se pueden aprender:
dónde son parámetros de escala de tiempo que se pueden aprender y que se inicializan con un espacio logarítmico de microsegundos a segundos. El efecto deseado es que la atención pueda ponderar los eventos por relevancia temporal en lugar de por índice de intervalo: una operación grande hace 200 microsegundos debería ser alcanzable de manera diferente que una operación pequeña hace 50 milisegundos.
La parte que se puede aprender es la parte interesante. Si inicializa escalas de tiempo espaciadas logarítmicamente de 1 microsegundo a 10 segundos y entrena, donde terminan las escalas de tiempo es en sí mismo una medida: si colapsan hacia el final de milisegundos, el modelo le está diciendo que todo más allá de unos pocos milisegundos es intercambiable, y eso es un hallazgo sobre el mercado, no sobre la arquitectura.
import numpy as np
import torch
import torch.nn as nn
class ContinuousTimeEncoding(nn.Module):
"""Sinusoidal encoding for irregular inter-arrival times."""
def __init__(self, d_model: int, num_timescales: int = 64):
super().__init__()
log_timescales = torch.linspace(
np.log(1e-6), np.log(10.0), num_timescales
)
self.log_timescales = nn.Parameter(log_timescales)
self.proj = nn.Linear(num_timescales * 2, d_model)
def forward(self, delta_t: torch.Tensor) -> torch.Tensor:
"""
Args:
delta_t: (batch, seq_len) inter-arrival times in seconds
Returns:
(batch, seq_len, d_model) time encoding
"""
timescales = torch.exp(self.log_timescales) # (num_timescales,)
scaled = delta_t.unsqueeze(-1) / timescales.unsqueeze(0).unsqueeze(0)
encoding = torch.cat([torch.sin(scaled), torch.cos(scaled)], dim=-1)
return self.proj(encoding)
Colocado en un codificador estándar, reemplaza exactamente una línea: la incrustación posicional agrega:
class TickTransformer(nn.Module):
def __init__(self, input_dim: int, d_model: int = 128,
nhead: int = 8, num_layers: int = 4, num_classes: int = 3):
super().__init__()
self.feature_proj = nn.Linear(input_dim, d_model)
self.time_encoding = ContinuousTimeEncoding(d_model)
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model, nhead=nhead,
dim_feedforward=d_model * 4,
dropout=0.1, batch_first=True
)
self.encoder = nn.TransformerEncoder(
encoder_layer, num_layers=num_layers
)
self.head = nn.Linear(d_model, num_classes)
def forward(self, features: torch.Tensor,
delta_t: torch.Tensor) -> torch.Tensor:
h = self.feature_proj(features) + self.time_encoding(delta_t)
h = self.encoder(h)
return self.head(h[:, -1, :])
El texto estándar del codificador en sí no es nada nuevo: lo mismo nn.TransformerEncoderLayer scaffolding ya está publicado como Arquitectura 2 en modelado extendido. Solo ContinuousTimeEncoding y el argumento de la escala de tiempo que se puede aprender lo son.
Opción C: estado latente continuo entre ticks (ODE-RNN)
La opción más basada en principios trata el estado latente como un proceso de tiempo continuo modelado por una ODA neuronal (Chen et al., 2018). Entre tics, el estado oculto evoluciona según una ecuación diferencial aprendida:
Cuando llega un tick, el estado se actualiza con la observación:
Este es el marco ODE-RNN. Maneja el espaciado irregular estructuralmente en lugar de como una característica de entrada: el solucionador se integra exactamente entre eventos, sin relleno, sin interpolación y sin paso de remuestreo en el que perder información.
El costo es computacional. Los solucionadores de ODE son secuenciales y difíciles de paralelizar, lo que hace que este sea el brazo con menos probabilidades de sobrevivir a un presupuesto de latencia; consulte la disciplina de latencia establecida en The IPC Tax y the backtest Engine speed ladder para saber cómo se deben medir dichas afirmaciones antes de realizarlas. Se incluye aquí como el límite superior de lo que se puede comprar con el modelado de tiempo explícito, no como un candidato de implementación.
Atención ponderada por eventos

Una segunda idea ortogonal: no todos los ticks son igualmente informativos. Una operación de 100 acciones en la oferta es una rutina. Un comercio que barre tres niveles de precios es un cambio de régimen. Podemos inyectar ese previo directamente en la atención. Defina una puntuación de importancia de evento:
dónde es el tamaño del comercio, el cambio de precio, volatilidad reciente, y marca un barrido de varios niveles. Agréguelo a los logits de atención antes de softmax:
con . El modelo mantiene la capacidad de aprender patrones de atención arbitrarios, pero comienza a sesgarse hacia las operaciones que movieron el mercado.
Esta es una fórmula inventada. La forma funcional, la elección de tres términos y el softplus son todas conjeturas, y nada aquí muestra que el sesgo ayuda más que simplemente quemar capacidad.
Cabezas multihorizonte

Diferentes horizontes informan diferentes decisiones, un codificador compartido con múltiples resultados de horizonte supera a los modelos separados por horizonte, y la pérdida conjunta se regulariza; ese argumento, más los resultados cuantiles y la interpretabilidad, se presenta en Temporal Fusion Transformer for Trading. La única parte específica de los ticks es el horizonte establecido: 1, 10, 50 y 100 eventos en lugar de días, lo que significa que los horizontes se superponen mucho en el tiempo del reloj de pared durante las ráfagas y apenas durante los períodos de calma, una complicación que la literatura sobre horizontes diarios nunca tiene que manejar.
La purga debe medirse en eventos

La validación de avance purgado se trata de principio a fin en Optimización de avance (CV anclado, rodante, purgado combinatorio, WFER, tasa de degradación) y un sistema operativo purged_walk_forward() con una purga explícita y una brecha de embargo en modelado de difusión. La taxonomía del sesgo de anticipación cuantifica lo que filtraciones de este tipo le hacen al reportado Sharpe.
Lo único que es específico de los datos de ticks: los ticks adyacentes separados por milisegundos son casi duplicados, por lo que un espacio de purga de tamaño en días no tiene sentido cuando una ráfaga coloca 500 muestras casi idénticas en un segundo. La brecha debe dimensionarse en eventos, y el tamaño correcto es una cuestión empírica sobre la estructura de ráfaga de su instrumento, no una constante para copiar de un papel.
Es barato probar esa afirmación: barrer la brecha de purga en los eventos y trazar la validación F1 en su contra. Si la validación F1 cae a medida que la brecha se amplía y luego se aplana, el punto de aplanamiento es su brecha; si nunca cae, la fuga de garrapatas adyacentes no fue el problema que pensaba que era.
El experimento que decide esto

Todo lo de arriba es arquitectura. Nada de eso es evidencia. El artículo no borra la barra de este blog hasta que se ejecute lo siguiente:
Configuración. Corrige un conjunto de datos (las operaciones BTC/USDT son el conjunto de datos interno), un codificador, una definición de etiqueta y una división eliminada. Variar exactamente una cosa.
Tres brazos.
| Brazo | Información horaria | Codificación posicional |
|---|---|---|
| Un | como característica de entrada sin formato | incrustación posicional aprendida simple |
| B | ninguno, más allá de la codificación | ContinuousTimeEncoding (escalas de tiempo aprendibles) |
| C | como característica y codificación en tiempo continuo | ContinuousTimeEncoding |
Qué reportar.
- F1 por clase, no precisión: bajo una etiqueta de zona muerta, la clase FLAT domina y la precisión no es informativa exactamente por la razón que da el artículo de DeepLOB.
- Un intervalo de confianza, de ejecuciones repetidas con diferentes semillas. Una brecha F1 de 0,4 puntos entre brazos no significa nada sin uno.
- Los plazos ajustados. Imprimir
torch.exp(model.time_encoding.log_timescales)después del entrenamiento. El lugar donde se asientan es el número más interesante del experimento y su obtención cuesta una línea. - Costo de hardware y reloj de pared por brazo, al estilo de The IPC Tax: medido, en hardware divulgado, mediana sobre N. Si el brazo B cuesta 3 veces el tiempo de inferencia del brazo A por una fracción de un punto F1, esa es la respuesta.
Informe el resultado negativo si lo hay. "La codificación de tiempo continuo no compra nada en comparación con la alimentación como característica, en 30 días de ticks BTC" es una publicación más útil que una encuesta de tres arquitecturas que nadie midió. También sería consistente con el hallazgo general en este blog de que los bordes cuidadosamente construidos tienden a evaporarse bajo una validación honesta.
Dónde está esto

La pregunta descubierta es real: los modelos de secuencia alimentados con eventos espaciados irregularmente aún codifican la posición en lugar del tiempo, y la cobertura existente del blog, incluido el codificador Transformer en el modelado extendido, utiliza una incrustación posicional aprendida simple. Las codificaciones de tiempo continuo, el estado latente ODE-RNN y la atención ponderada por eventos son tres formas de solucionar este problema.
Lo que falta es evidencia de que solucionarlo es importante. Los tres son actualmente temas, no hallazgos. La medición decisiva es una ablación de tres brazos en un codificador fijo y una división de purga fija, que informa F1 por clase con un intervalo de confianza y las escalas de tiempo ajustadas, y no se ha realizado.
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.