← Makalelere geri dön
March 4, 2026
5 dakikalık okuma

Algoritmik Trading için QuestDB: Oyunu Değiştiren SQL Uzantıları

Algoritmik Trading için QuestDB: Oyunu Değiştiren SQL Uzantıları
#QuestDB
#SQL
#ASOF JOIN
#SAMPLE BY
#zaman serisi
#algoritmik trading

3'lü serinin 2. Bölümü — ayrıca şurada mevcuttur: RU · ZH

Sorumluluk Reddi: Bu makalede sunulan bilgiler yalnızca eğitim ve bilgilendirme amaçlıdır; finansal, yatırım veya ticaret tavsiyesi niteliği taşımaz. Kripto para ticareti önemli kayıp riski içerir.


QuestDB serimizin 2. Bölümüne hoş geldiniz. 1. Bölüm'de, üç katmanlı depolama mimarisini ve şema tasarım ilkelerini ele aldık. Şimdi ise gerçek anlamda ayrışmayı sağlayan özellik setine — QuestDB'yi sanki traderlar tarafından, traderlar için tasarlanmış gibi hissettiren SQL uzantılarına — giriyoruz.

Standart SQL, 1970'lerde ilişkisel veriler için oluşturuldu. Zamanı birinci sınıf bir kavram olarak tanımaz. PostgreSQL veya MySQL'de her zaman serisi işlemi uzun çözüm yolları gerektirir — birbirinin üstüne yığılmış pencere fonksiyonları, lateral join'ler, üç kat CTE. QuestDB'nin uzantıları bu çok paragraflı sorguları tek, ifadeli cümlelere indirger.

Gerçek trading örnekleriyle her birini inceleyelim.

SAMPLE BY: ham işlemler OHLCV mumlarına toplu hale getirilir

SAMPLE BY: Yerel Zaman Dilimleme Toplulaştırması

Her trading sisteminin diğerlerinden daha sık çalıştırdığı sorgu varsa, o da OHLCV toplulaştırmasıdır — ham işlemleri mum çubuğu verisine dönüştürme. Standart SQL'de şöyle bir şey yazarsınız:

-- Standart SQL: ayrıntılı ve yavaş
SELECT
  date_trunc('minute', timestamp) AS bucket,
  symbol,
  (array_agg(price ORDER BY timestamp))[1] AS open,
  max(price) AS high,
  min(price) AS low,
  (array_agg(price ORDER BY timestamp DESC))[1] AS close,
  sum(quantity) AS volume
FROM trades
WHERE timestamp >= now() - interval '1 hour'
GROUP BY bucket, symbol
ORDER BY bucket;

QuestDB'de bu şu hale gelir:

SELECT timestamp, symbol,
  first(price) AS open,
  max(price) AS high,
  min(price) AS low,
  last(price) AS close,
  sum(quantity) AS volume
FROM trades
WHERE timestamp IN today()
SAMPLE BY 1m;

Hepsi bu kadar. SAMPLE BY 1m, QuestDB'ye veriyi 1 dakikalık aralıklara bölmesini söyler; first() / last() ise her dilim içindeki zaman sıralamasını gözeten yerel toplulaştırma fonksiyonlarıdır. date_trunc yok, array_agg cambazlığı yok, açık GROUP BY yok.

Kullanılabilir aralıklar esnektir: 1s, 5s, 15m, 1h, 1d, 7d — zaman birimlerinin herhangi bir kombinasyonu. Hiç uyumayan kripto piyasaları için saat dilimi farkındalığıyla takvim sınırlarına hizalanabilirsiniz:

SAMPLE BY 1d ALIGN TO CALENDAR TIME ZONE 'UTC';

FILL: Veri Boşluklarını Yönetme

Gerçek piyasalarda boşluklar olur — likidite düşük çiftler dakikalar veya saatler boyunca işlem görmeyebilir. SAMPLE BY, birkaç FILL stratejisini destekler:

-- Önceki değerle doldur (öne taşıma — finansta yaygın)
SAMPLE BY 15m FILL(PREV);

