σμ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 11 · Systèmes de production et modèles de pointe›Sujet 11.13·~4 hExpert

Projet final : une bibliothèque de valorisation ou de stratégie de bout en bout

Position dans le parcoursModule 11 sur 11 · Sujet 13 sur 13
11.13 Projet final : une bibliothèque de valorisation ou de stratégie de bout en bout
—Score du lab— / 100XP total—Série—

Des exercices à une bibliothèque

Vous avez écrit une centaine de petites fonctions. Une bibliothèque est l'agencement de ces fonctions pour que quelqu'un d'autre (vous dans six mois compris) puisse s'en servir en confiance : une interface publique claire, chaque pièce testée seule, les pièces composées en pipeline, le tout installable, documenté et vérifié automatiquement. Ce projet de synthèse est le modèle en miniature. Une bibliothèque de stratégie est le meilleur endroit pour s'y exercer : le signal transforme les prix en positions, le backtest transforme les positions en rendements après coûts, les métriques résument les rendements, et un exécuteur relie le tout. Chaque étape prend des tableaux simples et en renvoie, si bien que chacune peut être testée seule avec des nombres que l'on vérifie à la main.

# 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

Signal et backtest : pas d'anticipation, coûts inclus

Le signal est ici un croisement de moyennes mobiles : être long quand la moyenne rapide des prix est au-dessus de la lente, short quand elle est en dessous, neutre quand elles sont égales ou que la lente n'existe pas encore. Les deux règles qui décident si un backtest est honnête se voient dans le code. D'abord, pas d'anticipation : la position décidée à la clôture du jour t ne peut utiliser que les prix jusqu'au jour t, et elle gagne le rendement du jour t + 1, c'est pourquoi le rendement du pas t utilise positions[t - 1]. Un test le prouve : retirer les derniers prix ne doit pas changer les positions antérieures. Ensuite, les coûts : chaque changement de position paie cost * |changement|, si bien qu'une stratégie qui bascule sans cesse peut gagner avant coûts et perdre après.

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

Les chiffres de performance, et l'exécuteur

Une série de rendements se résume en quelques chiffres. Le rendement total compose les périodes, prod(1 + r) - 1 ; le rendement annuel le ramène à un an, (1 + total)^(périodes_par_an / n) - 1 ; la volatilité annuelle est l'écart-type d'échantillon fois sqrt(périodes_par_an) ; le ratio de Sharpe divise le rendement moyen par cet écart-type, annualisé, et c'est la mesure phare du rendement par unité de risque (0 pour une série plate plutôt qu'une erreur de division) ; le drawdown maximal est la pire chute depuis un sommet de la courbe de capital, le chiffre qui dit combien de douleur la stratégie vous demande d'encaisser. Une fonction exécuteur relie le tout : positions, puis rendements, puis résumé. Elle ne doit que composer les autres fonctions, pour qu'un bug se retrouve toujours dans une étape.

# 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

Votre projet de synthèse

Construisez maintenant le vôtre, dans votre propre dépôt. Choisissez une des deux directions. Une bibliothèque de valorisation : Black-Scholes et les grecques, un valorisateur Monte Carlo avec variables antithétiques, un solveur de volatilité implicite et la calibration d'un modèle sur un jeu de cotations. Ou une bibliothèque de stratégies : plusieurs signaux, un backtest avec coûts et limites de position, une évaluation walk-forward et un rapport. Dans les deux cas, livrez les mêmes choses : un paquet avec une API publique claire et des annotations de type ; des tests à graines fixées et cas vérifiés à la main ; un pyproject.toml et un README montrant comment installer et lancer un exemple ; un workflow de CI avec lint, types et tests ; une note de performance avec un goulot profilé ; et un court texte sur les hypothèses et ce qui casserait le modèle. Répartissez votre temps : concevez d'abord l'interface, construisez le plus petit pipeline qui marche, puis élargissez.

Un bon résultat est ennuyeux à reproduireQuelqu'un d'autre doit pouvoir cloner votre dépôt, lancer une commande et obtenir exactement vos chiffres. Fixez les graines, figez les versions, gardez l'instantané de données ou un script qui le récupère, et montrez la courbe de capital à côté d'une référence simple. Indiquez ce que la stratégie coûte en drawdown et en rotation autant que ce qu'elle rapporte, et dites clairement les parties que vous n'avez pas testées. Cette honnêteté est ce qui rend le travail d'un quant digne de confiance.
  • Une bibliothèque est faite de petites fonctions testées composées en pipeline derrière une interface claire : signal -> positions -> rendements après coûts -> métriques, reliés par un exécuteur.
  • Un backtest honnête n'a pas d'anticipation (la position à t utilise les prix jusqu'à t et gagne le rendement suivant) et facture des coûts à chaque changement de position.
  • Résumez avec rendement total et annuel, volatilité, Sharpe et drawdown maximal ; puis livrez un paquet avec tests, CI, un README et un texte honnête.

Voilà qui termine le parcours. Vous avez maintenant le langage, les outils numériques, les briques de données de marché et d'exécution, les modèles et les habitudes d'ingénierie d'un développeur quant en activité. Gardez votre dépôt de synthèse : c'est la meilleure preuve des compétences acquises ici.

Lab : ce qui est noté

  • Transformer une tendance en positions avec sma_positions25 pts
  • Backtester avec coûts dans strategy_returns25 pts
  • Juger le résultat avec performance_summary25 pts
  • Tout relier dans run_strategy25 pts
Explorateur · lab-142-1
▾ lab-142-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-142-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-142-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