📝

Draft article

This draft is visible to admins and superusers only. Sign in with an authorized account.

← Volver a los artículos
August 22, 2026
5 min de lectura

Tiempo irregular en modelos de ticks: codificaciones de tiempo continuo versus incrustaciones posicionales simples

Tiempo irregular en modelos de ticks: codificaciones de tiempo continuo versus incrustaciones posicionales simples
#HFT
#tick-data
#deep-learning
#high-frequency
#prediction

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

Pulsos de eventos irregulares

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/dt La 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 α\alphay la advertencia de LOBFrame sobre la sensibilidad de la etiqueta a α\alpha y kk 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

Codificación de eventos secuencial de referencia

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 k=10k=10), 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

Codificaciones de tiempo irregulares alternativas

Opción A: delta_t como característica simple

La respuesta trivial. Añadir Δti=titi1\Delta t_i = t_i - t_{i-1} (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:

TE(Δt)=[sin(Δtτ1),cos(Δtτ1),,sin(Δtτd),cos(Δtτd)]\text{TE}(\Delta t) = \left[\sin\left(\frac{\Delta t}{\tau_1}\right), \cos\left(\frac{\Delta t}{\tau_1}\right), \ldots, \sin\left(\frac{\Delta t}{\tau_d}\right), \cos\left(\frac{\Delta t}{\tau_d}\right)\right]

dónde τ1,,τd\tau_1, \ldots, \tau_d 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:

dhdt=fθ(h(t),t)\frac{d\mathbf{h}}{dt} = f_\theta(\mathbf{h}(t), t)

Cuando llega un tick, el estado se actualiza con la observación:

h(ti+)=Update(h(ti),xi)\mathbf{h}(t_i^+) = \text{Update}(\mathbf{h}(t_i^-), \mathbf{x}_i)

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 Δti\Delta t_i 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

Campo de atención ponderada por evento

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:

wi=softplus(α1log(qi)+α2Δpi/σ+α31[sweepi])w_i = \text{softplus}\left(\alpha_1 \cdot \log(q_i) + \alpha_2 \cdot |\Delta p_i| / \sigma + \alpha_3 \cdot \mathbb{1}[\text{sweep}_i]\right)

dónde qiq_i es el tamaño del comercio, Δpi\Delta p_i el cambio de precio, σ\sigma volatilidad reciente, y sweepi\text{sweep}_i marca un barrido de varios niveles. Agréguelo a los logits de atención antes de softmax:

Attention(Q,K,V)=softmax(QKTdk+W)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + \mathbf{W}\right) V

con Wij=wj\mathbf{W}_{ij} = w_j. 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

Cintas de predicción de horizontes múltiples

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

Intervalo de exclusión del espacio de 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

Evaluación del modelo temporal controlado

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 Δt\Delta t 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 Δt\Delta t como característica y codificación en tiempo continuo ContinuousTimeEncoding

Qué reportar.

  1. 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.
  2. Un intervalo de confianza, de ejecuciones repetidas con diferentes semillas. Una brecha F1 de 0,4 puntos entre brazos no significa nada sin uno.
  3. 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.
  4. 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 Δt\Delta t 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

Ruta de investigación en tiempo continuo

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.

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

Mantente a la vanguardia

Suscríbete a nuestro boletín para recibir información exclusiva sobre trading con IA, análisis de mercado y actualizaciones de la plataforma.

Respetamos tu privacidad. Puedes darte de baja en cualquier momento.