Chande Forecast Oscillator (CFO)
period = 14 (2–200) | scalar = 100 (10–500) Overview
The Chande Forecast Oscillator (CFO), developed by Tushar Chande, measures the percentage deviation between price and its linear regression forecast. The indicator calculates a least squares regression line over the specified period, then expresses the current price position as a percentage of the forecast value. When prices exceed the regression projection, CFO turns positive to signal upward momentum. Conversely, prices below the projection generate negative readings that indicate downward pressure.
Traders use CFO to identify momentum shifts and potential reversals when price action diverges from its statistical trend. The oscillator typically ranges between negative 50 and positive 50 percent, with readings beyond 50 suggesting strong directional moves. Because CFO normalizes its output as a percentage, you can compare momentum strength across different markets and timeframes without additional scaling. The indicator excels at highlighting when price stretches too far from its regression channel, often signaling mean reversion opportunities.
Implementation Examples
Calculate CFO from slices or pre-loaded candles with default helpers:
use vectorta::indicators::cfo::{cfo, CfoInput, CfoParams};
use vectorta::utilities::data_loader::Candles;
// Closing prices
let closes = vec![100.0, 101.5, 102.8, 101.9, 100.4, 99.8];
let params = CfoParams {
period: Some(20),
scalar: Some(100.0),
};
let input = CfoInput::from_slice(&closes, params);
let output = cfo(&input)?;
// Access the oscillator values
for value in output.values.iter().take(3) {
println!("CFO: {value:.4}");
}
// Candle helper uses close prices with default params (14 / 100)
let candles: Candles = load_candles()?;
let default_input = CfoInput::with_default_candles(&candles);
let default_output = cfo(&default_input)?;
println!("Warmup period: {}", default_input.get_period()); API Reference
Core Types ▾
pub struct CfoParams {
pub period: Option<usize>, // Default: 14
pub scalar: Option<f64>, // Default: 100.0
}
pub struct CfoInput<'a> {
pub data: CfoData<'a>,
pub params: CfoParams,
}
pub enum CfoData<'a> {
Candles { candles: &'a Candles, source: &'a str },
Slice(&'a [f64]),
}
// Helper constructors
CfoInput::from_slice(&[f64], CfoParams) -> CfoInput
CfoInput::from_candles(&Candles, &str, CfoParams) -> CfoInput
CfoInput::with_default_candles(&Candles) -> CfoInput // close, 14/100
pub struct CfoOutput {
pub values: Vec<f64>,
} Builder & Stream Utilities ▾
pub struct CfoBuilder {
period: Option<usize>,
scalar: Option<f64>,
kernel: Kernel,
}
// Fluent configuration
CfoBuilder::new() -> CfoBuilder
CfoBuilder::period(self, usize) -> Self
CfoBuilder::scalar(self, f64) -> Self
CfoBuilder::kernel(self, Kernel) -> Self
CfoBuilder::apply(&Candles) -> Result<CfoOutput, CfoError>
CfoBuilder::apply_slice(&[f64]) -> Result<CfoOutput, CfoError>
CfoBuilder::into_stream() -> Result<CfoStream, CfoError>
pub struct CfoStream {
// Maintains rolling regression state
}
CfoStream::try_new(CfoParams) -> Result<CfoStream, CfoError>
CfoStream::update(&mut self, f64) -> Option<f64>
} Batch APIs ▾
pub struct CfoBatchRange {
pub period: (usize, usize, usize),
pub scalar: (f64, f64, f64),
}
pub struct CfoBatchOutput {
pub values: Vec<f64>, // rows * cols flattened
pub combos: Vec<CfoParams>,
pub rows: usize,
pub cols: usize,
}
// Helpers
CfoBatchOutput::values_for(&CfoParams) -> Option<&[f64]>
CfoBatchOutput::row_for_params(&CfoParams) -> Option<usize>
pub fn cfo_batch_with_kernel(data: &[f64], sweep: &CfoBatchRange, k: Kernel)
-> Result<CfoBatchOutput, CfoError> Error Handling ▾
use vectorta::indicators::cfo::{cfo, CfoError};
match cfo(&input) {
Ok(output) => handle(output.values),
Err(CfoError::NoData) => log::warn!("input series empty"),
Err(CfoError::InvalidPeriod { period, data_len }) => {
panic!("period {period} exceeds data length {data_len}");
}
Err(CfoError::NotEnoughValidData { needed, valid }) => {
println!("Need {needed} clean points, only {valid} supplied");
}
Err(err) => return Err(err.into()),
} Python Bindings
Basic Usage ▾
Operate on NumPy arrays and optionally select CPU kernels:
import numpy as np
from vectorta import cfo
prices = np.array([100.0, 101.3, 103.1, 102.4, 100.6, 99.8], dtype=float)
# Defaults: period=14, scalar=100.0
baseline = cfo(prices)
# Custom configuration with explicit kernel
tuned = cfo(prices, period=20, scalar=120.0, kernel="avx2")
print(tuned[:5]) Streaming Real-time Updates ▾
Use the Python wrapper for the native streaming implementation:
from vectorta import CfoStream
stream = CfoStream(period=14, scalar=100.0)
for bar in price_feed:
value = stream.update(bar.close)
if value is not None:
if value > 25:
print("Overbought stretch")
elif value < -25:
print("Oversold stretch") Batch Parameter Optimization ▾
VectorTA exposes the batch runner through cfo_batch:
import numpy as np
from vectorta import cfo_batch
prices = np.array([...], dtype=float)
period_range = (10, 40, 5)
scalar_range = (80.0, 140.0, 20.0)
result = cfo_batch(
prices,
period_range=period_range,
scalar_range=scalar_range,
kernel="avx512_batch",
)
values = result["values"]
combos = result["combos"]
first_combo = combos[0]
first_series = values[0]
print(f"period={first_combo['period']} scalar={first_combo['scalar']} len={len(first_series)}") Python CUDA Bindings
CUDA acceleration for CFO is planned. The API surface will mirror other GPU-enabled indicators with zero-copy batch helpers.
# Coming soon: CUDA-accelerated CFO helpers
# from vectorta import cfo_cuda_batch, cfo_cuda_many_series_one_param
#
# prices = np.array([...], dtype=np.float32)
# combos = cfo_cuda_batch(
# data=prices,
# period_range=(10, 40, 1),
# scalar_range=(80.0, 140.0, 5.0),
# device_id=0,
# ) JavaScript/WASM Bindings
Basic Usage ▾
Work with Float64Array data directly inside WebAssembly:
import { cfo_js } from 'vectorta-wasm';
const prices = new Float64Array([100, 101.3, 102.4, 101.8, 100.9]);
const values = cfo_js(prices, 20, 100.0);
console.log('CFO length', values.length); Structured Batch API ▾
Use the ergonomic config wrapper that replaces the deprecated helpers:
import { cfo_batch } from 'vectorta-wasm';
const prices = new Float64Array([/* historical closes */]);
const { values, combos, rows, cols } = cfo_batch(prices, {
period_range: [10, 40, 5],
scalar_range: [80.0, 140.0, 20.0],
});
console.log('rows', rows, 'cols', cols);
console.log('first combo period', combos[0].period); Memory-Efficient Operations ▾
Leverage zero-copy routines via explicit WASM allocations:
import { memory, cfo_alloc, cfo_free, cfo_into } from 'vectorta-wasm';
const prices = new Float64Array([/* data */]);
const len = prices.length;
const inputPtr = cfo_alloc(len);
const outputPtr = cfo_alloc(len);
new Float64Array(memory.buffer, inputPtr, len).set(prices);
cfo_into(inputPtr, outputPtr, len, 14, 100.0);
const result = new Float64Array(memory.buffer, outputPtr, len);
console.log(result.slice(0, 5));
cfo_free(inputPtr, len);
cfo_free(outputPtr, len); Low-level Batch Into ▾
Interoperate with pre-allocated shared memory for high-throughput scans:
import { cfo_batch_into, cfo_alloc, cfo_free, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* data */]);
const len = prices.length;
const inputPtr = cfo_alloc(len);
new Float64Array(memory.buffer, inputPtr, len).set(prices);
const combosGuess = 12; // Pre-allocate enough rows for your sweep
const outputPtr = cfo_alloc(len * combosGuess);
const rows = cfo_batch_into(
inputPtr,
outputPtr,
len,
10, 30, 5,
80.0, 160.0, 20.0,
);
const flat = new Float64Array(memory.buffer, outputPtr, rows * len);
cfo_free(inputPtr, len);
cfo_free(outputPtr, rows * len); Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Accumulation/Distribution
Technical analysis indicator
Accumulation/Distribution Oscillator
Technical analysis indicator
Balance of Power
Technical analysis indicator
Elder Force Index
Technical analysis indicator
Ease of Movement
Technical analysis indicator
Klinger Volume Oscillator
Technical analysis indicator