GaiaExGaiaEx
Finansal Veri Ambarları için SQL
GeliştiriciProgramlama9 min read

Finansal Veri Ambarları için SQL

Yapılandırılmış finansal veriyi sorgula, topla ve analiz et

Paylaş

SQL Neden Finansal Verinin Dili

Her işlem, her tick, her emir defteri güncellemesi — finansal piyasalar şaşırtıcı miktarda yapılandırılmış veri üretir. Ve dört on yılı aşkın süredir SQL (Yapılandırılmış Sorgu Dili), o veriyi sorgulamak, dönüştürmek ve analiz etmek için hâkim araç olmuştur. Gösterişli değildir. Trend değildir. Ama bir portföy yöneticisi „Geçen salı UTC 14:00 ile 15:00 arasında ETH işlemlerimdeki ortalama gerçekleşme fiyatım neydi?" diye sorduğunda, cevap SQL'den gelir.

Finansal veri özünde ilişkiseldir (relational). Bir işlem bir siparişe atıfta bulunur, sipariş bir hesaba atıfta bulunur, hesap bir kullanıcıya aittir. Pozisyonlar araçlarla ilişkilidir; araçlar piyasalarla ilişkilidir. İlişkisel model — yabancı anahtarlarla bağlanmış satır ve sütunlu tablolar — bu ilişkilere doğal olarak yansır. Bu yüzden PostgreSQL, MySQL ve SQL Server gibi ilişkisel veritabanları, Goldman Sachs'tan GaiaEx'e kadar finansal altyapının belkemiği olarak kalır.

SQL'in gücü, bildirimsel (declarative) doğasında yatar: ne istediğini tanımlarsın, nasıl elde edeceğini değil. Veritabanı motoru en uygun yürütme planını bulur. İşlem geçmişi tablonda 500 milyon satır olduğunda ve sonuçlara bir saniyenin altında ihtiyacın olduğunda bu son derece önemlidir. Doğru sorguyu yaz, doğru indeksi kur ve SQL sonuç verir — bunu yerel bir PostgreSQL örneğine karşı mı yoksa bulut ölçekli bir veri ambarına karşı mı çalıştırdığın önemli değildir.

Relational model (simplified) users user_id PK … accounts user_id FK → users tier, limits … trades user_id FK → accounts symbol, price, time … JOINs follow foreign keys — the shape of finance data is already relational.
İşlemler hesaplara ve kullanıcılara bağlıdır: SQL JOIN'lerinin geçtiği aynı bağlantılar.

SELECT, JOIN ve WHERE: İşlem Verisini Sorgulamak

Somut bir şemayla başlayalım. GaiaEx gibi bir platformda bir trades tablosu düşün:

CREATE TABLE trades (
    trade_id    BIGINT PRIMARY KEY,
    user_id     INT NOT NULL,
    symbol      VARCHAR(20) NOT NULL,
    side        VARCHAR(4) NOT NULL,  -- 'buy' or 'sell'
    price       NUMERIC(18,8) NOT NULL,
    quantity    NUMERIC(18,8) NOT NULL,
    fee         NUMERIC(18,8) DEFAULT 0,
    executed_at TIMESTAMPTZ NOT NULL
);

En temel işlem, filtrelemeli SELECT ifadesidir. Son 24 saatte $3,000'ın üzerindeki tüm ETH-USD alış işlemlerini bulmak için:

SELECT trade_id, price, quantity, executed_at
FROM trades
WHERE symbol = 'ETH-USD'
  AND side = 'buy'
  AND price > 3000
  AND executed_at > NOW() - INTERVAL '24 hours'
ORDER BY executed_at DESC;

JOIN'ler ilişkili tabloları birbirine bağlar. Bir accounts tablon olduğunu ve hesap kademesine (tier) göre işlem hacmini görmek istediğini varsayalım:

SELECT a.tier, COUNT(*) AS trade_count,
       SUM(t.price * t.quantity) AS total_volume
FROM trades t
JOIN accounts a ON t.user_id = a.user_id
WHERE t.executed_at > NOW() - INTERVAL '30 days'
GROUP BY a.tier
ORDER BY total_volume DESC;

Bu sorgular, finansal analistlerin, risk ekiplerinin ve uyum sorumlularının günlük ekmeğidir. Onları öğren ve verinin içerdiği her soruyu yanıtlayabilirsin.

Pencere Fonksiyonları: Analistin Gizli Silahı

Pencere fonksiyonları, finansta SQL başlangıç seviyesindekileri SQL uzmanlarından ayıran şeydir. Sonucu tek bir toplu değere çökertmeden, mevcut satırla ilişkili bir satır kümesi üzerinden hesaplamalar yapmana izin verirler. Onları verin üzerinde „çalışan hesaplamalar" olarak düşün.

