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)
* trabalhando tanto em modo backtest quanto em modo tempo real.
*
* IMPORTANTE:
* - Este detector FINALIZA o padrão assim que identifica o G3.
* - O G4 será identificado apenas na camada de sinais de trade, fora desta classe.
*
* REGRAS IMPLEMENTADAS (com GR DINÂMICO invertido)
* ================================================
*
* COMPRADOR
* ---------
* Ponto de partida: um candle COMPRADOR é candidato à referência.
*
* G1:
* - Enquanto não houver G1:
* * O candidato à referência pode ser atualizado dinamicamente:
* QUALQUER candle posterior, antes do G1, que fizer uma MÁXIMA
* MAIOR que o candidato atual passa a ser o novo candidato.
* * Se algum candle VENDEDOR subsequente ROMPER o FUNDO
* do candle candidato à referência (mínima < mínima do candidato),
* esse candle será o gatilho tipo 1 [G1] e o candidato vigente
* torna-se o candle referência [GR].
*
* G2:
* - Após o G1 (com GR definido):
* * Assim que tiver nova tendência COMPRADORA:
* - O último candle dessa sequência compradora deve ter o FECHAMENTO
* dentro da região total (mínima..máxima) do GR.
* - Se mudar para vendedor sem nenhum candle fechar dentro da região
* do GR → descarta o padrão.
* * Se qualquer candle (após o GR) romper o TOPO do GR
* (máxima > máxima GR) → descarta o padrão.
* * Se válido, esse último comprador é o gatilho tipo 2 [G2].
*
* G3:
* - Após o G2:
* * O próximo direcional deve ser VENDEDOR; se for comprador,
* o padrão vai apenas até G2 (parcial).
* * Numa sequência vendedora:
* - Se algum candle VENDEDOR posterior ROMPER o FUNDO do G2
* (mínima < mínima G2) e tiver TOPO <= topo do GR
* (máxima <= máxima GR), sem mudança de tendência
* (sem compradores no meio), será o gatilho tipo 3 [G3].
*
* Regra global comprador:
* - Se algum candle posterior ao GR ROMPER o TOPO do GR
* (máxima > máxima GR), desconsiderar o padrão.
*
* -----------------------------------------------------------
*
* VENDEDOR
* --------
* Ponto de partida: um candle VENDEDOR é candidato à referência.
*
* G1:
* - Enquanto não houver G1:
* * O candidato à referência pode ser atualizado dinamicamente:
* QUALQUER candle posterior, antes do G1, que fizer uma MÍNIMA
* MENOR que a do candidato atual passa a ser o novo candidato.
* * Se algum candle COMPRADOR subsequente ROMPER o TOPO
* do candidato (máxima > máxima do candidato), esse candle
* será o gatilho tipo 1 [G1] e o candidato vigente torna-se o GR.
*
* Regra global vendedor:
* - Se algum candle posterior ao GR ROMPER o FUNDO do GR
* (mínima < mínima GR), desconsiderar o padrão.
*
* G2:
* - Após o G1 (com GR definido):
* * Assim que tiver nova tendência VENDEDORA:
* - O último candle dessa sequência vendedora deve ter FECHAMENTO
* dentro da região total (mínima..máxima) do GR.
* - Se mudar para comprador sem nenhum candle fechar dentro
* da região do GR → descarta o padrão.
* * Se romper o FUNDO do GR em qualquer momento após o GR
* → descarta o padrão.
* * Se válido, esse último vendedor é o gatilho tipo 2 [G2].
*
* G3:
* - Após o G2:
* * O próximo direcional deve ser COMPRADOR; se for vendedor,
* o padrão vai até G2 (parcial).
* * Numa sequência compradora:
* - Se algum COMPRADOR posterior ROMPER o TOPO de G2
* (máxima > máxima G2) e tiver FUNDO >= fundo do GR
* (mínima >= mínima GR), sem mudança de tendência
* (sem vendedores no meio), será o gatilho tipo 3 [G3].
*
* Regra global adicional (texto original repetia):
* - Mantida a regra principal: vendedor é invalidado
* quando rompem o FUNDO do GR.
*
* -----------------------------------------------------------
*
* 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:
* * Desconsidera o padrão atual (GR+G1+G2) e recomeça
* a busca a partir do GR (na prática, aborta o padrão).
*
* OBS:
* - Os gatilhos devem seguir ORDEM cronológica:
* G1 -> G2 -> G3, sempre em candles diferentes.
*/
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
);
}
}
// -----------------------------------------------------------------
// Estrutura interna de retorno
// -----------------------------------------------------------------
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); // G4 só na camada de trade
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());
}
private boolean isDirecional
(Candle c
) {
return c.
isCandleComprador() || c.
isCandleVendedor();
}
// =====================================================================
// CASO COMPRADOR – GR DINÂMICO PELA MÁXIMA
// =====================================================================
private ResultadoPadrao detectarPadraoComprador
(List<Candle
> candles,
int idxInicio
) {
int n = candles.
size();
int lastIndex = idxInicio
;
Candle candidatoRef = candles.
get(idxInicio
);
if (!candidatoRef.
isCandleComprador()) {
return new ResultadoPadrao
(null, idxInicio
);
}
int idxCandidatoRef = idxInicio
;
// --------------------
// 1) Buscar G1 (vendedor rompe fundo do candidato)
// --------------------
Candle g1 =
null;
int idxG1 = -
1;
boolean temGR =
false;
Candle gr =
null;
int idxGR = -
1;
for (int i = idxInicio +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (isInside
(candles, i
) ||
!isDirecional
(c
))
continue;
// Outside antes de G3 (aqui ainda antes de G1)
if (isOutside
(candles, i
)) {
lastIndex = i
;
log
(String.
format("Comprador: OUTSIDE em [%d] antes de G1. Abortando padrão.", i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// G1: vendedor rompe fundo do candidato
if (c.
isCandleVendedor()
&& BigDecimalUtils.
ehMenorQue(c.
getMinima(), candidatoRef.
getMinima())) {
g1 = c
;
idxG1 = i
;
gr = candidatoRef
;
idxGR = idxCandidatoRef
;
temGR =
true;
lastIndex = i
;
break;
}
// GR DINÂMICO (compra): atualiza pelo TOPO mais alto antes de G1
if (BigDecimalUtils.
ehMaiorQue(c.
getMaxima(), candidatoRef.
getMaxima())) {
candidatoRef = c
;
idxCandidatoRef = i
;
lastIndex = i
;
continue;
}
lastIndex = i
;
}
if (g1 ==
null ||
!temGR
) {
log
(String.
format("Comprador: não foi possível formar G1 a partir de idx %d.", idxInicio
));
return new ResultadoPadrao
(null, lastIndex
);
}
log
(String.
format("GR COMPRADOR em [%d], G1 (vendedor) em [%d].", idxGR, idxG1
));
// --------------------
// 2) Buscar G2 (tendência compradora com fechamento dentro 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
) ||
!isDirecional
(c
))
continue;
// Outside antes de G3
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] antes da perna compradora de G2.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global: romper topo do GR invalida
if (BigDecimalUtils.
ehMaiorQue(c.
getMaxima(), gr.
getMaxima())) {
lastIndex = i
;
log
(String.
format("GR[%d]: candle[%d] rompeu topo do GR antes de G2.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (c.
isCandleComprador()) {
idxPrimeiroComprador = i
;
break;
} else {
lastIndex = i
;
}
}
if (idxPrimeiroComprador == -
1) {
log
(String.
format("GR[%d], G1[%d]: não houve nova tendência compradora para G2.", idxGR, idxG1
));
return new ResultadoPadrao
(null, lastIndex
);
}
Candle ultimoComprador =
null;
int idxUltimoComprador = -
1;
for (int i = idxPrimeiroComprador
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (isInside
(candles, i
) ||
!isDirecional
(c
))
continue;
// Outside durante G2
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] durante G2 (comprador).", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global: romper topo do GR invalida
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
);
}
if (c.
isCandleComprador()) {
ultimoComprador = c
;
idxUltimoComprador = i
;
lastIndex = i
;
} else if (c.
isCandleVendedor()) {
lastIndex = i -
1;
break;
}
}
if (ultimoComprador ==
null) {
log
(String.
format("GR[%d], G1[%d]: não houve comprador válido para G2.", idxGR, idxG1
));
return new ResultadoPadrao
(null, lastIndex
);
}
boolean fechamentoDentroRegiaoGR =
BigDecimalUtils.
ehMaiorOuIgualQue(ultimoComprador.
getFechamento(), gr.
getMinima()) &&
BigDecimalUtils.
ehMenorOuIgualQue(ultimoComprador.
getFechamento(), gr.
getMaxima());
if (!fechamentoDentroRegiaoGR
) {
log
(String.
format(
"GR[%d], G1[%d]: último comprador[%d] não fechou dentro da região do GR (fech=%s, faixa=[%s,%s]).",
idxGR, idxG1, idxUltimoComprador,
ultimoComprador.
getFechamento().
toPlainString(),
gr.
getMinima().
toPlainString(),
gr.
getMaxima().
toPlainString()
));
return new ResultadoPadrao
(null, lastIndex
);
}
g2 = ultimoComprador
;
idxG2 = idxUltimoComprador
;
log
(String.
format("GR[%d], G1[%d] => G2 (comprador) em [%d].", idxGR, idxG1, idxG2
));
// --------------------
// 3) Buscar G3 (vendedor 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
) ||
!isDirecional
(c
))
continue;
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] antes da sequência vendedora de G3.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global: romper topo do GR invalida
if (BigDecimalUtils.
ehMaiorQue(c.
getMaxima(), gr.
getMaxima())) {
lastIndex = i
;
log
(String.
format("GR[%d]: candle[%d] rompeu topo do GR antes da perna vendedora de G3.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (c.
isCandleVendedor()) {
idxPrimeiroVendedorAposG2 = i
;
break;
} else {
// primeiro direcional após G2 é comprador → padrão só até G2
lastIndex = i
;
log
(String.
format("GR[%d], G1[%d], G2[%d]: primeiro direcional após G2 é comprador (idx %d).",
idxGR, idxG1, idxG2, i
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
}
if (idxPrimeiroVendedorAposG2 == -
1) {
log
(String.
format("GR[%d], G1[%d], G2[%d]: não houve vendedor após G2.", idxGR, idxG1, idxG2
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
for (int i = idxPrimeiroVendedorAposG2
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (isInside
(candles, i
) ||
!isDirecional
(c
))
continue;
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (vendedor).", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global: romper topo do GR invalida
if (BigDecimalUtils.
ehMaiorQue(c.
getMaxima(), gr.
getMaxima())) {
lastIndex = i
;
log
(String.
format("GR[%d]: candle[%d] rompeu topo do GR durante busca de G3.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (!c.
isCandleVendedor()) {
// apareceu comprador antes de G3 → padrão só até G2
lastIndex = i -
1;
log
(String.
format("GR[%d], G1[%d], G2[%d]: comprador em [%d] antes de G3.", idxGR, idxG1, idxG2, i
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
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) {
log
(String.
format("GR[%d], G1[%d], G2[%d]: não houve G3, padrão parcial.", idxGR, idxG1, idxG2
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
lastIndex = idxG3
;
log
(String.
format("GR[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d].", idxGR, idxG1, idxG2, idxG3
));
PadraoGatilho padrao =
new PadraoGatilho
();
padrao.
setReferencia(gr
);
padrao.
setGatilho1(g1
);
padrao.
setGatilho2(g2
);
padrao.
setGatilho3(g3
);
padrao.
setGatilho4(null);
return new ResultadoPadrao
(padrao, lastIndex
);
}
// =====================================================================
// CASO VENDEDOR – GR DINÂMICO PELA MÍNIMA
// =====================================================================
private ResultadoPadrao detectarPadraoVendedor
(List<Candle
> candles,
int idxInicio
) {
int n = candles.
size();
int lastIndex = idxInicio
;
Candle candidatoRef = candles.
get(idxInicio
);
if (!candidatoRef.
isCandleVendedor()) {
return new ResultadoPadrao
(null, idxInicio
);
}
int idxCandidatoRef = idxInicio
;
// --------------------
// 1) Buscar G1 (comprador rompe topo do candidato)
// --------------------
Candle g1 =
null;
int idxG1 = -
1;
boolean temGR =
false;
Candle gr =
null;
int idxGR = -
1;
for (int i = idxInicio +
1; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (isInside
(candles, i
) ||
!isDirecional
(c
))
continue;
// Outside antes de G1
if (isOutside
(candles, i
)) {
lastIndex = i
;
log
(String.
format("Vendedor: OUTSIDE em [%d] antes de G1. Abortando padrão.", i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// G1: comprador rompe topo do candidato
if (c.
isCandleComprador()
&& BigDecimalUtils.
ehMaiorQue(c.
getMaxima(), candidatoRef.
getMaxima())) {
g1 = c
;
idxG1 = i
;
gr = candidatoRef
;
idxGR = idxCandidatoRef
;
temGR =
true;
lastIndex = i
;
break;
}
// GR DINÂMICO (venda): atualiza pelo FUNDO mais baixo antes de G1
if (BigDecimalUtils.
ehMenorQue(c.
getMinima(), candidatoRef.
getMinima())) {
candidatoRef = c
;
idxCandidatoRef = i
;
lastIndex = i
;
continue;
}
lastIndex = i
;
}
if (g1 ==
null ||
!temGR
) {
log
(String.
format("Vendedor: não foi possível formar G1 a partir de idx %d.", idxInicio
));
return new ResultadoPadrao
(null, lastIndex
);
}
log
(String.
format("GR VENDEDOR em [%d], G1 (comprador) em [%d].", idxGR, idxG1
));
// --------------------
// 2) Buscar G2 (tendência vendedora com fechamento dentro 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
) ||
!isDirecional
(c
))
continue;
// Outside antes de G3
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] antes da perna vendedora de G2.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global vendedor: romper fundo do GR invalida
if (BigDecimalUtils.
ehMenorQue(c.
getMinima(), gr.
getMinima())) {
lastIndex = i
;
log
(String.
format("GR[%d]: candle[%d] rompeu fundo do GR antes de G2.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (c.
isCandleVendedor()) {
idxPrimeiroVendedor = i
;
break;
} else {
lastIndex = i
;
}
}
if (idxPrimeiroVendedor == -
1) {
log
(String.
format("GR[%d], G1[%d]: não houve nova tendência vendedora para G2.", idxGR, idxG1
));
return new ResultadoPadrao
(null, lastIndex
);
}
Candle ultimoVendedor =
null;
int idxUltimoVendedor = -
1;
for (int i = idxPrimeiroVendedor
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (isInside
(candles, i
) ||
!isDirecional
(c
))
continue;
// Outside durante G2
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] durante G2 (vendedor).", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global vendedor: romper fundo do GR invalida
if (BigDecimalUtils.
ehMenorQue(c.
getMinima(), gr.
getMinima())) {
lastIndex = i
;
log
(String.
format("GR[%d]: candle[%d] rompeu fundo do GR durante G2.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (c.
isCandleVendedor()) {
ultimoVendedor = c
;
idxUltimoVendedor = i
;
lastIndex = i
;
} else if (c.
isCandleComprador()) {
lastIndex = i -
1;
break;
}
}
if (ultimoVendedor ==
null) {
log
(String.
format("GR[%d], G1[%d]: não houve vendedor válido para G2.", idxGR, idxG1
));
return new ResultadoPadrao
(null, lastIndex
);
}
boolean fechamentoDentroRegiaoGR =
BigDecimalUtils.
ehMaiorOuIgualQue(ultimoVendedor.
getFechamento(), gr.
getMinima()) &&
BigDecimalUtils.
ehMenorOuIgualQue(ultimoVendedor.
getFechamento(), gr.
getMaxima());
if (!fechamentoDentroRegiaoGR
) {
log
(String.
format(
"GR[%d], G1[%d]: último vendedor[%d] não fechou dentro da região do GR (fech=%s, faixa=[%s,%s]).",
idxGR, idxG1, idxUltimoVendedor,
ultimoVendedor.
getFechamento().
toPlainString(),
gr.
getMinima().
toPlainString(),
gr.
getMaxima().
toPlainString()
));
return new ResultadoPadrao
(null, lastIndex
);
}
g2 = ultimoVendedor
;
idxG2 = idxUltimoVendedor
;
log
(String.
format("GR[%d], G1[%d] => G2 (vendedor) em [%d].", idxGR, idxG1, idxG2
));
// --------------------
// 3) Buscar G3 (comprador 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
) ||
!isDirecional
(c
))
continue;
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] antes da sequência compradora de G3.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global vendedor: romper fundo do GR antes de G3 invalida
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()) {
idxPrimeiroCompradorAposG2 = i
;
break;
} else {
// primeiro direcional não é comprador → padrão até G2
lastIndex = i
;
log
(String.
format("GR[%d], G1[%d], G2[%d]: primeiro direcional após G2 não é comprador (idx %d).",
idxGR, idxG1, idxG2, i
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
}
if (idxPrimeiroCompradorAposG2 == -
1) {
log
(String.
format("GR[%d], G1[%d], G2[%d]: não houve comprador após G2.", idxGR, idxG1, idxG2
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
for (int i = idxPrimeiroCompradorAposG2
; i
< n
; i++
) {
Candle c = candles.
get(i
);
if (isInside
(candles, i
) ||
!isDirecional
(c
))
continue;
if (isOutside
(candles, i
)) {
lastIndex = idxGR
;
log
(String.
format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (comprador).", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
// Regra global vendedor: romper fundo do GR invalida
if (BigDecimalUtils.
ehMenorQue(c.
getMinima(), gr.
getMinima())) {
lastIndex = i
;
log
(String.
format("GR[%d]: candle[%d] rompeu fundo do GR durante busca de G3.", idxGR, i
));
return new ResultadoPadrao
(null, lastIndex
);
}
if (!c.
isCandleComprador()) {
// apareceu vendedor antes de G3 → padrão só até G2
lastIndex = i -
1;
log
(String.
format("GR[%d], G1[%d], G2[%d]: vendedor em [%d] antes de G3.", idxGR, idxG1, idxG2, i
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
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) {
log
(String.
format("GR[%d], G1[%d], G2[%d]: não houve G3, padrão parcial.", idxGR, idxG1, idxG2
));
return criarResultadoParcialComG2
(gr, g1, g2, lastIndex
);
}
lastIndex = idxG3
;
log
(String.
format("GR[%d], G1[%d], G2[%d] => G3 (comprador) em [%d].", idxGR, idxG1, idxG2, idxG3
));
PadraoGatilho padrao =
new PadraoGatilho
();
padrao.
setReferencia(gr
);
padrao.
setGatilho1(g1
);
padrao.
setGatilho2(g2
);
padrao.
setGatilho3(g3
);
padrao.
setGatilho4(null);
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;
}
}