Irregular Time in Tick Models: Continuous-Time Encodings vs. Plain Positional Embeddings
시간 bar는 이벤트 시점을 파괴하는 임의의 압축입니다. 따라서 bar의 경계는 활동이 정해야 합니다. 알고리즘 트레이딩을 위한 Bar 유형과 집계 방법에서 17가지 bar 유형과 실제로 작동하는 생성기를 포함해 이 주장을 자세히 다뤘습니다. 이 글은 그 다음 단계, 아직 아무도 해결하지 못한 부분에서 시작합니다. tick, 거래량 또는 불균형 bar로 바꾼 뒤에도 데이터를 입력받는 모델은 여전히 일정한 간격을 가정합니다. 학습된 위치 임베딩을 가진 nn.TransformerEncoder는 위치 6 이후 200마이크로초 만에 도착한 tick이든 40초 뒤에 도착한 tick이든 위치 7을 “일곱 번째 슬롯”으로 취급합니다. 활동 bar가 보존하려고 했던 도착 간격 시간이 입력층에서 다시 버려지는 것입니다.
따라서 이 글의 질문은 좁고 검증 가능합니다. 시퀀스 모델에 tick이 실제로 언제 발생했는지를 어떻게 알려야 하며, 정교한 해법이 단순한 해법보다 나은가?
전제 배경

다음은 모두 이 블로그에서 이미 다룬 전제 지식이며, 여기서는 다시 유도하지 않습니다.
- 미시구조 노이즈와 매수-매도 반동. Roll의 암묵적 스프레드 모델은 가격 단위에서 tick 수익률의 음의 자기공분산을 제1원리로부터 유도하고, 흔한 구현 오류도 지적합니다. 머신러닝을 이용한 매수-매도 스프레드 모델링을 참고하세요.
- 호가창 특성. 장부 불균형, 가중 중간가격, 1–5단계 깊이 비율, 호가창 압력, 스프레드/tick 비율은 같은 글에 정리되어 있습니다. OBI와 거래량 가중 중간가격의 유도(가중 중간가격은 Stoikov의 microprice가 아니라는 주의 포함)는 DeepLOB: 지정가 호가창의 딥러닝에 있습니다.
- 주문 흐름과 거래 강도. 거래 불균형, VPIN, Kyle의 lambda는 스프레드 모델링 글에 있고, 주문 간격과 Hawkes 자기흥분 강도를 시간 특성으로 사용하는 방법은 디지털 지문: 트레이더 식별에 있습니다. 이 초안이 원래 제안한 조악한
1/dt강도는 해당 Hawkes 정식화의 약한 버전입니다. - 장중 비정상성. U자형 거래량 패턴, 조용한 구간에서 넓어지는 스프레드, 시간대 sin/cos 인코딩은 스프레드 모델링에서 시장 국면 특성으로 다룹니다.
- 라벨. 두 가지 평활화 관례(미래 평균 대 현재 가격, 미래 평균 대 이전 평균), 임계값 를 사용한 3등급 이산화, 와 에 대한 라벨 민감성에 관한 LOBFrame의 경고는 DeepLOB 글에 있습니다.
- 클래스 불균형. 데드존이 있으면 FLAT 클래스가 지배하므로 정확도보다 F1을 보고해야 합니다. 같은 글에서 설명한 내용입니다.
- Bar 채움 시간. 거래량 bar가 채워지는 데 걸리는 실제 경과 시간은 tick 모델에 쓸 수 있는 특성이고, 이를 생성하는 생성기는 Bar 유형과 집계 방법에 있습니다.
초안의 원래 버전이 틀렸기 때문에 한 가지를 명시해야 합니다. 방향 정확도가 50.0%에서 50.5%로 오른 것이 수익성을 자명하게 보장하지는 않습니다. DeepLOB 글에서 논의한 LOBFrame의 핵심은 예측된 움직임이 스프레드를 넘을 만큼 커야 정확도가 의미를 갖는다는 것입니다. 정직한 부정적 결과는 그 반대로 가정할 때 이 블로그에서 일어나는 일을 보여줍니다.
기준선

DeepLOB 스타일의 CNN-Inception-LSTM을 기준 인코더로 가정합니다. 아키텍처, 검증된 FI-2010 Setup 2 수치(에서 F1 83.40 / 정확도 84.47), LOBFrame의 주의사항, 작동하는 PyTorch 재구현은 모두 DeepLOB: 지정가 호가창의 딥러닝에 있습니다. 아래에서는 이 인코더를 바꾸지 않습니다. 입력 표현에 무엇을 더할지가 유일한 질문입니다.
불규칙한 시간을 인코딩하는 세 가지 방법

옵션 A: delta_t를 일반 특성으로 사용
가장 단순한 답입니다. (로그 변환 후 z-score)를 OFI와 장부 불균형 옆의 특성 벡터 열로 추가하고, 표준 학습 위치 임베딩은 그대로 둡니다. 비용은 없고 입력 차원 하나만 늘어나며, 실제 프로덕션 tick 모델 대부분이 이 방법을 사용합니다.
이것은 더 복잡한 제안들이 반드시 이겨야 하는 기준선이며, 더 복잡한 대안을 제안하는 논문에서 빠지는 기준선이기도 합니다.
옵션 B: 연속 시간 위치 인코딩
이산 위치 임베딩을 경과 시간의 사인 함수로 바꾸고, 학습 가능한 시간 척도를 사용합니다.
여기서 는 마이크로초부터 초까지 로그 간격으로 초기화되는 학습 가능한 시간 척도 매개변수입니다. 의도한 효과는 어텐션이 슬롯 인덱스가 아니라 시간적 관련성으로 이벤트에 가중치를 주게 하는 것입니다. 200마이크로초 전의 큰 거래와 50밀리초 전의 작은 거래가 서로 다른 방식으로 도달 가능해집니다.
학습 가능한 부분이 흥미롭습니다. 시간 척도를 1마이크로초에서 10초까지 로그 간격으로 초기화해 학습한다면, 시간 척도가 최종적으로 어디에 놓이는지 자체가 측정값입니다. 밀리초 쪽으로 수렴한다면 몇 밀리초를 넘는 모든 것이 서로 바꿔도 된다는 시장의 신호이지, 아키텍처에 관한 결과가 아닙니다.
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)
표준 인코더에 넣으면 정확히 한 줄, 위치 임베딩을 더하는 줄만 바뀝니다.
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, :])
인코더 보일러플레이트 자체는 새롭지 않습니다. 동일한 nn.TransformerEncoderLayer 골격이 스프레드 모델링의 아키텍처 2로 이미 공개되어 있습니다. 새로운 것은 ContinuousTimeEncoding과 학습 가능한 시간 척도라는 주장뿐입니다.
옵션 C: tick 사이의 연속 잠재 상태(ODE-RNN)
가장 원칙적인 방법은 잠재 상태를 Neural ODE(Chen 등, 2018)로 모델링한 연속 시간 과정으로 취급하는 것입니다. 두 tick 사이에서 은닉 상태는 학습된 미분방정식에 따라 진화합니다.
tick이 도착하면 관측값으로 상태를 업데이트합니다.
이것이 ODE-RNN 프레임워크입니다. 불규칙한 간격을 입력 특성으로 처리하는 대신 구조적으로 처리합니다. 솔버가 이벤트 사이의 정확한 만큼 적분하므로 패딩, 보간, 정보를 잃게 만드는 리샘플링 단계가 없습니다.
대가는 계산 비용입니다. ODE 솔버는 순차적이고 병렬화하기 어려워 지연 예산을 통과할 가능성이 가장 낮습니다. 배포 전에 이런 주장을 측정하는 방법은 IPC 세금과 백테스트 엔진 속도 사다리의 지연 규율을 참고하세요. 여기서는 명시적 시간 모델링이 얻을 수 있는 상한으로 포함했으며, 배포 후보로 제시한 것은 아닙니다.
이벤트 가중 어텐션

두 번째이자 직교하는 아이디어는 모든 tick이 같은 정보를 주지 않는다는 것입니다. 매수호가에서 100주를 거래하는 것은 일상적이지만, 세 가격 레벨을 쓸어가는 거래는 국면 변화입니다. 이 사전 정보를 어텐션에 직접 주입할 수 있습니다. 이벤트 중요도 점수를 정의합니다.
여기서 는 거래량, 는 가격 변화, 는 최근 변동성, 는 여러 레벨을 쓸어간 경우를 표시합니다. softmax 전에 어텐션 logits에 더합니다.
입니다. 모델은 임의의 어텐션 패턴을 학습할 능력을 유지하면서도 시장을 움직인 거래를 향해 편향된 상태에서 시작합니다.
이것은 발명된 공식입니다. 함수 형태, 세 항의 선택, softplus는 모두 추측이며, 이 편향이 용량을 소모할 뿐 실제로 도움이 된다는 증거는 없습니다.
다중 시계열 헤드

서로 다른 시계열은 서로 다른 의사결정에 정보를 주며, 여러 시계열 출력을 가진 공유 인코더가 시계열마다 별도 모델을 두는 것보다 낫고, 공동 손실은 정규화 역할을 합니다. 이 주장은 분위수 출력과 해석 가능성을 포함해 트레이딩을 위한 Temporal Fusion Transformer에서 설명했습니다. tick에 특화된 부분은 시계열 집합입니다. 일수가 아니라 1, 10, 50, 100 이벤트를 사용하므로 폭주 중에는 실제 시간상 시계열이 크게 겹치고, 조용한 구간에는 거의 겹치지 않습니다. 일일 시계열 문헌에는 없는 복잡성입니다.
퍼징은 이벤트 단위로 측정해야 한다

퍼지 워크포워드 검증은 워크포워드 최적화에서 처음부터 끝까지 다룹니다(앵커드, 롤링, 조합 퍼지 CV, WFER, 성능 저하율). 명시적인 퍼지 및 엠바고 간격이 있는 실행 가능한 purged_walk_forward()는 스프레드 모델링에 포함되어 있습니다. 룩어헤드 편향 분류는 이런 누수가 보고된 샤프에 미치는 영향을 정량화합니다.
tick 데이터에 특유한 점은 밀리초 차이의 인접 tick이 거의 중복이라는 것입니다. 따라서 일 단위 퍼지 간격은 한 번의 폭주가 1초 안에 거의 같은 샘플 500개를 만들 때 의미가 없습니다. 간격은 이벤트 단위여야 하며, 올바른 크기는 도구의 폭주 구조에 관한 경험적 문제이지 논문에서 복사할 상수가 아닙니다.
이 주장은 쉽게 테스트할 수 있습니다. 이벤트 단위 퍼지 간격을 바꾸어 보고 검증 F1을 그리세요. 간격이 커질수록 검증 F1이 떨어지다가 평평해진다면 평평해지는 지점이 간격입니다. 전혀 떨어지지 않는다면 인접 tick 누수가 문제라고 생각했던 것이 실제 문제는 아니었습니다.
이를 결정하는 실험

위의 모든 내용은 아키텍처일 뿐 증거가 아닙니다. 다음 실행이 끝나기 전에는 이 글이 블로그의 기준을 통과하지 못합니다.
설정. 하나의 데이터셋(BTC/USDT 거래가 이 팀의 데이터셋), 하나의 인코더, 하나의 라벨 정의, 하나의 퍼지 분할을 고정하고 정확히 한 가지만 바꿉니다.
세 가지 실험군.
| 실험군 | 시간 정보 | 위치 인코딩 |
|---|---|---|
| A | 를 원시 입력 특성으로 사용 | 일반 학습 위치 임베딩 |
| B | 인코딩 외에는 없음 | ContinuousTimeEncoding(학습 가능한 시간 척도) |
| C | 를 특성으로 사용하고 연속 시간 인코딩도 사용 | ContinuousTimeEncoding |
보고할 내용.
- 클래스별 F1이지 정확도가 아닙니다. 데드존 라벨에서는 FLAT 클래스가 지배하며, 정확도가 무의미한 이유는 DeepLOB 글이 설명한 것과 같습니다.
- 신뢰구간입니다. 서로 다른 시드로 반복 실행해 산출하세요. 신뢰구간 없이 실험군 사이의 F1 0.4포인트 차이는 아무 의미가 없습니다.
- 학습된 시간 척도입니다. 학습 후
torch.exp(model.time_encoding.log_timescales)를 출력하세요. 어디에 정착하는지가 실험에서 가장 흥미로운 수치이며 한 줄이면 얻을 수 있습니다. - 실험군별 하드웨어와 실제 경과 시간 비용입니다. IPC 세금 방식으로 공개된 하드웨어에서 N회 중앙값을 측정하세요. B의 추론 시간이 A의 3배인데 F1은 일부 포인트만 높다면 그것이 답입니다.
부정적 결과가 있으면 보고하세요. “30일간의 BTC tick에서 연속 시간 인코딩은 를 특성으로 넣는 것보다 아무것도 얻지 못했다”는 아무도 측정하지 않은 세 아키텍처의 설문보다 유용합니다. 정교하게 구성한 엣지가 정직한 검증에서 증발한다는 이 블로그의 일반적 발견과도 일치합니다.
현재 위치

해결되지 않은 질문은 실제입니다. 불규칙하게 간격을 둔 이벤트를 입력받는 시퀀스 모델은 여전히 시간보다 위치를 인코딩합니다. 스프레드 모델링의 Transformer 인코더를 포함한 이 블로그의 기존 내용도 일반 학습 위치 임베딩을 사용합니다. 연속 시간 인코딩, ODE-RNN 잠재 상태, 이벤트 가중 어텐션은 이를 고치는 세 가지 방법입니다.
빠진 것은 고치는 일이 정말 중요한지 보여주는 증거입니다. 세 방법 모두 현재는 발견이 아니라 주제입니다. 결정적인 측정은 고정 인코더와 고정 퍼지 분할에서 세 실험군을 절제하고, 신뢰구간이 있는 클래스별 F1과 학습된 시간 척도를 보고하는 것입니다. 아직 실행되지 않았습니다.
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.