Voici une première approche à tester, les conditions étant assez strictes, peu de résultats vont remonter.
// ============================================================
// SCREENER PEA - COMPRESSION / PRE-EXPLOSION HAUSSIERE
// Timeframe recommandé : Daily
// ============================================================
// -----------------------------------------------------------
// BLOC 1 : MOYENNES MOBILES - prix au-dessus de toutes les MAs
// -----------------------------------------------------------
sma30 = Average[30](close)
sma50 = Average[50](close)
sma150 = Average[150](close)
sma200 = Average[200](close)
wma4 = WeightedAverage[4](close)
wma12 = WeightedAverage[12](close)
ema21 = ExponentialAverage[21](close)
aboveSMA30 = close > sma30
aboveSMA50 = close > sma50
aboveSMA150 = close > sma150
aboveSMA200 = close > sma200
aboveWMA12 = close > wma12
aboveWMA4 = close > wma4
// Prix au-dessus ou sur le point de casser l'EMA21
// (close dans les 1% sous l'EMA21 ou au-dessus)
nearEMA21 = close >= ema21 * 0.99
allAboveMAs = aboveSMA30 AND aboveSMA50 AND aboveSMA150 AND aboveSMA200 AND aboveWMA12 AND aboveWMA4 AND nearEMA21
// -----------------------------------------------------------
// BLOC 2 : WMA4 HAUSSIERE depuis 1 à 3 jours
// -----------------------------------------------------------
wma4Rising = (wma4 > wma4[1]) AND (wma4[1] > wma4[2] OR wma4[2] > wma4[3])
// -----------------------------------------------------------
// BLOC 3 : BOLLINGER BANDS (20, 0.75) - Resserrement
// Calcul manuel pour utiliser le coefficient 0.75
// -----------------------------------------------------------
bbPeriod = 20
bbCoeff = 0.75
bbMid = Average[bbPeriod](close)
bbStd = std[bbPeriod](close)
bbUp = bbMid + bbCoeff * bbStd
bbDown = bbMid - bbCoeff * bbStd
// Largeur actuelle et sur les 5 derniers jours
bbWidth = bbUp - bbDown
bbWidth1 = (Average[bbPeriod](close[1]) + bbCoeff * std[bbPeriod](close[1])) - (Average[bbPeriod](close[1]) - bbCoeff * std[bbPeriod](close[1]))
bbWidth2 = (Average[bbPeriod](close[2]) + bbCoeff * std[bbPeriod](close[2])) - (Average[bbPeriod](close[2]) - bbCoeff * std[bbPeriod](close[2]))
bbWidth3 = (Average[bbPeriod](close[3]) + bbCoeff * std[bbPeriod](close[3])) - (Average[bbPeriod](close[3]) - bbCoeff * std[bbPeriod](close[3]))
bbWidth4 = (Average[bbPeriod](close[4]) + bbCoeff * std[bbPeriod](close[4])) - (Average[bbPeriod](close[4]) - bbCoeff * std[bbPeriod](close[4]))
bbWidth5 = (Average[bbPeriod](close[5]) + bbCoeff * std[bbPeriod](close[5])) - (Average[bbPeriod](close[5]) - bbCoeff * std[bbPeriod](close[5]))
// Contraction continue sur 5 jours
bbSqueeze = bbWidth <= bbWidth1 AND bbWidth <= bbWidth2 AND bbWidth <= bbWidth3 AND bbWidth <= bbWidth4 AND bbWidth <= bbWidth5
// Contraction dans le bas des 30 dernières barres (+5% tolérance)
bbWidthLow30 = Lowest[30](bbWidth)
bbSqueezeStrong = bbWidth <= bbWidthLow30 * 1.05
// -----------------------------------------------------------
// BLOC 4 : BAISSE DE VOLATILITE (ATR décroissant)
// -----------------------------------------------------------
atr14 = AverageTrueRange[14](close)
atrDecreasing = atr14 < atr14[3]
// -----------------------------------------------------------
// BLOC 5 : BAISSE DES VOLUMES (compression des volumes)
// -----------------------------------------------------------
avgVol20 = Average[20](volume)
volDecreasing = volume < avgVol20 AND volume < volume[1]
// -----------------------------------------------------------
// BLOC 6 : RESISTANCE HORIZONTALE IDENTIFIEE
// Prix à moins de 3% sous le plus haut des 20 dernières barres
// -----------------------------------------------------------
recentHigh20 = Highest[20](high)
nearResistance = close >= recentHigh20 * 0.97 AND close < recentHigh20
// -----------------------------------------------------------
// BLOC 7 : SUPPORT HORIZONTAL IDENTIFIE
// Prix à moins de 8% au-dessus du plus bas des 20 dernières barres
// -----------------------------------------------------------
recentLow20 = Lowest[20](low)
aboveSupport = close <= recentLow20 * 1.08 AND close > recentLow20
// Au moins support OU résistance identifié
srIdentified = nearResistance OR aboveSupport
srBoth = nearResistance AND aboveSupport
// -----------------------------------------------------------
// BLOC 8 : COMPRESSION OBLIQUE (triangle convergent)
// Pente des hauts et des bas sur les 15 dernières barres
// -----------------------------------------------------------
lookback = 15
highNow = Highest[3](high)
highPast = Highest[3](high[lookback])
slopeHigh = (highNow - highPast) / lookback
lowNow = Lowest[3](low)
lowPast = Lowest[3](low[lookback])
slopeLow = (lowNow - lowPast) / lookback
// Résistance oblique descendante (hauts qui baissent)
fallingResistance = slopeHigh < 0
// Support oblique montant (bas qui montent)
risingSupport = slopeLow > 0
// Compression = au moins un côté oblique actif
obliqueCompression = fallingResistance OR risingSupport
// Compression forte = triangle des deux côtés
strongCompression = fallingResistance AND risingSupport
// -----------------------------------------------------------
// SYNTHESE - SCORE DE QUALITE (affiché en colonne)
// -----------------------------------------------------------
score = 0
if allAboveMAs then
score = score + 1
endif
if wma4Rising then
score = score + 1
endif
if bbSqueeze then
score = score + 1
endif
if bbSqueezeStrong then
score = score + 1
endif
if atrDecreasing then
score = score + 1
endif
if volDecreasing then
score = score + 1
endif
if nearResistance then
score = score + 1
endif
if aboveSupport then
score = score + 1
endif
if srBoth then
score = score + 1
endif
if strongCompression then
score = score + 2
endif
if obliqueCompression then
score = score + 1
endif
// -----------------------------------------------------------
// CONDITION FINALE DU SCREENER
// -----------------------------------------------------------
mandatory = allAboveMAs AND wma4Rising AND bbSqueeze AND atrDecreasing AND volDecreasing
srCondition = srIdentified
compressionCondition = obliqueCompression
finalCondition = mandatory AND srCondition AND compressionCondition
SCREENER[finalCondition](score AS "Score" , (bbWidth/bbMid*100) AS "BB%" , (atr14/close*100) AS "ATR%" , (close/sma200-1)*100 AS "vs200")
Voici l’architecture logique du screener bloc par bloc :
BLOC 1 – Moyennes mobiles : le close doit être au-dessus de SMA30, SMA50, SMA150, SMA200, WMA12, WMA4. Pour l’EMA21, une tolérance de 1% est acceptée vers le bas pour attraper les actions qui sont sur le point de la casser.
BLOC 2 – WMA4 haussière : la WMA4 monte depuis aujourd’hui, et au moins une des deux journées précédentes était également en hausse, ce qui couvre les 1 à 3 jours demandés.
BLOC 3 – Bollinger (20, 0.75) : comme ProRealTime ne permet pas de passer 0.75 directement dans BollingerUp/Down, le calcul est fait manuellement avec std. Le resserrement est détecté sur 5 jours consécutifs et validé par rapport au plus bas des 30 dernières barres.
BLOC 4 – Baisse de volatilité : l’ATR14 actuel est inférieur à l’ATR14 d’il y a 3 jours.
BLOC 5 – Baisse des volumes : le volume est inférieur à la moyenne 20 jours ET inférieur à celui d’hier.
BLOC 6 & 7 – Support / résistance horizontaux : le close est proche (à moins de 3%) d’un sommet récent 20 barres pour la résistance, ou proche (à moins de 8%) d’un creux récent 20 barres pour le support.
BLOC 8 – Compression oblique : la pente des hauts (15 barres) est négative = résistance oblique descendante. La pente des bas est positive = support oblique montant. Les deux ensemble = triangle convergent parfait. Le screener exige au moins l’un des deux.
Colonnes de sortie : Score (0 à 12), BB% (largeur des bandes en % du prix), ATR% (volatilité relative), vs200 (écart en % au-dessus de la SMA200).
Tu peux assouplir nearEMA21 en remplaçant 0.99 par 0.98 si tu veux capturer des actions encore un peu sous l’EMA21, ou augmenter le lookback du bloc 8 de 15 à 20 pour des triangles plus larges.