package br.com.kronus.core;
import java.math.BigDecimal;
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)
* para o modelo Kronus.
*
* REGRAS GERAIS:
* - Não usar candles INSIDE (range totalmente dentro do candle anterior).
* - Gatilhos sempre em ordem cronológica: GR -> G1 -> G2 -> G3.
* - Um candle não pode ser usado para dois gatilhos.
* - Não pode haver G2 antes de G1.
*
* -------------------------------------------------------------
* PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
* -------------------------------------------------------------
*
* 1) Referência (GR) e G1
*
* A) Tenho um candle A COMPRADOR.
* Se o PRÓXIMO candle direcional (não-inside) tiver mudança direcional
* (for VENDEDOR), então A passa a ser CANDIDATO À REFERÊNCIA.
*
* B) Referência dinâmica (antes do G1):
* Enquanto G1 ainda não apareceu, entre o índice do candidato e o G1:
* - Se algum candle posterior fizer MÁXIMA MAIOR que a máxima do candidato,
* esse candle passa a ser o NOVO candidato à referência.
*
* C) G1:
* - Se algum candle VENDEDOR subsequente romper o FUNDO do candidato à referência
* (minima < minimaCandRef), esse candle será o G1.
* - O candidato atual torna-se o GR definitivo.
*
* D) Regra de saída pelo G1:
* - Considerar a Fibo do G1 com origem na MÁXIMA do G1 e destino na MÍNIMA do G1.
* - Calcular o nível 200% dessa Fibo.
* - Se ALGUM candle após o G1 ATINGIR ou PASSAR ESSE NÍVEL (mínima <= nível200)
* ANTES de identificar G3, descartar a operação:
* -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
*
* 2) G2 (após G1)
*
* - Após o G1, QUALQUER candle COMPRADOR que tiver FECHAMENTO DENTRO da região
* do GR (minGR <= fechamento <= maxGR) passa a ser CANDIDATO a G2.
*
* - Regra de descarte:
* Se ALGUM candle posterior ao G1 ROMPER o TOPO do GR (máxima > maxGR),
* descartar a operação:
* -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
*
* - G2 dinâmico (entre o primeiro G2 candidato e o G3):
* Enquanto G3 não tiver aparecido:
* - Se surgir outro candle COMPRADOR com FECHAMENTO dentro da região do GR
* e MÁXIMA MAIOR que a máxima do candidato atual a G2,
* esse novo candle passa a ser o NOVO G2.
*
* 3) G3 (após ter G2)
*
* - Após existir um candidato a G2:
* Se algum candle VENDEDOR posterior:
* * romper o FUNDO do G2 (minima < minimaG2) e
* * tiver TOPO MENOR OU IGUAL ao TOPO do GR (max <= maxGR)
* => esse candle será o G3, confirmando o padrão.
*
* - Ao encontrar G3, o padrão COMPRADOR é considerado COMPLETO (GR,G1,G2,G3).
*
* -------------------------------------------------------------
* PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
* -------------------------------------------------------------
*
* 1) Referência (GR) e G1
*
* A) Tenho um candle A VENDEDOR.
* Se o PRÓXIMO candle direcional (não-inside) tiver mudança direcional
* (for COMPRADOR), então A passa a ser CANDIDATO À REFERÊNCIA.
*
* B) Referência dinâmica (antes do G1):
* Enquanto G1 ainda não apareceu, entre o índice do candidato e o G1:
* - Se algum candle posterior fizer MÍNIMA MENOR que a mínima do candidato,
* esse candle passa a ser o NOVO candidato à referência.
*
* C) G1:
* - Se algum candle COMPRADOR subsequente romper o TOPO do candidato à referência
* (máxima > máximaCandRef), esse candle será o G1.
* - O candidato atual torna-se o GR definitivo.
*
* D) Regra de saída pelo G1:
* - Considerar a Fibo do G1 com origem na MÍNIMA do G1 e destino na MÁXIMA do G1.
* - Calcular o nível -100% dessa Fibo.
* - Se ALGUM candle após o G1 ATINGIR ou PASSAR ESSE NÍVEL para baixo
* (mínima <= nível -100%) ANTES de identificar G3,
* descartar a operação:
* -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
*
* 2) G2 (após G1)
*
* - Após o G1, QUALQUER candle VENDEDOR que tiver FECHAMENTO DENTRO da região
* do GR (minGR <= fechamento <= maxGR) passa a ser CANDIDATO a G2.
*
* - Regra de descarte:
* Se ALGUM candle posterior ao G1 ROMPER o FUNDO do GR (mínima < minGR),
* descartar a operação:
* -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
*
* - G2 dinâmico (entre o primeiro G2 candidato e o G3):
* Enquanto G3 não tiver aparecido:
* - Se surgir outro candle VENDEDOR com FECHAMENTO dentro da região do GR
* e MÍNIMA MENOR que a mínima do candidato a G2,
* esse novo candle passa a ser o NOVO G2.
*
* 3) G3 (após ter G2)
*
* - Após existir um candidato a G2:
* Se algum candle COMPRADOR posterior:
* * romper o TOPO do G2 (máxima > máximaG2) e
* * tiver FUNDO MENOR OU IGUAL ao FUNDO do GR (mínima <= minGR)
* => esse candle será o G3, confirmando o padrão.
*
* - Ao encontrar G3, o padrão VENDEDOR é considerado COMPLETO (GR,G1,G2,G3).
*
* -------------------------------------------------------------
* BACKTEST:
* - identificarPadroes: varre a lista inteira e retorna todos os padrões
* completos ou parciais (até G2).
*
* TEMPO REAL:
* - processarCandleTempoReal: chamado a cada novo candle, retorna um padrão
* assim que ele for identificado (até G3).
*
* DEBUG:
* - debugarAPartirDoIndice: roda a detecção a partir de um índice e
* retorna um relatório (List<String>) com tudo o que aconteceu.
*/
public class DetectorGatilhosAnterior
{
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 DetectorGatilhosAnterior
() {
this(true);
}
public DetectorGatilhosAnterior
(boolean logAtivo
) {
this.
logAtivo = logAtivo
;
}
private void log
(String msg
) {
if (logAtivo
) {
System.
out.
println(msg
);
}
if (bufferDebug
!=
null) {
bufferDebug.
add(msg
);
}
}
/**
* Estrutura interna para carregar o resultado da detecção
* iniciando em um índice específico.
*
* lastIndex = último índice efetivamente analisado na busca daquele padrão.
* proximoInicio = índice sugerido para o PRÓXIMO início de análise:
* - Se NÃO existiu GR: idxA + 1
* - Se existiu GR: idxGR + 1 (recomeça após a referência)
*/
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
;
}
}
/**
* Cria um resultado com padrão PARCIAL (até G2).
* Sempre que há GR, o próximo início deve ser após o GR.
*/
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); // G4 será tratado na camada de estratégia, não aqui.
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());
}
/**
* Extensão de Fibonacci simples:
* origem + (destino - origem) * fator
*/
private BigDecimal fibExtend
(BigDecimal origem,
BigDecimal destino,
BigDecimal fator
) {
BigDecimal diff = destino.
subtract(origem
);
return origem.
add(diff.
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
;
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);
}
// Regra de avanço:
// - Se houve GR, proximoInicio = idxGR + 1
// - Se não houve GR, proximoInicio = idxRef + 1
int proximoInicio = resultado.
proximoInicio;
if (proximoInicio
<= idxRef
) {
proximoInicio = idxRef +
1;
}
idxRef = proximoInicio
;
}
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 neutro não é A; não existe GR, avança 1 candle.
return new ResultadoPadrao
(null, idxRef, idxRef +
1);
}
}
// =====================================================================
// PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
// =====================================================================
private ResultadoPadrao detectarPadraoComprador
(List<Candle
> candles,
int idxA
) {
int n = candles.
size();
Candle candleA = candles.
get(idxA
);
if (!candleA.
isCandleComprador()) {
// Não pode ser A comprador -> reinicia no próximo candle.
return new ResultadoPadrao
(null, idxA, idxA +
1);
}
int lastIndex = idxA
;
log
(String.
format("[VENDER] Iniciando busca a partir de A[%d]", idxA +
1));
// 1) Verifica se o próximo candle direcional (não-inside) muda a 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) {
// Não houve candle B; A não vira candidato; recomeça do próximo candle.
return new ResultadoPadrao
(null, idxA, idxA +
1);
}
Candle prox = candles.
get(idxProxDirecional
);
if (!prox.
isCandleVendedor()) {
// Não houve mudança direcional imediata -> A não vira candidato
return new ResultadoPadrao
(null, idxA, idxA +
1);
}
// A vira candidato à referência
Candle candidatoRef = candleA
;
int idxCandidatoRef = idxA
;
log
(String.
format("[VENDER] CandidatoRef inicial = idx %d", idxCandidatoRef +
1));
// 2) Busca G1, com referência dinâmica até G1
Candle g1 =
null;
int idxG1 = -
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) antes do G1", i +
1));
continue;
}
lastIndex = i
;
// Primeiro checa G1: vendedor que rompe fundo do candidatoRef
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;
}
// Se ainda não encontrou G1, atualiza candidatoRef se máxima maior
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) {
// Não formou G1; não existe GR definitivo; recomeça a partir do próximo candle 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));
// 3) Regra de saída do G1 – 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, fib200.
toPlainString()));
// 4) Busca G2 dinâmico 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
;
// --- Regras de DESCARTE após G1 (devem reiniciar do candle após o GR) ---
// 4.1) Se candle romper TOPO do GR => descarta operação
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);
}
// 4.2) Regra Fib 200% do G1: se mínima <= fib200 => descarta
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);
}
// --- Construção de G2 dinâmico (COMPRADOR com FECHAMENTO dentro da região do GR) ---
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 {
// Atualização dinâmica: máxima maior e fechamento ainda dentro da região
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: só pode ser avaliado após existir candidato G2 ---
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;
}
}
}
if (g3 ==
null) {
// Padrão só até G2 (se G2 existir), senão nada.
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
);
}
// Já houve GR e G1, mas não houve G2/G3 -> recomeça após GR.
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);
}
// Finaliza padrão no G3
PadraoGatilho padrao =
new PadraoGatilho
();
padrao.
setReferencia(gr
);
padrao.
setGatilho1(g1
);
padrao.
setGatilho2(g2
);
padrao.
setGatilho3(g3
);
padrao.
setGatilho4(null);
// Padrão completo -> próxima busca deve começar após o GR
return new ResultadoPadrao
(padrao, idxG3, idxGR +
1);
}
// =====================================================================
// PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
// =====================================================================
private ResultadoPadrao detectarPadraoVendedor
(List<Candle
> candles,
int idxA
) {
int n = candles.
size();
Candle candleA = candles.
get(idxA
);
if (!candleA.
isCandleVendedor()) {
// Não pode ser A vendedor -> reinicia no próximo candle.
return new ResultadoPadrao
(null, idxA, idxA +
1);
}
int lastIndex = idxA
;
log
(String.
format("[COMPRAR] Iniciando busca a partir de A[%d]", idxA +
1));
// 1) Verifica se o próximo candle direcional (não-inside) muda a 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("[COMPRAR] Candle[%d] ignorado (não direcional ou inside)", i +
1));
continue;
}
idxProxDirecional = i
;
break;
}
if (idxProxDirecional == -
1) {
// Não houve candle B; A não vira candidato; recomeça do próximo candle.
return new ResultadoPadrao
(null, idxA, idxA +
1);
}
Candle prox = candles.
get(idxProxDirecional
);
if (!prox.
isCandleComprador()) {
// Não houve mudança direcional imediata -> A não vira candidato
return new ResultadoPadrao
(null, idxA, idxA +
1);
}
// A vira candidato à referência
Candle candidatoRef = candleA
;
int idxCandidatoRef = idxA
;
log
(String.
format("[COMPRAR] CandidatoRef inicial = idx %d", idxCandidatoRef +
1));
// 2) Busca G1, com referência dinâmica até G1
Candle g1 =
null;
int idxG1 = -
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) antes do G1", i +
1));
continue;
}
lastIndex = i
;
// Primeiro checa G1: comprador que rompe topo do candidatoRef
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;
}
// Se ainda não encontrou G1, atualiza candidatoRef se mínima menor
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) {
// Não formou G1; não existe GR definitivo; recomeça a partir do próximo candle 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));
// 3) Regra de saída do G1 – Fibonacci 200% (origem = mínG1, destino = máxG1)
BigDecimal fib200MinMax = fibExtend
(g1.
getMinima(), g1.
getMaxima(),
new BigDecimal("2"));
log
(String.
format("[COMPRAR] Fibo200 G1[%d] = %s", idxG1 +
1, fib200MinMax.
toPlainString()));
// 4) Busca G2 dinâmico 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
;
// --- Regras de DESCARTE após G1 (reiniciar após GR) ---
// 4.1) Se candle romper FUNDO do GR => descarta operação
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);
}
// 4.2) Regra Fib 200% do G1: se mínima > fib200MinMax => descarta
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);
}
// --- Construção de G2 dinâmico (VENDEDOR com FECHAMENTO dentro da região do GR) ---
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 {
// Atualização dinâmica: mínima menor e fechamento ainda dentro da região
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: só pode ser avaliado após existir candidato G2 ---
if (g2
!=
null && c.
isCandleComprador()) {
boolean rompeTopoG2 = BigDecimalUtils.
ehMaiorQue(c.
getMaxima(), g2.
getMaxima());
boolean fundoMaiorOuIgualGR = BigDecimalUtils.
ehMaiorOuIgualQue(c.
getMinima(), gr.
getMinima());
if (rompeTopoG2
&& 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;
}
}
}
if (g3 ==
null) {
// Padrão só até G2 (se G2 existir), senão nada
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
);
}
// Já houve GR e G1, mas não houve G2/G3 -> recomeça após GR.
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);
}
// Finaliza padrão no G3
PadraoGatilho padrao =
new PadraoGatilho
();
padrao.
setReferencia(gr
);
padrao.
setGatilho1(g1
);
padrao.
setGatilho2(g2
);
padrao.
setGatilho3(g3
);
padrao.
setGatilho4(null);
// Padrão completo -> próxima busca deve começar após o GR
return new ResultadoPadrao
(padrao, idxG3, idxGR +
1);
}
// =====================================================================
// 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 (até G3) 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.
proximoInicio;
if (proximoInicio
<= idxProximaAnaliseTempoReal
) {
proximoInicio = idxProximaAnaliseTempoReal +
1;
}
idxProximaAnaliseTempoReal = proximoInicio
;
if (resultado.
padrao !=
null) {
return resultado.
padrao;
}
}
return null;
}
// =====================================================================
// DEBUG / RELATÓRIO
// =====================================================================
/**
* 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> antigoBuffer =
this.
bufferDebug;
this.
bufferDebug = relatorio
;
try {
log
("====================================================");
log
(String.
format("DEBUG: iniciando debugarAPartirDoIndice(%d)", idxInicio
));
detectarPadraoAPartir
(candles, idxInicio
);
log
(String.
format("DEBUG: fim da análise a partir do índice %d", idxInicio
));
log
("====================================================");
} finally {
this.
bufferDebug = antigoBuffer
;
}
return relatorio
;
}
}