-- Doğrusal enterpolasyonla doldur
SAMPLE BY 15m FILL(LINEAR);

-- Sabit bir değerle doldur
SAMPLE BY 15m FILL(0);

-- Doldurmama — NULL döndür (varsayılan)
SAMPLE BY 15m FILL(NONE);

FILL(PREV), trading gösterge panolarının temel taşıdır — 15 dakikalık bir dilimde işlem gerçekleşmediyse son bilinen fiyatı ileriye taşır. FILL(LINEAR), fonlama oranları veya faiz oranları gibi sürekli sinyaller için daha uygundur.

ASOF JOIN işlemler ve kotasyonların zamansal hizalaması

ASOF JOIN: Piyasa Verisi Hizalamasının "Tam Bunu Kastediyorum" Çözümü

Bu, QuestDB'nin baş mücevheridir ve piyasa verisiyle çalıştıysanız nedenini hemen anlarsınız.

Temel sorun şudur: bir tabloda işlemler, başka bir tabloda kotasyonlar (alış/satış) var. Her işlemin tam olarak gerçekleştiği anda geçerli olan kotasyonu bilmek istiyorsunuz. Normal bir veritabanında zaman damgaları neredeyse hiçbir zaman mükemmel hizalanmaz — 12:00:00.123'teki bir işlem, 12:00:00.201'deki değil, 12:00:00.098'deki bir kotasyonla eşleşmesi gerekir.

ASOF JOIN bunu tek satırda çözer:

SELECT trades.*, quotes.bid, quotes.ask
FROM trades
ASOF JOIN quotes ON (symbol);

trades'deki her satır için QuestDB, quotes içinde işlemin zaman damgasından küçük veya eşit olan en son zaman damgasına sahip satırı bulur; symbol sütunuyla eşleştirir. Bağıntılı alt sorgu yok. Pencere fonksiyonu yok. Uygulama katmanı mantığı yok.

Bu, İşlem Maliyet Analizi'nin (TCA) temelidir — yürütme fiyatınızı yürütme anındaki geçerli piyasayla karşılaştırma. PostgreSQL'de eşdeğer sorgu, her satır için ORDER BY ve LIMIT 1 ile LATERAL JOIN gerektirir; bu da büyük veri kümelerinde katbekat daha yavaştır.

TOLERANCE: Eski Veri Birleştirmelerini Önleme

İşte toy uygulamaları üretim sistemlerinden ayıran bir incelik. Bir işlem anında az işlem gören bir varlığa ait kotasyon 5 dakika öncesine aitse ne olur? Oynak bir piyasada 5 dakika önceki kotasyon özünde işe yaramaz. Varsayılan ASOF JOIN yine de onu kullanır — ne kadar eski olursa olsun en son eşleşmeyi bulur.

QuestDB'nin TOLERANCE cümlesi bunu düzeltir:

SELECT trades.*, quotes.bid, quotes.ask
FROM trades
ASOF JOIN quotes ON (symbol)
TOLERANCE 1s;

Artık işlemden 1 saniye içinde eşleşen bir kotasyon yoksa, birleştirme eski veri yerine NULL döndürür. Bu, doğru TCA ve veri tazeliğinin önem taşıdığı her analitik için kritiktir.

Ekstra bir kazanım olarak, TOLERANCE sorgu performansını önemli ölçüde iyileştirebilir. TOLERANCE olmadan, motor bir eşleşme ararken kotasyonlar tablosunda çok geriye tarayabilir. TOLERANCE ile, kayıtlar uygunluk için çok eski olduğunda geriye dönük taramayı erken sonlandırabilir.

LT JOIN ve SPLICE JOIN

Bilmekte fayda olan iki varyasyon: LT JOIN, ASOF JOIN gibidir ancak zaman damgasından kesinlikle önce eşleşir (eşit değil). Bu, geriye dönük testlerde ileriye bakış yanlılığından kaçınmanız gerektiğinde faydalıdır — aynı mikrosaniyede gelen değil, işleminizden önce var olan kotasyonu istersiniz.

