End Point Moving Average (EPMA)
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 < 2orperiod > len),InvalidOffset(offset ≥ period),NotEnoughValidData(needed = period + offset + 1),InvalidOutputLen(for into APIs),InvalidKernel(batch). - Warmup: outputs are
NaNuntilfirst_valid + period + offset + 1. After that, values are finite unless the weight sum is zero. - Streaming:
EpmaStream::updatereturns 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
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