Subversion Repositories Integrator Subversion

Rev

Rev 764 | Rev 771 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

package br.com.kronus.core;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import br.com.sl.domain.model.Candle;
import br.com.sl.domain.util.BigDecimalUtils;

/**
 * Detector de padrões de gatilhos (GR, G1, G2, G3, G4)
 * trabalhando tanto em modo backtest quanto em modo tempo real.
 *
 * Regras resumidas:
 *
 * GR comprador (A):
 *   - Candle comprador.
 *   - Último candle da tendência compradora.
 *   - Maior topo da pernada compradora.
 *   - Após ele, tendência muda para vendedora.
 *   - Um vendedor subsequente rompe o fundo de A => G1 vendedor, A vira GR.
 *
 * GR vendedor (B):
 *   - Candle vendedor.
 *   - Último candle da tendência vendedora.
 *   - Menor fundo da pernada vendedora.
 *   - Após ele, tendência muda para compradora.
 *   - Um comprador subsequente rompe o topo de B => G1 comprador, B vira GR.
 *
 * G2 comprador (a partir de GR comprador):
 *   - Após G1 (vendedor), surge nova tendência compradora.
 *   - O ÚLTIMO candle dessa tendência compradora tem fechamento dentro da região do GR.
 *   - Se nessa tendência compradora alguma barra romper o topo do GR, o padrão é descartado.
 *   - Se a tendência terminar sem que o último comprador feche dentro da região do GR, descarta.
 *
 * G3 comprador:
 *   - Após G2, o próximo candle deve ser vendedor.
 *   - Em uma sequência vendedora, algum vendedor:
 *       * rompe o fundo de G2 (mínima < mínima de G2) e
 *       * tem topo <= topo do GR
 *     => esse candle é G3.
 *
 * G4 comprador:
 *   - Próximo candle após G3.
 *   - Se romper o topo do GR (máxima > máxima do GR) => G4.
 *   - Desarma a operação, padrão é encerrado, nova análise começa após esse candle.
 *
 * G2 vendedor (a partir de GR vendedor):
 *   - Após G1 (comprador), surge nova tendência vendedora.
 *   - O ÚLTIMO candle dessa tendência vendedora tem fechamento dentro da região do GR.
 *   - Se nessa tendência vendedora alguma barra romper o fundo do GR, o padrão é descartado.
 *
 * G3 vendedor:
 *   - Após G2, o próximo candle deve ser comprador.
 *   - Em uma sequência compradora, algum comprador:
 *       * rompe o topo de G2 (máxima > máxima de G2) e
 *       * tem fundo >= fundo do GR
 *     => esse candle é G3.
 *
 * G4 vendedor:
 *   - Próximo candle após G3.
 *   - Se romper o fundo do GR (mínima < mínima do GR) => G4.
 *
 * Outside em relação ao GR:
 *   - Qualquer candle C após o GR que:
 *       * C.maxima > GR.maxima E
 *       * C.minima < GR.minima
 *     => padrão atual é descartado, nova análise começa após C.
 *
 * Obs.: Quando um ciclo termina (padrão encontrado ou descartado),
 * a próxima análise SEMPRE começa após o último candle usado.
 */

public class DetectorGatilhos {

    private final boolean logAtivo;

    // Estado apenas para o modo tempo real
    private int idxProximaAnaliseTempoReal = 0;

    public DetectorGatilhos() {
        this(false);
    }

    public DetectorGatilhos(boolean logAtivo) {
        this.logAtivo = logAtivo;
    }

    private void log(String msg) {
        if (logAtivo) {
            System.out.println(msg);
        }
    }

    private static class ResultadoPadrao {
        PadraoGatilho padrao; // pode ser null (quando não houve padrão válido)
        int lastIndex;        // último índice de candle usado no ciclo

        ResultadoPadrao(PadraoGatilho padrao, int lastIndex) {
            this.padrao = padrao;
            this.lastIndex = lastIndex;
        }
    }

    /**
     * Cria um resultado com padrão PARCIAL (até G2).
     * Usado quando GR, G1 e G2 são válidos, mas G3 não se forma.
     */

    private ResultadoPadrao criarResultadoParcialComG2(Candle ref,
                                                       Candle g1,
                                                       Candle g2,
                                                       int lastIndex) {
        PadraoGatilho padrao = new PadraoGatilho();
        padrao.setReferencia(ref);
        padrao.setGatilho1(g1);
        padrao.setGatilho2(g2);
        padrao.setGatilho3(null);
        padrao.setGatilho4(null);
        return new ResultadoPadrao(padrao, lastIndex);
    }

    // =====================================================================
    // API PRINCIPAL – BACKTEST (lista completa de candles)
    // =====================================================================

    public List<PadraoGatilho> identificarPadroes(List<Candle> candles) {
        List<PadraoGatilho> padroes = new ArrayList<>();
        int n = candles.size();
        if (n < 4) {
            return padroes;
        }

        int idxRef = 0;

        while (idxRef < n - 3) {
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef);

            if (resultado == null) {
                idxRef++;
                continue;
            }

            if (resultado.padrao != null) {
                padroes.add(resultado.padrao);
            }

            // Próxima análise sempre começa após o último candle usado no ciclo
            idxRef = Math.max(resultado.lastIndex + 1, idxRef + 1);
        }

