📝

Draft article

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

← Retour aux articles
August 22, 2026
5 min de lecture

Temps irrégulier dans les modèles de ticks : codages en temps continu par rapport aux intégrations positionnelles simples

Temps irrégulier dans les modèles de ticks : codages en temps continu par rapport aux intégrations positionnelles simples
#HFT
#tick-data
#deep-learning
#high-frequency
#prediction

Les barres de temps sont une compression arbitraire qui détruit le timing des événements, et l'activité devrait plutôt définir les limites des barres - cet argument est présenté dans son intégralité, avec 17 types de barres et générateurs fonctionnels, dans [Types de barres et méthodes d'agrégation pour le trading algorithmique] (/en/blog/post/beyond-time-bars-candle-construction). Cet article commence une étape après, à la partie que personne n'a résolue : même une fois que vous êtes passé aux barres de graduation, de volume ou de déséquilibre, le modèle auquel vous les alimentez suppose toujours un espacement régulier. Un nn.TransformerEncoder avec une intégration positionnelle apprise, traite la position 7 comme "le septième emplacement", que ce tick soit arrivé 200 microsecondes ou 40 secondes après la position 6. Le temps entre les arrivées - les barres d'activité des choses ont été conçues pour préserver - est à nouveau rejeté au niveau de la couche d'entrée.

La question sur laquelle porte cet article est donc étroite et testable : comment un modèle de séquence doit-il être informé du moment où un tick s'est réellement produit, et la réponse sophistiquée bat-elle la réponse triviale ?

Contexte présumé

Impulsions d'événements irréguliers

Les prérequis suivants sont tous déjà abordés sur ce blog, et aucun d'entre eux n'est redirigé ici :

  • Bruit de microstructure et rebond acheteur-vendeur. Le modèle de spread implicite de Roll dérive la covariance série négative des rendements des ticks à partir des premiers principes, en unités de prix, avec l'erreur de mise en œuvre courante appelée : Modélisation du spread Bid-Ask avec ML.
  • Caractéristiques du carnet de commandes. Le déséquilibre du livre, le prix moyen pondéré, le ratio de profondeur sur les niveaux 1 à 5, la pression du livre et le ratio spread/tick sont présentés dans le même article ; l'OBI et les dérivations moyennes pondérées en volume - y compris la mise en garde selon laquelle la moyenne pondérée n'est pas le microprix de Stoikov - se trouvent dans [DeepLOB : Deep Learning on Limit Order Books] (/en/blog/post/deeplob-deep-learning-order-book).
  • Flux de commandes et intensité commerciale. Le déséquilibre commercial, le VPIN et le lambda de Kyle sont dans l'article sur la modélisation du spread ; l'intervalle inter-ordres et l'intensité d'auto-excitation de Hawkes en tant que caractéristiques de synchronisation se trouvent dans Digital Fingerprint: Trader Identification. Le brut 1/dt L'intensité de ce projet initialement proposé est une version plus faible de la formulation de Hawkes.
  • Non-stationnarité intrajournalière. Le modèle de volume en forme de U, l'élargissement du spread pendant les périodes calmes et le codage sin/cos de l'heure de la journée sont traités dans la modélisation du spread en tant que caractéristiques du régime de marché.
  • Étiquettes. Les deux conventions de lissage (futur moyen par rapport au prix actuel, futur moyen par rapport à la moyenne précédente), la discrétisation en trois classes avec seuil α\alpha, et l'avertissement de LOBFrame concernant la sensibilité des étiquettes à α\alpha et kk sont dans l'article DeepLOB.
  • Déséquilibre de classe. Avec une zone morte, la classe FLAT domine, c'est pourquoi la F1 plutôt que la précision est la métrique à signaler — même article.
  • Durée de remplissage de la barre. Le temps d'horloge murale nécessaire à une barre de volume pour se remplir est une fonctionnalité utilisable pour un modèle de tick ; les générateurs qui le produisent se trouvent dans Types de barres et méthodes d'agrégation.

Une chose mérite d'être déclarée explicitement, car la version originale de ce projet s'est trompée : un saut de 50,0 % à 50,5 % de précision directionnelle n'est pas évidemment rentable. Le point central de LOBFrame, abordé dans l'article DeepLOB, est qu'un mouvement prédit doit être suffisamment important pour effacer l'écart avant que la précision ait un sens, et le résultat négatif honnête sur ce blog est ce qui se passe lorsque vous supposez le contraire.

Référence

Encodage séquentiel des événements de base

Supposons un CNN-Inception-LSTM de style DeepLOB comme encodeur de base ; l'architecture, les numéros vérifiés de la configuration FI-2010 2 (F1 83,40 / précision 84,47 à k=10k=10), les mises en garde LOBFrame et une réimplémentation fonctionnelle de PyTorch se trouvent tous dans DeepLOB : Deep Learning on Limit Order Books. Rien ci-dessous ne change cet encodeur - toute la question est de savoir ce qui est ajouté à sa représentation d'entrée.

