Les indicateurs en code : SMA, EMA, RSI, ATR et bandes de Bollinger vectorisés, vérifiés par rapport à une plateforme graphique
Écrivez des fonctions pandas vectorisées pour les cinq indicateurs les plus utilisés, et découvrez pourquoi leurs valeurs peuvent différer de celles d'une plateforme graphique et comment les réconcilier.
Ce que vous allez apprendre
- Comment
rollingetewmimplémentent la SMA, l'EMA, le lissage de Wilder, l'ATR et les bandes de Bollinger, en quelques lignes chacun. - Quels paramètres (
adjust,alpha,ddof) modifient silencieusement les chiffres. - Pourquoi votre RSI ou votre EMA diffère de la plateforme graphique sur les premières barres, et comment comparer correctement les deux.
- Comment organiser les indicateurs sous forme de fonctions pures dans
src/indicators.pypour les réutiliser et les tester.
Explication
Le cours L'analyse technique approfondie expliquait ce que signifient ces indicateurs ; ici, l'objectif est un code correct, rapide et testable. Chaque fonction prend une Series et renvoie une Series sur le même index.
SMA (simple moving average, moyenne mobile simple) : def sma(s, n): return s.rolling(n).mean().
EMA (exponential moving average, moyenne mobile exponentielle) : def ema(s, n): return s.ewm(span=n, adjust=False).mean(). Avec span=n, la constante de lissage vaut α = 2 ÷ (n + 1) ; pour n = 20, α = 0.0952. adjust=False donne la formule récursive EMAt = α × Pt + (1 − α) × EMAt−1 qu'utilisent les plateformes ; la valeur par défaut adjust=True pondère différemment les quelques dizaines de premières barres. pandas initialise avec le premier prix ; la plupart des plateformes initialisent avec la SMA des n premières barres. Les deux initialisations s'estompent ; après environ 3n barres, les séries concordent étroitement.
RSI (relative strength index, indice de force relative), version de Wilder :
delta = s.diff()
gain = delta.clip(lower=0); loss = -delta.clip(upper=0)
avg_gain = gain.ewm(alpha=1/n, adjust=False).mean()
avg_loss = loss.ewm(alpha=1/n, adjust=False).mean()
rsi = 100 - 100 / (1 + avg_gain / avg_loss)
Le lissage de Wilder est une EMA avec α = 1 ÷ n, et non 2 ÷ (n + 1) ; utiliser span=14 ici est le bug de RSI le plus fréquent. Lorsque avg_loss vaut zéro, le RSI devient correctement 100.
ATR (average true range, étendue vraie moyenne) : l'étendue vraie (true range) est la plus grande des valeurs plus haut − plus bas, |plus haut − clôture précédente| et |plus bas − clôture précédente| :
pc = close.shift(1)
tr = pd.concat([high - low, (high - pc).abs(), (low - pc).abs()], axis=1).max(axis=1)
atr = tr.ewm(alpha=1/n, adjust=False).mean()
Certaines plateformes affichent plutôt un ATR fondé sur une SMA ; vérifiez avant de comparer. La leçon 8 utilise l'ATR pour les stops et le dimensionnement des positions.
Bandes de Bollinger : bande centrale = SMA(n), bandes supérieure et inférieure = centrale ± k × écart-type sur les mêmes n barres, typiquement n = 20, k = 2. Le piège est ddof : dans pandas, rolling(n).std() divise par n − 1 (échantillon), alors que les plateformes divisent généralement par n (population). Écrivez std(ddof=0) pour concorder. La valeur d'échantillon est plus grande, suffisamment pour transformer un « contact » avec la bande en « absence de contact ».
Vérifier par rapport à une plateforme. Utilisez la même série ajustée que la plateforme, calculez votre indicateur et comparez les 100 dernières valeurs, car les écarts d'initialisation s'atténuent. Utilisez np.allclose avec une petite tolérance. Les écarts persistants proviennent de cinq sources habituelles : données ajustées ou non ajustées, initialisation, span au lieu de alpha, ddof, ou un autre prix d'entrée. Corrigez ou documentez l'écart ; n'« ajustez » jamais un paramètre jusqu'à ce que les chiffres concordent.
Exemple commenté
Exemple hypothétique : dix clôtures journalières en euros : 20.00, 20.50, 21.00, 20.80, 21.40, 21.00, 21.60, 22.00, 21.80, 22.40.
SMA(5) sur la dernière barre : (21.00 + 21.60 + 22.00 + 21.80 + 22.40) ÷ 5 = 108.80 ÷ 5 = €21.76.
Bollinger(5, 2) sur la dernière barre. Écarts par rapport à 21.76 : −0.76, −0.16, 0.24, 0.04, 0.64 ; somme des carrés 1.0720. Variance de population = 1.0720 ÷ 5 = 0.2144, écart-type 0.4630, bande supérieure = 21.76 + 2 × 0.4630 = €22.69, bande inférieure €20.83. Formule d'échantillon : 1.0720 ÷ 4 = 0.2680, écart-type 0.5177, bande supérieure €22.80, bande inférieure €20.72.
EMA(5) avec α = 2 ÷ 6 = 0.3333 et adjust=False : EMA1 = 20.00 (initialisation), EMA2 = 0.3333 × 20.50 + 0.6667 × 20.00 = 20.1667, EMA3 = 0.3333 × 21.00 + 0.6667 × 20.1667 = 20.4444, et ainsi de suite jusqu'à 21.8310 sur la dernière barre.
Étendue vraie d'une barre avec un plus haut de €21.20, un plus bas de €20.60 et une clôture précédente de €20.80 : max(0.60, |21.20 − 20.80| = 0.40, |20.60 − 20.80| = 0.20) = €0.60.
Erreur fréquente
ewm(span=14) utilise α = 2 ÷ 15 = 0.133, alors que le RSI et l'ATR de Wilder exigent ewm(alpha=1/14), α = 0.0714. La version avec span réagit presque deux fois plus vite et ne concordera avec aucune plateforme standard. Écrivez explicitement l'α voulu et consignez-le dans la docstring de la fonction.Exercice pratique
Sur papier, avec un RSI de Wilder sur 3 périodes à titre purement illustratif (l'usage réel est de 14) : clôtures 50.00, 50.50, 51.00, 50.80. (a) Listez les gains et les pertes par barre. (b) ewm(alpha=1/3, adjust=False) de pandas initialise avec la première observation ; calculez avg_gain et avg_loss sur la dernière barre, ainsi que le RSI. (c) Expliquez en une phrase pourquoi une plateforme qui initialise avec la moyenne simple des trois premières variations afficherait ici une valeur différente, et ce que vous feriez avant de conclure que votre code est faux.
Afficher la solution
(a) Variations : +0.50, +0.50, −0.20. Gains : 0.50, 0.50, 0.00. Pertes : 0.00, 0.00, 0.20.
(b) α = 1/3. avg_gain : 0.50 (initialisation) → (1/3)(0.50) + (2/3)(0.50) = 0.50 → (1/3)(0) + (2/3)(0.50) = 0.3333. avg_loss : 0 → 0 → (1/3)(0.20) + (2/3)(0) = 0.0667. RS = 0.3333 ÷ 0.0667 = 5.0 ; RSI = 100 − 100 ÷ (1 + 5) = 83.33.
(c) Ici, l'initialisation par moyenne simple (gains 0.3333, pertes 0.0667) donne par hasard le même 83.33, mais en général les deux initialisations diffèrent sur les quelques dizaines de premières barres ; comparez donc les 100 valeurs les plus récentes d'une longue série avant de toucher au code.
Limites : un RSI sur 3 périodes est un outil pédagogique ; quatre barres ne peuvent pas montrer le comportement de convergence qui compte en pratique.
Points clés à retenir
- La SMA s'écrit
rolling(n).mean(); l'EMAewm(span=n, adjust=False); le lissage de Wilder pour le RSI et l'ATRewm(alpha=1/n, adjust=False). - Les bandes de Bollinger de la plupart des plateformes utilisent l'écart-type de population : passez
ddof=0. - Les écarts d'initialisation s'estompent ; comparez les 100 dernières barres avec une tolérance, et cherchez les cinq causes habituelles d'écart avant de modifier le code.
- Conservez les indicateurs sous forme de fonctions pures documentées afin de pouvoir les tester unitairement et les réutiliser dans chaque backtest.
Étape suivante
La leçon 7 assemble les éléments en un backtest vectorisé : signaux, positions, le décalage qui empêche le biais d'anticipation, coûts de transaction et courbe de capital.
Auto-évaluation
Répondez aux questions, puis vérifiez vos réponses. Vous pouvez recommencer autant de fois que vous le souhaitez.
Auto-évaluation automatique pour vos propres révisions. Vos réponses sont vérifiées dans votre navigateur ; nous ne les voyons pas et ne les évaluons pas. Ce n'est ni un examen ni une qualification.
Votre progression
« Terminée » signifie seulement que vous avez marqué la leçon comme lue. Ce n'est pas un test de votre compréhension. La progression et les notes sont enregistrées uniquement dans ce navigateur, sur cet appareil — pas dans un compte.