mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
252 lines
17 KiB
Markdown
252 lines
17 KiB
Markdown
# PFE: Polarized Fractal Efficiency
|
||
|
||
> *The shortest distance between two points is a straight line. The market never takes the shortest distance. PFE measures how badly it misses.*
|
||
|
||
| Property | Value |
|
||
| ---------------- | -------------------------------- |
|
||
| **Category** | Dynamic |
|
||
| **Inputs** | Source (close) |
|
||
| **Parameters** | `period` (default 10), `smoothPeriod` (default 5) |
|
||
| **Outputs** | Single series (Pfe) |
|
||
| **Output range** | Varies (see docs) |
|
||
| **Warmup** | `period + 1` bars |
|
||
| **PineScript** | [pfe.pine](pfe.pine) |
|
||
|
||
- Polarized Fractal Efficiency (PFE) quantifies trend strength by comparing the Euclidean distance a price series actually travels bar-to-bar against...
|
||
- **Similar:** [ADX](../adx/Adx.md), [VHF](../vhf/Vhf.md) | **Complementary:** Moving average for trend direction | **Trading note:** Polarized Fractal Efficiency; measures how efficiently price travels. ±100 scale.
|
||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||
|
||
Polarized Fractal Efficiency (PFE) quantifies trend strength by comparing the Euclidean distance a price series actually travels bar-to-bar against the straight-line distance between the endpoints over the same window. The ratio, scaled to [-100, +100] and smoothed with an EMA, distinguishes efficient trending motion (values near ±100) from fractal, self-similar noise (values near 0). Created by Hans Hannula and published in *Technical Analysis of Stocks & Commodities* (January 1994), PFE applies fractal geometry to price action without requiring Hurst exponent estimation or rescaled-range analysis. With default parameters (period=10, smooth=5), the indicator needs 11 close values for the first raw reading plus 5 bars of EMA convergence, totaling ~16 bars of warmup. The core loop executes $N$ square roots per bar, making it $O(N)$ per update in streaming mode.
|
||
|
||
## Historical Context
|
||
|
||
Hans Hannula holds a PhD in systems engineering and spent decades mapping chaos theory onto financial markets. His work drew from Benoit Mandelbrot's observation that price series exhibit fractal properties: the statistical character of bar-to-bar moves resembles the statistical character of week-to-week moves. But where Mandelbrot quantified this self-similarity via the Hurst exponent $H$ (a computationally expensive procedure requiring rescaled-range analysis over multiple scales), Hannula wanted a single-scale, single-pass metric that a trader could compute in real time.
|
||
|
||
The insight was geometric, not statistical. Plot price on the Y-axis and time (bar index) on the X-axis with a fixed unit spacing. The path the market traces from bar $t-N$ to bar $t$ is a polygonal chain through $N+1$ points. If the market moves in a perfectly straight line, the chain length equals the endpoint distance. If the market chops back and forth, the chain length far exceeds the endpoint distance. The ratio of endpoint distance to chain length, expressed as a percentage, measures how efficiently the market traverses the price-time plane.
|
||
|
||
Hannula added polarity: when the current close exceeds the close $N$ bars ago, the sign is positive (uptrend efficiency). When below, negative (downtrend efficiency). An EMA smooth removes jitter from the raw ratio.
|
||
|
||
PFE occupies a unique niche. ADX measures trend strength via directional movement ratios but has no geometric interpretation. Choppiness Index (CHOP) uses ATR-to-range ratios on a logarithmic scale. Kaufman's Efficiency Ratio (ER) computes |net change| / sum(|bar changes|), which is PFE's one-dimensional cousin: ER ignores the time axis, treating price movement as a scalar quantity rather than a vector in price-time space. PFE's inclusion of the time dimension via $\sqrt{\Delta p^2 + \Delta t^2}$ Euclidean distances provides a geometrically rigorous efficiency metric that penalizes both price noise and temporal inefficiency.
|
||
|
||
Most implementations across platforms (TradingView, MetaTrader, Amibroker, NinjaTrader) follow Hannula's original formula faithfully. The only variation worth noting is whether the EMA uses standard initialization (first value as seed) or compensated warmup. This implementation uses exponential warmup compensation for faster convergence during the initial bars.
|
||
|
||
## Architecture and Physics
|
||
|
||
### 1. Euclidean Distance Engine
|
||
|
||
PFE operates in a two-dimensional price-time plane where:
|
||
- The X-axis represents time in discrete bar units (spacing = 1)
|
||
- The Y-axis represents price (close values)
|
||
|
||
The straight-line distance between the current bar and the bar $N$ periods ago uses the standard Euclidean metric:
|
||
|
||
$$
|
||
D_{\text{straight}} = \sqrt{(C_t - C_{t-N})^2 + N^2}
|
||
$$
|
||
|
||
where $C_t$ is the close at bar $t$ and $N$ is the period. The $N^2$ term accounts for the horizontal displacement in the time dimension. Without it, the formula would reduce to $|C_t - C_{t-N}|$, losing all geometric content.
|
||
|
||
### 2. Fractal Path Accumulator
|
||
|
||
The fractal (polygonal chain) path sums the Euclidean distances between consecutive bars:
|
||
|
||
$$
|
||
D_{\text{fractal}} = \sum_{i=0}^{N-1} \sqrt{(C_{t-i} - C_{t-i-1})^2 + 1}
|
||
$$
|
||
|
||
Each segment has a horizontal displacement of 1 bar and a vertical displacement equal to the bar-to-bar price change. The minimum possible segment length is 1.0 (when consecutive closes are identical), ensuring $D_{\text{fractal}} \geq N$.
|
||
|
||
The fractal path must always exceed or equal the straight-line distance (triangle inequality). Equality occurs only when all intermediate points are collinear, meaning the price moved in a perfectly straight line.
|
||
|
||
### 3. Sign Determination
|
||
|
||
The raw efficiency ratio is unsigned. Polarity encodes trend direction:
|
||
|
||
$$
|
||
\text{sign} = \begin{cases}
|
||
+1 & \text{if } C_t \geq C_{t-N} \\
|
||
-1 & \text{if } C_t < C_{t-N}
|
||
\end{cases}
|
||
$$
|
||
|
||
This maps upward-efficient motion to positive values and downward-efficient motion to negative values. A flat market (close unchanged over $N$ bars) yields a positive sign by convention, though the efficiency value itself will be low because the fractal path still accumulates bar-to-bar noise.
|
||
|
||
### 4. EMA Smoother
|
||
|
||
The raw PFE signal contains bar-to-bar jitter as the lookback window slides. Hannula prescribed EMA smoothing with a default period of 5:
|
||
|
||
$$
|
||
\text{EMA}_t = \alpha \cdot \text{PFE}_{\text{raw},t} + (1 - \alpha) \cdot \text{EMA}_{t-1}
|
||
$$
|
||
|
||
where $\alpha = \frac{2}{M + 1}$ and $M$ is the smoothing period. The EMA has infinite impulse response with group delay approximately $(M-1)/2$ bars. For $M = 5$, group delay is ~2 bars.
|
||
|
||
This implementation uses exponential warmup compensation: during the initial bars, the EMA output is divided by $(1 - \beta^n)$ where $\beta = 1 - \alpha$ and $n$ is the bar count. This eliminates the initialization bias that occurs when seeding with the first raw PFE value.
|
||
|
||
### 5. Complexity
|
||
|
||
- **Time:** $O(N)$ per bar for the fractal path summation ($N$ square roots). The straight-line distance is $O(1)$. The EMA is $O(1)$.
|
||
- **Space:** $O(N)$ for the close value circular buffer (size $N+1$) plus $O(1)$ for EMA state.
|
||
- **Warmup:** $N+1$ bars for the first raw PFE value (need $C_{t-N}$). Full EMA convergence requires approximately $3M$ additional bars. Total effective warmup: $N + 3M$ bars.
|
||
- **State footprint:** One circular buffer of $N+1$ doubles, one double for EMA state, one double for exponential decay tracker.
|
||
|
||
## Mathematical Foundation
|
||
|
||
### Raw PFE Derivation
|
||
|
||
Given a price series $\{C_0, C_1, \ldots, C_t\}$, the PFE at bar $t$ with period $N$ is:
|
||
|
||
$$
|
||
\text{PFE}_{\text{raw}}(t) = \text{sgn}(C_t - C_{t-N}) \times \frac{D_{\text{straight}}}{D_{\text{fractal}}} \times 100
|
||
$$
|
||
|
||
Expanding:
|
||
|
||
$$
|
||
\text{PFE}_{\text{raw}}(t) = \text{sgn}(C_t - C_{t-N}) \times \frac{\sqrt{(C_t - C_{t-N})^2 + N^2}}{\sum_{i=0}^{N-1} \sqrt{(C_{t-i} - C_{t-i-1})^2 + 1}} \times 100
|
||
$$
|
||
|
||
### Bounds Analysis
|
||
|
||
**Upper bound:** When price moves in a perfect straight line (all intermediate points collinear), $D_{\text{fractal}} = D_{\text{straight}}$, so $|\text{PFE}| = 100$.
|
||
|
||
**Lower bound:** Consider a flat market where $C_t = C_{t-N}$ but intermediate bars oscillate. Then $D_{\text{straight}} = \sqrt{0 + N^2} = N$ and $D_{\text{fractal}} = \sum \sqrt{\Delta p_i^2 + 1} > N$. The ratio approaches $N / D_{\text{fractal}} \times 100$, which can approach 0 as oscillation amplitude increases but never reaches exactly 0 (because $D_{\text{straight}} = N > 0$).
|
||
|
||
In practice, PFE values rarely exceed ±80 for typical equity data and rarely fall below ±10 except during sustained sideways periods.
|
||
|
||
### Relationship to Efficiency Ratio (ER)
|
||
|
||
Kaufman's Efficiency Ratio is PFE's one-dimensional projection:
|
||
|
||
$$
|
||
\text{ER}(t) = \frac{|C_t - C_{t-N}|}{\sum_{i=0}^{N-1} |C_{t-i} - C_{t-i-1}|}
|
||
$$
|
||
|
||
PFE adds the time dimension via Pythagorean extension:
|
||
|
||
$$
|
||
\text{PFE} \approx \text{sgn} \times \frac{\sqrt{\text{ER}_{\text{num}}^2 + N^2}}{\sum \sqrt{|\Delta C_i|^2 + 1}} \times 100
|
||
$$
|
||
|
||
When bar-to-bar price changes are large relative to 1.0, PFE and ER converge. When price changes are small (sub-unit), PFE's time component dominates and the indicator becomes less sensitive to small wiggles, acting as an implicit noise filter.
|
||
|
||
### Fractal Dimension Connection
|
||
|
||
For a self-similar curve, the fractal dimension $D$ relates path length to measurement scale $\epsilon$ via:
|
||
|
||
$$
|
||
L(\epsilon) \propto \epsilon^{1-D}
|
||
$$
|
||
|
||
PFE implicitly measures at two scales: the coarse scale ($N$ bars) and the fine scale (1 bar). The efficiency ratio $D_{\text{straight}} / D_{\text{fractal}}$ is related to the fractal dimension by:
|
||
|
||
$$
|
||
\frac{D_{\text{straight}}}{D_{\text{fractal}}} \approx N^{1-D}
|
||
$$
|
||
|
||
For $D = 1$ (smooth curve), the ratio is 1 (PFE = ±100). For $D = 2$ (space-filling curve), the ratio decreases toward $1/N$ (PFE approaches ±$100/N$). Typical equity data exhibits $D \approx 1.3\text{-}1.5$ in ranging markets and $D \approx 1.0\text{-}1.2$ during strong trends.
|
||
|
||
### Parameter Mapping
|
||
|
||
| Symbol | Parameter | Default | Constraint |
|
||
|--------|-----------|---------|------------|
|
||
| $N$ | period | 10 | $N \geq 2$ |
|
||
| $M$ | smoothPeriod | 5 | $M \geq 1$ |
|
||
| $\alpha$ | EMA factor | $2/(M+1)$ | Derived |
|
||
|
||
| Period | Fractal Window | EMA Lag | Sensitivity | Best For |
|
||
|--------|---------------|---------|-------------|----------|
|
||
| 5 | Tight | ~2 bars | High | Scalping, intraday |
|
||
| 10 | Standard | ~2 bars | Medium | Swing trading |
|
||
| 20 | Wide | ~2 bars | Low | Position trading |
|
||
| 40 | Very wide | ~2 bars | Very low | Long-term trend analysis |
|
||
|
||
Increasing $N$ smooths the raw PFE naturally (longer path windows average out noise) but increases warmup time and lag. Increasing $M$ smooths the output but adds EMA lag on top of the geometric lag.
|
||
|
||
## Performance Profile
|
||
|
||
### Operation Count (Streaming Mode, Scalar)
|
||
|
||
Per-bar operations with circular buffer for close history:
|
||
|
||
| Operation | Count | Cost (cycles) | Subtotal |
|
||
|:----------|:-----:|:-------------:|:--------:|
|
||
| SQRT (fractal path segments) | $N$ | 15 | $15N$ |
|
||
| SQRT (straight-line distance) | 1 | 15 | 15 |
|
||
| MUL (squared differences) | $N + 1$ | 3 | $3(N+1)$ |
|
||
| ADD/SUB (differences, accumulation) | $2N + 3$ | 1 | $2N + 3$ |
|
||
| DIV (efficiency ratio) | 1 | 15 | 15 |
|
||
| FMA (EMA update) | 1 | 4 | 4 |
|
||
| CMP (sign determination) | 1 | 1 | 1 |
|
||
| **Total ($N = 10$)** | **~35** | | **~191 cycles** |
|
||
|
||
### Batch Mode (SIMD Analysis)
|
||
|
||
| Operation | Vectorizable? | Notes |
|
||
|:----------|:-------------:|:------|
|
||
| Bar-to-bar $\Delta p$ computation | Yes | Independent differences, SIMD-friendly |
|
||
| $\Delta p^2 + 1$ per segment | Yes | Vectorized FMA |
|
||
| SQRT per segment | Yes | `Avx2` VSQRTPD (4 doubles/op) |
|
||
| Fractal path sum | Partial | Horizontal reduction after vectorized sqrt |
|
||
| Straight-line distance | Yes | Single SQRT |
|
||
| Sign determination | Yes | Conditional select |
|
||
| EMA smoothing | No | Sequential state dependency |
|
||
|
||
For the `Calculate(Span)` path, the $N$ square roots per bar dominate. With AVX2, 4 square roots execute per VSQRTPD instruction, reducing the $N$-sqrt loop from $N$ to $\lceil N/4 \rceil$ SIMD operations. For $N = 10$, that is 3 SIMD instructions instead of 10 scalar, a ~3× speedup on the hot loop.
|
||
|
||
The EMA pass is inherently sequential, limiting end-to-end SIMD benefit, but it is $O(1)$ per bar and does not dominate.
|
||
|
||
### Quality Metrics
|
||
|
||
| Metric | Score | Notes |
|
||
|:-------|:-----:|:------|
|
||
| **Accuracy** | 9/10 | Exact Euclidean geometry, no approximations |
|
||
| **Timeliness** | 6/10 | $N$-bar lookback + EMA lag; responds to new trends only after $N$ bars of directional movement |
|
||
| **Smoothness** | 7/10 | EMA removes jitter; raw PFE can be noisy at small $N$ |
|
||
| **Noise Rejection** | 7/10 | Time dimension provides implicit filtering of sub-unit price noise |
|
||
| **Interpretability** | 8/10 | ±100 = strong trend, 0 = choppy; intuitive geometric meaning |
|
||
|
||
## Validation
|
||
|
||
| Library | Status | Notes |
|
||
|:--------|:------:|:------|
|
||
| **TA-Lib** | N/A | Not implemented in TA-Lib |
|
||
| **Skender** | Pending | `Pfe` available in Skender.Stock.Indicators |
|
||
| **Tulip** | N/A | Not implemented in Tulip Indicators |
|
||
| **OoplesFinance** | Pending | Available as `PolarizedFractalEfficiency` |
|
||
| **TradingView** | Reference | Built-in `ta.pfe()` function; community scripts available |
|
||
| **MetaTrader** | Reference | Multiple community implementations; formula matches Hannula original |
|
||
| **NinjaTrader** | Reference | Built-in PFE indicator; default period=10, smooth=5 |
|
||
|
||
Key validation points:
|
||
|
||
- For a perfectly linear price series (constant increment per bar), PFE should approach ±100
|
||
- For a symmetric oscillating series (e.g., sinusoidal), PFE should hover near 0
|
||
- The absolute value of raw PFE must never exceed 100 (geometric constraint)
|
||
- $D_{\text{fractal}} \geq D_{\text{straight}}$ must hold for every bar (triangle inequality)
|
||
- With $N = 2$, the fractal path has only 2 segments; PFE reduces to a basic 2-bar efficiency metric
|
||
- Warmup: first $N$ bars produce NaN; EMA convergence adds $\sim 3M$ bars of bias
|
||
|
||
## Common Pitfalls
|
||
|
||
1. **Forgetting the time dimension.** The vertical-only variant ($\sqrt{\Delta p^2}$ instead of $\sqrt{\Delta p^2 + 1}$) collapses PFE into a signed version of Kaufman's Efficiency Ratio. The +1 under each segment's square root is not optional; it encodes the one-bar horizontal displacement that gives PFE its fractal-geometric interpretation. Dropping it changes the indicator's sensitivity profile by 15-30% for typical equity data where bar-to-bar changes are small relative to 1.0.
|
||
|
||
2. **Using $N^2$ in the fractal path instead of the straight-line distance.** Some implementations accidentally add $N^2$ to each segment rather than just the endpoint calculation. The straight-line formula is $\sqrt{\Delta p^2 + N^2}$; each segment formula is $\sqrt{\Delta p_i^2 + 1^2}$. Mixing up the $N$ and the $1$ produces nonsensical values.
|
||
|
||
3. **Sign inversion.** Hannula defined positive PFE as uptrend-efficient (close > close[N]) and negative as downtrend-efficient (close < close[N]). Some implementations reverse this convention. Consuming code that expects positive = bullish will generate inverted signals if the convention is wrong. Impact: 100% signal inversion.
|
||
|
||
4. **Skipping EMA smoothing.** Raw PFE is noisy because sliding the $N$-bar window by one bar replaces one segment in the fractal path and shifts both endpoints. The EMA is not cosmetic; without it, bar-to-bar PFE changes can swing 20-40 points, making threshold-based signals unreliable. Signal quality degrades by roughly 2-3× in backtesting metrics.
|
||
|
||
5. **Expecting PFE to reach exactly ±100.** The theoretical maximum requires a perfectly linear price trajectory over the full lookback window. Real markets never achieve this. In practice, peak PFE values for strongly trending equities are ±70 to ±85. Setting thresholds at ±100 means the signal never fires. Use ±50 for moderate trend detection and ±30 for loose detection.
|
||
|
||
6. **Scaling issues with different price magnitudes.** PFE's Euclidean distance treats one bar of time as equivalent to one unit of price. For a stock at $500 with typical $5 daily moves, the price component dominates ($\sqrt{25 + 1} \approx 5.1$). For a stock at $5 with $0.05 moves, time dominates ($\sqrt{0.0025 + 1} \approx 1.001$). PFE is not price-scale invariant. This rarely matters in practice (the ratio normalizes much of the scale), but extreme price levels can shift the sensitivity slightly.
|
||
|
||
7. **Confusing PFE output range with ADX.** ADX ranges from 0 to 100 (unsigned). PFE ranges from -100 to +100 (signed). Treating PFE like ADX (taking the absolute value) discards the directional information that distinguishes PFE from other trend-strength indicators. The sign carries half the signal.
|
||
|
||
## References
|
||
|
||
- Hannula, Hans. "Polarized Fractal Efficiency." *Technical Analysis of Stocks & Commodities*, V12:1, January 1994.
|
||
- Mandelbrot, Benoit. "The Variation of Certain Speculative Prices." *The Journal of Business*, Vol. 36, No. 4, October 1963.
|
||
- Kaufman, Perry. *Trading Systems and Methods*, 5th Edition. Wiley, 2013. (Efficiency Ratio comparison)
|
||
- Hannula, Hans. "Chaos and the Stock Market." *Cycles Magazine*, 1993.
|
||
- PineScript reference: `pfe.pine` in indicator directory. |