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 vectorta::indicators::pfe::{pfe, PfeInput, PfeParams};
use vectorta::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 vectorta::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 vectorta 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 vectorta 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 vectorta 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 support for PFE is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated PFE calculations
# See APO page for the expected API patterns.

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;

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators