← Alle Insights

SQL für Marktdaten: jenseits von SELECT * FROM.

Die meisten Quants schreiben Backtest-Code in Python und Datenbank-Queries als SELECT * FROM bars. Dabei kann ein modernes PostgreSQL 80 % der Analyse-Aufgaben in einer einzigen Query erledigen — schneller, klarer und ohne Pandas-Speicher-Limit.

Warum SQL die Lingua Franca für Marktdaten ist.

SQL hat drei Eigenschaften, die für Marktdaten-Analyse unschlagbar sind: Deklarativ (Sie sagen was, nicht wie), set-orientiert (eine Query auf 100 Mio. Rows ist nicht komplexer als auf 100), und stabil (der Standard ist 40 Jahre alt; Ihr Code funktioniert auch in zehn Jahren).

PostgreSQL mit der TimescaleDB-Extension ist mein Default-Backend: Hypertables für Zeitreihen, kompletter Postgres-SQL-Sprachumfang, riesiges Tooling-Ökosystem. Alles, was hier gezeigt wird, läuft genauso auf einem schlanken Postgres ohne Extension.

Window-Functions: OHLC aus Ticks.

Window-Functions sind das Rückgrat jeder ernsthaften SQL-Analyse. Sie erlauben Berechnungen über Zeilen-Fenster, ohne dass Sie joinen oder gruppieren müssen. OHLC-Bars aus einem Tick-Stream — ohne Python-Loop:

SELECT
    time_bucket('1 minute', ts) AS bucket,
    symbol,
    first_value(price) OVER w AS open,
    max(price)         OVER w AS high,
    min(price)         OVER w AS low,
    last_value(price)  OVER w AS close,
    sum(size)          OVER w AS volume
FROM ticks
WHERE symbol = 'AAPL'
  AND ts >= now() - INTERVAL '1 day'
WINDOW w AS (
    PARTITION BY symbol, time_bucket('1 minute', ts)
    ORDER BY ts
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
);

In TimescaleDB ist first(price, ts)/last(price, ts) sogar noch eleganter. Aber das Prinzip ist universell: aggregierte Spalten und Detailspalten in einer Query, ohne zweite Abfrage.

Common-Table-Expressions: lesbare Backtest-Queries.

CTEs (WITH ... AS) machen aus einer 200-Zeilen-Query etwas, das man ohne Whiteboard versteht. Ein einfacher Momentum-Backtest:

WITH daily AS (
    SELECT ts, symbol, close,
           close / lag(close, 20) OVER (PARTITION BY symbol ORDER BY ts) - 1
               AS ret_20d
    FROM bars
    WHERE timeframe = '1d'
),
signals AS (
    SELECT ts, symbol,
           CASE WHEN ret_20d > 0.05 THEN 1
                WHEN ret_20d < -0.05 THEN -1
                ELSE 0 END AS signal
    FROM daily
),
trades AS (
    SELECT s.ts, s.symbol, s.signal,
           lead(b.close) OVER (PARTITION BY s.symbol ORDER BY s.ts)
               / b.close - 1 AS next_day_ret
    FROM signals s
    JOIN bars b USING (ts, symbol)
    WHERE b.timeframe = '1d' AND s.signal <> 0
)
SELECT symbol,
       count(*) AS trades,
       avg(signal * next_day_ret) AS avg_ret,
       sum(signal * next_day_ret) AS total_ret
FROM trades
GROUP BY symbol
ORDER BY total_ret DESC;

Eine Query, lesbar in Stufen: Daily-Returns berechnen → Signale ableiten → Folge-Tag-Returns joinen → aggregieren. Kein Pandas, kein Loop, keine Speicher-Probleme.

Rolling-Calculations ohne Python-Loop.

Moving Averages, Rolling Volatility, Rolling Sharpe — alles native SQL über ROWS BETWEEN:

SELECT ts, symbol, close,
       avg(close) OVER w20  AS sma_20,
       avg(close) OVER w50  AS sma_50,
       stddev_samp(close / lag(close) OVER (PARTITION BY symbol ORDER BY ts) - 1)
           OVER w20 * sqrt(252) AS vol_ann
