FVG Positioning Average

Parameters: lookback = 30 | lookback_type = Bar Count | atr_multiplier = 0.25

Overview

FVG Positioning Average monitors bullish and bearish fair value gaps separately and turns those active gap levels into two rolling averages. A bullish gap is recorded when the current low clears the high from two bars earlier and the intervening close confirms the imbalance. A bearish gap is recorded with the mirrored condition. Once stored, the indicator keeps an average level for each side so you can judge whether current price is trading above, below, or directly inside the recent imbalance inventory.

The implementation also returns midpoint overlays that compare the current candle body midpoint against those running averages. The bullish midpoint clamps upward to the bullish average, while the bearish midpoint clamps downward to the bearish average. That makes the output useful for visual regime mapping, zone-tracking, and automation that needs separate references for supportive and resistant gap structure.

Defaults: FVG Positioning Average uses `lookback = 30`, `lookback_type = "Bar Count"`, and `atr_multiplier = 0.25`.

Implementation Examples

Compute the four FVG positioning series from raw OHLC slices or candle data.

use vector_ta::indicators::fvg_positioning_average::{
    fvg_positioning_average,
    FvgPositioningAverageInput,
    FvgPositioningAverageParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let open = vec![100.0, 101.0, 102.5, 103.2, 104.0, 105.1];
let high = vec![101.0, 102.4, 103.1, 104.6, 105.3, 106.2];
let low = vec![99.6, 100.8, 101.9, 102.8, 103.7, 104.5];
let close = vec![100.7, 102.0, 102.9, 104.2, 104.9, 105.8];

let output = fvg_positioning_average(&FvgPositioningAverageInput::from_slices(
    &open,
    &high,
    &low,
    &close,
    FvgPositioningAverageParams {
        lookback: Some(30),
        lookback_type: Some("Bar Count".to_string()),
        atr_multiplier: Some(0.25),
    },
))?;

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = fvg_positioning_average(
    &FvgPositioningAverageInput::with_default_candles(&candles),
)?;

println!("latest bull average = {:?}", output.bull_average.last());
println!("latest bear mid = {:?}", candle_output.bear_mid.last());

API Reference

Input Methods
// From candles
FvgPositioningAverageInput::from_candles(&Candles, FvgPositioningAverageParams)
    -> FvgPositioningAverageInput

// From OHLC slices
FvgPositioningAverageInput::from_slices(&[f64], &[f64], &[f64], &[f64], FvgPositioningAverageParams)
    -> FvgPositioningAverageInput

// From candles with default parameters
FvgPositioningAverageInput::with_default_candles(&Candles)
    -> FvgPositioningAverageInput
Parameters Structure
pub struct FvgPositioningAverageParams {
    pub lookback: Option<usize>,        // default 30
    pub lookback_type: Option<String>,  // "Bar Count" or "FVG Count"
    pub atr_multiplier: Option<f64>,    // default 0.25
}
Output Structure
pub struct FvgPositioningAverageOutput {
    pub bull_average: Vec<f64>,
    pub bear_average: Vec<f64>,
    pub bull_mid: Vec<f64>,
    pub bear_mid: Vec<f64>,
}
Validation, Warmup & NaNs
  • All OHLC inputs must be non-empty and have matching lengths, or the function returns EmptyInputData or InputLengthMismatch.
  • lookback must be greater than 0.
  • lookback_type must resolve to either Bar Count or FVG Count.
  • atr_multiplier must be finite and greater than or equal to 0.0.
  • The engine requires a contiguous valid OHLC run of at least 3 bars; shorter runs return NotEnoughValidData.
  • Invalid bars reset the streaming state and clear stored gap levels.
  • When no qualifying bullish or bearish gaps exist yet, the related average and midpoint outputs remain NaN.
  • Batch mode validates sweep ranges and rejects unsupported batch kernels with InvalidKernelForBatch.
Builder, Streaming & Batch APIs
// Builder
FvgPositioningAverageBuilder::new()
    .lookback(usize)
    .lookback_type(&str)
    .atr_multiplier(f64)
    .kernel(Kernel)
    .apply(&Candles)

FvgPositioningAverageBuilder::new()
    .apply_slices(&[f64], &[f64], &[f64], &[f64])

FvgPositioningAverageBuilder::new()
    .into_stream()

// Stream
FvgPositioningAverageStream::try_new(FvgPositioningAverageParams)
FvgPositioningAverageStream::update(f64, f64, f64, f64)
    -> Option<(f64, f64, f64, f64)>

// Batch
FvgPositioningAverageBatchBuilder::new()
    .lookback_range(start, end, step)
    .lookback_static(value)
    .atr_multiplier_range(start, end, step)
    .atr_multiplier_static(value)
    .lookback_type(&str)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64], &[f64])

