PPO (Percentage Price Oscillator) measures the percentage difference between a fast EMA and a slow EMA. It is functionally equivalent to MACD normalized by the slow EMA, producing values that are comparable across instruments with different price levels. The implementation outputs three components: the PPO line, a signal line (EMA of PPO), and a histogram (PPO minus Signal).
## Historical Context
PPO emerged as a direct answer to MACD's most significant architectural limitation: scale dependency. Gerald Appel's MACD (1979) reports the absolute spread between two EMAs, meaning a MACD value of 2.0 on a \$200 stock represents a 1% divergence, while the same value on a \$20 stock represents 10%. PPO normalizes this by dividing by the slow EMA, producing a percentage that is directly comparable across any price level.
The formula appears in most technical analysis textbooks as the "normalized MACD" or "percentage MACD." StockCharts.com popularized the PPO terminology. The default parameters (12, 26, 9) mirror MACD's defaults, making PPO a drop-in replacement for cross-instrument analysis.
This implementation uses compensated EMAs internally (via the `Ema` class) for improved warmup accuracy, and applies FMA where applicable for performance.
## Architecture & Physics
### 1. Dual EMA Pipeline
Two independent EMA instances process the same input:
The indicator delegates state management to three internal `Ema` instances. The `_state` / `_p_state` pattern handles only the `LastValid` value for NaN sanitization.
| **Total** | **~7 ops** | Plus internal EMA ops |
### Batch Mode (Span-based)
| Operation | Complexity | Notes |
| :--- | :---: | :--- |
| Per-element | O(1) | Fixed operations per bar |
| Total | O(n) | Linear scan |
| Memory | O(1) | Internal EMA state only |
### Quality Metrics
| Metric | Score | Notes |
| :--- | :---: | :--- |
| **Accuracy** | 9/10 | Compensated EMA for warmup precision |
| **Timeliness** | 6/10 | EMA lag from both smoothing stages |
| **Smoothness** | 7/10 | Dual EMA provides good noise rejection |
| **Simplicity** | 7/10 | Straightforward composition of EMAs |
## Validation
| Library | Status | Notes |
| :--- | :---: | :--- |
| **Skender** | ✅ | Matches within 1e-9 tolerance |
| **TA-Lib** | ✅ | PPO function matches |
| **Tulip** | ✅ | PPO matches exactly |
| **Ooples** | ✅ | Matches within 1e-6 tolerance |
## Common Pitfalls
1.**Period ordering**: `fastPeriod` must be strictly less than `slowPeriod`. The constructor enforces this with an `ArgumentException`.
2.**Division by zero**: When SlowEMA is zero (only during initial startup), the PPO defaults to 0.0. This is a transient condition that resolves after the first few bars.
3.**Signal vs PPO**: The histogram (`PPO - Signal`) is the derivative of momentum. Histogram shrinking toward zero indicates momentum deceleration, not necessarily a reversal.
4.**Warmup asymmetry**: The fast EMA becomes hot before the slow EMA. `IsHot` requires both EMAs to be warmed up, which depends on the slow period.
5.**Three outputs**: PPO exposes `Last` (PPO line), `Signal`, and `Histogram` as separate `TValue` properties. Consumers must access the appropriate property for their use case.
6.**MACD equivalence**: PPO zero crossovers occur at exactly the same points as MACD zero crossovers. The only difference is the vertical scale.
## References
- Appel, G. (2005). "Technical Analysis: Power Tools for Active Investors." FT Press.
- Murphy, J. J. (1999). "Technical Analysis of the Financial Markets." New York Institute of Finance.