SPLICE JOIN, her iki yönde tam bir ASOF'tur: sol tablodaki her kayıt için geçerli sağ tablo kaydını, sağ tablodaki her kayıt için geçerli sol tablo kaydını bulur. Sonuç, her iki veri kaynağının birleştirilmiş, iç içe geçmiş bir zaman çizelgesidir. Bu, birden fazla veri akışından birleşik olay zaman çizelgeleri oluşturmak için özellikle kullanışlıdır.

HORIZON JOIN: Tek Sorguda İşlem Sonrası Analiz

QuestDB 9.3.3'te tanıtılan HORIZON JOIN, markout analizi için özel olarak oluşturulmuştur — yürütme kalitesi değerlendirmesinin ve piyasa mikro yapı araştırmasının temel taşı.

Cevapladığı soru şudur: "Bir işlem yürütüldükten sonra fiyat, sonraki N saniyede nasıl bir seyir izledi?" Geleneksel olarak bu, öz birleştirmeler, birden fazla ASOF sorgusu üzerinde UNION ALL veya mantığı uygulama koduna taşıma gerektirir. HORIZON JOIN hepsini bir araya sığdırır:

SELECT h.offset / 1000000 AS offset_sec,
  avg(mid.price - fill.price) AS avg_markout
FROM fills
HORIZON JOIN mid_prices ON (symbol)
RANGE BETWEEN 0 AND 60s STEP 1s;

Bu, her işlemden sonra 60 saniyeye kadar 1 saniyelik aralıklarla ortalama fiyat hareketini hesaplar. Motor, zaman ofseti hesaplamasını, her ufuk noktasındaki ASOF eşleştirmesini ve toplulaştırmayı — hepsini tek bir geçişte — yönetir.

Düzensiz ufuklar için veya olaydan önce bakmak için LIST sözdizimini kullanın:

HORIZON JOIN mid_prices ON (symbol)
LIST (-5s, -1s, 0, 1s, 5s, 30s, 60s);

Bu, hem işlem öncesi hem de işlem sonrası markout eğrilerini verir. Venue, strateji veya emir büyüklüğüne göre filtrelemeyle birleştirilerek veritabanı içinde eksiksiz bir yürütme kalitesi çerçevesi oluşturulabilir.

QuestDB'nin yemek kitabı, HORIZON JOIN üzerine kurulu beş işlem sonrası analiz kalıbı içerir: kayma analizi (yürütme ile orta fiyat), markout eğrileri, uygulama açığı (Perold ayrıştırması), akıllı emir yönlendirmesi için venue puanlama ve akış toksisite tespiti (VPIN). Her biri gerçek verilerle canlı demolarında çalışır.

WINDOW JOIN: Olayları Çevreleyen Verilerle İlişkilendirme

WINDOW JOIN, QuestDB 9.3'te tanıtıldı. Birincil tablodaki her satırın, başka bir tablodaki satırların zaman penceresine bağlanmasına ve eşleşen satırlar üzerinde toplulaştırmaların hesaplanmasına olanak tanır.

Her işlemi, işlemden sonraki 10 saniyedeki ortalama alış ve satış fiyatlarıyla ilişkilendirmek istediğiniz bir FX trading senaryosunu düşünün:

SELECT
  trades.timestamp,
  trades.symbol,
  trades.price,
  avg(quotes.bid) AS avg_bid,
  avg(quotes.ask) AS avg_ask
FROM trades
WINDOW JOIN quotes ON (symbol)
RANGE BETWEEN 0 AND 10s FOLLOWING
INCLUDE PREVAILING;

INCLUDE PREVAILING cümlesi, pencere sınırında tam eşleşme olmasa bile en son fiyatı almanızı sağlar. Bu, kısa vadeli analizlerin genellikle gerektirdiği sayfalarca alt sorguyu ortadan kaldırır.

Trading'deki kullanım örnekleri: her yürütme etrafındaki ortalama piyasa koşullarını hesaplama, büyük emirlerden önce bir zaman penceresinde olağandışı fiyat davranışını tespit etme, IoT/altyapı olaylarını (ağ gecikmesi artışları) yürütme kalitesiyle ilişkilendirme.

LATEST ON: Anlık Mevcut Durum