        return padroes;
    }

    private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) {
        Candle ref = candles.get(idxRef);

        if (ref.isCandleComprador()) {
            return detectarPadraoComprador(candles, idxRef);
        } else if (ref.isCandleVendedor()) {
            return detectarPadraoVendedor(candles, idxRef);
        } else {
            // candle sem tendência não serve como GR
            return new ResultadoPadrao(null, idxRef);
        }
    }

    // =====================================================================
    // CASO 1 – GR COMPRADOR (candle A)
    // =====================================================================

    private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxRef) {
        int n = candles.size();
        Candle ref = candles.get(idxRef); // candidato a GR
        int lastIndex = idxRef;

        // 0) Validar se idxRef pode ser GR comprador (A)
        if (!ref.isCandleComprador()) {
            return new ResultadoPadrao(null, idxRef);
        }

        // 0.1 – início da pernada compradora que termina em A
        int inicioTrendCompra = idxRef;
        for (int i = idxRef - 1; i >= 0; i--) {
            Candle c = candles.get(i);
            if (!c.isCandleComprador()) {
                break;
            }
            inicioTrendCompra = i;
        }

        // 0.2 – primeiro candle direcional após A
        int idxPrimeiroDirecionalAposRef = -1;
        for (int i = idxRef + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
                continue;
            }

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefC[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            idxPrimeiroDirecionalAposRef = i;
            break;
        }

        if (idxPrimeiroDirecionalAposRef == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        Candle primeiroDirecional = candles.get(idxPrimeiroDirecionalAposRef);
        if (!primeiroDirecional.isCandleVendedor()) {
            // primeiro direcional após A ainda é comprador → A não é último da pernada
            return new ResultadoPadrao(null, idxRef);
        }

        // 0.3 – A tem o maior topo da pernada compradora
        BigDecimal topoRef = ref.getMaxima();
        for (int i = inicioTrendCompra; i <= idxRef; i++) {
            Candle c = candles.get(i);
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), topoRef)) {
                return new ResultadoPadrao(null, idxRef);
            }
        }

        int idxPrimeiroVendedor = idxPrimeiroDirecionalAposRef;

        // 1) Encontrar G1 (vendedor que rompe o fundo do GR)
        int idxG1 = -1;
        Candle g1 = null;

        for (int i = idxPrimeiroVendedor; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefC[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleVendedor()) {
                // tendência vendedora terminou antes do rompimento
                lastIndex = i - 1;
                return new ResultadoPadrao(null, lastIndex);
            }

            lastIndex = i;

            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
                idxG1 = i;
                g1 = c;
                break;
            }
        }

        if (idxG1 == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        log(String.format("RefC[%d] (GR) => G1 (vendedor) em [%d]", idxRef, idxG1));

        // 2) Encontrar G2 (retorno comprador à região do GR)
        int idxPrimeiroComprador = -1;
        for (int i = idxG1 + 1; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefC[%d]: OUTSIDE em [%d] antes da tendência de G2. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
                continue;
            }
            if (c.isCandleComprador()) {
                idxPrimeiroComprador = i;
                break;
            } else {
                lastIndex = i;
            }
        }

        if (idxPrimeiroComprador == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        boolean rompeuTopoRef = false;
        Candle ultimoCompradorTrend = null;
        int idxUltimoCompradorTrend = -1;

        for (int i = idxPrimeiroComprador; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefC[%d]: OUTSIDE em [%d] durante formação de G2. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleComprador()) {
                break;
            }

            ultimoCompradorTrend = c;
            idxUltimoCompradorTrend = i;
            lastIndex = i;

            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
                rompeuTopoRef = true;
                break;
            }
        }

        if (rompeuTopoRef || idxUltimoCompradorTrend == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        boolean fechamentoDentroRegiaoRef =
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMinima()) &&
                BigDecimalUtils.ehMenorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMaxima());

        if (!fechamentoDentroRegiaoRef) {
            return new ResultadoPadrao(null, lastIndex);
        }

        Candle g2 = ultimoCompradorTrend;
        int idxG2 = idxUltimoCompradorTrend;
        log(String.format("RefC[%d], G1[%d] => G2 (comprador) em [%d]", idxRef, idxG1, idxG2));

        // 3) Encontrar G3 (vendedor rompendo fundo de G2, topo <= topo do GR)
        int idxPrimeiroVendedorAposG2 = -1;
        for (int i = idxG2 + 1; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefC[%d]: OUTSIDE em [%d] antes da sequência vendedora de G3. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
                continue;
            }

            if (c.isCandleVendedor()) {
                idxPrimeiroVendedorAposG2 = i;
                break;
            } else {
                lastIndex = i;
                // pela sua regra, o próximo após G2 deveria ser vendedor
                // => padrão chegou até G2, mas não evoluiu corretamente para G3
                return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
            }
        }

        if (idxPrimeiroVendedorAposG2 == -1) {
            // padrão com GR, G1, G2, mas sem sequência adequada para G3
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
        }

        int idxG3 = -1;
        Candle g3 = null;

        for (int i = idxPrimeiroVendedorAposG2; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (ATÉ IDENTIFICAR G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefC[%d]: OUTSIDE em [%d] durante formação de G3. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleVendedor()) {
                lastIndex = i - 1;
                break;
            }

            lastIndex = i;

            boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
            boolean topoMenorOuIgualRef = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), ref.getMaxima());

            if (rompeFundoG2 && topoMenorOuIgualRef) {
                idxG3 = i;
                g3 = c;
                break;
            }
        }

        if (idxG3 == -1 || g3 == null) {
            // Chegou até G2 mas não encontrou G3
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
        }

        log(String.format("RefC[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d]",
                idxRef, idxG1, idxG2, idxG3));

        // 4) G4 – próximo candle rompe topo do GR (aqui já não precisamos mais checar outside)
        Candle g4 = null;
        int proxIdx = idxG3 + 1;
        if (proxIdx < n) {
            Candle c = candles.get(proxIdx);
            lastIndex = proxIdx;

            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
                g4 = c;
                log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
            } else {
                log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
            }
        }

        PadraoGatilho padrao = new PadraoGatilho();
        padrao.setReferencia(ref);
        padrao.setGatilho1(g1);
        padrao.setGatilho2(g2);
        padrao.setGatilho3(g3);
        padrao.setGatilho4(g4);

        return new ResultadoPadrao(padrao, lastIndex);
    }


    // =====================================================================
    // CASO 2 – GR VENDEDOR (candle B)
    // =====================================================================

    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxRef) {
        int n = candles.size();
        Candle ref = candles.get(idxRef); // candidato a GR vendedor
        int lastIndex = idxRef;

        // 0) Validar se idxRef pode ser GR vendedor (B)
        if (!ref.isCandleVendedor()) {
            return new ResultadoPadrao(null, idxRef);
        }

        // 0.1 – início da pernada vendedora que termina em B
        int inicioTrendVenda = idxRef;
        for (int i = idxRef - 1; i >= 0; i--) {
            Candle c = candles.get(i);
            if (!c.isCandleVendedor()) {
                break;
            }
            inicioTrendVenda = i;
        }

        // 0.2 – primeiro candle direcional após B
        int idxPrimeiroDirecionalAposRef = -1;
        for (int i = idxRef + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
                continue;
            }

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefV[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            idxPrimeiroDirecionalAposRef = i;
            break;
        }

        if (idxPrimeiroDirecionalAposRef == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        Candle primeiroDirecional = candles.get(idxPrimeiroDirecionalAposRef);
        if (!primeiroDirecional.isCandleComprador()) {
            // primeiro direcional após B ainda é vendedor
            return new ResultadoPadrao(null, idxRef);
        }

        // 0.3 – B tem o MENOR fundo da pernada vendedora
        BigDecimal fundoRef = ref.getMinima();
        for (int i = inicioTrendVenda; i <= idxRef; i++) {
            Candle c = candles.get(i);
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), fundoRef)) {
                return new ResultadoPadrao(null, idxRef);
            }
        }

        int idxPrimeiroComprador = idxPrimeiroDirecionalAposRef;

        // 1) Encontrar G1 (comprador que rompe topo do GR)
        int idxG1 = -1;
        Candle g1 = null;

        for (int i = idxPrimeiroComprador; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefV[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleComprador()) {
                lastIndex = i - 1;
                return new ResultadoPadrao(null, lastIndex);
            }

            lastIndex = i;

            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
                idxG1 = i;
                g1 = c;
                break;
            }
        }

        if (idxG1 == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        log(String.format("RefV[%d] (GR) => G1 (comprador) em [%d]", idxRef, idxG1));

        // 2) Encontrar G2 (retorno vendedor à região do GR)
        int idxPrimeiroVendedor = -1;
        for (int i = idxG1 + 1; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefV[%d]: OUTSIDE em [%d] antes da tendência de G2. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
                continue;
            }

            if (c.isCandleVendedor()) {
                idxPrimeiroVendedor = i;
                break;
            } else {
                lastIndex = i;
            }
        }

        if (idxPrimeiroVendedor == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        boolean rompeuFundoRef = false;
        Candle ultimoVendedorTrend = null;
        int idxUltimoVendedorTrend = -1;

        for (int i = idxPrimeiroVendedor; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefV[%d]: OUTSIDE em [%d] durante formação de G2. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleVendedor()) {
                break;
            }

            ultimoVendedorTrend = c;
            idxUltimoVendedorTrend = i;
            lastIndex = i;

            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
                rompeuFundoRef = true;
                break;
            }
        }

        if (rompeuFundoRef || idxUltimoVendedorTrend == -1) {
            return new ResultadoPadrao(null, lastIndex);
        }

        boolean fechamentoDentroRegiaoRef =
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMinima()) &&
                BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMaxima());

        if (!fechamentoDentroRegiaoRef) {
            return new ResultadoPadrao(null, lastIndex);
        }

        Candle g2 = ultimoVendedorTrend;
        int idxG2 = idxUltimoVendedorTrend;
        log(String.format("RefV[%d], G1[%d] => G2 (vendedor) em [%d]", idxRef, idxG1, idxG2));

        // 3) Encontrar G3 (comprador rompendo topo de G2, fundo >= fundo do GR)
        int idxPrimeiroCompradorAposG2 = -1;
        for (int i = idxG2 + 1; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (até G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefV[%d]: OUTSIDE em [%d] antes da sequência compradora de G3. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
                continue;
            }

            if (c.isCandleComprador()) {
                idxPrimeiroCompradorAposG2 = i;
                break;
            } else {
                lastIndex = i;
                // esperado comprador; padrão segue apenas até G2
                return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
            }
        }

        if (idxPrimeiroCompradorAposG2 == -1) {
            // não teve sequência adequada após G2
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
        }

        int idxG3 = -1;
        Candle g3 = null;

        for (int i = idxPrimeiroCompradorAposG2; i < n; i++) {
            Candle c = candles.get(i);

            // OUTSIDE em relação ao GR (ATÉ IDENTIFICAR G3)
            if (isOutsideReferencia(ref, c)) {
                lastIndex = i;
                log(String.format(
                        "RefV[%d]: OUTSIDE em [%d] durante formação de G3. Padrão descartado.",
                        idxRef, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (!c.isCandleComprador()) {
                lastIndex = i - 1;
                break;
            }

            lastIndex = i;

            boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
            boolean fundoMaiorOuIgualRef = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), ref.getMinima());

            if (rompeTopoG2 && fundoMaiorOuIgualRef) {
                idxG3 = i;
                g3 = c;
                break;
            }
        }

        if (idxG3 == -1 || g3 == null) {
            // padrão só até G2
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
        }

        log(String.format("RefV[%d], G1[%d], G2[%d] => G3 (comprador) em [%d]",
                idxRef, idxG1, idxG2, idxG3));

        // 4) G4 – próximo candle rompe fundo do GR
        Candle g4 = null;
        int proxIdx = idxG3 + 1;
        if (proxIdx < n) {
            Candle c = candles.get(proxIdx);
            lastIndex = proxIdx;

            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
                g4 = c;
                log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
            } else {
                log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
            }
        }

        PadraoGatilho padrao = new PadraoGatilho();
        padrao.setReferencia(ref);
        padrao.setGatilho1(g1);
        padrao.setGatilho2(g2);
        padrao.setGatilho3(g3);
        padrao.setGatilho4(g4);

        return new ResultadoPadrao(padrao, lastIndex);
    }


    // =====================================================================
    // HELPER – Candle outside em relação ao GR
    // =====================================================================

    /**
     * Outside em relação ao GR:
     *  - atual.max > GR.max e atual.min < GR.min
     */

    private boolean isOutsideReferencia(Candle referencia, Candle atual) {
        if (referencia == null || atual == null) return false;
        return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), referencia.getMaxima())
                && BigDecimalUtils.ehMenorQue(atual.getMinima(), referencia.getMinima());
    }

    // =====================================================================
    // MODO TEMPO REAL (candle a candle)
    // =====================================================================

    /**
     * Reinicia o estado do modo tempo real.
     */

    public void resetTempoReal() {
        this.idxProximaAnaliseTempoReal = 0;
    }

    /**
     * Deve ser chamado SEMPRE que um novo candle for adicionado à lista.
     *
     * Exemplo:
     *   candles.add(novoCandle);
     *   PadraoGatilho padrao = detector.processarCandleTempoReal(candles);
     *
     *   if (padrao != null) {
     *       // padrão completo encontrado
     *   }
     */

    public PadraoGatilho processarCandleTempoReal(List<Candle> candles) {
        int n = candles.size();
        if (n < 4) {
            return null;
        }

        while (idxProximaAnaliseTempoReal < n - 3) {
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal);

            if (resultado == null) {
                idxProximaAnaliseTempoReal++;
                continue;
            }

            int proximoInicio = resultado.lastIndex + 1;
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
                proximoInicio = idxProximaAnaliseTempoReal + 1;
            }
            idxProximaAnaliseTempoReal = proximoInicio;

            if (resultado.padrao != null) {
                return resultado.padrao;
            }
        }

        return null;
    }

}