Predictive Moving Average (PMA)
Overview
The Predictive Moving Average applies John Ehlers' cascade of two seven point weighted moving averages to produce a prediction line that anticipates price movements while filtering market noise. The indicator computes a first weighted moving average using triangular weights over seven bars, then applies the same weighting scheme to those results, and finally calculates the prediction as twice the first smoothed value minus the double smoothed value. This extrapolation technique projects the smoothed trend forward by one period, creating a line that responds quickly to genuine trend changes while rejecting random fluctuations. A four point weighted trigger line of the prediction provides signal confirmation and helps identify momentum shifts. Traders watch for prediction line crossovers with the trigger to enter positions, with the prediction leading price by design to provide early warnings of directional changes. The fixed internal parameters eliminate curve fitting concerns while delivering consistent results across different instruments and timeframes, though the short smoothing window makes PMA most effective on intraday charts where responsiveness matters more than long term stability.
Defaults: PMA uses fixed internal windows and weights (no user parameters).
Implementation Examples
Get PMA prediction and trigger in a few lines:
use vector_ta::indicators::pma::{pma, PmaInput, PmaParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5, 106.0, 107.0];
let input = PmaInput::from_slice(&prices, PmaParams {});
let result = pma(&input)?;
// result.predict and result.trigger match input length
println!("predict[7] = {}", result.predict[7]);
// Using with Candles (default source = "close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = PmaInput::with_default_candles(&candles);
let result = pma(&input)?; API Reference
Input Methods ▼
// From price slice
PmaInput::from_slice(&[f64], PmaParams) -> PmaInput
// From candles with custom source
PmaInput::from_candles(&Candles, &str, PmaParams) -> PmaInput
// From candles with default params (source = "close")
PmaInput::with_default_candles(&Candles) -> PmaInput Parameters Structure ▼
#[derive(Debug, Clone)]
pub struct PmaParams; // No tunable fields; Default = PmaParams {} Output Structure ▼
pub struct PmaOutput {
pub predict: Vec<f64>, // predictive line
pub trigger: Vec<f64>, // weighted trigger of prediction
} Validation, Warmup & NaNs ▼
- Errors:
PmaError::EmptyData,PmaError::AllValuesNaN,PmaError::NotEnoughValidData { valid }. - Requires at least
7valid points after the first finite value; otherwiseNotEnoughValidData. - Warmup: indices before
first_valid_idx + 7 − 1areNaNfor both lines; prediction first becomes finite atfirst_valid_idx + 6. - Trigger becomes finite once 4 predictions exist (from
first_valid_idx + 9onward); earlier trigger values remainNaN.
Error Handling ▼
use vector_ta::indicators::pma::PmaError;
match pma(&input) {
Ok(output) => {
use_output(output.predict, output.trigger);
}
Err(PmaError::EmptyData) => println!("Input data is empty"),
Err(PmaError::AllValuesNaN) => println!("All input values are NaN"),
Err(PmaError::NotEnoughValidData { valid }) =>
println!("Need at least 7 valid points after first finite; only {}", valid),
} Python Bindings
Basic Usage ▼
Calculate PMA prediction and trigger using NumPy arrays:
import numpy as np
from vector_ta import pma, pma_batch, PmaStream
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5, 106.0, 107.0])
# Compute arrays (predict, trigger)
predict, trigger = pma(prices)
# Streaming usage
stream = PmaStream()
for price in prices:
pt = stream.update(price)
if pt is not None:
pred, trig = pt
# Batch API (single row; no parameter sweep)
res = pma_batch(prices)
print(res['values'].shape, res['rows'], res['cols']) CUDA Acceleration ▼
CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).
import numpy as np
from vector_ta import pma_cuda_batch_dev, pma_cuda_many_series_one_param_dev
# One series (float32)
data_f32 = np.asarray(load_data(), dtype=np.float32)
dev = pma_cuda_batch_dev(
data_f32=data_f32,
device_id=0,
)
# Many series (time-major)
data_tm_f32 = np.asarray(load_data_time_major_matrix(), dtype=np.float32)
dev_tm = pma_cuda_many_series_one_param_dev(
data_tm_f32=data_tm_f32,
device_id=0,
) JavaScript/WASM Bindings
Basic Usage ▼
Calculate PMA in JavaScript/TypeScript:
import { pma_js } from 'vectorta-wasm';
// Price data as Float64Array
const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5, 106.0, 107.0]);
// Flat array: [predict..., trigger...]
const flat = pma_js(prices);
const len = prices.length;
const predict = flat.slice(0, len);
const trigger = flat.slice(len); Memory-Efficient Operations ▼
Use zero-copy operations with explicit buffers:
import { pma_alloc, pma_free, pma_into, memory } from 'vectorta-wasm';
const prices = new Float64Array(/* your data */);
const n = prices.length;
// Allocate WASM memory for input and outputs
const inPtr = pma_alloc(n);
const predPtr = pma_alloc(n);
const trigPtr = pma_alloc(n);
// Copy input data
new Float64Array(memory.buffer, inPtr, n).set(prices);
// Compute (writes directly into predict/trigger buffers)
await pma_into(inPtr, predPtr, trigPtr, n);
// Read results
const predict = new Float64Array(memory.buffer, predPtr, n).slice();
const trigger = new Float64Array(memory.buffer, trigPtr, n).slice();
// Free buffers
pma_free(inPtr, n);
pma_free(predPtr, n);
pma_free(trigPtr, n); Batch Processing ▼
Compute both lines with one call:
import { pma_batch } from 'vectorta-wasm';
const prices = new Float64Array(/* historical prices */);
const out = pma_batch(prices);
// out = { predict: Float64Array, trigger: Float64Array, rows: 1, cols: n } CUDA Bindings (Rust)
use vector_ta::cuda::CudaPma;
use vector_ta::indicators::pma::PmaBatchRange;
let cuda = CudaPma::new(0)?;
let prices: [f32] = /* ... */;
let sweep = PmaBatchRange::default();
let out = cuda.pma_batch_dev(&prices, &sweep)?;
let _ = out; Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28
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