Finansal analizde en yaygın kullanılan pencere fonksiyonları:

  • ROW_NUMBER() — Bir bölüm (partition) içindeki her satıra sıralı bir tam sayı atar. Tekilleştirme veya sembol başına N'inci işlemi seçmek için yararlıdır.
  • LAG() ve LEAD() — Önceki veya sonraki satırın değerine erişir. İşlemden işleme getirileri hesaplamak veya zaman serisi verisindeki boşlukları tespit etmek için gereklidir.
  • SUM() OVER () — Çalışan toplamlar. Kümülatif hacmi, kümülatif kâr/zararı veya çalışan pozisyon boyutunu hesapla.
  • AVG() OVER (ROWS BETWEEN) — Doğrudan SQL'de hareketli ortalamalar. Python'a gerek yok.

Pratik bir örnek — BTC için işlemden işleme fiyat değişimini ve çalışan pozisyonu hesaplamak:

SELECT executed_at, price, quantity, side,
       price - LAG(price) OVER (ORDER BY executed_at) AS price_change,
       SUM(CASE WHEN side = 'buy' THEN quantity ELSE -quantity END)
           OVER (ORDER BY executed_at) AS running_position
FROM trades
WHERE symbol = 'BTC-USD'
ORDER BY executed_at;

Pencere fonksiyonları satır sayını azaltmaz — GROUP BY'ın aksine, her girdi satırı bir çıktı satırı üretir. Bu onları, detaylı işlem günlüklerine ayrıntı kaybetmeden analitik sütunlar eklemek için mükemmel yapar. Onları verini „özetlemek" yerine „zenginleştirmek" olarak düşün.

OHLCV toplaması için — mum grafiklerinin temeli — GROUP BY'ı zaman-kovalama (time-bucketing) ve standart toplama fonksiyonlarıyla birleştirebilirsin:

SELECT date_trunc('hour', executed_at) AS bucket,
       (ARRAY_AGG(price ORDER BY executed_at))[1] AS open,
       MAX(price) AS high,
       MIN(price) AS low,
       (ARRAY_AGG(price ORDER BY executed_at DESC))[1] AS close,
       SUM(quantity) AS volume
FROM trades
WHERE symbol = 'ETH-USD'
GROUP BY bucket
ORDER BY bucket;

CTE'ler: Karmaşık Analitik Sorguları Bir Araya Getirmek

Ortak Tablo İfadeleri (CTE'ler), karmaşık bir sorguyu — programlamadaki fonksiyonlar gibi — adlandırılmış, okunabilir aşamalara bölmene izin verir. WITH anahtar sözcüğüyle tanıtılırlar ve birbirlerine sırayla atıfta bulunabilirler.

En kârlı 10 işlem gününü belirlemek istediğini ama kârlılığın ücretler dahil günlük net kâr/zarar hesaplamasını gerektirdiğini varsayalım. CTE'lerle bunu adım adım inşa edebilirsin:

WITH daily_trades AS (
    SELECT DATE(executed_at) AS trade_date,
           SUM(CASE WHEN side = 'sell' THEN price * quantity
                    ELSE -price * quantity END) AS gross_pnl,
           SUM(fee) AS total_fees
    FROM trades
    WHERE user_id = 42
    GROUP BY DATE(executed_at)
),
daily_pnl AS (
    SELECT trade_date,
           gross_pnl - total_fees AS net_pnl,
           SUM(gross_pnl - total_fees) OVER (ORDER BY trade_date) AS cumulative_pnl
    FROM daily_trades
)
SELECT trade_date, net_pnl, cumulative_pnl
FROM daily_pnl
ORDER BY net_pnl DESC
LIMIT 10;

Her CTE bir paragraf gibi okunur. daily_trades, ham işlemleri günlük özetlere toplar. daily_pnl, net kâr/zararı ve çalışan bir toplamı hesaplar. Son SELECT en iyi 10 günü seçer. Bunu tek, monolitik bir alt sorguyla karşılaştır — CTE versiyonu sürdürülebilir, test edilebilir ve kendini belgeleyendir.

Üretim finansal sistemlerinde, CTE'ler düzenleyici raporlamadan (hesaplar arası marj gereksinimlerini toplamak) gerçek zamanlı gösterge paneli sorgularına (GaiaEx'te dönen 24 saatlik hacmi hesaplamak) kadar her şey için kullanılır. İyi yapılandırılmış kodun bir araya geldiği gibi bir araya gelirler — her katman öncekinin üzerine inşa edilir.

Zaman Serisi Performansı İçin İndeksleme ve Bölümleme

