Fractal Adaptive Moving Average (FRAMA)
window = 10 | sc = 300 | fc = 1 Overview
The Fractal Adaptive Moving Average dynamically adjusts its smoothing factor by measuring the fractal dimension of price movement, becoming more responsive during trending periods and more stable during consolidations. FRAMA calculates the roughness of price action using fractal geometry concepts, where smooth trends exhibit lower fractal dimensions near 1 while choppy markets approach dimension 2, then uses this measurement to modify the exponential smoothing constant in real time. During strong directional moves, the fractal dimension drops and FRAMA tracks price closely like a fast moving average, capturing trend momentum without significant lag. Conversely, when markets enter sideways phases with high fractal dimension, FRAMA automatically increases smoothing to filter out noise, behaving more like a slow moving average to avoid whipsaws. Traders value FRAMA for its intelligent adaptation that eliminates the compromise between responsiveness and stability inherent in fixed period averages. The indicator particularly excels in volatile markets where it maintains trend following capabilities while automatically adjusting to reduce false signals during choppy consolidations that plague traditional moving averages.
Implementation Examples
Compute FRAMA from slices or candles:
use vectorta::indicators::moving_averages::frama::{frama, FramaInput, FramaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using explicit high/low/close slices
let high = vec![/* ... */];
let low = vec![/* ... */];
let close = vec![/* ... */];
let params = FramaParams { window: Some(10), sc: Some(300), fc: Some(1) };
let input = FramaInput::from_slices(&high, &low, &close, params);
let result = frama(&input)?;
// Using Candles with default parameters (window=10, sc=300, fc=1)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = FramaInput::with_default_candles(&candles);
let result = frama(&input)?;
// Access the FRAMA values
for value in result.values {
println!("FRAMA: {}", value);
} API Reference
Input Methods ▼
// From high/low/close slices
FramaInput::from_slices(&[f64], &[f64], &[f64], FramaParams) -> FramaInput
// From candles (uses high/low/close; close as seed source)
FramaInput::from_candles(&Candles, FramaParams) -> FramaInput
// Candles with default params (window=10, sc=300, fc=1)
FramaInput::with_default_candles(&Candles) -> FramaInput Parameters Structure ▼
pub struct FramaParams {
pub window: Option<usize>, // Default: 10 (odd rounded up to even)
pub sc: Option<usize>, // Default: 300
pub fc: Option<usize>, // Default: 1
} Output Structure ▼
pub struct FramaOutput {
pub values: Vec<f64>, // FRAMA values (seeded SMA then adaptive updates)
} Validation, Warmup & NaNs ▼
window > 0; oddwindowis evenized internally for computation.- Uses first index where
high,low, andcloseare all finite; prior outputs areNaN. - Requires at least
even(window)valid points after the first finite triple; elseFramaError::NotEnoughValidData. - Seed at
warm = first + even(window) - 1is the SMA ofcloseover the evenized window. - After warmup, if any of
high/low/closeattisNaN, FRAMA holds previous value (no NaN propagation).
Error Handling ▼
#[derive(Debug, Error)]
pub enum FramaError {
#[error("frama: Input data slice is empty.")] EmptyInputData,
#[error("frama: Mismatched slice lengths: high={high}, low={low}, close={close}")]
MismatchedInputLength { high: usize, low: usize, close: usize },
#[error("frama: All values are NaN.")] AllValuesNaN,
#[error("frama: Invalid window: window = {window}, data length = {data_len}")]
InvalidWindow { window: usize, data_len: usize },
#[error("frama: Not enough valid data: needed = {needed}, valid = {valid}")]
NotEnoughValidData { needed: usize, valid: usize },
}
// Example usage
match frama(&input) {
Ok(out) => println!("{} values", out.values.len()),
Err(FramaError::InvalidWindow { window, data_len }) =>
eprintln!("Invalid window={} for len={}", window, data_len),
Err(FramaError::NotEnoughValidData { needed, valid }) =>
eprintln!("Need {} valid points, only {}", needed, valid),
Err(e) => eprintln!("frama error: {}", e),
} Python Bindings
Basic Usage ▼
Compute FRAMA from NumPy arrays (H/L/C):
import numpy as np
from vectorta import frama
high = np.array([...], dtype=np.float64)
low = np.array([...], dtype=np.float64)
close = np.array([...], dtype=np.float64)
# Defaults: window=10, sc=300, fc=1
vals = frama(high, low, close, window=10, sc=300, fc=1, kernel=None)
print(vals) Streaming Real-time Updates ▼
Feed high/low/close triples per tick:
from vectorta import FramaStream
stream = FramaStream(window=10, sc=300, fc=1)
for h, l, c in realtime_hlc:
v = stream.update(h, l, c)
if v is not None:
print('FRAMA:', v) Batch Parameter Sweep ▼
Sweep window/sc/fc combinations:
import numpy as np
from vectorta import frama_batch
high = np.array([...])
low = np.array([...])
close = np.array([...])
res = frama_batch(
high, low, close,
window_range=(10, 32, 2),
sc_range=(300, 300, 0),
fc_range=(1, 1, 0),
kernel='auto'
)
# res contains: values (2D flat), windows, scs, fcs
print(res['values'].shape) CUDA Acceleration ▼
import numpy as np
from vectorta import frama_cuda_batch_dev, frama_cuda_many_series_one_param_dev
# One series, many params (F32 arrays expected)
h = np.asarray(high, dtype=np.float32)
l = np.asarray(low, dtype=np.float32)
c = np.asarray(close, dtype=np.float32)
(device_out, combos) = frama_cuda_batch_dev(
h, l, c,
window_range=(10, 32, 2),
sc_range=(300, 300, 0),
fc_range=(1, 1, 0),
device_id=0
)
# Many series (time-major), one parameter set
tm_h, tm_l, tm_c = H_tm_f32, L_tm_f32, C_tm_f32 # shape [T, N]
dev_out = frama_cuda_many_series_one_param_dev(
tm_h, tm_l, tm_c,
window=10, sc=300, fc=1,
device_id=0
) JavaScript/WASM Bindings
Basic Usage ▼
Compute FRAMA in JavaScript/TypeScript:
import { frama_js } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);
const values = frama_js(high, low, close, 10, 300, 1);
console.log('FRAMA values:', values); Memory-Efficient Operations ▼
Use zero-copy operations for large arrays:
import { frama_alloc, frama_free, frama_into, memory } from 'vectorta-wasm';
const len = 10_000;
const h = new Float64Array(len);
const l = new Float64Array(len);
const c = new Float64Array(len);
const hPtr = frama_alloc(len);
const lPtr = frama_alloc(len);
const cPtr = frama_alloc(len);
const outPtr = frama_alloc(len);
new Float64Array(memory.buffer, hPtr, len).set(h);
new Float64Array(memory.buffer, lPtr, len).set(l);
new Float64Array(memory.buffer, cPtr, len).set(c);
// Args: high_ptr, low_ptr, close_ptr, out_ptr, len, window, sc, fc
frama_into(hPtr, lPtr, cPtr, outPtr, len, 10, 300, 1);
const out = new Float64Array(memory.buffer, outPtr, len).slice();
frama_free(hPtr, len);
frama_free(lPtr, len);
frama_free(cPtr, len);
frama_free(outPtr, len); Batch Processing ▼
Test multiple parameter combinations:
import { frama_batch_js, frama_batch_metadata_js } from 'vectorta-wasm';
const h = new Float64Array([/* ... */]);
const l = new Float64Array([/* ... */]);
const c = new Float64Array([/* ... */]);
// Define parameter sweep ranges
const w0=10, w1=32, ws=2;
const s0=300, s1=300, ss=0;
const f0=1, f1=1, fs=0;
// Get flattened [window, sc, fc, window, sc, fc, ...]
const meta = frama_batch_metadata_js(w0, w1, ws, s0, s1, ss, f0, f1, fs);
const combos = meta.length / 3;
// Compute all combos (returns flat values)
const flat = frama_batch_js(h, l, c, w0, w1, ws, s0, s1, ss, f0, f1, fs);
// Reshape into matrix [combos x len]
const len = c.length;
const rows = combos;
const matrix = [] as Float64Array[];
for (let i = 0; i < rows; i++) {
const start = i * len;
matrix.push(flat.slice(start, start + len));
} Performance Analysis
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.
Related Indicators
Arnaud Legoux Moving Average
Moving average indicator
Compound Ratio Moving Average (CoRa Wave)
Moving average indicator
Centered Weighted Moving Average
Moving average indicator
Double Exponential Moving Average
Moving average indicator
Ehlers Distance Coefficient Filter
Moving average indicator
Ehlers Error-Correcting EMA (ECEMA)
Moving average indicator