Volatility Hull Ribbon [BackQuant]Volatility Hull Ribbon
Overview
Volatility Hull Ribbon is a trend-following overlay built from a Hull-style moving average that replaces traditional volume weighting with volatility weighting . Instead of weighting price by traded volume, this indicator weights price by the absolute True Range of each bar, meaning bars with larger range expansion have more influence on the final trend estimate.
The goal is to create a smoother but responsive trend line that pays more attention to bars where the market actually moved with force. It then plots this volatility-weighted Hull structure as either a clean line or a ribbon-style band, with gradient fill, candle coloring, and long/short flip markers.
At a high level, the indicator does three things:
Builds a volatility-weighted moving average using True Range as the weighting source.
Applies Hull-style lag reduction to produce a faster trend-following curve.
Visualizes trend direction using slope, ribbon fill, candles, and flip signals.
Core idea
Most moving averages treat each bar equally or weight only by time. That means a quiet candle and a high-range expansion candle can have similar influence depending on the MA type.
Volatility Hull Ribbon takes a different approach:
Bars with larger True Range are treated as more important.
Bars with smaller True Range have less influence.
Recent bars are also weighted more heavily than older bars.
This creates a trend estimate that responds more strongly when the market expands, while remaining smoother during lower-energy movement.
What “volatility-weighted” means here
The custom weighting function uses:
Price source
Absolute True Range
A decreasing time weight
For each bar inside the lookback:
Weighted price contribution = source * abs(True Range ) * recency weight
Weight contribution = abs(True Range ) * recency weight
Then:
Volatility-weighted average = weighted price sum / weighted True Range sum
So price movement on wide-range bars matters more than price movement on quiet bars.
Why True Range is used
True Range captures more than just high-low movement. It accounts for gaps and previous close displacement. This makes it a broader volatility proxy than simple candle range.
Using True Range as the weight means the filter gives more importance to bars where:
Range expanded,
Price displaced aggressively,
Volatility increased,
Market participation likely intensified.
This is useful because strong trend moves often occur during volatility expansion, not during quiet drift.
Hull-style construction
The indicator then applies a Hull-style transformation to the volatility-weighted average.
The structure is:
VWHMA = VWMA_TR( 2 * VWMA_TR(src, len / 2) - VWMA_TR(src, len), sqrt(len) )
Where VWMA_TR means the custom True-Range-weighted moving average.
This follows the same logic as the classic Hull Moving Average:
Use a faster half-length average.
Use a slower full-length average.
Subtract the lagging component.
Smooth the result with sqrt(length).
The difference is that every smoothing step is volatility-weighted instead of standard weighted-average based.
Why this matters
A classic Hull Moving Average is already designed to reduce lag. This version modifies the internal weighting so the curve becomes more sensitive to volatility-backed price movement .
That means:
Large expansion bars can pull the filter faster.
Weak low-range chop has less effect.
Trend changes during strong movement can be reflected more clearly.
Trend detection
Trend direction is based on the slope of the VWHMA:
Bullish when VWHMA > VWHMA
Bearish when VWHMA < VWHMA
This is a simple but effective regime definition:
Rising volatility-weighted Hull = bullish trend pressure.
Falling volatility-weighted Hull = bearish trend pressure.
The script uses this slope state to color:
The main line,
The ribbon fill,
Optional candles,
Signal markers.
Ribbon mode
When “Plot as Band?” is enabled, the script creates a second line:
onebar_off = WMA(VWHMA , 10)
This is a delayed and smoothed version of the VWHMA. The area between the current VWHMA and this offset line becomes the ribbon.
Interpretation:
Ribbon expansion shows separation between current trend structure and its delayed reference.
Ribbon compression shows trend slowing or flattening.
A clean flip in the ribbon often coincides with trend transition.
The ribbon is not a volatility band. It is a trend displacement ribbon built from the difference between the current VWHMA and its delayed smoothed version.
Gradient fill logic
The fill is directional:
If VWHMA is above the offset line, fill intensity is stronger near the VWHMA and fades toward the offset.
If VWHMA is below the offset line, the gradient reverses.
This creates a cleaner visual than a flat fill because it emphasizes the active side of the ribbon.
In practice:
Strong bright ribbon = trend line leading the delayed reference.
Faded/narrow ribbon = weaker separation.
Ribbon reversal = trend pressure has shifted.
Signal logic
Signals are generated when the VWHMA slope changes direction:
Long signal: crossover(VWHMA, VWHMA )
Short signal: crossunder(VWHMA, VWHMA )
This means:
A long signal prints when the current VWHMA turns upward relative to the previous value.
A short signal prints when the current VWHMA turns downward.
These are slope-flip signals, not price crossover signals.
Important interpretation
A signal does not mean “buy blindly” or “sell blindly.” It means the volatility-weighted trend estimate has changed direction. The quality of the signal depends on:
Market structure,
Higher timeframe trend,
Volatility conditions,
Whether the ribbon is expanding or compressing.
Candle coloring
When enabled, candles are painted according to the VWHMA slope:
Bullish slope = long color.
Bearish slope = short color.
This makes the indicator easier to read as a regime overlay. You can quickly see when the market is consistently aligned with the volatility-weighted trend.
How to use it
1) Trend filter
Use the VWHMA color as a bias filter:
Only favor longs when the VWHMA is rising.
Only favor shorts when the VWHMA is falling.
2) Trend transition tool
Slope flips can identify early trend shifts:
Long marker = VWHMA has turned upward.
Short marker = VWHMA has turned downward.
Because the filter is Hull-style and volatility-weighted, it can react faster than slower trend filters while still suppressing some low-range noise.
3) Ribbon strength reading
The ribbon gives additional context:
Expanding ribbon = stronger separation and cleaner trend pressure.
Contracting ribbon = momentum weakening.
Ribbon flattening = chop or transition risk.
4) Pullback structure
In strong trends, price often respects the VWHMA or ribbon area:
Bull regime: pullbacks into the ribbon can act as support.
Bear regime: rallies into the ribbon can act as resistance.
5) Volatility-backed trend confirmation
Because large True Range bars influence the calculation more, this tool is useful for identifying whether trend changes are being supported by actual range expansion.
If price moves but the VWHMA does not respond strongly, the move may lack volatility-backed confirmation.
Input guide
Price Source
Defines the input series used for the calculation. Close is standard, but hl2, hlc3, or ohlc4 can be used for smoother structural behavior.
Lookback Period
Controls the smoothing length:
Lower values = faster response, more signals, more noise.
Higher values = smoother trend, fewer flips, more lag.
Plot as Band
Enables the ribbon view using the delayed smoothed VWHMA reference.
Line Width
Controls the main line thickness when not relying heavily on band mode.
Show Trend Candles
Paints candles by current trend state.
Show Signals
Toggles the long/short slope-flip markers.
Strengths
Uses volatility-weighted smoothing instead of equal weighting.
Combines volatility sensitivity with Hull-style lag reduction.
Clean ribbon visualization for trend displacement.
Simple slope-based regime interpretation.
Works well as a trend overlay or bias filter.
Limitations
Slope flips can still whipsaw in sideways markets.
Large wick bars can influence the filter strongly because True Range is used as weight.
It does not measure volume, despite using a VWMA-style internal function.
It is a trend tool, not a complete trading system.
Best use case
Volatility Hull Ribbon works best when used as a visual trend structure layer:
Use color for bias.
Use ribbon expansion/compression for strength.
Use slope flips for regime transitions.
Use price interaction with the ribbon for pullback context.
Summary
Volatility Hull Ribbon is a Hull-style trend overlay that replaces traditional weighting with True Range weighting, making the moving average more responsive to volatility-backed price movement. It builds a low-lag volatility-weighted Hull curve, compares it to a delayed smoothed reference to form a ribbon, and uses slope changes to define trend direction and signals. The result is a clean, responsive trend ribbon that highlights when volatility-backed trend pressure is rising, fading, or reversing. Indicator

Indicator

Indicator

Effort & Result [UAlgo]Effort & Result is a volume spread relationship oscillator inspired by the classic idea that market effort and market result do not always move in balance. The script compares how unusual current volume is versus how unusual current price range is, then measures the gap between those two conditions. The result is a compact oscillator that helps reveal whether the market is showing heavy participation with limited progress, or strong price expansion with relatively weak participation.
The core concept is simple. Volume represents effort, while true range represents result. When effort rises much faster than result, the market may be meeting opposing liquidity and progress can become inefficient. When result rises much faster than effort, price may be moving through thinner liquidity with relatively little resistance. This script transforms that relationship into standardized values so both dimensions can be compared on the same scale.
To make the comparison more useful, the script converts both volume and true range into rolling z scores. That means each bar is judged relative to its own recent context rather than by raw magnitude alone. A large volume bar may not mean much in a market that always trades large volume, while the same raw value could be highly unusual in another market. The same logic applies to price range. By standardizing both series, the indicator focuses on anomaly versus normal behavior rather than on absolute size.
The final oscillator is the difference between effort z score and result z score. Positive readings suggest effort is leading result, while negative readings suggest result is leading effort. The script also highlights two special regimes. Absorption appears when effort is strongly positive but result remains weak. Vacuum appears when result is strongly positive but effort remains weak. These conditions are then labeled directly on the oscillator.
In practical use, the indicator can help identify hidden resistance to price movement, low liquidity expansion, or moments where market participation and delivered movement are out of balance. It is best used as a context tool rather than a standalone entry engine.
🔹 Features
🔸 Effort Versus Result Framework
The script separates market behavior into two dimensions. Volume is treated as effort, and true range is treated as result. This creates a clean and intuitive model for comparing participation versus delivered movement.
🔸 Rolling Z Score Standardization
Both effort and result are transformed into rolling z scores over the selected lookback window. This makes the oscillator adaptive to the recent environment and allows direct comparison between volume and range.
🔸 Delta Oscillator
The final plotted value is the difference between effort z score and result z score. This gives the user a direct read on whether volume is leading range or range is leading volume.
🔸 Absorption Detection
When effort is strongly positive but result is weak or negative, the script flags absorption. This can indicate that strong participation is being met by opposing liquidity and price progress is being contained.
🔸 Vacuum Detection
When result is strongly positive but effort is weak or negative, the script flags a vacuum condition. This can indicate that price is moving through thin liquidity with little resistance.
🔸 Context Aware Histogram Coloring
The histogram changes color depending on whether the bar reflects absorption, vacuum, or neutral conditions. This makes regime identification faster and more visual.
🔸 Threshold Guides
The oscillator includes reference lines for equilibrium as well as absorption and vacuum alert thresholds, making it easier to interpret extremes.
🔸 Direct Chart Labels
Special conditions are labeled directly on the oscillator so absorption and vacuum events stand out immediately without requiring separate scanning.
🔹 Calculations
1) Defining the Flow Metrics Container
type FlowMetrics
float totalVol
float spread
float effortZ
float resultZ
This object stores the four main values used by the indicator.
totalVol stores the current bar volume.
spread stores the current bar range measure.
effortZ stores the standardized effort reading.
resultZ stores the standardized result reading.
So before any signal logic is built, the script already has a clean structure for the raw inputs and their normalized forms.
2) Measuring Effort and Result Inputs
float v = nz(volume, 1)
float tr = ta.tr(true)
This block defines the two core raw inputs of the indicator.
v is the current volume, with a fallback of 1 in case the symbol does not provide volume data.
tr is the true range of the bar, which is used as the result measure.
The reason true range is used instead of a simpler high minus low calculation is that true range also accounts for gaps relative to the prior close. This makes it a more complete measure of actual delivered price movement.
So the indicator begins with one participation variable and one movement variable.
3) Rolling Mean and Standard Deviation for Effort
float volMean = ta.sma(vol, len)
float volStd = ta.stdev(vol, len)
this.effortZ := volStd == 0 ? 0 : (vol - volMean) / volStd
This is the effort standardization step.
The script first computes the rolling average volume over the chosen window. Then it computes the rolling volume standard deviation over the same window. Finally, it converts the current volume into a z score:
effortZ = (current volume minus mean volume) divided by volume standard deviation
This means:
a positive effort z score implies current volume is above normal,
a negative effort z score implies current volume is below normal,
and zero means current volume is near its rolling average.
So effort is not judged by raw volume alone. It is judged by how unusual that volume is relative to recent history.
4) Safe Spread Handling for Result Calculation
float safeSpread = r == 0 ? syminfo.mintick : r
This line prevents division and standardization issues when the range is zero.
If the current true range is zero, the script substitutes the instrument’s minimum tick size instead. This ensures that the result side of the calculation always has a valid positive value and avoids unstable behavior in rare flat bars.
So the script remains numerically stable even when a bar has no measurable range.
5) Rolling Mean and Standard Deviation for Result
float spreadMean = ta.sma(safeSpread, len)
float spreadStd = ta.stdev(safeSpread, len)
this.resultZ := spreadStd == 0 ? 0 : (safeSpread - spreadMean) / spreadStd
This is the result standardization step.
Just like effort, the script calculates the rolling average and rolling standard deviation for the bar spread. It then converts the current spread into a z score:
resultZ = (current spread minus mean spread) divided by spread standard deviation
This means:
a positive result z score implies current movement is above normal,
a negative result z score implies current movement is below normal.
So result becomes directly comparable to effort on the same statistical scale.
6) Full Metric Calculation Method
method calcMetrics(FlowMetrics this, float vol, float r, int len) =>
this.totalVol := vol
this.spread := r
float volMean = ta.sma(vol, len)
float volStd = ta.stdev(vol, len)
this.effortZ := volStd == 0 ? 0 : (vol - volMean) / volStd
float safeSpread = r == 0 ? syminfo.mintick : r
float spreadMean = ta.sma(safeSpread, len)
float spreadStd = ta.stdev(safeSpread, len)
this.resultZ := spreadStd == 0 ? 0 : (safeSpread - spreadMean) / spreadStd
This method combines the full effort and result workflow into one place.
It first stores the raw bar volume and raw spread. Then it calculates the effort z score from rolling volume statistics and the result z score from rolling spread statistics.
So each bar receives:
a raw effort reading,
a raw result reading,
a normalized effort score,
and a normalized result score.
This normalized pair is what the rest of the oscillator uses.
7) Building the Main Oscillator Value
FlowMetrics flow = FlowMetrics.new()
flow.calcMetrics(v, tr, length)
float deltaZ = flow.effortZ - flow.resultZ
This is the main oscillator formula.
After the metrics object is updated, the script computes:
deltaZ = effortZ minus resultZ
This value answers the central question of the indicator:
is effort stronger than result, or is result stronger than effort?
If deltaZ is positive, effort is outrunning result.
If deltaZ is negative, result is outrunning effort.
If deltaZ is near zero, effort and result are more balanced.
So the oscillator is really a normalized imbalance measure between participation and delivered movement.
8) Absorption Condition
bool isAbsorption = flow.effortZ > 1.5 and flow.resultZ < 0.0
This is the first special regime filter.
Absorption is defined as:
effort significantly above normal,
while result remains weak.
The threshold 1.5 means effort must be at least 1.5 standard deviations above its rolling average. At the same time, result must still be below zero, meaning current movement is not even above its recent average.
This combination suggests that strong participation is entering the market but price is not expanding proportionally. That can imply opposing liquidity, passive absorption, or resistance to movement.
So absorption is the classic high effort, low result condition.
9) Vacuum Condition
bool isVacuum = flow.resultZ > 1.5 and flow.effortZ < 0.0
This is the second special regime filter.
Vacuum is defined as:
result significantly above normal,
while effort remains weak.
Here, price is delivering unusually large movement, but volume is not confirming that move with above average participation. This can imply thin liquidity, poor resistance, or fast movement through lightly traded space.
So vacuum is the classic low effort, high result condition.
10) Histogram Color Logic
color histColor = isAbsorption ? color.new(color.fuchsia, 30) :
isVacuum ? (close >= open ? color.new(color.aqua, 30) : color.new(color.orange, 30)) :
color.new(color.gray, 70)
This block determines how the histogram is colored.
If the current bar meets the absorption condition, the histogram is colored fuchsia.
If it meets the vacuum condition, the histogram is colored aqua when the candle is bullish and orange when the candle is bearish.
If neither special regime is active, the histogram is colored neutral gray.
So the visual layer helps the user distinguish ordinary imbalance readings from the two emphasized special states.
11) Plotting the Oscillator
plot(deltaZ, "Effort/Result Delta", style=plot.style_columns, color=histColor)
This line plots the main effort versus result delta as a column histogram.
The use of columns is helpful because it emphasizes relative magnitude and direction around the zero line. Positive columns show effort leading result. Negative columns show result leading effort.
So the visual output is both directional and strength sensitive.
12) Threshold and Equilibrium Lines
hline(1.5, "Vacuum Alert", color=color.new(color.aqua, 50), linestyle=hline.style_dashed)
hline(-1.5, "Absorption Alert", color=color.new(color.fuchsia, 50), linestyle=hline.style_dashed)
hline(0, "Equilibrium", color=color.new(color.gray, 50))
These reference lines give the oscillator context.
The zero line marks equilibrium, where effort and result are more balanced.
The positive 1.5 line acts as a visual vacuum threshold.
The negative 1.5 line acts as a visual absorption threshold.
These levels do not define the regime conditions directly by themselves, because the actual logic checks the separate effort and result z scores. But they still give the user a useful visual frame for interpreting the size of the delta reading.
13) Labeling Absorption Events
if isAbsorption
label.new(bar_index, deltaZ, text="Absorbed", color=color.new(color.fuchsia, 100), textcolor=color.fuchsia, style=label.style_none, size=size.small, yloc=yloc.price)
When an absorption condition is detected, the script prints an Absorbed label directly at the oscillator value for that bar.
This makes the event easier to spot when scanning history and also helps separate truly qualified absorption conditions from merely positive delta readings.
So the label is not attached to every strong positive bar, only to the bars that meet the specific high effort and weak result rule.
14) Labeling Vacuum Events
if isVacuum
label.new(bar_index, deltaZ, text="Vacuum", color=color.new(color.aqua, 100), textcolor=color.aqua, style=label.style_none, size=size.small, yloc=yloc.price)
This block does the same for vacuum events.
When a bar shows unusually strong range with weak volume participation, the script prints a Vacuum label at the oscillator level.
So the chart distinguishes not only statistical imbalance in general, but specifically the regime where result is outrunning effort. Indicator

Volume Scatter Plot [UAlgo]Volume Scatter Plot is a visual analytics tool that transforms recent candles into a two dimensional distribution of price and volume. Instead of plotting volume in the traditional way at the bottom of the chart, the script projects each recent bar as a point inside a custom scatter plot area drawn directly on the chart. This allows the user to study the relationship between traded volume and price location in a much more spatial and intuitive format.
Each point represents one candle from the selected lookback period. The vertical location of the point is taken from price, while the horizontal location is determined by that candle’s volume relative to all other bars inside the lookback window. As a result, the plot makes it possible to quickly see whether higher volume tends to cluster near higher prices, lower prices, or specific sections of the recent range.
The script also colors every point according to candle direction, which adds a simple but useful order flow style layer. Bullish candles are shown with one color and bearish candles with another, so the scatter plot can reveal not only the price and volume relationship, but also whether those clusters were formed more often by bullish or bearish candles.
To add structure, the indicator draws a framed plotting area with guide lines, then overlays a linear regression line across the cloud of points. This regression line gives the user a fast view of the overall relationship between volume and price. If enabled, deviation bands are also plotted above and below the regression line, which can help identify points that stand out from the average relationship.
In practical use, this script is useful for studying whether strong participation is appearing at premium or discount prices, whether extreme volume is clustering around certain regions, and whether recent bars are tightly aligned with the broader price volume relationship or scattered away from it.
🔹 Features
🔸 Price and Volume Scatter Mapping
The script converts each candle in the lookback period into a scatter point. Price controls vertical placement and volume controls horizontal placement. This creates a true two dimensional view of recent market behavior rather than a separate price chart and volume pane.
🔸 Bullish and Bearish Point Coloring
Each point is colored according to candle direction. Bullish candles use the bullish point color, while bearish candles use the bearish point color. This makes it easier to visually separate positive and negative participation inside the same distribution.
🔸 Custom Plot Area Overlay
The scatter plot is drawn in a dedicated chart area offset to the right of current price. The width of this area and the horizontal offset are both configurable, giving the user control over how large and how far away the visualization appears.
🔸 Flexible Scatter Symbols
The plotted points can use different characters such as circles, stars, squares, crosses, or other symbols. This helps users adapt the style of the plot to their preferred chart appearance.
🔸 Automatic Bounds Calculation
The indicator automatically calculates the highest price, lowest price, highest volume, and lowest volume across the current point set. These values are then used to scale the scatter plot so the entire distribution fits inside the frame cleanly.
🔸 Framed Axes and Mid Guides
The script draws the outer borders of the plot area as well as horizontal and vertical midpoint guides. This creates a clearer analytical space and makes it easier to judge where points sit relative to the full distribution.
🔸 Tooltip Enabled Data Points
Every scatter point includes a tooltip that shows the underlying price and volume values. This allows the user to inspect specific points directly on the chart without losing the visual overview.
🔸 Linear Regression Overlay
A regression line is calculated from the relationship between volume and price across the current dataset. This gives the user an immediate view of whether the cloud of points implies a positive, negative, or flat volume price relationship.
🔸 Optional Deviation Bands
When enabled, the script also draws lines one standard deviation above and below the regression line. These bands help visualize how tightly or loosely the scatter cloud is distributed around the fitted relationship.
🔸 Live Refresh on the Latest Bar
The plot refreshes on the latest bar and in realtime conditions, ensuring that the scatter map always reflects the most recent market state.
🔹 Calculations
1) Defining the Scatter Point and Plot Structures
type ScatterPoint
float price
float volume
color pt_color
int bar_time
type ScatterPlot
array points
int lookback
int width
int x_offset
float min_price
float max_price
float min_vol
float max_vol
array drawn_labels
array drawn_lines
This is the data model behind the whole indicator.
Each ScatterPoint stores one candle’s contribution to the plot. It contains:
the target price,
the candle volume,
the point color,
and the candle time.
The ScatterPlot structure stores the full plotting state:
the rolling point array,
the configuration values,
the current plot bounds,
and the labels and lines used for drawing.
So before any calculations begin, the script already has a full container for both the raw data and the visual objects used to display it.
2) Adding New Points Into the Rolling Dataset
method add_point(ScatterPlot this, float p, float v, color c, int t) =>
this.points.unshift(ScatterPoint.new(p, v, c, t))
if this.points.size() > this.lookback
this.points.pop()
This method manages the rolling dataset.
Every new bar creates a new ScatterPoint and inserts it at the front of the array with unshift() . That means the newest point is always stored first. If the array grows larger than the selected lookback size, the oldest point is removed from the end with pop() .
This gives the script a continuously updating point cloud that always contains only the most recent bars.
In practical terms, the scatter plot is always a moving window of recent market behavior rather than an ever growing history.
3) Choosing the Price and Color for Each Point
color bar_color = close >= open ? c_bull : c_bear
float target_price = hl2
data_plot.add_point(target_price, volume, bar_color, time)
This block explains how each bar is converted into a scatter point.
First, the script determines the point color from candle direction. If the close is above or equal to the open, the point uses the bullish color. Otherwise it uses the bearish color.
Second, the script chooses hl2 as the target price. That means the vertical position of each point is the midpoint of the bar’s high and low, not the close or the open. This is a useful choice because it represents the candle’s central traded location rather than only its final close.
Finally, the script sends that price, the candle volume, the directional color, and the time into the rolling plot dataset.
So each plotted point reflects:
where the candle sat in price,
how much volume it traded,
and whether it closed bullish or bearish.
4) Calculating the Plot Bounds
method calculate_bounds(ScatterPlot this) =>
float max_p = na
float min_p = na
float max_v = na
float min_v = na
if this.points.size() > 0
max_p := this.points.get(0).price
min_p := this.points.get(0).price
max_v := this.points.get(0).volume
min_v := this.points.get(0).volume
for p in this.points
if p.price > max_p
max_p := p.price
if p.price < min_p
min_p := p.price
if p.volume > max_v
max_v := p.volume
if p.volume < min_v
min_v := p.volume
This method scans all stored points and finds the extreme values needed for scaling.
It identifies:
the maximum price,
the minimum price,
the maximum volume,
and the minimum volume.
These values form the raw boundaries of the point cloud. Without them, the script would not know how to map prices and volumes into the framed plotting area.
So this is the normalization step that prepares the scatter plot for accurate positioning.
5) Adding a Small Price Margin Around the Point Cloud
float p_range = math.max(max_p - min_p, 0.0001)
this.max_price := max_p + (p_range * 0.05)
this.min_price := min_p - (p_range * 0.05)
this.max_vol := max_v
this.min_vol := min_v
After the raw bounds are found, the script adds a small vertical margin to the price range.
It computes the price span and then extends the upper and lower bounds by five percent of that range. This prevents the highest and lowest points from sitting directly on the frame border.
Volume bounds are stored without an added margin because they are used mainly for horizontal scaling.
In practical terms, this makes the plot easier to read and visually less cramped.
6) Mapping Volume Into Horizontal Position
method get_x_pos(ScatterPlot this, float vol, int current_bar) =>
float max_range = math.max((this.max_vol - this.min_vol), 0.0001)
float ratio = (vol - this.min_vol) / max_range
float active_width = this.width * 0.95
int pos = current_bar + this.x_offset + int(ratio * active_width)
pos
This function is what turns volume into horizontal placement.
First, it measures the full volume range across the current dataset. Then it converts the current point’s volume into a ratio between zero and one:
ratio = (vol - this.min_vol) / max_range
That ratio tells the script where the volume sits between the smallest and largest volume values in the lookback.
The ratio is then multiplied by the active plot width and shifted to the right of current price using x_offset .
So low volume points appear closer to the left side of the scatter area, and high volume points appear closer to the right side.
This is the key transformation that makes the chart behave like a true scatter plot rather than a simple time series.
7) Drawing the Plot Frame and Guides
method draw_axes(ScatterPlot this, int current_bar) =>
int x_start = current_bar + this.x_offset
int x_end = x_start + this.width
this.push_line(line.new(x_start, this.min_price, x_start, this.max_price, color = c_axis, style = line.style_dotted, width = 1))
this.push_line(line.new(x_end, this.min_price, x_end, this.max_price, color = c_axis, style = line.style_dotted, width = 1))
this.push_line(line.new(x_start, this.min_price, x_end, this.min_price, color = c_axis, style = line.style_dotted, width = 1))
this.push_line(line.new(x_start, this.max_price, x_end, this.max_price, color = c_axis, style = line.style_dotted, width = 1))
int mid_x = x_start + math.round(this.width / 2)
float mid_y = (this.max_price + this.min_price) / 2
this.push_line(line.new(mid_x, this.min_price, mid_x, this.max_price, color = color.new(c_axis, 70), style = line.style_dashed, width = 1))
this.push_line(line.new(x_start, mid_y, x_end, mid_y, color = color.new(c_axis, 70), style = line.style_dashed, width = 1))
This method draws the visual frame of the scatter plot.
It defines the left and right horizontal edges of the plotting area, then draws four dotted boundary lines:
left border,
right border,
bottom border,
and top border.
After that, it draws a vertical midpoint guide and a horizontal midpoint guide.
These guides help the user interpret where the point cloud sits relative to the full price and volume range. For example, it becomes much easier to see whether most points cluster in the upper half of price or the right half of volume.
8) Drawing Axis Labels
this.push_label(label.new(x_start, this.max_price, "Price Max", textcolor = c_axis, color = transparent, style = label.style_label_down, size = size.small))
this.push_label(label.new(x_start, this.min_price, "Price Min", textcolor = c_axis, color = transparent, style = label.style_label_up, size = size.small))
this.push_label(label.new(x_end, this.min_price, "Vol Max", textcolor = c_axis, color = transparent, style = label.style_label_left, size = size.small))
These labels give the plot basic orientation.
The script marks:
the highest price boundary,
the lowest price boundary,
and the far right side of the plot as the maximum volume direction.
This is a simple but useful usability feature because it immediately tells the user how to read the scatter space:
vertical movement corresponds to price,
and movement toward the right corresponds to increasing volume.
9) Drawing the Scatter Points Themselves
method draw_points(ScatterPlot this, int current_bar, string char_symbol) =>
color transparent = color.new(color.white, 100)
for p in this.points
int x_pos = this.get_x_pos(p.volume, current_bar)
string tooltip_txt = "P: " + str.tostring(p.price, format.mintick) + " V: " + str.tostring(p.volume, format.volume)
this.push_label(label.new(x_pos, p.price, text = char_symbol, textcolor = p.pt_color, color = transparent, style = label.style_none, size = size.small, tooltip = tooltip_txt))
This method plots every stored point inside the scatter area.
For each point, the script first converts volume into an x position using get_x_pos() . The y position is simply the stored point price. Then it draws a label using the selected point symbol and the stored bullish or bearish color.
Each point also gets a tooltip showing:
the exact price,
and the exact volume.
So visually, the user sees a clean scatter cloud, but each point still preserves its detailed numeric information.
10) Computing the Regression Line
method draw_regression(ScatterPlot this, int current_bar, bool show_dev) =>
int n = this.points.size()
if n > 1
float sum_x = 0.0
float sum_y = 0.0
float sum_xy = 0.0
float sum_xx = 0.0
for p in this.points
sum_x += p.volume
sum_y += p.price
sum_xy += p.volume * p.price
sum_xx += p.volume * p.volume
float denom = (n * sum_xx - sum_x * sum_x)
float slope = denom == 0 ? 0 : (n * sum_xy - sum_x * sum_y) / denom
float intercept = (sum_y - slope * sum_x) / n
This is the statistical core of the indicator.
The script performs a standard linear regression where:
x is volume,
and y is price.
It first accumulates the sums needed for the regression formula:
sum of x,
sum of y,
sum of xy,
and sum of xx.
From those totals, it calculates:
the slope,
and the intercept.
So the regression line answers a simple analytical question:
as volume changes across the recent dataset, what is the average linear relationship with price?
A positive slope suggests higher volume tends to align with higher prices.
A negative slope suggests higher volume tends to align with lower prices.
A flat slope suggests little directional relationship between the two.
11) Measuring Dispersion Around the Regression
float variance = 0.0
for p in this.points
float expected_y = slope * p.volume + intercept
variance += math.pow(p.price - expected_y, 2)
float std_dev = math.sqrt(variance / n)
After the regression line is found, the script measures how far the actual points deviate from that fitted relationship.
For each point, it calculates the expected price on the regression line for that point’s volume. It then measures the squared difference between the actual price and the expected price. The average of those squared differences becomes the variance, and the square root of that value becomes the standard deviation.
This tells the user how tightly or loosely the point cloud clusters around the regression line. A small deviation means the relationship is relatively consistent. A large deviation means the cloud is more dispersed.
12) Converting the Regression Into Drawable Chart Coordinates
float y_min_vol = slope * this.min_vol + intercept
float y_max_vol = slope * this.max_vol + intercept
int x_start_clamped = this.get_x_pos(this.min_vol, current_bar)
int x_end_clamped = this.get_x_pos(this.max_vol, current_bar)
this.push_line(line.new(x_start_clamped, y_min_vol, x_end_clamped, y_max_vol, color = c_reg, width = 2, style = line.style_solid))
This block translates the regression model into something the chart can display.
The script evaluates the regression line at the minimum and maximum volume values of the dataset. Those two calculated prices define the start and end of the regression segment in price space.
Then it converts the minimum and maximum volumes into actual chart x positions using get_x_pos() .
Finally, it draws a straight line between those two points.
So even though the regression is calculated in price and volume coordinates, it becomes a visible line inside the custom scatter plot area.
13) Drawing the Deviation Bands
if show_dev
color dev_color = color.new(c_reg, 60)
this.push_line(line.new(x_start_clamped, y_min_vol + std_dev, x_end_clamped, y_max_vol + std_dev, color = dev_color, width = 1, style = line.style_dashed))
this.push_line(line.new(x_start_clamped, y_min_vol - std_dev, x_end_clamped, y_max_vol - std_dev, color = dev_color, width = 1, style = line.style_dashed))
If deviation display is enabled, the script draws two additional dashed lines:
one standard deviation above the regression,
and one standard deviation below it.
These bands help the user judge whether points are staying close to the average relationship or whether some bars are standing far away from the expected line.
In practical terms, points well outside these bands can be interpreted as unusually strong or unusually weak price locations relative to their traded volume.
14) Clearing and Redrawing the Plot
method clear_drawings(ScatterPlot this) =>
if this.drawn_labels.size() > 0
for l in this.drawn_labels
l.delete()
this.drawn_labels.clear()
if this.drawn_lines.size() > 0
for b in this.drawn_lines
b.delete()
this.drawn_lines.clear()
Before each refresh, the script deletes all previously drawn labels and lines. This ensures that the scatter plot does not accumulate stale points or outdated regression segments.
Because the visualization is rebuilt from the current rolling dataset, clearing old drawings first is necessary for a clean and accurate live display.
15) Final Execution Flow
if barstate.islast or barstate.isrealtime
data_plot.clear_drawings()
data_plot.calculate_bounds()
data_plot.draw_axes(bar_index)
data_plot.draw_points(bar_index, i_pt_char)
data_plot.draw_regression(bar_index, i_show_dev)
This block summarizes the entire display engine.
On the latest bar or in realtime:
the script clears old drawings,
recalculates the current bounds,
draws the plot frame,
plots all scatter points,
and overlays the regression line with optional deviation bands.
So the chart always shows a fresh snapshot of the current price volume relationship based on the selected lookback window. Indicator