FROM bars
WHERE timeframe = '1d'
WINDOW
    w20 AS (PARTITION BY symbol ORDER BY ts ROWS BETWEEN 19 PRECEDING AND CURRENT ROW),
    w50 AS (PARTITION BY symbol ORDER BY ts ROWS BETWEEN 49 PRECEDING AND CURRENT ROW);

Performance: Indexes und Partitioning.

Eine Marktdaten-Tabelle ohne ordentlichen Index ist eine Frage der Zeit, bis sie unbenutzbar wird. Die zwei wichtigsten Hebel:

CREATE INDEX ON bars (symbol, ts DESC);
CREATE INDEX ON ticks (symbol, ts DESC);

-- TimescaleDB Hypertable
SELECT create_hypertable('ticks', 'ts',
    chunk_time_interval => INTERVAL '1 day');

-- Compression für ältere Chunks
ALTER TABLE ticks SET (timescaledb.compress,
    timescaledb.compress_segmentby = 'symbol');
SELECT add_compression_policy('ticks', INTERVAL '7 days');

Mit Compression schrumpfen Tick-Daten typischerweise um Faktor 10–15. Bei einem Mandanten haben wir damit 2,3 TB unkomprimierte Ticks auf 190 GB gedrückt — bei voller SQL-Verfügbarkeit.

Sektor-Rotation-Backtest in einer Query.

Das wahre Argument für SQL: Komplette Backtests, die in Python 200 Zeilen wären, in einer Query. Sektor-Rotation auf monatlicher Basis, top-3 nach 6-Monats-Return:

WITH monthly AS (
    SELECT date_trunc('month', ts) AS month, sector,
           last(close, ts) AS close
    FROM bars b
    JOIN symbol_master m USING (symbol)
    WHERE timeframe = '1d'
    GROUP BY 1, 2
),
ranked AS (
    SELECT month, sector, close,
           close / lag(close, 6) OVER (PARTITION BY sector ORDER BY month) - 1
               AS ret_6m
    FROM monthly
),
picks AS (
    SELECT month, sector, ret_6m,
           rank() OVER (PARTITION BY month ORDER BY ret_6m DESC) AS rnk
    FROM ranked
    WHERE ret_6m IS NOT NULL
),
fwd AS (
    SELECT p.month, p.sector,
           lead(r.close) OVER (PARTITION BY p.sector ORDER BY p.month) / r.close - 1
               AS next_month_ret
    FROM picks p
    JOIN monthly r USING (month, sector)
    WHERE p.rnk <= 3
)
SELECT month, avg(next_month_ret) AS portfolio_ret
FROM fwd
GROUP BY month
ORDER BY month;

Diese Query läuft auf 30 Jahren Sektor-Daten in deutlich unter einer Sekunde. Dieselbe Logik in Pandas mit Multi-Index-DataFrames ist nicht nur länger, sondern auch fehleranfälliger (Stichwort: Look-Ahead-Bias bei falscher Shift-Reihenfolge).

Warum SQL Python in 80 % der Fälle schlägt.

Meine Praxis: SQL für Aggregation, Python für ML.

Die Grenze, die ich konsequent ziehe: Alles, was Aggregation, Filterung, Joins, Window-Functions ist — SQL. Alles, was Modelle trainiert, Optimierer aufruft oder mit Numpy-Matrizen rechnet — Python. Das Übergabeformat ist ein DataFrame, der aus einer einzigen sauberen Query kommt.

Konkret: Feature-Engineering für ein Klassifikations-Modell baue ich zu 90 % in SQL. Was rauskommt, ist eine fertige Feature-Tabelle, die mit read_sql in Pandas wandert. Das Modell-Training selbst ist Python — scikit-learn, XGBoost, manchmal PyTorch. Backtests laufen wieder zur Hälfte in SQL (Aggregationen, Signal-Joins), zur Hälfte in Python (Execution-Simulation, Reporting).

Wer in seinem Quant-Stack SQL ernsthaft nutzt, gewinnt Geschwindigkeit, Klarheit und Reviewbarkeit auf einen Schlag. Es ist die unterschätzteste Investition, die ich kenne.

Sie wollen Ihre Marktdaten-Analyse aus Pandas-Notebooks in eine saubere SQL-Pipeline überführen? Erstgespräch buchen — wir bauen das Setup, das Ihr Team auch in fünf Jahren noch versteht.