σμ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 11 · Sistemas de produção e modelos de ponta›Tópico 11.13·~4 hEspecialista

Projeto final: uma biblioteca completa de precificação ou estratégia

Posição no roteiroMódulo 11 de 11 · Tópico 13 de 13
11.13 Projeto final: uma biblioteca completa de precificação ou estratégia
—Nota do lab— / 100XP total—Sequência—

De exercícios a uma biblioteca

Você escreveu uma centena de funções pequenas. Uma biblioteca é essas funções organizadas para que outra pessoa (inclusive você daqui a seis meses) possa usá-las com confiança: uma interface pública clara, cada peça testada sozinha, as peças compostas num pipeline e tudo instalável, documentado e verificado automaticamente. Este projeto final é o padrão em miniatura. Uma biblioteca de estratégia é o lugar mais fácil para praticar: o sinal transforma preços em posições, o backtest transforma posições em retornos depois de custos, as métricas resumem os retornos e um executor liga tudo. Cada etapa recebe arrays simples e devolve arrays simples, então cada uma pode ser testada sozinha com números que se conferem à mão.

# The shape of a small library: a package, its tests, its metadata and its automated checks
tree = """\
quantlib/
├── pyproject.toml            name, version, dependencies (numpy)
├── src/quantlib/
│   ├── __init__.py           the public API: sma_positions, strategy_returns, performance_summary, run_strategy
│   ├── signals.py            prices -> positions
│   ├── backtest.py           positions -> returns after costs
│   └── metrics.py            returns -> total return, volatility, Sharpe, drawdown
├── tests/                    one test file per module, fixed seeds, hand-checked cases
└── .github/workflows/ci.yml  lint, types, tests on every push
"""
print(tree.splitlines()[0])                          # quantlib/
print(sum(1 for line in tree.splitlines() if ".py" in line))     # 4   the four module files

Sinal e backtest: sem antecipação e com custos

O sinal aqui é um cruzamento de médias móveis: ficar comprado quando a média rápida dos preços está acima da lenta, vendido quando está abaixo, zerado quando são iguais ou a lenta ainda não existe. As duas regras que decidem se um backtest é honesto aparecem no código. Primeiro, sem antecipação: a posição decidida no fechamento do dia t só pode usar preços até o dia t, e ganha o retorno do dia t + 1, por isso o retorno do passo t usa positions[t - 1]. Um teste prova: remover os últimos preços não pode mudar as posições anteriores. Segundo, os custos: cada mudança de posição paga cost * |mudança|, então uma estratégia que vira o tempo todo pode ganhar antes dos custos e perder depois.

# The signal and the honest backtest: positions decided at the close are earned on the NEXT step, and trading costs money
import numpy as np

def sma_positions(prices, fast, slow):
    prices = np.asarray(prices, dtype=float)
    positions = np.zeros(len(prices), dtype=int)
    for t in range(slow - 1, len(prices)):
        f = prices[t - fast + 1 : t + 1].mean()
        s = prices[t - slow + 1 : t + 1].mean()
        positions[t] = 1 if f > s else -1 if f < s else 0
    return positions

def strategy_returns(prices, positions, cost):
    returns = []
    for t in range(1, len(prices)):
        before = positions[t - 2] if t >= 2 else 0
        returns.append(positions[t - 1] * (prices[t] / prices[t - 1] - 1) - cost * abs(positions[t - 1] - before))
    return np.array(returns)

prices = np.array([100, 101, 103, 102, 105, 108, 107, 110, 112, 111, 115, 118], dtype=float)
positions = sma_positions(prices, 2, 4)
print(positions)                                     # [0 0 0 1 1 1 1 1 1 1 1 1]   flat until the 4-day average exists, then long
print(np.allclose(sma_positions(prices[:8], 2, 4), positions[:8]))      # True   later prices never change earlier positions
print(np.round(strategy_returns(prices, positions, 0.0).sum() - strategy_returns(prices, positions, 0.001).sum(), 3))    # 0.001   one purchase at 0.1%

Os números de desempenho e o executor

Uma série de retornos se resume em poucos números. O retorno total compõe os períodos, prod(1 + r) - 1; o retorno anual o reescala para um ano, (1 + total)^(períodos_por_ano / n) - 1; a volatilidade anual é o desvio padrão amostral vezes sqrt(períodos_por_ano); o índice de Sharpe divide o retorno médio por esse desvio, anualizado, e é a medida principal de retorno por unidade de risco (0 para uma série plana em vez de um erro de divisão); o drawdown máximo é a pior queda a partir de um pico da curva de capital, o número que diz quanta dor a estratégia pede que você suporte. Uma função executor amarra tudo: posições, depois retornos, depois o resumo. Ela deve apenas compor as outras funções, para que um bug sempre se rastreie até uma etapa.

# Performance statistics of a return series, and the runner that wires the three stages together
import math
import numpy as np

def performance_summary(returns, periods_per_year):
    returns = np.asarray(returns, dtype=float)
    n = len(returns)
    equity = np.concatenate(([1.0], np.cumprod(1 + returns)))
    total = float(equity[-1] - 1)
    deviation = float(np.std(returns, ddof=1))
    peak = np.maximum.accumulate(equity)
    return {
        "total_return": total,
        "annual_return": float((1 + total) ** (periods_per_year / n) - 1),
        "annual_vol": deviation * math.sqrt(periods_per_year),
        "sharpe": float(returns.mean() / deviation * math.sqrt(periods_per_year)) if deviation > 0 else 0.0,
        "max_drawdown": float(np.max((peak - equity) / peak)),
    }

summary = performance_summary([0.10, -0.20, 0.05], 252)
print(round(summary["total_return"], 4), round(summary["max_drawdown"], 4))     # -0.076 0.2   equity 1 -> 1.1 -> 0.88 -> 0.924: a 20% fall from the peak
print(performance_summary([0.0, 0.0, 0.0], 252)["sharpe"])                       # 0.0   no variation: a Sharpe of 0, not an error
# The runner only composes: if a number looks wrong you can test each stage on its own
def run_strategy(prices, fast, slow, cost, periods_per_year=252):
    positions = sma_positions(prices, fast, slow)
    returns = strategy_returns(prices, positions, cost)
    return performance_summary(returns, periods_per_year)

# (sma_positions, strategy_returns and performance_summary are the functions you wrote in this lab)
steps = ["sma_positions", "strategy_returns", "performance_summary"]
print(" -> ".join(steps))                           # sma_positions -> strategy_returns -> performance_summary
print(len(steps), "stages, one result: a dict of five numbers")        # 3 stages, one result: a dict of five numbers

Seu projeto final

Agora construa o seu, no seu próprio repositório. Escolha uma de duas direções. Uma biblioteca de precificação: Black-Scholes e as gregas, um precificador Monte Carlo com variáveis antitéticas, um solucionador de volatilidade implícita e a calibração de um modelo a um conjunto de cotações. Ou uma biblioteca de estratégias: vários sinais, um backtest com custos e limites de posição, avaliação walk-forward e um relatório. Em ambos os casos, entregue as mesmas coisas: um pacote com API pública clara e anotações de tipo; testes com sementes fixas e casos conferidos à mão; um pyproject.toml e um README mostrando como instalar e rodar um exemplo; um workflow de CI com lint, tipos e testes; uma nota de desempenho com um gargalo perfilado; e um texto curto sobre as premissas e o que quebraria o modelo. Divida seu tempo: projete a interface primeiro, construa o menor pipeline que funcione e depois amplie.

Um bom resultado é chato de reproduzirOutra pessoa deve poder clonar seu repositório, rodar um comando e obter exatamente seus números. Fixe sementes, congele versões, guarde o instantâneo de dados ou um script que o busque e mostre a curva de capital ao lado de uma referência simples. Informe o que a estratégia custa em drawdown e giro além do que ganha, e diga claramente quais partes você não testou. Essa honestidade é o que torna confiável o trabalho de um quant.
  • Uma biblioteca são pequenas funções testadas compostas num pipeline atrás de uma interface clara: sinal -> posições -> retornos após custos -> métricas, ligadas por um executor.
  • Um backtest honesto não tem antecipação (a posição em t usa preços até t e ganha o retorno seguinte) e cobra custos a cada mudança de posição.
  • Resuma com retorno total e anual, volatilidade, Sharpe e drawdown máximo; depois entregue um pacote com testes, CI, um README e um texto honesto.

Isso completa o roteiro. Agora você tem a linguagem, as ferramentas numéricas, as peças de dados de mercado e execução, os modelos e os hábitos de engenharia de um desenvolvedor quant em atividade. Guarde seu repositório final: é a melhor prova das habilidades que você construiu aqui.

Lab: o que é avaliado

  • Transformar uma tendência em posições com sma_positions25 pts
  • Fazer backtest com custos em strategy_returns25 pts
  • Julgar o resultado com performance_summary25 pts
  • Ligar tudo em run_strategy25 pts
Explorador · lab-142-1
▾ lab-142-1
…
bash · no navegador
learner@quantcraft:~/lab-142-1$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-142-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