σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademySkills
Ctrl K
AO
+1,2 Tsd. Lernende
AnmeldenBeitreten ↗
Skills
AO
+1,2 Tsd. Lernende
Anmelden
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python-Skill›Modul 10 · Hochleistungs-Python-Entwicklung›Thema 10.12·~50 Min.Fortgeschritten

Parquet und Arrow

Position in der SkillModul 10 von 11 · Thema 12 von 13
10.12 Parquet und Arrow
—Lab-Punkte— / 100XP gesamt—Serie—

Zeilen oder Spalten?

Eine CSV-Datei oder ein zeilenorientierter Datenspeicher hält jeden Datensatz zusammen: alle Felder von Trade 1, dann von Trade 2 usw. Analysen wollen das selten. Den Durchschnittspreis von zehn Millionen Trades zu berechnen braucht nur die Spalte price, doch ein Zeilenlayout zwingt den Leser, über alle anderen Felder hinwegzusteigen. Ein spaltenorientiertes Layout hält jede Spalte zusammenhängend: Eine Abfrage liest nur die genannten Spalten, ähnliche Werte liegen nebeneinander und komprimieren sehr gut, und Arithmetik über eine Spalte läuft über einen einzigen dichten Speicherblock. Deshalb nutzen quantitative Data Lakes Parquet auf der Platte und Arrow im Speicher.

# The same table as rows and as columns
rows = [{"symbol": "AAPL", "price": 184.25, "qty": 100},
        {"symbol": "MSFT", "price": 410.5, "qty": 50},
        {"symbol": "AAPL", "price": 184.5, "qty": 25}]

columns = {name: [row[name] for row in rows] for name in rows[0]}
print(columns)                                       # {'symbol': ['AAPL', 'MSFT', 'AAPL'], 'price': [184.25, 410.5, 184.5], 'qty': [100, 50, 25]}
print(sum(columns["price"]) / len(rows))             # 259.75   the average only touched one column

back = [{name: columns[name][i] for name in columns} for i in range(len(rows))]
print(back == rows)                                  # True  nothing is lost either way

Parquet: Zeilengruppen, Statistiken und Kodierungen

Eine Parquet-Datei teilt die Tabelle in Zeilengruppen (etwa eine Million Zeilen je Gruppe); innerhalb einer Gruppe wird jede Spalte als eigener Block gespeichert, und die Datei vermerkt Minimum und Maximum jedes Blocks. Eine Abfrage für price zwischen 180 und 190 liest zuerst die Statistiken und überspringt jede Zeilengruppe, deren Bereich einen solchen Preis nicht enthalten kann (Predicate Pushdown), und liest nur die angeforderten Spalten (Projection Pushdown). Spalten werden außerdem kodiert: Eine Spalte mit wenigen verschiedenen Werten, etwa das Symbol, wird als kleines Wörterbuch plus einen winzigen Ganzzahlcode je Zeile gespeichert und danach komprimiert. Eine Datei mit sich wiederholenden Marktdaten ist oft zehnmal kleiner als das CSV.

# Dictionary encoding turns a few distinct values into a small dictionary plus integer codes
def dictionary_encode(values):
    positions, dictionary, codes = {}, [], []
    for value in values:
        if value not in positions:
            positions[value] = len(dictionary)
            dictionary.append(value)
        codes.append(positions[value])
    return dictionary, codes

symbols = ["AAPL", "MSFT", "AAPL", "AAPL", "MSFT", "NVDA"] * 1000
dictionary, codes = dictionary_encode(symbols)
print(dictionary, codes[:6])                         # ['AAPL', 'MSFT', 'NVDA'] [0, 1, 0, 0, 1, 2]
print([dictionary[c] for c in codes] == symbols)     # True  decoding restores the column
print(len(dictionary), "distinct values for", len(symbols), "rows")      # 3 distinct values for 6000 rows
# Statistics let a reader skip whole row groups without opening them
def groups_to_read(stats, low, high):
    return [i for i, (minimum, maximum) in enumerate(stats) if maximum >= low and minimum <= high]

stats = [(180.0, 183.5), (183.0, 186.0), (185.5, 190.0), (189.0, 195.0)]        # (min, max) of price per row group
print(groups_to_read(stats, 184.0, 185.0))           # [1]        only the second group can hold a price in this range
print(groups_to_read(stats, 189.5, 192.0))           # [2, 3]
print(groups_to_read(stats, 200.0, 210.0))           # []         no group needs to be read at all

Arrow, pandas und weniger lesen

