- Measures price displacement from its SMA, **normalized by ATR**, producing a dimensionless ratio comparable across instruments and timeframes.
- A reading of +2.0 means price is two ATRs above the mean; -3.0 means three ATRs below. The same threshold carries the same statistical weight regardless of price level.
- Uses **True Range** (which captures gap risk) rather than standard deviation, making it more responsive to overnight gaps and limit moves than Bollinger-based alternatives.
- Single-parameter design (both SMA and ATR share the same period) keeps configuration simple while the ATR denominator automatically adapts to volatility regimes.
- EMA-based ATR with **warmup compensation** ensures accurate normalization from the first bar.
Mark Johnson introduced the Pretty Good Oscillator in the late 1990s as a practical alternative to oscillators that produce instrument-dependent readings. The core insight: dividing by ATR creates a dimensionless ratio. A $500 stock and a $2 penny stock can both produce a PGO of +3.0, and that reading carries the same statistical meaning. The deliberately modest name reflects Johnson's positioning: not the ultimate oscillator, but a reliable workhorse that normalizes displacement by realized volatility.
The indicator shares conceptual DNA with z-scores and Bollinger %B but makes a different normalization choice. Bollinger uses standard deviation, which treats upside and downside gaps identically and ignores the previous close. PGO uses ATR, which incorporates the gap between the previous close and the current bar's range. In markets where overnight gaps represent real risk (futures, individual stocks), this distinction matters.
PGO answers a simple question: "How far is price from its average, measured in units of typical bar-to-bar movement?" A reading of +3.0 means price has moved three typical ranges above its mean. This is statistically unusual regardless of whether the instrument is a penny stock or a major index, which is the indicator's principal advantage over raw price-minus-average oscillators.
The ATR denominator automatically adjusts for volatility regime changes. During quiet periods, ATR contracts and the same price displacement produces a larger PGO reading, flagging the move as statistically significant. During volatile periods, ATR expands and price needs to move further from the mean to register the same PGO level. This self-calibration means fixed overbought/oversold thresholds (±3.0) are more stable across market conditions than thresholds on non-normalized oscillators.
The [`RingBuffer`](lib/oscillators/pgo/Pgo.cs:26) maintains a circular buffer of close prices. [`SmaSum`](lib/oscillators/pgo/Pgo.cs:29) tracks the running total with O(1) incremental updates: subtract the oldest value, add the new value.
True Range is computed from High, Low, Close, and previous Close using three comparisons. The EMA of TR uses [`Math.FusedMultiplyAdd`](lib/oscillators/pgo/Pgo.cs:158) for the `alpha * (tr - ema) + ema` pattern. During warmup, the geometric decay factor $e = e \times (1 - \alpha)$ provides bias correction.
Both SMA lookback and ATR smoothing use the same `period`, which means a single parameter controls the entire indicator. This simplifies optimization and reduces overfitting risk compared to dual-parameter designs.
[`Update(TBar)`](lib/oscillators/pgo/Pgo.cs:115) provides full OHLC context for proper True Range computation. [`Update(TValue)`](lib/oscillators/pgo/Pgo.cs:176) creates a synthetic bar with H=L=C=value, producing TR=0, which is documented as suboptimal.
- **Bullish divergence**: Price makes lower lows while PGO makes higher lows; ATR-normalized displacement is contracting despite new price lows.
- **Bearish divergence**: Price makes higher highs while PGO makes lower highs; displacement relative to volatility is shrinking.
- **Extreme reversion**: PGO beyond ±3.0 reverting toward zero suggests mean reversion is underway.
### Practical Notes
- The ±3.0 thresholds are guidelines, not hard rules. Fat-tailed distributions (common in finance) produce more extreme readings than Gaussian models suggest.
- In strong trends, PGO may persist at ±2 to ±4 for extended periods. This confirms trend strength rather than signaling imminent reversal.
- PGO requires proper OHLC data for meaningful ATR computation. Feeding close-only data (H=L=C) produces TR=0 and a meaningless oscillator.
## Related Indicators
- [**Bollinger %B**](../bbb/Bbb.md): Normalizes price position by standard deviation (Bollinger Bands) rather than ATR.
- [**CFO**](../cfo/Cfo.md): Normalizes regression residual by price level rather than volatility.
- [**Williams %R**](../willr/Willr.md): Normalizes price position within the high-low range, bounded to [-100, 0].
1.**Using without OHLC data.** PGO requires High/Low/Close for True Range. Feeding close-only data produces TR=0, making ATR=0 and PGO=0 (worthless). Always provide full bar data.
2.**Fixed thresholds on fat-tailed data.** The ±3.0 levels assume approximately normal displacement. Financial returns have fat tails; PGO can reach ±5 or beyond during tail events. Calibrate thresholds per instrument.
3.**Fading strong trends.** PGO at +3.0 in a strong uptrend is a sign of strength, not an automatic sell signal. Combine with trend direction indicators before trading mean reversion.
4.**Comparing across different periods.** PGO(14) and PGO(50) are not directly comparable. Longer periods produce smoother, smaller-magnitude readings.
5.**Ignoring SMA lag.** SMA introduces (N-1)/2 bars of lag. In fast-moving markets, the SMA component drags the PGO reading, making it slow to register sharp reversals.
6.**Assuming ATR is volatility.** ATR measures range, not statistical volatility. It can underestimate risk in markets with large intrabar reversals that end near unchanged.
## FAQ
**Q: Why use ATR instead of standard deviation for normalization?**
A: ATR captures gap risk (via True Range's inclusion of the previous close) that standard deviation misses. In overnight-gap-prone instruments, ATR gives a more complete picture of actual price movement than close-to-close standard deviation.
**Q: What happens when I feed TValue instead of TBar?**
A: The `Update(TValue)` overload creates a synthetic bar with H=L=C=value, which produces TR=0. This means ATR decays toward zero and PGO becomes unreliable. Always prefer the `Update(TBar)` overload for meaningful results.
**Q: How does PGO compare to Bollinger %B?**
A: Both normalize price displacement against a volatility measure. Bollinger %B uses standard deviation (Gaussian assumption, ignores gaps) and outputs [0, 1] typically. PGO uses ATR (captures gaps) and outputs unbounded ATR multiples. PGO is more suitable for gapping instruments; %B is more suitable when you want a bounded indicator.