Onregelmatige tijd in tick-modellen: continue tijdcoderingen versus gewone positionele inbedding
Tijdbalken zijn een willekeurige compressie die de timing van gebeurtenissen vernietigt, en activiteit zou in plaats daarvan bargrenzen moeten definiëren - dat argument wordt volledig gemaakt, met 17 bar-types en werkende generatoren, in Bar Types and Aggregation Methods for Algorithmic Trading. Dit artikel begint een stap erna, bij het deel dat niemand heeft opgelost: zelfs als je eenmaal bent overgeschakeld naar tick-, volume- of onbalansbalken, gaat het model waaraan je ze voedt nog steeds uit van een regelmatige afstand. Een nn.TransformerEncoder met een aangeleerde positionele inbedding beschouwt positie 7 als "het zevende slot", ongeacht of dat vinkje 200 microseconden of 40 seconden na positie 6 arriveerde. De tussenaankomsttijd (de activiteitsbalken zijn gebouwd om te behouden) wordt weer weggegooid op de invoerlaag.
De vraag waar dit artikel over gaat is dus beperkt en testbaar: hoe moet een sequentiemodel worden verteld wanneer een tick daadwerkelijk heeft plaatsgevonden, en is het verfijnde antwoord beter dan het triviale?
Veronderstelde achtergrond

Het volgende zijn vereisten, die allemaal al op deze blog zijn behandeld, en geen daarvan is hier opnieuw afgeleid:
- Microstructuurruis en bid-ask bounce. Het impliciete spreadmodel van Roll leidt de negatieve seriële covariantie in tick-rendementen af van de eerste principes, in prijseenheden, waarbij de veel voorkomende implementatiefout wordt genoemd: Bid-Ask Spread Modeling with ML.
- Orderboekfuncties. Boekonbalans, gewogen middenprijs, diepteratio over niveaus 1-5, boekdruk en spread/tick-ratio worden in hetzelfde artikel weergegeven; de OBI en volume-gewogen-middenafleidingen – inclusief het voorbehoud dat het gewogen midden niet de microprijs van Stoikov is – staan in DeepLOB: Deep Learning on Limit Order Books.
- Orderstroom en handelsintensiteit. Handelsonevenwicht, VPIN en Kyle's lambda staan in het artikel over spread-modellering; inter-order interval en Hawkes zelf-excitatie-intensiteit als timingfuncties zijn te vinden in [Digital Fingerprint: Trader Identification] (/en/blog/post/digital-fingerprint-trader-identification). De ruwe
1/dtintensiteit dit oorspronkelijk voorgestelde ontwerp is een zwakkere versie van de Hawkes-formulering daar. - Niet-stationariteit binnen de dag. Het U-vormige volumepatroon, de groter wordende spread in rustige periodes en de sin/cos-codering van het tijdstip van de dag worden in spreadmodellering behandeld als kenmerken van het marktregime.
- Labels. Beide afvlakkingsconventies (toekomstig gemiddelde versus huidige prijs, toekomstig gemiddelde versus vorig gemiddelde), de discretisatie van drie klassen met drempelwaarde , en de waarschuwing van LOBFrame over labelgevoeligheid voor En staan in het DeepLOB-artikel.
- Klasse-onevenwicht. Bij een dode zone domineert de FLAT-klasse, en daarom is F1 in plaats van nauwkeurigheid de rapporteerbare maatstaf – hetzelfde artikel.
- Duur van het vullen van de balk. De tijd die een volumebalk nodig heeft om de wandklok te vullen is een bruikbare functie voor een tick-model; de generatoren die het produceren bevinden zich in Bar Types and Aggregation Methods.
Eén ding is de moeite waard om expliciet te vermelden, omdat de oorspronkelijke versie van dit concept het bij het verkeerde eind had: een sprong van 50,0% naar 50,5% richtingsnauwkeurigheid is niet vanzelfsprekend winstgevend. Het centrale punt van LOBFrame, besproken in het DeepLOB-artikel, is dat een voorspelde beweging groot genoeg moet zijn om de spread op te heffen voordat nauwkeurigheid iets betekent, en het eerlijke negatieve resultaat op deze blog is wat er gebeurt als je anders aanneemt.
Basislijn

Veronderstel een CNN-Inception-LSTM in DeepLOB-stijl als de basislijn-encoder; de architectuur, de geverifieerde FI-2010 Setup 2-nummers (F1 83.40 / nauwkeurigheid 84.47 bij ), de LOBFrame-waarschuwingen en een werkende PyTorch-herimplementatie staan allemaal in [DeepLOB: Deep Learning on Limit Order Books] (/en/blog/post/deeplob-deep-learning-order-book). Niets hieronder verandert die encoder - de hele vraag is wat er aan de invoerrepresentatie wordt toegevoegd.
Drie manieren om onregelmatige tijd te coderen

