← Makalelere geri dön
July 16, 2026
5 dakikalık okuma

Fill simülasyonu: kapanış-fiyatı fantezisinden kuyruğu dikkate alan gerçekliğe uzanan merdiven

Fill simülasyonu: kapanış-fiyatı fantezisinden kuyruğu dikkate alan gerçekliğe uzanan merdiven
#fill simülasyonu
#backtest
#limit emirler
#kuyruk pozisyonu
#kısmi fill'ler
#market making
#execution
#market mikroyapısı

Backtest'iniz iki model içerir: alfanızın bir modeli ve fill'lerinizin bir modeli. Çoğu kişi çabasının %95'ini birincisine harcar ve ikincisini hangi framework'ü kullanıyorsa ondan miras alır. Bu tersine bir yaklaşımdır. Dürüst bir fill modeliyle birleşen vasat bir sinyal, vasat ama gerçek bir PnL tahmini üretir. fill_price = candle.close ile birleşen harika bir sinyal ise hiçbir şeyin tahmini olmayan bir sayı üretir — hiç sorgulamadığınız bir varsayımın çıktısıdır sadece.

Likit enstrümanlarda taker stratejileri için fill modeli bir düzeltme terimidir. Bekleyen limit emirler içeren her şey için — market making, pasif girişler, post-only rebate toplama — fill modeli stratejinin kendisidir. Fill alıp almadığınız, ne zaman, ne miktarda ve hangi sonraki fiyat hareketine bağlı olduğu, PnL'in sadece büyüklüğünü değil işaretini de belirler.

Backtest-canlı paritesi yazısında backtest-canlı sapmalarının tam taksonomisini çıkarmış ve execution sapmasını 5/5 şiddet ile derecelendirmiştik — en kötü sınıf. O yazı fill simülasyonuna üç kaba doğruluk seviyesi verip devam etmişti. Bu yazı derinlik yoldaşı: basamak basamak eksiksiz merdiven, bir durum makinesi olarak emir yaşam döngüsü, gerçekten hesaplayabileceğiniz fill-olasılığı sınırları ve "kârlı" bir maker stratejisinin merdivenin tam olarak neresinde öldüğünü gösteren ölçülmüş bir deney.

Merdiven

Beş basamaklı fill simülasyonu fidelite merdiveni

Her basamak daha fazla veri ve daha fazla kod gerektirir ve her basamak belirli bir sistematik yanlılığı ortadan kaldırır. Basamaklar sadece maliyete göre değil, neyi yanlış yaptıklarına göre sıralanmıştır.

Basamak 0: kapanış-fiyatı fill

fill_price = bar.close

Emir, sinyali üreten mumun kapanışında anında ve tam olarak dolar. Sinyal aynı kapanıştan hesaplanmıştır, dolayısıyla fiyat basıldığında var olmayan veri üzerinden işlem yapıyorsunuzdur: execution kostümü giymiş look-ahead bias. Devir hızı olan her strateji burada iyi görünür.

Basamak 1: sonraki-mum açılışı

fill_price = next_bar.open

Mumlar üzerinde taker mantığı için minimum dürüst model. Sinyal tt mumunda hesaplanır, fill t+1t+1 mumunun ilk gözlemlenebilir fiyatında gerçekleşir. Bu look-ahead'i öldürür ama yine de sıfır spread, sıfır etki, açılış baskısında sonsuz likidite ve %100 fill kesinliği varsayar. Limit emirler için bu, touch-fill mantığına dejenere olur (aşağıda bunun neden zehir olduğuna dair daha fazlası var).

Basamak 2: spread + sabit slippage

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

Artık her taker işlem yarım spread artı kalibre edilmiş bir sabit öder. Bu, yüksek devir hızlı bir stratejinin backtest'te ölebileceği ilk basamaktır — ki mesele de zaten budur. Kalan hata: slippage sabit değildir. Görüntülenen derinliğe göre emir büyüklüğüyle ölçeklenir ve stratejinizin en çok işlem yapmak istediği anda tam olarak patlar. Sabit 5 bps, rejimler üzerinden bir ortalamadır; stratejiniz ortalama rejimde işlem yapmıyor.

