같은 1나노초에 실행 기록 114건: Hawkes 우도는 어디서 잘못 계산될까?
NASDAQ ITCH 주문 이벤트를 정리하다가, 평범한 ‘한 줄 읽을 때마다 Hawkes 상태를 갱신하는’ 코드에 그대로 넣기 어려운 숫자를 만났다. 114다.
2019년 7월 30일 AAPL 정규장 데이터에는 동일한 나노초 타임스탬프에 execution 메시지 114건이 기록된 배치가 있다. 131건과 187건 배치도 각각 한 번 나온다. 이날 실행 계열 메시지 49,172건 중 30,080건(61.17%)은 다른 기록과 나노초 타임스탬프가 같았다. 동시각 기록은 흔하지만 100건이 넘는 배치는 극단적인 꼬리다.
여기서 실행 이벤트란 연구에서 E/C로 묶은 메시지 행이다. 114개의 서로 다른 주문도 아니고, 114건의 체결이 물리적으로 정확히 동시에 일어났다는 증거도 아니다.
문제는 행 순서가 모델의 인과 순서를 몰래 결정한다는 것이다. 기록된 시각 차이가 0이어도 행마다 여기(excitation)를 갱신하면 114번째 행은 앞의 113개 행에 영향을 받는다. 비대칭 유형별 여기 행렬이 있으면 파일 내부의 행 배열만 바뀌어도 점수가 달라질 수 있다. 동일한 시각 배치 내부에서 어떤 선후관계를 가정할 수 있을까?
그래서 피팅 전에 pre-batch 규약을 고정했다. 종목과 정수 나노초 시각으로 배치를 만들고, 배치 직전 이력으로 전체 배치를 평가한 다음 실제 유형별 개수만큼 이력을 한 번에 갱신한다. 거래소의 메시지 전송 순서는 의미 있는 정보지만, 별도 모델 없이 나노초보다 정밀한 물리적 시간으로 간주하지 않는다.
나노초 동률은 흔하지만 114건은 극단적인 꼬리
Nasdaq TotalView-ITCH 5.0 공식 명세는 타임스탬프를 자정 이후 나노초로 정의한다. E는 Order Executed, C는 Order Executed With Price이며 동일 주문 참조에 여러 부분 실행 메시지가 있을 수 있다.
연구용 이벤트는 add=A/F, cancel_delete=X/D, execution=E/C 세 종류다. C의 Printable=Y와 Printable=N을 둘 다 포함한다. 출력 가능한 체결만 집계한 것이 아니다. P(비표시 주문 거래)와 Q(교차 거래)는 제외한다. 이는 Nasdaq 전체 체결의 보편적인 분류가 아닌 연구자가 채택한 이벤트 정의다.
다음은 2019-07-30 AAPL, 미국 동부 거래소 시각 09:30–16:00 전체 정규장에 대한 저장된 내부 집계다.
| 지표 | 관측값 |
|---|---|
execution 메시지 기록 수 | 49,172 |
| 서로 다른 나노초 타임스탬프 수 | 29,379 |
| 여러 기록이 있는 타임스탬프 수 | 10,287 |
| 같은 시각을 공유하는 기록 수 | 30,080 (61.17%) |
| 동시각 배치 크기의 중앙값 / 최빈값 | 2건 / 2건 |
| 크기가 2–3건인 동시각 배치 | 8,439 (동시각 배치의 82.04%) |
| 정확히 114건인 타임스탬프 배치 | 1 |
| 더 큰 131건, 187건 배치 | 각각 1 |
이전에 사용한 09:30–11:00 여러 거래일 공통 구간과 혼동하면 안 된다. 저장된 히스토그램으로는 배치 크기만 알 수 있고, 114개 메시지 각각의 주문 정체나 미세한 발생 순서는 복원할 수 없다.
전체 29,379개 타임스탬프당 평균은 1.67건이다. 동시각 배치 10,287개만 보면 평균 2.92건, 중앙값과 최빈값 모두 2건이다. 114건은 동시각 배치 평균의 약 39배이며, 114건 이상은 114·131·187건 배치 세 개뿐이다. 큰 배치로 오류를 드러낼 수는 있어도 전형적인 규모라고 할 수 없다.
동시각 비율 61.17%만 보고 매개변수가 식별 불가능하다고 단정할 수 없다. pre-batch 점수는 배치 내부의 관측 불가능한 미세 간격을 이용하지 않으며, 배치 간 간격과 유형 조합이 식별에 기여한다.
같은 타임스탬프에서 누가 먼저 누구를 흥분시키는가?
3유형 지수 커널 Hawkes의 조건부 강도를 다음과 같이 둔다.
lambda_k(t-) = mu_k(t)
+ sum_j [alpha[k,j] / tau
* sum_{u<t} m_j(u) * exp(-(t-u)/tau)]
k는 영향을 받는 대상 유형, j는 영향의 출처다. m_j(u)는 시각 u에 기록된 출처 유형의 실제 건수이며, 행렬 방향은 대상 × 출처다. mu_k의 단위는 이벤트/초, tau는 초, alpha[k,j]는 무차원의 적분 여기량이다. 따라서 즉시 커널 계수는 alpha[k,j]/tau(1/초)이며, 커널 적분에는 alpha가 등장한다.
핵심은 **u<t**라는 엄격한 조건이다. 현재 배치는 자신의 점수를 계산하는 이력에 포함시키지 않는다. 같은 종목·시각의 이벤트 로그 항은 다음과 같다.
event_term(t) = sum_k m_k(t) * log(lambda_k(t-))
모든 lambda_k(t-)를 같은 과거 이력으로 계산하고, m_add, m_cancel_delete, m_execution 실제 개수로 점수를 합산한 뒤 이력을 한 번 갱신한다. 114건을 한 건으로 압축하면 안 된다. 합성 회귀 테스트는 배치 행 순서 바꾸기, 종목별 이력 분리, 거래일 간 초기화까지 확인해야 한다.
전송된 행 순서로 점수를 매기는 것도 별도의 관측 가정이라면 가능하지만, 동일한 우도의 단순한 코드 최적화로 소개할 수는 없다.
114건의 인공 예제로 계산 차이를 드러내기
이는 실제 AAPL 추정치가 아닌 Python 표준 라이브러리 교육용 반례다. 세션 시작점에 동일 유형 114건, 이전 여기 이력 0, mu=5건/초, alpha=0.6, tau=1초, 시작점 이후 1초의 관측 구간을 가정한다.
import math
m, mu, alpha, tau, horizon = 114, 5.0, 0.6, 1.0, 1.0
# 전체 배치를 이전 강도로 평가
pre_batch = m * math.log(mu)
# 반례: 같은 시각 앞 행이 뒤 행을 여기했다고 가정
row_ordered = math.fsum(
math.log(mu + alpha * i / tau) for i in range(m)
)
# 전체 우도는 관측 구간의 강도 적분을 빼야 함
baseline_area = mu * horizon
excitation_area = alpha * m * (-math.expm1(-horizon / tau))
compensator = baseline_area + excitation_area
print(f"prebatch_event_log_term: {pre_batch:.6f}")
print(f"row_ordered_event_log_term: {row_ordered:.6f}")
print(f"artificial_excess: {row_ordered - pre_batch:.6f}")
print(f"one_second_compensator: {compensator:.6f}")
print(f"one_second_full_LL: {pre_batch - compensator:.6f}")
격리된 Linux 환경의 Python 3.13.5로 실제 실행했으며 종료 코드는 0이었다.
prebatch_event_log_term: 183.475922
row_ordered_event_log_term: 396.770155
artificial_excess: 213.294233
one_second_compensator: 48.237046
one_second_full_LL: 135.238876
같은 시각 안에서 앞 행이 뒤 행을 여기하도록 허용하기만 해도 이벤트 로그 항이 213.294233 nats 늘어난다. 그런데 두 방식 모두 전체 배치 평가 후에는 정확히 114건의 동일한 이력을 보존한다. 시간 길이가 0인 배치 다음 1초의 보상적분은 48.237046 nats로 같다. 행 순서 방식의 전체 LL은 348.533109, pre-batch는 135.238876이고 차이는 그대로 213.294233 nats다.
이는 실제 예측 성능의 향상도, 동일한 연속시간 관측모형에서 얻은 두 엄밀한 우도의 비교도 아니다. pre-batch가 후속 여기 효과를 버리는 것은 아니다. 1초 뒤 114건이 자기 유형 강도에 주는 기여는 이 인공 설정에서 114 × 0.6 × exp(-1) ≈ 25.163건/초다.
이벤트 항을 맞춰도 전체 우도는 아니다
발생 시점의 log(lambda)만 더하고 로그 우도라 부르면 중요한 항을 빠뜨린다. 점과정의 전체 로그 우도는 유효한 관측 구간 전체의 강도 적분을 빼야 한다.
LL = sum_k sum_batches m_k(t) * log(lambda_k(t-))
- sum_k integral_session lambda_k(t) dt
피팅 전에 장중 기준 강도를 09:30–16:00의 390분을 덮는 5분 간격 78개 구간으로 고정했다. 구간과 세션의 반개구간 겹침을 정확히 합산하며 지수 커널에는 각 배치의 실제 multiplicity를 반영한다.
baseline_area[k] = sum_r mu[k,r] * overlap_seconds(r, session)
excitation_area[k] = sum_j sum_{u in session}
alpha[k,j] * m_j(u) * (1 - exp(-(end-u)/tau))
작은 시간 차이의 부동소수점 소거를 줄이기 위해 -expm1(-(end-u)/tau)를 사용한다. 1초 반례의 기준 강도 적분은 5, 여기 적분은 약 43.237046, 합계 보상적분은 48.237046이다. 따라서 전체 pre-batch LL은 135.238876이지 이벤트 로그 항 183.475922가 아니다.
실제 배치 히스토그램으로 어디까지 시나리오를 계산할 수 있나
114건 인공 예제는 계산 메커니즘을 보여준다. 이어서 이미 보관된 2019-07-30 AAPL 전체 정규장 execution 배치 크기 히스토그램을 인공 매개변수 감도 시나리오의 입력으로 사용했다. 원본 ITCH 데이터를 다시 다운로드하거나 실제 Hawkes를 피팅하지 않았다.
실행 유형의 양의 자기 여기만 고려하고 각 배치 직전 강도 lambda^-가 인위적으로 설정한 mu>0 이상이라면, 배치 크기 m의 행 순서 자기 여기 점수 증가량은 다음 조건부 상한을 만족한다.
0 <= Delta(m) = sum_{i=0}^{m-1} log(1 + alpha*i/(tau*lambda^-))
<= sum_{i=0}^{m-1} log(1 + alpha*i/(tau*mu))
오른쪽은 같은 유형의 조건부 시나리오 상한일 뿐 3유형 Hawkes 전체 LL 오차 상한이 아니다. 실제 배치 직전 강도와 같은 시각의 다른 유형은 반영하지 않는다.
mu=5건/초로 두고 저장된 배치 크기의 실제 출현 횟수를 가중하면:
| 가정한 alpha | tau=0.1초 | tau=1초 | tau=10초 |
|---|---|---|---|
| 0.1 | 8,392.65 | 1,436.32 | 178.46 |
| 0.3 | 17,088.26 | 3,472.79 | 500.41 |
| 0.6 | 25,186.19 | 5,837.61 | 928.45 |
값의 단위는 nats이며 alpha와 tau는 인공 가정이다. 중간 시나리오 mu=5, alpha=0.3, tau=1의 누적 조건부 상한은 3,472.79 nats이고, 49,172건으로 나누면 0.0706 nats/기록이다. 동일한 조건에서 2건과 3건의 단일 배치 상한은 각각 0.0583, 0.1716 nats다. 인공 강도 하한 mu를 1 또는 20으로 바꾸면 합계는 11,044.42, 1,125.70 nats가 된다.
9개 인공 조합의 상한은 178.46–25,186.19 nats, 즉 0.00363–0.51221 nats/기록이다. 이를 실제 AAPL 로그 우도 오차나 추정된 시장 매개변수로 해석해서는 안 된다.
중간 시나리오에서 가장 큰 배치를 제거한 효과는 다음과 같다.
| 제외한 최대 배치 | 상한 감소량(nats) | 기존 상한 대비 |
|---|---|---|
| 187건(top 1) | 321.54 | 9.26% |
| 187, 131, 114건(top 3) | 665.62 | 19.17% |
| 상위 10개 배치 | 1,123.57 | 32.35% |
가장 큰 세 배치의 기록은 432건뿐이지만 이 인공 조건부 상한의 거의 5분의 1을 차지한다. 기존 히스토그램은 그 배치의 장중 발생 시각을 남겨 두지 않았으므로 개장/마감 집중이나 매칭 방식·타임스탬프 기록 구조를 원인으로 단정할 수 없다.
이 인공 시나리오와 집계 확인에는 13/13개의 별도 테스트가 통과했으며, 99개 합성 연구 테스트와 별개 실행이다.
테스트 99개가 실제로 증명한 것
실제 AAPL 집계가 확인하는 것은 동시각 execution 기록의 존재와 규모다. 알려진 매개변수의 합성 실험은 배치 점수, 보상적분, 이력 갱신이 정한 계산 규약대로 동작하는지를 점검한다. 서로 다른 증거 범위다.
| 합성 테스트 범위 | 검증 내용 |
|---|---|
| 배치 점수(10개) | 같은 나노초의 공통 강도, multiplicity 유지, 입력 순서 불변 |
| 전체 우도(33개) | 기준 강도와 지수 커널 보상적분, 해석/수치 적분 대조 |
| M0/M1/M2 계약(34개) | 동질 Poisson, 78구간 계절성 NHPP, 세 유형 Hawkes의 알려진 매개변수 평가 |
| 합성 이벤트 스트림(22개) | 고정 시드 Ogata thinning, 독립 이력 합산, 두 프로세스 재현성 |
| 총계 | 99/99 연구 슬라이스 테스트 통과(Python 3.13.5, 격리 Linux x86_64) |
동시각 스트레스 픽스처는 총 209개의 합성 이벤트와 네 개의 시간 그룹으로 구성했다. 시작점에 Add 90개, +1초 Execution 114개, +2초 Cancel/Delete 3개, +3초에는 Add와 Execution이 하나씩이다. 크기는 90/114/3/2. 독립 작성한 직접 이력 합산/적분 경로와의 최대 절대 차이는 약 1.42e-14였고 행 순서를 뒤집어도 점수는 같았다.
Ogata thinning으로 별도 생성한 연속시간 합성 스트림은 각각 285건, 265건이었다. 독립 우도 비교의 최대 차이는 약 7.11e-15다. 이것이 자연스럽게 정확한 동시각 114건을 만들어낸 것은 아니다. 대규모 동률 배치는 의도적인 스트레스 입력이다.
99개 테스트는 격리된 Linux x86_64, Python 3.13.5에서 과거 수행했다. 현재 편집 브랜치 HEAD의 GitHub Actions 성공을 뜻하지 않는다. 실제 장중 매개변수 피팅, 표본외 점수, 계절성 NHPP 대비 Hawkes의 우월성은 아직 확인하지 않았다.
엄밀한 관측 밀도가 아닌 작업 우도
단순한 연속시간 점과정은 완전히 같은 도착 시각이 양의 확률로 발생하지 않는 것이 보통이다. ITCH 나노초 표시는 기록/인코딩된 시간이고, 송신 순서가 나노초 이하의 물리적 선후관계를 알려 주는 것은 아니다. 따라서 이 점수는 **pre-batch 작업 우도(또는 유사 우도 점수)**이며, 양자화된 타임스탬프 관측치의 엄밀한 결합 밀도를 도출한 결과가 아니다. 구성 밀도와 의존성 처리를 정의하지 않고 composite likelihood라고 불러도 안 된다.
고정 시간 구간의 건수에 관심이 있다면 이산시간 계수 모형, 구간 내 알려지지 않은 도착을 추정하려면 구간 검열 모형이 대안이 될 수 있다. 소규모 배치의 잠재적 순서를 바꿔 보는 것은 민감도 분석이지 보편적인 코드 대체가 아니다.
동일한 데이터·그룹화·관측모형에서 두 점수가 매개변수와 무관한 동일한 상수 C(D)를 생략했다면 (LL_A+C(D))-(LL_B+C(D))=LL_A-LL_B로 차이에서 상수가 상쇄된다. 일부 이산 계수 모형의 -sum log(m!)가 그 예가 될 수 있다. 절대 LL까지 동일해지는 것은 아니고, 계승 항을 추가한다고 pre-batch가 엄밀한 관측 우도가 되지도 않는다. 그룹화나 시간 규약이 바뀌면 상수 동일성도 보장되지 않는다.
비음수 여기 매개변수의 경계와 귀무가설하 감쇠 매개변수의 식별 불가능성 때문에 고전적인 카이제곱 우도비 검정도 곧바로 적용할 수 없다.
동시각 타임스탬프의 잔차는 어떻게 점검하나?
동일한 나노초의 114건을 114개의 순차 도착으로 간주하면 도착 간격 0이 대량으로 생겨 시간 재척도 잔차 진단을 왜곡한다. 사전 규약은 이벤트 유형별 고유 타임스탬프를 기준으로 그룹 잔차를 만들고 배치 multiplicity를 따로 기록하는 것이다. QQ 플롯, 잔차 자기상관, KS 거리는 부적합을 설명하는 지표로만 사용한다. 동률이 없는 연속 도착을 전제로 한 KS p 값을 공식 추론으로 사용하지 않는다. 향후 bootstrap/순열 검정을 한다면 귀무가설, 생성 규칙, 배치 의존성을 보존할 재표본 단위를 먼저 정해야 한다.
먼저 입력 파일의 행 순서가 모델의 인과적 시각으로 바뀌지 않도록 한다.
공개 재현 자료: Python 스크립트, 히스토그램, 테스트와 SHA-256 검증.