Optie A: delta_t als gewoon kenmerk
Het triviale antwoord. Toevoegen (log-getransformeerd, z-gescoord) als nog een kolom in de kenmerkvector, naast OFI en boekonbalans, en de standaard geleerde positionele inbedding behouden. Kost niets, voegt één invoerdimensie toe, en is wat de meeste productie-tick-modellen feitelijk doen.
Dit is de arm die elk voorstel van een liefhebber moet verslaan, en het is de arm die wordt weggelaten uit de artikelen waarin liefhebberalternatieven worden voorgesteld.
Optie B: positionele codering in continue tijd
Vervang de discrete positionele inbedding door een sinusoïdale functie van de verstreken tijd, over leerbare tijdschalen:
waar zijn leerbare tijdschaalparameters die zijn geïnitialiseerd op log-afstand van microseconden tot seconden. Het beoogde effect is dat de aandacht gebeurtenissen kan wegen op temporele relevantie in plaats van op slotindex – een grote transactie van 200 microseconden geleden zou anders bereikbaar moeten zijn dan een kleine transactie van 50 milliseconden geleden.
Het leerbare deel is het interessante deel. Als je tijdschalen initialiseert met een log-afstand van 1 microseconde tot 10 seconden en traint, waar de tijdschalen terechtkomen, is op zichzelf een meting: als ze instorten richting het einde van de milliseconde, vertelt het model je dat alles voorbij een paar milliseconden uitwisselbaar is, en dat is een bevinding over de markt, niet over de architectuur.
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)
Als het in een standaard encoder wordt geplaatst, vervangt het precies één regel – de positionele inbedding voegt toe:
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, :])
De standaardtekst van de encoder zelf is niets nieuws: hetzelfde nn.TransformerEncoderLayer scaffolding is al gepubliceerd als Architectuur 2 in spread modeling. Alleen ContinuousTimeEncoding en het leerbare-tijdschaal-argument is dat wel.
Optie C: continue latente toestand tussen ticks (ODE-RNN)
De meest principiële optie behandelt de latente toestand als een continu-tijdproces gemodelleerd door een Neural ODE (Chen et al., 2018). Tussen teken evolueert de verborgen toestand volgens een geleerde differentiaalvergelijking:
Wanneer een teek arriveert, wordt de status bijgewerkt met de observatie:
Dit is het ODE-RNN-framework. Het verwerkt onregelmatige spaties structureel in plaats van als invoerfunctie: de oplosser integreert voor exact tussen gebeurtenissen, zonder opvulling, zonder interpolatie en zonder herbemonsteringsstap waarbij informatie verloren gaat.
De kosten zijn rekenkundig. ODE-oplossers zijn sequentieel en moeilijk te parallelliseren, waardoor dit de arm is die het minst waarschijnlijk een latentiebudget zal overleven. Zie de latentiediscipline die is vastgelegd in The IPC Tax en the backtest engine speed ladder voor hoe dergelijke claims moeten worden gemeten voordat ze worden gedaan. Het is hier opgenomen als de bovengrens voor wat expliciete tijdmodellering kan opleveren, niet als een inzetkandidaat.
Gebeurtenisgewogen aandacht

Een tweede, orthogonaal idee: niet alle vinkjes zijn even informatief. Een transactie van 100 aandelen tegen het bod is routine. Een transactie die drie prijsniveaus doorkruist, is een regimeverandering. We kunnen dat vooraf direct in de aandacht injecteren. Definieer een belangrijkheidsscore voor een gebeurtenis:
waar is de handelsomvang, de prijsverandering, recente volatiliteit, en markeert een sweep op meerdere niveaus. Voeg het toe aan de aandachtslogits vóór softmax:
met . Het model behoudt het vermogen om willekeurige aandachtspatronen te leren, maar begint zich te richten op transacties die de markt in beweging brengen.
Dit is een verzonnen formule. De functionele vorm, de keuze uit drie termen en de softplus zijn allemaal gissingen, en niets hier toont aan dat de vooringenomenheid helpt in plaats van alleen maar capaciteit te verbranden.
Multi-Horizon-koppen

Verschillende horizonten vormen de basis voor verschillende beslissingen, een gedeelde encoder met meerdere horizon-outputs verslaat afzonderlijke modellen per horizon, en het gezamenlijke verlies wordt geregulariseerd – dat argument, plus kwantiel-outputs en interpreteerbaarheid, wordt gemaakt in Temporal Fusion Transformer for Trading. Het enige tick-specifieke deel is de horizonset: 1, 10, 50 en 100 gebeurtenissen in plaats van dagen, wat betekent dat de horizonten elkaar sterk overlappen in wandkloktijd tijdens bursts en nauwelijks tijdens rustige periodes – een complicatie waar de literatuur over de dagelijkse horizon nooit mee te maken heeft.
Zuivering moet worden gemeten bij evenementen

Opgeschoonde walk-forward-validatie wordt van begin tot eind behandeld in Walk-Forward Optimization (verankerd, rollend, combinatorisch opgeschoond CV, WFER, degradatiesnelheid) en een werkend purged_walk_forward() met een expliciete opschonings- en embargo-kloof wordt verzonden in spread modeling. De look-ahead bias-taxonomie kwantificeert wat dit soort lekken met gerapporteerde Sharpe doen.
Het enige dat specifiek is voor tick-gegevens: aangrenzende ticks die milliseconden uit elkaar liggen, zijn bijna duplicaten, dus een zuiveringsgat van dagen is zinloos als een burst 500 vrijwel identieke monsters binnen één seconde plaatst. De kloof moet worden gedimensioneerd in gebeurtenissen, en de juiste grootte is een empirische vraag over de burst-structuur van uw instrument, en niet een constante die u van papier kunt kopiëren.
Die bewering is goedkoop om te testen: veeg het zuiveringsgat in de gebeurtenissen weg en plan de validatie van de F1 daartegen. Als validatie F1 afneemt naarmate de kloof groter wordt en vervolgens vlakker wordt, is het afvlakkingspunt jouw kloof; als het nooit valt, was het lekken van aangrenzende teken niet het probleem dat u dacht dat het was.
Het experiment dat dit beslist

Alles daarboven is architectuur. Niets ervan is bewijs. Het artikel wist de balk van deze blog pas als het volgende wordt uitgevoerd:
Setup. Repareer één dataset (BTC/USDT-transacties is de huisdataset), één encoder, één labeldefinitie en één opgeschoonde splitsing. Varieer precies één ding.
Drie armen.
| Wapen | Tijdinformatie | Positionele codering |
|---|---|---|
| Een | als onbewerkte invoerfunctie | duidelijk geleerde positionele inbedding |
| B | geen, buiten de codering | ContinuousTimeEncoding (leerbare tijdschalen) |
| C | als een functie en continue tijdcodering | ContinuousTimeEncoding |
Wat te melden.
- F1 per klasse, niet nauwkeurigheid – onder het label dode zone domineert de FLAT-klasse, en nauwkeurigheid is niet informatief om precies de reden die het DeepLOB-artikel geeft.
- Een betrouwbaarheidsinterval, van herhaalde runs met verschillende zaden. Een F1-opening van 0,4 punten tussen de armen betekent niets zonder één.
- De aangepaste tijdschema's. Afdrukken
torch.exp(model.time_encoding.log_timescales)na de training. Waar ze zich vestigen is het meest interessante getal in het experiment, en het kost één regel om te verkrijgen. - Hardware- en wandklokkosten per arm, in de stijl van The IPC Tax — gemeten, op openbaar gemaakte hardware, mediaan boven N. Als arm B 3x de inferentietijd van arm A kost voor een fractie van een F1-punt, is dat het antwoord.
Rapporteer het negatieve resultaat als dat er is. "Continu-time coderen kost niets meer dan voeden als kenmerk, op 30 dagen BTC-tikken" is een nuttiger bericht dan een onderzoek van drie architecturen die niemand heeft gemeten. Het zou ook consistent zijn met de algemene bevinding op deze blog dat zorgvuldig geconstrueerde randen de neiging hebben te verdampen onder eerlijke validatie.
Waar dit staat

De ongedekte vraag is reëel: sequentiemodellen die onregelmatig gespreide gebeurtenissen voeden, coderen nog steeds positie in plaats van tijd, en de bestaande berichtgeving van de blog – inclusief de Transformer-encoder in spread-modellering – maakt gebruik van een duidelijk aangeleerde positionele inbedding. Continue tijdcoderingen, de latente status van ODE-RNN en gebeurtenisgewogen aandacht zijn drie manieren om dit op te lossen.
Wat ontbreekt is enig bewijs dat het oplossen ervan ertoe doet. Alle drie zijn momenteel onderwerpen, geen bevindingen. De beslissende meting is een ablatie met drie armen op een vaste encoder en een vaste, gezuiverde splitsing, die per klasse F1 rapporteert met een betrouwbaarheidsinterval en de gepaste tijdschalen – en deze is niet uitgevoerd.
Auteurs
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.