Subversion Repositories Integrator Subversion

Rev

Blame | Last modification | View Log | Download | RSS feed

package br.com.kronus.core;

import br.com.ec.core.util.VerificadorUtil;
import br.com.kronus.core.Candle;

import java.util.ArrayList;
import java.util.List;

/**
 * Detector + Monitor de Gatilhos (Referência, G1, G2, G3, G4)
 *
 * REGRAS IMPLEMENTADAS (resumo):
 *
 * Candle Referência:
 *  - Candle que demarca uma região (topo ou fundo) e que posteriormente
 *    será rompido contrariamente à sua tendência pelo Gatilho 1.
 *  - Ex.: candle comprador -> se um candle posterior romper seu FUNDO,
 *        esse posterior é G1 e o rompido é a referência.
 *    Ex.: candle vendedor -> se um candle posterior romper seu TOPO,
 *        esse posterior é G1 e o rompido é a referência.
 *
 * Gatilho 1:
 *  - Primeiro candle que rompe a tendência contrária a algum candle anterior
 *    (sem insiders).
 *  - Se G1 for COMPRADOR: deve romper o TOPO do candle referência.
 *  - Se G1 for VENDEDOR: deve romper o FUNDO do candle referência.
 *
 * Gatilho 2:
 *  - Candle que retorna à região do candle referência buscando liquidez.
 *  - Último candle antes da nova quebra de tendência.
 *  - Fechamento deve estar dentro da região total (incluindo pavio)
 *    do candle referência.
 *  - Se ultrapassar a máxima (ou mínima) do candle referência, deve-se procurar
 *    por um novo candle referência a partir desse candle.
 *  - EXCEÇÃO: se o candle for da mesma tendência de G1 e tiver rompido
 *    sua tendência (novo topo na compra / novo fundo na venda), também é G2.
 *
 * Gatilho 3:
 *  - Candle posterior ao G2.
 *  - Mesma tendência de G1.
 *  - Se referência for COMPRADORA:
 *      * topo de G3 < topo da referência
 *      * G3 rompe o FUNDO do G2
 *      * se romper o topo da referência após o G2 → invalida padrão.
 *  - Se referência for VENDEDORA:
 *      * topo de G3 > topo da referência
 *      * G3 rompe o TOPO do G2
 *      * se romper o fundo da referência após o G2 → invalida padrão.
 *
 * Gatilho 4:
 *  - Desarma a operação.
 *  - Se referência COMPRADORA: seu topo será rompido pelo candle do G4.
 *  - Se referência VENDEDORA: seu fundo será rompido.
 *  - Após G4 a análise recomeça.
 */

public class DetectorGatilhos_ {

    private Candle referencia;
    private Candle gatilho1;
    private Candle gatilho2;
    private Candle gatilho3;
    private Candle gatilho4;
    private int idxRef  = -1;
    private int idxG1   = -1;
    private int idxG2   = -1;
    private int idxG3   = -1;
    private int idxG4   = -1;
   
    private boolean candidatoG4Avaliado = false;


    private PadraoGatilho padraoAtual;

    private boolean logAtivo;

    public DetectorGatilhos_() {
        this(false);
    }

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

    public boolean isLogAtivo() {
        return logAtivo;
    }

    public void setLogAtivo(boolean logAtivo) {
        this.logAtivo = logAtivo;
    }

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

    private void reset() {
        referencia = null;
        gatilho1 = null;
        gatilho2 = null;
        gatilho3 = null;
        gatilho4 = null;
        padraoAtual = null;

        idxRef = -1;
        idxG1  = -1;
        idxG2  = -1;
        idxG3  = -1;
        idxG4  = -1;

        candidatoG4Avaliado = false;
    }

    // ============================================================
    //  A) MODO SIMULADOR / BACKTEST
    //     -> continua: List<PadraoGatilho> padroes = detector.identificarPadroes(candles);
    // ============================================================

