GCP und BigQuery für Marktdaten in Größe.
Wer einmal versucht hat, ein Jahrzehnt an Tick-Daten in einer lokalen Postgres-Datenbank zu analysieren, weiß warum BigQuery existiert. Aber BigQuery ist auch ein Werkzeug, mit dem man sehr schnell sehr viel Geld verbrennen kann. In diesem Artikel zeige ich Ihnen, wie ich Marktdaten in BigQuery organisiere, was sich rechnet und wo die typischen Fallen liegen.
Warum BigQuery für Marktdaten?
Marktdaten haben eine Eigenschaft, die klassische Datenbanken schlecht abbilden: sie wachsen linear mit der Zeit und werden fast nie verändert, nur ergänzt. Eine Append-only- Workload mit gelegentlichen analytischen Scans über lange Zeiträume — das ist exakt das Profil, für das BigQuery gebaut wurde.
Konkrete Zahlen aus einem realen Projekt: ein Datensatz von zehn Jahren US-Aktien-Minuten-
Bars umfasst rund 8 Milliarden Zeilen. In Postgres mit guten Indizes brauchen analytische
Queries Minuten bis Stunden. In BigQuery, mit Partitionierung auf trade_date
und Clustering auf symbol, antworten dieselben Queries in zwei bis fünf
Sekunden. Der Unterschied wird relevant, sobald Sie hundert Strategien parallel backtesten
wollen — oder ein ML-Feature über alle Symbole gleichzeitig berechnen.
Die richtige Tabellenstruktur.
BigQuery rechnet pro gescanntem Byte ab. Eine falsche Tabellenstruktur kann jede Query zur Vollkostenrechnung machen — selbst wenn Sie nur einen Tag und ein Symbol brauchen. Die Lösung sind zwei Konzepte: Partitionierung reduziert die zu scannenden Partitionen anhand eines Datums- oder Integer-Filters, Clustering sortiert Daten innerhalb einer Partition und beschleunigt Filter auf weiteren Spalten.
-- Standard-Setup für Minuten-Bars
CREATE TABLE marketdata.us_equities_1min (
trade_date DATE NOT NULL,
timestamp_utc TIMESTAMP NOT NULL,
symbol STRING NOT NULL,
open NUMERIC(18, 6),
high NUMERIC(18, 6),
low NUMERIC(18, 6),
close NUMERIC(18, 6),
volume INT64,
vwap NUMERIC(18, 6),
trade_count INT64,
source STRING
)
PARTITION BY trade_date
CLUSTER BY symbol, timestamp_utc
OPTIONS (
description = "US-Aktien 1-Minuten-Bars, partitioniert nach Handelstag, geclustert nach Symbol",
require_partition_filter = TRUE,
partition_expiration_days = NULL
);
Drei wichtige Details. Erstens require_partition_filter = TRUE — das zwingt
jede Query, einen Filter auf trade_date anzugeben. Ohne diese Option scannt
ein vergessenes SELECT * die ganze Tabelle und kostet 50 Euro statt zehn
Cent. Zweitens CLUSTER BY symbol, timestamp_utc — die Reihenfolge ist
wichtig: häufiger gefilterte Spalten zuerst. Drittens kein partition_expiration —
historische Marktdaten sollen nicht von BigQuery automatisch gelöscht werden.
Ingest-Pattern.
Marktdaten kommen typischerweise als End-of-Day-Files oder als Live-Streams. Für
End-of-Day-Workloads ist der direkteste Weg: CSV oder Parquet nach Google Cloud Storage,
dann per bq load in eine staging-Tabelle, dann mit einer
INSERT INTO ... SELECT die Daten in die produktive Tabelle übernehmen.
Niemals direkt in die produktive Tabelle schreiben — wenn ein File defekt ist, haben Sie
korrupte Daten und keinen sauberen Rollback.
#!/usr/bin/env bash
# scripts/load_eod.sh — täglicher End-of-Day-Load
set -euo pipefail
TRADE_DATE="${1:-$(date -I -d 'yesterday')}"
BUCKET="gs://mg-marketdata-eod"
PROJECT="mg-quant-prod"
DATASET="marketdata"
# 1. Load in staging
bq load \
--project_id="${PROJECT}" \
--source_format=PARQUET \
--replace=true \
"${DATASET}.staging_us_equities_1min" \
"${BUCKET}/us_equities_1min/${TRADE_DATE}/*.parquet"
# 2. Sanity-Check: Zeilenanzahl pro Symbol prüfen
ROWS=$(bq query --project_id="${PROJECT}" --use_legacy_sql=false --format=csv \
"SELECT COUNT(*) FROM \`${PROJECT}.${DATASET}.staging_us_equities_1min\`" | tail -n 1)
if [ "${ROWS}" -lt 1000000 ]; then
echo "FEHLER: Nur ${ROWS} Zeilen geladen, erwartet > 1M. Abbruch."
exit 1
fi
# 3. Merge in produktive Tabelle (idempotent)
bq query --project_id="${PROJECT}" --use_legacy_sql=false \
"MERGE \`${PROJECT}.${DATASET}.us_equities_1min\` T
USING \`${PROJECT}.${DATASET}.staging_us_equities_1min\` S
ON T.trade_date = S.trade_date
AND T.symbol = S.symbol
AND T.timestamp_utc = S.timestamp_utc
WHEN NOT MATCHED THEN INSERT ROW;"
echo "Load fuer ${TRADE_DATE} abgeschlossen: ${ROWS} Zeilen."
Das Skript läuft per Cloud Scheduler täglich um 23:00 UTC und triggert eine Cloud
Function, die wiederum den bq load orchestriert. Wichtig ist der Sanity-Check
— eine Strategie, die sich auf fehlerhafte Daten verlässt, kostet mehr als der ganze
Datenstack.
Query-Patterns für Backtests.
Der Klassiker beim BigQuery-Einstieg: ein Anfänger schreibt SELECT * FROM
marketdata.us_equities_1min WHERE symbol = 'AAPL' und ist erstaunt, dass die
Query 12 Euro kostet. Grund: ohne Partitionsfilter scannt BigQuery alle Daten der
Tabelle, auch wenn nachher gefiltert wird.
Richtig macht man es so: erst Partition filtern, dann Cluster nutzen, nur die Spalten selektieren, die Sie wirklich brauchen.
-- 12 Monate AAPL-Daten fuer einen Backtest
SELECT
timestamp_utc,
open,
close,
volume
FROM `mg-quant-prod.marketdata.us_equities_1min`
WHERE trade_date BETWEEN DATE '2025-01-01' AND DATE '2025-12-31'
AND symbol = 'AAPL'
ORDER BY timestamp_utc;
Diese Query scannt rund 200 MB statt 800 GB — Faktor 4000 weniger Kosten. Bei einem Backtest, der über ein Hundert Symbole iteriert, ist das der Unterschied zwischen 10 Cent und 400 Euro.
BigQuery ML für Quant-Modelle.
BigQuery ML erlaubt es, ML-Modelle direkt in SQL zu trainieren. Für quantitative Strategien ist das nicht das richtige Werkzeug für das finale Modell — dafür sind die Constraints zu hart. Aber für Feature-Exploration und schnelle Baselines ist es ausgezeichnet: ein logistisches Regressionsmodell über zehn Jahre Aktiendaten zu trainieren, dauert in BigQuery ML wenige Minuten und kostet wenige Euro.
-- Baseline: vorhersagen, ob naechster Tag positive Rendite hat
CREATE OR REPLACE MODEL `mg-quant-prod.models.next_day_direction_v1`
OPTIONS (
model_type = 'LOGISTIC_REG',
input_label_cols = ['target_up'],
auto_class_weights = TRUE
) AS
SELECT
LOG(close / LAG(close, 1) OVER w) AS r_1d,
LOG(close / LAG(close, 5) OVER w) AS r_5d,
LOG(close / LAG(close, 20) OVER w) AS r_20d,
volume / AVG(volume) OVER (PARTITION BY symbol ORDER BY trade_date
ROWS BETWEEN 20 PRECEDING AND 1 PRECEDING) AS vol_ratio,
IF(LEAD(close, 1) OVER w > close, 1, 0) AS target_up
FROM `mg-quant-prod.marketdata.us_equities_daily`
WHERE trade_date BETWEEN DATE '2015-01-01' AND DATE '2023-12-31'
WINDOW w AS (PARTITION BY symbol ORDER BY trade_date);
Das Modell ist absichtlich naiv und hat alle bekannten Probleme von In-Sample- Backtests — aber für die Frage „lohnt sich Modellklasse X für mein Problem überhaupt" ist das eine Antwort in zehn Minuten statt drei Tagen.
Kosten und Reservierungen.
BigQuery hat zwei Abrechnungsmodi: On-Demand (pro gescanntem Byte) und Editions (Reserved Slots). Für Trading-Workloads, die ausgeprägte Lastspitzen haben — Backtests am Wochenende, Reporting am Morgen, sonst wenig — ist On-Demand fast immer die günstigere Wahl. Die Faustregel: erst ab etwa 1500 Euro On-Demand-Kosten pro Monat lohnt sich ein Reserved-Slot-Modell.
- Custom Cost Controls: setzen Sie ein Tageslimit pro User. Niemand braucht 10 TB Scans pro Tag im Normalbetrieb — ein Limit von 500 GB verhindert teure Unfälle.
- Query-Validator: vor jedem großen Job im UI den „Dry Run" laufen lassen. Der zeigt die zu scannenden Bytes. Bei über 100 GB lohnt es sich, die Query zu überdenken.
- Materialized Views: für oft genutzte Aggregate (z.B. Tages-OHLC aus Minuten-Bars) eine materialisierte Sicht anlegen. Aktualisiert sich automatisch und ist günstig im Lesezugriff.
Wo BigQuery nicht hingehört.
BigQuery ist keine OLTP-Datenbank. Für Live-Positionen, Orders, Account-Stand brauchen Sie etwas anderes — Cloud SQL, Firestore, oder bei AWS DynamoDB. BigQuery ist auch keine Low-Latency-Source für Strategie-Entscheidungen — die Mindestlatenz einer BigQuery-Query liegt bei rund einer Sekunde, was für End-of-Day-Strategien okay, für Intraday- Entscheidungen aber zu viel ist. Cache die Daten, die Ihre Live-Strategie braucht, in Memorystore (Redis) oder im Prozess selbst.
Sie planen einen Marktdaten-Lake auf GCP oder wollen Ihre bestehende Postgres-Lösung entlasten? Erstgespräch buchen — wir gehen Architektur, Modellierung und Kosten gemeinsam durch.