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.
*/
//package br.com.kronus.strategy.gatilhos;
import java.util.ArrayList;
import java.util.List;
/**
* Detector de padrões de gatilhos (GR, G1, G2, G3, G4)
* trabalhando tanto em modo backtest quanto em modo tempo real.
*
* Regras resumidas:
*
* GR comprador (A):
* - Candle comprador.
* - Último candle da tendência compradora.
* - Maior topo da pernada compradora.
* - Após ele, tendência muda para vendedora.
* - Um vendedor subsequente rompe o fundo de A => G1 vendedor, A vira GR.
*
* GR vendedor (B):
* - Candle vendedor.
* - Último candle da tendência vendedora.
* - Menor fundo da pernada vendedora.
* - Após ele, tendência muda para compradora.
* - Um comprador subsequente rompe o topo de B => G1 comprador, B vira GR.
*
* G2 comprador (a partir de GR comprador):
* - Após G1 (vendedor), surge nova tendência compradora.
* - O ÚLTIMO candle dessa tendência compradora tem fechamento dentro da região do GR.
* - Se nessa tendência compradora alguma barra romper o topo do GR, o padrão é descartado.
* - Se a tendência terminar sem que o último comprador feche dentro da região do GR, descarta.
*
* G3 comprador:
* - Após G2, o próximo candle deve ser vendedor.
* - Em uma sequência vendedora, algum vendedor:
* * rompe o fundo de G2 (mínima < mínima de G2) e
* * tem topo <= topo do GR
* => esse candle é G3.
*
* G4 comprador:
* - Próximo candle após G3.
* - Se romper o topo do GR (máxima > máxima do GR) => G4.
* - Desarma a operação, padrão é encerrado, nova análise começa após esse candle.
*
* G2 vendedor (a partir de GR vendedor):
* - Após G1 (comprador), surge nova tendência vendedora.
* - O ÚLTIMO candle dessa tendência vendedora tem fechamento dentro da região do GR.
* - Se nessa tendência vendedora alguma barra romper o fundo do GR, o padrão é descartado.
*
* G3 vendedor:
* - Após G2, o próximo candle deve ser comprador.
* - Em uma sequência compradora, algum comprador:
* * rompe o topo de G2 (máxima > máxima de G2) e
* * tem fundo >= fundo do GR
* => esse candle é G3.
*
* G4 vendedor:
* - Próximo candle após G3.
* - Se romper o fundo do GR (mínima < mínima do GR) => G4.
*
* Outside em relação ao GR:
* - Qualquer candle C após o GR que:
* * C.maxima > GR.maxima E
* * C.minima < GR.minima
* => padrão atual é descartado, nova análise começa após C.
*
* Obs.: Quando um ciclo termina (padrão encontrado ou descartado),
* a próxima análise SEMPRE começa após o último candle usado.
*/
public class DetectorGatilhos
{
private final boolean logAtivo
;
// Estado apenas para o modo tempo real
private int idxProximaAnaliseTempoReal =
0;
public DetectorGatilhos
() {
this(false);
}
public DetectorGatilhos
(boolean logAtivo
) {
this.
logAtivo = logAtivo
;
}
private void log
(String msg
) {
if (logAtivo
) {
System.
out.
println(msg
);
}
}
private static class ResultadoPadrao
{
PadraoGatilho padrao
; // pode ser null (quando não houve padrão válido)
int lastIndex
; // último índice de candle usado no ciclo
ResultadoPadrao
(PadraoGatilho padrao,
int lastIndex
) {
this.
padrao = padrao
;
this.
lastIndex = lastIndex
;
}
}
// =====================================================================
// API PRINCIPAL – BACKTEST (lista completa de candles)
// =====================================================================
public List<PadraoGatilho
> identificarPadroes
(List<Candle
> candles
) {
List<PadraoGatilho
> padroes =
new ArrayList<>();
int n = candles.
size();
if (n
< 4) {
return padroes
;
}
int idxRef =
0;
while (idxRef
< n -
3) {
ResultadoPadrao resultado = detectarPadraoAPartir
(candles, idxRef
);
if (resultado ==
null) {
idxRef++
;
continue;
}
if (resultado.
padrao !=
null) {
padroes.
add(resultado.
padrao);
}
// Próxima análise sempre começa após o último candle usado no ciclo
idxRef =
Math.
max(resultado.
lastIndex +
1, idxRef +
1);
}
return padroes
;
}
private ResultadoPadrao detectarPadraoAPartir
(List<Candle
> candles,
int idxRef
) {
Candle ref = candles.
get(idxRef
);
if (ref.
isCandleComprador()) {
return detectarPadraoComprador
(candles, idxRef
);
} else if (ref.
isCandleVendedor()) {
return detectarPadraoVendedor
(candles, idxRef
);
} else {
// candle sem tendência não serve como GR
return new ResultadoPadrao
(null, idxRef
);
}
}
// =====================================================================
// CASO 1 – GR COMPRADOR (candle A)
// =====================================================================
private ResultadoPadrao detectarPadraoComprador
(List<Candle
> candles,
int idxRef
) {
int n = candles.
size();
Candle ref = candles.
get(idxRef
); // candidato a GR
int lastIndex = idxRef
;
// 0) Validar se idxRef pode ser GR comprador (A)
if (!ref.
isCandleComprador()) {
return new ResultadoPadrao
(null, idxRef
);
}
// 0.1 – início da pernada compradora que termina em A
int inicioTrendCompra = idxRef
;
for (int i = idxRef -
1; i
>=
0; i--
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador()) {
break;
}
inicioTrendCompra = i
;
}
// 0.2 – primeiro candle direcional após A
int idxPrimeiroDirecionalAposRef = -
1;
for (int i = idxRef +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador() && !c.
isCandleVendedor()) {
continue;
}
// 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
if (isOutsideReferencia
(ref, c
)) {
lastIndex = i
;
log
(String.
format(
"RefC[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.",
idxRef, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
idxPrimeiroDirecionalAposRef = i
;
break;
}
if (idxPrimeiroDirecionalAposRef == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
Candle primeiroDirecional = candles.
get(idxPrimeiroDirecionalAposRef
);
if (!primeiroDirecional.
isCandleVendedor()) {
// primeiro direcional após A ainda é comprador → A não é último da pernada
return new ResultadoPadrao
(null, idxRef
);
}
// 0.3 – A tem o maior topo da pernada compradora
double topoRef = ref.
getMaxima();
for (int i = inicioTrendCompra
; i
<= idxRef
; i++
) {
Candle c = candles.
get(i
);
if (c.
getMaxima() > topoRef
) {
return new ResultadoPadrao
(null, idxRef
);
}
}
int idxPrimeiroVendedor = idxPrimeiroDirecionalAposRef
;
// 1) Encontrar G1 (vendedor que rompe o fundo do GR)
int idxG1 = -
1;
Candle g1 =
null;
for (int i = idxPrimeiroVendedor
; i
< n
; i++
) {
Candle c = candles.
get(i
);
// 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
if (isOutsideReferencia
(ref, c
)) {
lastIndex = i
;
log
(String.
format(
"RefC[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.",
idxRef, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (!c.
isCandleVendedor()) {
// tendência vendedora terminou antes do rompimento
lastIndex = i -
1;
return new ResultadoPadrao
(null, lastIndex
);
}
lastIndex = i
;
if (c.
getMinima() < ref.
getMinima()) {
idxG1 = i
;
g1 = c
;
break;
}
}
if (idxG1 == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
log
(String.
format("RefC[%d] (GR) => G1 (vendedor) em [%d]", idxRef, idxG1
));
// =====================================================
// A PARTIR DAQUI (G2, G3, G4) NÃO VERIFICAMOS MAIS OUTSIDE
// =====================================================
// 2) Encontrar G2 (retorno comprador à região do GR)
int idxPrimeiroComprador = -
1;
for (int i = idxG1 +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador() && !c.
isCandleVendedor()) {
continue;
}
if (c.
isCandleComprador()) {
idxPrimeiroComprador = i
;
break;
} else {
lastIndex = i
;
}
}
if (idxPrimeiroComprador == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
boolean rompeuTopoRef =
false;
Candle ultimoCompradorTrend =
null;
int idxUltimoCompradorTrend = -
1;
for (int i = idxPrimeiroComprador
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador()) {
break;
}
ultimoCompradorTrend = c
;
idxUltimoCompradorTrend = i
;
lastIndex = i
;
if (c.
getMaxima() > ref.
getMaxima()) {
rompeuTopoRef =
true;
break;
}
}
if (rompeuTopoRef || idxUltimoCompradorTrend == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
boolean fechamentoDentroRegiaoRef =
ultimoCompradorTrend.
getFechamento() >= ref.
getMinima() &&
ultimoCompradorTrend.
getFechamento() <= ref.
getMaxima();
if (!fechamentoDentroRegiaoRef
) {
return new ResultadoPadrao
(null, lastIndex
);
}
Candle g2 = ultimoCompradorTrend
;
int idxG2 = idxUltimoCompradorTrend
;
log
(String.
format("RefC[%d], G1[%d] => G2 (comprador) em [%d]", idxRef, idxG1, idxG2
));
// 3) Encontrar G3 (vendedor rompendo fundo de G2, topo <= topo do GR)
int idxPrimeiroVendedorAposG2 = -
1;
for (int i = idxG2 +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador() && !c.
isCandleVendedor()) {
continue;
}
if (c.
isCandleVendedor()) {
idxPrimeiroVendedorAposG2 = i
;
break;
} else {
lastIndex = i
;
// pela sua regra, o próximo após G2 deveria ser vendedor
return new ResultadoPadrao
(null, lastIndex
);
}
}
if (idxPrimeiroVendedorAposG2 == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
int idxG3 = -
1;
Candle g3 =
null;
for (int i = idxPrimeiroVendedorAposG2
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleVendedor()) {
lastIndex = i -
1;
break;
}
lastIndex = i
;
boolean rompeFundoG2 = c.
getMinima() < g2.
getMinima();
boolean topoMenorOuIgualRef = c.
getMaxima() <= ref.
getMaxima();
if (rompeFundoG2
&& topoMenorOuIgualRef
) {
idxG3 = i
;
g3 = c
;
break;
}
}
if (idxG3 == -
1 || g3 ==
null) {
return new ResultadoPadrao
(null, lastIndex
);
}
log
(String.
format("RefC[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d]",
idxRef, idxG1, idxG2, idxG3
));
// 4) G4 – próximo candle rompe topo do GR
Candle g4 =
null;
int proxIdx = idxG3 +
1;
if (proxIdx
< n
) {
Candle c = candles.
get(proxIdx
);
lastIndex = proxIdx
;
if (c.
getMaxima() > ref.
getMaxima()) {
g4 = c
;
log
(String.
format("RefC[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
idxRef, idxG1, idxG2, idxG3, proxIdx
));
} else {
log
(String.
format("RefC[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
idxRef, idxG1, idxG2, idxG3, proxIdx
));
}
}
PadraoGatilho padrao =
new PadraoGatilho
();
padrao.
setReferencia(ref
);
padrao.
setGatilho1(g1
);
padrao.
setGatilho2(g2
);
padrao.
setGatilho3(g3
);
padrao.
setGatilho4(g4
);
return new ResultadoPadrao
(padrao, lastIndex
);
}
// =====================================================================
// CASO 2 – GR VENDEDOR (candle B)
// =====================================================================
private ResultadoPadrao detectarPadraoVendedor
(List<Candle
> candles,
int idxRef
) {
int n = candles.
size();
Candle ref = candles.
get(idxRef
); // candidato a GR vendedor
int lastIndex = idxRef
;
// 0) Validar se idxRef pode ser GR vendedor (B)
if (!ref.
isCandleVendedor()) {
return new ResultadoPadrao
(null, idxRef
);
}
// 0.1 – início da pernada vendedora que termina em B
int inicioTrendVenda = idxRef
;
for (int i = idxRef -
1; i
>=
0; i--
) {
Candle c = candles.
get(i
);
if (!c.
isCandleVendedor()) {
break;
}
inicioTrendVenda = i
;
}
// 0.2 – primeiro candle direcional após B
int idxPrimeiroDirecionalAposRef = -
1;
for (int i = idxRef +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador() && !c.
isCandleVendedor()) {
continue;
}
// 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
if (isOutsideReferencia
(ref, c
)) {
lastIndex = i
;
log
(String.
format(
"RefV[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.",
idxRef, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
idxPrimeiroDirecionalAposRef = i
;
break;
}
if (idxPrimeiroDirecionalAposRef == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
Candle primeiroDirecional = candles.
get(idxPrimeiroDirecionalAposRef
);
if (!primeiroDirecional.
isCandleComprador()) {
// primeiro direcional após B ainda é vendedor
return new ResultadoPadrao
(null, idxRef
);
}
// 0.3 – B tem o MENOR fundo da pernada vendedora
double fundoRef = ref.
getMinima();
for (int i = inicioTrendVenda
; i
<= idxRef
; i++
) {
Candle c = candles.
get(i
);
if (c.
getMinima() < fundoRef
) {
return new ResultadoPadrao
(null, idxRef
);
}
}
int idxPrimeiroComprador = idxPrimeiroDirecionalAposRef
;
// 1) Encontrar G1 (comprador que rompe topo do GR)
int idxG1 = -
1;
Candle g1 =
null;
for (int i = idxPrimeiroComprador
; i
< n
; i++
) {
Candle c = candles.
get(i
);
// 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
if (isOutsideReferencia
(ref, c
)) {
lastIndex = i
;
log
(String.
format(
"RefV[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.",
idxRef, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (!c.
isCandleComprador()) {
lastIndex = i -
1;
return new ResultadoPadrao
(null, lastIndex
);
}
lastIndex = i
;
if (c.
getMaxima() > ref.
getMaxima()) {
idxG1 = i
;
g1 = c
;
break;
}
}
if (idxG1 == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
log
(String.
format("RefV[%d] (GR) => G1 (comprador) em [%d]", idxRef, idxG1
));
// =====================================================
// A PARTIR DAQUI (G2, G3, G4) NÃO VERIFICAMOS MAIS OUTSIDE
// =====================================================
// 2) Encontrar G2 (retorno vendedor à região do GR)
int idxPrimeiroVendedor = -
1;
for (int i = idxG1 +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador() && !c.
isCandleVendedor()) {
continue;
}
if (c.
isCandleVendedor()) {
idxPrimeiroVendedor = i
;
break;
} else {
lastIndex = i
;
}
}
if (idxPrimeiroVendedor == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
boolean rompeuFundoRef =
false;
Candle ultimoVendedorTrend =
null;
int idxUltimoVendedorTrend = -
1;
for (int i = idxPrimeiroVendedor
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleVendedor()) {
break;
}
ultimoVendedorTrend = c
;
idxUltimoVendedorTrend = i
;
lastIndex = i
;
if (c.
getMinima() < ref.
getMinima()) {
rompeuFundoRef =
true;
break;
}
}
if (rompeuFundoRef || idxUltimoVendedorTrend == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
boolean fechamentoDentroRegiaoRef =
ultimoVendedorTrend.
getFechamento() >= ref.
getMinima() &&
ultimoVendedorTrend.
getFechamento() <= ref.
getMaxima();
if (!fechamentoDentroRegiaoRef
) {
return new ResultadoPadrao
(null, lastIndex
);
}
Candle g2 = ultimoVendedorTrend
;
int idxG2 = idxUltimoVendedorTrend
;
log
(String.
format("RefV[%d], G1[%d] => G2 (vendedor) em [%d]", idxRef, idxG1, idxG2
));
// 3) Encontrar G3 (comprador rompendo topo de G2, fundo >= fundo do GR)
int idxPrimeiroCompradorAposG2 = -
1;
for (int i = idxG2 +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador() && !c.
isCandleVendedor()) {
continue;
}
if (c.
isCandleComprador()) {
idxPrimeiroCompradorAposG2 = i
;
break;
} else {
lastIndex = i
;
return new ResultadoPadrao
(null, lastIndex
);
}
}
if (idxPrimeiroCompradorAposG2 == -
1) {
return new ResultadoPadrao
(null, lastIndex
);
}
int idxG3 = -
1;
Candle g3 =
null;
for (int i = idxPrimeiroCompradorAposG2
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (!c.
isCandleComprador()) {
lastIndex = i -
1;
break;
}
lastIndex = i
;
boolean rompeTopoG2 = c.
getMaxima() > g2.
getMaxima();
boolean fundoMaiorOuIgualRef = c.
getMinima() >= ref.
getMinima();
if (rompeTopoG2
&& fundoMaiorOuIgualRef
) {
idxG3 = i
;
g3 = c
;
break;
}
}
if (idxG3 == -
1 || g3 ==
null) {
return new ResultadoPadrao
(null, lastIndex
);
}
log
(String.
format("RefV[%d], G1[%d], G2[%d] => G3 (comprador) em [%d]",
idxRef, idxG1, idxG2, idxG3
));
// 4) G4 – próximo candle rompe fundo do GR
Candle g4 =
null;
int proxIdx = idxG3 +
1;
if (proxIdx
< n
) {
Candle c = candles.
get(proxIdx
);
lastIndex = proxIdx
;
if (c.
getMinima() < ref.
getMinima()) {
g4 = c
;
log
(String.
format("RefV[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
idxRef, idxG1, idxG2, idxG3, proxIdx
));
} else {
log
(String.
format("RefV[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
idxRef, idxG1, idxG2, idxG3, proxIdx
));
}
}
PadraoGatilho padrao =
new PadraoGatilho
();
padrao.
setReferencia(ref
);
padrao.
setGatilho1(g1
);
padrao.
setGatilho2(g2
);
padrao.
setGatilho3(g3
);
padrao.
setGatilho4(g4
);
return new ResultadoPadrao
(padrao, lastIndex
);
}
// =====================================================================
// HELPER – Candle outside em relação ao GR
// =====================================================================
/**
* Outside em relação ao GR:
* - atual.max > GR.max e atual.min < GR.min
*/
private boolean isOutsideReferencia
(Candle referencia, Candle atual
) {
if (referencia ==
null || atual ==
null) return false;
return atual.
getMaxima() > referencia.
getMaxima()
&& atual.
getMinima() < referencia.
getMinima();
}
// =====================================================================
// MODO TEMPO REAL (candle a candle)
// =====================================================================
/**
* Reinicia o estado do modo tempo real.
*/
public void resetTempoReal
() {
this.
idxProximaAnaliseTempoReal =
0;
}
/**
* Deve ser chamado SEMPRE que um novo candle for adicionado à lista.
*
* Exemplo:
* candles.add(novoCandle);
* PadraoGatilho padrao = detector.processarCandleTempoReal(candles);
*
* if (padrao != null) {
* // padrão completo encontrado
* }
*/
public PadraoGatilho processarCandleTempoReal
(List<Candle
> candles
) {
int n = candles.
size();
if (n
< 4) {
return null;
}
while (idxProximaAnaliseTempoReal
< n -
3) {
ResultadoPadrao resultado = detectarPadraoAPartir
(candles, idxProximaAnaliseTempoReal
);
if (resultado ==
null) {
idxProximaAnaliseTempoReal++
;
continue;
}
int proximoInicio = resultado.
lastIndex +
1;
if (proximoInicio
<= idxProximaAnaliseTempoReal
) {
proximoInicio = idxProximaAnaliseTempoReal +
1;
}
idxProximaAnaliseTempoReal = proximoInicio
;
if (resultado.
padrao !=
null) {
return resultado.
padrao;
}
}
return null;
}
}