Ablaufplan für dein Docker-natives Trading-Framework — von der ersten Zeile Code bis zur Enterprise-Skalierung.
Du baust hier keine Wegwerf-App. Dieser Plan folgt einer klaren Regel: Starte monolithisch mit sauberen Service-Grenzen, extrahiere Microservices erst wenn die Last es erzwingt. Jede Phase liefert ein lauffähiges System — kein "erst fertig wenn alles fertig ist".
Bevor du über Latenz und Skalierung nachdenkst: Du brauchst ein System, das überhaupt Kurse zieht, speichert und anzeigt. Der Monolith ist hier kein Kompromiss, sondern die richtige Wahl — solange du unter 10.000 Orders/Tag und ~100 Strategien bleibst, ist alles andere Over-Engineering.
FastAPI (Backend) + PostgreSQL (Storage) + Streamlit (Frontend), alles in einem Docker-Compose-Stack auf einem einzelnen Server (z.B. AWS EC2 t3.medium). Datenquelle: Finnhub Free Tier.
Das minimale, lauffähige Setup. Drei Services, ein Netzwerk, ein Volume.
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: trading
volumes:
- pg_data:/var/lib/postgresql/data
api:
build: ./backend
environment:
DATABASE_URL: postgresql://admin:${DB_PASSWORD}@postgres/trading
FINNHUB_KEY: ${FINNHUB_KEY}
ports: ["8000:8000"]
depends_on: [postgres]
frontend:
build: ./frontend
environment:
API_URL: http://api:8000
ports: ["8501:8501"]
depends_on: [api]
volumes:
pg_data:
API-Keys gehören niemals in den Quellcode oder ins Git-Repo. Lokal: .streamlit/secrets.toml (in .gitignore). In Docker: als ENV-Variablen aus einer .env-Datei injizieren. Streamlit liest ENV-Variablen automatisch als Teil von st.secrets.
Das deckt sich exakt mit deiner AutoFinanz-Secrets-Architektur: master.env → setup_secrets.sh → bind-mounted secret files.
docker compose upgit log ist sauberDas API-Rate-Limit ist nicht eine Einstellung — es ist der Grundstein deines gesamten Systemdesigns. Ein Limit von 5 Calls/Min bedeutet: Eine naive App, die bei jedem Klick direkt die API ruft, stirbt sofort am 429 Too Many Requests. Genau das erzwingt später die asynchrone Architektur mit Celery (die du schon hast).
| API | Calls/Min | Calls/Tag | Latenz | EU-Märkte | WebSocket |
|---|---|---|---|---|---|
| Finnhub | 60 | Unlimited | Moderat | ✓ 60+ Börsen | ✓ |
| Twelve Data | 8 | 800 | ~170ms | ✓ 250+ Börsen | ✓ |
| Polygon.io | 5 | Unlimited | <20ms (Paid) | ✗ | ✓ (Paid) |
| Alpha Vantage | 5 | 25 | Nur EOD frei | Begrenzt | ✗ |
| Alpaca | 200 | Unlimited | Niedrig | ✗ | ✓ |
import finnhub
client = finnhub.Client(api_key="YOUR_KEY")
print(client.quote('AAPL'))
# WebSocket für Echtzeit-Streaming
import websocket
ws = websocket.WebSocketApp(
"wss://ws.finnhub.io?token=YOUR_TOKEN",
on_message=on_message)
ws.run_forever()
IEX Cloud: Betrieb am 31.08.2024 eingestellt — viele mussten migrieren.
Alpha Vantage: Free-Tier drastisch reduziert von 500 auf nur noch 25 Calls/Tag — für Intraday unbrauchbar.
Lehre: API-Abhängigkeiten sind Risikomanagement. Baue deine Fetch-Logik so, dass ein Provider-Wechsel ein Config-Change ist, kein Rewrite.
Das Backtesting-Framework entscheidet über Geschwindigkeit, Komplexität und Zukunftsfähigkeit. Zwei Paradigmen konkurrieren: event-gesteuert (Takt für Takt, näher am Live-Handel) vs. vektorisiert (ganze Arrays gleichzeitig, extrem schnell für Parameter-Sweeps).
| Framework | Paradigma | Performance | Status 2025 |
|---|---|---|---|
| vectorbt | Vektorisiert | Extrem hoch | ✅ Sehr aktiv |
| Backtrader | Event-driven | Mittel | ⚠️ Feature-complete (2019) |
| backtesting.py | Vektorisiert | Hoch | ✅ Aktiv |
| Zipline-Reloaded | Event-driven | Mittel | ✅ Aktiv |
| Freqtrade | Event-driven | Mittel | ✅ Sehr aktiv (Crypto) |
pandas-ta-classic (141 Indikatoren, aktiv gepflegt).
Bis zu 1000x Speedup durch vollständige Vektorisierung + Numba. Simuliert Millionen Trades in unter einer Sekunde, testet 10.000 Strategiekombinationen in Sekunden. Killer-Feature: native Plotly-Ausgabe → direkt interaktiv in Streamlit. Und es spricht out-of-the-box ~99% der Indikatoren aus TA-Lib, Pandas TA & Co. an — du musst dich also nicht auf eine TA-Bibliothek festlegen.
import vectorbt as vbt
price = vbt.YFData.download('BTC-USD').get('Close')
fast_ma = vbt.MA.run(price, 10)
slow_ma = vbt.MA.run(price, 50)
entries = fast_ma.ma_crossed_above(slow_ma)
exits = fast_ma.ma_crossed_below(slow_ma)
pf = vbt.Portfolio.from_signals(price, entries, exits, init_cash=100)
print(pf.stats())
pf.plot().show() # → interaktives Plotly-Objekt für st.plotly_chart
Rolling Mean (1000x1000 DataFrame):
Pandas: 45.6 ms
vectorbt (Numba): 5.33 ms (8.5x schneller)
vectorbt (Parallel): 1.82 ms (25x schneller)
Rolling Sortino Ratio:
QuantStats: ~Sekunden
vectorbt: ~Millisekunden (1000x schneller)
Du nutzt vectorbt bereits Streamlit-Integration nativ Plotly
Minutendaten summieren sich brutal schnell. Ein normaler Postgres-Table wird bei Millionen von Ticks pro Symbol zäh. TimescaleDB ist Postgres mit automatischer zeitbasierter Partitionierung (Hypertables), Kompression und kontinuierlichen Aggregaten — die optimale Zeitreihen-DB für Trading. Der Umstieg ist ein Extension-Load, kein DB-Wechsel.
Der komplette Setup-Flow: Hypertable anlegen, Composite-Index für schnelle Symbol-Abfragen, kontinuierliche 5-Min-Aggregation, Auto-Kompression nach 7 Tagen, Retention nach 2 Jahren.
CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE TABLE stock_prices (
time TIMESTAMPTZ NOT NULL,
symbol TEXT NOT NULL,
open NUMERIC, high NUMERIC, low NUMERIC,
close NUMERIC, volume BIGINT
);
SELECT create_hypertable('stock_prices', 'time');
CREATE INDEX idx_symbol_time ON stock_prices (symbol, time DESC);
-- Kontinuierliche 5-Minuten-Bars
CREATE MATERIALIZED VIEW stock_prices_5min
WITH (timescaledb.continuous) AS
SELECT time_bucket('5 minutes', time) AS bucket, symbol,
first(open, time) AS open, max(high) AS high,
min(low) AS low, last(close, time) AS close,
sum(volume) AS volume
FROM stock_prices
GROUP BY bucket, symbol;
ALTER TABLE stock_prices SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'symbol'
);
-- Auto-Kompression nach 7 Tagen
SELECT add_compression_policy('stock_prices', INTERVAL '7 days');
-- Retention: Daten > 2 Jahre löschen
SELECT add_retention_policy('stock_prices', INTERVAL '2 years');
Redis reduziert API-Calls um 80-90% und dient gleichzeitig als Pub/Sub-Bus für Echtzeit-Updates. Essentiell, nicht optional. TTL-basiertes Caching pro API-Call, Pub/Sub für Fan-Out an mehrere Clients.
Ab hier trennt sich Bastelei von Betrieb. Monitoring, Health Checks, Non-Root-Container und automatisierte Deployments baust du von Anfang an ein — nicht nachträglich. Genau hier lebt deine AutoFinanz-Erkenntnis: Deploy-Drift ist real. Committeter Code wirkt erst nach Image-Rebuild und Container-Recreate.
Standard-Dockerfiles laufen als root — ein Sicherheitsrisiko. Dedizierter Non-Root-User plus Health Check.
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN groupadd -g 1005 appgroup && \
useradd -u 1005 -g appgroup appuser && \
chown -R appuser:appgroup /app
USER appuser
EXPOSE 8501
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
ENTRYPOINT ["streamlit", "run", "app.py", \
"--server.port=8501", "--server.address=0.0.0.0"]
Wenn TA-Lib (oder vectorbt mit TA-Lib-Support) in requirements.txt steht, schlägt pip install fehl — die C-Bibliothek fehlt. Lösung: C-Lib im Dockerfile aus Quellcode bauen bevor pip läuft.
RUN apt-get update && apt-get install -y build-essential wget \
&& wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz \
&& tar -xzf ta-lib-0.4.0-src.tar.gz && cd ta-lib/ \
&& ./configure --prefix=/usr && make && make install \
&& cd .. && rm -rf ta-lib*
docker ps-Zeitstempel lügen nach System-Sleep. Immer mit echten Log-Timestamps gegenprüfen.
Erst wenn die Last es erzwingt: >10 Entwickler, Richtung 100.000 Orders/Tag, 1000 Strategien, Multi-Asset. Vorher ist der Monolith überlegen. Der Übergang ist eine Extraktion — du schneidest den ersten Service (meist Market Data oder Order Execution) sauber aus dem Monolithen, nicht ein Big-Bang-Rewrite.
Das produktionsreife Muster für Hochfrequenz-Daten:
Market Feed → Kafka Topic → Stream Processor → TimescaleDB
→ Redis Cache → WebSocket → Frontend
from kafka import KafkaProducer
import websocket, json
producer = KafkaProducer(
bootstrap_servers=['kafka:9092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8'),
compression_type='lz4')
def on_message(ws, message):
producer.send('market_ticks', json.loads(message))
ws = websocket.WebSocketApp(
"wss://ws.finnhub.io?token=YOUR_TOKEN",
on_message=on_message)
ws.run_forever()
Typischer Schnitt: Auth-Service, Market-Data-Service (gern Go für Durchsatz), Order-Execution (FastAPI), Analytics (Python). Verbunden über einen Kafka Event Bus, davor ein API-Gateway (Kong/Nginx).
100K+ Nutzer, $10K+/Monat. Hier geht es um Full Microservices mit Service Mesh, Multi-Region-Redundanz und — für echtes HFT — physische Nähe zur Börse. Das ist aspirationale Roadmap, kein sofortiges Arbeitspaket. Aber es lohnt sich, die Richtung zu kennen, damit frühe Entscheidungen sie nicht verbauen.
| Phase | Nutzer | Budget/Monat | Kern-Stack |
|---|---|---|---|
| MVP | 0-1K | $0-100 | Monolith + Compose |
| Growth | 1K-10K | $100-1K | TimescaleDB + Redis + LB |
| Scale | 10K-100K | $1K-10K | K8s + Kafka + Multi-Region |
| Enterprise | 100K+ | $10K+ | Service Mesh + Co-Location |