Comment détecter les supports et les résistances dynamiques avec Python ?
Les supports et les résistances occupent une place importante dans l'analyse technique des marchés financiers. Ils permettent d'identifier des zones de prix où le marché a précédemment rencontré une opposition support/résistance, marqué un retournement ou changé de direction.
L'analyse des supports et des résistances est une tâche réalisée par le trader technique de façon subjective alors comment supprimer la subjectivité en automatisant leur détection ? Comment distinguer un pivot significatif d'une simple fluctuation du cours ? Et comment évaluer la qualité des niveaux obtenus ?
L'objectif est de comprendre comment Python peut détecter des pivots de marché, rechercher des zones de volume pertinentes, calculer un score de qualité et gérer le cycle de vie des supports et résistances.
Pour cet algorithme nous utiliserons principalement les librairies Pandas et NumPy pour les calculs sur les signaux, yfinance pour la récupération des datas et matplotlib pour le tracé des graphiques.
Voici le graphique obtenu par l'algorithme pour l'action AIR LIQUIDE à l'échelle de temps 1 jour / 2 ans :
![]() |
| Détection des supports et des résistances avec Python |
Niveau de résistance : 176,48
Niveau de support : 163,81 - 155,56 - 143,75
Le Niveau 46 vert commence par être un niveau de support puis il est cassé, il devient un niveau de résistance, cassé à son tour.
Principe général de l'algorithme
Une méthode classique consiste à rechercher les plus hauts et les plus bas locaux d'un graphique, puis à tracer des lignes horizontales à ces endroits.
Cette approche présente une limite : tous les sommets et tous les creux n'ont pas la même importance. Certains correspondent à des retournements significatifs, tandis que d'autres ne sont que des fluctuations temporaires.
Faut-il chercher en priorité les prix où les acheteurs et les vendeurs se sont le plus affrontés, ou les prix où le cours a effectivement été rejeté ?
Notre algorithme va chercher à combiner les deux phénomènes, mais il va donner la priorité à la structure des prix pour localiser les niveaux, puis utiliser le volume pour sélectionner et affiner ces niveaux.
C'est une démarche algorithmique qui repose sur une l'idée selon laquelle le prix propose un niveau, le volume contribue à le préciser, et la réaction du marché permet d'évaluer sa pertinence.
Le prix détermine les niveaux candidats
La première étape consiste à rechercher les pivots hauts et les pivots bas. Un pivot bas constitue un candidat support, tandis qu'un pivot haut constitue un candidat résistance.
Dans notre implémentation, cette détection est réalisée dans la fonction dynamic_levels(). Le paramètre swing définit le nombre de bougies utilisées de part et d'autre du pivot pour confirmer son importance locale.
def dynamic_levels( df: pd.DataFrame, cfg: Optional[SR_Settings] = None ) -> list:
cfg = cfg or SR_Settings()
# --- a) pivots confirmés : niveaux candidats, rangés par barre de confirmation --- #
pending = {}
for i in range(s, n - s):
for is_support, hit in ((True, l[i - s:i + s + 1].argmin() == s),
(False, h[i - s:i + s + 1].argmax() == s)):
if not hit:
continue
t = i + s # barre de confirmation
lo = max(0, t - cfg.lookback + 1)
price = l[i] if is_support else h[i]
edges, buy, sell = volume_profile( h[lo:t + 1], l[lo:t + 1], c[lo:t + 1], pc[lo:t + 1], v[lo:t + 1], cfg.rows )
...
Pour comprendre le concept de pivot vous pouvez vous référer à la stratégie des fractales de Bill Williams, c'est exactement le même principe :
Pour être considéré comme un pivot bas, le plus bas de la fenêtre doit se trouver sur la bougie centrale. La même logique s'applique au pivot haut, en recherchant le plus haut de la fenêtre.
Le paramètre swing détermine donc la sensibilité de la détection. Une petite valeur produit davantage de pivots, tandis qu'une valeur plus importante privilégie des mouvements de plus grande amplitude.
Le profil de volume affine la localisation
Une fois le pivot identifié et confirmé, l'algorithme construit un profil de volume à partir des bougies disponibles jusqu'à cette date.
La fonction volume_profile() répartit le volume de chaque bougie entre plusieurs tranches de prix, en fonction de la portion de son intervalle High-Low recouvrant chaque tranche.
Le profil permet d'identifier les zones où les volumes estimés sont les plus concentrés. Le POC (Point of Control) désigne la tranche présentant le volume cumulé le plus élevé dans le profil étudié.
Cependant, le POC n'est pas automatiquement un support ou une résistance. L'algorithme recherche plutôt un nœud de volume pertinent à proximité du pivot, en tenant compte de plusieurs critères.
Une autre particularité de cette implémentation concerne la répartition estimée des volumes acheteurs et vendeurs. Elle tient compte de la position de la clôture dans le True Range, qui intègre le plus haut, le plus bas et la clôture précédente.
def volume_profile( high: np.ndarray, low: np.ndarray, close: np.ndarray,
prev_close: np.ndarray, volume: np.ndarray, rows: int ):
"""
Profil acheteurs / vendeurs sur les barres fournies.
Retourne (edges, buy, sell) : bords des lignes (rows + 1) et volumes estimés par ligne.
"""
edges = np.linspace(low.min(), high.max(), rows + 1)
bot, top = edges[:-1], edges[1:]
rng = high - low
flat = rng < 1e-12 # bougie sans range : High == Low
rng = np.where( flat, 1.0, rng )
# part du range de chaque bougie qui recouvre chaque ligne (barres x lignes)
overlap = np.minimum(high[:, None], top) - np.maximum(low[:, None], bot)
share = np.clip( overlap, 0, None) / rng[:, None] # plus rapide que np.round(overlap / rng[:, None], 6)
# bougie plate : tout son volume sur la ligne qui contient la clôture
row_close = np.clip( np.searchsorted( edges, close, side="right" ) - 1, 0, rows - 1 )
share[flat] = 0.0
share[flat, row_close[flat]] = 1.0
# sens du volume : position de la clôture dans le range VRAI (gap avec la clôture précédente inclus)
# clôture près du haut -> acheteurs ; aucun mouvement du tout -> 50/50
tr_low = np.minimum( low, prev_close )
tr_rng = np.maximum( high, prev_close ) - tr_low
tr_flat = tr_rng < 1e-12
buy_ratio = np.where( tr_flat, 0.5, (close - tr_low) / np.where( tr_flat, 1.0, tr_rng ) )
vol = volume[:, None] * share
buy = (vol * buy_ratio[:, None]).sum(axis=0)
sell = (vol * (1.0 - buy_ratio)[:, None]).sum(axis=0)
return edges, buy, sell
Cette approche prend en compte les gaps entre deux bougies successives. Une clôture située vers le haut du True Range augmente la part estimée des acheteurs, tandis qu'une clôture vers le bas augmente celle des vendeurs.
Il faut préciser que ces volumes sont "estimés" à partir des données OHLCV. Ils ne correspondent pas à une mesure réelle des transactions initiées par les acheteurs et les vendeurs, laquelle nécessiterait des données de transactions plus détaillées.
Sélectionner un nœud de volume pertinent
Le simple fait qu'un volume important ait été échangé à proximité d'un pivot ne suffit pas. L'algorithme doit déterminer si le nœud de volume apporte réellement des informations utiles à la localisation du niveau.
Cette recherche est effectuée par la fonction _best_node(). Elle combine plusieurs critères :
- La force du nœud : son volume rapporté à celui du POC.
- Le relief local : l'importance du volume par rapport aux tranches voisines.
- La dominance directionnelle relative : l'excès estimé d'acheteurs pour un support, ou de vendeurs pour une résistance, par rapport au biais global du profil.
- La proximité du pivot : la distance entre le nœud et le prix du retournement.
La règle numéro trois est importante. Dans une tendance, les clôtures peuvent se situer fréquemment vers le haut favorisant les acheteurs ou vers le bas des bougies favorisant les vendeurs. La répartition estimée des volumes peut alors présenter un biais général. Comparer chaque nœud à ce biais global permet de rechercher une dominance locale plus pertinente.
Le rayon de recherche est défini par le paramètre search_atr. Il dépend de l'ATR, un indicateur de volatilité qui permet d'adapter la distance de recherche aux caractéristiques du titre.
L'algorithme sélectionne ensuite le nœud présentant la meilleure évidence selon les pondérations définies dans le code.
Le niveau devient une zone de prix
Une fois le nœud sélectionné, l'algorithme rapproche le pivot initial du prix central de ce nœud. Ce déplacement est contrôlé par deux paramètres : pull, qui règle l'attraction vers le nœud, et le score d'évidence obtenu.
Le calcul est réalisé ainsi :
# --- c) le profil tire le pivot vers le noeud --- #
level = price + cfg.pull * evidence * (node_price - price)
Si l'évidence est faible, le niveau reste proche du pivot. Si elle est élevée, le niveau se rapproche davantage du nœud de volume retenu.
Mais notre algorithme ne s'arrête pas à une ligne horizontale. Il définit également une zone de prix autour du niveau, en conservant le pivot brut et le prix final comme références.
half = _MIN_ZONE_ATR * atr_np[t] / 2
lo, hi = min( price, level - half ), max( price, level + half ) # zone : pivot brut / niveau final
La largeur minimale de la zone dépend de l'ATR. Cette règle évite de réduire le support ou la résistance à une ligne trop précise, notamment lorsque le déplacement du pivot vers le nœud de volume est faible.
La ligne affichée sur le graphique représente le prix de référence de la zone. La bande colorée matérialise son étendue.
Évaluer la qualité du niveau
L'algorithme calcule ensuite un score compris entre 0 et 100 à chaque niveau candidat.
Ce score combine quatre composantes : l'évidence du profil de volume, une estimation de l'absorption, le volume relatif de la bougie du pivot et la taille relative de sa mèche de rejet.
L'absorption est ici une approximation fondée sur l'association d'un volume élevé et d'un corps de bougie relativement petit. Elle constitue un indice potentiel de confrontation entre acheteurs et vendeurs, mais ne prouve pas à elle seule qu'une absorption effective a eu lieu.
Les pondérations sont définies dans le code :
_W_SCORE = np.array([
0.55, # évidence du profil
0.15, # absorption estimée
0.15, # volume relatif du pivot
0.15 # rejet par la mèche
])
L'évidence apportée par le profil représente ainsi 55 % du score final. Les trois autres critères représentent chacun 15 %.
Le paramètre min_score détermine le score minimal nécessaire pour conserver un niveau. Cette valeur permet de filtrer les candidats les moins convaincants selon les règles de l'algorithme.
Il est important de comprendre que ce score est un indicateur de classement, et non une probabilité de réussite d'un futur rebond.
Gérer le cycle de vie des supports et des résistances
Les niveaux sélectionnés sont ensuite suivis au fil des bougies. Un support est invalidé lorsque le cours de clôture passe suffisamment sous son prix et inversement une résistance est invalidée lorsque le cours passe suffisamment au-dessus.
L'algorithme distingue alors trois états :
- Niveau actif : la zone reste valide selon les règles de l'algorithme.
- Niveau cassé : le cours a franchi la zone au-delà du seuil d'invalidation.
- Niveau dont le rôle a été inversé : une cassure peut transformer un support en résistance, ou une résistance en support.
Cette dernière fonctionnalité est contrôlée par le paramètre flip_levels. Lorsqu'elle est activée, une cassure peut créer un nouveau niveau de rôle opposé, au même prix de référence et avec le même score. Ce niveau retourné ne peut lui-même changer de rôle qu'une seule fois : s'il est cassé à son tour, il s'arrête.
L'algorithme prévoit également la fusion des niveaux de même nature suffisamment proches, grâce au paramètre merge_atr, ainsi qu'une limite sur le nombre de niveaux actifs avec max_levels.
Le graphique final distingue ces états par le style des lignes : les niveaux actifs sont tracés en trait plein, les niveaux dont le rôle a été inversé en tirets, et les niveaux cassés en pointillés. Les zones sont matérialisées par des bandes colorées.
L'objectif est donc de conserver une représentation lisible du marché, en évitant de multiplier les niveaux redondants et en suivant leur évolution au fil des nouvelles bougies.
L'algorithme est piloter par un certain nombre de paramètres évoqués plus haut avec leurs possibles ajustement par l'utilisateur :
@dataclass
class SR_Settings:
# --- Profil de volume (reconstruit à la confirmation de chaque pivot) --- #
lookback: int = 200 # nombre de barres du profil
rows: int = 40 # nombre de lignes de prix
# --- Moteur de pivots guidé par le profil --- #
swing: int = 10 # confirmation gauche/droite des pivots
atr_period: int = 14
search_atr: float = 1.5 # rayon de recherche d'un noeud autour du pivot (x ATR)
min_node: float = 0.25 # volume mini du noeud / POC
min_evidence: float = 0.45 # évidence minimale (0-1)
pull: float = 0.7 # attraction du pivot vers le noeud (0 = pivot brut, 1 = noeud)
min_score: float = 45.0 # score final minimal (0-100)
max_levels: int = 8 # nombre max de niveaux actifs
merge_atr: float = 0.5 # fusion des niveaux de même sens plus proches que ça (x ATR)
break_atr: float = 0.3 # tampon d'invalidation sur la clôture (x ATR)
flip_levels: bool = True # un niveau cassé change de rôle (support / résistance), une seule fois
show_broken: bool = True # affiche aussi les niveaux cassés (pointillés + croix à la cassure)
show_cross: bool = False # affiche les croisements de niveaux
Conclusion
Il est intéressant de détailler chacune des étapes de l'algorithme qui sont en réalité la traduction de ce que ferait le trader lorsqu'il analyse un graphique manuellement repérer les sommets et les creux, observer les réactions du cours, examiner les volumes échangés, identifier les zones de prix importantes, puis suivre leur évolution.
La différence, ici, c'est que cette démarche est formalisée et automatisée. Une fois l'algorithme développé, il devient possible de l'appliquer à des dizaines, voire des centaines d'actions, sans devoir analyser chaque graphique individuellement. C'est tout l'intérêt de la programmation appliquée à l'analyse technique : transformer une méthode d'observation en un processus reproductible, capable de traiter un grand nombre de titres.
Mais développer ce type d'algorithme est plus complexe qu'il n'y paraît.
Lorsqu'un trader trace un support ou une résistance à la main, il prend en compte de nombreux facteurs, parfois même inconsciemment. Il peut accepter une certaine imprécision sur le prix, accorder davantage d'importance à une réaction spectaculaire ou relativiser un niveau en fonction du contexte général du marché. Traduire ce raisonnement en règles informatiques oblige à rendre explicites des choix qui restent souvent intuitifs dans l'analyse manuelle.
Chaque règle introduite dans l'algorithme implique ainsi un compromis :
- Combien de bougies faut-il prendre en compte pour confirmer un pivot ?
- Quelle distance doit séparer deux niveaux pour qu'ils soient considérés comme distincts ?
- Quelle importance accorder au volume par rapport à la réaction du prix ?
- À partir de quel dépassement un support ou une résistance doit-il être considéré comme cassé ?
Il n'existe pas de réponse universelle à ces questions. Une détection très sensible identifiera davantage de niveaux, au risque de multiplier les signaux peu significatifs. Des critères plus stricts produiront moins de niveaux, mais pourront ignorer certaines réactions intéressantes. De même, un niveau de prix ne peut pas toujours être réduit à une valeur exacte, le marché réagit souvent autour d'une zone, et non sur une ligne parfaitement définie.
L'objectif n'est donc pas de reproduire une prétendue vérité mathématique du marché. Il s'agit de construire une méthode cohérente, de formaliser ses hypothèses et de choisir des règles suffisamment robustes pour être appliquées dans des contextes différents.
Enfin, un score élevé ne garantit pas qu'un support tiendra ou qu'une résistance provoquera un retournement. L'algorithme classe les niveaux selon les critères que nous avons définis ; il ne prédit pas avec certitude le comportement futur du marché. Sa pertinence doit être évaluée sur des données historiques, en veillant notamment à ne pas utiliser d'informations qui n'étaient pas disponibles au moment où les décisions auraient été prises.
Ce travail illustre finalement l'un des aspects les plus intéressants du trading algorithmique : la programmation ne remplace pas le raisonnement du trader, elle oblige à le préciser. En transformant une analyse visuelle en une succession de règles explicites, elle permet de tester ces choix, de les comparer et de les appliquer à grande échelle.
C'est cette démarche qui rend l'exercice intéressant : non pas chercher l'algorithme parfait, mais construire un outil d'analyse dont on comprend le fonctionnement, les limites et les compromis.
Comment exécutez ce script ?
Installez la plateforme de trading technique TradingInPython
En installant le logiciel de trading technique , vous pourrez exécuter ce script sur des dizaines d'actions de votre choix :
Ce script ainsi que de nombreux autres scripts vous sont livrés avec l'installation de la plateforme pour Windows desktop.
Code source en Python
Retrouvez le code source de ce script en Python dans le GitHub de la solution logicielle TradingInPython :
Note éditoriale : cet article a été élaboré avec l'assistance de ChatGPT et de Claude pour structurer les explications et approfondir l'analyse de l'algorithme. Le développement de la méthode, les choix techniques et la validation du contenu relèvent de l'auteur, qui assume la responsabilité de la publication.

Commentaires
Enregistrer un commentaire
Merci pour votre commentaire.
S'il n'apparaît pas tout de suite c'est qu'il est en attente de modération.
Merci de votre compréhension.