Trois façons d'encoder une heure irrégulière

Encodages temporels irréguliers alternatifs

Option A : delta_t comme fonctionnalité simple

La réponse triviale. Ajouter Δti=titi1\Delta t_i = t_i - t_{i-1} (transformé en journal, noté z) comme une colonne supplémentaire dans le vecteur de caractéristiques, aux côtés de l'OFI et du déséquilibre du livre, et conserve l'intégration positionnelle apprise standard. Cela ne coûte rien, ajoute une dimension d'entrée et c'est ce que font réellement la plupart des modèles de ticks de production.

C’est le bras que toute proposition plus sophistiquée doit battre, et c’est le bras qui est omis des articles proposant des alternatives plus fantaisistes.

Option B : codage positionnel en temps continu

Remplacez l'intégration positionnelle discrète par une fonction sinusoïdale du temps écoulé, sur des échelles de temps apprenables :

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]

τ1,,τd\tau_1, \ldots, \tau_d sont des paramètres d'échelle de temps apprenables initialisés en log espacés de quelques microsecondes à quelques secondes. L'effet recherché est que l'attention peut pondérer les événements en fonction de leur pertinence temporelle plutôt que de leur indice de créneau : une transaction importante il y a 200 microsecondes devrait être accessible différemment d'une petite transaction il y a 50 millisecondes.

La partie apprenable est la partie intéressante. Si vous initialisez des échelles de temps espacées de 1 microseconde à 10 secondes et que vous vous entraînez, l'endroit où finissent les échelles de temps est en soi une mesure : si elles s'effondrent vers la fin de la milliseconde, le modèle vous dit que tout au-delà de quelques millisecondes est interchangeable, et c'est une découverte sur le marché, pas sur l'architecture.

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)

Déposé dans un encodeur standard, il remplace exactement une ligne - l'intégration positionnelle ajoute :

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, :])

Le passe-partout de l’encodeur lui-même n’a rien de nouveau – le même nn.TransformerEncoderLayer l'échafaudage est déjà publié en tant qu'Architecture 2 dans spread modeling. Seulement ContinuousTimeEncoding et l'argument de l'échelle de temps d'apprentissage le sont.

Option C : état latent continu entre les ticks (ODE-RNN)

L'option la plus fondée sur des principes traite l'état latent comme un processus en temps continu modélisé par une Neural ODE (Chen et al., 2018). Entre les ticks l'état caché évolue selon une équation différentielle apprise :

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

Lorsqu'un tick arrive, l'état est mis à jour avec l'observation :

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

Il s'agit du cadre ODE-RNN. Il gère les espacements irréguliers de manière structurelle plutôt que comme une fonctionnalité d'entrée : le solveur intègre exactement Δti\Delta t_i entre les événements, sans remplissage, sans interpolation et sans étape de rééchantillonnage à laquelle perdre des informations.

Le coût est calculatoire. Les solveurs ODE sont séquentiels et difficiles à paralléliser, ce qui en fait l'arme la moins susceptible de survivre à un budget de latence - voir la discipline de latence établie dans La taxe IPC et l'échelle de vitesse du moteur de backtest pour savoir comment de telles affirmations doivent être mesurées avant d'être faites. Il est inclus ici comme limite supérieure de ce que la modélisation temporelle explicite peut acheter, et non comme candidat au déploiement.

Attention pondérée par les événements

Champ d'attention pondéré par l'événement

Deuxième idée orthogonale : tous les ticks ne sont pas également informatifs. Une transaction de 100 actions à l’offre est courante. Un commerce qui balaie trois niveaux de prix est un changement de régime. Nous pouvons injecter cela directement dans l’attention. Définissez un score d'importance d'événement :

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)

qiq_i est la taille du commerce, Δpi\Delta p_i le changement de prix, σ\sigma volatilité récente, et sweepi\text{sweep}_i signale un balayage à plusieurs niveaux. Ajoutez-le aux journaux d'attention avant 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

avec Wij=wj\mathbf{W}_{ij} = w_j. Le modèle conserve la capacité d'apprendre des schémas d'attention arbitraires, mais commence à être biaisé en faveur des transactions qui ont fait bouger le marché.

C'est une formule inventée. La forme fonctionnelle, le choix de trois termes et le softplus ne sont que des suppositions, et rien ici ne montre que le biais aide plutôt que la simple capacité de combustion.

Têtes multi-horizons

Rubans de prédiction multi-horizons

