σμ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 AcademyRoteiros
Ctrl K
AO
+1,2 mil alunos
EntrarParticipar ↗
Roteiros
AO
+1,2 mil alunos
Entrar
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Roteiro de Python›Módulo 10 · Engenharia Python de alto desempenho›Tópico 10.13·~50 minAvançado

Bancos de dados de séries temporais

Posição no roteiroMódulo 10 de 11 · Tópico 13 de 13
10.13 Bancos de dados de séries temporais
—Nota do lab— / 100XP total—Sequência—

Dados que chegam com um relógio

Ticks, cotações, números de risco e métricas de servidor têm a mesma forma: um carimbo de tempo, alguns rótulos, alguns valores medidos e um fluxo sem fim. Um banco de dados de séries temporais (InfluxDB, TimescaleDB, QuestDB, kdb+) é feito para isso. As escritas são quase sempre acréscimos no tempo mais recente, então são rápidas; os dados ficam em blocos ordenados no tempo, de modo que «a última hora» toca pouco; o detalhe antigo pode ser resumido em dados mais grossos e por fim apagado. O modelo de dados do InfluxDB é típico: uma medição (como uma tabela), tags (rótulos indexados, como o símbolo ou o mercado), campos (os valores medidos) e um carimbo de tempo.

# 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

Reamostragem: de ticks a barras

Guardar cada tick para sempre é caro e raramente necessário. A reamostragem agrupa os pontos em baldes de tempo fixos e guarda um resumo de cada um. Para preços o resumo padrão é o OHLCV: abertura (primeiro preço do balde), máxima, mínima, fechamento (último preço) e volume. Um tick no instante ts pertence ao balde que começa em ts - ts % bucket. Os bancos fazem isso continuamente e gravam o resultado numa tabela separada bem menor (um agregado contínuo), que é o que os gráficos e a maioria dos backtests leem, enquanto os ticks brutos são guardados por pouco tempo.

# 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}

Junções as-of e retenção

As séries raramente marcam nos mesmos instantes, então juntá-las por carimbos iguais quase não encontra nada. A pergunta real é «qual era a cotação quando este negócio ocorreu?»: para cada negócio, pegue a última cotação no seu instante ou antes. Essa junção as-of (pandas.merge_asof, ASOF JOIN no kdb+ e no DuckDB) deve olhar só para trás; usar uma cotação posterior ao negócio vaza o futuro para a análise. Como as cotações estão ordenadas por tempo, uma busca binária (bisect_right) acha a certa em tempo logarítmico. Por fim, uma política de retenção descarta pontos mais antigos que um limite para manter o armazenamento limitado: guarde 30 dias de ticks, um ano de barras de minuto e barras diárias para sempre.

# 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
Fique de olho na cardinalidade das tagsTags são indexadas, então cada combinação distinta de valores de tags cria uma série separada. Um símbolo ou mercado serve (centenas de valores); pôr um id de ordem ou um carimbo de tempo numa tag cria milhões de séries e pode levar o banco ao limite. Coloque nos campos, não nas tags, os identificadores que mudam a cada ponto. Guarde também os carimbos de tempo em UTC com uma só precisão (segundos, milissegundos ou nanossegundos) em todo o sistema, pois misturá-las produz junções que não casam nada sem avisar.
  • Um ponto de série temporal é uma medição, tags indexadas, campos medidos e um carimbo de tempo; as escritas são acréscimos e os dados ficam em ordem temporal.
  • Reamostre os ticks em barras OHLCV (ts - ts % bucket) e leia as barras; guarde os ticks brutos só por pouco tempo.
  • Una séries com uma junção as-of que só olha para trás (busca binária), aplique retenção e controle a cardinalidade das tags e as unidades de tempo.

A seguir: empacotar uma biblioteca Python, transformar seu código em algo que outros possam instalar.

Lab: o que é avaliado

  • Agrupar ticks em barras com ohlcv_bars25 pts
  • Achar a cotação de cada negócio com asof_join25 pts
  • Escrever um ponto com line_protocol25 pts
  • Descartar dados expirados com apply_retention25 pts
Explorador · lab-129-1
▾ lab-129-1
…
bash · no navegador
learner@quantcraft:~/lab-129-1$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-129-1 volta aos arquivos iniciais e o terminal é limpo. Sua solução atual será perdida — isso não pode ser desfeito.

Será redefinido
  • main.py → código inicial
  • Histórico do terminal e saída dos testes
  • Alterações não enviadas
Será mantido
  • Melhor nota do lab
  • Seu XP e sua sequência
  • Progresso da aula e anotações

Entre para executar o seu código.

Todas as aulas são gratuitas para ler. Crie uma conta ou entre para executar código, rodar os testes e enviar este lab.

  • IDE no navegador com terminal bash — nada para instalar
  • Correção instantânea, XP e o seu lugar no roteiro
Criar uma contaEntrar
OU
Continuar com o GitHub