Polarized Fractal Efficiency (PFE)

Parameters: period = 10 | smoothing = 5

Overview

Polarized Fractal Efficiency (PFE) quantifies how efficiently price travels from point A to point B by comparing the direct distance against the actual path taken. The indicator divides the straight line distance between current price and the price n periods ago by the sum of all incremental step lengths within that window, then multiplies by 100 and applies a sign reflecting direction. When price moves in a straight line, PFE approaches +100 or -100 depending on trend direction; when price meanders sideways, PFE gravitates toward zero. An exponential moving average smooths the raw efficiency calculation to filter noise and highlight persistent directional movement. Traders interpret high absolute PFE values as strong trending markets suitable for momentum strategies, while values near zero suggest choppy conditions better suited for mean reversion approaches.

Implementation Examples

Get started with PFE in just a few lines:

use vector_ta::indicators::pfe::{pfe, PfeInput, PfeParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with price data slice
let prices = vec![100.0, 101.0, 102.5, 101.8, 103.2, 104.0];
let params = PfeParams { period: Some(10), smoothing: Some(5) }; // defaults are 10/5
let input = PfeInput::from_slice(&prices, params);
let result = pfe(&input)?;

// Using with Candles data structure
// Quick and simple with default parameters (period=10, smoothing=5; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = PfeInput::with_default_candles(&candles);
let result = pfe(&input)?;

// Access the PFE values
for value in result.values {
    println!("PFE: {}", value);
}

API Reference

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

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

// From candles with default params (close prices, period=10, smoothing=5)
PfeInput::with_default_candles(&Candles) -> PfeInput
Parameters Structure
#[derive(Default)]
pub struct PfeParams {
    pub period: Option<usize>,    // Default: 10
    pub smoothing: Option<usize>, // Default: 5
}
Output Structure
pub struct PfeOutput {
    pub values: Vec<f64>, // PFE values (signed, EMA-smoothed), NaN prefix during warmup
}
Validation, Warmup & NaNs
  • period > 0 else PfeError::InvalidPeriod; smoothing > 0 else PfeError::InvalidSmoothing.
  • Data must contain at least one finite value; otherwise PfeError::AllValuesNaN. Empty slice: PfeError::EmptyInputData.
  • Requires at least period + 1 valid points after the first finite value; otherwise PfeError::NotEnoughValidData (needed = period + 1).
  • Warmup: indices [0 .. first + period) are NaN; first computable index is first + period.
  • EMA smoothing is seeded with the first available signed raw value (α = 2/(s+1)).
  • If the denominator is ≈0 at any step, raw PFE is treated as 0.0.
Error Handling
use vector_ta::indicators::pfe::{pfe, PfeError};

match pfe(&input) {
    Ok(output) => process_results(output.values),
    Err(PfeError::EmptyInputData) => eprintln!("input is empty"),
    Err(PfeError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(PfeError::InvalidPeriod { period, data_len }) => {
        eprintln!("invalid period: {} for len {}", period, data_len)
    }
    Err(PfeError::NotEnoughValidData { needed, valid }) => {
        eprintln!("not enough valid data: needed {}, got {}", needed, valid)
    }
    Err(PfeError::InvalidSmoothing { smoothing }) => {
        eprintln!("invalid smoothing: {}", smoothing)
    }
}

Python Bindings

Basic Usage

Calculate PFE using NumPy arrays (defaults: period=10, smoothing=5):

import numpy as np
from vector_ta import pfe

# Prepare price data as NumPy array
prices = np.array([100.0, 101.0, 102.5, 101.8, 103.2, 104.0])

# Calculate PFE with defaults (10/5)
result = pfe(prices, period=10, smoothing=5)

# Or specify custom parameters and kernel
result = pfe(prices, period=14, smoothing=5, kernel="avx2")

print(f"PFE values: {result}")
Streaming Real-time Updates

Process real-time price updates efficiently:

from vector_ta import PfeStream

# Initialize streaming PFE calculator
stream = PfeStream(period=10, smoothing=5)

# Process real-time price updates
for price in price_feed:
    pfe_value = stream.update(price)

    if pfe_value is not None:
        # PFE value is ready (None during warmup period)
        print(f"Current PFE: {pfe_value}")
Batch Parameter Optimization

Test multiple parameter combinations for optimization:

import numpy as np
from vector_ta import pfe_batch

prices = np.array([...])  # Your historical prices

# (start, end, step) for each parameter
period_range = (5, 20, 5)     # 5, 10, 15, 20
smoothing_range = (1, 10, 1)  # 1..10

results = pfe_batch(
    prices,
    period_range=period_range,
    smoothing_range=smoothing_range,
    kernel="auto"
)

print(results['values'].shape)   # (num_combinations, len(prices))
print(results['periods'])        # periods tested
print(results['smoothings'])     # smoothings tested
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 pfe_cuda_batch_dev, pfe_cuda_many_series_one_param_dev

# One series (float32)
data = np.asarray(load_data(), dtype=np.float32)

dev = pfe_cuda_batch_dev(
    data=data,
    period_range=(5, 30, 5),
    smoothing_range=(2, 20, 2),
    device_id=0,
)

# Many series (time-major)
data_tm = np.asarray(load_data_time_major_matrix(), dtype=np.float32)
rows, cols = data_tm.shape
data_tm = data_tm.ravel()

dev_tm = pfe_cuda_many_series_one_param_dev(
    data_tm=data_tm,
    cols=cols,
    rows=rows,
    period=14,
    smoothing=14,
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage

Calculate PFE in JavaScript/TypeScript:

import { pfe_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 101.0, 102.5, 101.8, 103.2, 104.0]);

// Calculate PFE with specified parameters
const result = pfe_js(prices, 10, 5);

console.log('PFE values:', result);
Memory-Efficient Operations

Use zero-copy operations for better performance with large datasets:

import { pfe_alloc, pfe_free, pfe_into, memory } from 'vectorta-wasm';

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

const inPtr = pfe_alloc(length);
const outPtr = pfe_alloc(length);

new Float64Array(memory.buffer, inPtr, length).set(prices);

// Args: in_ptr, out_ptr, len, period, smoothing
pfe_into(inPtr, outPtr, length, 10, 5);

const pfeValues = new Float64Array(memory.buffer, outPtr, length).slice();

pfe_free(inPtr, length);
pfe_free(outPtr, length);

console.log('PFE values:', pfeValues);
Batch Processing

Test multiple parameter combinations efficiently:

import { pfe_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);

// Unified batch API accepts a config object
const config = {
  period_range: [5, 20, 5],     // 5, 10, 15, 20
  smoothing_range: [1, 10, 1],  // 1..10
};

const out = pfe_batch(prices, config);
// out: { values: Float64Array, combos: Array<{period:number, smoothing:number}>, rows:number, cols:number }

// Reshape flat values into [rows x cols]
const rows = out.rows, cols = out.cols;
const matrix: number[][] = [];
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  matrix.push(Array.from(out.values.slice(start, start + cols)));
}

// Access row for a specific parameter combo
const target = { period: 10, smoothing: 5 };
const idx = out.combos.findIndex(c => c.period === target.period && c.smoothing === target.smoothing);
const values = idx >= 0 ? matrix[idx] : undefined;

CUDA Bindings (Rust)

use vector_ta::cuda::CudaPfe;
use vector_ta::indicators::pfe::PfeBatchRange;

let cuda = CudaPfe::new(0)?;

let data_f32: [f32] = /* ... */;
let sweep = PfeBatchRange::default();

let out = cuda.pfe_batch_dev(&data_f32, &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