Basamak 3: market emirler için L2 derinlik-taraması

Order book anlık görüntüleriyle artık slippage'ı tahmin etmeyi bırakıp hesaplıyorsunuz. QQ büyüklüğünde bir market alış emri, ask tarafını seviye seviye tarar; fill fiyatı, tüketilen seviyeler üzerinden hacim ağırlıklı ortalamadır:

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), ...] en iyiden başlayarak sıralı. (vwap, filled_qty) döner."""
    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)

İki düzeltme bunu önemli ölçüde daha dürüst hale getirir. Birincisi, gecikme: kitabı, karar zaman damganızdan Δt\Delta t sonra durduğu haliyle tarayın; Δt\Delta t ölçülmüş sinyal-borsa gecikmenizdir — gördüğünüz kitap, vurduğunuz kitap değildir. İkincisi, kısmi işlenebilir fill'ler: limit-through fiyatınız içindeki görüntülenen derinlik QQ'dan azsa, model kısmi bir fill döndürmeli ve bekleyen bir kalıntı bırakmalıdır, bu da sorunu aşağıdaki durum makinesine devreder.

Basamak 3'teki kalan hata etki ve yeniden doldurmadır: kitabı statik bir nesne gibi tüketiyorsunuz, ama gerçek kitaplar kısmen yeniden dolar (ve gerçek karşı taraflar tepki verir). Top-of-book derinliğinin birkaç yüzdesinin altındaki clip büyüklükleri için bu hata küçüktür; daha büyük clip'ler için üzerine bindirilmiş bir etki modeline (Almgren-Chriss 2001) ihtiyacınız vardır.

Basamak 4: limit emirler için olasılıksal kuyruk-pozisyonu fill'leri

Basamak 0-3, "agresif emrim hangi fiyattan dolar" sorusunu yanıtlar. Basamak 4 daha zor soruyu yanıtlar: pasif emrim hiç dolar mı — ve bir maker stratejisini fiyatlandırabilecek tek basamak budur. pp fiyatında bekleyen bir limit emir, pp'de gerçekleşen kümülatif işlem hacmi, önündeki kuyruk hacmini aştığında dolar. Bu, doğrudan gözlemleyemediğiniz bir FIFO kuyruğundaki pozisyonunuzu takip etmeyi gerektirir.

Kuyruk-pozisyonu tahmin mekanizması — başlangıç pozisyonu, işlemler ve iptaller üzerindeki güncelleme kuralları, gözlemlenemeyen iptalleri paylaştırmak için f(x)f(x) olasılık ailesi — Duvarın içindeki kuyruk yazısında inşa ettiğimiz temel yapıdır. Burada tekrar türetmeyeceğim; simülatör bunu tüketir. Basamak 4'ün eklediği şey, aşağıdaki fill-olasılığı bölümünde ele alınan fill karar kuralıdır.

Bu basamağın neden önemli olduğu literatürde nicelenmiştir: Moallemi ve Yuan (2016), "A Model for Queue Position Valuation in a Limit Order Book," büyük-tick enstrümanlar için kuyruk başındaki bir pozisyonun kuyruk sonundakine karşı ekonomik değerinin yarım-spread ile karşılaştırılabilir olduğunu gösteriyor — bir market-making stratejisinin tüm teorik edge'iyle aynı büyüklük mertebesinde. Kuyruk pozisyonunu göz ardı eden bir fill modeli, bir maker'ın PnL'ini yanlış tahmin etmez; başka bir stratejinin PnL'ini tahmin eder.

Bir de basamak 5 var — piyasanın emirlerinize tepki verdiği tam agent-tabanlı simülasyon (Huang, Lehalle ve Rosenbaum 2015 anlamında kuyruk-reaktif modeller; ABIDES gibi multi-agent framework'ler, Byrd ve ark. 2020). Kuyruk-farkında olsa bile tarihsel replay, emrinizin başka herkesin davranışında hiçbir şey değiştirmediğini varsayar. Bu varsayım perakende ölçekte iyidir ve kotalarınız seviyenin görünür bir kesri haline geldikçe giderek yanlışlaşır. Basamak 5 burada kapsam dışıdır; merdivenin 4'te bitmediğini bilin yeter.

Bir durum makinesi olarak kısmi fill'ler

Basamak 0-2, bir emrin fonksiyon çağrısı olduğunu varsayabilir: gönder, fiyat al, bitti. Basamak 3'ten itibaren emir bir yaşam döngüsüne sahip bir süreçtir ve simülatör bunu bir durum makinesi olarak modellemelidir, yoksa en çok önem taşıyan durumları sessizce yanlış işler.

Kuyruk pozisyonu notasyonlu emir yaşam döngüsü durum makinesi

enum OrderState {
    PendingNew,                                  // gönderildi, henüz onaylanmadı (gecikme penceresi)
    Resting     { remaining: f64, q_ahead: f64 }, // kitapta, kuyruk pozisyonu tahmin edildi
    PartialFill { remaining: f64, q_ahead: f64 }, // bir miktar tamamlandı, gerisi hâlâ kuyrukta
    PendingAmend,                                 // değişiklik iletiliyor
    PendingCancel,                                // iptal iletiliyor
    Filled,
    Canceled    { filled_qty: f64 },              // iptal anında kısmen dolmuş olabilir
    Rejected,
}

Geçişler ekonomiyi taşır:

  • PendingNewResting: emir, karar anında değil onay anında mevcut olan her şeyin arkasındaki kuyruğa katılır. Kuyruk pozisyonunuz t+Δtackt + \Delta t_{\text{ack}} itibarıyla seviye hacmiyle başlatılır. Karar anında başlatan simülatörler, kuyruk önceliğini sistematik olarak abartır — gecikme penceresi sırasında gelen hacim kadar, ki bu miktar patlamalar (burst) sırasında en yüksek olur.
  • RestingPartialFill: seviyenizde önündeki kuyruktan daha büyük bir işlem sizi kısmen doldurur. Kalan miktar (şimdi kuyruk başında) pozisyonunu korur. Kısmi fill'ler gürültü değildir — bilgidir: 1.0'ın 0.3'ünü doldurup fiyatın uzağa sıçradığını izlemek, tam fill'den farklı bir PnL olayıdır ve bir maker'ın envanter süreci bu parçalardan inşa edilir.
  • RestingPendingAmendResting: tuzak. Neredeyse her kripto borsasında, bir değişiklik (amend) bir cancel/replace işlemidir — Binance'in cancelReplace'i çift-execution'a karşı atomiktir ama kuyruğun sonunda yeni bir emir ID'si döndürür. Native modify semantiğine sahip borsalar bile (CME Globex) zaman önceliğini yalnızca miktar azaltımı için korur; fiyat değişikliği veya miktar artışı önceliği kaybettirir. Yani simülatörde: herhangi bir fiyat değişikliği q_ahead'i mevcut seviye hacminin tamamına sıfırlar. Her 500ms'de bir yeniden fiyatlayan bir kotalama motoru "bir kotayı sürdürmüyor" — sürekli olarak kuyruğun sonuna yeniden giriyordur ve gerçekçi fill profili neredeyse saf adverse selection'dır. Moallemi-Yuan sonucuyla birleştirildiğinde: yeniden kotalamanın bir bedeli vardır ve bu bedel kuyruk pozisyonunuzdur.
  • PendingCancelPartialFillCanceled: iptaller de gecikme alır. Bir kotayı çekme kararı ile iptalin matching engine'e ulaşması arasındaki pencerede yine de dolabilirsiniz — ve bu fill'ler alacağınız en kötü fill'lerdir, çünkü kotayı çekiyor olmanızın nedeni, piyasanın sizi ezip geçmek üzere olmasıdır. İptal gecikmesi olmayan bir simülatör, geçmişinizden tam olarak en toksik fill'leri siler.

Durum makinesi aynı zamanda simülatörün muhasebesini dürüst kılan şeydir: ücretler her fill olayı başına tahakkuk eder, envanter her fill olayı başına güncellenir ve durumda-geçen-süre istatistikleri (emirlerin dolmadan veya iptal edilmeden önce ne kadar süre beklediği) canlı loglarla doğrudan karşılaştırılabilir hale gelir — ki sondaki kalibrasyon döngüsü bununla beslenir.

Limit-fill olasılığı: üç model ve bir parantez

pp fiyatında bekleyen bir emir verildiğinde (diyelim ki bir bid), simülatör bir fill'i ne zaman ilan eder? Artan dürüstlük sırasına göre üç karar kuralı:

1. Touch-fill (naif). Fiyat seviyenize dokunursa fill: lowtp\text{low}_t \le p. Bu bir ilk-geçiş-zamanı kuralıdır ve başarısızlığı çeyrek asır önce ölçülmüştü: Lo, MacKinlay ve Zhang (2002), "Econometric Models of Limit-Order Executions" (Journal of Financial Economics 65), gerçek limit emir verisine survival modelleri fit ederek ilk-geçiş zamanlarından kurulan varsayımsal execution'ların "gerçek limit-emir execution'ları için çok zayıf proxy'ler" olduğu sonucuna varıyor. Başarısızlık modu yapısaldır: fiyat seviyenize dokunup sıçradığında, dokunma kuyruğun önünü tüketmiştir — sizden önce kota veren traderları. Touch-fill onların fill'lerini size verir. Daha kötüsü, tam olarak iyi fill'leri verir (touch-and-bounce bir maker için kârlı senaryodur), oysa gerçek fill setiniz touch-and-run-through'a — olumsuz seçime uğrayanlara — kaymıştır.

2. Trade-through (muhafazakar sınır). Yalnızca fiyat seviyenizin kesinlikle içinden geçerse fill: lowt<pϵ\text{low}_t < p - \epsilon, ya da tick verisinde, pp'deki kümülatif işlem hacmi tüm seviyeyi aşarsa. Fiyat içinden geçtiyse, pp'deki tüm kuyruk tüketilmiştir, dolayısıyla pozisyonunuz ne olursa olsun dolmuşsunuzdur. Bu asla almayacağınız bir fill'i size vermez. Yanlılığı touch-fill'in ayna görüntüsüdür: kuyruğun tam penetrasyon olmadan pozisyonunuza kadar tükendiği her fill'i size reddeder ve verdiği fill'ler orantısız biçimde run-through (olumsuz seçime uğrayan) olanlardır. Trade-through altında bir maker backtest'i bir tahmin değil, bir stres testidir.

3. Kuyruk-tükenme tahmini. İşlem tape'inden fiyatınızdaki kümülatif işlem hacmini MtM_t ve L2 delta'larından çıkarılan iptal edilen hacmi CtC_t'yi takip edin. Önündeki tahmini kuyruğunuz:

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

burada Q0Q_0 (onay-anındaki) emir girişindeki seviye hacmi ve ϕ[0,1]\phi \in [0,1], önünüzden geldiği varsayılan iptallerin kesridir — ilkeli formu (f(x)f(x) ailesi) kuyruk-pozisyonu yazısında türetilen düğme. Fill, Q^ahead=0\hat{Q}_{\text{ahead}} = 0 olduğunda başlar; dolan miktarınız bunu aşan işlem hacmidir, bu da doğal olarak kısmi fill'ler verir:

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 ayarlamak (tüm iptaller önünüzde) bu modelin iyimser ucunu verir; ϕ=0\phi = 0 kötümser ucunu. Yalnızca seyrek L2 anlık görüntüleriniz ve seviyede işlem tape'i yoksa — 100ms throttle'lı kripto akışlarında yaygındır — model-tabanlı bir prior'a başvurabilirsiniz: Cont, Stoikov ve Talreja (2010), "A Stochastic Model for Order Book Dynamics" (Operations Research 58), her fiyat seviyesini bir doğum-ölüm kuyruğu olarak modelliyor ve Laplace dönüşümleri yoluyla, mevcut kuyruk büyüklüklerine koşullu olarak, bid'deki bir emrin mid hareket etmeden önce dolma olasılığını hesaplıyor. Bu analitik bir fill-olasılığı kehanetidir: tape replay'e göre kaba, touch-fill'den çok daha iyi ve sıcak bir backtest döngüsünün içinde değerlendirmek için yeterince ucuz.

Parantez disiplini

Üç kural rakip değildir — bir sıralamadır:

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

bu da bir PnL parantezi doğurur. Her maker backtest'ini üç kez çalıştırın ve aralığı bildirin:

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

(üstteki eşitsizlik yaklaşıktır — touch-fill sadece şişirmekle kalmaz, stratejileri yanlış sıralayabilir de, çünkü size karşı-olgusal iyi fill'ler verir). Buradan çıkan karar kuralı: bir maker stratejisi yalnızca parantezin muhafazakar ucunda hayatta kalırsa dağıtılabilir ve parantez, nokta tahminin bir anlam ifade edeceği kadar dar olmalıdır. +9ktouch/3k touch / -3k trade-through gösteren bir strateji, 12kgenis\clig˘indebirparantezdedir;simu¨lato¨ru¨nu¨zu¨nvarsayımınınalfanızabaskınoldug˘udıs\cındahic\cbirs\ceybilmiyorsunuzdur.+2.1k genişliğinde bir parantezdedir; simülatörünüzün varsayımının alfanıza baskın olduğu dışında hiçbir şey bilmiyorsunuzdur. +2.1k / +0.4k$ gösteren bir strateji size gerçek bir şey söylüyordur.

Deney: bir maker stratejisini merdivenden aşağı yürütmek

Fill-model merdiveninde basamak basamak çöken maker strateji PnL'i

Kasıtlı olarak sade bir maker alın — en iyi bid/ask'ta simetrik kotalar, sabit 0.05 BTC clip'ler, ±0.5 BTC'de envanter üst sınırı ve üst sınırda taker ile düzleştirme. Bir aylık BTCUSDT perpetual verisi: düşük basamaklar için 1 dakikalık mumlar, yüksek basamaklar için 100ms L2 diff'leri artı işlem tape'i; maker ücreti 1.0 bps, taker 4.0 bps. Her basamakta aynı sinyal kodu (paylaşılan çekirdek, yani tek değişken fill modelidir). Aşağıdaki sayılar bizim temsili bir çalışmamızdır — büyüklükleriniz borsaya, aya ve ölçeğe göre farklılık gösterecektir; şekil değişmeyecektir:

Basamak Fill modeli Kota fill oranı Fill'ler Aylık PnL Değerlendirme
0 close-fill %98 41.200 +14.800$ fantezi
1 next-bar-open / 1m mumlarda touch %89 37.400 +9.600$ gecikmeyle fantezi
2 taker düzleştirmelerde touch + spread & sabit slippage %89 37.400 +7.100$ maliyetler modellendi, fill'ler hâlâ kurgusal
3 + taker düzleştirmelerde L2 derinlik-taraması %89 37.400 +6.400$ çıkışlar dürüst, girişler hâlâ kurgusal
4a trade-through (muhafazakar) %21 8.900 -3.900$ stres sınırı
4b kuyruk-tükenme, ϕ\phi kalibre edilmiş %37 15.600 -700$ en iyi tahmin
4c kuyruk-tükenme, ϕ=1\phi = 1 (iyimser) %44 18.700 +1.900$ üst sınır
canlı gölge çalışması, aynı ay %35 14.100 -1.150$ gerçeklik

Tabloyu yukarıdan aşağıya okuyun ve stratejinin nerede öldüğünü izleyin. Bu, basamak 2 değildir — ücretler ve slippage %26'sını kırpar ve strateji hâlâ sağlam kârlı görünür. Basamak 3 ile basamak 4 arasında ölür ve hiçbir maliyet modelinin yakalayamayacağı bir nedenle ölür: fill seçimi. Touch-fill 37.400 fill vermişti, bunların çoğunluğu touch-and-bounce'tı — saf spread yakalama. Kuyruk-farkında model bu fill'lerin %58'ini sildi ve sildiği fill'ler orantısız biçimde kârlı olanlardı: seviye hafifçe dokunulduğunda, yeniden kotalayan perakende-gecikmeli bir maker'ın önündeki kuyruk her şeyi soğurur. Basamak 4'e hayatta kalan fill'ler, seviye-temizleyen sweep'lere kaymıştır — fiyatın zaten sizin içinizden geçmekte olduğu fill'ler. Fill oranı 2,4 kat düştü; PnL işaret değiştirdi. Bu asimetri — iyi fill'leri kaybedip kötüleri korumak — mekanik olarak açığa çıkarılmış adverse selection'dır ve basamak 4'ün altındaki her basamakta görünmezdir.

Parantezin ne yaptığına da dikkat edin: [-3.900,+1.900, +1.900] sıfırı, kalibre edilmiş tahmin -700davecanlı1.150'da ve canlı -1.150'da olacak şekilde kucaklıyor. Simülatör canlı PnL'i dolar dolar tutturmadı — işareti, büyüklüğü ve fill oranını 2 puan içinde yakaladı. Bir fill modelinin işlevi budur. Basamak-1 backtest'i, aylık brüt edge'inin tamamı birkaç bin dolar olan bir stratejide canlı PnL'i 10.750$ ıskaladı: fill-model hatası, alfanın büyüklüğünün kabaca 3 katıydı. Dolayısıyla tez şu: fill modeliniz alfanızdan daha büyük bir varsayımdır.

Düşük basamaklar için bir uyarı: mum verisiyle yaşamak zorundaysanız (basamak 0-2), en azından bar-içi belirsizliği adaptif drill-down ile çözün — SL, TP veya kota seviyeleri bar aralığının içine düştüğünde 1m'den 1s/100ms/işlemlere kadar inerek. Drill-down sıralama hatalarını (hangi seviyenin önce vurulduğunu) düzeltir ama kuyruk hatalarını düzeltemez; bu yazının veri-çözünürlüğü yoldaşıdır, basamak 4'ün yerine geçmez.

Kalibrasyon döngüsü: canlı fill'lere karşı kapatmak

Basamak-4 bir simülatörün serbest parametreleri vardır — ϕ\phi, gecikme Δt\Delta t, iptal-gecikmesi, seviye-yeniden-doldurma varsayımı. Kalibre edilmemişse, farklı şekilli bir tahminden başka bir şey değildir. Onu bir enstrümana dönüştüren döngü:

1. Her şeyi canlı olarak loglayın. Borsa zaman damgalarıyla her emir olayı: gönderim, onay, her kısmi fill, değişiklik onayları, iptal onayları. Ayrıca gönderim anındaki L2 durumu. Bu, backtest-canlı paritesi yazısının DivergenceMonitor'ü için talep ettiği aynı loglama disiplinidir — fill-model kalibrasyonu o monitörün en derin katmanıdır.

2. Aynı emirleri simülatörden tekrar geçirin. Kaydedilmiş piyasa verisini ve kaydedilmiş emir talimatlarını (fill'leri değil) simülatöre besleyin. Artık eşleştirilmiş sonuçlarınız var: her canlı emir için, simüle edilmiş bir kader.

3. Ortalamaları değil, dağılımları, gruplar halinde karşılaştırın. Tek bir global fill-oranı eşleşmesi telafi edici hataları gizleyebilir (sakin rejimlerde çok iyimser, patlamalarda çok kötümser — netleşince "kalibre edilmiş" gibi görünür). Sürücülere göre gruplayın:

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,      # işaretli: + sim iyimser demek
            'ttf_ks_pvalue':  ks.pvalue if ks else None,
        }
    return out

Grup başına iki istatistik: işaretli fill-oranı farkı (simülatör eksi canlı) ve dolan emirler arasında time-to-fill dağılımları üzerinde bir KS testi. Time-to-fill karşılaştırması keskin olandır — bir simülatör fill oranlarını eşleştirirken sistematik olarak yanlış zamanlarda doldurabilir, bu da her aşağı akış envanter ve adverse-selection istatistiğini bozar. Bu, tam olarak Lo-MacKinlay-Zhang'ın survival-analizi çerçevelemesinin dersidir: execution bir time-to-event problemidir, o yüzden onu öyle doğrulayın.

4. Düğmeleri, tanımlanabilirlik sırasına göre fit edin. Önce gecikme (doğrudan onay zaman damgalarından ölçülür — fit edilmez). Sonra ϕ\phi, kuyruk-derinliği grupları arasındaki fill-oranı farkını minimize ederek. Sonra volatilite-rejimi gruplarını kontrol edin: patlama gruplarında yoğunlaşan kalıcı bir iyimser fark, genellikle ϕ\phi değil, simülatörünüzün iptal-gecikmesi toksik fill'lerini veya seviye yeniden-doldurmayı yetersiz modellediği anlamına gelir.

5. Paranteziyeniden çalıştırın. Kalibrasyondan sonra, kuyruk-tükenme tahmini parantezin içinde canlıya yakın oturmalıdır — ve gerçek kabul testi, strateji varyantlarının simülatör altındaki sıralamasının gölge moddaki sıralamalarıyla eşleşmesidir. Sonra parametreleri dondurun ve bir programa göre yeniden kalibre edin; fill dinamikleri borsanın ücret katmanlarıyla, tick-büyüklüğü değişiklikleriyle ve HFT popülasyonuyla birlikte kayar ve Mart'ta kalibre edilmiş bir ϕ\phi, Temmuz'da bir hipotezdir.

Çalışmalarımızdan yakınsama beklentileri: kalibre edilmemiş bir basamak-4 simülatörü tipik olarak canlı fill oranının ±10-15 puanı içine düşer; bir kalibrasyon geçişinden sonra ±3-5 puan, time-to-fill KS p-değerleri artık tekdüze şekilde reddetmiyor. Tarihsel replay ile bundan daha iyisini yapmayacaksınız — kalan, piyasanın size verdiği tepkidir, ki bu basamak 5'in sorunudur.

Çıkarımlar

  1. Basamağınızı adlandırın. Her backtest, basamağı siz seçmiş olsanız da framework'ünüz sizin yerinize seçmiş olsa da bu merdivenin üzerinde durur. Fill modelinizin basamağını ve bilinen yanlılıklarını adlandıramıyorsanız, PnL sayınızın görmediğiniz hata çubukları vardır.
  2. Taker'lar basamak 3'te durabilir. Derinlik-taraması artı ölçülmüş gecikme, perakende ölçekte agresif execution'ı dürüstçe fiyatlandırır. Tasarruf edilen çabayı veri kalitesine harcayın.
  3. Maker'lar basamak 4'ten başlamalı. Altında, limit-emir fill mantığı yaklaşık değildir — ters adverse-selection eğimine sahip karşı-olgusal bir fill kümesi seçer. Maker stratejilerinin touch-fill backtest'leri, üretimle temas ettiğinde ölen stratejilerin en güvenilir üreticisidir.
  4. Fill'i değil, yaşam döngüsünü modelleyin. Kısmi fill'ler, değişikliğin kuyruğu sıfırlaması, iptal gecikmesi — toksik fill'ler durum makinesinde yaşar ve toksik fill'ler maker PnL'inin öldüğü yerdir.
  5. Paranteziyi bildirin. Muhafazakar ve iyimser sınırlar iki ekstra backtest çalıştırmasına mal olur ve "backtest'im +7kdiyor"u"gerc\ceklik[3.9k diyor"u "gerçeklik [-3.9k, +1.9k$] aralığında bir yerde"ye dönüştürür — ki bu farklı ve daha iyi bir karardır.
  6. Canlı fill'lere karşı gruplar halinde kalibre edin. Yalnızca toplamda doğrulanmış bir fill modeli, gizli telafi edici hatalara sahip bir fill modelidir. Gruplandırılmış fill-oranı farkları artı time-to-fill KS testleri, üç ayda bir yeniden kalibre edilmeli.

Merdivenin basamakları akademik derecelendirmeler değildir — her biri backtest'inizin artık size söylemediği belirli bir yalandır. Yalanlar edge'inizden küçük olana kadar tırmanın.

Faydalı bağlantılar

  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

Citation

@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.}
}
Sorumluluk Reddi: Bu makalede sağlanan bilgiler yalnızca eğitim ve bilgilendirme amaçlıdır ve finansal, yatırım veya ticaret tavsiyesi niteliği taşımaz. Kripto para ticareti önemli bir kayıp riski içerir.

Yazarlar

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

Piyasanın Önünde Olun

Özel yapay zeka ticaret içgörüleri, piyasa analizi ve platform güncellemeleri için bültenimize abone olun.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.