ICT MMXL Model [UAlgo]ICT MMXL Model is a liquidity sweep and exhaustion zone indicator designed to detect reversal candidates after price raids a prior swing and fails to hold beyond it. The script tracks confirmed swing highs and swing lows, waits for price to take one of those liquidity pools, then checks whether the sweep candle closes back inside the old structure. When that rejection is strong enough and the selected filters agree, the script builds an exhaustion zone that can later be monitored for retests or invalidation.
The core logic follows a very practical structure based workflow. A bearish setup forms when price sweeps above a prior swing high, but the same sweep candle closes back below that high. A bullish setup forms when price sweeps below a prior swing low, but the same sweep candle closes back above that low. This creates a failed expansion or exhaustion event, which is then projected forward on the chart as a zone.
What makes the script more selective than a simple sweep detector is its filter stack. It can require above average volume, a meaningful wick relative to the candle body, and visible momentum fade before the sweep occurs. These filters help focus the model on conditions where displacement appears to be tiring before the market raids liquidity and rejects the move.
Once a zone is created, it remains active until price closes beyond the invalidation boundary. While active, the script extends the zone, counts retests, optionally draws a mid line, and keeps the structure visible for future interaction. This makes the indicator useful not only for spotting the original sweep event, but also for tracking how price behaves when it revisits the exhaustion area later.
In practice, ICT MMXL Model can be used for liquidity based reversal analysis, rejection zone mapping, and structured retest monitoring. It is especially useful for traders who want a rules based way to identify failed sweep behavior and keep those zones on the chart until the market either respects or invalidates them.
🔹 Features
🔸 Prior Swing Liquidity Tracking
The script continuously records confirmed swing highs and swing lows using user defined pivot settings. Only the most recent group of swings is kept in memory, which keeps the model focused on nearby liquidity rather than distant historical structure.
🔸 Bearish and Bullish Sweep Detection
A bearish MMXL candidate appears when price trades above a prior swing high but closes back below it. A bullish MMXL candidate appears when price trades below a prior swing low but closes back above it. This captures the idea of a liquidity raid followed by rejection.
🔸 Volume Confirmation Filter
An optional volume filter requires the sweep candle to trade at or above a multiple of average volume. This helps avoid weaker sweeps that occur without meaningful participation.
🔸 Wick Rejection Filter
An optional wick filter requires the rejecting wick to be large relative to the candle body. For bearish setups, the upper wick must be large enough. For bullish setups, the lower wick must be large enough. This helps focus the model on candles that visibly reject the sweep area.
🔸 Momentum Fade Filter
An optional momentum fade filter checks whether body size and ATR have both decreased over a chosen lookback. This helps identify sweeps that occur after displacement has begun losing energy.
🔸 Automatic Exhaustion Zone Construction
When all setup conditions are met, the script creates a bullish or bearish exhaustion zone using the swept prior swing and the extreme of the sweep candle. This gives the user a clearly defined reaction area rather than a single line only.
🔸 Active Zone Extension
Once a zone is formed, it extends forward in time while it remains valid. This allows the trader to monitor later interaction without manually redrawing structure.
🔸 Retest Counting
The script tracks whether price reenters an active zone. Each first fresh entry increments the retest count, which is shown inside the zone label. This gives the user quick feedback on how many times the area has been revisited.
🔸 Mid Line Support
An optional dashed mid line can be drawn through the center of each zone. This can help visualize equilibrium inside the exhaustion area and provide an extra internal reference.
🔸 Sweep Marker Visualization
An optional visual marker is placed on the original sweep candle so the user can immediately see where the liquidity raid happened.
🔸 Violation Logic
A bullish zone is invalidated if price closes below the zone bottom. A bearish zone is invalidated if price closes above the zone top. Violated zones remain visible with muted styling so the user can still see where the structure failed.
🔸 Zone Limit Control
The script can limit the number of simultaneously active zones. If the maximum is reached, the oldest active zone is removed first. This helps control visual clutter.
🔸 Alert Support
Alerts are included for:
new bearish zone formation,
new bullish zone formation,
bullish zone retest,
bearish zone retest,
and zone violation.
This makes the script suitable for both visual chart work and alert driven monitoring.
🔹 Calculations
1) Defining Swing and Zone Objects
type SwingPoint
int barIdx
float price
bool isHigh
type ExhaustionZone
float zoneTop
float zoneBot
bool isBullish
int formBar
int formTime
int retests
bool isActive
bool inZone
bool hasMomentumFade
box zoneBox
line midLine
label zoneLabel
label sweepLabel
This is the structural foundation of the script.
A SwingPoint stores a confirmed swing location. It contains the bar index, the swing price, and whether the swing is a high or a low.
An ExhaustionZone stores the full lifecycle of a bearish or bullish MMXL setup. It keeps:
the zone top,
the zone bottom,
its direction,
when it formed,
how many retests it has seen,
whether it is still active,
whether price is currently inside it,
whether momentum fade was present,
and all visual objects used to draw it.
So before any detection begins, the script already has a clean data structure for both liquidity references and live exhaustion zones.
2) Confirming Swing Highs and Swing Lows
phPrice = ta.pivothigh(high, pivLeft, pivRight)
plPrice = ta.pivotlow (low, pivLeft, pivRight)
pivBarIdx = bar_index - pivRight
pivBarTime = time
if not na(phPrice)
sp = SwingPoint.new(barIdx=pivBarIdx, price=phPrice, isHigh=true)
swingHighs.push(sp)
if swingHighs.size() > maxSwings
swingHighs.shift()
if not na(plPrice)
sp = SwingPoint.new(barIdx=pivBarIdx, price=plPrice, isHigh=false)
swingLows.push(sp)
if swingLows.size() > maxSwings
swingLows.shift()
This is the first live detection step.
The script uses ta.pivothigh and ta.pivotlow to confirm structural highs and lows. Because a pivot is only confirmed after the chosen right side bars have passed, the actual pivot bar is not the current bar. That is why the script calculates:
pivBarIdx = bar_index - pivRight
pivBarTime = time
These values point back to the actual swing location.
When a confirmed pivot high appears, it is pushed into the swingHighs array. When a confirmed pivot low appears, it is pushed into the swingLows array. Each array is capped at the selected maximum size, so only the most recent prior swings are kept.
This means the script always maintains a fresh liquidity map of nearby highs and lows that may later be swept.
3) Referencing the Actual Sweep Candle
sweepBar = bar_index - pivRight
sweepHigh = high
sweepLow = low
sweepClose = close
sweepOpen = open
sweepVol = volume
sweepTime = time
This block is very important because the MMXL setup is evaluated on the pivot confirmed candle, not on the current bar.
Since pivot confirmation happens after pivRight bars, the actual candidate sweep candle lives pivRight bars in the past. The script therefore reads all needed values from that offset:
the high,
the low,
the open,
the close,
the volume,
and the time.
This allows the model to evaluate the true sweep candle rather than mixing its logic with the current bar.
4) Volume Filter Calculation
avgVol = ta.sma(volume, volLookback)
volOk = not useVolFilter or (sweepVol >= avgVol * volMult)
This is the first optional filter.
The script calculates average volume over the chosen lookback period and then reads that average at the sweep candle offset. It then compares the sweep candle volume against a user selected multiple of that average.
If the volume filter is enabled, the sweep candle must satisfy:
sweepVol >= avgVol * volMult
If the filter is disabled, volume automatically passes.
This helps the model avoid sweep candles that occur on weak participation and focus more on events where the raid happened with notable activity.
5) Momentum Fade Filter Calculation
isMomentumFading(int offset, int lookback) =>
bodyNow = math.abs(close - open )
bodyPrev = math.abs(close - open )
atrNow = ta.atr(14)
atrPrev = ta.atr(14)
bodyFade = bodyNow < bodyPrev
atrFade = atrNow < atrPrev
bodyFade and atrFade
This function checks whether displacement was weakening before the sweep occurred.
It compares two things:
the body size of the sweep candle versus the body size from earlier in the lookback window,
and the ATR at the sweep candle versus the ATR from earlier in the same window.
For momentum to be considered fading, both conditions must be true:
current body size must be smaller than the earlier body size,
and current ATR must be smaller than the earlier ATR.
That means the filter is looking for a sweep that occurs after both candle expansion and volatility have started to cool off. This can be useful for detecting conditions where price makes one final reach into liquidity as momentum begins to fade.
6) Bearish MMXL Detection Logic
if swingHighs.size() > 0 and barstate.isconfirmed
priorHigh = swingHighs.last().price
bearSweepCondition = sweepHigh > priorHigh and sweepClose < priorHigh
sweepBody = math.abs(sweepClose - sweepOpen)
upperWick = sweepHigh - math.max(sweepClose, sweepOpen)
bearWickOk = not useWickFilter or (sweepBody == 0.0 or upperWick >= sweepBody * wickRatio)
bearMomFade = momFade
momOkBear = not useMomFilter or bearMomFade
This is the core bearish setup logic.
First, the script checks that at least one prior swing high exists. It then defines the most recent stored swing high as the liquidity reference.
A bearish sweep exists only if both conditions are true:
the sweep candle trades above the prior swing high,
and the same sweep candle closes back below that prior high.
That is the classic failed buy side liquidity raid behavior.
Then the script calculates the candle body and upper wick. If the wick filter is enabled, the upper wick must be large enough relative to the body. If the momentum fade filter is enabled, the earlier momentum fade calculation must also pass.
So a bearish MMXL zone requires:
a sweep above prior highs,
a close back below the liquidity level,
and any enabled filters to agree.
7) Bullish MMXL Detection Logic
if swingLows.size() > 0 and barstate.isconfirmed
priorLow = swingLows.last().price
bullSweepCondition = sweepLow < priorLow and sweepClose > priorLow
sweepBody = math.abs(sweepClose - sweepOpen)
lowerWick = math.min(sweepClose, sweepOpen) - sweepLow
bullWickOk = not useWickFilter or (sweepBody == 0.0 or lowerWick >= sweepBody * wickRatio)
bullMomFade = momFade
momOkBull = not useMomFilter or bullMomFade
This is the mirror image of the bearish model.
The script checks that a prior swing low exists, then defines it as the current sell side liquidity reference.
A bullish sweep exists only if:
the sweep candle trades below the prior swing low,
and the same sweep candle closes back above that prior low.
That represents a failed sell side liquidity raid.
If the wick filter is enabled, the lower wick must be large enough relative to the body. If the momentum fade filter is enabled, the sweep must also occur while body size and ATR are both fading.
So a bullish MMXL zone requires:
a sweep below prior lows,
a close back above the liquidity level,
and all enabled filters to pass.
8) Preventing Duplicate Zone Creation
alreadyExists = false
for z in zones
if z.formTime == sweepTime
alreadyExists := true
break
This small block avoids duplicate zones from the same sweep candle.
Before creating a new zone, the script checks whether a zone with the same formation time already exists. If it does, the new one is ignored.
This matters because bullish and bearish checks both run inside the script, and repeated recalculation on confirmed bars could otherwise produce duplicate objects from the same event.
9) Controlling the Maximum Number of Active Zones
activeCount = 0
for z in zones
if z.isActive
activeCount += 1
if activeCount >= maxZones
for idx = 0 to zones.size() - 1
z = zones.get(idx)
if z.isActive
z.deleteDrawings()
z.isActive := false
break
This is the script’s chart management block.
Before a new zone is added, the script counts how many zones are still active. If that count has already reached the maximum allowed level, it searches from oldest to newest and removes the first active zone it finds.
The removed zone has its drawings deleted and its active state turned off.
This keeps the chart readable and prevents unlimited zone accumulation.
10) Building the Bearish and Bullish Zones
newZone = ExhaustionZone.new(
zoneTop = sweepHigh,
zoneBot = priorHigh,
isBullish = false,
formBar = sweepBar,
formTime = sweepTime,
retests = 0,
isActive = true,
inZone = false,
hasMomentumFade = bearMomFade,
zoneBox = na,
midLine = na,
zoneLabel = na,
sweepLabel = na)
newZone = ExhaustionZone.new(
zoneTop = priorLow,
zoneBot = sweepLow,
isBullish = true,
formBar = sweepBar,
formTime = sweepTime,
retests = 0,
isActive = true,
inZone = false,
hasMomentumFade = bullMomFade,
zoneBox = na,
midLine = na,
zoneLabel = na,
sweepLabel = na)
These two blocks define the actual exhaustion zones.
For bearish MMXL:
the zone top is the sweep candle high,
and the zone bottom is the prior swing high that got raided.
For bullish MMXL:
the zone top is the prior swing low that got raided,
and the zone bottom is the sweep candle low.
This is a very practical choice because the zone spans the rejection area between the liquidity level and the extreme of the sweep candle. In other words, the model does not just mark the swept price. It marks the full exhaustion region created by the failed expansion.
11) Drawing the Zone, Mid Line, and Sweep Marker
this.zoneBox := box.new(
left = this.formTime,
top = this.zoneTop,
right = time,
bottom = this.zoneBot,
border_color = bordCol,
border_width = 1,
bgcolor = fillCol,
xloc = xloc.bar_time)
if showMidLine
midLineCol = color.new(this.isBullish ? #00BCD4 : #FF5252, 55)
this.midLine := line.new(
x1 = this.formTime,
y1 = midPrice,
x2 = time,
y2 = midPrice,
color = midLineCol,
style = line.style_dashed,
width = 1,
xloc = xloc.bar_time)
This method handles the primary visual construction of each zone.
The script draws a filled box from the formation time to the current time, using the top and bottom prices of the exhaustion zone. If the mid line option is enabled, it also draws a dashed center line at the average of the zone top and zone bottom.
So the user gets:
the full zone body,
an optional equilibrium reference,
and live forward projection of the zone.
12) Zone Label and Momentum Fade Tag
fadeTag = this.hasMomentumFade ? " ⚡ Fading" : ""
labelText = (this.isBullish ? "MMXL ▲" : "MMXL ▼") +
" Retests: " + str.tostring(this.retests) + fadeTag
The label text is not static. It contains live information about the zone state.
Every label shows:
whether the zone is bullish or bearish,
how many retests have occurred,
and whether the setup formed with momentum fade.
This is useful because it lets the trader read structural context directly from the chart without opening settings or rechecking filters manually.
13) Extending the Zone and Updating Retests
method updateRight(ExhaustionZone this) =>
if not na(this.zoneBox)
box.set_right(this.zoneBox, time)
if showMidLine and not na(this.midLine)
line.set_x2(this.midLine, time)
if not na(this.zoneLabel)
label.set_x(this.zoneLabel, time)
fadeTagU = this.hasMomentumFade ? " ⚡ Fading" : ""
label.set_text(this.zoneLabel,
(this.isBullish ? "MMXL ▲" : "MMXL ▼") +
" Retests: " + str.tostring(this.retests) + fadeTagU)
if barstate.isconfirmed
for z in zones
if z.isActive
z.updateRight()
nowInside = z.isInsideZone()
if nowInside and not z.inZone
z.retests += 1
z.inZone := nowInside
These blocks manage the live state of every active zone.
On each confirmed bar, the script extends the right side of the box, the mid line, and the label to the latest time.
Then it checks whether price is currently inside the zone using isInsideZone() . If price has just entered the zone and it was not inside on the previous bar, the retest counter is incremented. The inZone flag is then updated.
This is important because retests are counted on fresh reentry, not on every bar that remains inside the zone. That prevents overcounting during long stays inside the same area.
14) How the Script Defines a Zone Retest
method isInsideZone(ExhaustionZone this) =>
high >= this.zoneBot and low <= this.zoneTop
This method defines zone interaction.
Price is considered inside a zone when the bar’s high is above or equal to the zone bottom and the bar’s low is below or equal to the zone top. In other words, any overlap between the current candle range and the zone counts as interaction.
This broad overlap definition is practical because zones are areas, not exact prices. A full candle close inside the zone is not required for retest counting.
15) Zone Violation Logic
method checkViolation(ExhaustionZone this) =>
if this.isBullish
close < this.zoneBot
else
close > this.zoneTop
This method defines when a zone is no longer valid.
For bullish zones:
a close below the zone bottom invalidates the structure.
For bearish zones:
a close above the zone top invalidates the structure.
This is a strict close based rule, which helps avoid invalidating the zone on a temporary wick only.
16) Visual Handling of Violated Zones
method violate(ExhaustionZone this) =>
this.isActive := false
if not na(this.zoneBox)
box.set_bgcolor(this.zoneBox, violatedCol)
box.set_border_color(this.zoneBox, color.new(#78909C, 50))
if showMidLine and not na(this.midLine)
line.set_color(this.midLine, color.new(#78909C, 70))
if not na(this.zoneLabel)
label.set_textcolor(this.zoneLabel, color.new(#78909C, 40))
label.set_text(this.zoneLabel,
(this.isBullish ? "MMXL ▲" : "MMXL ▼") +
" VIOLATED")
When a violation happens, the zone is not fully deleted. Instead, the script changes its state and appearance.
The zone becomes inactive, its colors are muted, and the label text changes to indicate that the structure has been violated.
This preserves chart history while clearly separating valid zones from failed ones.
17) Alert Logic for Formations, Retests, and Violations
alertcondition(newBearZone,
title = "MMXL — Bearish Exhaustion Zone Formed",
message = "MMXL: BSL sweep detected on {{ticker}} {{interval}}. Bearish exhaustion zone created.")
alertcondition(newBullZone,
title = "MMXL — Bullish Exhaustion Zone Formed",
message = "MMXL: SSL sweep detected on {{ticker}} {{interval}}. Bullish exhaustion zone created.")
alertcondition(bullRetest,
title = "MMXL — Bullish Zone Retest",
message = "MMXL: Price retesting bullish exhaustion zone on {{ticker}} {{interval}}.")
alertcondition(bearRetest,
title = "MMXL — Bearish Zone Retest",
message = "MMXL: Price retesting bearish exhaustion zone on {{ticker}} {{interval}}.")
alertcondition(bullViolated or bearViolated,
title = "MMXL — Zone Violated",
message = "MMXL: An exhaustion zone was violated (close outside zone) on {{ticker}} {{interval}}.")
These alerts turn the script into an event driven structure tool.
The first set alerts when a new bullish or bearish MMXL zone forms.
The second set alerts when price retests an active zone.
The third alert triggers when a zone becomes invalidated.
This makes the model useful not only for visual chart review, but also for live monitoring workflows. Indicator

CVD Divergence & Absorption [UAlgo]CVD Divergence & Absorption is a dual context indicator that combines a cumulative volume delta (CVD) oscillator with price pivot structure to detect three important signal classes: Regular Divergence, Hidden Divergence, and Absorption. The script is designed to help traders compare price movement against directional volume participation and identify moments where price and CVD disagree, or where price stalls at similar levels while CVD continues to expand or contract.
The indicator runs in a separate pane ( overlay=false ) and plots a continuous CVD line, while signal labels and price side connecting lines are projected onto the main chart using force_overlay=true . This gives a clean workflow where you can monitor the CVD series in its own panel and still see exact divergence locations directly on price.
A key strength of this script is its lower timeframe volume decomposition. Instead of assigning the full chart bar volume to a single direction, it samples lower timeframe candles through request.security_lower_tf() , classifies each sub candle as up volume or down volume based on its close versus open, and aggregates the result into a bar level delta. That delta is then accumulated into the running CVD value. This approach is practical, efficient, and more granular than a simple chart timeframe approximation.
The script also includes quality controls for signal validation:
Pivot based comparisons for both price and CVD
Minimum and maximum bar distance filters between pivot comparisons
Equal price tolerance for absorption detection
A line of sight filter that rejects visually obstructed divergences where intervening candles violate the connecting path
The result is a professional divergence framework focused on cleaner, more interpretable signals rather than high frequency marking of every pivot mismatch.
Educational tool only. Not financial advice.
🔹 Features
🔸 1) Lower Timeframe CVD Construction (LTF Volume Decomposition)
The script builds CVD using lower timeframe candles selected by the user through the Lower Timeframe (LTF) input. For each chart bar, it retrieves arrays of lower timeframe open, close, and volume values and computes a signed delta:
Up LTF candle (close > open) adds volume
Down LTF candle (close < open) subtracts volume
Neutral LTF candle contributes zero
This produces a more refined bar delta than a single bar directional assumption and makes the CVD line more responsive to intrabar rotation.
🔸 2) Pivot Based Signal Engine for Price and CVD
Signal generation is anchored to confirmed price pivots using user defined left and right pivot bars. A signal candidate is considered only when:
A price pivot high/low is confirmed by ta.pivothigh or ta.pivotlow
The CVD value at that pivot location also behaves like a local high/low (simple local extremum check)
This means the script does not compare arbitrary points. It compares structurally meaningful swing locations.
🔸 3) Regular Divergence Detection (Bullish and Bearish)
The indicator supports classic regular divergence logic:
Bullish Regular Divergence: price makes a lower low while CVD makes a higher low
Bearish Regular Divergence: price makes a higher high while CVD makes a lower high
These signals can indicate weakening trend continuation pressure and potential reversal behavior, depending on context.
🔸 4) Hidden Divergence Detection (Bullish and Bearish)
The script also detects hidden divergence, which many traders use as continuation style confirmation:
Bullish Hidden Divergence: price makes a higher low while CVD makes a lower low
Bearish Hidden Divergence: price makes a lower high while CVD makes a higher high
Hidden divergence is optional and can be toggled independently from regular divergence.
🔸 5) Absorption Detection with Equal Price Tolerance
Absorption logic is included to capture situations where price prints near equal pivots, but CVD continues moving in a direction that suggests aggressive participation is being absorbed at the level:
Bearish Absorption (at highs): price is approximately equal high, but CVD is higher
Bullish Absorption (at lows): price is approximately equal low, but CVD is lower
The Equal Price Tolerance % input allows the script to treat two pivots as "equal" within a configurable percentage band. This makes absorption detection adaptable across instruments with different volatility profiles.
🔸 6) Minimum / Maximum Pivot Distance Filters
To avoid weak or overly stale comparisons, the script enforces:
A minimum number of bars between pivots
A maximum lookback distance for valid pivot pairing
This helps reduce noisy signals from pivots that are too close together and prevents pairing pivots that are too far apart to be contextually useful.
🔸 7) Line of Sight Validation (Signal Quality Filter)
Before accepting a pivot comparison, the script checks whether the straight line connecting the two price pivots is "clear" from intervening candle violations:
For bearish (high based) comparisons, intervening highs must not cross above the connecting line
For bullish (low based) comparisons, intervening lows must not cross below the connecting line
This is a strong visual integrity filter. It avoids many cluttered or ambiguous divergence lines that would look invalid once drawn on the chart.
🔸 8) Dual Visualization on Price and CVD
When a signal is detected, the script draws:
A label on price ("Reg", "Hid", or "Abs")
A line connecting the two relevant price pivots on the main chart
A line connecting the corresponding CVD pivot values in the CVD pane
Line styles are used to distinguish signal types:
Solid for Regular Divergence
Dashed for Hidden Divergence
Dotted for Absorption
This synchronized plotting makes it easy to verify the signal logic visually.
🔸 9) Conflict Handling for Cleaner Labels
Absorption labels are intentionally suppressed when a Hidden Divergence signal is already active on the same side in the same event block:
bullAbs and not bullHidDiv
bearAbs and not bearHidDiv
This prevents duplicate labels on the same pivot and improves chart readability.
🔸 10) Lightweight Pivot Memory Management
The script stores historical pivot comparison points in separate arrays for highs and lows and caps them using a helper method ( maxSize = 15 ). This keeps the logic efficient while preserving enough recent history for valid comparisons.
🔹 Calculations
1) Lower Timeframe Delta Aggregation
The script retrieves lower timeframe OHLCV arrays and computes bar delta by summing signed volume from each LTF candle:
array ltf_open = request.security_lower_tf(syminfo.tickerid, i_ltf, open)
array ltf_close = request.security_lower_tf(syminfo.tickerid, i_ltf, close)
array ltf_volume = request.security_lower_tf(syminfo.tickerid, i_ltf, volume)
if ltf_c > ltf_o
totalDelta += ltf_v
else if ltf_c < ltf_o
totalDelta -= ltf_v
Interpretation:
The script uses candle direction as a proxy for buying/selling pressure inside each chart bar.
This is an estimated delta model based on candle body direction, not true bid/ask tape delta.
2) CVD Accumulation
Bar delta is added into a running cumulative value stored inside a custom tracker object:
method update_cvd(CVD_Tracker this, float delta) =>
this.currentCVD += delta
this.currentCVD
The tracker persists across bars using:
var CVD_Tracker tracker = CVD_Tracker.new(0.0, array.new(), array.new())
This design keeps both the CVD value and pivot histories in one structured container.
3) Price Pivot Detection
Price pivots are confirmed using standard left/right pivot logic:
float ph = ta.pivothigh(high, i_left, i_right)
float pl = ta.pivotlow(low, i_left, i_right)
Because pivot confirmation occurs after i_right bars, signal labels and lines are placed at:
bar_index - i_right
This aligns the plotted signal with the actual pivot bar, not the confirmation bar.
4) CVD Pivot Confirmation at the Same Pivot Location
The script requires CVD to form a local extremum at the price pivot location using a simple 3-point comparison around currentCVD :
bool cvdIsPh = currentCVD > currentCVD and currentCVD > currentCVD
bool cvdIsPl = currentCVD < currentCVD and currentCVD < currentCVD
Then:
bool isPh = not na(ph) and cvdIsPh
bool isPl = not na(pl) and cvdIsPl
This ensures price and CVD are compared on synchronized pivot events rather than unrelated timestamps.
5) Pivot Pair Selection with Distance Constraints
When a new pivot is confirmed, the script scans prior pivots of the same type (highs with highs, lows with lows) and applies:
i_min_bars as the minimum spacing
i_max_bars as the maximum valid distance
if barsBetween < minBars
continue
if barsBetween > maxBars
break
This keeps comparisons within a user defined structural window.
6) Line of Sight Filter (Price Geometry Validation)
Before checking divergence conditions, the script verifies that the connecting price line is not invalidated by intervening candles.
For highs:
if barsBack >= 0 and high >= lineY
clear := false
For lows:
if barsBack >= 0 and low <= lineY
clear := false
Interpretation:
Bearish comparisons require a clean descending/ascending line between highs without intermediate highs breaking above it.
Bullish comparisons require a clean line between lows without intermediate lows breaking below it.
This is one of the script’s strongest anti-noise mechanisms.
7) Equal Price Tolerance for Absorption
The script calculates percentage difference between pivot prices and treats them as equal if the difference is within the tolerance:
float priceDiffPct = math.abs(newPivot.priceVal - histPivot.priceVal) / histPivot.priceVal * 100
bool isEqual = priceDiffPct <= eqTol
This enables absorption logic to work with approximate equal highs/lows instead of requiring perfect price matches, which are rare in live markets.
8) Bearish Signal Logic (Regular, Hidden, Absorption)
For pivot highs, the script compares a new pivot high against a historical pivot high after passing distance and line of sight checks.
Bearish Regular Divergence
newPivot.priceVal > histPivot.priceVal and not isEqual and newPivot.cvdVal < histPivot.cvdVal
Meaning:
Price makes a higher high
CVD makes a lower high
Bearish Hidden Divergence
newPivot.priceVal < histPivot.priceVal and not isEqual and newPivot.cvdVal > histPivot.cvdVal
Meaning:
Price makes a lower high
CVD makes a higher high
Bearish Absorption
isEqual and newPivot.cvdVal > histPivot.cvdVal
Meaning:
Price prints an approximately equal high
CVD pushes higher, suggesting buying effort is absorbed near the same price zone
9) Bullish Signal Logic (Regular, Hidden, Absorption)
For pivot lows, the script compares a new pivot low against a historical pivot low after passing distance and line of sight checks.
Bullish Regular Divergence
newPivot.priceVal < histPivot.priceVal and not isEqual and newPivot.cvdVal > histPivot.cvdVal
Meaning:
Price makes a lower low
CVD makes a higher low
Bullish Hidden Divergence
newPivot.priceVal > histPivot.priceVal and not isEqual and newPivot.cvdVal < histPivot.cvdVal
Meaning:
Price makes a higher low
CVD makes a lower low
Bullish Absorption
isEqual and newPivot.cvdVal < histPivot.cvdVal
Meaning:
Price prints an approximately equal low
CVD pushes lower, suggesting selling effort is absorbed near the same price zone
10) Signal Plotting and Visual Encoding
When a condition is confirmed, the script plots both price side and CVD side lines between the historical pivot and the new pivot, plus a compact label at the new pivot location.
Examples:
label.new(bar_index - i_right, low , text="Reg", ...)
line.new(bullLastPivot.loc, bullLastPivot.priceVal, bullNewPivot.loc, bullNewPivot.priceVal, ..., force_overlay=true)
line.new(bullLastPivot.loc, bullLastPivot.cvdVal, bullNewPivot.loc, bullNewPivot.cvdVal, ..., force_overlay=false)
Style mapping:
line.style_solid for Regular Divergence
line.style_dashed for Hidden Divergence
line.style_dotted for Absorption
11) Pivot History Storage and Capacity Control
Each confirmed pivot is stored in a side specific array (highs or lows) using a helper method:
method add_pivot(array this, Pivot p, int maxSize = 15) =>
this.push(p)
if this.size() > maxSize
this.shift()
This preserves recent structural history for future comparisons while keeping memory usage controlled. Indicator

