Subversion Repositories Integrator Subversion

Rev

Rev 775 | 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.kronus.core.PadraoGatilho.TipoPadrao;
import br.com.sl.domain.model.Candle;
import br.com.sl.domain.util.BigDecimalUtils;

/**
 * Detector de padrões de gatilhos (GR, G1, G2, G3)
 * completamente integrado com enum TipoPadrao.
 */

public class DetectorGatilhos {

    private final boolean logAtivo;
    private int idxProximaAnaliseTempoReal = 0;

    /**
     * Buffer opcional para capturar logs em memória (modo debug).
     * Se for null, não acumula; se não for null, log() adiciona aqui também.
     */

    private List<String> bufferDebug;

    public DetectorGatilhos() {
        this(false);
    }

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

    private void log(String msg) {
        if (logAtivo) {
            System.out.println(msg);
        }
        if (bufferDebug != null) {
            bufferDebug.add(msg);
        }
    }

    // ================================================================
    // Estrutura interna de retorno
    // ================================================================
    private static class ResultadoPadrao {
        PadraoGatilho padrao;
        int lastIndex;
        int proximoInicio;

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

    // ================================================================
    // Criador de padrão parcial
    // ================================================================
    private ResultadoPadrao criarResultadoParcialComG2(Candle ref,
                                                       Candle g1,
                                                       Candle g2,
                                                       int lastIndex,
                                                       int idxGR) {

        PadraoGatilho padrao = new PadraoGatilho();
        padrao.setReferencia(ref);
        padrao.setGatilho1(g1);
        padrao.setGatilho2(g2);
        padrao.setGatilho3(null);
        padrao.setGatilho4(null);
        padrao.setTipoPadrao(TipoPadrao.PARCIAL_G2); // ENUM

        log(String.format("Padrão PARCIAL_G2: GR[%s], G1[%s], G2[%s]",
                ref.getDataHora(), g1.getDataHora(), g2.getDataHora()));

        return new ResultadoPadrao(padrao, lastIndex, idxGR + 1);
    }

    // ================================================================
    // 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 isDirecional(Candle c) {
        return c.isCandleComprador() || c.isCandleVendedor();
    }

    private boolean fechamentoDentroRegiaoGR(Candle c, Candle gr) {
        return BigDecimalUtils.ehMaiorOuIgualQue(c.getFechamento(), gr.getMinima())
            && BigDecimalUtils.ehMenorOuIgualQue(c.getFechamento(), gr.getMaxima());
    }

    private BigDecimal fibExtend(BigDecimal origem, BigDecimal destino, BigDecimal fator) {
        return origem.add(destino.subtract(origem).multiply(fator));
    }

    // ================================================================
    // API PRINCIPAL – BACKTEST
    // ================================================================
 public List<PadraoGatilho> identificarPadroes(List<Candle> candles) {

     List<PadraoGatilho> padroes = new ArrayList<>();
     int n = candles.size();
     if (n < 4) return padroes;

     log("===== INÍCIO BACKTEST (varrendo todos os candles como possível A) =====");

     for (int idxRef = 0; idxRef < n - 3; idxRef++) {

         log(String.format("---- Nova tentativa: idxRef = %d (candle #%d) ----", idxRef, idxRef + 1));

         ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef);

         if (resultado == null) {
             log(String.format("idxRef=%d: ResultadoPadrao null, seguindo para próximo candle.", idxRef));
             continue;
         }

         if (resultado.padrao != null) {
             PadraoGatilho p = resultado.padrao;
             Candle gr = p.getReferencia();
             Candle g1 = p.getGatilho1();
             Candle g2 = p.getGatilho2();
             Candle g3 = p.getGatilho3();

             log(String.format(
                     ">> PADRÃO ENCONTRADO a partir de idxRef=%d: GR[%s], G1[%s], G2[%s], G3[%s], tipo=%s",
                     idxRef,
                     (gr != null ? gr.getDataHora() : "null"),
                     (g1 != null ? g1.getDataHora() : "null"),
                     (g2 != null ? g2.getDataHora() : "null"),
                     (g3 != null ? g3.getDataHora() : "null"),
                     p.getTipoPadrao()
             ));

             padroes.add(p);
         } else {
             log(String.format("idxRef=%d: nenhuma formação de padrão (sem GR/G1/G2/G3 válidos).", idxRef));
         }
     }

     log("===== FIM BACKTEST (varredura completa) =====");
     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 {
            log(String.format("Candle[%d] neutro; avançando.", idxRef + 1));
            return new ResultadoPadrao(null, idxRef, idxRef + 1);
        }
    }
   
         // ================================================================
         // DEBUG BACKTEST COMPLETO
         // ================================================================
         public List<String> debugarBacktestCompleto(List<Candle> candles) {
             List<String> relatorio = new ArrayList<>();
             List<String> antigo = this.bufferDebug;
       
             this.bufferDebug = relatorio;
             try {
                 log("####################################################");
                 log("DEBUG BACKTEST COMPLETO - iniciar identificarPadroes()");
                 identificarPadroes(candles);
                 log("DEBUG BACKTEST COMPLETO - fim identificarPadroes()");
                 log("####################################################");
             } finally {
                 this.bufferDebug = antigo;
             }
       
             return relatorio;
         }


    // ================================================================
    // PADRÃO COMPRADOR (VENDA)
    // ================================================================
    private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxA) {

        int n = candles.size();
        Candle candleA = candles.get(idxA);

        if (!candleA.isCandleComprador()) {
            return new ResultadoPadrao(null, idxA, idxA + 1);
        }

        int lastIndex = idxA;

        log("[VENDER] Iniciando em A[" + (idxA + 1) + "] " + candleA.getDataHora());

        // --------------------------------------------------------------
        // Busca candle B direcional (mudança de direção)
        // --------------------------------------------------------------
        int idxProxDirecional = -1;

        for (int i = idxA + 1; i < n; i++) {

            Candle c = candles.get(i);

            if (!isDirecional(c) || isInside(candles, i)) {
                log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside)", i + 1));
                continue;
            }

            idxProxDirecional = i;
            break;
        }

        if (idxProxDirecional == -1) {
            log("[VENDER] Nenhum candle direcional após A; abortando padrão.");
            return new ResultadoPadrao(null, idxA, idxA + 1);
        }

        Candle prox = candles.get(idxProxDirecional);

        if (!prox.isCandleVendedor()) {
            log(String.format("[VENDER] Próximo direcional [%d] não é vendedor; A não vira referência.", idxProxDirecional + 1));
            return new ResultadoPadrao(null, idxA, idxA + 1);
        }

        // Candidato à referência
        Candle candidatoRef = candleA;
        int idxCandidatoRef = idxA;
        log(String.format("[VENDER] CandidatoRef inicial = idx %d", idxCandidatoRef + 1));

        Candle g1 = null;
        int idxG1 = -1;

        // --------------------------------------------------------------
        // Busca GR dinâmico e G1
        // --------------------------------------------------------------
        for (int i = idxA + 1; i < n; i++) {

            Candle c = candles.get(i);

            if (!isDirecional(c) || isInside(candles, i)) {
                log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1));
                continue;
            }

            lastIndex = i;

            // G1
            if (c.isCandleVendedor()
                && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {

                g1 = c;
                idxG1 = i;
                log(String.format("[VENDER] G1 encontrado em [%d], rompendo fundo de candRef[%d]",
                        idxG1 + 1, idxCandidatoRef + 1));
                break;
            }

            // Atualização do GR dinâmico
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
                candidatoRef = c;
                idxCandidatoRef = i;
                log(String.format("[VENDER] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1));
            }
        }

        if (g1 == null) {
            log("[VENDER] Não formou G1; recomeçando após A.");
            return new ResultadoPadrao(null, lastIndex, idxA + 1);
        }

        Candle gr = candidatoRef;
        int idxGR = idxCandidatoRef;

        log(String.format("[VENDER] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1));

        // --------------------------------------------------------------
        // Fibonacci 200% (origem = máxG1, destino = mínG1)
        // --------------------------------------------------------------
        BigDecimal fib200 = fibExtend(g1.getMaxima(), g1.getMinima(), new BigDecimal("2"));
        log(String.format("[VENDER] Fibo200 G1[%d] = %s", idxG1 + 1, fib200.toPlainString()));

        // --------------------------------------------------------------
        // Busca G2 e G3
        // --------------------------------------------------------------
        Candle g2 = null;
        int idxG2 = -1;
        Candle g3 = null;
        int idxG3 = -1;

        for (int i = idxG1 + 1; i < n; i++) {

            Candle c = candles.get(i);

            if (!isDirecional(c) || isInside(candles, i)) {
                log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1));
                continue;
            }

            lastIndex = i;

            // DESCARTES:

            // Rompe topo do GR
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
                log(String.format("[VENDER] GR[%d]: candle[%d] rompeu topo do GR. Descartando padrão.",
                        idxGR + 1, i + 1));
                return new ResultadoPadrao(null, i, idxGR + 1);
            }

            // Rompe nível Fib 200%
            if (BigDecimalUtils.ehMenorOuIgualQue(c.getMinima(), fib200)) {
                log(String.format("[VENDER] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.",
                        idxGR + 1, idxG1 + 1, i + 1));
                return new ResultadoPadrao(null, i, idxGR + 1);
            }

            // ==========================
            // G2
            // ==========================
            if (c.isCandleComprador()
                && fechamentoDentroRegiaoGR(c, gr)) {

                if (g2 == null) {
                    g2 = c;
                    idxG2 = i;
                    log(String.format("[VENDER] GR[%d], G1[%d]: candidato G2 em [%d]",
                            idxGR + 1, idxG1 + 1, idxG2 + 1));

                } else if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima())) {
                    g2 = c;
                    idxG2 = i;
                    log(String.format("[VENDER] GR[%d], G1[%d]: G2 atualizado em [%d]",
                            idxGR + 1, idxG1 + 1, idxG2 + 1));
                }

                continue;
            }

            // ==========================
            // G3
            // ==========================
            if (g2 != null && c.isCandleVendedor()) {

                boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
                boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima());

                if (rompeFundoG2 && topoMenorOuIgualGR) {
                    g3 = c;
                    idxG3 = i;
                    log(String.format("[VENDER] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)",
                            idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1));
                    break;
                }
            }
        }

        // Nenhum G3 → padrão parcial
        if (g3 == null) {

            if (g2 != null) {
                log(String.format("[VENDER] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.",
                        idxGR + 1, idxG1 + 1, idxG2 + 1));
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR);
            }

            log(String.format("[VENDER] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.",
                    idxGR + 1, idxG1 + 1));
            return new ResultadoPadrao(null, lastIndex, idxGR + 1);
        }

        // --------------------------------------------------------------
        // PADRÃO COMPLETO (COM G3)
        // --------------------------------------------------------------
        PadraoGatilho padrao = new PadraoGatilho();
        padrao.setReferencia(gr);
        padrao.setGatilho1(g1);
        padrao.setGatilho2(g2);
        padrao.setGatilho3(g3);
        padrao.setGatilho4(null);
        padrao.setTipoPadrao(TipoPadrao.COMPLETO_G3);

        log(String.format("[VENDER] Padrão COMPLETO_G3: GR[%d], G1[%d], G2[%d], G3[%d]",
                idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1));

        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
    }

    // ================================================================
    // PADRÃO VENDEDOR (COMPRA)
    // ================================================================
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) {

        int n = candles.size();
        Candle candleA = candles.get(idxA);

        if (!candleA.isCandleVendedor())
            return new ResultadoPadrao(null, idxA, idxA + 1);

        int lastIndex = idxA;

        log("[COMPRAR] Iniciando em A[" + (idxA + 1) + "] " + candleA.getDataHora());

        // --------------------------------------------------------------
        // Busca B direcional
        // --------------------------------------------------------------
        int idxProxDirecional = -1;

        for (int i = idxA + 1; i < n; i++) {

            Candle c = candles.get(i);

            if (!isDirecional(c) || isInside(candles, i)) {
                log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside)", i + 1));
                continue;
            }

            idxProxDirecional = i;
            break;
        }

        if (idxProxDirecional == -1) {
            log("[COMPRAR] Nenhum candle direcional após A; abortando padrão.");
            return new ResultadoPadrao(null, idxA, idxA + 1);
        }

        Candle prox = candles.get(idxProxDirecional);

        if (!prox.isCandleComprador()) {
            log(String.format("[COMPRAR] Próximo direcional [%d] não é comprador; A não vira referência.",
                    idxProxDirecional + 1));
            return new ResultadoPadrao(null, idxA, idxA + 1);
        }

        // GR dinâmico
        Candle candidatoRef = candleA;
        int idxCandidatoRef = idxA;

        Candle g1 = null;
        int idxG1 = -1;

        log(String.format("[COMPRAR] CandidatoRef inicial = idx %d", idxCandidatoRef + 1));

        // --------------------------------------------------------------
        // Busca G1 e GR
        // --------------------------------------------------------------
        for (int i = idxA + 1; i < n; i++) {

            Candle c = candles.get(i);
            if (!isDirecional(c) || isInside(candles, i)) {
                log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1));
                continue;
            }

            lastIndex = i;

            if (c.isCandleComprador()
                && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {

                g1 = c;
                idxG1 = i;
                log(String.format("[COMPRAR] G1 encontrado em [%d], rompendo topo de candRef[%d]",
                        idxG1 + 1, idxCandidatoRef + 1));
                break;
            }

            if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
                candidatoRef = c;
                idxCandidatoRef = i;
                log(String.format("[COMPRAR] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1));
            }
        }

        if (g1 == null) {
            log("[COMPRAR] Não formou G1; recomeçando após A.");
            return new ResultadoPadrao(null, lastIndex, idxA + 1);
        }

        Candle gr = candidatoRef;
        int idxGR = idxCandidatoRef;

        log(String.format("[COMPRAR] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1));

        // --------------------------------------------------------------
        // Fibonacci -100% (aqui usando fator 2 entre mín e máx)
        // --------------------------------------------------------------
        BigDecimal fib200MinMax = fibExtend(g1.getMinima(), g1.getMaxima(), new BigDecimal("2"));
        log(String.format("[COMPRAR] Fibo200 G1[%d] = %s", idxG1 + 1, fib200MinMax.toPlainString()));

        // --------------------------------------------------------------
        // Busca G2 e G3
        // --------------------------------------------------------------
        Candle g2 = null;
        int idxG2 = -1;
        Candle g3 = null;
        int idxG3 = -1;

        for (int i = idxG1 + 1; i < n; i++) {

            Candle c = candles.get(i);
            if (!isDirecional(c) || isInside(candles, i)) {
                log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1));
                continue;
            }

            lastIndex = i;

            // DESCARTES

            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
                log(String.format("[COMPRAR] GR[%d]: candle[%d] rompeu fundo do GR. Descartando padrão.",
                        idxGR + 1, i + 1));
                return new ResultadoPadrao(null, i, idxGR + 1);
            }

            if (BigDecimalUtils.ehMaiorQue(c.getMinima(), fib200MinMax)) {
                log(String.format("[COMPRAR] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.",
                        idxGR + 1, idxG1 + 1, i + 1));
                return new ResultadoPadrao(null, i, idxGR + 1);
            }

            // ==========================
            // G2
            // ==========================
            if (c.isCandleVendedor() && fechamentoDentroRegiaoGR(c, gr)) {

                if (g2 == null) {
                    g2 = c;
                    idxG2 = i;
                    log(String.format("[COMPRAR] GR[%d], G1[%d]: candidato G2 em [%d]",
                            idxGR + 1, idxG1 + 1, idxG2 + 1));

                } else if (BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima())) {
                    g2 = c;
                    idxG2 = i;
                    log(String.format("[COMPRAR] GR[%d], G1[%d]: G2 atualizado em [%d]",
                            idxGR + 1, idxG1 + 1, idxG2 + 1));
                }

                continue;
            }

            // ==========================
            // G3
            // ==========================
            if (g2 != null && c.isCandleComprador()) {

                boolean rompeTopo = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
                boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima());

                if (rompeTopo && fundoMaiorOuIgualGR) {
                    g3 = c;
                    idxG3 = i;
                    log(String.format("[COMPRAR] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)",
                            idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1));
                    break;
                }
            }
        }

        // ====================================
        // Nenhum G3 → padrão parcial
        // ====================================
        if (g3 == null) {

            if (g2 != null) {
                log(String.format("[COMPRAR] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.",
                        idxGR + 1, idxG1 + 1, idxG2 + 1));
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR);
            }

            log(String.format("[COMPRAR] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.",
                    idxGR + 1, idxG1 + 1));
            return new ResultadoPadrao(null, lastIndex, idxGR + 1);
        }

        // ====================================
        // PADRÃO COMPLETO (COM G3)
        // ====================================
        PadraoGatilho padrao = new PadraoGatilho();
        padrao.setReferencia(gr);
        padrao.setGatilho1(g1);
        padrao.setGatilho2(g2);
        padrao.setGatilho3(g3);
        padrao.setGatilho4(null);
        padrao.setTipoPadrao(TipoPadrao.COMPLETO_G3);

        log(String.format("[COMPRAR] Padrão COMPLETO_G3: GR[%d], G1[%d], G2[%d], G3[%d]",
                idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1));

        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
    }

    // ================================================================
    // TEMPO REAL
    // ================================================================
    public void resetTempoReal() {
        idxProximaAnaliseTempoReal = 0;
    }

    public PadraoGatilho processarCandleTempoReal(List<Candle> candles) {

        int n = candles.size();
        if (n < 4) return null;

        while (idxProximaAnaliseTempoReal < n - 3) {

            ResultadoPadrao r = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal);

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

            int next = Math.max(r.proximoInicio, idxProximaAnaliseTempoReal + 1);
            idxProximaAnaliseTempoReal = next;

            if (r.padrao != null)
                return r.padrao;
        }

        return null;
    }

    // ================================================================
    // DEBUG - para usar em JSF
    // ================================================================
    /**
     * Roda a lógica de detecção a partir de um índice específico e devolve
     * um "relatório" em forma de lista de strings com tudo que aconteceu.
     *
     * NÃO altera idxProximaAnaliseTempoReal.
     */

    public List<String> debugarAPartirDoIndice(List<Candle> candles, int idxInicio) {

        List<String> relatorio = new ArrayList<>();

        List<String> antigo = bufferDebug;
        bufferDebug = relatorio;

        try {
            log("============= DEBUG =============");
            log("Iniciando análise no índice " + idxInicio);
            detectarPadraoAPartir(candles, idxInicio);
            log("Fim da análise");
            log("================================");
        }
        finally {
            bufferDebug = antigo;
        }

        return relatorio;
    }

}