End Point Moving Average (EPMA)

Parameters: period = 11 | offset = 4

Overview

The End Point Moving Average extrapolates price trends by applying linearly increasing weights that emphasize the most recent data points while using polynomial regression concepts to project forward momentum. Rather than averaging prices equally or exponentially, EPMA assigns weights based on a linear ramp with an adjustable offset, creating a forward looking average that anticipates price direction based on recent trajectory. When EPMA rises steeply above price, it signals accelerating upward momentum that often precedes breakouts, while a declining EPMA below price warns of weakening support and potential reversals. Traders utilize EPMA for early trend detection since its polynomial weighting scheme responds faster to directional changes than traditional moving averages, particularly during the initial stages of new trends. The offset parameter allows fine tuning between responsiveness and stability, with higher offsets creating more aggressive projections that excel at catching turns but may produce more false signals in choppy markets.

Implementation Examples

Compute EPMA from a price slice or Candles dataset:

use vectorta::indicators::moving_averages::epma::{epma, EpmaInput, EpmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From a price slice
let prices = vec![100.0, 101.5, 102.0, 101.0, 103.5];
let params = EpmaParams { period: Some(11), offset: Some(4) }; // defaults
let input = EpmaInput::from_slice(&prices, params);
let out = epma(&input)?; // out.values: Vec<f64>

// From Candles (defaults: source="close", period=11, offset=4)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = EpmaInput::with_default_candles(&candles);
let out = epma(&input)?;

for v in out.values { println!("EPMA: {}", v); }

API Reference

Input Methods
// From price slice
EpmaInput::from_slice(&[f64], EpmaParams) -> EpmaInput

// From candles with custom source
EpmaInput::from_candles(&Candles, &str, EpmaParams) -> EpmaInput

// From candles with default params (close, period=11, offset=4)
EpmaInput::with_default_candles(&Candles) -> EpmaInput
Parameters Structure
pub struct EpmaParams {
    pub period: Option<usize>, // Default: 11 (>=2)
    pub offset: Option<usize>, // Default: 4  (must be < period)
}
Output Structure
pub struct EpmaOutput {
    pub values: Vec<f64>, // EPMA values (weighted average with linear ramp)
}
Validation, Warmup & NaNs
  • Errors on invalid inputs: EmptyInputData, AllValuesNaN, InvalidPeriod (period < 2 or period > len), InvalidOffset (offset ≥ period), NotEnoughValidData (needed = period + offset + 1), InvalidOutputLen (for into APIs), InvalidKernel (batch).
  • Warmup: outputs are NaN until first_valid + period + offset + 1. After that, values are finite unless the weight sum is zero.
  • Streaming: EpmaStream::update returns a value each call, echoing the input during warmup and switching to EPMA once ready.
Error Handling
use vectorta::indicators::moving_averages::epma::{epma, EpmaError, EpmaInput, EpmaParams};

let input = EpmaInput::from_slice(&prices, EpmaParams::default());
match epma(&input) {
    Ok(output) => handle(output.values),
    Err(EpmaError::EmptyInputData) => eprintln!("input is empty"),
    Err(EpmaError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(EpmaError::InvalidPeriod { period, data_len }) => eprintln!("invalid period {} for len {}", period, data_len),
    Err(EpmaError::InvalidOffset { offset }) => eprintln!("offset {} must be < period", offset),
    Err(EpmaError::NotEnoughValidData { needed, valid }) => eprintln!("need {} valid points, found {}", needed, valid),
    Err(e) => eprintln!("EPMA error: {}", e),
}

Python Bindings

Basic Usage

Compute EPMA with explicit parameters (defaults 11/4):

import numpy as np
from vectorta import epma

prices = np.array([100.0, 101.5, 102.0, 101.0, 103.5])

# period=11, offset=4 (defaults in Rust)
result = epma(prices, period=11, offset=4)
print(result)  # NumPy array of float64
Streaming Updates
from vectorta import EpmaStream

stream = EpmaStream(period=11, offset=4)
for price in price_feed:
    val = stream.update(price)  # returns float; echoes input during warmup
    use_value(val)
Batch Parameter Optimization
import numpy as np
from vectorta import epma_batch

prices = np.array([...], dtype=float)

# (start, end, step)
period_range = (8, 16, 2)
offset_range = (2, 6, 1)

res = epma_batch(prices, period_range, offset_range)
values = res['values']      # shape: (rows, len(prices))
periods = res['periods']
offsets = res['offsets']
CUDA Acceleration

CUDA support is available for EPMA (developer APIs):

# Developer CUDA APIs (feature-gated)
from vectorta import epma_cuda_batch_dev, epma_cuda_many_series_one_param_dev
import numpy as np

# One series × many parameters (returns device buffer handle)
data_f32 = np.asarray(prices, dtype=np.float32)
dev_buf = epma_cuda_batch_dev(data_f32, period_range=(8,16,1), offset_range=(2,6,1), device_id=0)

# Many series × one parameter set (time‑major [T,N])
tm = np.random.rand(1000, 64).astype(np.float32)
dev_buf2 = epma_cuda_many_series_one_param_dev(tm, period=11, offset=4, device_id=0)

JavaScript/WASM Bindings

Basic Usage

Compute EPMA in JS/TS:

import { epma_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 101.5, 102, 101, 103.5]);
const values = await epma_js(prices, 11, 4);
console.log(values);
Memory-Efficient Operations

Use zero‑copy style APIs for large datasets:

import { epma_alloc, epma_free, epma_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* your data */]);
const len = prices.length;
const inPtr = epma_alloc(len);
const outPtr = epma_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(prices);
await epma_into(inPtr, outPtr, len, 11, 4);
const out = new Float64Array(memory.buffer, outPtr, len).slice();

epma_free(inPtr, len);
epma_free(outPtr, len);
Batch Processing

Run parameter sweeps and receive a matrix result:

import { epma_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);
const cfg = { period_range: [8, 16, 2], offset_range: [2, 6, 1] };
const { values, rows, cols, combos } = await epma_batch(prices, cfg);

// values is row‑major: rows = combos.length, cols = prices.length
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  const end = start + cols;
  const row = values.slice(start, end);
  console.log(combos[r], row[0], row[row.length-1]);
}

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators