Magnetismo de FVG: Fair Value Gaps Realmente Fecham?

Magnetismo de FVG: Fair Value Gaps Realmente Fecham?

Fair Value Gaps realmente fecham? Análise estatística do magnetismo de FVG em forex e crypto com backtest em tick data.

fvgprice-actionbacktesting

A Afirmação Que Todo Trader de Price Action Faz

Se você passou mais de dez minutos em comunidades de price action, já ouviu: “Fair value gaps atuam como ímãs — o preço sempre volta para preenchê-los.”

Está em toda thumbnail do YouTube. Está em todo breakdown de metodologia ICT. É dito com convicção absoluta e aproximadamente zero evidência estatística.

Então decidimos testar de verdade.

Veredito final (Fev 2026): Após uma autópsia multi-timeframe em nível de tick com 42M ticks de EURUSD, FVG está formalmente morto. Os resultados degradam monotonicamente com timeframes mais rápidos: 5min PF 0.80, 15min PF 0.94, 1H PF 1.04. OHLC diário mostrou PF 4.28 — inflação de 4× pelo viés de SL/TP baseado em Close. Até o sweep ótimo de R:R (1.7:1) produz PF 1.114 — finíssimo e não operável. Autópsia completa no cemitério.

O Que É um Fair Value Gap?

Um fair value gap (FVG) é um padrão de três candles onde os pavios do primeiro e terceiro candle não se sobrepõem, deixando um “gap” nos preços negociados no segundo candle. A teoria diz que esse gap representa uma ineficiência — os preços se moveram rápido demais, e o mercado precisa retornar para “preencher” o gap e estabelecer valor justo.

Veja como os detectamos programaticamente:

import pandas as pd
import numpy as np

def detect_fvgs(df: pd.DataFrame, min_gap_atr: float = 0.3) -> pd.DataFrame:
    """
    Detect Fair Value Gaps in OHLC data.

    A bullish FVG occurs when candle[i-1].high < candle[i+1].low
    A bearish FVG occurs when candle[i-1].low > candle[i+1].high

    min_gap_atr: minimum gap size as multiple of ATR to filter noise
    """
    df = df.copy()

    # ATR for filtering
    tr = pd.concat([
        df['high'] - df['low'],
        (df['high'] - df['close'].shift(1)).abs(),
        (df['low'] - df['close'].shift(1)).abs()
    ], axis=1).max(axis=1)
    df['atr'] = tr.rolling(14).mean()

    fvgs = []

    for i in range(1, len(df) - 1):
        prev_high = df['high'].iloc[i - 1]
        prev_low = df['low'].iloc[i - 1]
        next_high = df['high'].iloc[i + 1]
        next_low = df['low'].iloc[i + 1]
        atr = df['atr'].iloc[i]

        if pd.isna(atr) or atr == 0:
            continue

        # Bullish FVG: gap between prev high and next low
        if next_low > prev_high:
            gap_size = next_low - prev_high
            if gap_size >= min_gap_atr * atr:
                fvgs.append({
                    'timestamp': df.index[i],
                    'type': 'bullish',
                    'gap_top': next_low,
                    'gap_bottom': prev_high,
                    'gap_size': gap_size,
                    'gap_atr': gap_size / atr,
                    'mid': (next_low + prev_high) / 2,
                })

        # Bearish FVG: gap between next high and prev low
        if prev_low > next_high:
            gap_size = prev_low - next_high
            if gap_size >= min_gap_atr * atr:
                fvgs.append({
                    'timestamp': df.index[i],
                    'type': 'bearish',
                    'gap_top': prev_low,
                    'gap_bottom': next_high,
                    'gap_size': gap_size,
                    'gap_atr': gap_size / atr,
                    'mid': (prev_low + next_high) / 2,
                })

    return pd.DataFrame(fvgs)

O Estudo de Taxa de Preenchimento de FVG

Detectamos FVGs em seis instrumentos de forex e crypto (timeframe H1, dados de 2021–2025) e medimos se o preço retornou para preenchê-los dentro de várias janelas de tempo:

def measure_fill_rate(
    df: pd.DataFrame,
    fvgs: pd.DataFrame,
    max_bars: list[int] = [24, 48, 96, 240]
) -> dict:
    """
    For each FVG, check whether price returned to fill it
    within max_bars candles.
    """
    results = {n: {'filled': 0, 'total': 0} for n in max_bars}

    for _, fvg in fvgs.iterrows():
        ts = fvg['timestamp']
        idx = df.index.get_loc(ts)

        for window in max_bars:
            end_idx = min(idx + 2 + window, len(df))
            future_bars = df.iloc[idx + 2:end_idx]

            if len(future_bars) == 0:
                continue

            results[window]['total'] += 1

            if fvg['type'] == 'bullish':
                # Filled when price drops to gap_bottom
                if future_bars['low'].min() <= fvg['gap_bottom']:
                    results[window]['filled'] += 1
            else:
                # Filled when price rises to gap_top
                if future_bars['high'].max() >= fvg['gap_top']:
                    results[window]['filled'] += 1

    return {n: r['filled'] / r['total'] if r['total'] > 0 else 0
            for n, r in results.items()}

Resultados: Taxas de Preenchimento por Janela de Tempo

JanelaEURUSDGBPUSDBTCUSDETHUSDSPXGold
24 barras62.3%64.1%58.7%55.2%67.4%61.8%
48 barras74.1%75.8%69.3%64.8%78.2%73.5%
96 barras83.7%84.2%78.1%73.6%86.9%82.1%
240 barras91.2%90.8%85.4%81.3%93.1%89.7%

À primeira vista, parece confirmação: FVGs de fato preenchem com taxas altas, especialmente com tempo suficiente.

Mas aqui está o problema.

O Baseline Aleatório

Qualquer nível de preço arbitrário eventualmente será “preenchido” pelo movimento aleatório de preço. Para saber se FVGs são especiais, precisamos comparar contra uma hipótese nula. Geramos “FVGs falsos” aleatórios em timestamps aleatórios e medimos suas taxas de preenchimento:

def random_baseline_fill_rate(df, n_samples=1000, gap_size_atr=0.5, max_bars=[24, 48, 96, 240]):
    """Generate random price levels and measure how often price returns to them."""
    rng = np.random.default_rng(42)
    results = {n: {'filled': 0, 'total': 0} for n in max_bars}

    valid_indices = range(200, len(df) - 250)
    sample_indices = rng.choice(valid_indices, size=n_samples, replace=False)

    for idx in sample_indices:
        atr = df['atr'].iloc[idx]
        # Random level near current price
        level = df['close'].iloc[idx] + rng.choice([-1, 1]) * gap_size_atr * atr

        for window in max_bars:
            end_idx = min(idx + window, len(df))
            future = df.iloc[idx + 1:end_idx]
            results[window]['total'] += 1

            if future['low'].min() <= level <= future['high'].max():
                results[window]['filled'] += 1

    return {n: r['filled'] / r['total'] for n, r in results.items()}

Taxa de Preenchimento FVG vs. Baseline Aleatório (EURUSD)

JanelaTaxa FVGBaseline AleatórioDiferença
24 barras62.3%54.8%+7.5%
48 barras74.1%68.2%+5.9%
96 barras83.7%80.1%+3.6%
240 barras91.2%90.7%+0.5%

A narrativa “FVGs sempre preenchem” desmorona quando se adiciona um baseline. Sim, 91% dos FVGs preenchem dentro de 240 barras — mas 90.7% de níveis aleatórios também são atingidos. O alpha específico de FVG em taxas de preenchimento é modesto em horizontes curtos e negligível em horizontes longos.

Onde FVGs Realmente Importam: O Efeito Magnético

A história da taxa de preenchimento não impressiona. Mas encontramos algo mais interessante: FVGs criam um viés direcional de curto prazo nas primeiras 12 barras.