Liquidity Raids [UAlgo]Liquidity Raids is a market structure overlay designed to highlight classic liquidity sweep events around recent swing levels. The script continuously maps swing highs and swing lows using pivot detection, projects those levels forward as active lines, and then monitors price behavior around each level to detect a raid.
A raid is defined here as a sweep through a prior level followed by rejection back across it within the same bar. This behavior often represents stop runs, liquidity grabs, or failed break attempts. The script separates these events into:
BSL sweeps, where buy side liquidity above prior highs is taken and price closes back below the level
SSL sweeps, where sell side liquidity below prior lows is taken and price closes back above the level
To improve signal quality, an optional relative volume confirmation filter can be enabled. When active, a sweep is only valid if the sweep bar’s volume exceeds a multiple of the recent average, and the script prints the relative volume percentage on the chart for additional context.
The indicator also includes practical object management to keep charts clean by limiting the number of active levels and removing invalidated lines automatically.
🔹 Features
1) Automatic Swing Level Mapping via Pivot Highs and Lows
The script uses pivot detection to identify meaningful swing highs and swing lows. Each confirmed pivot becomes a projected liquidity level that extends forward in time. These levels represent areas where stops and breakout orders tend to cluster.
Pivot Length controls how sensitive the swing detection is. Higher values produce fewer but more significant levels. Lower values react faster and produce more frequent levels.
2) Active Level Projection and Management
Each pivot level is drawn as a horizontal line and stored in an internal array. On every new bar, the script updates each active line so it extends to the current bar. A Maximum Active Levels setting prevents chart clutter and controls the number of stored objects. When the limit is exceeded, the oldest level is removed.
3) Clear Sweep Definitions for BSL and SSL
Each level is monitored for two outcomes:
Sweep and reject
Broken and accepted
For resistance levels, a BSL sweep requires the bar high to trade above the level while the close finishes at or below the level. A break requires the close to finish above the level.
For support levels, an SSL sweep requires the bar low to trade below the level while the close finishes at or above the level. A break requires the close to finish below the level.
When a sweep is detected, the level is removed after the event is confirmed. When a level is broken, it is removed to prevent outdated levels from remaining on the chart.
4) Optional Volume Confirmation Using Relative Volume
When enabled, sweeps are filtered using Relative Volume (RVOL). The script compares the current bar’s volume to the 20 bar average volume and requires it to exceed a user defined multiplier.
This is useful for separating meaningful stop runs from thin market spikes. The script also prints the RVOL percentage near the swept level for quick evaluation.
5) Sweep Highlighting and Labels
On a valid sweep, the script highlights the swept level with a bright confirmation line and optionally prints labels:
▼ BSL for buy side liquidity sweeps
▲ SSL for sell side liquidity sweeps
Volume information is also displayed as a percentage at the midpoint of the swept segment, positioned above for BSL and below for SSL to reduce overlap.
6) Configurable Visual Styling
You can control resistance and support colors independently, choose line style (solid, dotted, dashed), and toggle labels. This makes the overlay adaptable to both clean minimalist charts and more information dense layouts.
7) Alerts for Automation and Monitoring
Alert conditions are included for both sweep types. In addition, the script triggers immediate alerts with the close price when a sweep is detected on bar close. This supports both discretionary monitoring and automated notification workflows.
🔹 Calculations
1) Pivot Based Level Detection
Swing highs and lows are detected using symmetric pivot logic:
float ph = ta.pivothigh(high, pivotPeriodInput, pivotPeriodInput)
float pl = ta.pivotlow(low, pivotPeriodInput, pivotPeriodInput)
Interpretation:
A pivot high is confirmed only after pivotPeriodInput bars to the right
A pivot low is confirmed only after pivotPeriodInput bars to the right
Confirmed pivot values become new resistance or support liquidity levels
2) Level Storage and Line Creation
When a pivot is confirmed, the script creates a line starting at the pivot bar and stores it as a LiquidityLevel object:
line newL = line.new(bar_index , ph, bar_index, ph, color = resistanceColorInput, style = getLineStyle(lineStyleInput))
resistanceLevels.push(LiquidityLevel.new(ph, bar_index , newL))
The same logic applies to pivot lows for support levels.
To prevent excessive object growth, levels are capped:
if resistanceLevels.size() > maxLinesInput
(resistanceLevels.shift()).delete()
3) Relative Volume and Volume Filter
The script computes average volume over the last 20 bars and converts current volume into a percentage:
float volAvg = ta.sma(volume, 20)
float volRelative = (volume / volAvg) * 100
The volume filter is satisfied when either the filter is disabled or the current volume exceeds the average multiplied by the chosen multiplier:
bool isVolStrong = not useVolFilterInput or (volume > volAvg * volMultiplierInput)
4) Sweep and Break Conditions
Each active resistance level is checked for a sweep or a break:
bool priceSwept = high > lvl.price and close <= lvl.price
bool broken = close > lvl.price
Each active support level is checked similarly:
bool priceSwept = low < lvl.price and close >= lvl.price
bool broken = close < lvl.price
Interpretation:
A sweep requires a wick through the level and a close back across it
A break requires acceptance beyond the level on close
5) Sweep Confirmation Handling and Cleanup
When a sweep occurs with strong volume, the script sets a flag for alerts, draws highlight objects, prints labels, and removes the level from active tracking:
Resistance sweep flow:
if priceSwept and isVolStrong
buySweepOccurred := true
line.new(lvl.startBar, lvl.price, bar_index, lvl.price, color = C_SWEEP_BUY)
resistanceLevels.remove(i).delete()
Support sweep flow:
if priceSwept and isVolStrong
sellSweepOccurred := true
line.new(lvl.startBar, lvl.price, bar_index, lvl.price, color = C_SWEEP_SELL)
supportLevels.remove(i).delete()
If a level is broken, it is removed as invalid:
else if broken
resistanceLevels.remove(i).delete()
6) Volume Annotation Placement
On a sweep, the script computes the midpoint of the level segment in bar index space and prints RVOL percent:
int midX = math.round((lvl.startBar + bar_index) / 2)
label.new(midX, lvl.price, str.tostring(volRelative, "#") + "% VOL")
Placement is above for BSL sweeps and below for SSL sweeps to align with the direction of the liquidity being taken.
7) Alerts
Alert conditions and direct alerts are provided:
alertcondition(buySweepOccurred, "Buy Liquidity Sweep", "BSL Swept!")
alertcondition(sellSweepOccurred, "Sell Liquidity Sweep", "SSL Swept!")
The script also triggers runtime alerts including the close price once per bar close when a sweep occurs. Indicator

Parkinson Range Oscillator [BackQuant]Parkinson Range Oscillator
Overview
Parkinson Range Oscillator is a volatility regime indicator built around the Parkinson volatility estimator , a high-low based variance model originally proposed as a more statistically efficient alternative to close-to-close volatility. Instead of measuring volatility from closing returns, this script measures volatility from the intrabar price range using ln(H/L), then converts it into a normalized oscillator (z-score) so you can identify volatility expansion vs compression relative to the asset’s own history.
The indicator is designed to answer questions like:
Is volatility currently elevated or suppressed relative to its baseline?
Is volatility expanding (risk rising) or compressing (coiling)?
How extreme is the current vol state in percentile terms?
How does range-based vol compare to a more common ATR-based vol read?
It plots:
A Parkinson-based volatility z-score oscillator with gradient fills.
A signal line (EMA) for expansion/compression transitions.
An ATR-based z-score for context comparison.
A dashboard with current vol %, z-score, percentile rank, regime label, and ATR z-score.
Where Parkinson volatility comes from (origin and intuition)
The Parkinson estimator comes from academic finance and the study of volatility estimation. The key insight is simple:
The daily high and low contain more information about variability than the close alone.
Close-to-close volatility only uses one price per bar (the close), throwing away intrabar information. The high-low range captures the realized dispersion inside the bar, so under ideal assumptions it can estimate variance more efficiently.
The Parkinson model is derived assuming:
Price follows a continuous-time diffusion process (often framed like geometric Brownian motion).
No drift matters for the variance estimate over the interval.
No jumps and no microstructure distortions (idealized).
Even though real markets violate these assumptions (gaps, jumps, wicks from order flow), the estimator remains useful because:
Range is still a strong proxy for realized volatility.
It reacts to intrabar expansion earlier than close-based methods.
It is less dependent on where the bar closes.
Core Parkinson formula (what the script implements)
Parkinson variance for a window of n bars is:
Var = (1 / (4 * n * ln(2))) * Σ
This script computes it in the common rolling form:
logHL2 = (ln(high/low))²
parkVar = SMA(logHL2, n) / (4 * ln(2))
parkVol = sqrt(parkVar) * 100
Key details:
ln(H/L) makes the range scale-invariant (percent-like), so it behaves more consistently across price levels.
Squaring gives variance contribution.
The 1/(4 ln 2) constant comes from the expected distribution of high-low range under a Brownian diffusion.
sqrt converts variance to standard deviation (volatility).
*100 expresses it as a percentage for readability.
So parkVol is a “range-based realized volatility proxy” in percent terms.
Why range-based volatility behaves differently than ATR
ATR measures average true range, which is a linear range magnitude measure (high-low plus gaps). Parkinson uses ln(H/L) which is:
Log-scaled (closer to a return-based measure).
More directly tied to variance estimation theory.
In practice:
ATR can be driven by gaps and absolute range.
Parkinson is driven by proportional range and tends to emphasize how wide the bar is relative to its price level.
Parkinson often reacts sharply when wicks expand even if closes are stable.
Normalization into an oscillator (making it comparable through time)
Raw volatility values are hard to interpret across regimes because every market has different “normal.” This script normalizes Parkinson volatility against its own rolling baseline using a z-score:
parkMA = SMA(parkVol, baselineLen)
parkSD = stdev(parkVol, baselineLen)
osc = (parkVol - parkMA) / parkSD
Interpretation:
osc = 0 means current vol is at its baseline average.
osc = +1 means 1 standard deviation above normal (high vol).
osc = -1 means 1 standard deviation below normal (compressed).
osc > +2 flags extreme expansion states.
This is the core output. It turns “volatility” into “volatility regime” in standardized units.
Signal line and expansion/compression transitions
The oscillator is smoothed with an EMA to create a signal line:
signal = EMA(osc, signalLen)
Then transitions are defined as:
Expansion cross: crossover(osc, signal) and osc > 0
Compression cross: crossunder(osc, signal) and osc < 0
Why the extra osc > 0 and osc < 0 conditions:
It prevents treating small oscillations around zero as meaningful.
It forces expansion signals to occur in above-average volatility territory.
It forces compression signals to occur in below-average volatility territory.
So signals are regime-confirming, not constant cross spam.
Percentile rank (how extreme is vol relative to the past)
In addition to the z-score, the script computes the percentile rank of the raw Parkinson volatility:
pctRank = percentrank(parkVol, pctRankLookback)
Interpretation:
pctRank near 90–100 means current vol is among the highest levels seen in that lookback.
pctRank near 0–10 means it is among the lowest (compression).
Z-score tells you “how many SDs from mean.” Percentile tells you “how rare is this state historically.” Those are different but complementary.
ATR comparison line (context, not the main engine)
The indicator also computes an ATR-based volatility proxy and normalizes it in the same way:
atrVol = ATR(n) / close * 100
atrOsc = zscore(atrVol, baselineLen)
This gives you a direct visual comparison:
If Parkinson oscillator is high but ATR oscillator isn’t, range expansion may be happening in a way ATR is not emphasizing (or vice versa).
If both agree, you have stronger confirmation of a true volatility regime shift.
ATR is included as a “common benchmark,” not as the primary signal.
Regime classification (human-readable state mapping)
The script labels regimes from osc:
osc > 2.0 → EXTREME
osc > 1.0 → HIGH
osc > 0.0 → ABOVE AVG
osc > -1.0 → BELOW AVG
else → COMPRESSED
This is a practical mapping for dashboards and quick reads. It is not pretending that 2.0 is a universal constant, it is just a standardized “rare expansion” threshold.
Coloring follows the same logic:
More positive = more “expansion” coloring (bearCol).
More negative = more “compression” coloring (bullCol).
Note: the color naming is semantic here:
“Low Vol / Compression” is bullCol because compression often precedes trend expansion opportunities.
“High Vol / Expansion” is bearCol because high vol often implies risk, disorder, liquidation, or unstable conditions.
You can interpret those however you prefer, the tool is measuring volatility regime, not directional bias.
Plot design (why the oscillator is split into positive/negative)
The oscillator is split into two series:
oscPos = osc if osc > 0 else na
oscNeg = osc if osc < 0 else na
This is purely for visuals:
Positive region is drawn with expansion color and expansion gradient fill to zero.
Negative region is drawn with compression color and compression gradient fill to zero.
This makes it obvious at a glance which side of “normal volatility” you’re on.
How to interpret the indicator correctly
1) The oscillator is volatility regime, not price direction
High osc does not mean price will go down. It means the market is moving violently relative to its baseline. That can occur in:
Selloffs, liquidations, panic.
Breakouts and momentum expansions.
News-driven repricing.
Low osc does not mean price will go up. It means the market is quiet relative to baseline:
Ranges, coils, low realized movement.
Slow grind trends with suppressed pullbacks.
Pre-breakout compressions.
2) Compression regimes are often “setup states”
When osc is deeply negative (compressed), it often indicates that realized movement has collapsed. In many markets this precedes:
Breakouts (vol expansion from compression).
Trend acceleration.
Mean reversion bursts.
But compression can also persist. This is why the script includes signal crosses and percentile rank to judge when compression is shifting.
3) Expansion regimes are often “risk states”
When osc is positive and rising, the environment is more chaotic:
Stops are more likely to be hit.
Mean reversion can get violent.
Trend continuation can be strong but timing becomes harder.
In those regimes, the tool can be used to:
Reduce leverage.
Widen stops (if your system supports it).
Switch to volatility-aware sizing.
Wait for stabilization if you trade mean reversion.
4) Use percentile rank to identify “rare” volatility
Two markets can both show osc = +1, but one might be at the 95th percentile and the other at the 70th depending on distribution shape. Percentile tells you whether the current vol is truly rare in that lookback.
Cross dots (how to treat them)
ExpansionCross and CompressionCross are not buy/sell signals. They are “volatility phase change” markers:
ExpansionCross: vol regime moving up, above baseline, acceleration risk increases.
CompressionCross: vol regime moving down, below baseline, quieting environment.
These are useful for:
Strategy toggles (trend mode vs chop mode).
Sizing changes.
Timing filters (avoid entries during extreme expansion if your edge hates noise).
Dashboard (what it gives you at a glance)
The table summarizes everything that matters without you needing to interpret plots manually:
Parkinson Vol %: current raw range-based volatility level.
Z-Score: current standardized regime reading.
Percentile: rarity of current vol in the lookback.
Regime: discrete label based on z-score thresholds.
ATR Z-Score: comparison metric in standardized units.
The dashboard is positioned and sized via inputs so it can fit different chart layouts.
Parameter tuning guidance
Parkinson Length
Controls how quickly the raw Parkinson vol responds:
Shorter = more reactive to immediate range changes.
Longer = smoother volatility estimate, less noisy.
Baseline Length
Controls what “normal” means:
Long baseline (like 100) creates stable regime definitions.
Short baseline makes z-scores jump around and can overreact.
Signal Length
Controls how quickly you detect regime turning points:
Short signal = more crosses, earlier detection, more noise.
Long signal = fewer crosses, later detection, cleaner regime shifts.
Percentile Lookback
Controls rarity context:
252 approximates one trading year on daily charts.
On intraday, it becomes “252 bars,” so adjust to match your horizon.
Limitations and what to watch for
Parkinson assumes continuous diffusion. Jumps and gaps can distort it.
Wicks caused by illiquidity can inflate ln(H/L) and produce false “expansion.”
Z-score assumes the baseline distribution is reasonably stable. If volatility distribution shifts structurally, your z-scores can be biased until baseline catches up.
Percentile rank is lookback-dependent. Different lookbacks can change “rarity” classification materially.
Summary
Parkinson Range Oscillator converts a statistically grounded high-low volatility estimator into a regime oscillator by z-scoring Parkinson volatility against its own rolling baseline. It highlights expansion vs compression states with clear gradients, flags volatility phase changes via oscillator-signal crosses, ranks current volatility by percentile for rarity context, and overlays an ATR-based z-score for comparison. This makes it a practical tool for volatility-aware trading, regime filtering, sizing adjustments, and identifying compression-to-expansion transitions. Indicator

