Makine Öğrenmesiyle Alış-Satış Spread'i Modelleme ve Tahmini
Alış-satış spread'i, bir piyasa yapıcısının kontrol ettiği en önemli tek değişkendir. Çok geniş ayarlarsanız akışı rakiplerinize kaptırırsınız. Çok dar ayarlarsanız ters seçim envanterinizi diri diri yer. Geleneksel mikroyapı teorisi, spread'i ekonomik bileşenlerine ayrıştıran zarif çözümlemeler sunar. Makine öğrenmesi ise bu bileşenlerin gerçek zamanlı olarak nasıl kaydığını tahmin edecek araçları verir. Bu yazı her iki dünyayı birbirine bağlıyor: klasik teoriyle başlıyoruz, Roll'un örtük spread tahmincisine kadar ilerliyoruz, ardından emir defteri özelliklerinden spread tahmin eden gradyan artırma ve derin öğrenme modellerine geçiyoruz. Bu süreçte, pratikte spread modellerini sessizce geçersiz kılan birim, sızıntı ve kıyaslama tuzaklarına işaret ediyoruz.
Spread'ler Piyasa Yapıcıları İçin Neden Önemli
Bir piyasa yapıcı sürekli olarak bir alış fiyatı ve bir satış fiyatı kote eder. Kote edilen spread şudur:
Her gidiş-dönüş (piyasa yapıcının alışından satın alma, piyasa yapıcının satışına satma, ikisi de taker'lar tarafından gerçekleştirilir) taker'lardan piyasa yapıcıya en fazla kadar transfer eder — teoride. Pratikte piyasa yapıcı, ters seçim nedeniyle 'den daha azını kazanır: bazı taker'lar bilgilidir ve fiyat piyasa yapıcının aleyhine hareket etmeden hemen önce işlem yaparlar. Gidiş-dönüş başına gerçekleşen kâr, etkin spread eksi fiyat etkisine eşit olan gerçekleşen spread'dir:
Üç büyüklüğün de aynı tam-spread temelinde (yarım değil) ölçeriz, böylece özdeşlik boyutsal olarak tutarlı olur. Tek bir işlem için etkin spread şöyledir:
Burada işlem yönüdür (taker alışı veya satışı), işlem fiyatıdır ve işlem anındaki en iyi alış ve satışın orta noktasıdır. Fiyat etkisi terimi, işlem sonrası bir ufku üzerinde simetrik olarak tanımlanır:
burada işlemden ufku sonraki orta noktadır. Ufuk açıkça belirtilmelidir — yaygın seçimler hisse senetlerinde 5 dakika ve fiyatların daha hızlı yeniden fiyatlandığı kriptoda 30 saniyedir. Etkin spread'den etkiyi çıkarmak gerçekleşen spread'i bırakır: piyasa hareket ettikten sonra piyasa yapıcının elinde kalan.
Sonraki 1, 5 veya 60 saniyedeki spread'i — ve bileşenlerini — tahmin edebilen bir piyasa yapıcı, gerçekleşme oranlarını korurken gerçekleşen spread'i en üst düzeye çıkarmak için kotaları dinamik olarak ayarlayabilir.
Spread'in Üç Bileşeni

Piyasa mikroyapısı literatürü (Stoll 1978, Glosten ve Milgrom 1985, Huang ve Stoll 1997) alış-satış spread'ini üç ekonomik bileşene ayrıştırır.
1. Emir İşleme Maliyeti ()
Bu, gerçekleştirilen taraf başına piyasa yapma hizmetini sunmanın maliyetidir: piyasa yapıcının fiilen ödediği ücret artı teknoloji altyapısı, düzenleyici uyum ve konuşlandırılan sermayenin fırsat maliyeti. Demsetz (1968) ve Tinic (1972) bu bileşeni ilk biçimlendirenler oldu.
Temel ayrım hangi ücreti kimin ödediğidir. Pasif olarak kote eden bir piyasa yapıcı, kendi gerçekleşmelerinde maker ücreti öder — ve birçok borsada bir iadedir, yani negatiftir. Bu pasif gerçekleşmelerde taker ücreti 'yi ödemez; spread'i geçen karşı taraf 'yi öder. Yani piyasa yapıcının taraf başına emir işleme maliyeti şudur:
burada işaretlidir (bir iade 'yı düşürür ve negatif yapabilir) ve bağlantı, ortak yerleşim ve hesaplamayı kapsar. Modern elektronik piyasalarda bu bileşen dramatik biçimde küçüldü — hisse senetlerinde kuruşun altında, kriptoda birkaç baz puan veya net bir iade.
Taker ücreti farklı bir nedenle önemlidir: tam spread'in kârlı biçimde ne kadar dar olabileceğine bir taban koyar, çünkü geçen bir taker spread'in üzerine öder. Kotalarınızı bu taker maliyetine göre ekonomik olarak çekici tutan bir spread tabanı istiyorsanız, bunu 'yi piyasa yapıcının kendi maliyetine katlamak yerine ayrıca gerekçelendirin. İkisini birbirine karıştırmak, tek bir yarım spread içinde bir gidiş-dönüş ücretini çift saymaktır.
2. Envanter Tutma Maliyeti ()
Bir piyasa yapıcı yönlü bir pozisyon (uzun veya kısa) biriktirdiğinde, fiyat riskine maruz kalır. Envanter bileşeni bu riski telafi eder. Stoll (1978) ile Amihud ve Mendelson (1980) bunu volatilite ve piyasa yapıcının mevcut envanterinin bir fonksiyonu olarak modelledi:
burada varlığın volatilitesi ve piyasa yapıcının mevcut envanteridir. Envanter büyüdükçe piyasa yapıcı maruz kaldığı tarafta spread'i genişletir ve diğer tarafta daraltır; bu envanter eğme (inventory skewing) adı verilen bir tekniktir.
3. Ters Seçim Maliyeti ()
Bu en tehlikeli bileşendir. Bilgili tüccarlar — yaklaşan fiyat hareketleri hakkında üstün bilgiye sahip olanlar — sistematik olarak bayatlamış kotaları toplarlar. Ters seçim maliyeti, bilgili karşı taraflara işlem başına beklenen kayba eşittir. Copeland ve Galai (1983) bunu piyasa yapıcının bilgili tüccarlara verdiği bedava bir opsiyonun değeri olarak modelledi. Glosten ve Milgrom (1985) bunu, bir işlem gözlemledikten sonra piyasa yapıcının inançlarındaki Bayesçi revizyon olarak biçimlendirdi:
burada gerçek temel değerdir. Likit piyasalarda ters seçim, toplam spread'in %30-60'ını oluşturabilir.
Tam Ayrıştırma
Kote edilen yarım spread şöyle yazılabilir:
burada , ve hepsi taraf başına (yarım spread) maliyetler olarak ifade edilir — muhasebeyi tutarlı tutan budur. Huang ve Stoll (1997) bu bileşenleri işlem ve kotasyon verilerinden tahmin etmek için bir ekonometrik yöntem önerdi. Temel kavrayış: emir işleme maliyetleri sabit bir spread tabanı yaratır, envanter maliyetleri pozisyon ve volatiliteyle değişen bir spread yaratır ve ters seçim maliyetleri bilgi asimetrisiyle değişen bir spread yaratır.
Roll'un Örtük Spread Modeli

Yüksek frekanslı veriler yaygın olarak mevcut olmadan önce, Richard Roll (1984) yalnızca işlem fiyatlarını kullanarak etkin spread'i tahmin etmek için zarif bir yöntem önerdi. Kavrayışı şuydu: etkin bir piyasada, alış-satış sıçraması, yeni bir bilgi olmasa bile fiyat değişimlerinde negatif seri kovaryansı tetikler.
Model
Temel değer 'nin rastgele yürüyüş izlediğini varsayalım:
Gözlemlenen işlem fiyatı alış ve satış arasında zıplar:
burada eşit olasılıkla (yani alışlar ve satışlar eşit derecede olasıdır). Fiyat değişimi şöyledir:
Birinci dereceden otokovaryansı hesaplayarak:
Model fiyat birimleri cinsinden türetilir: , getirilerin değil, fiyat değişimlerinin otokovaryansından çıkar. Bu ayrım, en yaygın tek uygulama hatasıdır ve aşağıdaki kodu buna sadık tutuyoruz.
Roll Tahmincisi
için çözerek:
Örnek otokovaryans pozitif olduğunda (gürültü veya momentum nedeniyle pratikte sık gerçekleşir), tahminci tanımsızdır. Yaygın bir düzeltme, tahmini sıfıra ayarlamak veya işaretli kökü kullanmaktır:
burada örnek birinci dereceden otokovaryanstır.
Python'da Uygulama
Tahminci, spread'i fiyat birimleri cinsinden döndürür. Baz puan olarak ifade etmek için bir kez orta fiyata böleriz — çünkü, getiri uzayı tahmincisinin aksine, henüz fiyata bölünmemiştir:
import numpy as np
import pandas as pd
def roll_spread(prices: pd.Series, window: int = 200) -> pd.Series:
"""
Rolling Roll (1984) spread estimator, in PRICE units.
The model is P_t = V_t + (S/2) d_t with Cov(ΔP_t, ΔP_{t-1}) = -S^2/4,
so S is recovered from the autocovariance of price CHANGES (diff),
not returns (pct_change). Using returns rescales the estimate by the
price level and is wrong by roughly that factor.
Parameters
----------
prices : pd.Series
Transaction prices.
window : int
Rolling window size (number of price changes).
Returns
-------
pd.Series
Estimated spread per window, in price units.
"""
dprice = prices.diff().dropna()
autocov = dprice.rolling(window).apply(
lambda x: np.cov(x[:-1], x[1:])[0, 1], raw=True
)
return 2.0 * np.sqrt(np.maximum(-autocov, 0.0))
trades = pd.read_parquet("trades.parquet")
trades["roll_spread"] = roll_spread(trades["price"], window=200)
trades["quoted_spread"] = trades["ask"] - trades["bid"]
trades["midprice"] = 0.5 * (trades["ask"] + trades["bid"])
trades["quoted_spread_bps"] = trades["quoted_spread"] / trades["midprice"] * 1e4
trades["roll_spread_bps"] = trades["roll_spread"] / trades["midprice"] * 1e4
Simüle edilmiş bir seri üzerinde hızlı bir mantık kontrolü — 100 fiyatı civarında bir temel rastgele yürüyüş ve gerçek spread'i ile — fiyat değişimlerinden elde eder. Getiri tabanlı varyant, fiyat seviyesi kadar sapmayla döndürür ve ardından bunu tekrar orta fiyata bölerek baz puan elde etmek hatayı katlar. Getiri uzayı tahmincisini tercih ederseniz, modeli log-fiyat uzayında türetin ve orta fiyata ikinci bölmeyi atın; bir konvansiyon seçin ve kodu matematikle eşleştirin.
Roll Modelinin Sınırlamaları
Roll'un modeli şunları varsayar: (1) piyasa etkinliği, (2) bilgi asimetrisinin olmaması, (3) i.i.d. işlem yönü ve (4) sabit spread. Bunların hepsi pratikte ihlal edilir. Harris (1990), gürültülü verilere uygulandığında tahmincinin Jensen eşitsizliği nedeniyle ciddi biçimde yanlı olduğunu gösterdi. Bu sınırlamalara rağmen, Roll tahmincisi hızlı bir temel çizgi olarak yararlı olmaya devam ediyor ve ampirik finans araştırmalarında yaygın olarak kullanılıyor.
Spread Tahmini İçin ML Özellikleri

Statik modellerin ötesine geçmek için, spread varyasyonunun dinamik itici güçlerini yakalayan özelliklere ihtiyacımız var. İşte temsil ettikleri spread bileşenine göre düzenlenmiş bir özellik taksonomisi.
Emir Defteri Özellikleri (Envanter ve Ters Seçim)
| Özellik | Formül | Temsil Ettiği |
|---|---|---|
| Defter dengesizliği | Yönlü baskı | |
| Ağırlıklı orta fiyat | Kısa vadeli adil değer | |
| Derinlik oranı (seviye 1-5) | Çok seviyeli arz/talep | |
| Defter baskısı | Mesafe ağırlıklı baskı | |
| Spread / tik oranı | Minimuma göre darlık |
Buradaki defter baskısı, orta noktaya mutlak mesafe sönümü kullanır, , böylece dokunuşun yakınında duran hacim derin hacimden daha fazla sayılır ve her iki taraf da pozitif, azalan bir fonksiyonla ağırlıklandırılır. Bu, hacmi işaretli mesafe 'e bölmenin yapısal işaret yanlılığından kaçınır (bu, alış tarafında negatif, satış tarafında pozitiftir ve bir seviye orta noktaya yaklaştıkça patlar). 'yı tipik defter derinliğinden seçin veya üstel ifadeyi mesafede azalan herhangi bir pozitif ağırlık ile değiştirin.
İşlem Akışı Özellikleri (Ters Seçim)
| Özellik | Formül | Temsil Ettiği |
|---|---|---|
| İşlem dengesizliği | Net bilgili akış | |
| VPIN | Hacim senkronize bilgili işlem olasılığı | Toksisite |
| Kyle's lambda | 'in işaretli hacme regresyonu | Birim başına fiyat etkisi |
| Büyük işlem sıklığı | Pencerede işlemlerin sayısı | Kurumsal faaliyet |
Volatilite Özellikleri (Envanter Maliyeti)
| Özellik | Formül | Temsil Ettiği |
|---|---|---|
| Gerçekleşen volatilite | Kısa vadeli risk | |
| Garman-Klass volatilitesi | Aralık tabanlı volatilite | |
| Volatilitenin volatilitesi | 'nin yuvarlanan std'si | Rejim belirsizliği |
| Getiri otokorelasyonu | Momentum / ortalamaya dönüş |
Piyasa Rejimi Özellikleri
| Özellik | Açıklama | Temsil Ettiği |
|---|---|---|
| Günün saati kodlaması | Gün içi mevsimsellik | |
| Son işlemden bu yana saniye | Zaman boşluğu | Faaliyet seviyesi |
| Varlıklar arası korelasyon | Endeks/BTC ile yuvarlanan korelasyon | Sistematik risk |
| Fonlama oranı (kripto) | Perp fonlama oranı | Kaldıraçlı konumlanma |
Spread Tahmini İçin Gradyan Artırma
Gradyan artırmalı ağaçlar (XGBoost, LightGBM, CatBoost) kantitatif finansta tablo verili tahminin beygiridir. Karışık özellik türlerini işler, doğrusal olmayan etkileşimleri yakalar, minimum ön işleme gerektirir ve milyonlarca satır üzerinde hızlı eğitilir — özellik oluşturmanın kendisi vektörleştirildiği sürece (aşağıdaki otokorelasyon notuna bakın).
Problemin Formülasyonu
Spread tahminini bir regresyon görevi olarak çerçeveliyoruz. Hedef, sonraki saniyedeki zaman ağırlıklı ortalama kote edilen spread'dir:
Pratikte bunu, sonraki anlık görüntüdeki hacim ağırlıklı ortalama spread ile yaklaşık olarak hesaplarız:
Bu hedef, (veya horizon) uzunluğunda bir ileri penceredir, bu da bitişik satırların örtüşen gelecek pencereleri paylaştığı anlamına gelir. Bu örtüşme, naif bir eğitim/doğrulama bölmesi boyunca bilgi sızdırır — aşağıdaki eğitim kodunda bunu açıkça ele alıyoruz.
Tam Boru Hattı
import lightgbm as lgb
import numpy as np
import pandas as pd
from sklearn.metrics import mean_absolute_error, r2_score
def build_features(df: pd.DataFrame) -> pd.DataFrame:
"""Build spread-prediction features from L2 order book snapshots."""
f = pd.DataFrame(index=df.index)
f["imb1"] = (df["bid_vol_1"] - df["ask_vol_1"]) / (
df["bid_vol_1"] + df["ask_vol_1"] + 1e-9
)
bid_depth = df[[f"bid_vol_{i}" for i in range(1, 6)]].sum(axis=1)
ask_depth = df[[f"ask_vol_{i}" for i in range(1, 6)]].sum(axis=1)
f["depth_imb5"] = (bid_depth - ask_depth) / (bid_depth + ask_depth + 1e-9)
mid = 0.5 * (df["ask_1"] + df["bid_1"])
f["spread_bps"] = (df["ask_1"] - df["bid_1"]) / mid * 1e4
f["log_spread"] = np.log1p(df["ask_1"] - df["bid_1"])
log_ret = np.log(mid / mid.shift(1))
f["rvol_50"] = log_ret.rolling(50).std()
f["rvol_200"] = log_ret.rolling(200).std()
if "trade_sign" in df.columns and "trade_vol" in df.columns:
signed_vol = df["trade_sign"] * df["trade_vol"]
total_vol = df["trade_vol"].rolling(50).sum()
f["tfi_50"] = signed_vol.rolling(50).sum() / (total_vol + 1e-9)
lag1 = log_ret.shift(1)
f["ret_autocorr"] = log_ret.rolling(100).corr(lag1)
if isinstance(df.index, pd.DatetimeIndex):
seconds = df.index.hour * 3600 + df.index.minute * 60 + df.index.second
f["tod_sin"] = np.sin(2 * np.pi * seconds / 86400)
f["tod_cos"] = np.cos(2 * np.pi * seconds / 86400)
for lag in [1, 5, 10, 50]:
f[f"spread_lag_{lag}"] = f["spread_bps"].shift(lag)
return f.dropna()
def build_target(df: pd.DataFrame, horizon: int = 10) -> pd.Series:
"""Forward mean spread over the next `horizon` snapshots (in bps).
target[t] = mean(spread_bps[t+1 .. t+horizon]). Note that consecutive
targets share an overlapping forward window of length `horizon`, which
is why the CV below purges a gap of `horizon` rows around each fold.
"""
mid = 0.5 * (df["ask_1"] + df["bid_1"])
spread_bps = (df["ask_1"] - df["bid_1"]) / mid * 1e4
fwd = spread_bps.shift(-1).rolling(horizon).mean().shift(-(horizon - 1))
return fwd
def purged_walk_forward(n: int, n_splits: int, horizon: int):
"""Expanding-window splits with a purge/embargo gap of `horizon` rows.
Because each target spans `horizon` future snapshots, rows straddling a
train/val boundary share overlapping target windows. Dropping a gap of
`horizon` rows between train and validation removes that leakage
(Lopez de Prado-style purging). Without it, validation R²/MAE are
optimistically biased by the target overlap.
"""
fold_size = n // (n_splits + 1)
for k in range(1, n_splits + 1):
train_end = fold_size * k
val_start = train_end + horizon # embargo gap
val_end = val_start + fold_size
if val_end > n:
break
train_idx = np.arange(0, train_end - horizon) # purge gap
val_idx = np.arange(val_start, val_end)
yield train_idx, val_idx
def train_spread_model(features: pd.DataFrame, target: pd.Series, horizon: int = 10):
"""Train LightGBM with purged, embargoed walk-forward validation."""
common = features.index.intersection(target.dropna().index)
X = features.loc[common].reset_index(drop=True)
y = target.loc[common].reset_index(drop=True)
models, scores = [], []
params = {
"objective": "mae",
"learning_rate": 0.05,
"num_leaves": 63,
"min_child_samples": 100,
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_alpha": 0.1,
"reg_lambda": 1.0,
"verbose": -1,
}
for fold, (train_idx, val_idx) in enumerate(
purged_walk_forward(len(X), n_splits=5, horizon=horizon)
):
X_tr, X_val = X.iloc[train_idx], X.iloc[val_idx]
y_tr, y_val = y.iloc[train_idx], y.iloc[val_idx]
ds_tr = lgb.Dataset(X_tr, y_tr)
ds_val = lgb.Dataset(X_val, y_val, reference=ds_tr)
model = lgb.train(
params,
ds_tr,
num_boost_round=2000,
valid_sets=[ds_val],
callbacks=[lgb.early_stopping(50), lgb.log_evaluation(200)],
)
preds = model.predict(X_val)
mae = mean_absolute_error(y_val, preds)
r2 = r2_score(y_val, preds)
print(f"Fold {fold}: MAE={mae:.4f} bps, R²={r2:.4f}")
models.append(model)
scores.append({"mae": mae, "r2": r2})
return models[-1], scores
Kritik ayrıntı temizleme/ambargo boşluğudur. İleri-ortalama hedefi, ardışık satırların horizon kadar anlık görüntüye kadar örtüştüğü anlamına gelir, bu nedenle düz bir TimeSeriesSplit, doğrulama satırlarının gelecek pencerelerini eğitim satırlarıyla paylaşmasına izin verir — cevabı sızdırır ve doğrulama R²'sini şişirir. Her kat sınırının her iki tarafında en az horizon kadar satırlık bir boşluk düşürmek (Lopez de Prado tarzı temizlenmiş k-fold) bu yanlılığı ortadan kaldırır. Sızıntı daha çok dizi modelleri için tartışılsa da, bu gradyan artırma boru hattı için de derin öğrenme kadar geçerlidir.
Özellik Önemi Analizi
Ağaç tabanlı modellerin temel avantajlarından biri yorumlanabilirliktir. Eğitimden sonra, hangi özelliklerin spread tahminlerini yönlendirdiğini anlamak için SHAP değerlerini inceleyin:
import shap
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_val)
shap.summary_plot(shap_values, X_val, max_display=15)
Varlık sınıfları genelinde tipik bulgular:
- Gecikmeli spread () neredeyse her zaman en önemli özelliktir — spread'ler yüksek derecede otokorelelidir. Başlıktaki R²'nin yüksek görünmesinin nedeni de budur: skorun çoğu sadece kalıcılıktır, bu yüzden her zaman bir AR/EWMA temel çizgisine karşı kıyaslayın (bunun hakkında daha fazlası aşağıda).
- Gerçekleşen volatilite ikinci en önemlisidir — gün içi volatilite ve spread'ler hem eşzamanlı hem de dinamik olarak güçlü biçimde pozitif korelelidir.
- Defter dengesizliği en çok volatil dönemlerde önemlidir — yaklaşan yönlü hareketlere işaret eder.
- İşlem akışı dengesizliği kısa vadeli ters seçimi yakalar — tek taraflı bir akış patlaması spread genişlemesini öngörür.
- Günün saati, U-şeklindeki gün içi deseni yakalar (açılış/kapanışta daha geniş, gün ortasında daha dar).
Hiperparametre Hususları
Özellikle spread tahmini için:
- MSE yerine MAE veya Huber kaybı kullanın. Spread dağılımları, ara sıra aşırı uç değerlerle (haber olayları sırasında) sağa çarpıktır. MAE daha sağlamdır.
- Modelin tek tek anlık görüntülerdeki mikroyapı gürültüsüne uymasını önlemek için
min_child_samplesdeğerini yüksek (100+) ayarlayın. - Ağaçları korelasyonsuzlaştırmak ve farklı volatilite rejimleri genelinde genellemeyi iyileştirmek için
subsample < 1.0kullanın.
Derin Öğrenme Yaklaşımları
Gradyan artırma, tablo verili özelliklerde mükemmel olsa da, derin öğrenme temsilleri doğrudan ham emir defteri verisinden öğrenebilir. İki mimari, spread ile ilgili tahmin görevleri için etkili olduğunu kanıtladı.
Mimari 1: Emir Defteri Anlık Görüntüleri İçin CNN-LSTM
DeepLOB mimarisi (Zhang ve diğerleri 2019), bu uzamsal yapıyı korurken emir defteri seviyeleri boyunca uzamsal desenleri çıkarmak için yığılmış küçük çekirdekli evrişimleri — ve bir Inception modülünü — kullanır, ardından zamansal bağımlılıkları modellemek için LSTM katmanları gelir. Önemli tasarım seçimi, seviye eksenini yinelemeli katmandan önce küresel olarak havuzlamak (global-pool) değildir: bunu yapmak, evrişimlerin yakalaması amaçlanan seviyeler arası yapıyı tam olarak çökertir.
Spread tahmini için girdi, şeklinde bir tensördür:
- = zaman adımı sayısı (örneğin 100 anlık görüntü)
- = fiyat seviyesi sayısı (örneğin 10 alış + 10 satış = 20)
- = seviye başına özellikler (fiyat, hacim, emir sayısı)
Aşağıdaki model, seviyeler üzerindeki evrişimsel özellik haritasını korur ve seviye boyutunu 16 kanal ortalamasına ortalamak yerine onu LSTM girdisine düzleştirir (input_size = 16 * L):
import torch
import torch.nn as nn
class SpreadPredictor(nn.Module):
"""
CNN-LSTM model for bid-ask spread prediction from L2 order book.
Input: (batch, seq_len, n_levels, n_features)
Output: (batch, 1) — predicted spread in bps
"""
def __init__(
self,
n_levels: int = 20,
n_features: int = 3,
seq_len: int = 100,
hidden_dim: int = 64,
n_lstm_layers: int = 2,
dropout: float = 0.2,
):
super().__init__()
self.seq_len = seq_len
self.n_levels = n_levels
self.conv = nn.Sequential(
nn.Conv1d(n_features, 32, kernel_size=3, padding=1),
nn.BatchNorm1d(32),
nn.LeakyReLU(0.1),
nn.Conv1d(32, 16, kernel_size=3, padding=1),
nn.BatchNorm1d(16),
nn.LeakyReLU(0.1),
)
conv_out_dim = 16 * n_levels # flattened (channels × levels)
self.lstm = nn.LSTM(
input_size=conv_out_dim,
hidden_size=hidden_dim,
num_layers=n_lstm_layers,
batch_first=True,
dropout=dropout,
)
self.head = nn.Sequential(
nn.Linear(hidden_dim, 32),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(32, 1),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Parameters
----------
x : Tensor of shape (batch, seq_len, n_levels, n_features)
Returns
-------
Tensor of shape (batch, 1) — predicted spread
"""
batch, T, L, F = x.shape
x = x.reshape(batch * T, L, F).permute(0, 2, 1)
x = self.conv(x) # (batch * T, 16, L)
x = x.reshape(batch, T, 16 * L) # (batch, T, 16 * L) — keep levels
lstm_out, _ = self.lstm(x) # (batch, T, hidden_dim)
last_hidden = lstm_out[:, -1, :] # (batch, hidden_dim)
return self.head(last_hidden) # (batch, 1)
Parametre sayısını kontrol etmek için seviye ekseninde havuzlama isterseniz, tek bir konumdan fazlasını koruyan adımlı veya öğrenilmiş bir havuzlama kullanın — her seviyeyi tek bir sayıya ortalayan ve uzamsal sinyali atan AdaptiveAvgPool1d(1) değil.
Mimari 2: Transformer Kodlayıcı
Transformer'lar, LSTM'lerin sıralı darboğazı olmadan emir defteri dizilerindeki uzun menzilli bağımlılıkları yakalayabilir. Spread tahmini için hafif bir transformer kodlayıcı iyi çalışır:
class TransformerSpreadPredictor(nn.Module):
"""Transformer encoder for spread prediction from order book sequences."""
def __init__(
self,
input_dim: int = 40, # 20 levels * 2 features (price_offset, volume)
d_model: int = 64,
nhead: int = 4,
n_layers: int = 3,
seq_len: int = 100,
dropout: float = 0.1,
):
super().__init__()
self.input_proj = nn.Linear(input_dim, d_model)
self.pos_encoding = nn.Parameter(torch.randn(1, seq_len, d_model) * 0.02)
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=nhead,
dim_feedforward=d_model * 4,
dropout=dropout,
batch_first=True,
activation="gelu",
)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=n_layers)
self.head = nn.Sequential(
nn.LayerNorm(d_model),
nn.Linear(d_model, 1),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
x: (batch, seq_len, input_dim) — flattened order book snapshots
"""
x = self.input_proj(x) + self.pos_encoding[:, : x.size(1), :]
x = self.encoder(x)
return self.head(x[:, -1, :])
Eğitim Hususları
-
Normalizasyon: Fiyatları orta fiyattan ofsetler olarak normalize edin (tik veya baz puan cinsinden). Hacimleri yuvarlanan ortalamalarına göre normalize edin. Ham fiyatlar ve hacimler eğitim kararsızlığına neden olur.
-
Kayıp fonksiyonu: Spread sıçramalarını ele almak için Huber kaybı () kullanın:
-
Pencere örnekleme ve sızıntı: Eğitim için örtüşmeyen pencereler kullanın ve — tam olarak gradyan artırma boru hattında olduğu gibi — eğitim ile doğrulama arasında en az
horizonkadar anlık görüntülük bir boşluğu temizleyin/ambargolayın. Hem ileri-ortalama hedefi hem de örtüşen girdi pencereleri, bölme sınırları boyunca gelecek bilgisini sızdırır ve görünür performansı şişirir. -
Çevrimiçi adaptasyon: Üretimde, modeli düşük bir öğrenme oranıyla periyodik olarak son veriler üzerinde (son 1-2 saat) ince ayarlayın. Piyasa mikroyapısı gün içinde değişir ve sabah verisiyle eğitilmiş bir model öğleden sonra daha düşük performans gösterebilir.
Derin Öğrenme ile Gradyan Artırma Ne Zaman Kullanılır
| Kriter | Gradyan Artırma | Derin Öğrenme |
|---|---|---|
| Girdi türü | Tablo verili özellikler | Ham emir defteri dizileri |
| Eğitim veri boyutu | 100K+ satırla çalışır | 1M+ satıra ihtiyaç duyar |
| Özellik mühendisliği | Manuel (yüksek çaba, yüksek kontrol) | Öğrenilmiş (daha düşük çaba, daha az yorumlanabilir) |
| Çıkarım gecikmesi | Derlenmiş bir tahminci ile tek haneli µs; Python'dan onlarca µs | GPU'da yüzlerce µs |
| Yorumlanabilirlik | Yüksek (SHAP) | Düşük (dikkat haritaları) |
| Rejim adaptasyonu | Yeniden eğitim / çevrimiçi güncelleme | Son veriler üzerinde ince ayar |
| Kısa ufuk spread becerisi | Genel olarak DL ile karşılaştırılabilir | Daha uzun ufuklarda / daha büyük veride avantaj büyür |
Belirli R² rakamlarını alıntılamaktan kasıtlı olarak kaçınıyoruz: spread tahmin doğruluğu büyük ölçüde ufka, varlığa ve skorun ne kadarının basitçe spread otokorelasyonu olduğuna bağlıdır. Bir model, tek satırlık bir EWMA üzerine neredeyse hiçbir şey eklemeden etkileyici bir ham R² gösterebilir. Başlıktaki bir R² yerine, ufuk belirtilmiş halde aynı veri üzerinde bir AR/EWMA temel çizgisinin üzerindeki beceriyi raporlayın. Benzer şekilde, gecikme rakamlarını uygulamaya bağlı olarak ele alın: 2000 turlu, 63 yapraklı bir LightGBM modeli, Python'dan tek bir satırı onlarca mikrosaniyede tahmin eder ve yalnızca derlenmiş/C++ bir tahminci ile birkaç mikrosaniyeye ulaşır.
Pratikte, birçok üretim sistemi iki aşamalı bir yaklaşım kullanır: gerçek zamanlı kotasyon için hızlı bir gradyan artırma modeli (gecikme açısından kritik) ve artırma modelinin parametrelerini ayarlamak veya ikincil bir sinyal sağlamak için asenkron çalışan bir derin öğrenme modeli.
Tahminden Kotasyona

Bir spread tahmini ancak daha iyi kotalara dönüştüğünde değerlidir. İşte tahmin edilen spread'i kullanan basitleştirilmiş bir kotasyon kuralı:
def compute_quotes(
mid: float,
predicted_spread_bps: float,
inventory: float,
max_inventory: float,
skew_factor: float = 0.5,
min_spread_bps: float = 1.0,
) -> tuple[float, float]:
"""
Compute bid/ask quotes from predicted spread and inventory.
Parameters
----------
mid : float
Current midprice.
predicted_spread_bps : float
Model-predicted spread in basis points.
inventory : float
Current inventory (positive = long).
max_inventory : float
Maximum allowed inventory.
skew_factor : float
How aggressively to skew quotes toward inventory neutrality.
min_spread_bps : float
Minimum spread floor (covers order processing costs).
Returns
-------
(bid, ask) : tuple[float, float]
"""
spread_bps = max(predicted_spread_bps, min_spread_bps)
half_spread = mid * spread_bps / 2e4
inv_ratio = inventory / max_inventory # in [-1, 1]
skew = skew_factor * inv_ratio * half_spread
bid = mid - half_spread - skew
ask = mid + half_spread - skew
return bid, ask
Envanter uzun olduğunda (), eğme hem alışı hem de satışı düşürür. Tek, tutarlı bir perspektiften: daha düşük bir satış, taker'ların bizden satın almasını ucuzlatır, bu da uzun envanterimizi boşaltır; daha düşük bir alış, satıcılar tarafından vurulma olasılığımızı azaltır, daha fazla birikmeyi yavaşlatır. Tahmin edilen spread genel genişliği kontrol eder — model volatilite veya ters seçim beklediğinde genişler, koşullar sakin olduğunda daralır.
Değerlendirme ve Geriye Dönük Test

Spread Tahmin Metrikleri
Standart regresyon metriklerinin (MAE, ) ötesinde, spread tahminlerini piyasa yapma için önemli olan metriklerle değerlendirin:
- Bir temel çizgi üzerindeki beceri: MAE/R²'yi her zaman son spread'lerin bir AR(1) veya EWMA tahminine göreli olarak raporlayın. Spread'ler güçlü biçimde kalıcı olduğundan, mutlak skor otokorelasyon tarafından domine edilir; yalnızca önemsiz bir temel çizgi üzerindeki iyileşme gerçek öngörü içeriğini yansıtır.
- Yönlü doğruluk: Model, spread'in genişleyip genişlemeyeceğini veya daralacağını doğru tahmin ediyor mu? Vasat MAE ama yüksek yönlü doğruluğa sahip bir model yine de kârlı olabilir.
- Kuyruk kapsamı: Model spread sıçramalarını tahmin ediyor mu? Spread değerlerinin en üst %5'i için MAE'yi ayrı hesaplayın — ters seçim kayıplarının yoğunlaştığı yer burasıdır.
- Kalibrasyon: Tahmin edilen ve gerçekleşen spread kuantillerini çizin. İyi kalibre edilmiş bir modelin 90. yüzdelik dilim tahmini, gerçekleşen spread'lerin 90. yüzdelik dilimiyle eşleşmelidir.
PnL Tabanlı Değerlendirme
Nihayetinde, önemli olan tek metrik gerçekleşen PnL'dir. Tam döngüyü geriye dönük test edin:
- Her zaman damgasında spread'i tahmin edin
- Tahmin edilen spread + envanter eğmesini kullanarak kotaları hesaplayın
- Geçmiş işlemlere karşı gerçekleşmeleri simüle edin
- Envanteri, gerçekleşen PnL'yi ve Sharpe oranını takip edin
Temel çizgilere karşı karşılaştırın: (a) sabit spread (zaman serisi medyanı), (b) son spread'lerin EWMA'sı ve (c) Roll tahmincisi.
Sonuç
Spread modelleme, finansal teori ile uygulamalı ML'nin kesişiminde yer alır. Klasik ayrıştırma — emir işleme, envanter ve ters seçim maliyetlerine — spread'lerin neden değiştiğine dair ekonomik sezgiyi sağlar. Roll'un modeli, minimum veriden zarif bir temel tahminci verir — onu fiyat birimleri cinsinden hesapladığınız sürece. Gradyan artırma modelleri, mikroyapı özelliklerini düşük gecikmeli çıkarımla doğru kısa ufuklu spread tahminlerine dönüştürür. Derin öğrenme mimarileri, doğrudan ham emir defteri verisinden öğrenir, el yapımı özelliklerin kaçırabileceği desenleri yakalar — mimari, seviyeler arası yapıyı havuzlayıp atmak yerine koruduğu sürece.
Bir üretim piyasa yapma sistemi için pratik öneri katmanlıdır:
- Spread bileşenlerinizi anlamak ve risk limitlerini kalibre etmek için çevrimdışı olarak Huang-Stoll ayrıştırmasını kullanın
- Bir mantık kontrolü olarak ve emir defteri verisinden yoksun olduğunuz enstrümanlar için Roll'un tahmincisini kullanın
- Gerçek zamanlı spread tahmini için bir LightGBM modeli dağıtın — hızlı, yorumlanabilir ve sağlamdır — temizlenmiş ileriye dönük doğrulama ve bir AR/EWMA kıyaslamasıyla
- Rejim değişikliklerini tespit etmek ve birincil modeli ayarlamak için ikincil bir döngüde bir CNN-LSTM veya Transformer modeli çalıştırın
Spread bir sayı değildir — bir sinyaldir. Onu ne kadar iyi modellerseniz (ve o modeli ne kadar dürüst ölçerseniz), likidite sağlamayı o kadar kesin fiyatlandırabilirsiniz.
Bu yazı, algoritmik piyasa yapma ve mikroyapı üzerine marketmaker.cc serisinin bir parçasıdır.
Yazarlar
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.