Subversion Repositories Integrator Subversion

Rev

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

package br.com.kronus.core;

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 implementadas (texto alinhado):
 *
 * COMPRADOR
 * ---------
 * 1) Candle A comprador, candidato à referência.
 *    - Após A deve haver mudança de tendência (B).
 *    - A deve ser o último da tendência compradora:
 *      o primeiro candle direcional após A (ignorando inside/neutros)
 *      deve ser vendedor (B). Se for comprador, descarta A.
 *
 * 2) Ajuste da referência com B:
 *    - Se B (vendedor) tiver TOPO maior que o topo de A,
 *      B passa a ser o candidato à referência.
 *      Senão, A permanece como candidato.
 *
 * 3) G1:
 *    - A partir do candidato à referência, o primeiro candle vendedor
 *      subsequente que romper o FUNDO do candidato (mínima < mínima do candidato)
 *      será o G1. O candidato torna-se o GR.
 *
 * 4) G2:
 *    - Após o G1, assim que houver nova mudança de tendência para compradora:
 *      * encontrar o primeiro comprador após G1 (ignorando inside/neutros);
 *      * seguir a sequência compradora até aparecer vendedor.
 *      * durante essa sequência:
 *          - se houver OUTSIDE (atual rompe topo e fundo do anterior) => descarta.
 *          - se ALGUM candle romper o TOPO do GR (máxima > máxima GR) => descarta.
 *      * o ÚLTIMO candle dessa tendência compradora deve ter o fechamento
 *        dentro da região total do GR (mínimaGR <= fechamento <= máximaGR).
 *      * se virar vendedor sem atingir a região do GR => descarta.
 *      * caso válido, esse último comprador é o G2.
 *
 * 5) G3:
 *    - Após o G2, o PRÓXIMO candle direcional deve ser vendedor.
 *      se for comprador => padrão apenas até G2 (parcial).
 *    - numa sequência vendedora:
 *      * se houver OUTSIDE => descarta.
 *      * se algum candle vendedor romper o fundo de G2 (mínima < mínima G2)
 *        e tiver topo <= topo do GR => G3.
 *      * se surgir candle que rompa o TOPO do GR (máxima > máximaGR) ANTES do G3,
 *        o padrão é descartado.
 *
 * 6) G4:
 *    - Após o G3, se o PRÓXIMO candle romper o topo do GR (máxima > máximaGR),
 *      será o G4. Depois disso, o padrão é encerrado e um novo padrão é buscado.
 *
 * VENDEDOR
 * --------
 * 1) Candle A vendedor, candidato à referência.
 *    - Após A deve haver mudança de tendência (B).
 *    - A deve ser o último da tendência vendedora:
 *      o primeiro candle direcional após A (ignorando inside/neutros)
 *      deve ser comprador (B). Se for vendedor, descarta A.
 *
 * 2) Ajuste da referência com B:
 *    - Se B (comprador) tiver FUNDO mais baixo (mínima menor) que o fundo de A,
 *      B passa a ser candidato à referência.
 *      Senão, A permanece como candidato.
 *
 * 3) G1:
 *    - A partir do candidato à referência, o primeiro candle comprador
 *      subsequente que romper o TOPO do candidato (máxima > máxima do candidato)
 *      será o G1. O candidato torna-se o GR.
 *
 * 4) G2:
 *    - Após o G1, nova mudança para tendência vendedora:
 *      * encontrar o primeiro vendedor após G1 (ignorando inside/neutros);
 *      * seguir a sequência vendedora até aparecer comprador.
 *      * durante essa sequência:
 *          - se houver OUTSIDE => descarta.
 *          - se algum candle romper o FUNDO do GR (mínima < mínimaGR) => descarta.
 *      * o ÚLTIMO candle dessa tendência vendedora deve ter o fechamento
 *        dentro da região total do GR (mínimaGR <= fechamento <= máximaGR).
 *      * se virar comprador sem atingir a região do GR => descarta.
 *      * caso válido, esse último vendedor é o G2.
 *
 * 5) G3:
 *    - Após o G2, o PRÓXIMO candle direcional deve ser comprador.
 *      se for vendedor => padrão apenas até G2 (parcial).
 *    - numa sequência compradora:
 *      * se houver OUTSIDE => descarta.
 *      * se algum candle comprador romper o topo de G2 (máxima > máxima G2)
 *        e tiver fundo >= fundo do GR => G3.
 *      * se algum candle romper o FUNDO do GR (mínima < mínimaGR)
 *        ANTES do G3 => descarta.
 *
 * 6) G4:
 *    - Após o G3, se o PRÓXIMO candle romper o fundo do GR (mínima < mínimaGR),
 *      será o G4. Depois disso, o padrão é encerrado e um novo padrão é buscado.
 *
 * OUTSIDE (ambos os lados):
 *    - Candle cuja máxima > máxima do candle anterior E mínima < mínima do candle anterior.
 *    - Se houver um outside antes de identificar o G3, o padrão é descartado.
 */

public class DetectorGatilhos {

    private final boolean logAtivo;
    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;
        int lastIndex;
        ResultadoPadrao(PadraoGatilho padrao, int lastIndex) {
            this.padrao = padrao;
            this.lastIndex = lastIndex;
        }
    }

    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
    // =====================================================================

    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);
            }

            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 {
            return new ResultadoPadrao(null, idxRef);
        }
    }

    // =====================================================================
    // HELPERS
    // =====================================================================

    private boolean isInside(List<Candle> candles, int idx) {
        if (idx <= 0) return false;
        Candle atual = candles.get(idx);
        Candle anterior = candles.get(idx - 1);
        return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima())
                && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima());
    }

    private boolean isOutside(List<Candle> candles, int idx) {
        if (idx <= 0) return false;
        Candle atual = candles.get(idx);
        Candle anterior = candles.get(idx - 1);
        return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), anterior.getMaxima())
                && BigDecimalUtils.ehMenorQue(atual.getMinima(), anterior.getMinima());
    }

    // =====================================================================
    // CASO 1 – PADRÃO A PARTIR DE CANDLE COMPRADOR
    // =====================================================================

    private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxA) {
        int n = candles.size();
        int lastIndex = idxA;
        Candle candleA = candles.get(idxA);
        if (!candleA.isCandleComprador()) {
            return new ResultadoPadrao(null, idxA);
        }

        // 1) A deve ser último da tendência compradora -> achar B vendedor
        int idxB = -1;
        for (int i = idxA + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) {
                continue;
            }
            if (c.isCandleComprador()) {
                // A não é o último comprador
                return new ResultadoPadrao(null, idxA);
            }
            if (c.isCandleVendedor()) {
                idxB = i;
                break;
            }
        }
        if (idxB == -1) return new ResultadoPadrao(null, idxA);

        Candle candleB = candles.get(idxB);

        // 2) Candidato à referência: A ou B (conforme topo)
        Candle candidatoRef = candleA;
        int idxCandidatoRef = idxA;
        if (BigDecimalUtils.ehMaiorQue(candleB.getMaxima(), candleA.getMaxima())) {
            candidatoRef = candleB;
            idxCandidatoRef = idxB;
        }

        // 3) G1 – primeiro vendedor que rompe fundo do candidatoRef
        Candle g1 = null;
        int idxG1 = -1;
        for (int i = idxCandidatoRef + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) {
                continue;
            }

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

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

        if (g1 == null) return new ResultadoPadrao(null, lastIndex);

        Candle gr = candidatoRef;
        int idxGR = idxCandidatoRef;
        log(String.format("GR comprador em [%d], G1 (vendedor) em [%d]", idxGR, idxG1));

        // 4) G2 – nova tendência compradora com fechamento dentro da região do GR
        Candle g2 = null;
        int idxG2 = -1;

        int idxPrimeiroComprador = -1;
        for (int i = idxG1 + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            if (c.isCandleComprador()) {
                idxPrimeiroComprador = i;
                break;
            } else {
                // ainda vendedor
                lastIndex = i;
            }
        }

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

        Candle ultimoComprador = null;
        int idxUltimoComprador = -1;

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

            // outside antes de G3 descarta
            if (isOutside(candles, i)) {
                lastIndex = i;
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (comprador).", idxGR, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            if (c.isCandleComprador()) {
                // se romper topo do GR, descarta
                if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
                    lastIndex = i;
                    log(String.format("GR[%d]: candle [%d] rompeu topo do GR durante G2.", idxGR, i));
                    return new ResultadoPadrao(null, lastIndex);
                }
                ultimoComprador = c;
                idxUltimoComprador = i;
                lastIndex = i;
            } else if (c.isCandleVendedor()) {
                // terminou tendência compradora
                lastIndex = i - 1;
                break;
            }
        }

        if (ultimoComprador == null) return new ResultadoPadrao(null, lastIndex);

        boolean fechamentoDentroRegiaoGR =
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoComprador.getFechamento(), gr.getMinima()) &&
                BigDecimalUtils.ehMenorOuIgualQue(ultimoComprador.getFechamento(), gr.getMaxima());

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

        g2 = ultimoComprador;
        idxG2 = idxUltimoComprador;
        log(String.format("GR[%d], G1[%d] => G2 (comprador) em [%d]", idxGR, idxG1, idxG2));

        // 5) G3 – próximo direcional vendedor; vendedor que rompe fundo de G2 com topo <= topo GR
        Candle g3 = null;
        int idxG3 = -1;

        int idxPrimeiroVendedorAposG2 = -1;
        for (int i = idxG2 + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            if (c.isCandleVendedor()) {
                idxPrimeiroVendedorAposG2 = i;
                break;
            } else {
                // primeiro direcional não é vendedor => padrão só até G2
                lastIndex = i;
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
            }
        }
        if (idxPrimeiroVendedorAposG2 == -1)
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);

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

            // outside antes de G3 descarta
            if (isOutside(candles, i)) {
                lastIndex = i;
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (vendedor).", idxGR, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            // se romper topo do GR antes de G3 => descarta
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
                lastIndex = i;
                log(String.format("GR[%d]: candle [%d] rompeu topo do GR antes de G3.", idxGR, i));
                return new ResultadoPadrao(null, lastIndex);
            }

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

            lastIndex = i;
            boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
            boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima());
            if (rompeFundoG2 && topoMenorOuIgualGR) {
                g3 = c;
                idxG3 = i;
                break;
            }
        }

        if (g3 == null)
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);

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

        // 6) G4 – próximo candle rompe topo do GR
        Candle g4 = null;
        int proxIdx = idxG3 + 1;
        if (proxIdx < n) {
            Candle c = candles.get(proxIdx);
            lastIndex = proxIdx;
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
                g4 = c;
                log(String.format("GR[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
                        idxGR, idxG1, idxG2, idxG3, proxIdx));
            } else {
                log(String.format("GR[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
                        idxGR, idxG1, idxG2, idxG3, proxIdx));
            }
        }

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

        return new ResultadoPadrao(padrao, lastIndex);
    }

    // =====================================================================
    // CASO 2 – PADRÃO A PARTIR DE CANDLE VENDEDOR
    // =====================================================================

    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) {
        int n = candles.size();
        int lastIndex = idxA;
        Candle candleA = candles.get(idxA);
        if (!candleA.isCandleVendedor()) {
            return new ResultadoPadrao(null, idxA);
        }

        // 1) A deve ser último da tendência vendedora -> achar B comprador
        int idxB = -1;
        for (int i = idxA + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            if (c.isCandleVendedor()) {
                return new ResultadoPadrao(null, idxA);
            }
            if (c.isCandleComprador()) {
                idxB = i;
                break;
            }
        }
        if (idxB == -1) return new ResultadoPadrao(null, idxA);

        Candle candleB = candles.get(idxB);

        // 2) Candidato à referência: A ou B (conforme fundo)
        Candle candidatoRef = candleA;
        int idxCandidatoRef = idxA;
        if (BigDecimalUtils.ehMenorQue(candleB.getMinima(), candleA.getMinima())) {
            candidatoRef = candleB;
            idxCandidatoRef = idxB;
        }

        // 3) G1 – primeiro comprador que rompe topo do candidatoRef
        Candle g1 = null;
        int idxG1 = -1;
        for (int i = idxCandidatoRef + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

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

            lastIndex = i;
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
                g1 = c;
                idxG1 = i;
                break;
            }
        }
        if (g1 == null) return new ResultadoPadrao(null, lastIndex);

        Candle gr = candidatoRef;
        int idxGR = idxCandidatoRef;
        log(String.format("GR vendedor em [%d], G1 (comprador) em [%d]", idxGR, idxG1));

        // 4) G2 – nova tendência vendedora com fechamento dentro da região do GR
        Candle g2 = null;
        int idxG2 = -1;

        int idxPrimeiroVendedor = -1;
        for (int i = idxG1 + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            if (c.isCandleVendedor()) {
                idxPrimeiroVendedor = i;
                break;
            } else {
                lastIndex = i;
            }
        }
        if (idxPrimeiroVendedor == -1) return new ResultadoPadrao(null, lastIndex);

        Candle ultimoVendedor = null;
        int idxUltimoVendedor = -1;
        boolean rompeuFundoGR = false;

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

            if (isOutside(candles, i)) {
                lastIndex = i;
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (vendedor).", idxGR, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            if (c.isCandleVendedor()) {
                // se romper fundo do GR, descarta
                if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
                    rompeuFundoGR = true;
                    lastIndex = i;
                    break;
                }
                ultimoVendedor = c;
                idxUltimoVendedor = i;
                lastIndex = i;
            } else if (c.isCandleComprador()) {
                lastIndex = i - 1;
                break;
            }
        }

        if (rompeuFundoGR || ultimoVendedor == null)
            return new ResultadoPadrao(null, lastIndex);

        boolean fechamentoDentroRegiaoGR =
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMinima()) &&
                BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMaxima());

        if (!fechamentoDentroRegiaoGR)
            return new ResultadoPadrao(null, lastIndex);

        g2 = ultimoVendedor;
        idxG2 = idxUltimoVendedor;
        log(String.format("GR[%d], G1[%d] => G2 (vendedor) em [%d]", idxGR, idxG1, idxG2));

        // 5) G3 – próximo direcional comprador; comprador que rompe topo de G2 com fundo >= fundo GR
        Candle g3 = null;
        int idxG3 = -1;

        int idxPrimeiroCompradorAposG2 = -1;
        for (int i = idxG2 + 1; i < n; i++) {
            Candle c = candles.get(i);
            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            if (c.isCandleComprador()) {
                idxPrimeiroCompradorAposG2 = i;
                break;
            } else {
                lastIndex = i;
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
            }
        }
        if (idxPrimeiroCompradorAposG2 == -1)
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);

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

            if (isOutside(candles, i)) {
                lastIndex = i;
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (comprador).", idxGR, i));
                return new ResultadoPadrao(null, lastIndex);
            }

            if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor()))
                continue;

            // se romper fundo do GR antes de G3 => descarta
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
                lastIndex = i;
                log(String.format("GR[%d]: candle [%d] rompeu fundo do GR antes de G3.", idxGR, i));
                return new ResultadoPadrao(null, lastIndex);
            }

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

            lastIndex = i;
            boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
            boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima());
            if (rompeTopoG2 && fundoMaiorOuIgualGR) {
                g3 = c;
                idxG3 = i;
                break;
            }
        }

        if (g3 == null)
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);

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

        // 6) 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(), gr.getMinima())) {
                g4 = c;
                log(String.format("GR[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
                        idxGR, idxG1, idxG2, idxG3, proxIdx));
            } else {
                log(String.format("GR[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
                        idxGR, idxG1, idxG2, idxG3, proxIdx));
            }
        }

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

        return new ResultadoPadrao(padrao, lastIndex);
    }

    // =====================================================================
    // MODO TEMPO REAL
    // =====================================================================

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

    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;
    }

}