    public List<PadraoGatilho> identificarPadroes(List<Candle> candles) {
        reset();
        List<PadraoGatilho> padroes = new ArrayList<>();

        for (int i = 0; i < candles.size(); i++) {
            processarCandleInterno(candles, i, null, padroes);
        }

        return padroes;
    }

    // ============================================================
    //  B) MODO MONITOR (tempo real / replay)
    // ============================================================

    /**
     * Processa o candle idx e retorna os logs deste passo.
     * Os logs também são enviados para System.out.println se logAtivo = true.
     */

    public List<String> processarCandle(List<Candle> candles, int idx) {
        List<String> logs = new ArrayList<>();
        processarCandleInterno(candles, idx, logs, null);
        return logs;
    }

    // ============================================================
    //  C) LÓGICA INTERNA (compartilhada entre simulador e monitor)
    // ============================================================

    private void processarCandleInterno(List<Candle> candles,
                                        int idx,
                                        List<String> logs,
                                        List<PadraoGatilho> padroes) {

        if (idx < 0 || idx >= candles.size()) {
            return;
        }

        Candle atual = candles.get(idx);
        if (VerificadorUtil.naoEstaNulo(atual)) {
                if (VerificadorUtil.estaNulo(atual.getContador())) {
                        System.out.println("TESTE");
                } else {
                        if (atual.getContador() == 11) {
                                System.out.println("TESTE");
                        }
                        if (atual.getContador() == 57) {
                                System.out.println("TESTE");
                        }
                }
        }
       
        // --------------------------------------------------------------------
        // 1) Ainda não temos Ref + G1 -> tentar formar com o candle atual
        // --------------------------------------------------------------------
        if (referencia == null || gatilho1 == null) {
            ParRefG1 par = encontrarReferenciaEGatilho1(candles, idx);

            if (par != null) {
                referencia = par.referencia;
                gatilho1   = par.g1;

                idxRef = candles.indexOf(referencia);
                idxG1  = candles.indexOf(gatilho1);

                // Segurança extra: não aceitar se por algum bug der o mesmo índice ou inverso
                if (idxRef < 0 || idxG1 < 0 || idxRef >= idxG1) {
                    // Inconsistente, descarta e segue
                    referencia = null;
                    gatilho1   = null;
                    idxRef = -1;
                    idxG1  = -1;
                    String msg = String.format(
                            "[%d] Par (Ref, G1) inconsistente encontrado. Descartando.",
                            idx
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);
                    return;
                }

                gatilho2 = null;
                gatilho3 = null;
                gatilho4 = null;
                idxG2 = idxG3 = idxG4 = -1;

                padraoAtual = new PadraoGatilho();
                padraoAtual.setReferencia(referencia);
                padraoAtual.setGatilho1(gatilho1);

                String direcao = gatilho1.isCandleComprador() ? "COMPRA" : "VENDA";
                int idxRef = candles.indexOf(referencia);
                int idxG1  = candles.indexOf(gatilho1);

                String msg = String.format(
                        "[%d] Gatilho 1 (%s) identificado. Referência = candle [%d], G1 = candle [%d].",
                        idx, direcao, idxRef, idxG1
                );
                if (logs != null) logs.add(msg);
                log(msg);
            } else {
                String msg = "[" + idx + "] Sem referência/G1 formados ainda.";
                if (logs != null) logs.add(msg);
                log(msg);
            }

            return;
        }

        // --------------------------------------------------------------------
        // 2) Temos Ref + G1, mas ainda não temos G2
        // --------------------------------------------------------------------
        if (gatilho2 == null) {

            boolean cEhG2 = isGatilho2(referencia, gatilho1, atual);

            if (cEhG2) {
                // G2 deve ser posterior a G1 e candle diferente
                if (idx > idxG1) {
                    gatilho2 = atual;
                    idxG2    = idx;

                    if (padraoAtual != null) {
                        padraoAtual.setGatilho2(gatilho2);
                    }

                    String msg = String.format(
                            "[%d] Gatilho 2 identificado.",
                            idx
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);
                } else {
                    // Se por alguma razão o método estiver sendo chamado de forma estranha
                    String msg = String.format(
                            "[%d] Candle candidato a G2, mas índice %d <= idxG1 %d. Ignorando para manter ordem cronológica.",
                            idx, idx, idxG1
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);
                }
                return;
            }

            // Se NÃO é G2, verificamos se:
            // 1) ultrapassou a região da referência (topo/fundo)
            // 2) houve nova quebra de tendência sem atingir a região da referência
            boolean ultrapassaRef = ultrapassaRegiaoReferencia(referencia, atual);
            boolean novaQuebraSemRegiao = novaQuebraDeTendenciaSemAtingirRegiao(referencia, gatilho1, atual);

            if (ultrapassaRef || novaQuebraSemRegiao) {
                String motivo = ultrapassaRef
                        ? "ultrapassou a região da referência"
                        : "nova quebra de tendência sem atingir a região da referência";

                String msg = String.format(
                        "[%d] Candle %s. Procurando novo candle referência a partir deste candle.",
                        idx, motivo
                );
                if (logs != null) logs.add(msg);
                log(msg);

                // Reset e tentamos nova Ref+G1 com o candle atual
                reset();
                ParRefG1 novo = encontrarReferenciaEGatilho1(candles, idx);
                if (novo != null) {
                    referencia = novo.referencia;
                    gatilho1 = novo.g1;
                    padraoAtual = new PadraoGatilho();
                    padraoAtual.setReferencia(referencia);
                    padraoAtual.setGatilho1(gatilho1);

                    String direcao = gatilho1.isCandleComprador() ? "COMPRA" : "VENDA";
                    int idxRef = candles.indexOf(referencia);
                    int idxG1  = candles.indexOf(gatilho1);

                    String msg2 = String.format(
                            "[%d] Novo Gatilho 1 (%s) identificado após reset. Ref = [%d], G1 = [%d].",
                            idx, direcao, idxRef, idxG1
                    );
                    if (logs != null) logs.add(msg2);
                    log(msg2);
                }
                return;
            }

            // Nem é G2, nem invalida a referência → seguimos aguardando G2
            String msg = "[" + idx + "] Aguardando Gatilho 2.";
            if (logs != null) logs.add(msg);
            log(msg);

            return;
        }

        // --------------------------------------------------------------------
        // 3) Temos Ref + G1 + G2, mas ainda não G3
        // --------------------------------------------------------------------
        if (gatilho3 == null) {

            // Invalidação após G2:
            if (rompeReferenciaAposG2(referencia, atual)) {
                String msg = String.format(
                        "[%d] Rompimento da referência após o G2. Padrão invalidado. Resetando.",
                        idx
                );
                if (logs != null) logs.add(msg);
                log(msg);
                reset();
                return;
            }
           
            if (isGatilho3(referencia, gatilho1, gatilho2, atual)) {

                if (idx > idxG2) {
                    gatilho3 = atual;
                    idxG3    = idx;
                    candidatoG4Avaliado = false;

                    if (padraoAtual != null) {
                        padraoAtual.setGatilho3(gatilho3);
                    }

                    String msg = String.format(
                            "[%d] Gatilho 3 identificado.",
                            idx
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);

                    if (padroes != null && padraoAtual != null && !padroes.contains(padraoAtual)) {
                        padroes.add(padraoAtual);
                    }
                } else {
                    String msg = String.format(
                            "[%d] Candle candidato a G3, mas índice %d <= idxG2 %d. Ignorando.",
                            idx, idx, idxG2
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);
                }

                return;
            }

            return;
        }

        // --------------------------------------------------------------------
        // 4) Já temos Ref + G1 + G2 + G3 -> monitorar G4 (desarme) monitorar APENAS o primeiro candle pós G3 para possível G4
        // --------------------------------------------------------------------
        if (gatilho3 != null) {
            Candle g3 = gatilho3;

            // Se ainda não avaliamos o candidato a G4
            if (!candidatoG4Avaliado) {

                // 4.1) Descartar insiders: candles completamente dentro do range do G3
                if (isInsiderEmRelacaoAoG3(g3, atual)) {
                    String msg = String.format(
                            "[%d] Candle insider em relação ao G3. Aguardando próximo candle para possível G4.",
                            idx
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);
                    return;
                }

                // 4.2) Este é o PRIMEIRO candle "útil" pós G3 (não insider)
                candidatoG4Avaliado = true;

                // G4 só pode ser depois de G3 e em candle diferente
                if (idx > idxG3 && isGatilho4(referencia, atual)) {
                    gatilho4 = atual;
                    idxG4    = idx;

                    if (padraoAtual != null) {
                        padraoAtual.setGatilho4(gatilho4);
                    }

                    String msg = String.format(
                            "[%d] Gatilho 4 identificado. Operação desarmada. Padrão encerrado.",
                            idx
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);

                    reset();
                } else {
                    String msg = String.format(
                            "[%d] Primeiro candle pós G3 não configurou Gatilho 4. Padrão encerrado sem G4.",
                            idx
                    );
                    if (logs != null) logs.add(msg);
                    log(msg);
                    reset();
                }

                return;
            }

            // Se já avaliamos o candidato a G4, o padrão já foi encerrado (com ou sem G4),
            // então não deveríamos mais cair aqui em condições normais.
            return;
        }


    }
   
    /**
     * Um candle é considerado insider em relação ao G3 se estiver completamente
     * contido dentro da máxima e mínima do G3.
     */

    private boolean isInsiderEmRelacaoAoG3(Candle g3, Candle c) {
        return c.getMaxima() <= g3.getMaxima()
                && c.getMinima() >= g3.getMinima();
    }


    // ============================================================
    //  BLOCO: REFERÊNCIA + G1
    // ============================================================

    private static class ParRefG1 {
        Candle referencia;
        Candle g1;
        ParRefG1(Candle referencia, Candle g1) {
            this.referencia = referencia;
            this.g1 = g1;
        }
    }

    /**
     * Encontra par (Referência, G1) partindo de um possível candle referência,
     * analisando os candles posteriores.
     *
     * Regras:
     *
     * Candle Referência:
     *  - Candle que demarca topo/fundo e que será rompido, posteriormente,
     *    contrariamente à sua tendência pelo Gatilho 1.
     *
     * Exemplo:
     *  - Candle COMPRADOR (referência):
     *      Se ALGUM candle VENDEDOR subsequente romper o FUNDO dele:
     *          -> esse VENDEDOR é o Gatilho 1
     *          -> o COMPRADOR rompido é a Referência.
     *
     *  - Candle VENDEDOR (referência):
     *      Se ALGUM candle COMPRADOR subsequente romper o TOPO dele:
     *          -> esse COMPRADOR é o Gatilho 1
     *          -> o VENDEDOR rompido é a Referência.
     *
     * A função procura o PRIMEIRO G1 que respeita essas regras.
     */

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

