| Output range | Unbounded, centered at zero (typically -10 to +10) |
| Warmup | `period` bars (default: 14) |
### Key takeaways
- CFO measures the percentage difference between the current price and its linear regression forecast (Time Series Forecast), quantifying how far price has deviated from its own trend.
- Primary use: detecting momentum via regression deviation. Positive CFO means price is above where the trend line predicts; negative means it has fallen behind.
- Unlike moving average oscillators (MACD, APO), CFO uses least-squares regression, which is mathematically optimal for fitting a straight line to the lookback window and produces zero output on a perfect linear trend.
- The implementation achieves O(1) per update via incremental sumY/sumXY maintenance, avoiding the O(N) cost of a full regression recalculation each bar.
- When source price is exactly zero, CFO returns NaN to avoid division by zero.
Tushar Chande introduced the Forecast Oscillator in *The New Technical Trader* (1994). Chande's innovation was combining linear regression (traditionally a statistical tool) with the oscillator framework that traders were familiar with. Rather than just plotting the regression line on a chart, he normalized the deviation as a percentage of price, creating a bounded-ish oscillator that is directly comparable across time periods.
The Forecast Oscillator is not widely implemented in standard TA libraries. No direct equivalent exists in TA-Lib, Tulip, Skender, or Ooples. Validation relies on cross-referencing the Time Series Forecast component against QuanTAlib's own `LinReg` class (which computes the same regression) and known mathematical properties (CFO = 0 for a perfect linear trend, CFO = 0 for constant input).
Most textbook implementations compute a full least-squares regression each bar at O(N) cost. QuanTAlib uses an incremental algorithm from the PineScript reference that maintains running `sumY` and `sumXY` accumulators, achieving O(1) per update after warmup. The trade-off is floating-point drift in the running sums, bounded by periodic resynchronization every 1,000 ticks.
CFO answers: "How far is the current price from where linear regression says it should be?" If a stock has been climbing steadily at $1/bar, the regression line predicts the next value at the current level. If actual price is $2 above the line, CFO is positive -- price is running ahead of its fitted trend. If price is $2 below, CFO is negative -- price has fallen behind.
The percentage normalization ($100 \times (P - TSF) / P$) makes CFO somewhat comparable across different price levels, though it is not perfectly bounded. On a $100 stock, a $5 deviation (CFO ≈ 5%) is a moderate signal. On a $10 stock, the same $5 deviation (CFO ≈ 50%) would be extreme. In practice, CFO values between -5 and +5 represent normal fluctuations around the trend; values beyond that range suggest significant deviation.
CFO is most useful for trend-following systems where you want to quantify whether momentum is above or below the statistically best-fit trend. It is more mathematically rigorous than EMA-based oscillators (which use exponential weighting) because ordinary least-squares regression minimizes the sum of squared errors. The cost is that regression is more sensitive to outliers at the window boundary (the oldest point exiting and the newest point entering have outsized influence on the slope).
The Time Series Forecast uses `Math.FusedMultiplyAdd(slope, period - 1, intercept)` to compute `slope × (N-1) + intercept` in a single fused operation.
Floating-point drift accumulates in the running sums. Every 1,000 ticks, `RecalculateSums()` recomputes `SumY` and `SumXY` from the ring buffer. This bounds the maximum accumulated error to approximately 1,000 additions' worth of ULP drift.
- **Zero-line crossover**: CFO crossing from negative to positive signals that price has moved from below to above its regression forecast. This is the primary signal, analogous to a MACD zero-line cross but using regression rather than EMA.
- **Divergence**: Price making new highs while CFO makes lower highs indicates the regression slope is steepening -- the trend is extrapolating further but price is not keeping up with its own acceleration. This suggests the trend may be exhausting.
- **CFO = 0 on a trend**: In a perfectly linear uptrend, CFO remains at zero. Deviations from zero measure how "non-linear" the current move is -- acceleration (CFO > 0) or deceleration (CFO < 0) relative to the fitted line.
### Practical Notes
CFO is best used on daily or weekly charts where price tends to follow more linear trends. On intraday charts, price paths are less linear, producing noisier CFO readings. Pair CFO with a trend indicator (SMA direction, ADX) to filter signals: zero-line crossovers in the direction of the larger trend are more reliable than counter-trend crossovers. The default period of 14 provides a good balance between responsiveness and smoothness; shorter periods amplify noise, longer periods increase lag.
## Related Indicators
- **[DPO](../dpo/Dpo.md)**: Detrended Price Oscillator. DPO removes trend by subtracting a shifted SMA; CFO removes trend by subtracting a regression forecast. Different detrending methods, similar concept.
- **[APO](../apo/Apo.md)**: Absolute Price Oscillator. APO uses EMA difference for momentum; CFO uses regression deviation. CFO is mathematically more rigorous but more computationally complex.
- **[Inertia](../inertia/Inertia.md)**: Uses RVI (Relative Vigor Index) smoothed by linear regression. CFO and Inertia both leverage regression but in different ways.
| **LinReg cross-validation** | ✓ | CFO reconstructed from `LinReg.Last.Value` (TSF), tolerance 1e-6, periods 5/10/14/20/50 |
| **Self-consistency** | ✓ | Batch and span agree to 1e-12; streaming agrees to 1e-4 (drift between resyncs) |
| **Known values** | ✓ | Perfect linear trend yields CFO = 0 to 1e-10 |
| **Multi-period** | ✓ | Different periods produce different results (no parameter aliasing) |
| **TA-Lib** | -- | Not implemented |
| **Tulip** | -- | Not implemented |
The streaming vs batch tolerance is 1e-4 (not 1e-9) because the O(1) incremental sumXY maintenance accumulates floating-point cancellation drift between the 1,000-tick resync intervals. Batch and span use the same code path and agree to 1e-12.
## Performance Profile
### Key Optimizations
- **O(1) incremental regression**: Running `SumY` and `SumXY` avoid O(N) full regression each bar.
- **Precomputed x-axis constants**: `_sumX` and `_denomX` are computed once in the constructor and stored as `readonly` fields.
- **FMA for TSF**: `Math.FusedMultiplyAdd(slope, period - 1, intercept)` eliminates one intermediate rounding step.
- **Resync guard**: Every 1,000 ticks, sums are recalculated from the ring buffer to bound drift.
- **Aggressive inlining**: `Update`, `Handle`, and `Batch(Span)` are decorated with `[MethodImpl(MethodImplOptions.AggressiveInlining)]`.
The three DIVs (slope denominator, intercept mean, CFO percentage) dominate. The incremental update itself (steps 1-4) costs only ~4 ADD/SUBs.
### SIMD Analysis (Batch Mode)
| Operation | Vectorizable? | Reason |
|-----------|:-------------:|--------|
| Incremental sumY/sumXY | No | Sequential ring buffer dependency |
| Regression computation | No | Depends on running sums |
| CFO normalization | No | Depends on per-element regression output |
CFO is fully sequential: the running sums have sequential dependencies, and all subsequent computations depend on them. No SIMD vectorization is possible.
1.**Division by zero at source = 0**: When price is exactly zero, CFO returns NaN. Downstream logic must handle this. For instruments that can trade at zero (rare), filter out NaN values.
2.**Unbounded output**: CFO is expressed as a percentage but is not bounded to [-100, +100]. During volatile periods or with very short lookback periods, values can be extreme. Do not assume fixed overbought/oversold thresholds without calibration.
3.**Warmup is 14 bars with defaults**: Before the ring buffer fills, CFO returns 0.0. This is a sentinel value, not a meaningful reading.
4.**Streaming vs batch drift**: The O(1) incremental maintenance accumulates floating-point drift (~1e-5) between the 1,000-tick resync intervals. Batch computation does not share this drift because it also runs incrementally but resets at the start. Validation tests use 1e-4 tolerance for streaming.
5.**Short periods amplify noise**: Period 1 or 2 produces mathematically valid but extremely noisy output. Regression over 2 points is a line between them -- any deviation is captured as a large percentage. Use period >= 5 for meaningful signals.
6.**Bar correction triggers full recalculation**: `isNew=false` restores the previous state and calls `RecalculateSums()` from the ring buffer, degrading to O(N). In high-frequency scenarios with many corrections per bar, this can be costly.
## FAQ
**Q: How does CFO differ from the R-squared indicator?**
A: Both use linear regression, but they measure different things. CFO measures the percentage deviation of price from the regression line. R-squared measures how well the regression fits the data (1.0 = perfect fit). CFO tells you "how far off the trend are you?" while R-squared tells you "how trending is the data?"
**Q: Can I chain CFO after another indicator?**
A: Yes. Use the chaining constructor: `new Cfo(source, 14)`. CFO subscribes to the source's `Pub` event via the `Handle` method inherited from `AbstractBase`.
**Q: Why does CFO use source price for normalization instead of TSF?**
A: Chande's original definition normalizes by `source`: `100 × (source - TSF) / source`. This ensures that when price and TSF agree, CFO is exactly zero, and the deviation is expressed relative to the current price level.
## References
- Chande, T. (1994). *The New Technical Trader*. Wiley. Forecast Oscillator chapter.
- Chande, T. & Kroll, S. (1994). *The New Technical Trader*. Wiley. Companion strategies and applications.
- [PineScript reference](cfo.pine) -- original O(1) incremental implementation.