ProRealCode - Trading & Coding with ProRealTime™
Hola de nuevo.
Antes de nada, os deseo unas felices fiestas!!!!
Os agradecería pudierais echarme una mano con la conversión del indicador “Percentile Rank Oscillator (Price + VWMA)”
Lo podéis ver en el siguiente enlace:
La descripción que viene es:
“A statistical oscillator designed to identify potential market turning points using percentile-based price analytics and volume-weighted confirmation.
What is PRO?
Percentile Rank Oscillator measures how extreme current price behavior is relative to its own recent history. It calculates a rolling percentile rank of price midpoints and VWMA deviation (volume-weighted price drift). When price reaches historically rare levels – high or low percentiles – it may signal exhaustion and potential reversal conditions.
How it works
Why percentile rank?
Median-based percentiles ignore outliers and read the market statistically – not by fixed thresholds. Instead of guessing “overbought/oversold” values, the indicator adapts to current volatility and structure.
Key features
How to use
Tip: Look for percentile spikes at key HTF levels, after extended moves, or where liquidity sweeps occur. Strong moves into rare percentile territory may precede mean reversion.
Suggested settings
El codigo del indicador en Pinescript es el siguiente:
//@version=6
indicator("Percentile Rank Oscillator (Price + VWMA)", shorttitle="PRO", overlay=false, max_lines_count=80)
// ----------------- INPUTS -----------------
grp_core = "Core"
len_in = input.int(100, "Lookback (bars for percentile)", minval=3, maxval=500, group=grp_core)
out_mode = input.string("-1..+1", "Output scale", options=["-1..+1","0..100"], group=grp_core)
// VWMA options
grp_vw = "VWMA Percentile"
enable_vwma_pct = input.bool(true, "Enable VWMA-based percentile", group=grp_vw)
vwma_len_same = input.bool(true, "Use VWMA length = main lookback", group=grp_vw)
vwma_len_custom = input.int(100, "VWMA length (if custom)", minval=1, group=grp_vw)
// Display & thresholds
grp_display = "Display"
smooth = input.int(1, "Smoothing (RMA) length (1 = none)", minval=1, group=grp_display)
high_thresh = input.float(95.0, "Upper percentile threshold (%)", minval=50.0, maxval=100.0, step=0.1, group=grp_display)
low_thresh = input.float(5.0, "Lower percentile threshold (%)", minval=0.0, maxval=50.0, step=0.1, group=grp_display)
fill_extremes = input.bool(true, "Fill background when extremes hit", group=grp_display)
// Alerts & colors
grp_alerts = "Alerts & Colors"
alerts_price = input.bool(true, "Enable price percentile alerts", group=grp_alerts)
alerts_vwma = input.bool(true, "Enable VWMA percentile alerts", group=grp_alerts)
alerts_confl = input.bool(true, "Enable confluence alerts (price + VWMA)", group=grp_alerts) // default ON
// Colors: single top/bottom colors to keep UI minimal
grp_colors = "Colors (bg fills)"
top_color = input.color(color.new(#A53860, 80), "Top (overbought) BG color", group=grp_colors)
bot_color = input.color(color.new(#4D8B31, 80), "Bottom (oversold) BG color", group=grp_colors)
band_fill_color = input.color(color.new(#423E3B, 95), "Working band fill (between thresholds)", group=grp_colors)
// ----------------- HELPERS & SAFETY -----------------
tiny = 1e-9
len = math.max(3, math.min(len_in, 500))
// price midpoint
mid = (high + low) / 2.0
// ----------------- rolling percentile with linear interpolation -----------------
// returns percentile rank in 0..100 for current value against last n values (including current)
rolling_percentile_interp(src, n) =>
avail = math.min(n, bar_index + 1)
arr = array.new_float(0)
for i = 0 to avail - 1
array.push(arr, nz(src[i]))
array.sort(arr) // ascending
current = nz(src)
less = 0
equal = 0
for j = 0 to array.size(arr) - 1
v = array.get(arr, j)
less := less + (v < current ? 1 : 0)
equal := equal + (v == current ? 1 : 0)
pct = 0.0
if equal > 0
pct := (less + 0.5 * equal) / math.max(avail, 1) * 100.0
else
if less == 0
pct := 0.0
else if less >= avail
pct := 100.0
else
v_low = array.get(arr, less - 1)
v_high = array.get(arr, less)
denom = v_high - v_low
frac = denom == 0.0 ? 0.5 : math.max(0.0, math.min(1.0, (current - v_low) / denom))
pct := (less + frac) / math.max(avail, 1) * 100.0
pct
// ----------------- PRICE percentile -----------------
price_pct = rolling_percentile_interp(mid, len)
price_pct_smooth = smooth > 1 ? ta.rma(price_pct, smooth) : price_pct
map_to_out(x_pct) => out_mode == "-1..+1" ? (x_pct / 50.0 - 1.0) : x_pct
price_out = map_to_out(price_pct_smooth)
// ----------------- VWMA percentile (optional) -----------------
// initialize numeric variables to avoid NA-type reassignments
vwma_pct = 0.0
vwma_pct_smooth = 0.0
vw_out = 0.0
if enable_vwma_pct
vwlen = vwma_len_same ? len : math.max(1, vwma_len_custom)
vwma_mid = ta.vwma(mid, vwlen)
disp = mid - vwma_mid
vwma_pct := rolling_percentile_interp(disp, len) // percentile of deviation
vwma_pct_smooth := smooth > 1 ? ta.rma(vwma_pct, smooth) : vwma_pct
vw_out := map_to_out(vwma_pct_smooth)
else
// keep numeric defaults (vw_out stays numeric); plotting will use enable_vwma_pct ? vw_out : na
vwma_pct := 0.0
vwma_pct_smooth := 0.0
vw_out := 0.0
// ----------------- thresholds & conditions -----------------
is_price_top = price_pct >= high_thresh
is_price_bot = price_pct <= low_thresh
is_vw_top = enable_vwma_pct ? (vwma_pct >= high_thresh) : false
is_vw_bot = enable_vwma_pct ? (vwma_pct <= low_thresh) : false
confl_top = enable_vwma_pct and is_price_top and is_vw_top
confl_bot = enable_vwma_pct and is_price_bot and is_vw_bot
// ----------------- background color priority (global) -----------------
// priority: confluence (price+vw) > price > vwma
bg_price = (is_price_top or is_price_bot) and fill_extremes and alerts_price ? (is_price_top ? top_color : bot_color) : na
bg_vw = (is_vw_top or is_vw_bot) and fill_extremes and alerts_vwma ? (is_vw_top ? top_color : bot_color) : na
bg_confl = (confl_top or confl_bot) and fill_extremes and alerts_confl ? (confl_top ? top_color : bot_color) : na
final_bg = not na(bg_confl) ? bg_confl : not na(bg_price) ? bg_price : not na(bg_vw) ? bg_vw : na
bgcolor(final_bg)
// ----------------- plots (global scope) -----------------
p_price = plot(price_out, title="Price Percentile", color=color.new(#5448C8, 0), linewidth=1)
// plot vw_out only when enabled (use ternary in plot call to show na when disabled)
p_vwma = plot(enable_vwma_pct ? vw_out : na, title="VWMA Percentile", color=color.new(#548687, 0), linewidth=1)
// working band lines (in output scale)
// compute raw then round to 6 decimal places to avoid showing awkward floats like 0.899999999....
top_line_raw = out_mode == "-1..+1" ? (high_thresh / 50.0 - 1.0) : high_thresh
bot_line_raw = out_mode == "-1..+1" ? (low_thresh / 50.0 - 1.0) : low_thresh
// round
top_line = math.round(top_line_raw * 1e6) / 1e6
bot_line = math.round(bot_line_raw * 1e6) / 1e6
h_top = hline(top_line, "Top", color=#A53860)
h_bot = hline(bot_line, "Bot", color=#4D8B31)
h_mid = hline(out_mode == "-1..+1" ? 0.0 : 50.0, "Mid", color=#423E3B)
fill(h_top, h_bot, band_fill_color)
// ----------------- alerts (global scope) -----------------
alertcondition(alerts_price and is_price_top, title="Price Percentile TOP", message="Price percentile reached TOP threshold")
alertcondition(alerts_price and is_price_bot, title="Price Percentile BOTTOM", message="Price percentile reached BOTTOM threshold")
alertcondition(enable_vwma_pct and alerts_vwma and is_vw_top, title="VWMA Percentile TOP", message="VWMA percentile reached TOP threshold")
alertcondition(enable_vwma_pct and alerts_vwma and is_vw_bot, title="VWMA Percentile BOTTOM", message="VWMA percentile reached BOTTOM threshold")
alertcondition(enable_vwma_pct and alerts_confl and confl_top, title="Confluence TOP", message="Price & VWMA percentiles both at TOP extreme")
alertcondition(enable_vwma_pct and alerts_confl and confl_bot, title="Confluence BOTTOM", message="Price & VWMA percentiles both at BOTTOM extreme")
// ----------------- final note -----------------
// Price percentile measures how rare the current midpoint is relative to last 'len' bars.
// VWMA percentile measures how rare the current VWMA deviation (mid - VWMA(mid)) is relative to last 'len' bars.
// Both percentiles use linear interpolation for smooth percent rank values.
Muchas gracias y disculpad las molestias!!!
Hola (otra vez)
Como continuación del correo anterior os envío mi intento de conversión a PRT del script en Pinescript del indicador “Percentile Rank Oscillator”.
No se donde estoy equivocando y por eso os pido me echéis una mano, si podéis:
LENIN=100//"Lookback (bars for percentile)", minval=3, maxval=500
VWMALEN=100//"VWMA length (if custom)", minval=1
smooth =1//"Smoothing (RMA) length (1 = none)", minval=1
highthresh =95.0//"Upper percentile threshold (%)", minval=50.0, maxval=100.0, step=0.1
lowthresh =5.0//"Lower percentile threshold (%)", minval=0.0, maxval=50.0, step=0.1
// ----------------- HELPERS & SAFETY -----------------
len =max(3,min(lenin, 500))
// price midpoint
mid =(high + low) / 2.0
// ----------------- PRICE percentile -----------------
avail = min(len, barindex + 1)
for i = 0 to avail - 1
$ARR[I]=mid[I]
NEXT
ARRAYSORT($ARR,ASCEND)
CURRENT=mid
less = 0
equal = 0
for j = 0 to avail-1
v = $ARR[J]
IF V<CURRENT THEN
LESS=LESS+1
ELSE
LESS=LESS
ENDIF
IF V=CURRENT THEN
EQUAL=EQUAL+1
ELSE
EQUAL=EQUAL
ENDIF
NEXT
PRICEPCT = 0.0
if equal > 0 THEN
PRICEPCT= ((less + 0.5 * equal) / max(avail, 1) )* 100.0
elsif less= 0 THEN
PRICEPCT= 0.0
elsif less >= avail THEN
PRICEPCT=100.0
else
vlow= $arr[less - 1]
vhigh= $arr[less]
denom= vhigh - vlow
IF DENOM= 0 THEN
FRAC=0.5
ELSE
FRAC= max(0.0,min(1.0, (current - vlow) / denom))
ENDIF
PRICEPCT= ((less + frac) / max(avail, 1)) * 100.0
ENDIF
IF SMOOTH>1 THEN
ALPHA=1/SMOOTH
SUM1=0
PRICEPCTSMOOTH=ALPHA*PRICEPCT+(1-ALPHA)*SUM1[1]
ELSE
PRICEPCTSMOOTH=PRICEPCT
ENDIF
priceout=(PRICEPCTSMOOTH/50)-1
// ----------------- VWMA percentile (optional) -----------------
// initialize numeric variables to avoid NA-type reassignments
vw = volume*mid
vwsum = SUMMATION[VWMALEN](vw)
volsum = SUMMATION[VWMALEN](volume)
vwmaMID = vwsum/volsum
DISP=ABS(MID-VWMAMID)
avail1 = min(LEN, barindex + 1)
for k = 0 to avail1 - 1
$ARR1[k]=DISP[k]
NEXT
ARRAYSORT($ARR1,ASCEND)
CURRENT1=DISP
less = 0
equal = 0
for l = 0 to AVAIL1-1
v1 = $ARR1[l]
IF V1<CURRENT1 THEN
LESS1=LESS1+1
ELSE
LESS1=LESS1
ENDIF
IF V1=CURRENT1 THEN
EQUAL1=EQUAL1+1
ELSE
EQUAL1=EQUAL1
ENDIF
NEXT
WVMAPCT = 0.0
if equal1 > 0 THEN
WVMAPCT = ((less1 + 0.5 * equal1) / max(avail1, 1) )* 100.0
elsif less1 = 0 THEN
WVMAPCT = 0.0
elsif less1 >= avail1 THEN
WVMAPCT=100.0
else
vlow1 = $arr1[less1 - 1]
vhigh1 = $arr1[less1]
denom1 = vhigh1 - vlow1
IF DENOM1=0 THEN
FRAC1=0.5
ELSE
FRAC1=max(0.0,min(1.0, (current1 - vlow1) / denom1))
ENDIF
WVMAPCT=((less1 + frac1) / max(avail1, 1)) * 100.0
ENDIF
IF SMOOTH>1 THEN
ALPHA=1/SMOOTH
SUM2=0
WVMAPCTSMOOTH=ALPHA*WVMAPCT+(1-ALPHA)*SUM2[1]
ELSE
WVMAPCTSMOOTH=WVMAPCT
ENDIF
vwout=(WVMAPCTSMOOTH/50)-1
HIGHTHRESH1=((HIGHTHRESH/50)-1)
LOWTHRESH1=((LOWTHRESH/50)-1)
IF PRICEPCT <= lowthresh1 AND WVMAPCT <= lowthresh1 THEN
DRAWARROWUP(barindex,priceout)coloured("GREEN")
ENDIF
IF PRICEPCT >= highthresh1 AND WVMAPCT >= highthresh1 THEN
DRAWARROWDOWN(barindex,priceout)coloured("RED")
ENDIF
RETURN priceout AS "PRICE", highthresh1 AS "TOP", lowthresh1 AS "BOT", vwout AS "VWAP"
Disculpad pero no he publicado el enlace donde encontrar el indicador. Os lo pego a continuacion:
https://www.tradingview.com/script/vbIUmSZW-Percentile-Rank-Oscillator-Price-VWMA/
A ver si ahora lo he pegado bien.
Disculpad las molestias.
Buenas tardes! Aquí tienes el indicador traducido. Es lo más parecido al original que he conseguido.
// ----------------------------
// PRC_Percentile Rank Oscillator (Price + VWMA)
//version = 0
//18.12.2025
//Iván González @ www.prorealcode.com
//Sharing ProRealTime knowledge
// ----------------------------
LenIn=100// Lookback (periodos)
UseScaleMinusOne=1//1 = Escala -1 a +1, 0 = Escala 0 a 100
EnableVwmaPct=1 // 1 = Activar VWMA, 0 = Desactivar
VwmaLenSame=1 // 1 = Usar mismo periodo para VWMA
VwmaLenCustom=100// Periodo VWMA (si VwmaLenSame=0)
Smooth=1 // Suavizado (1 = ninguno)
HighThresh=95 // 95.0 Umbral Superior
LowThresh=5 //5.0 Umbral Inferior
FillExtremes=1 // 1 = Colorear fondo en extremos
// --------------------------------
// --- VARIABLES & PARAMETERS ---
// --------------------------------
// Color Definitions (R, G, B, Alpha)
// Top Color (#A53860) -> 165, 56, 96
ColorTopR = 165
ColorTopG = 56
ColorTopB = 96
// Bottom Color (#4D8B31) -> 77, 139, 49
ColorBotR = 77
ColorBotG = 139
ColorBotB = 49
// Mid/Fill Color (#423E3B) -> 66, 62, 59
ColorMidR = 66
ColorMidG = 62
ColorMidB = 59
// Calculation limits
RealLen = MAX(3, LenIn)
MidP = (high + low) / 2
// --------------------------------
// 1. PRICE PERCENTILE CALCULATION
// --------------------------------
// We calculate how many previous values are lower than the current MidP
countLess = 0
countEqual = 0
// Loop through history to calculate rank
FOR i = 0 TO RealLen - 1 DO
IF MidP[i] < MidP THEN
countLess = countLess + 1
ELSIF MidP[i] = MidP THEN
countEqual = countEqual + 1
ENDIF
NEXT
// Calculate raw percentile (0 to 100)
// Logic: (Less + 0.5 * Equal) / N * 100
PricePct = (countLess + (0.5 * countEqual)) / RealLen * 100
// Smoothing
IF Smooth > 1 THEN
PricePctSmooth = WilderAverage[Smooth](PricePct)
ELSE
PricePctSmooth = PricePct
ENDIF
// Map to Output Scale
IF UseScaleMinusOne THEN
PriceOut = (PricePctSmooth / 50.0) - 1.0
ELSE
PriceOut = PricePctSmooth
ENDIF
// --------------------------------
// 2. VWMA PERCENTILE CALCULATION
// --------------------------------
VwmaPct = 0
VwOut = 0
IF EnableVwmaPct THEN
// Determine VWMA Length
IF VwmaLenSame THEN
CurrentVwLen = RealLen
ELSE
CurrentVwLen = MAX(1, VwmaLenCustom)
ENDIF
// Manual VWMA Calculation: Sum(Price*Vol) / Sum(Vol)
VwmaNum = Summation[CurrentVwLen](MidP * Volume)
VwmaDenom = Summation[CurrentVwLen](Volume)
IF VwmaDenom > 0 THEN
MyVwma = VwmaNum / VwmaDenom
ELSE
MyVwma = MidP
ENDIF
Disp = MidP - MyVwma
// Rolling Percentile for Deviation
vCountLess = 0
vCountEqual = 0
FOR j = 0 TO RealLen - 1 DO
HistDisp = Disp[j]
IF HistDisp < Disp THEN
vCountLess = vCountLess + 1
ELSIF HistDisp = Disp THEN
vCountEqual = vCountEqual + 1
ENDIF
NEXT
VwmaPct = (vCountLess + (0.5 * vCountEqual)) / RealLen * 100
// Smoothing
IF Smooth > 1 THEN
VwmaPctSmooth = WilderAverage[Smooth](VwmaPct)
ELSE
VwmaPctSmooth = VwmaPct
ENDIF
// Map Output
IF UseScaleMinusOne THEN
VwOut = (VwmaPctSmooth / 50.0) - 1.0
ELSE
VwOut = VwmaPctSmooth
ENDIF
ENDIF
// --------------------------------
// 3. THRESHOLDS & CONDITIONS
// --------------------------------
// Determine Thresholds based on scale
IF UseScaleMinusOne THEN
TopLine = (HighThresh / 50.0) - 1.0
BotLine = (LowThresh / 50.0) - 1.0
MidLine = 0
ELSE
TopLine = HighThresh
BotLine = LowThresh
MidLine = 50
ENDIF
// Flags
IsPriceTop = PricePct >= HighThresh
IsPriceBot = PricePct <= LowThresh
IsVwTop = 0
IsVwBot = 0
IF EnableVwmaPct THEN
IsVwTop = VwmaPct >= HighThresh
IsVwBot = VwmaPct <= LowThresh
ENDIF
ConflTop = EnableVwmaPct AND IsPriceTop AND IsVwTop
ConflBot = EnableVwmaPct AND IsPriceBot AND IsVwBot
// --------------------------------
// 4. VISUALIZATION & BACKGROUND
// --------------------------------
// Background Logic (Priority: Confluence > Price > VWMA)
IF FillExtremes THEN
IF ConflTop THEN
BACKGROUNDCOLOR(ColorTopR, ColorTopG, ColorTopB, 50)
ELSIF ConflBot THEN
BACKGROUNDCOLOR(ColorBotR, ColorBotG, ColorBotB, 50)
ELSIF IsPriceTop THEN
BACKGROUNDCOLOR(ColorTopR, ColorTopG, ColorTopB, 30)
ELSIF IsPriceBot THEN
BACKGROUNDCOLOR(ColorBotR, ColorBotG, ColorBotB, 30)
ELSIF IsVwTop THEN
BACKGROUNDCOLOR(ColorTopR, ColorTopG, ColorTopB, 20)
ELSIF IsVwBot THEN
BACKGROUNDCOLOR(ColorBotR, ColorBotG, ColorBotB, 20)
ENDIF
ENDIF
// Draw Lines
// Price Percentile (Blue-ish)
// VWMA Percentile (Cyan-ish)
// We set VWMA to undefined if disabled so it doesn't plot 0 line
IF EnableVwmaPct = 0 THEN
VwOut = undefined
ENDIF
// Fill the band between thresholds
COLORBETWEEN(TopLine, BotLine, ColorMidR, ColorMidG, ColorMidB, 30)
// --------------------------------
RETURN PriceOut COLOURED(84, 84, 200) STYLE(Line, 2) AS "Price Percentile", VwOut COLOURED(84, 134, 135) STYLE(Line, 1) AS "VWMA Percentile", TopLine COLOURED(165, 56, 96) AS "Top Threshold", BotLine COLOURED(77, 139, 49) AS "Bot Threshold", MidLine COLOURED(66, 62, 59) STYLE(dottedline) AS "Mid Line"
Muchas gracias, Iván!!!!
Tan solo una pregunta, en el código en PineScript utilizan un array y veo que en tu código no lo utilizas, luego realmente no era necesario dicho array, no?
Te lo pregunto porque viendo otros scripts de TradingvView en los que se hace una clasificación por percentiles utilizan arrays (de hecho en informacion que he visto en la www para el calculo de percentiles en excel o a mano el procedimiento parece -o es lo que entiendo, si no estoy equivocado- que hacen uso de los arrays). Es decir, la poblacion de n-datos que se quiere clasificar primero se guarda en arrays, luego se ordena y finalmente se hace la clasificacion por percentiles mediante una formula. La verdad es que el tema me tiene desconcertado y estoy intentando entender la logica.
Recibe un cordial saludo.
Buenas! Tienes toda la razón… en la estadística clásica y en herramientas como Excel, el procedimiento estándar para hallar un percentil implica guardar los datos en una lista (array) y ordenarlos de menor a mayor.
Sin embargo, aquí he optado por un enfoque matemático diferente pero equivalente (rango percentil).
Aquí te explico la lógica exacta para que desaparezca el desconcierto:
1. El enfoque de “Array y Ordenación” (Pine Script / Excel) Imagina que tienes 100 alumnos en una clase y quieres saber en qué percentil está la nota de un alumno específico. El método clásico es:Poner a los 100 alumnos en fila.
Ordenarlos por nota de menor a mayor.
Ver en qué posición física ha caído tu alumno (por ejemplo, la posición 95).
Tomamos al alumno actual (el precio actual).
Lo comparamos uno por uno con los otros 99 alumnos anteriores (los precios pasados), sin moverlos de su sitio.
Simplemente contamos: “¿Es tu nota más alta que la de este? Sí (+1 punto). ¿Y que la de este? No (+0 puntos)”.
Si al final del recuento, su nota es superior a la de 95 alumnos, matemáticamente está en el Percentil 95.
En fin, espero haberme explicado bien 🙂
Percentile Rank Oscillator. Conversion Pinescript a PRT
This topic contains 6 replies,
has 2 voices, and was last updated by Pedro.lopez
1 month, 2 weeks ago.
| Forum: | ProBuilder: Indicadores y Herramientas |
| Language: | Spanish |
| Started: | 12/18/2025 |
| Status: | Active |
| Attachments: | 1 files |
The information collected on this form is stored in a computer file by ProRealCode to create and access your ProRealCode profile. This data is kept in a secure database for the duration of the member's membership. They will be kept as long as you use our services and will be automatically deleted after 3 years of inactivity. Your personal data is used to create your private profile on ProRealCode. This data is maintained by SAS ProRealCode, 407 rue Freycinet, 59151 Arleux, France. If you subscribe to our newsletters, your email address is provided to our service provider "MailChimp" located in the United States, with whom we have signed a confidentiality agreement. This company is also compliant with the EU/Swiss Privacy Shield, and the GDPR. For any request for correction or deletion concerning your data, you can directly contact the ProRealCode team by email at privacy@prorealcode.com If you would like to lodge a complaint regarding the use of your personal data, you can contact your data protection supervisory authority.