package br.com.kronus.core;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import br.com.kronus.core.PadraoGatilho.TipoPadrao;
/**
* 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;
p.
setIdAtivo(p.
getReferencia().
getNomeAtivo());
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
;
}
}