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 7 valid points after the first finite value; otherwise NotEnoughValidData.
  • Warmup: indices before first_valid_idx + 7 − 1 are NaN for both lines; prediction first becomes finite at first_valid_idx + 6.
  • Trigger becomes finite once 4 predictions exist (from first_valid_idx + 9 onward); earlier trigger values remain NaN.
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

Comparison:
View:
Loading chart...

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