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.
Pine Script® göstergesi






