FvgPositioningAverageBatchBuilder::new()
    .apply_candles(&Candles)
Error Handling
pub enum FvgPositioningAverageError {
    EmptyInputData,
    InputLengthMismatch { open_len: usize, high_len: usize, low_len: usize, close_len: usize },
    AllValuesNaN,
    InvalidLookback { lookback: usize },
    InvalidLookbackType { value: String },
    InvalidAtrMultiplier { atr_multiplier: f64 },
    NotEnoughValidData { needed: usize, valid: usize },
    OutputLengthMismatch { expected: usize, got: usize },
    MismatchedOutputLen { dst_len: usize, expected_len: usize },
    InvalidRange { start: String, end: String, step: String },
    InvalidKernelForBatch(Kernel),
    InvalidInput { msg: String },
}

Python Bindings

Python exposes a scalar OHLC function, a streaming class, and a batch sweep. The scalar path returns four NumPy arrays in bullish-average, bearish-average, bullish-mid, bearish-mid order. The batch path returns those four flattened matrices plus the tested parameter grids. Streaming emits one four-value tuple per valid bar after the indicator has enough context to detect gaps.

import numpy as np
from vector_ta import (
    fvg_positioning_average,
    fvg_positioning_average_batch,
    FvgPositioningAverageStream,
)

open_ = np.asarray(open_values, dtype=np.float64)
high = np.asarray(high_values, dtype=np.float64)
low = np.asarray(low_values, dtype=np.float64)
close = np.asarray(close_values, dtype=np.float64)

bull_average, bear_average, bull_mid, bear_mid = fvg_positioning_average(
    open_,
    high,
    low,
    close,
    lookback=30,
    lookback_type="Bar Count",
    atr_multiplier=0.25,
    kernel="auto",
)

stream = FvgPositioningAverageStream(
    lookback=30,
    lookback_type="FVG Count",
    atr_multiplier=0.25,
)
print(stream.update(open_[-1], high[-1], low[-1], close[-1]))

batch = fvg_positioning_average_batch(
    open_,
    high,
    low,
    close,
    lookback_range=(10, 30, 10),
    atr_multiplier_range=(0.1, 0.3, 0.1),
    lookback_type="Bar Count",
    kernel="auto",
)

print(batch["bull_average"].shape)
print(batch["lookback_types"])
print(batch["atr_multipliers"])

JavaScript/WASM Bindings

The WASM layer exposes scalar, batch, and into-buffer entry points. The scalar function returns a plain object with four arrays. The batch function returns those same four flattened outputs plus grid metadata, while the low-level allocation helpers support preallocated memory workflows in custom runtimes.

import init, {
  fvg_positioning_average_js,
  fvg_positioning_average_batch_js,
} from "@vectoralpha/vector_ta";

await init();

const single = fvg_positioning_average_js(
  open,
  high,
  low,
  close,
  30,
  "Bar Count",
  0.25,
);

console.log(single.bull_average);
console.log(single.bear_mid);

const batch = fvg_positioning_average_batch_js(open, high, low, close, {
  lookback_range: [10, 30, 10],
  atr_multiplier_range: [0.1, 0.3, 0.1],
  lookback_type: "FVG Count",
});

console.log(batch.rows, batch.cols);
console.log(batch.combos);
console.log(batch.bull_mid);

CUDA Bindings (Rust)

Additional details for the CUDA bindings can be found inside the VectorTA repository.

Performance Analysis

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators