Forecast Oscillator (FOSC)
period = 5 Overview
The Forecast Oscillator reveals whether prices trade above or below their statistically projected values by calculating the percentage deviation between actual price and a linear regression forecast. Using least squares regression over a rolling window, the indicator projects where price should theoretically reach based on recent trend, then measures how reality diverges from this mathematical expectation. When FOSC reads positive, price exceeds its forecast, suggesting bullish momentum pushing beyond statistical norms, while negative readings indicate price falling short of projected levels, signaling bearish pressure. Traders monitor zero line crossings as momentum shifts, with moves from negative to positive marking acceleration phases where price begins outperforming its trend forecast. The oscillator excels at identifying overbought and oversold conditions through extreme readings that suggest unsustainable deviations from the regression trend. Unlike traditional oscillators that compare price to its own history, FOSC uniquely compares price to its mathematical forecast, providing a forward looking perspective on whether current movement aligns with or diverges from statistical expectations.
Implementation Examples
Compute FOSC from a price slice or candles:
use vectorta::indicators::fosc::{fosc, FoscInput, FoscParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From a price slice
let prices = vec![100.0, 101.0, 102.5, 101.8, 103.2, 104.0];
let params = FoscParams { period: Some(5) }; // Default period = 5
let input = FoscInput::from_slice(&prices, params);
let result = fosc(&input)?;
// From candles with defaults (source = "close", period = 5)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = FoscInput::with_default_candles(&candles);
let result = fosc(&input)?;
// Access values
for value in result.values {
println!("FOSC: {}", value);
} API Reference
Input Methods ▼
// From price slice
FoscInput::from_slice(&[f64], FoscParams) -> FoscInput
// From candles with custom source
FoscInput::from_candles(&Candles, &str, FoscParams) -> FoscInput
// From candles with default params (close, period=5)
FoscInput::with_default_candles(&Candles) -> FoscInput Parameters Structure ▼
#[cfg_attr(feature = "wasm", derive(Serialize, Deserialize))]
pub struct FoscParams {
pub period: Option<usize>, // Default: 5
} Output Structure ▼
pub struct FoscOutput {
pub values: Vec<f64>, // FOSC values (% difference vs. TSF)
} Validation, Warmup & NaNs ▼
period > 0andperiod ≤ len; otherwiseFoscError::InvalidPeriod.- Requires at least
periodvalid points after the first finite value; otherwiseFoscError::NotEnoughValidData. - Warmup: indices before
first + period − 1areNaN. First output appears atfirst + period − 1. fosc_into_slice: destination slice length must equal input length; elseFoscError::OutputSliceWrongLen.- Division by zero: if
Pt == 0.0, output isNaN. InteriorNaNs propagate.
Error Handling ▼
use vectorta::indicators::fosc::{fosc, FoscError};
match fosc(&input) {
Ok(output) => handle(output.values),
Err(FoscError::EmptyInputData) => eprintln!("Input data is empty"),
Err(FoscError::AllValuesNaN) => eprintln!("All values are NaN"),
Err(FoscError::InvalidPeriod { period, data_len }) =>
eprintln!("Invalid period {} for data length {}", period, data_len),
Err(FoscError::NotEnoughValidData { needed, valid }) =>
eprintln!("Need {} points, only {} valid after first finite", needed, valid),
Err(FoscError::OutputSliceWrongLen { dst_len, data_len }) =>
eprintln!("dst len {} must equal data len {}", dst_len, data_len),
Err(FoscError::InvalidKernelForBatch) =>
eprintln!("Invalid kernel used for batch operation"),
} Python Bindings
Basic Usage ▾
Work with NumPy arrays and optional kernel selection:
import numpy as np
from vectorta import fosc
prices = np.asarray([100.0, 101.0, 102.5, 101.8, 103.2], dtype=np.float64)
values = fosc(prices, period=5)
values = fosc(prices, period=5, kernel="scalar") # or "auto", "avx2", "avx512" (if available) Streaming Updates ▾
Maintain rolling FOSC values via the PyO3-backed stream:
from vectorta import FoscStream
stream = FoscStream(period=5)
for p in price_feed():
val = stream.update(p)
if val is not None:
handle(val) Batch Sweeps ▾
Evaluate multiple periods without leaving Python:
from vectorta import fosc_batch
import numpy as np
prices = np.asarray(load_prices(), dtype=np.float64)
result = fosc_batch(prices, period_range=(5, 25, 5), kernel="auto")
values = result["values"] # shape: [rows, len(prices)]
periods = result["periods"] # list of periods used CUDA Acceleration ▾
CUDA support for FOSC is currently under development. The API will mirror other CUDA-enabled indicators once released.
# Coming soon: CUDA-accelerated FOSC calculations
# from vectorta import fosc_cuda_batch
# results = fosc_cuda_batch(prices, period_range=(5, 50, 1), device_id=0) JavaScript / WASM
Basic Usage ▾
Call FOSC directly from TypeScript or JavaScript with Float64Array:
import { fosc_js } from 'vectorta-wasm';
const prices = new Float64Array([100, 101, 102.5, 101.8, 103.2]);
const values = fosc_js(prices, 5);
console.log('FOSC values', Array.from(values)); Memory-Efficient Operations ▾
Reuse WASM buffers and compute in place:
import { fosc_alloc, fosc_free, fosc_into, memory } from 'vectorta-wasm';
const prices = new Float64Array(loadPrices());
const len = prices.length;
const inPtr = fosc_alloc(len);
const outPtr = fosc_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(prices);
// args: in_ptr, out_ptr, len, period
fosc_into(inPtr, outPtr, len, 5);
const values = new Float64Array(memory.buffer, outPtr, len).slice();
fosc_free(inPtr, len);
fosc_free(outPtr, len); Batch Processing ▾
Sweep period ranges and get a structured result:
import { fosc_batch_js } from 'vectorta-wasm';
const prices = new Float64Array(loadPrices());
const out = fosc_batch_js(prices, { period_range: [5, 25, 5] });
// out has: { values: Float64Array, combos: [{ period }...], rows, cols }
const rows = out.rows;
const cols = out.cols;
const matrix = [] as number[][];
for (let r = 0; r < rows; r++) {
const start = r * cols;
matrix.push(Array.from(out.values.slice(start, start + cols)));
}
// Access metadata
console.log(out.combos.map(c => c.period)); Performance Analysis
Across sizes, Rust CPU runs about 1.21× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
CUDA note
In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.