Bir sorgu, onu destekleyen indeks kadar hızlıdır. Doğru indeks olmadan, basit bir WHERE cümlesi bile sıralı bir tarama (sequential scan) zorlar — tablodaki her satırı okumak. 500 milyon satırda, bu dakikalar değil milisaniyeler demektir.

Finansal zaman serisi verisi için en kritik indeks örüntüsü, (symbol, executed_at) üzerinde bir bileşik B-ağacı indeksidir:

CREATE INDEX idx_trades_symbol_time
ON trades (symbol, executed_at DESC);

Bu tek indeks, analitik sorguların çoğunu hızlandırır: „son saatteki tüm ETH işlemleri", „iki zaman damgası arasındaki BTC işlemleri" veya „sembol başına en son işlem". Sütun sırası önemlidir — önce symbol eşitlik filtrelemesini sağlar, sonra executed_at indeksin o bölümü içinde verimli aralık taramalarını sağlar.

Yüz milyonlarca satırın üzerine büyüyen tablolar için tablo bölümleme (partitioning) gereklidir. PostgreSQL, zaman serisi için mükemmel olan aralığa göre bildirimsel bölümlemeyi destekler:

CREATE TABLE trades (
    trade_id BIGINT, symbol VARCHAR(20),
    price NUMERIC(18,8), executed_at TIMESTAMPTZ
) PARTITION BY RANGE (executed_at);

CREATE TABLE trades_2026_q1 PARTITION OF trades
    FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE trades_2026_q2 PARTITION OF trades
    FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');

Bölümlemeyle, Mart 2026 verisi için bir sorgu yalnızca Q1 bölümünü tarar — motor Q2, Q3 ve Q4'ü tamamen atlar. Bölüm ayıklama (partition pruning) denen bu teknik, büyük geçmiş veri kümelerinde büyüklük mertebesinde hızlanmalar sağlar.

Time-range partition pruning Q1 2026 Jan–Mar rows Q2 2026 skipped Q3 2026 Q4 2026 Query: WHERE executed_at in March → planner reads only Q1 Composite index (symbol, executed_at) still helps within each partition
Bölümler zaman aralıklarını sınırlar; ayıklama eşleşemeyecek tüm parçaları atlar.

PostgreSQL vs ClickHouse vs BigQuery: Doğru Motoru Seçmek

Tüm veritabanları finansal analitiği eşit şekilde ele almaz. Seçim, sorgu örüntülerine, veri hacmine ve gecikme gereksinimlerine bağlıdır.

PostgreSQL iş beygiridir. İşlemsel yüklerde (gerçek zamanlı işlemleri kaydetmek) üstündür, ACID garantilerini destekler, karmaşık JOIN'leri zarifçe ele alır ve uygun indeksleme ve bölümlemeyle yüz milyonlarca satıra ölçeklenir. Operasyonel veritabanları — kayıt sistemi — için doğru seçimdir. Örneğin GaiaEx, finansal sistemlerin talep ettiği güvenilirlik garantileriyle çekirdek işlem verisi için PostgreSQL-uyumlu altyapıya güvenir.

ClickHouse, milyarlarca satır üzerinde analitik sorgular için tasarlanmış sütunsal bir veritabanıdır. PostgreSQL veriyi satır satır saklarken (bireysel işlemleri eklemek için harika), ClickHouse veriyi sütun sütun saklar (milyonlarca satır boyunca tek bir sütunu toplamak için harika). PostgreSQL'de 30 saniye süren „BTC için 3 yıl boyunca ortalama saatlik hacim" gibi bir sorgu ClickHouse'da 200 milisaniyede bitebilir. Ödünleşim: ClickHouse, UPDATE veya DELETE'i verimli şekilde desteklemez — tasarım itibarıyla sadece-ekleme (append-only) çalışır.

BigQuery (Google Cloud), sunucusuz bir sütunsal ambardır. Yönetilecek altyapı yok, sorgu başına ödeme fiyatlandırması ve petabayt ölçekli kapasite. Ad-hoc analitik, araştırma ve keşif için idealdir — ama sorgu gecikmesi milisaniyelerle değil saniyelerle ölçülür, bu yüzden gerçek zamanlı uygulamalar için uygun değildir.

  • Gerçek zamanlı işlem kaydı ve sipariş yönetimi → PostgreSQL
  • Milyarlarca satır üzerinde geçmiş analitik ve geriye dönük test → ClickHouse
  • Ad-hoc keşif ve ekipler arası veri paylaşımı → BigQuery

Birçok profesyonel işlem firması, katmanlı bir mimaride üçünü de kullanır: sıcak operasyonel mağaza olarak PostgreSQL, ılık analitik katman olarak ClickHouse ve soğuk arşiv olarak BigQuery. İyi SQL yazma becerisi hepsine transfer olur — sözdizimi %90 aynıdır ve analitik düşünme %100 aynıdır.