Yanıltıcı şekilde basit ama inanılmaz derecede kullanışlı bir uzantı. LATEST ON, bir bölümleme sütununun her değeri için son satırı döndürür:

SELECT * FROM trades
LATEST ON timestamp PARTITION BY symbol, side
ORDER BY timestamp DESC;

Bu, her (symbol, side) çifti için son işlemi verir — özünde gerçek zamanlı bir "mevcut durum" anlık görüntüsü. Geleneksel bir veritabanında bu, bağıntılı bir alt sorgu veya ROW_NUMBER() içeren bir pencere fonksiyonu gerektirir.

Yüzlerce sembol genelinde en son fiyatı gösteren trading gösterge panolarında LATEST ON anında sonuç verir. Materyalize görünümlerle birlikte (3. Bölüm'de ele alacağımız), milisaniyenin altında portföy anlık görüntülerinin temeli haline gelir.

TWAP: Yerel Zaman Ağırlıklı Ortalama

twap(price, timestamp) toplulaştırması QuestDB 9.3.3'te eklendi. Hacme göre ağırlıklandıran VWAP'ın aksine, TWAP süreye göre ağırlıklandırır — her fiyat bir sonraki gözleme kadar geçerli olur ve sonuç, adım fonksiyonunun altındaki alanın toplam süreye bölümüdür.

SELECT symbol,
  twap(price, timestamp) AS twap_value,
  vwap(price, quantity) AS vwap_value
FROM trades
WHERE timestamp IN today()
SAMPLE BY 1h;

TWAP, algoritmik emirler için standart yürütme kıyaslamasıdır. Paralel GROUP BY ve tüm FILL modlarıyla SAMPLE BY'ı destekleyen yerel bir toplulaştırma olarak sahip olmak, herhangi bir istemci tarafı entegrasyonuna ihtiyaç duymamanızı sağlar — hesaplama tamamen sorgu motorunun içinde çalışır.

Pencere Fonksiyonları: Teknik Analizin Temeli

QuestDB, teknik gösterge hesaplamasının omurgasını oluşturan standart SQL pencere fonksiyonlarını destekler:

-- Materyalize OHLC görünümü kullanarak Bollinger Bantları
WITH stats AS (
  SELECT timestamp, close,
    AVG(close) OVER (
      ORDER BY timestamp
      ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
    ) AS sma20,
    AVG(close * close) OVER (
      ORDER BY timestamp
      ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
    ) AS avg_close_sq
  FROM trades_OHLC_15m
  WHERE timestamp BETWEEN dateadd('h', -24, now()) AND now()
    AND symbol = 'BTC-USDT'
)
SELECT timestamp,
  sma20,
  sma20 + 2 * sqrt(avg_close_sq - sma20 * sma20) AS upper_band,
  sma20 - 2 * sqrt(avg_close_sq - sma20 * sma20) AS lower_band
FROM stats;

QuestDB 9.3.3 ayrıca SQL standardı WINDOW cümlesini tanıttı — bir pencere belirtimini bir kez tanımlayın, birden fazla fonksiyon genelinde ada göre referans alın. Artık her ifadede aynı PARTITION BY ve ORDER BY'ı tekrarlamak yok:

SELECT timestamp, symbol,
  avg(price) OVER w AS avg_price,
  stddev(price) OVER w AS std_price,
  min(price) OVER w AS min_price,
  max(price) OVER w AS max_price
FROM trades
WINDOW w AS (PARTITION BY symbol ORDER BY timestamp ROWS BETWEEN 99 PRECEDING AND CURRENT ROW);

Daha temiz sorgular, daha az kopyala-yapıştır, aynı yürütme performansı.

Gerçek Dünya Sorgu Kalıpları

Üretim trading sistemlerinde sürekli karşılaşılan bazı kalıpları göstereyim:

Çapraz Varlık Korelasyonu

-- ETH ile başka bir varlık arasında saatlik ve günlük yuvarlanan korelasyon
WITH data AS (
  SELECT ETHUSD.timestamp,
    corr(ETHUSD.price, asset.price) AS corr
  FROM ETHUSD
  ASOF JOIN asset
  SAMPLE BY 1m
)
SELECT timestamp,
  avg(corr) OVER (ORDER BY timestamp
    RANGE BETWEEN 1 HOUR PRECEDING AND CURRENT ROW) AS hourly_corr,
  avg(corr) OVER (ORDER BY timestamp
    RANGE BETWEEN 24 HOUR PRECEDING AND CURRENT ROW) AS daily_corr
FROM data;

Tüm Semboller İçin RSI

-- Tüm USDT çiftleri için 14 günlük RSI
WITH gains_losses AS (
  SELECT timestamp, symbol,
    CASE WHEN close > lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)
      THEN close - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)
      ELSE 0 END AS gain,
    CASE WHEN close < lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)
      THEN lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) - close
      ELSE 0 END AS loss
  FROM trades_latest_1d
  WHERE symbol LIKE '%-USDT'
)
SELECT timestamp, symbol,
  100 - 100 / (1 + avg_gain / NULLIF(avg_loss, 0)) AS rsi
FROM (
  SELECT timestamp, symbol,
    AVG(gain) OVER (PARTITION BY symbol ORDER BY timestamp
      ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) AS avg_gain,
    AVG(loss) OVER (PARTITION BY symbol ORDER BY timestamp
      ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) AS avg_loss
  FROM gains_losses
);

ATR (Ortalama Gerçek Aralık)

-- 15 dakikalık OHLC çubuklarından 14 periyotlu ATR
WITH tr AS (
  SELECT timestamp, symbol,
    GREATEST(
      high - low,
      ABS(high - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)),
      ABS(low - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp))
    ) AS true_range
  FROM trades_OHLC_15m
)
SELECT timestamp, symbol,
  AVG(true_range) OVER (PARTITION BY symbol ORDER BY timestamp
    ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) AS atr_14
FROM tr;

Özel Koda Karşı SQL'in Avantajı

Şunu merak edebilirsiniz: ham veriyi Python'a çekip ta-lib veya pandas kullanabilecekken neden bu göstergeleri veritabanı içinde hesaplayalım?

Üç neden var. Birincisi, veri yerelliği — kayan bir ortalama hesaplamak için ağ üzerinden terabaytlarca tick verisi taşımak israftır. Hesaplama, verinin bulunduğu yerde yaşamalıdır. İkincisi, paralellik — JIT derleme ile QuestDB'nin vektörize motoru, bu sorguları SIMD talimatlarıyla birden fazla çekirdek genelinde işleyebilir; çoğu zaman tek iş parçacıklı Python kodundan daha hızlı. Üçüncüsü, tutarlılık — birden fazla gösterge paneli, strateji ve izleme sistemi aynı göstergelere ihtiyaç duyduğunda, veritabanında tek bir gerçek kaynağına sahip olmak senkronizasyon hatalarını ortadan kaldırır.

Bununla birlikte, QuestDB tüm analitik yığınınızı değiştirmeye çalışmıyor. Parquet birlikte çalışabilirliği, toplu iş yükleri için veritabanından geçmeden geçmiş verileri doğrudan ML pipeline'ınıza çekmenizi sağlar.

3. Bölümde Neler Var

Son bölümde gerçek zamanlı OHLC için materyalize görünümleri (birden fazla zaman diliminde basamaklı görünümler dahil), yerel emir defteri analitiği için 2D dizileri ve eksiksiz bir QuestDB destekli algoritmik trading platformu için referans mimarisini ele alacağız.

Atıf

@software{soloviov2025questdb_algotrading_p2,
  author = {Soloviov, Eugen},
  title = {QuestDB for Algorithmic Trading: SQL Extensions That Change the Game},
  year = {2025},
  url = {https://marketmaker.cc/tr/blog/post/questdb-algotrading-sql},
  version = {0.1.0},
  description = {QuestDB'nin zaman serisi SQL uzantılarına derinlemesine bakış: SAMPLE BY, ASOF JOIN, HORIZON JOIN, WINDOW JOIN, LATEST ON ve gerçek dünya trading sorgu kalıpları.}
}
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.