MBAND 200 4H BTC/USDT - By MGS-TradingMBAND 200 4H BTC/USDT with RSI and Volume by MGS-Trading: A Neural Network-Inspired Indicator
Introduction:
The MBAND 200 4H BTC/USDT with RSI and Volume represents a groundbreaking achievement in the integration of artificial intelligence (AI) into cryptocurrency market analysis. Developed by MGS-Trading, this indicator is the culmination of extensive research and development efforts aimed at leveraging AI's power to enhance trading strategies. By synthesizing neural network concepts with traditional technical analysis, the MBAND indicator offers a dynamic, multi-dimensional view of the market, providing traders with unparalleled insights and actionable signals.
Innovative Approach:
Our journey to create the MBAND indicator began with a simple question: How can we mimic the decision-making prowess of a neural network in a trading indicator? The answer lay in the weighted aggregation of Exponential Moving Averages (EMAs) from multiple timeframes, each serving as a unique input akin to a neuron in a neural network. These weights are not arbitrary; they were painstakingly optimized through backtesting across various market conditions to ensure they reflect the significance of each timeframe’s contribution to overall market dynamics.
Core Features:
Neural Network-Inspired Weights: The heart of the MBAND indicator lies in its AI-inspired weighting system, which treats each timeframe’s EMA as an input node in a neural network. This allows the indicator to process complex market data in a nuanced and sophisticated manner, leading to more refined and informed trading signals.
Multi-Timeframe EMA Analysis: By analyzing EMAs from 15 minutes to 3 days, the MBAND indicator captures a comprehensive snapshot of market trends, enabling traders to make informed decisions based on a broad spectrum of data.
RSI and Volume Integration: The inclusion of the Relative Strength Index (RSI) and volume data adds layers of confirmation to the signals generated by the EMA bands. This multi-indicator approach helps in identifying high-probability setups, reinforcing the neural network’s concept of leveraging multiple data points for decision-making.
Usage Guidelines:
Signal Interpretation: The MBAND bands provide a visual representation of the market’s momentum and direction. A price moving above the upper band signals strength and potential continuation of an uptrend, while a move below the lower band suggests weakness and a possible downtrend.
Overbought/Oversold Conditions: The RSI component identifies when the asset is potentially overbought (>70) or oversold (<30). Traders should watch for these conditions near the MBAND levels for potential reversal opportunities.
Volume Confirmation: An increase in volume accompanying a price move towards or beyond an MBAND level serves as confirmation of the strength behind the move. This can indicate whether a breakout is likely to sustain or if a reversal has substantial backing.
Strategic Entry and Exit Points: Combine the MBAND readings with RSI and volume indicators to pinpoint strategic entry and exit points. For example, consider entering a long position when the price is near the lower MBAND, RSI indicates oversold conditions, and there is a notable volume increase.
About MGS-Trading:
At MGS-Trading, we are passionate about harnessing the transformative power of AI to revolutionize cryptocurrency trading. Our indicators and tools are designed to provide traders with advanced analytics and insights, drawing on the latest AI techniques and methodologies. The MBAND 200 4H BTC/USDT with RSI and Volume indicator is a prime example of our commitment to innovation, offering traders a sophisticated, AI-enhanced tool for navigating the complexities of the cryptocurrency markets.
Disclaimer:
The MBAND indicator is provided for informational purposes only and does not constitute investment advice. Trading cryptocurrencies involves significant risk and can result in the loss of your investment. We recommend conducting your own research and consulting with a qualified financial advisor before making any trading decisions. Indicator

Indicator

Indicator

vx_termsUSAGE
--------
This script helps train your intuition for changes in the VX term structure. I recommend using it on the VIX chart, so you can compare changes in the terms to changes in VIX. It's also nice for calendar spread traders who want to get a feel for the same changes.
1. Select a day, month, and year using the inputs
2. Observe the data table.
3. Open the input again and increment or decrement the day (and month, year as necessary).
4. Click "Ok".
5. Click to deselect the indicator, which allows the chart to load new data.
6. The data table will be reloaded with the next/previous day's data.
The data table has the following columns:
- contract: the VX contracts, in sequence. refer to the CBOE for month codes (F for January, etc.)
- close: the closing price of the contract.
- ma:mb: the spread (difference) between this row and the next row.
- ma:mb chg: the spread's change from prior close.
For example, given the following values for the first two columns:
VXQ2021, 16.5, -3.1, -0.2
VXU2021, 19.6, ..., ...
The front month (Q = august) closed at 16.5, $3.1 below the s\September contract. The negative spread enlarged by $0.20 from $2.90 on the previous trading day.
BUGS, ODDITIES, AND LIMITATIONS:
-------------------------------------------
- The first column will be greyed out after expiration day, which is the 3rd Tuesday of that month. Unfortunately, I can't load the next month's contract due to some limitations with TV.
- The active date is highlighted with a yellow background. When a non-trading date is selected, the highlight will disappear. However, the data table will sometimes fill with the nearest trading date, prematurely. No worries, just know that the data is probably for the previous Friday.
- The script is clunky and slow, but this is the best I can do with TV. Hopefully they add more continuous contracts or allow true dynamic symbol loading.
SPECIAL THANKS:
---------------------
Thanks to HeWhoMustNotBeNamed for helping me get through some messiness. Very helpful guy.
www.pulsewire.com Indicator

0_dteUSAGE
This script guages the probability of an underlying moving a certain amount on expiration day, to aid the popular "0 dte" strategy. The script counts how many next-day moves exceeded a given magnitude in the past, under similar conditions. The inputs are:
mark_mode:
- "open": measures the magnitude as "open to close"--a true 0 dte.
- "previous close": for lazy people who don't want to wake up early. measures magnitude from the previous day's close.
move_mode:
- "percent": measures moves that exceed a given percentage.
- "absolute": measures moves that exceed a point value.
move-dir: measure only up moves, down moves, or both.
vol_model: the model for realized volatility. (may add more later).
min_vol: only measure moves when realized vol is above this value.
max_vol: only measure moves when realized vol is below this value.
precision: number of digits printed in the output table.
EXAMPLE:
- mark_mode: "previous close"
- move_mode: "percent"
- move_dir: "up"
- move_mag: 0.07
- vol_model: hv30
- min_vol: 0.2
- max_vol: 0.5
These settings will count the number of trading days that closed 7% higher than the previous day's close, when the previous day's realized volatility (annualized) was between 20% and 50%. The outputs are:
- current vol: green plot. Today's realized vol. Shown for convenience.
- max and min vol: red plots. Also shown for convenience.
- count: the number of days that exceeded the chosen magnitude, when the previous day's realized volatility was within the chosen bounds.
- total: the total number of days where realized volatility was within the chosen bounds
- probability: count / total. the percentage of days that exceeded the move when volatility was within the bounds.
- move: plotted as a purple line. purple "X" labels are plotted above
- bars where the move exceeded the magnitude threshold and volatility was in-bounds. a "hit".
CONCLUSION
This script is based on the idea that realized volatility has some bearing on future volatility. By seeing what happened in the past when volatility was close to its current value, we may be able to assess the probability that our short put will be in the money, tomorrow, and our account devastated.
NOTE: Unlike many of my other scripts, all percentages--both inputs and outputs--are given in fractional form. E.g., 0.01 means 1%. Indicator

pricing_tableThis script helps you evaluate the fair value of an option. It poses the question "if I bought or sold an option under these circumstances in the past, would it have expired in the money, or worthless? What would be its expected value, at expiration, if I opened a position at N standard deviations, given the volatility forecast, with M days to expiration at the close of every previous trading day?"
The default (and only) "hv" volatility forecast is based on the assumption that today's volatility will hold for the next M days.
To use this script, only one step is mandatory. You must first select days to expiration. The script will not do anything until this value is changed from the default (-1). These should be CALENDAR days. The script will convert to these to business days for forecasting and valuation, as trading in most contracts occurs over ~250 business days per year.
Adjust any other variables as desired:
model: the volatility forecasting model
window: the number of periods for a lagged model (e.g. hv)
filter: a filter to remove forecasts from the sample
filter type: "none" (do not use the filter), "less than" (keep forecasts when filter < volatility), "greater than" (keep forecasts when filter > volatility)
filter value: a whole number percentage. see example below
discount rate: to discount the expected value to present value
precision: number of decimals in output
trim outliers: omit upper N % of (generally itm) contracts
The theoretical values are based on history. For example, suppose days to expiration is 30. On every bar, the 30 days ago N deviation forecast value is compared to the present price. If the price is above the forecast value, the contract has expired in the money; otherwise, it has expired worthless. The theoretical value is the average of every such sample. The itm probabilities are calculated the same way.
The default (and only) volatility model is a 20 period EWMA derived historical (realized) volatility. Feel free to extend the script by adding your own.
The filter parameters can be used to remove some forecasts from the sample.
Example A:
filter:
filter type: none
filter value:
Default: the filter is not used; all forecasts are included in the the sample.
Example B:
filter: model
filter type: less than
filter value: 50
If the model is "hv", this will remove all forecasts when the historical volatility is greater than fifty.
Example C:
filter: rank
filter type: greater than
filter value: 75
If the model volatility is in the top 25% of the previous year's range, the forecast will be included in the sample apart from "model" there are some common volatility indexes to choose from, such as Nasdaq (VXN), crude oil (OVX), emerging markets (VXFXI), S&P; (VIX) etc.
Refer to the middle-right table to see the current forecast value, its rank among the last 252 days, and the number of business days until
expiration.
NOTE: This script is meant for the daily chart only. Indicator

vertical_pricer
USAGE
1. Select the type of contract (call or put), the long strike, and the width.
2. Select the volatility model
3. The standard deviation is shown, enter it into the input.
The tool gives a theoretical price of a vertical spread, based on a
historical sample. The test assumes that a spread of equal width was sold on
every prior trading day at the given standard deviation, based on the
volatility model and duration of the contract. For example, if the 20 dte
110 strike is presently two standard deviations based on the 30 period
historical volatility, then the theoretical value is the average price all
2SD (at 20 dte) calls upon expiration, limited by the width of the spread and
normalized according to the present value of the underlying.
Other statistics include:
- The number of spreads in the sample, and percentage expired itm
- The median value at expiration
- The Nth percentile value of spreads at expiration
- The number of spreads that expired at max loss
Check the script comments and release notes for further updates, since Tradingview doesn't allow me to edit this description. Indicator

Indicator

Indicator

Indicator

Indicator

Realized VolatilityRealized / Historical Volatility
Calculates historical, i.e. realized volatility of any underlying. If frequency is not the daily, but for example 6h, 30min, weeks or months, it scales the initial setting to be suitable for the different time frame.
Examples with default settings (30 day volatility, 365 days per year):
A) Frequency = Daily:
Returns 30 day historical volatility, under the assumption that there are 365 trading days in a year.
B) Frequency = 6h:
Still returns 30 day historical volatility, under the assumption that there are 365 trading days in a year. However, since 6h granularity fits 4 times in 24 hours, it rescales the look back period to rather 30*4 = 120 units to still reflect 30 day historical volatility. Indicator

Indicator

Indicator