Différents horizons informent différentes décisions, un encodeur partagé avec plusieurs sorties d'horizon bat des modèles séparés par horizon et la perte conjointe se régularise - cet argument, ainsi que les sorties quantiles et l'interprétabilité, est présenté dans [Temporal Fusion Transformer for Trading] (/en/blog/post/temporal-fusion-transformer-trading). La seule partie spécifique aux ticks est l'horizon défini : 1, 10, 50 et 100 événements plutôt que jours, ce qui signifie que les horizons se chevauchent fortement dans le temps de l'horloge murale pendant les rafales et à peine pendant les périodes calmes - une complication que la littérature sur l'horizon quotidien n'a jamais à gérer.

La purge doit être mesurée dans les événements

Intervalle d'exclusion de l'espace événementiel

La validation walk-forward purgée est couverte de bout en bout dans Walk-Forward Optimization (CV ancré, continu, combinatoire purgé, WFER, taux de dégradation) et un modèle de travail. purged_walk_forward() avec une purge explicite et un écart d'embargo livré dans spread modeling. La taxonomie des biais d'anticipation quantifie l'effet des fuites de ce type sur Sharpe.

La seule chose qui est spécifique aux données de tick : les ticks adjacents espacés de quelques millisecondes sont des quasi-doublons, donc un intervalle de purge dimensionné en jours n'a aucun sens lorsqu'une rafale place 500 échantillons presque identiques en une seconde. L'écart doit être dimensionné en événements, et la bonne taille est une question empirique sur la structure de rafale de votre instrument, pas une constante à copier à partir d'un papier.

Cette affirmation est peu coûteuse à tester – balayez l’écart de purge dans les événements et tracez la validation F1 par rapport à cela. Si la validation F1 chute à mesure que l'écart s'élargit puis s'aplatit, le point d'aplatissement est votre écart ; s'il ne tombe jamais, la fuite des tiques adjacentes n'est pas le problème que vous pensiez.

L'expérience qui décide de cela

Évaluation du modèle temporel contrôlé

Tout ce qui précède est architecture. Rien de tout cela n’est une preuve. L'article n'efface pas la barre de ce blog tant que ce qui suit n'est pas exécuté :

Configuration. Corrigez un ensemble de données (les transactions BTC/USDT sont l'ensemble de données de la maison), un encodeur, une définition d'étiquette et une division purgée. Variez exactement une chose.

Trois bras.

Bras Informations horaires Codage positionnel
Un Δt\Delta t comme fonctionnalité d'entrée brute intégration positionnelle simple apprise
B aucun, au-delà de l'encodage ContinuousTimeEncoding (échelles de temps apprenables)
C Δt\Delta t comme fonctionnalité et encodage en temps continu ContinuousTimeEncoding

Ce qu'il faut signaler.

  1. F1 par classe, pas de précision — sous une étiquette de zone morte, la classe FLAT domine et la précision n'est pas informative pour exactement la raison donnée par l'article DeepLOB.
  2. Un intervalle de confiance, à partir d'exécutions répétées avec différentes graines. Un écart F1 de 0,4 point entre les bras ne signifie rien sans un.
  3. Les délais ajustés. Imprimer torch.exp(model.time_encoding.log_timescales) après la formation. L'endroit où ils s'installent est le nombre le plus intéressant de l'expérience, et son obtention coûte une ligne.
  4. Coût du matériel et de l'horloge murale par bras, dans le style de The IPC Tax — mesuré, sur le matériel divulgué, médian sur N. Si le bras B coûte 3 fois le temps d'inférence du bras A pour une fraction d'un point F1, voilà la réponse.

Signalez le résultat négatif s'il y en a un. "L'encodage en temps continu n'apporte rien par rapport à l'alimentation. Δt\Delta t en tant que fonctionnalité, sur 30 jours de ticks BTC" est un article plus utile qu'une enquête sur trois architectures que personne n'a mesurées. Cela serait également cohérent avec la conclusion générale de ce blog selon laquelle les bords soigneusement construits ont tendance à s'évaporer sous une validation honnête.

Où en est-on ?

Chemin de recherche en temps continu

La question découverte est réelle : les modèles de séquence alimentés par des événements irrégulièrement espacés codent toujours la position plutôt que le temps, et la couverture existante du blog - y compris l'encodeur Transformer dans la modélisation de propagation - utilise une intégration positionnelle simple et apprise. Les codages en temps continu, l'état latent ODE-RNN et l'attention pondérée par les événements sont trois façons de résoudre ce problème.

Ce qui manque, c’est la preuve que la réparation est importante. Tous les trois sont actuellement des sujets, pas des conclusions. La mesure décisive est une ablation à trois bras sur un encodeur fixe et une division purgée fixe, rapportant par classe F1 avec un intervalle de confiance et les échelles de temps ajustées – et elle n’a pas été effectuée.

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

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.