Quando medimos não apenas se o preço preenche o gap, mas quão rápido ele se move em direção a ele, FVGs reais mostraram convergência significativamente mais rápida que o baseline aleatório:

Barras Após FVGDist. Média ao Gap (FVG)Dist. Média ao Nível (Aleatório)p-value
4 barras0.72 ATR0.95 ATR0.003
8 barras0.48 ATR0.82 ATR< 0.001
12 barras0.31 ATR0.71 ATR< 0.001
24 barras0.54 ATR0.68 ATR0.042

O efeito magnético é real mas efêmero. O preço converge em direção a FVGs mais rápido que níveis aleatórios por cerca de 12 barras, depois o efeito desaparece.

A Especificação FVG Wall

Com base nesses achados, desenvolvemos o FVG Wall — uma estratégia que identifica zonas onde múltiplos FVGs se agrupam:

def find_fvg_walls(fvgs: pd.DataFrame, price: float, atr: float,
                    cluster_distance: float = 1.0, min_cluster: int = 3) -> list[dict]:
    """
    Find FVG Wall zones where multiple unfilled FVGs cluster.

    A wall is defined as 3+ FVGs within cluster_distance * ATR of each other.
    """
    # Filter to unfilled FVGs
    active = fvgs[~fvgs['filled']].copy()

    if len(active) < min_cluster:
        return []

    # Sort by midpoint
    active = active.sort_values('mid')
    walls = []

    i = 0
    while i < len(active):
        cluster = [active.iloc[i]]
        j = i + 1

        while j < len(active):
            if abs(active.iloc[j]['mid'] - cluster[-1]['mid']) <= cluster_distance * atr:
                cluster.append(active.iloc[j])
                j += 1
            else:
                break

        if len(cluster) >= min_cluster:
            mids = [c['mid'] for c in cluster]
            walls.append({
                'zone_top': max(c['gap_top'] for c in cluster),
                'zone_bottom': min(c['gap_bottom'] for c in cluster),
                'zone_mid': np.mean(mids),
                'n_fvgs': len(cluster),
                'strength': sum(c['gap_atr'] for c in cluster),
                'direction': 'bullish' if sum(1 for c in cluster if c['type'] == 'bullish') > len(cluster) / 2 else 'bearish',
            })

        i = j

    return walls

Quando 3+ FVGs se agrupam dentro de 1 ATR entre si, o efeito magnético amplifica. FVG Walls mostraram taxa de toque de 73.2% dentro de 48 barras (vs. 58.1% do baseline aleatório), e o viés direcional das zonas de wall produziu um profit factor de 1.31 em nossos backtests iniciais.

Autópsia de FVG em nível de tick em múltiplos timeframes Autópsia multi-timeframe em tick: performance degrada monotonicamente do OHLC diário (inflação de 4×) até 5min (PF 0.80). FVG está formalmente morto.

O Resumo

FVGs são fenômenos reais de microestrutura de mercado, não mitologia de leitura de gráfico. Mas as narrativas populares sobre eles estão majoritariamente erradas:

  1. “FVGs sempre preenchem” — Quase verdade, mas níveis aleatórios também são atingidos. A taxa de preenchimento sozinha não é alpha operável.
  2. “FVGs são ímãs” — Verdade, mas apenas por cerca de 12 barras. O efeito é estatisticamente significativo mas limitado no tempo.
  3. “Mais FVGs = zonas mais fortes” — Verdade. Agrupamento de FVGs (walls) amplifica tanto o efeito magnético quanto o viés direcional.

O insight operável: use FVG Walls como indicadores de viés direcional de curto prazo, não como níveis permanentes de suporte/resistência. A metáfora da física está errada — FVGs não são poços gravitacionais, são gradientes de pressão temporários.


Para mais sobre nossa abordagem estatística a afirmações de price action, veja Expoentes de Hurst para Reversão à Média. Para o panorama completo do que testamos, leia 31 Estratégias Testadas, 4 Sobreviveram.