        boolean refComprador = ref.isCandleComprador();
        boolean refVendedor = ref.isCandleVendedor();

        // Se a referência não tiver tendência clara, ignoramos (doji/indeciso)
        if (!refComprador && !refVendedor) {
            return null;
        }

        // Percorre os candles POSTERIORES ao possível candle referência
        for (int i = idxRef + 1; i < candles.size(); i++) {
            Candle c = candles.get(i);

            // também ignoramos candles sem tendência clara como candidatos a G1
            boolean cComprador = c.isCandleComprador();
            boolean cVendedor = c.isCandleVendedor();
            if (!cComprador && !cVendedor) {
                continue;
            }

            if (refComprador && cVendedor) {
                // Referência COMPRADORA -> G1 VENDEDOR deve romper o FUNDO da referência
                if (c.getMinima() < ref.getMinima()) {
                    // Achamos o primeiro G1 que rompeu o fundo da referência
                    return new ParRefG1(ref, c);
                }

            } else if (refVendedor && cComprador) {
                // Referência VENDEDORA -> G1 COMPRADOR deve romper o TOPO da referência
                if (c.getMaxima() > ref.getMaxima()) {
                    // Achamos o primeiro G1 que rompeu o topo da referência
                    return new ParRefG1(ref, c);
                }
            }
        }

        // Nenhum G1 encontrado para esse possível candle referência
        return null;
    }


    // ============================================================
    //  BLOCO: GATILHO 2
    // ============================================================

    /**
    * Gatilho 2:
    *  - Candle que RETORNA à região do candle referência (fechamento dentro do range total).
    *  - É o último candle antes da nova quebra de tendência.
    *  - EXCEÇÃO: se for da mesma tendência de G1 e "romper a tendência" de G1
    *    (novo topo na compra / novo fundo na venda), também é G2 mesmo sem tocar a região.
    */

    private boolean isGatilho2(Candle referencia, Candle g1, Candle c) {

        boolean cComprador = c.isCandleComprador();
        boolean cVendedor = c.isCandleVendedor();

        if (!cComprador && !cVendedor) {
            return false;
        }

        // Fechamento dentro da região total do candle referência
        boolean fechamentoDentroRegiao =
                c.getFechamento() >= referencia.getMinima() &&
                c.getFechamento() <= referencia.getMaxima();

        boolean condicaoPrincipal = fechamentoDentroRegiao;

        boolean g1Comprador = g1.isCandleComprador();
        boolean g1Vendedor = g1.isCandleVendedor();

        boolean mesmaTendencia =
                (g1Comprador && cComprador) ||
                (g1Vendedor && cVendedor);

        boolean rompeTendenciaG1 =
                (g1Comprador && c.getMaxima() > g1.getMaxima()) ||
                (g1Vendedor && c.getMinima() < g1.getMinima());

        boolean condicaoExcecao = mesmaTendencia && rompeTendenciaG1;

        return condicaoPrincipal || condicaoExcecao;
    }

    /**
     * Se ultrapassar a região do candle referência:
     *  - Referência compradora: rompe TOPO
     *  - Referência vendedora: rompe FUNDO
     * Neste caso devemos buscar um novo candle referência a partir deste candle.
     */

    private boolean ultrapassaRegiaoReferencia(Candle referencia, Candle c) {
        if (referencia == null) return false;

        boolean refComprador = referencia.isCandleComprador();
        boolean refVendedor = referencia.isCandleVendedor();

        if (refComprador) {
            return c.getMaxima() > referencia.getMaxima();
        } else if (refVendedor) {
            return c.getMinima() < referencia.getMinima();
        }

        return false;
    }

   
    /**
     * Nova quebra de tendência sem atingir a região da referência:
     *
     * Ideia:
     *  - Se G1 é comprador:
     *      -> candle atual é vendedor
     *      -> mínima do candle atual < mínima do G1 (quebra da perna de compra)
     *      -> fechamento NÃO está dentro da região da referência
     *
     *  - Se G1 é vendedor:
     *      -> candle atual é comprador
     *      -> máxima do candle atual > máxima do G1
     *      -> fechamento NÃO está dentro da região da referência
     */

    private boolean novaQuebraDeTendenciaSemAtingirRegiao(Candle referencia,
                                                          Candle g1,
                                                          Candle c) {
        if (referencia == null || g1 == null) return false;

        boolean fechamentoDentroRegiao =
                c.getFechamento() >= referencia.getMinima() &&
                c.getFechamento() <= referencia.getMaxima();

        if (fechamentoDentroRegiao) {
            return false; // se tocou a região, não é "sem atingir a região"
        }

        boolean g1Comprador = g1.isCandleComprador();
        boolean g1Vendedor = g1.isCandleVendedor();

        boolean cComprador = c.isCandleComprador();
        boolean cVendedor = c.isCandleVendedor();

        if (g1Comprador && cVendedor) {
            // quebra forte contra a compra
            return c.getMinima() < g1.getMinima();
        } else if (g1Vendedor && cComprador) {
            // quebra forte contra a venda
            return c.getMaxima() > g1.getMaxima();
        }

        return false;
    }



    // ============================================================
    //  BLOCO: GATILHO 3
    // ============================================================

    /**
     * G3:
     *  - Candle posterior ao G2.
     *  - Mesma tendência de G1.
     *  - Ref COMPRADORA:
     *      * topo de G3 < topo da referência
     *      * mínima de G3 < mínima de G2 (rompe fundo do G2)
     *  - Ref VENDEDORA:
     *      * topo de G3 > topo da referência
     *      * máxima de G3 > máxima de G2 (rompe topo do G2)
     *  (A invalidação por rompimento da referência é tratada em outro método.)
     */

    private boolean isGatilho3(Candle referencia, Candle g1, Candle g2, Candle g3) {

        boolean g1Comprador = g1.isCandleComprador();
        boolean g1Vendedor = g1.isCandleVendedor();

        boolean g3Comprador = g3.isCandleComprador();
        boolean g3Vendedor = g3.isCandleVendedor();

        boolean mesmaTendenciaG1 =
                (g1Comprador && g3Comprador) ||
                (g1Vendedor && g3Vendedor);

        if (!mesmaTendenciaG1) {
            return false;
        }

        boolean refComprador = referencia.isCandleComprador();
        boolean refVendedor = referencia.isCandleVendedor();

        if (refComprador) {
            boolean topoMaisBaixo = g3.getMaxima() < referencia.getMaxima();
            boolean rompeFundoG2 = g3.getMinima() < g2.getMinima();
            return topoMaisBaixo && rompeFundoG2;
        } else if (refVendedor) {
            boolean topoMaiorQueRef = g3.getMaxima() > referencia.getMaxima();
            boolean rompeTopoG2 = g3.getMaxima() > g2.getMaxima();
            return topoMaiorQueRef && rompeTopoG2;
        }

        return false;
    }

    /**
     * Invalidação após o G2:
     *  - Ref COMPRADORA: se qualquer candle após G2 romper o topo da referência -> invalida.
     *  - Ref VENDEDORA: se romper o fundo da referência -> invalida.
     */

    private boolean rompeReferenciaAposG2(Candle referencia, Candle c) {
        if (referencia == null) return false;

        boolean refComprador = referencia.isCandleComprador();
        boolean refVendedor = referencia.isCandleVendedor();

        if (refComprador) {
            return c.getMaxima() > referencia.getMaxima();
        } else if (refVendedor) {
            return c.getMinima() < referencia.getMinima();
        }

        return false;
    }

    // ============================================================
    //  BLOCO: GATILHO 4
    // ============================================================

    /**
     * G4:
     *  - Desarma a operação.
     *  - Ref COMPRADORA: topo da referência rompido pelo candle G4.
     *  - Ref VENDEDORA: fundo da referência rompido pelo candle G4.
     */

    private boolean isGatilho4(Candle referencia, Candle c) {
        if (referencia == null) return false;

        boolean refComprador = referencia.isCandleComprador();
        boolean refVendedor = referencia.isCandleVendedor();

        if (refComprador) {
            return c.getMaxima() > referencia.getMaxima();
        } else if (refVendedor) {
            return c.getMinima() < referencia.getMinima();
        }

        return false;
    }


    // ============================================================
    //  Getters (se quiser inspecionar estado atual externamente)
    // ============================================================

    public Candle getReferencia() {
        return referencia;
    }

    public Candle getGatilho1() {
        return gatilho1;
    }

    public Candle getGatilho2() {
        return gatilho2;
    }

    public Candle getGatilho3() {
        return gatilho3;
    }

    public Candle getGatilho4() {
        return gatilho4;
    }

    public PadraoGatilho getPadraoAtual() {
        return padraoAtual;
    }
}