σμ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 AcademyParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Parcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Python›Module 10 · Ingénierie Python haute performance›Sujet 10.13·~50 minAvancé

Bases de données de séries temporelles

Position dans le parcoursModule 10 sur 11 · Sujet 13 sur 13
10.13 Bases de données de séries temporelles
—Score du lab— / 100XP total—Série—

Des données qui arrivent avec une horloge

Les ticks, cotations, chiffres de risque et métriques de serveur ont la même forme : un horodatage, quelques étiquettes, quelques valeurs mesurées, et un flux sans fin. Une base de séries temporelles (InfluxDB, TimescaleDB, QuestDB, kdb+) est conçue pour cela. Les écritures sont presque toujours des ajouts au temps le plus récent, donc rapides ; les données sont stockées en blocs ordonnés dans le temps, si bien que « la dernière heure » touche peu de choses ; le vieux détail peut être résumé en données plus grossières puis supprimé. Le modèle de données d'InfluxDB est typique : une mesure (comme une table), des tags (étiquettes indexées comme le symbole ou la place), des champs (les valeurs mesurées) et un horodatage.

# One point in InfluxDB line protocol: measurement,tags fields timestamp
point = "trades,symbol=AAPL,venue=XNAS price=184.25,qty=100i 1700000000000000000"
head, fields, ts = point.split(" ")
measurement, *tag_pairs = head.split(",")
tags = dict(pair.split("=") for pair in tag_pairs)
print(measurement, tags)                             # trades {'symbol': 'AAPL', 'venue': 'XNAS'}
print(fields, int(ts))                               # price=184.25,qty=100i 1700000000000000000

Sous-échantillonnage : des ticks aux barres

Garder chaque tick pour toujours coûte cher et sert rarement. Le sous-échantillonnage regroupe les points en tranches de temps fixes et garde un résumé de chacune. Pour les prix, le résumé standard est OHLCV : ouverture (premier prix de la tranche), plus haut, plus bas, clôture (dernier prix) et volume. Un tick à l'instant ts appartient à la tranche qui commence à ts - ts % bucket. Les bases le font en continu et écrivent le résultat dans une table séparée bien plus petite (un agrégat continu), que lisent les graphiques et la plupart des backtests, tandis que les ticks bruts ne sont gardés que peu de temps.

# Rolling ticks (ts, price, size) into 60-second OHLCV bars
def ohlcv_bars(ticks, bucket):
    bars = []
    for ts, price, size in ticks:
        start = ts - ts % bucket
        if bars and bars[-1]["start"] == start:
            bar = bars[-1]
            bar["high"], bar["low"] = max(bar["high"], price), min(bar["low"], price)
            bar["close"] = price
            bar["volume"] += size
        else:
            bars.append({"start": start, "open": price, "high": price, "low": price, "close": price, "volume": size})
    return bars

ticks = [(0, 100.0, 10), (30, 101.0, 5), (61, 99.5, 20), (65, 100.5, 1)]
for bar in ohlcv_bars(ticks, 60):
    print(bar)
# {'start': 0, 'open': 100.0, 'high': 101.0, 'low': 100.0, 'close': 101.0, 'volume': 15}
# {'start': 60, 'open': 99.5, 'high': 100.5, 'low': 99.5, 'close': 100.5, 'volume': 21}

Jointures as-of et rétention

Les séries ne battent presque jamais aux mêmes instants : les joindre sur des horodatages égaux ne trouve presque rien. La vraie question est « quelle était la cotation quand cette transaction a eu lieu ? » : pour chaque transaction, prenez la dernière cotation à son heure ou avant. Cette jointure as-of (pandas.merge_asof, ASOF JOIN dans kdb+ et DuckDB) ne doit regarder qu'en arrière ; utiliser une cotation postérieure à la transaction fait fuir le futur dans l'analyse. Comme les cotations sont triées par temps, une recherche binaire (bisect_right) trouve la bonne en temps logarithmique. Enfin, une politique de rétention supprime les points plus vieux qu'une limite pour borner le stockage : gardez 30 jours de ticks, un an de barres à la minute et des barres journalières pour toujours.

# For each trade, the latest quote at or before it, found by binary search
from bisect import bisect_right

def asof_join(trades, quotes):
    times = [quote[0] for quote in quotes]
    joined = []
    for ts, price in trades:
        position = bisect_right(times, ts)
        joined.append((ts, price, *quotes[position - 1][1:]) if position else (ts, price, None, None))
    return joined

quotes = [(10, 99.9, 100.1), (40, 100.5, 100.7)]
print(asof_join([(5, 100.0), (10, 100.0), (50, 101.0)], quotes))
# [(5, 100.0, None, None), (10, 100.0, 99.9, 100.1), (50, 101.0, 100.5, 100.7)]
# Retention: keep only the points younger than the limit
def apply_retention(points, now, ttl):
    return [(ts, value) for ts, value in points if ts > now - ttl]

points = [(10, 1.0), (40, 2.0), (50, 3.0), (90, 4.0)]
print(apply_retention(points, 100, 60))              # [(50, 3.0), (90, 4.0)]   the point at 40 is exactly 60 s old: dropped
print(apply_retention(points, 100, 1000))            # everything is younger than the limit
Surveillez la cardinalité de vos tagsLes tags sont indexés : chaque combinaison distincte de valeurs de tags crée une série séparée. Un symbole ou une place, c'est correct (des centaines de valeurs) ; mettre un identifiant d'ordre ou un horodatage dans un tag crée des millions de séries et peut mettre la base à genoux. Placez dans les champs, pas dans les tags, les identifiants qui changent à chaque point. Stockez aussi les horodatages en UTC avec une seule précision (secondes, millisecondes ou nanosecondes) dans tout le système, car les mélanger produit des jointures qui ne trouvent rien sans prévenir.
  • Un point de série temporelle est une mesure, des tags indexés, des champs mesurés et un horodatage ; les écritures sont des ajouts et les données sont stockées dans l'ordre du temps.
  • Sous-échantillonnez les ticks en barres OHLCV (ts - ts % bucket) et lisez les barres ; ne gardez les ticks bruts que peu de temps.
  • Joignez les séries par une jointure as-of qui ne regarde qu'en arrière (recherche binaire), appliquez une rétention, et maîtrisez cardinalité des tags et unités d'horodatage.

À suivre : empaqueter une bibliothèque Python, transformer votre code en quelque chose que d'autres peuvent installer.

Lab : ce qui est noté

  • Agréger les ticks en barres avec ohlcv_bars25 pts
  • Trouver la cotation de chaque transaction avec asof_join25 pts
  • Écrire un point avec line_protocol25 pts
  • Supprimer les données expirées avec apply_retention25 pts
Explorateur · lab-129-1
▾ lab-129-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-129-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-129-1 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes

Connectez-vous pour exécuter votre code.

Tous les cours sont gratuits à lire. Créez un compte ou connectez-vous pour exécuter du code, lancer les tests et soumettre ce lab.

  • IDE dans le navigateur avec terminal bash — rien à installer
  • Correction instantanée, XP et votre place dans le parcours
Créer un compteSe connecter
OU
Continuer avec GitHub