Apache Arrow definiert dasselbe Spaltenlayout für den Speicher, sodass Programme und Sprachen einander eine Tabelle ohne Konvertierung übergeben können: pandas, Polars, DuckDB und Spark sehen dieselbe Arrow-Tabelle ohne Kopie. In der Praxis nutzt man pandas.read_parquet(path, columns=["symbol", "price"], filters=[("price", ">", 180)]), das Spaltenauswahl und Filter in den Reader verlagert, oder pyarrow.parquet.read_table. Schreiben Sie Daten nach einer Spalte wie dem Datum partitioniert (df.to_parquet(path, partition_cols=["date"])), damit eine Abfrage für einen Tag nur ein Verzeichnis öffnet. Halten Sie Zeilengruppen groß genug zum guten Komprimieren, aber klein genug, dass die Statistiken viele überspringen können, und sortieren Sie die Daten nach der Filterspalte, damit jede Gruppe einen engen Bereich abdeckt.

# Sorting by the filter column makes the row groups cover narrow, non-overlapping ranges
import random

rng = random.Random(7)
prices = [round(rng.uniform(100, 200), 2) for _ in range(10_000)]

def group_stats(values, size):
    groups = [values[i : i + size] for i in range(0, len(values), size)]
    return [(min(g), max(g)) for g in groups]

def groups_to_read(stats, low, high):
    return [i for i, (a, b) in enumerate(stats) if b >= low and a <= high]

unsorted_groups = len(groups_to_read(group_stats(prices, 1000), 150.0, 151.0))
sorted_groups = len(groups_to_read(group_stats(sorted(prices), 1000), 150.0, 151.0))
print(unsorted_groups, sorted_groups)                # 10 1   without sorting every group's range overlaps the query
Eine Spaltendatei ist für Analyse, nicht für ÄnderungenParquet-Dateien werden einmal geschrieben und oft gelesen. Eine Zeile zu ändern heißt, eine Datei neu zu schreiben; sie eignen sich also für Historie (Ticks, Bars, Backtest-Ergebnisse), nicht für eine Live-Ordertabelle, die in eine Datenbank gehört. Hängen Sie neue Daten als neue Dateien oder Partitionen an, statt alte zu ändern. Halten Sie das Schema stabil und die Zeitstempel in UTC und denken Sie daran, dass Float-Spalten die Rundung behalten, die Sie früher kennengelernt haben.
  • Spaltenspeicherung hält jede Spalte zusammen: Abfragen lesen nur nötige Spalten, ähnliche Werte komprimieren gut, Spaltenarithmetik ist schnell.
  • Parquet teilt Daten in Zeilengruppen mit Min/Max-Statistik und kodiert wiederholte Werte per Wörterbuch; Reader überspringen nicht benötigte Gruppen und Spalten.
  • Arrow ist der Speicher-Zwilling, den pandas, Polars und DuckDB teilen; nach Datum partitionieren, nach der Filterspalte sortieren, anhängen statt ändern.

Als Nächstes: Zeitreihendatenbanken, Speicher für zeitgestempelte Daten wie Ticks und Metriken.

Lab: Was bewertet wird

  • Mit to_columns Zeilen in Spalten umwandeln25 Pkt.
  • Mit to_rows die Zeilen wiederherstellen25 Pkt.
  • Wiederholungen mit dictionary_encode verkleinern25 Pkt.
  • Mit groups_to_read überspringen, was geht25 Pkt.
Explorer · lab-128-1
▾ lab-128-1
…
bash · im Browser
learner@quantcraft:~/lab-128-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-128-1 wird auf die Startdateien zurückgesetzt und das Terminal geleert. Ihre aktuelle Lösung geht verloren – das lässt sich nicht rückgängig machen.

Wird zurückgesetzt
  • main.py → Startcode
  • Terminal-Verlauf und Testausgabe
  • Nicht eingereichte Änderungen
Bleibt erhalten
  • Beste Lab-Punktzahl
  • Ihre XP und Ihre Serie
  • Vorlesungsfortschritt und Notizen

Melde dich an, um deinen Code auszuführen.

Alle Lektionen kannst du kostenlos lesen. Erstelle ein Konto oder melde dich an, um Code auszuführen, Tests zu starten und dieses Lab abzugeben.

  • IDE im Browser mit Bash-Terminal — nichts zu installieren
  • Sofortige Bewertung, XP und dein Platz auf der Skill
Konto erstellenAnmelden
ODER
Weiter mit GitHub