← 기사 목록으로
July 16, 2026
5분 소요

체결 시뮬레이션: 종가 체결 환상에서 큐 인식 현실까지의 사다리

체결 시뮬레이션: 종가 체결 환상에서 큐 인식 현실까지의 사다리
#체결 시뮬레이션
#backtest
#지정가 주문
#큐 포지션
#부분 체결
#market making
#체결(execution)
#시장 미시구조

당신의 백테스트에는 두 개의 모델이 들어 있습니다. 알파의 모델과 체결의 모델입니다. 대부분의 사람은 노력의 95%를 첫 번째에 쏟고, 두 번째는 그저 쓰고 있는 프레임워크가 무엇이든 거기서 물려받습니다. 이건 앞뒤가 바뀐 것입니다. 정직한 체결 모델을 가진 평범한 시그널은 평범하지만 진짜인 PnL 추정치를 만들어냅니다. fill_price = candle.close를 가진 훌륭한 시그널은 그 무엇의 추정치도 아닌 숫자를 만들어냅니다 - 그것은 당신이 한 번도 검토한 적 없는 가정의 출력값일 뿐입니다.

유동성 높은 종목의 테이커 전략에서 체결 모델은 보정항입니다. 그러나 대기 중인 지정가 주문이 관여하는 모든 것 - 마켓 메이킹, 패시브 진입, post-only 리베이트 수확 - 에서 체결 모델은 곧 전략 그 자체입니다. 체결이 되는지, 언제, 어떤 수량으로, 그리고 이후 어떤 가격 움직임을 조건으로 체결되는지가 PnL의 크기뿐 아니라 부호까지 결정합니다.

백테스트-라이브 정합성에서 우리는 백테스트-라이브 괴리의 전체 분류 체계를 정리하고 체결 괴리에 심각도 5/5 - 최악의 등급 - 를 부여했습니다. 그 글은 체결 시뮬레이션에 세 가지 대략적인 정확도 수준을 제시하고 넘어갔습니다. 이 글은 그 심화 편입니다. 완전한 사다리를, 단계마다 하나씩, 주문 생애주기를 상태 기계로 다루고, 실제로 계산할 수 있는 체결 확률 경계와, "수익성 있는" 메이커 전략이 사다리의 어디에서 죽는지를 정확히 보여주는 측정 실험까지 담았습니다.

사다리

다섯 단계 체결 시뮬레이션 정밀도 사다리

각 단계는 더 많은 데이터와 더 많은 코드를 요구하며, 각 단계는 특정한 체계적 편향을 제거합니다. 이 단계들은 단순히 비용순이 아니라, 무엇을 틀리는지에 따라 정렬되어 있습니다.

0단계: 종가 체결

fill_price = bar.close

주문은 즉시, 전량, 시그널을 생성한 봉의 종가에 체결됩니다. 시그널은 바로 그 종가로부터 계산되었으므로, 당신은 가격이 찍힌 시점에 존재하지도 않았던 데이터로 거래하고 있는 것입니다. 체결이라는 옷을 입은 룩어헤드 편향입니다. 회전율이 있는 전략은 무엇이든 여기서 좋아 보입니다.

1단계: 다음 봉 시가

fill_price = next_bar.open

봉 데이터 기반 테이커 로직에 대한 최소한의 정직한 모델입니다. 시그널은 봉 tt에서 계산되고, 체결은 봉 t+1t+1의 첫 관측 가격에서 일어납니다. 이는 룩어헤드를 제거하지만, 여전히 spread 제로, 임팩트 제로, 시가에서의 무한 유동성, 100% 체결 확실성을 가정합니다. 지정가 주문의 경우 이는 터치 체결 로직으로 퇴화합니다(왜 그것이 독인지는 아래에서 더 다룹니다).

2단계: spread + 고정 slippage

half_spread = mid * spread_bps / 2e4
slip        = mid * slippage_bps / 1e4
fill_price  = mid + side * (half_spread + slip)   # side: +1 buy, -1 sell

이제 모든 테이커 거래는 spread의 절반과 캘리브레이션된 상수를 지불합니다. 이것이 고회전율 전략이 백테스트에서 죽을 수 있는 첫 번째 단계입니다 - 바로 그것이 핵심입니다. 잔차 오차는: slippage는 상수가 아니라는 점입니다. 그것은 표시된 depth 대비 주문 크기에 비례해 커지며, 당신의 전략이 가장 거래하고 싶어하는 바로 그 순간에 폭발합니다. 고정 5 bps는 여러 국면에 걸친 평균값이지만, 당신의 전략은 평균적인 국면에서 거래하지 않습니다.

3단계: 시장가 주문에 대한 L2 depth-walk

호가창 스냅샷이 있으면 slippage를 추측하는 것을 멈추고 계산하게 됩니다. 크기 QQ의 시장가 매수는 매도 호가 측을 레벨별로 걸어 올라가고, 체결 가격은 소모된 레벨들에 걸친 거래량 가중 평균입니다:

Pfill(Q)=1Qipimin ⁣(qi,  Qj<iqj)P_{\text{fill}}(Q) = \frac{1}{Q}\sum_{i} p_i \cdot \min\!\left(q_i,\; Q - \textstyle\sum_{j<i} q_j\right)

def depth_walk(levels: list[tuple[float, float]], qty: float) -> tuple[float, float]:
    """levels: [(price, size), ...] sorted best-first. Returns (vwap, filled_qty)."""
    remaining, cost = qty, 0.0
    for price, size in levels:
        take = min(size, remaining)
        cost += take * price
        remaining -= take
        if remaining <= 0:
            break
    filled = qty - remaining
    return (cost / filled if filled > 0 else float("nan"), filled)

두 가지 보정이 이를 실질적으로 더 정직하게 만듭니다. 첫째, latency: 당신의 결정 타임스탬프로부터 Δt\Delta t 이후에 호가창이 어떤 상태였는지를 기준으로 걸어야 합니다. 여기서 Δt\Delta t는 당신이 측정한 시그널-투-거래소 latency입니다 - 당신이 본 호가창은 당신이 때린 호가창이 아닙니다. 둘째, 부분적 마켓 체결: 당신의 지정가 통과 가격 이내의 표시된 depth가 QQ보다 작으면, 모델은 부분 체결을 반환하고 대기 중인 잔량을 남겨야 하며, 이는 문제를 아래의 상태 기계로 넘깁니다.

3단계의 잔차 오차는 임팩트와 리필입니다: 당신은 호가창을 정적인 대상으로 소모하지만, 실제 호가창은 부분적으로 다시 채워지고(그리고 실제 상대방은 반응합니다). 최상위 호가 depth의 몇 퍼센트 미만인 클립 크기에서 이 오차는 작지만, 더 큰 클립에서는 그 위에 임팩트 모델(Almgren-Chriss 2001)을 얹어야 합니다.

4단계: 지정가 주문에 대한 확률적 큐 포지션 체결

0-3단계는 "내 공격적 주문이 어떤 가격에 체결되는가"에 답합니다. 4단계는 더 어려운 질문에 답합니다: 내 패시브 주문이 애초에 체결되기는 하는가 - 그리고 이것이 메이커 전략의 가격을 매길 수 있는 유일한 단계입니다. 가격 pp에 대기 중인 지정가 주문은, pp에서의 누적 거래량이 그 앞에 있던 큐 물량을 초과할 때 체결됩니다. 그러려면 당신이 직접 관측할 수 없는 FIFO 큐 안에서의 당신의 위치를 추적해야 합니다.

큐 포지션 추정 기계 장치 - 초기 위치, 거래 대 취소에 따른 갱신 규칙, 관측되지 않은 취소를 배분하기 위한 f(x)f(x) 확률 계열 - 는 우리가 벽 안쪽의 큐에서 구축한 기본 요소입니다. 여기서 다시 유도하지는 않겠습니다. 시뮬레이터는 그것을 소비합니다. 4단계가 추가하는 것은 그 추정치 위에 얹는 체결 결정 규칙이며, 이는 아래 체결 확률 절에서 다룹니다.

이 단계가 왜 중요한지는 문헌에서 정량화되어 있습니다: Moallemi와 Yuan(2016)의 "A Model for Queue Position Valuation in a Limit Order Book"은, 틱이 큰 종목에서 큐 앞자리 포지션 대 큐 뒷자리 포지션의 경제적 가치가 half-spread와 견줄 만하다 - 마켓 메이킹 전략의 전체 이론적 엣지와 동일한 규모 - 는 것을 보여줍니다. 큐 포지션을 무시하는 체결 모델은 메이커의 PnL을 잘못 추정하는 것이 아닙니다. 그것은 아예 다른 전략의 PnL을 추정하는 것입니다.

5단계도 있습니다 - 시장이 당신의 주문에 반응하는 완전한 에이전트 기반 시뮬레이션입니다(Huang, Lehalle, Rosenbaum 2015 의미의 큐 반응 모델; Byrd 등 2020의 ABIDES 같은 멀티 에이전트 프레임워크). 역사적 리플레이는, 큐 인식형이라 하더라도, 당신의 주문이 다른 모든 사람의 행동에 대해 아무것도 바꾸지 않는다고 가정합니다. 그 가정은 리테일 규모에서는 괜찮지만, 당신의 호가가 해당 레벨의 눈에 띄는 비중이 될수록 점점 틀리게 됩니다. 5단계는 여기서 범위를 벗어나지만, 사다리가 4단계에서 끝나지 않는다는 점은 알아두십시오.

상태 기계로서의 부분 체결

0-2단계는 주문을 함수 호출인 척할 수 있습니다: 제출, 가격 받기, 끝. 3단계 이상부터 주문은 생애주기를 가진 프로세스이며, 시뮬레이터는 그것을 상태 기계로 모델링해야 합니다 - 그렇지 않으면 가장 중요한 케이스들을 조용히 잘못 처리하게 됩니다.

큐 포지션 주석이 달린 주문 생애주기 상태 기계

enum OrderState {
    PendingNew,                                  // sent, not yet acked (latency window)
    Resting     { remaining: f64, q_ahead: f64 }, // in book, queue position estimated
    PartialFill { remaining: f64, q_ahead: f64 }, // some qty done, rest still queued
    PendingAmend,                                 // amend in flight
    PendingCancel,                                // cancel in flight
    Filled,
    Canceled    { filled_qty: f64 },              // may be partially filled at cancel time
    Rejected,
}

전이(transition)들이 경제학을 담고 있습니다:

  • PendingNewResting: 주문은 결정 시점이 아니라 ack 시점에 존재하는 모든 것의 뒤에 큐에 합류합니다. 당신의 큐 포지션은 t+Δtackt + \Delta t_{\text{ack}} 시점의 레벨 물량으로 시드됩니다. 결정 시점에 시드하는 시뮬레이터는 큐 우선순위를 체계적으로 과대평가합니다 - 정확히 당신의 latency 윈도 동안 도착한 물량만큼이며, 이는 버스트 동안의 대부분의 물량입니다.
  • RestingPartialFill: 당신의 레벨에서 앞선 큐보다 큰 거래가 발생하면 당신은 부분적으로 체결됩니다. 잔량은 (이제 큐 앞자리가 된) 위치를 유지합니다. 부분 체결은 노이즈가 아니라 정보입니다: 1.0 중 0.3을 체결받고 가격이 튕겨 나가는 것을 지켜보는 것은 전량 체결과는 다른 PnL 이벤트이며, 메이커의 인벤토리 프로세스는 이런 조각들로 만들어집니다.
  • RestingPendingAmendResting: 함정입니다. 사실상 모든 크립토 거래소에서 amend는 취소/재등록입니다 - Binance의 cancelReplace는 이중 체결에 대해서는 원자적이지만 큐 맨 뒤에 새로운 주문 ID를 반환합니다. 네이티브 modify 시맨틱을 가진 거래소(CME Globex)조차 수량 감소에 대해서만 시간 우선순위를 보존합니다. 가격 변경이나 수량 증가는 그것을 포기하게 만듭니다. 따라서 시뮬레이터에서는: 모든 가격 amend는 q_ahead를 현재 레벨의 전체 물량으로 리셋합니다. 500ms마다 다시 페깅하는 호가 엔진은 "호가를 유지"하는 것이 아닙니다 - 그것은 영원히 큐 맨 뒤로 재진입하는 것이며, 그 현실적 체결 프로파일은 거의 순수한 역선택입니다. Moallemi-Yuan 결과와 결합하면: 재호가에는 가격이 있고, 그 가격은 당신의 큐 포지션입니다.
  • PendingCancelPartialFillCanceled: 취소에도 latency가 듭니다. 호가를 거두기로 결정한 시점과 취소가 매칭 엔진에 도달하는 시점 사이의 윈도에서, 당신은 여전히 체결될 수 있으며 - 그리고 그 체결들은 당신이 받게 될 최악의 체결입니다. 왜냐하면 당신이 호가를 거두려던 이유가 바로 시장이 당신을 짓밟으려던 참이었기 때문입니다. 취소 latency가 없는 시뮬레이터는 당신의 이력에서 정확히 가장 유독한 체결들을 삭제해 버립니다.

상태 기계는 또한 시뮬레이터의 회계를 정직하게 만드는 요소입니다: 수수료는 체결 이벤트마다 누적되고, 인벤토리는 체결 이벤트마다 갱신되며, 상태별 체류 시간 통계(주문이 체결되기까지 vs 취소되기까지 얼마나 대기하는가)는 라이브 로그와 직접 비교 가능해집니다 - 이것이 바로 마지막의 캘리브레이션 루프가 먹고 사는 재료입니다.

지정가 체결 확률: 세 가지 모델과 하나의 구간

가격 pp에 대기 중인 주문(가령 매수 호가)이 주어졌을 때, 시뮬레이터는 언제 체결을 선언하는가? 정직함이 커지는 순서로 세 가지 결정 규칙이 있습니다:

1. 터치 체결(순진한 방식). 가격이 당신의 레벨을 터치하면 체결: lowtp\text{low}_t \le p. 이것은 최초 통과 시간(first-passage-time) 규칙이며, 그 실패는 사반세기 전에 측정되었습니다: Lo, MacKinlay, Zhang(2002)의 "Econometric Models of Limit-Order Executions"(Journal of Financial Economics 65)는 실제 지정가 주문 데이터에 생존 모델을 적합시키고, 최초 통과 시간으로 구성된 가상의 체결은 "실제 지정가 주문 체결의 매우 나쁜 대리변수"라고 결론짓습니다. 실패 양상은 구조적입니다: 가격이 당신의 레벨을 터치하고 튕길 때, 그 터치는 큐의 앞부분 - 당신보다 먼저 호가를 낸 트레이더들 - 을 소모한 것입니다. 터치 체결은 당신에게 그들의 체결을 수여합니다. 더 나쁜 것은, 정확히 좋은 체결들을 수여한다는 것입니다(터치-앤-바운스는 메이커에게 수익성 있는 시나리오입니다). 반면 당신의 실제 체결 집합은 터치-앤-런스루 - 역선택된 것들 - 쪽으로 치우쳐 있습니다.

2. 트레이드-스루(보수적 경계). 가격이 당신의 레벨을 엄격하게 관통해 거래될 때만 체결: lowt<pϵ\text{low}_t < p - \epsilon, 또는 틱 데이터에서는 pp에서의 누적 거래량이 전체 레벨을 초과할 때. 가격이 관통해 거래되었다면 pp의 큐 전체가 소모된 것이므로, 당신은 위치와 무관하게 체결되었습니다. 이것은 당신이 받지 못했을 체결을 절대 수여하지 않습니다. 그 편향은 터치 체결의 정확히 거울상입니다: 큐가 완전한 관통 없이 당신의 위치까지 소진된 모든 체결을 당신에게서 거부하며, 그것이 실제로 수여하는 체결은 불균형하게 런스루(역선택된) 것들입니다. 트레이드-스루 하의 메이커 백테스트는 추정치가 아니라 스트레스 테스트입니다.

3. 큐 소진 추정. 거래 테이프에서 당신 가격의 누적 거래량 MtM_t를 추적하고, L2 델타에서 추론한 취소 물량 CtC_t를 추적하십시오. 당신의 추정 앞선 큐:

Q^ahead(t)=max ⁣(Q0MtϕCt,  0)\hat{Q}_{\text{ahead}}(t) = \max\!\Big(Q_0 - M_t - \phi \cdot C_t,\; 0\Big)

여기서 Q0Q_0는 (ack 시점) 주문 진입 시의 레벨 물량이고, ϕ[0,1]\phi \in [0,1]는 당신 앞에서 나온 것으로 가정되는 취소의 비율입니다 - 그 원리에 기반한 형태(f(x)f(x) 계열)는 큐 포지션 글에서 유도됩니다. 체결은 Q^ahead=0\hat{Q}_{\text{ahead}} = 0일 때 시작되며, 당신의 체결 수량은 그것을 초과하는 거래량으로, 부분 체결이 자연스럽게 산출됩니다:

filled(t)=min ⁣(S,  max(MtQ0+ϕCt,0))\text{filled}(t) = \min\!\Big(S,\; \max(M_t - Q_0 + \phi\, C_t,\, 0)\Big)

ϕ=1\phi = 1(모든 취소가 당신 앞에서)로 설정하면 이 모델의 낙관적 끝이 되고, ϕ=0\phi = 0이면 비관적 끝이 됩니다. 희소한 L2 스냅샷만 있고 해당 레벨의 거래 테이프가 없을 때 - 100ms로 스로틀링된 크립토 피드에서 흔한 경우 - 에는 모델 기반 사전분포로 물러날 수 있습니다: Cont, Stoikov, Talreja(2010)의 "A Stochastic Model for Order Book Dynamics"(Operations Research 58)는 각 가격 레벨을 birth-death 큐로 모델링하고, 라플라스 변환을 통해, 현재 큐 크기를 조건으로 매수 호가의 주문이 mid가 움직이기 전에 체결될 확률을 계산합니다. 이는 해석적 체결 확률 오라클입니다: 테이프 리플레이에 비하면 조악하지만, 터치 체결보다는 훨씬 낫고, 뜨거운 백테스트 루프 안에서 평가할 만큼 저렴합니다.

구간 규율

세 규칙은 경쟁자가 아니라 하나의 순서입니다:

fillstrade-through    fillsqueue    fillstouch\text{fills}_{\text{trade-through}} \;\subseteq\; \text{fills}_{\text{queue}} \;\subseteq\; \text{fills}_{\text{touch}}

이는 PnL 구간을 유도합니다. 모든 메이커 백테스트를 세 번 돌리고 그 구간을 보고하십시오:

PnLtrade-through    PnLlivePnLtouch\text{PnL}_{\text{trade-through}} \;\le\; \text{PnL}_{\text{live}} \lesssim \text{PnL}_{\text{touch}}

(위쪽 부등식은 근사입니다 - 터치 체결은 전략을 부풀리기만 하는 것이 아니라 오순위(misrank)로 만들 수도 있는데, 반사실적인 좋은 체결을 당신에게 건네주기 때문입니다). 여기서 따라오는 결정 규칙은: 메이커 전략은 구간의 보수적 끝에서 살아남고, 그 구간이 점 추정치가 의미를 가질 만큼 좁을 때에만 배포 가능합니다. +9k터치/9k 터치 / -3k 트레이드-스루를 보이는 전략은 12k폭의구간을가집니다.당신이아는것은시뮬레이터의가정이당신의알파를압도한다는것뿐입니다.+12k 폭의 구간을 가집니다. 당신이 아는 것은 시뮬레이터의 가정이 당신의 알파를 압도한다는 것뿐입니다. +2.1k / +$0.4k를 보이는 전략은 당신에게 진짜인 무언가를 말하고 있습니다.

실험: 하나의 메이커 전략을 사다리 아래로 걸어 내리기

체결 모델 사다리를 따라 단계마다 붕괴하는 메이커 전략 PnL

의도적으로 평범한 메이커 하나를 봅시다 - 최우선 매수/매도 호가에 대칭 호가, 고정 0.05 BTC 클립, ±0.5 BTC로 인벤토리 상한을 두고 상한에서 테이커로 플래튼. BTCUSDT 무기한 데이터 한 달치: 낮은 단계에는 1m 봉, 높은 단계에는 100ms L2 diff에 거래 테이프를 더함; 메이커 수수료 1.0 bps, 테이커 4.0 bps. 모든 단계에서 동일한 시그널 코드(공유 코어, 따라서 유일한 변수는 체결 모델). 아래 숫자는 우리의 대표적인 한 번의 실행 결과입니다 - 당신의 크기는 거래소, 월, 사이즈에 따라 다르겠지만, 형태는 다르지 않을 것입니다:

단계 체결 모델 호가 체결률 체결 수 월 PnL 판정
0 종가 체결 98% 41,200 +$14,800 환상
1 다음 봉 시가 / 1m 봉에서 터치 89% 37,400 +$9,600 latency를 곁들인 환상
2 터치 + 테이커 플래튼에 spread & 고정 slippage 89% 37,400 +$7,100 비용은 모델링, 체결은 여전히 허구
3 + 테이커 플래튼에 L2 depth-walk 89% 37,400 +$6,400 청산은 정직, 진입은 여전히 허구
4a 트레이드-스루(보수적) 21% 8,900 -$3,900 스트레스 경계
4b 큐 소진, ϕ\phi 캘리브레이션 37% 15,600 -$700 최선의 추정
4c 큐 소진, ϕ=1\phi = 1(낙관적) 44% 18,700 +$1,900 상한 끝
라이브 섀도 실행, 같은 월 35% 14,100 -$1,150 현실

표를 위에서 아래로 읽으며 전략이 어디에서 죽는지 보십시오. 그것은 2단계가 아닙니다 - 수수료와 slippage가 26%를 깎아내지만 전략은 여전히 견고하게 수익성 있어 보입니다. 그것은 3단계와 4단계 사이에서 죽으며, 어떤 비용 모델도 포착할 수 없는 이유로 죽습니다: 체결 선택(fill selection). 터치 체결은 37,400건의 체결을 수여했는데 그 대다수는 터치-앤-바운스 - 순수한 spread 포착 - 였습니다. 큐 인식 모델은 그 체결의 58%를 삭제했으며, 삭제된 체결은 불균형하게 수익성 있는 것들이었습니다: 레벨이 가볍게 두드려질 때, 재호가하는 리테일 latency 메이커 앞의 큐가 모든 것을 흡수합니다. 4단계까지 살아남은 체결들은 레벨을 청소하는 스윕 쪽으로 치우쳐 있습니다 - 가격이 이미 당신을 통과해 움직이고 있는 체결들입니다. 체결률은 2.4배 떨어졌고, PnL은 부호가 뒤집혔습니다. 그 비대칭 - 좋은 체결은 잃고, 나쁜 체결은 유지하는 것 - 이 바로 기계적으로 명시화된 역선택이며, 4단계 아래의 모든 단계에서는 보이지 않습니다.

또한 구간이 무엇을 했는지 주목하십시오: [-3,900,+3,900, +1,900]는 캘리브레이션된 추정치 -700과라이브700과 라이브 -1,150을 두고 0을 걸치고 있습니다. 시뮬레이터는 라이브 PnL을 달러 단위까지 못 박지는 못했지만 - 부호, 크기, 그리고 2포인트 이내의 체결률을 맞췄습니다. 그것이 체결 모델의 존재 이유입니다. 1단계 백테스트는 전체 월 총 엣지가 몇천 달러였던 전략에서 라이브 PnL을 $10,750만큼 놓쳤습니다: 체결 모델 오차가 알파 크기의 대략 3배였던 셈입니다. 그래서 이 테제입니다: 당신의 체결 모델은 당신의 알파보다 더 큰 가정입니다.

낮은 단계들에 대한 한 가지 주의사항: 봉 데이터로 살아야 한다면(0-2단계), 최소한 어댑티브 드릴다운으로 봉 내부의 모호성을 해소하십시오 - SL, TP, 또는 호가 레벨이 봉 범위 안에 들어오는 곳에서 1m에서 1s/100ms/트레이드로 드릴다운합니다. 드릴다운은 순서(sequencing) 오차(어느 레벨이 먼저 맞았는가)를 고치지만 오차는 고치지 못합니다. 그것은 이 글의 데이터 해상도 동반 편이지, 4단계의 대체물이 아닙니다.

캘리브레이션 루프: 라이브 체결에 맞춰 닫기

4단계 시뮬레이터에는 자유 파라미터가 있습니다 - ϕ\phi, latency Δt\Delta t, 취소 latency, 레벨 리필 가정. 캘리브레이션되지 않은 상태라면 그것은 그저 모양만 다른 추측일 뿐입니다. 그것을 계측 장비로 바꾸는 루프:

1. 라이브를 전부 로깅하라. 거래소 타임스탬프가 붙은 모든 주문 이벤트: 제출, ack, 각 부분 체결, amend ack, 취소 ack. 여기에 제출 시점의 L2 상태를 더합니다. 이것은 백테스트-라이브 정합성이 자신의 DivergenceMonitor를 위해 요구하는 것과 동일한 로깅 규율입니다 - 체결 모델 캘리브레이션은 그 모니터의 가장 깊은 층입니다.

2. 같은 주문들을 시뮬레이터를 통해 리플레이하라. 기록된 시장 데이터와 기록된 주문 지시(instructions)(체결이 아님)를 시뮬레이터에 넣습니다. 이제 당신은 짝지어진 결과를 갖게 됩니다: 각 라이브 주문에 대해, 시뮬레이션된 운명.

3. 평균이 아니라 분포를, 버킷별로 비교하라. 단일한 전역 체결률 일치는 상쇄하는 오차를 숨길 수 있습니다(조용한 국면에서는 너무 낙관적, 버스트에서는 너무 비관적 - 상쇄되어 "캘리브레이션됨"으로). 그 동인들로 버킷을 나누십시오:

import numpy as np
from scipy.stats import ks_2samp

def calibration_report(pairs, bucket_key):
    """pairs: [{'bucket':…, 'live_filled':bool, 'sim_filled':bool,
                'live_ttf':float|None, 'sim_ttf':float|None}, …]"""
    out = {}
    for b in sorted({p['bucket'] for p in pairs}):
        grp = [p for p in pairs if p['bucket'] == b]
        live_fr = np.mean([p['live_filled'] for p in grp])
        sim_fr  = np.mean([p['sim_filled']  for p in grp])
        live_ttf = [p['live_ttf'] for p in grp if p['live_ttf'] is not None]
        sim_ttf  = [p['sim_ttf']  for p in grp if p['sim_ttf']  is not None]
        ks = ks_2samp(live_ttf, sim_ttf) if len(live_ttf) > 20 and len(sim_ttf) > 20 else None
        out[b] = {
            'n': len(grp),
            'fill_rate_live': live_fr,
            'fill_rate_sim':  sim_fr,
            'fill_rate_gap':  sim_fr - live_fr,      # signed: + means sim optimistic
            'ttf_ks_pvalue':  ks.pvalue if ks else None,
        }
    return out

버킷별로 두 가지 통계: 부호가 있는 체결률 갭(시뮬레이터 빼기 라이브)과 체결된 주문들 사이의 체결까지 시간(time-to-fill) 분포에 대한 KS 검정. 체결까지 시간 비교가 날카로운 쪽입니다 - 시뮬레이터는 체결 시점을 체계적으로 틀리면서도 체결 을 맞출 수 있으며, 이는 이후의 모든 인벤토리 및 역선택 통계를 오염시킵니다. 이것이 바로 Lo-MacKinlay-Zhang의 생존 분석 프레이밍의 교훈입니다: 체결은 사건 발생 시간(time-to-event) 문제이므로, 그렇게 검증하십시오.

4. 식별 가능성 순서로 손잡이를 맞춰라. latency 먼저(ack 타임스탬프에서 직접 측정 - 적합이 아님). 그다음 큐 depth 버킷들에 걸쳐 체결률 갭을 최소화하여 ϕ\phi. 그다음 변동성 국면 버킷들을 확인하십시오: 버스트 버킷에 집중된 지속적인 낙관적 갭은 보통 당신의 시뮬레이터가 ϕ\phi가 아니라 취소 latency 유독 체결이나 레벨 리필을 과소 모델링한다는 뜻입니다.

5. 구간을 다시 돌려라. 캘리브레이션 후, 큐 소진 추정치는 라이브 근처에서 구간 안에 위치해야 하며, 그리고 - 진짜 합격 테스트는 - 시뮬레이터 하의 전략 변형들의 순위가 섀도 모드에서의 순위와 일치해야 한다는 것입니다. 그런 다음 파라미터를 고정하고 일정에 따라 재캘리브레이션하십시오. 체결 동역학은 거래소의 수수료 티어, 틱 사이즈 변경, HFT 개체군과 함께 드리프트하며, 3월에 캘리브레이션된 ϕ\phi는 7월에는 하나의 가설일 뿐입니다.

우리의 실행에서 얻은 수렴 기대치: 캘리브레이션되지 않은 4단계 시뮬레이터는 보통 라이브 체결률의 ±10-15포인트 이내에 안착하고, 한 번의 캘리브레이션 패스 이후에는 ±3-5포인트, 체결까지 시간 KS p값이 더 이상 일률적으로 기각하지 않게 됩니다. 역사적 리플레이로는 그보다 잘하지 못할 것입니다 - 잔차는 당신에 대한 시장의 반응이며, 그것은 5단계의 문제입니다.

무엇을 가져갈 것인가

  1. 당신의 단계를 명명하라. 모든 백테스트는 이 사다리 위에 놓여 있습니다 - 당신이 그 단계를 골랐든, 프레임워크가 당신을 위해 골랐든. 당신의 체결 모델의 단계와 그 알려진 편향을 명명할 수 없다면, 당신의 PnL 숫자에는 당신이 보지 못한 오차 막대가 붙어 있습니다.
  2. 테이커는 3단계에서 멈춰도 된다. depth-walk에 측정된 latency를 더하면 리테일 규모에서 공격적 체결의 가격을 정직하게 매깁니다. 아낀 노력은 데이터 품질에 쓰십시오.
  3. 메이커는 4단계에서 시작한다. 그 아래에서 지정가 주문 체결 로직은 근사적인 것이 아닙니다 - 그것은 반대되는 역선택 왜곡을 가진 반사실적 체결 집합을 선택합니다. 메이커 전략의 터치 체결 백테스트는 프로덕션과 접촉하는 순간 죽는 전략을 만들어내는 가장 신뢰할 수 있는 단일 발생원입니다.
  4. 체결이 아니라 생애주기를 모델링하라. 부분 체결, amend가 큐를 리셋, 취소 latency - 상태 기계가 바로 유독한 체결이 사는 곳이며, 유독한 체결은 메이커 PnL이 죽으러 가는 곳입니다.
  5. 구간을 보고하라. 보수적 경계와 낙관적 경계는 백테스트 실행 두 번을 추가로 요구하며 "내 백테스트가 +7k라고한다""현실은[7k라고 한다"를 "현실은 [-3.9k, +$1.9k] 어딘가에 있다"로 바꿔줍니다 - 이는 다른, 그리고 더 나은 결정입니다.
  6. 라이브 체결에 대해 버킷별로 캘리브레이션하라. 집계 수준에서만 검증된 체결 모델은 숨겨진 상쇄 오차를 가진 체결 모델입니다. 버킷별 체결률 갭에 체결까지 시간 KS 검정을 더해, 분기마다 재캘리브레이션하십시오.

사다리의 단계들은 학문적 등급이 아닙니다 - 각각은 당신의 백테스트가 당신에게 하기를 멈추는 특정한 거짓말입니다. 그 거짓말들이 당신의 엣지보다 작아질 때까지 올라가십시오.

유용한 링크

  1. Moallemi, C., Yuan, K. — A Model for Queue Position Valuation in a Limit Order Book (2016)
  2. Cont, R., Stoikov, S., Talreja, R. — A Stochastic Model for Order Book Dynamics, Operations Research 58(3), 549-563 (2010)
  3. Lo, A., MacKinlay, C., Zhang, J. — Econometric Models of Limit-Order Executions, Journal of Financial Economics 65(1), 31-71 (2002)
  4. Huang, W., Lehalle, C.-A., Rosenbaum, M. — Simulating and Analyzing Order Book Data: The Queue-Reactive Model, JASA 110(509), 107-122 (2015)
  5. Almgren, R., Chriss, N. — Optimal Execution of Portfolio Transactions (2001)
  6. Byrd, D., Hybinette, M., Balch, T. — ABIDES: Towards High-Fidelity Multi-Agent Market Simulation (2020)
  7. Binance API — Cancel-Replace order semantics
  8. CME Globex — Order modification and time priority rules

인용

@article{soloviov2026fillsimulation,
  author = {Soloviov, Eugen},
  title = {Fill simulation: the ladder from close-price fantasy to queue-aware reality},
  year = {2026},
  url = {https://marketmaker.cc/blog/fill-simulation-partial-fills-backtest},
  description = {Five rungs of fill simulation fidelity — from close-price fills to probabilistic queue-position models. Partial fills as a state machine, limit-fill probability bounds as a PnL bracket, and a calibration loop against live fills.}
}
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

시장에서 앞서 나가세요

뉴스레터를 구독하여 독점적인 AI 트레이딩 통찰력, 시장 분석 및 플랫폼 업데이트를 받아보세요.

귀하의 개인정보를 존중합니다. 언제든지 구독을 취소할 수 있습니다.