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 vectorta::indicators::pma::{pma, PmaInput, PmaParams};
use vectorta::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 vectorta::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 vectorta 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 support for PMA is currently under development.

# Coming soon: CUDA-accelerated PMA calculations

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 }

Performance Analysis

Comparison:
View:
Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators