Kostenlose Börsendaten-API mit Python nutzen
Ubuntu-Anleitung: Rufen Sie eine kostenlose Börsendaten-API mit requests auf, laden Sie JSON mit pandas in einen DataFrame und berechnen Sie das 20-Tage-Durchschnittsvolumen.
Eine kostenlose Börsendaten-API aus Python aufzurufen, erfordert eine HTTP-Anfrage und zwei Bibliotheken, die die meisten bereits installiert haben: requests zum Abrufen der JSON-Daten und pandas zum Umwandeln in einen DataFrame. Ein API-Schlüssel ist nicht erforderlich. Diese Anleitung läuft vollständig in einem frischen Ubuntu-Container und endet mit der Ausgabe des durchschnittlichen Handelsvolumens über 20 Sitzungen für einen einzelnen ticker. Welche Daten die Endpunkte bereitstellen, wird im Leitfaden zur kostenlosen Börsendaten-API erläutert; diese Seite behandelt die Python-Seite derselben API.
Python, requests und pandas in einem neuen Ubuntu-Container einrichten
Ein standardmäßiges Ubuntu-Image enthält das Python-Modul venv nicht. Außerdem verweigern aktuelle Releases die Installation von Paketen in den Systeminterpreter. Eine virtuelle Umgebung umgeht beide Probleme und verhält sich in allen derzeit unterstützten Ubuntu-Releases gleich. Führen Sie die folgenden Befehle als root aus oder stellen Sie den apt-get-Zeilen sudo voran.
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get install -y python3 python3-venv
python3 -m venv .venv
. .venv/bin/activate
pip install "requests>=2.31,<3" "pandas>=2.0,<4"
Die Versionsbereiche sind bewusst gewählt. Jeder Bereich akzeptiert alle unterstützten Versionen der Bibliothek und schließt nur eine künftige Hauptversion mit unbekanntem Verhalten aus. Dadurch lässt sich dieselbe Befehlszeile sauber ausführen, wenn Ubuntu seine standardmäßige Python-Version aktualisiert. Nichts ist auf einen Build festgelegt, der zufällig heute verfügbar ist. Alle folgenden Schritte setzen voraus, dass die Umgebung aktiviert ist.
Wie rufe ich eine kostenlose API für Börsenmarktdaten in Python auf?
Die Demo-Endpunkte beantworten einfache GET-Anfragen ohne API-Key und ohne Registrierung. Das erste Skript fragt eine der kuratierten Abfragen ab und gibt die Struktur der Antwort aus.
import json
import requests
BASE = "https://ai.strasmore.com/api/demo"
resp = requests.get(BASE, params={"q": "dividend_yield_leaders"}, timeout=30)
resp.raise_for_status()
payload = resp.json()
print(list(payload.keys()))
print(payload["columns"])
print(json.dumps(payload["rows"][0], indent=2))
requests.get erstellt die Abfragezeichenfolge aus params, raise_for_status() wandelt jeden 4xx- oder 5xx-Status in eine Ausnahme um, und .json() parst den Inhalt in ein Dictionary. Auf seine Struktur reduziert sieht dieses Dictionary so aus:
{
"key": "dividend_yield_leaders",
"label": "...",
"nl": "Which large-cap US stocks currently have the highest dividend yields?",
"sql": "SELECT ...",
"columns": ["as_of", "ticker", "dividend_yield_pct", "price", "market_cap_bn", "price_to_earnings"],
"rows": [{"as_of": "<date>", "ticker": "<symbol>", "dividend_yield_pct": <number>, ...}, ...],
"elapsed": "...",
"source": "Strasmore Research",
"more": "..."
}
Für pandas sind zwei Schlüssel relevant. columns enthält die Feldnamen in der richtigen Reihenfolge, und rows enthält pro Datensatz ein JSON-Objekt, in dem diese Namen als Schlüssel verwendet werden. Der Schlüssel sql enthält die exakte Abfrage, mit der die Zahlen erzeugt wurden, und wird mit jeder Antwort zurückgegeben. Dadurch sind die Daten prüfbar und keine Blackbox. Ein GET-Aufruf an /api/demo/catalog listet jeden kuratierten Schlüssel sowie den anschließend verwendeten SQL-Endpunkt auf.
Wie lade ich das JSON in einen pandas DataFrame?
Die kuratierten Abfragen beantworten festgelegte Fragen. Tagesdaten für einen Ticker Ihrer Wahl erhalten Sie über den SQL-Endpunkt ohne Registrierung unter /api/demo/sql. Er nimmt eine schreibgeschützte Abfrage im Parameter sql entgegen und gibt dasselbe Paar aus columns und rows zurück. Die zum September 2026 geltenden Beschränkungen werden in jeder Antwort ausgewiesen: 500 Zeilen, 20 Sekunden, ein Jahr Historie, kein API-Schlüssel. Die kostenlose SQL-API mit API-Schlüssel erhöht das Limit auf 100 Abfragen pro Tag und ermöglicht den Zugriff auf eine längere Historie. Der Anfragecode bleibt identisch.
Zwei Vorkehrungen in der SQL-Abfrage halten den Durchschnitt aussagekräftig. Eine Tabelle, die während der Sitzung aktualisiert wird, kann für den aktuellen Tag mehr als eine Zeile enthalten. Außerdem ist das Volumen dieses Tages während der Handelszeit nur vorläufig. Die Abfrage endet mit date < today() am Vortag und fasst mit GROUP BY date und max(volume) doppelte Zeilen für ein Datum zusammen. Wenn Sie noch nicht geprüft haben, wie ein Tagesbalken aus den ausgeführten Trades erstellt wird, erklärt wie OHLCV-Balken erstellt werden, woher diese Zeilen stammen.
import time
import requests
import pandas as pd
SQL_URL = "https://ai.strasmore.com/api/demo/sql"
TICKER = "AAPL"
SQL = f"""
SELECT date, max(volume) AS volume
FROM stocks_daily_aggs
WHERE ticker = '{TICKER}'
AND date < today()
GROUP BY date
ORDER BY date DESC
LIMIT 20
"""
def fetch(sql, attempts=4):
"""GET the SQL endpoint. Back off on 429 and 5xx; stop on any other 4xx."""
delay = 2.0
for attempt in range(1, attempts + 1):
resp = requests.get(SQL_URL, params={"sql": " ".join(sql.split())}, timeout=30)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429 or resp.status_code >= 500:
try:
wait = max(1.0, float(resp.headers.get("Retry-After")))
except (TypeError, ValueError):
wait = delay
print(f"HTTP {resp.status_code}; waiting {wait:.0f}s (attempt {attempt} of {attempts})")
time.sleep(wait)
delay *= 2
continue
raise SystemExit(f"HTTP {resp.status_code}: {resp.text[:400]}")
raise SystemExit("gave up after repeated 429 or 5xx responses")
payload = fetch(SQL)
df = pd.DataFrame(payload["rows"], columns=payload["columns"])
df["date"] = pd.to_datetime(df["date"])
df["volume"] = pd.to_numeric(df["volume"])
df = df.sort_values("date").reset_index(drop=True)
print(df.to_string(index=False))
avg_20 = df["volume"].mean()
first, last = df["date"].iloc[0], df["date"].iloc[-1]
print(f"{TICKER} 20-session average volume: {avg_20 / 1e6:.1f}M shares "
f"({first:%Y-%m-%d} to {last:%Y-%m-%d}, {len(df)} sessions)")
pd.DataFrame(rows, columns=columns) erstellt die Tabelle direkt aus der Liste der Objekte. Die Übergabe von columns behält die Feldreihenfolge der API bei. Danach folgen zwei Konvertierungen: Die Datumswerte werden als Zeichenfolgen geliefert und in echte Zeitstempel umgewandelt. volume wird in eine Zahl konvertiert, falls eine Antwort den Wert einmal als Text serialisiert. Die Sortierung vom ältesten zum neuesten Wert bringt den DataFrame in die Reihenfolge, die ein Chart erwartet. df["volume"].mean() über genau zwanzig Zeilen ist der 20-Tage-Durchschnitt. Die letzte Zeile gibt ihn zusammen mit dem abgedeckten Datumsbereich in Millionen aus.
Was misst das durchschnittliche 20-Tage-Volumen tatsächlich?
Das durchschnittliche Volumen glättet eine schwankungsanfällige Tagesreihe. Die Zahl der gehandelten Aktien kann in einer einzelnen Sitzung durch Indexneugewichtungen, Optionsverfallstermine, Veröffentlichungstermine von Quartalszahlen und Schlagzeilen stark variieren. Zwanzig Sitzungen entsprechen ungefähr einem Kalendermonat. Der Zeitraum ist lang genug, um solche Ausschläge zu dämpfen, und kurz genug, um Veränderungen in der Handelsaktivität einer Aktie abzubilden. Das folgende Panel berechnet aus derselben Tagestabelle dieselbe Kennzahl, die das Skript ausgibt. Es zeigt die vergangenen Monate mit den Rohdaten der Sitzungen und dem rollierenden Durchschnitt nebeneinander.
| Sitzung | Volumen in Millionen | 20-Tage-Durchschnitt in Millionen |
|---|---|---|
| 2026-08-17 | 38.2 | 51.9 |
| 2026-08-18 | 53.4 | 52.5 |
| 2026-08-19 | 50.5 | 53 |
| 2026-08-20 | 41 | 53.1 |
| 2026-08-21 | 46.9 | 53 |
| 2026-08-24 | 34.7 | 52.3 |
| 2026-08-25 | 25.9 | 51 |
| 2026-08-26 | 34 | 49.9 |
| 2026-08-27 | 32.4 | 47.8 |
| 2026-08-28 | 38.6 | 43.1 |
| 2026-08-31 | 41.2 | 41.4 |
| 2026-09-01 | 53.2 | 40.6 |
| 2026-09-02 | 33.8 | 39.8 |
| 2026-09-03 | 37.2 | 39.4 |
| 2026-09-04 | 39.6 | 39.7 |
| 2026-09-08 | 35.5 | 39.2 |
| 2026-09-09 | 65.6 | 40.6 |
| 2026-09-10 | 70 | 42 |
| 2026-09-11 | 50.7 | 42.5 |
| 2026-09-14 | 39.3 | 43.1 |
Das exakte SQL hinter jeder Zahl
SELECT
session,
volume_millions,
avg_20d_millions
FROM
(
SELECT
toString(d) AS session,
round(vol / 1e6, 1) AS volume_millions,
round(avg(vol) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) / 1e6, 1) AS avg_20d_millions,
count() OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS sessions_in_window
FROM
(
SELECT
date AS d,
toFloat64(max(volume)) AS vol
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'AAPL'
AND date >= today() - 75
AND date < today()
GROUP BY date
)
)
WHERE sessions_in_window = 20
ORDER BY sessionDie Tagesreihe enthält einen Wert je Sitzung. Die geglättete Reihe entspricht dem Durchschnitt dieser Sitzung und der 19 vorangegangenen Sitzungen. Das Panel reicht von 2026-08-17 bis 2026-10-02. Es umfasst 34 Sitzungen, für die jeweils ein vollständiges 20-Sitzungs-Fenster vorliegt. In der letzten dieser Sitzungen wurden bei AAPL 33.3 Millionen Aktien gehandelt. Der Durchschnitt lag bei 42.2 Millionen. Das ist die Zahl, die das Skript an dem Tag ausgegeben hat, an dem diese Seite erstellt wurde. Wenn Sie das Skript heute ausführen, haben sich die 20 Sitzungen weiter verschoben – und die Kennzahl mit ihnen.
Ticker austauschen
Ändern Sie TICKER, dann funktioniert das Skript für jedes in der Tabelle aufgeführte US-notierte Wertpapier. Der folgende Vergleich verwendet für vier bekannte Namen dieselbe Berechnung über 20 Handelssitzungen. Er vermittelt schnell ein Gefühl dafür, was „Durchschnittsvolumen“ bei einem Index-ETF und drei Mega-Caps bedeutet.
| ticker | 20-Tage-Durchschnitt in Millionen | Erste Sitzung | Letzte Sitzung |
|---|---|---|---|
| NVDA | 110 | 2026-09-04 | 2026-10-02 |
| SPY | 46 | 2026-09-04 | 2026-10-02 |
| AAPL | 42.2 | 2026-09-04 | 2026-10-02 |
| MSFT | 21.3 | 2026-09-04 | 2026-10-02 |
Das exakte SQL hinter jeder Zahl
SELECT
ticker,
round(avg(vol) / 1e6, 1) AS avg_20d_millions,
toString(min(d)) AS first_session,
toString(max(d)) AS last_session
FROM
(
SELECT
ticker,
d,
vol,
row_number() OVER (PARTITION BY ticker ORDER BY d DESC) AS rn
FROM
(
SELECT
ticker,
date AS d,
toFloat64(max(volume)) AS vol
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('SPY', 'NVDA', 'AAPL', 'MSFT')
AND date >= today() - 45
AND date < today()
GROUP BY ticker, date
)
)
WHERE rn <= 20
GROUP BY ticker
HAVING count() = 20
ORDER BY avg_20d_millions DESCNVDA weist über 20 Handelssitzungen den höchsten Durchschnitt der vier Werte auf: 110 Millionen Aktien je Sitzung. MSFT liegt mit 21.3 Millionen am niedrigsten. Gemessen wurde jeweils über die Sitzungen von 2026-09-04 bis 2026-10-02. Der Wert für AAPL entspricht hier genau der Zahl, mit der die obige Verlaufskurve endet: eine Definition, einmal berechnet und überall gleich verwendet.
Wie sieht ein 429-Fehler aus, und wie wiederhole ich die Anfrage korrekt?
Ein Rate Limit wird als HTTP-Statuscode und nicht als Datensatz zurückgegeben. Die Antwort enthält den Status 429 Too Many Requests und häufig einen Retry-After-Header mit der Anzahl der Sekunden, die gewartet werden soll. Der Antworttext enthält nicht die angeforderten Daten. Deshalb prüft der Helfer fetch zunächst status_code, bevor er etwas parst. Die Regeln in dieser Reihenfolge:
200: JSON parsen und zurückgeben.429oder beliebiges5xx: Wenn vorhanden,Retry-Afterabwarten. Andernfalls die Wartezeit schrittweise verlängern (2, 4, 8, dann 16 Sekunden) und die Anfrage erneut senden, insgesamt vier Versuche.- Jedes andere
4xx: abbrechen. Der Antworttext nennt den genauen Grund für die Ablehnung der Abfrage: eine nicht vorhandene Tabelle oder eine Anweisung, die die Nur-Lese-Sperre abgewiesen hat. Weitere Wiederholungen ändern daran nichts. Eine unbekannte Tabelle führt beispielsweise zu einem400.
Zwei Gewohnheiten helfen, das Limit einzuhalten. Kuratierte Ergebnisse werden höchstens alle zehn Minuten aktualisiert. Eine Abfrageschleife, die schneller läuft, lädt daher identische Bytes erneut. Das lokale Zwischenspeichern der Nutzdaten kostet nichts. Fragen Sie außerdem nur die benötigten Daten ab: LIMIT 20 für einen Durchschnitt über 20 Sitzungen statt ein ganzes Jahr an Zeilen, die anschließend verworfen werden. Netzwerkfehler wie eine unterbrochene Verbindung oder eine DNS-Störung werden als requests.RequestException ausgegeben. Der Helfer fängt sie nicht ab. Wenn das Skript unbeaufsichtigt läuft, kapseln Sie den Aufruf in try/except.
FAQ
Gibt es eine kostenlose Börsendaten-API für Python?
Ja. Die Demo-Endpunkte in diesem Leitfaden beantworten nicht authentifizierte GET-Anfragen von requests mit JSON, das sich direkt in pandas laden lässt: kuratierte Abfragen unter /api/demo?q=<key> und selbst verfasstes, schreibgeschütztes SQL unter /api/demo/sql, begrenzt auf 500 Zeilen und eine Historie von einem Jahr. Kein API-Schlüssel, keine Registrierung.
Benötige ich einen API-Schlüssel, um in Python Börsendaten abzurufen?
Nicht für die hier verwendete Demo-Stufe. Ein kostenloser Schlüssel erhöht das Limit auf 100 Abfragen pro Tag und ermöglicht eine längere Historie. Der Python-Code bleibt unverändert. Entwickler, die dieselben Endpunkte als Tools für ein LLM einbinden, können mit Marktdaten-Funktionen für KI-Agenten beginnen.
Wie wandle ich die Antwort einer JSON-API in einen pandas DataFrame um?
Parsen Sie den Inhalt mit resp.json() und übergeben Sie anschließend die Liste der Zeilenobjekte an pd.DataFrame(rows, columns=columns). Konvertieren Sie Datumstexte mit pd.to_datetime und numerische Texte mit pd.to_numeric, bevor Sie Berechnungen durchführen. JSON kennt keinen Datentyp für Datumsangaben, und manche APIs serialisieren Dezimalzahlen als Text.
Was bedeutet ein 429-Fehler beim Abruf über eine Börsen-API?
HTTP 429 bedeutet „Too Many Requests“: Der Server begrenzt die Anfragerate des Absenders. Warten Sie die im Header Retry-After angegebene Zahl von Sekunden, sofern ein solcher Header gesendet wird. Andernfalls erhöhen Sie die Warteintervalle exponentiell und speichern Sie Antworten zwischen, statt ein Ergebnis wiederholt abzufragen, das nur alle zehn Minuten aktualisiert wird.
Wie berechnet man das durchschnittliche Handelsvolumen in pandas?
Laden Sie eine Zeile pro Handelstag mit einer volume-Spalte, behalten Sie die letzten zwanzig vollständig abgeschlossenen Handelstage und rufen Sie df["volume"].mean() auf. Für eine gleitende Berechnung über eine längere Historie verwenden Sie df["volume"].rolling(20).mean(). Darauf basiert das darüber angezeigte Trace-Panel.
Jedes darüber angezeigte Panel enthält das exakte SQL darunter. Das Skript folgt demselben Prinzip und beginnt mit einem Aufruf von requests. Kostenloser API-Schlüssel. 100 Abfragen pro Tag, 22 Jahre Historie und SQL über die Rohdaten. Keine Kreditkarte. API-Schlüssel anfordern