Rev 775 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed
| Rev 775 | Rev 776 | ||
|---|---|---|---|
| Line 2... | Line 2... | ||
| 2 | 2 | ||
| 3 | import java.math.BigDecimal; |
3 | import java.math.BigDecimal; |
| 4 | import java.util.ArrayList; |
4 | import java.util.ArrayList; |
| 5 | import java.util.List; |
5 | import java.util.List; |
| 6 | 6 | ||
| - | 7 | import br.com.kronus.core.PadraoGatilho.TipoPadrao; |
|
| 7 | import br.com.sl.domain.model.Candle; |
8 | import br.com.sl.domain.model.Candle; |
| 8 | import br.com.sl.domain.util.BigDecimalUtils; |
9 | import br.com.sl.domain.util.BigDecimalUtils; |
| 9 | 10 | ||
| 10 | /**
|
11 | /**
|
| 11 | * Detector de padrões de gatilhos (GR, G1, G2, G3)
|
12 | * Detector de padrões de gatilhos (GR, G1, G2, G3)
|
| 12 | * para o modelo Kronus.
|
- | |
| 13 | *
|
- | |
| 14 | * REGRAS GERAIS:
|
- | |
| 15 | * - Não usar candles INSIDE (range totalmente dentro do candle anterior).
|
- | |
| 16 | * - Gatilhos sempre em ordem cronológica: GR -> G1 -> G2 -> G3.
|
- | |
| 17 | * - Um candle não pode ser usado para dois gatilhos.
|
- | |
| 18 | * - Não pode haver G2 antes de G1.
|
- | |
| 19 | *
|
- | |
| 20 | * -------------------------------------------------------------
|
- | |
| 21 | * PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
|
- | |
| 22 | * -------------------------------------------------------------
|
- | |
| 23 | *
|
- | |
| 24 | * 1) Referência (GR) e G1
|
- | |
| 25 | *
|
- | |
| 26 | * A) Tenho um candle A COMPRADOR.
|
- | |
| 27 | * Se o PRÓXIMO candle direcional (não-inside) tiver mudança direcional
|
- | |
| 28 | * (for VENDEDOR), então A passa a ser CANDIDATO À REFERÊNCIA.
|
- | |
| 29 | *
|
- | |
| 30 | * B) Referência dinâmica (antes do G1):
|
- | |
| 31 | * Enquanto G1 ainda não apareceu, entre o índice do candidato e o G1:
|
- | |
| 32 | * - Se algum candle posterior fizer MÁXIMA MAIOR que a máxima do candidato,
|
- | |
| 33 | * esse candle passa a ser o NOVO candidato à referência.
|
- | |
| 34 | *
|
- | |
| 35 | * C) G1:
|
- | |
| 36 | * - Se algum candle VENDEDOR subsequente romper o FUNDO do candidato à referência
|
- | |
| 37 | * (minima < minimaCandRef), esse candle será o G1.
|
- | |
| 38 | * - O candidato atual torna-se o GR definitivo.
|
- | |
| 39 | *
|
- | |
| 40 | * D) Regra de saída pelo G1:
|
- | |
| 41 | * - Considerar a Fibo do G1 com origem na MÁXIMA do G1 e destino na MÍNIMA do G1.
|
- | |
| 42 | * - Calcular o nível 200% dessa Fibo.
|
- | |
| 43 | * - Se ALGUM candle após o G1 ATINGIR ou PASSAR ESSE NÍVEL (mínima <= nível200)
|
- | |
| 44 | * ANTES de identificar G3, descartar a operação:
|
- | |
| 45 | * -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
|
- | |
| 46 | *
|
- | |
| 47 | * 2) G2 (após G1)
|
- | |
| 48 | *
|
- | |
| 49 | * - Após o G1, QUALQUER candle COMPRADOR que tiver FECHAMENTO DENTRO da região
|
- | |
| 50 | * do GR (minGR <= fechamento <= maxGR) passa a ser CANDIDATO a G2.
|
- | |
| 51 | *
|
- | |
| 52 | * - Regra de descarte:
|
- | |
| 53 | * Se ALGUM candle posterior ao G1 ROMPER o TOPO do GR (máxima > maxGR),
|
- | |
| 54 | * descartar a operação:
|
- | |
| 55 | * -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
|
- | |
| 56 | *
|
- | |
| 57 | * - G2 dinâmico (entre o primeiro G2 candidato e o G3):
|
- | |
| 58 | * Enquanto G3 não tiver aparecido:
|
- | |
| 59 | * - Se surgir outro candle COMPRADOR com FECHAMENTO dentro da região do GR
|
- | |
| 60 | * e MÁXIMA MAIOR que a máxima do candidato atual a G2,
|
- | |
| 61 | * esse novo candle passa a ser o NOVO G2.
|
- | |
| 62 | *
|
- | |
| 63 | * 3) G3 (após ter G2)
|
- | |
| 64 | *
|
- | |
| 65 | * - Após existir um candidato a G2:
|
- | |
| 66 | * Se algum candle VENDEDOR posterior:
|
- | |
| 67 | * * romper o FUNDO do G2 (minima < minimaG2) e
|
- | |
| 68 | * * tiver TOPO MENOR OU IGUAL ao TOPO do GR (max <= maxGR)
|
- | |
| 69 | * => esse candle será o G3, confirmando o padrão.
|
- | |
| 70 | *
|
- | |
| 71 | * - Ao encontrar G3, o padrão COMPRADOR é considerado COMPLETO (GR,G1,G2,G3).
|
- | |
| 72 | *
|
- | |
| 73 | * -------------------------------------------------------------
|
- | |
| 74 | * PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
|
- | |
| 75 | * -------------------------------------------------------------
|
- | |
| 76 | *
|
- | |
| 77 | * 1) Referência (GR) e G1
|
- | |
| 78 | *
|
- | |
| 79 | * A) Tenho um candle A VENDEDOR.
|
- | |
| 80 | * Se o PRÓXIMO candle direcional (não-inside) tiver mudança direcional
|
- | |
| 81 | * (for COMPRADOR), então A passa a ser CANDIDATO À REFERÊNCIA.
|
- | |
| 82 | *
|
- | |
| 83 | * B) Referência dinâmica (antes do G1):
|
- | |
| 84 | * Enquanto G1 ainda não apareceu, entre o índice do candidato e o G1:
|
- | |
| 85 | * - Se algum candle posterior fizer MÍNIMA MENOR que a mínima do candidato,
|
- | |
| 86 | * esse candle passa a ser o NOVO candidato à referência.
|
- | |
| 87 | *
|
- | |
| 88 | * C) G1:
|
- | |
| 89 | * - Se algum candle COMPRADOR subsequente romper o TOPO do candidato à referência
|
- | |
| 90 | * (máxima > máximaCandRef), esse candle será o G1.
|
- | |
| 91 | * - O candidato atual torna-se o GR definitivo.
|
- | |
| 92 | *
|
- | |
| 93 | * D) Regra de saída pelo G1:
|
- | |
| 94 | * - Considerar a Fibo do G1 com origem na MÍNIMA do G1 e destino na MÁXIMA do G1.
|
- | |
| 95 | * - Calcular o nível -100% dessa Fibo.
|
- | |
| 96 | * - Se ALGUM candle após o G1 ATINGIR ou PASSAR ESSE NÍVEL para baixo
|
- | |
| 97 | * (mínima <= nível -100%) ANTES de identificar G3,
|
- | |
| 98 | * descartar a operação:
|
- | |
| 99 | * -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
|
- | |
| 100 | *
|
- | |
| 101 | * 2) G2 (após G1)
|
- | |
| 102 | *
|
- | |
| 103 | * - Após o G1, QUALQUER candle VENDEDOR que tiver FECHAMENTO DENTRO da região
|
- | |
| 104 | * do GR (minGR <= fechamento <= maxGR) passa a ser CANDIDATO a G2.
|
- | |
| 105 | *
|
- | |
| 106 | * - Regra de descarte:
|
- | |
| 107 | * Se ALGUM candle posterior ao G1 ROMPER o FUNDO do GR (mínima < minGR),
|
- | |
| 108 | * descartar a operação:
|
- | |
| 109 | * -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
|
- | |
| 110 | *
|
- | |
| 111 | * - G2 dinâmico (entre o primeiro G2 candidato e o G3):
|
- | |
| 112 | * Enquanto G3 não tiver aparecido:
|
- | |
| 113 | * - Se surgir outro candle VENDEDOR com FECHAMENTO dentro da região do GR
|
- | |
| 114 | * e MÍNIMA MENOR que a mínima do candidato a G2,
|
- | |
| 115 | * esse novo candle passa a ser o NOVO G2.
|
- | |
| 116 | *
|
- | |
| 117 | * 3) G3 (após ter G2)
|
- | |
| 118 | *
|
- | |
| 119 | * - Após existir um candidato a G2:
|
- | |
| 120 | * Se algum candle COMPRADOR posterior:
|
- | |
| 121 | * * romper o TOPO do G2 (máxima > máximaG2) e
|
- | |
| 122 | * * tiver FUNDO MENOR OU IGUAL ao FUNDO do GR (mínima <= minGR)
|
- | |
| 123 | * => esse candle será o G3, confirmando o padrão.
|
- | |
| 124 | *
|
- | |
| 125 | * - Ao encontrar G3, o padrão VENDEDOR é considerado COMPLETO (GR,G1,G2,G3).
|
- | |
| 126 | *
|
- | |
| 127 | * -------------------------------------------------------------
|
- | |
| 128 | * BACKTEST:
|
- | |
| 129 | * - identificarPadroes: varre a lista inteira e retorna todos os padrões
|
- | |
| 130 | * completos ou parciais (até G2).
|
- | |
| 131 | *
|
- | |
| 132 | * TEMPO REAL:
|
- | |
| 133 | * - processarCandleTempoReal: chamado a cada novo candle, retorna um padrão
|
- | |
| 134 | * assim que ele for identificado (até G3).
|
- | |
| 135 | *
|
- | |
| 136 | * DEBUG:
|
- | |
| 137 | * - debugarAPartirDoIndice: roda a detecção a partir de um índice e
|
- | |
| 138 | * retorna um relatório (List<String>) com tudo o que aconteceu.
|
- | |
| - | 13 | * completamente integrado com enum TipoPadrao.
|
|
| 139 | */
|
14 | */
|
| 140 | public class DetectorGatilhos { |
15 | public class DetectorGatilhos { |
| 141 | 16 | ||
| 142 | private final boolean logAtivo; |
17 | private final boolean logAtivo; |
| 143 | private int idxProximaAnaliseTempoReal = 0; |
18 | private int idxProximaAnaliseTempoReal = 0; |
| Line 147... | Line 22... | ||
| 147 | * Se for null, não acumula; se não for null, log() adiciona aqui também.
|
22 | * Se for null, não acumula; se não for null, log() adiciona aqui também.
|
| 148 | */
|
23 | */
|
| 149 | private List<String> bufferDebug; |
24 | private List<String> bufferDebug; |
| 150 | 25 | ||
| 151 | public DetectorGatilhos() { |
26 | public DetectorGatilhos() { |
| 152 | this(true); |
- | |
| - | 27 | this(false); |
|
| 153 | }
|
28 | }
|
| 154 | 29 | ||
| 155 | public DetectorGatilhos(boolean logAtivo) { |
30 | public DetectorGatilhos(boolean logAtivo) { |
| 156 | this.logAtivo = logAtivo; |
31 | this.logAtivo = logAtivo; |
| 157 | }
|
32 | }
|
| Line 163... | Line 38... | ||
| 163 | if (bufferDebug != null) { |
38 | if (bufferDebug != null) { |
| 164 | bufferDebug.add(msg); |
39 | bufferDebug.add(msg); |
| 165 | }
|
40 | }
|
| 166 | }
|
41 | }
|
| 167 | 42 | ||
| 168 | /**
|
- | |
| 169 | * Estrutura interna para carregar o resultado da detecção
|
- | |
| 170 | * iniciando em um índice específico.
|
- | |
| 171 | *
|
- | |
| 172 | * lastIndex = último índice efetivamente analisado na busca daquele padrão.
|
- | |
| 173 | * proximoInicio = índice sugerido para o PRÓXIMO início de análise:
|
- | |
| 174 | * - Se NÃO existiu GR: idxA + 1
|
- | |
| 175 | * - Se existiu GR: idxGR + 1 (recomeça após a referência)
|
- | |
| 176 | */
|
- | |
| - | 43 | // ================================================================
|
|
| - | 44 | // Estrutura interna de retorno
|
|
| - | 45 | // ================================================================
|
|
| 177 | private static class ResultadoPadrao { |
46 | private static class ResultadoPadrao { |
| 178 | PadraoGatilho padrao;
|
47 | PadraoGatilho padrao;
|
| 179 | int lastIndex; |
48 | int lastIndex; |
| 180 | int proximoInicio; |
49 | int proximoInicio; |
| 181 | 50 | ||
| Line 184... | Line 53... | ||
| 184 | this.lastIndex = lastIndex; |
53 | this.lastIndex = lastIndex; |
| 185 | this.proximoInicio = proximoInicio; |
54 | this.proximoInicio = proximoInicio; |
| 186 | }
|
55 | }
|
| 187 | }
|
56 | }
|
| 188 | 57 | ||
| 189 | /**
|
- | |
| 190 | * Cria um resultado com padrão PARCIAL (até G2).
|
- | |
| 191 | * Sempre que há GR, o próximo início deve ser após o GR.
|
- | |
| 192 | */
|
- | |
| - | 58 | // ================================================================
|
|
| - | 59 | // Criador de padrão parcial
|
|
| - | 60 | // ================================================================
|
|
| 193 | private ResultadoPadrao criarResultadoParcialComG2(Candle ref, |
61 | private ResultadoPadrao criarResultadoParcialComG2(Candle ref, |
| 194 | Candle g1, |
62 | Candle g1, |
| 195 | Candle g2, |
63 | Candle g2, |
| 196 | int lastIndex,
|
64 | int lastIndex,
|
| 197 | int idxGR) { |
65 | int idxGR) { |
| - | 66 | ||
| 198 | PadraoGatilho padrao = new PadraoGatilho(); |
67 | PadraoGatilho padrao = new PadraoGatilho(); |
| 199 | padrao.setReferencia(ref); |
68 | padrao.setReferencia(ref); |
| 200 | padrao.setGatilho1(g1); |
69 | padrao.setGatilho1(g1); |
| 201 | padrao.setGatilho2(g2); |
70 | padrao.setGatilho2(g2); |
| 202 | padrao.setGatilho3(null); |
71 | padrao.setGatilho3(null); |
| 203 | padrao.setGatilho4(null); // G4 será tratado na camada de estratégia, não aqui. |
- | |
| - | 72 | padrao.setGatilho4(null); |
|
| - | 73 | padrao.setTipoPadrao(TipoPadrao.PARCIAL_G2); // ENUM |
|
| - | 74 | ||
| - | 75 | log(String.format("Padrão PARCIAL_G2: GR[%s], G1[%s], G2[%s]", |
|
| - | 76 | ref.getDataHora(), g1.getDataHora(), g2.getDataHora())); |
|
| - | 77 | ||
| 204 | return new ResultadoPadrao(padrao, lastIndex, idxGR + 1); |
78 | return new ResultadoPadrao(padrao, lastIndex, idxGR + 1); |
| 205 | }
|
79 | }
|
| 206 | 80 | ||
| 207 | // =====================================================================
|
- | |
| - | 81 | // ================================================================
|
|
| 208 | // HELPERS
|
82 | // HELPERS
|
| 209 | // =====================================================================
|
- | |
| 210 | - | ||
| - | 83 | // ================================================================
|
|
| 211 | private boolean isInside(List<Candle> candles, int idx) { |
84 | private boolean isInside(List<Candle> candles, int idx) { |
| 212 | if (idx <= 0) return false; |
85 | if (idx <= 0) return false; |
| - | 86 | ||
| 213 | Candle atual = candles.get(idx); |
87 | Candle atual = candles.get(idx); |
| 214 | Candle anterior = candles.get(idx - 1); |
88 | Candle anterior = candles.get(idx - 1); |
| - | 89 | ||
| 215 | return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima()) |
90 | return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima()) |
| 216 | && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima()); |
- | |
| - | 91 | && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima()); |
|
| 217 | }
|
92 | }
|
| 218 | 93 | ||
| 219 | private boolean isDirecional(Candle c) { |
94 | private boolean isDirecional(Candle c) { |
| 220 | return c.isCandleComprador() || c.isCandleVendedor(); |
95 | return c.isCandleComprador() || c.isCandleVendedor(); |
| 221 | }
|
96 | }
|
| 222 | 97 | ||
| 223 | private boolean fechamentoDentroRegiaoGR(Candle c, Candle gr) { |
98 | private boolean fechamentoDentroRegiaoGR(Candle c, Candle gr) { |
| 224 | return BigDecimalUtils.ehMaiorOuIgualQue(c.getFechamento(), gr.getMinima()) |
99 | return BigDecimalUtils.ehMaiorOuIgualQue(c.getFechamento(), gr.getMinima()) |
| 225 | && BigDecimalUtils.ehMenorOuIgualQue(c.getFechamento(), gr.getMaxima()); |
- | |
| - | 100 | && BigDecimalUtils.ehMenorOuIgualQue(c.getFechamento(), gr.getMaxima()); |
|
| 226 | }
|
101 | }
|
| 227 | 102 | ||
| 228 | /**
|
- | |
| 229 | * Extensão de Fibonacci simples:
|
- | |
| 230 | * origem + (destino - origem) * fator
|
- | |
| 231 | */
|
- | |
| 232 | private BigDecimal fibExtend(BigDecimal origem, BigDecimal destino, BigDecimal fator) { |
103 | private BigDecimal fibExtend(BigDecimal origem, BigDecimal destino, BigDecimal fator) { |
| 233 | BigDecimal diff = destino.subtract(origem); |
- | |
| 234 | return origem.add(diff.multiply(fator)); |
- | |
| - | 104 | return origem.add(destino.subtract(origem).multiply(fator)); |
|
| 235 | }
|
105 | }
|
| 236 | 106 | ||
| 237 | // =====================================================================
|
- | |
| - | 107 | // ================================================================
|
|
| 238 | // API PRINCIPAL – BACKTEST
|
108 | // API PRINCIPAL – BACKTEST
|
| 239 | // =====================================================================
|
- | |
| - | 109 | // ================================================================
|
|
| - | 110 | public List<PadraoGatilho> identificarPadroes(List<Candle> candles) { |
|
| 240 | 111 | ||
| 241 | public List<PadraoGatilho> identificarPadroes(List<Candle> candles) { |
- | |
| 242 | List<PadraoGatilho> padroes = new ArrayList<>(); |
- | |
| 243 | int n = candles.size(); |
- | |
| 244 | if (n < 4) return padroes; |
- | |
| - | 112 | List<PadraoGatilho> padroes = new ArrayList<>(); |
|
| - | 113 | int n = candles.size(); |
|
| - | 114 | if (n < 4) return padroes; |
|
| 245 | 115 | ||
| 246 | int idxRef = 0; |
- | |
| 247 | while (idxRef < n - 3) { |
- | |
| 248 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef); |
- | |
| - | 116 | log("===== INÍCIO BACKTEST (varrendo todos os candles como possível A) ====="); |
|
| 249 | 117 | ||
| 250 | if (resultado == null) { |
- | |
| 251 | idxRef++;
|
- | |
| 252 | continue; |
- | |
| 253 | }
|
- | |
| - | 118 | for (int idxRef = 0; idxRef < n - 3; idxRef++) { |
|
| 254 | 119 | ||
| 255 | if (resultado.padrao != null) { |
- | |
| 256 | padroes.add(resultado.padrao); |
- | |
| 257 | }
|
- | |
| - | 120 | log(String.format("---- Nova tentativa: idxRef = %d (candle #%d) ----", idxRef, idxRef + 1)); |
|
| 258 | 121 | ||
| 259 | // Regra de avanço:
|
- | |
| 260 | // - Se houve GR, proximoInicio = idxGR + 1
|
- | |
| 261 | // - Se não houve GR, proximoInicio = idxRef + 1
|
- | |
| 262 | int proximoInicio = resultado.proximoInicio; |
- | |
| 263 | if (proximoInicio <= idxRef) { |
- | |
| 264 | proximoInicio = idxRef + 1; |
- | |
| 265 | }
|
- | |
| - | 122 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef); |
|
| 266 | 123 | ||
| 267 | idxRef = proximoInicio;
|
- | |
| 268 | }
|
- | |
| - | 124 | if (resultado == null) { |
|
| - | 125 | log(String.format("idxRef=%d: ResultadoPadrao null, seguindo para próximo candle.", idxRef)); |
|
| - | 126 | continue; |
|
| - | 127 | }
|
|
| - | 128 | ||
| - | 129 | if (resultado.padrao != null) { |
|
| - | 130 | PadraoGatilho p = resultado.padrao; |
|
| - | 131 | Candle gr = p.getReferencia(); |
|
| - | 132 | Candle g1 = p.getGatilho1(); |
|
| - | 133 | Candle g2 = p.getGatilho2(); |
|
| - | 134 | Candle g3 = p.getGatilho3(); |
|
| - | 135 | ||
| - | 136 | log(String.format( |
|
| - | 137 | ">> PADRÃO ENCONTRADO a partir de idxRef=%d: GR[%s], G1[%s], G2[%s], G3[%s], tipo=%s",
|
|
| - | 138 | idxRef, |
|
| - | 139 | (gr != null ? gr.getDataHora() : "null"), |
|
| - | 140 | (g1 != null ? g1.getDataHora() : "null"), |
|
| - | 141 | (g2 != null ? g2.getDataHora() : "null"), |
|
| - | 142 | (g3 != null ? g3.getDataHora() : "null"), |
|
| - | 143 | p.getTipoPadrao() |
|
| - | 144 | )); |
|
| - | 145 | ||
| - | 146 | padroes.add(p); |
|
| - | 147 | } else { |
|
| - | 148 | log(String.format("idxRef=%d: nenhuma formação de padrão (sem GR/G1/G2/G3 válidos).", idxRef)); |
|
| - | 149 | }
|
|
| - | 150 | }
|
|
| - | 151 | ||
| - | 152 | log("===== FIM BACKTEST (varredura completa) ====="); |
|
| - | 153 | return padroes; |
|
| - | 154 | }
|
|
| 269 | 155 | ||
| 270 | return padroes; |
- | |
| 271 | }
|
- | |
| 272 | 156 | ||
| 273 | private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) { |
157 | private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) { |
| - | 158 | ||
| 274 | Candle ref = candles.get(idxRef); |
159 | Candle ref = candles.get(idxRef); |
| 275 | 160 | ||
| 276 | if (ref.isCandleComprador()) { |
161 | if (ref.isCandleComprador()) { |
| 277 | return detectarPadraoComprador(candles, idxRef); |
162 | return detectarPadraoComprador(candles, idxRef); |
| - | 163 | ||
| 278 | } else if (ref.isCandleVendedor()) { |
164 | } else if (ref.isCandleVendedor()) { |
| 279 | return detectarPadraoVendedor(candles, idxRef); |
165 | return detectarPadraoVendedor(candles, idxRef); |
| - | 166 | ||
| 280 | } else { |
167 | } else { |
| 281 | // Candle neutro não é A; não existe GR, avança 1 candle.
|
- | |
| - | 168 | log(String.format("Candle[%d] neutro; avançando.", idxRef + 1)); |
|
| 282 | return new ResultadoPadrao(null, idxRef, idxRef + 1); |
169 | return new ResultadoPadrao(null, idxRef, idxRef + 1); |
| 283 | }
|
170 | }
|
| 284 | }
|
171 | }
|
| - | 172 | ||
| - | 173 | // ================================================================
|
|
| - | 174 | // DEBUG BACKTEST COMPLETO
|
|
| - | 175 | // ================================================================
|
|
| - | 176 | public List<String> debugarBacktestCompleto(List<Candle> candles) { |
|
| - | 177 | List<String> relatorio = new ArrayList<>(); |
|
| - | 178 | List<String> antigo = this.bufferDebug; |
|
| - | 179 | ||
| - | 180 | this.bufferDebug = relatorio; |
|
| - | 181 | try { |
|
| - | 182 | log("####################################################"); |
|
| - | 183 | log("DEBUG BACKTEST COMPLETO - iniciar identificarPadroes()"); |
|
| - | 184 | identificarPadroes(candles); |
|
| - | 185 | log("DEBUG BACKTEST COMPLETO - fim identificarPadroes()"); |
|
| - | 186 | log("####################################################"); |
|
| - | 187 | } finally { |
|
| - | 188 | this.bufferDebug = antigo; |
|
| - | 189 | }
|
|
| - | 190 | ||
| - | 191 | return relatorio; |
|
| - | 192 | }
|
|
| 285 | 193 | ||
| 286 | // =====================================================================
|
- | |
| 287 | // PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
|
- | |
| 288 | // =====================================================================
|
- | |
| 289 | 194 | ||
| - | 195 | // ================================================================
|
|
| - | 196 | // PADRÃO COMPRADOR (VENDA)
|
|
| - | 197 | // ================================================================
|
|
| 290 | private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxA) { |
198 | private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxA) { |
| - | 199 | ||
| 291 | int n = candles.size(); |
200 | int n = candles.size(); |
| 292 | Candle candleA = candles.get(idxA); |
201 | Candle candleA = candles.get(idxA); |
| - | 202 | ||
| 293 | if (!candleA.isCandleComprador()) { |
203 | if (!candleA.isCandleComprador()) { |
| 294 | // Não pode ser A comprador -> reinicia no próximo candle.
|
- | |
| 295 | return new ResultadoPadrao(null, idxA, idxA + 1); |
204 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 296 | }
|
205 | }
|
| 297 | 206 | ||
| 298 | int lastIndex = idxA; |
207 | int lastIndex = idxA; |
| 299 | log(String.format("[VENDER] Iniciando busca a partir de A[%d]", idxA + 1)); |
- | |
| 300 | 208 | ||
| 301 | // 1) Verifica se o próximo candle direcional (não-inside) muda a direção
|
- | |
| - | 209 | log("[VENDER] Iniciando em A[" + (idxA + 1) + "] " + candleA.getDataHora()); |
|
| - | 210 | ||
| - | 211 | // --------------------------------------------------------------
|
|
| - | 212 | // Busca candle B direcional (mudança de direção)
|
|
| - | 213 | // --------------------------------------------------------------
|
|
| 302 | int idxProxDirecional = -1; |
214 | int idxProxDirecional = -1; |
| - | 215 | ||
| 303 | for (int i = idxA + 1; i < n; i++) { |
216 | for (int i = idxA + 1; i < n; i++) { |
| - | 217 | ||
| 304 | Candle c = candles.get(i); |
218 | Candle c = candles.get(i); |
| - | 219 | ||
| 305 | if (!isDirecional(c) || isInside(candles, i)) { |
220 | if (!isDirecional(c) || isInside(candles, i)) { |
| 306 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside)", i + 1)); |
221 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside)", i + 1)); |
| 307 | continue; |
222 | continue; |
| 308 | }
|
223 | }
|
| - | 224 | ||
| 309 | idxProxDirecional = i;
|
225 | idxProxDirecional = i;
|
| 310 | break; |
226 | break; |
| 311 | }
|
227 | }
|
| 312 | 228 | ||
| 313 | if (idxProxDirecional == -1) { |
229 | if (idxProxDirecional == -1) { |
| 314 | // Não houve candle B; A não vira candidato; recomeça do próximo candle.
|
- | |
| - | 230 | log("[VENDER] Nenhum candle direcional após A; abortando padrão."); |
|
| 315 | return new ResultadoPadrao(null, idxA, idxA + 1); |
231 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 316 | }
|
232 | }
|
| 317 | 233 | ||
| 318 | Candle prox = candles.get(idxProxDirecional); |
234 | Candle prox = candles.get(idxProxDirecional); |
| - | 235 | ||
| 319 | if (!prox.isCandleVendedor()) { |
236 | if (!prox.isCandleVendedor()) { |
| 320 | // Não houve mudança direcional imediata -> A não vira candidato
|
- | |
| - | 237 | log(String.format("[VENDER] Próximo direcional [%d] não é vendedor; A não vira referência.", idxProxDirecional + 1)); |
|
| 321 | return new ResultadoPadrao(null, idxA, idxA + 1); |
238 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 322 | }
|
239 | }
|
| 323 | 240 | ||
| 324 | // A vira candidato à referência
|
- | |
| - | 241 | // Candidato à referência
|
|
| 325 | Candle candidatoRef = candleA;
|
242 | Candle candidatoRef = candleA;
|
| 326 | int idxCandidatoRef = idxA; |
243 | int idxCandidatoRef = idxA; |
| 327 | log(String.format("[VENDER] CandidatoRef inicial = idx %d", idxCandidatoRef + 1)); |
244 | log(String.format("[VENDER] CandidatoRef inicial = idx %d", idxCandidatoRef + 1)); |
| 328 | 245 | ||
| 329 | // 2) Busca G1, com referência dinâmica até G1
|
- | |
| 330 | Candle g1 = null; |
246 | Candle g1 = null; |
| 331 | int idxG1 = -1; |
247 | int idxG1 = -1; |
| 332 | 248 | ||
| - | 249 | // --------------------------------------------------------------
|
|
| - | 250 | // Busca GR dinâmico e G1
|
|
| - | 251 | // --------------------------------------------------------------
|
|
| 333 | for (int i = idxA + 1; i < n; i++) { |
252 | for (int i = idxA + 1; i < n; i++) { |
| - | 253 | ||
| 334 | Candle c = candles.get(i); |
254 | Candle c = candles.get(i); |
| - | 255 | ||
| 335 | if (!isDirecional(c) || isInside(candles, i)) { |
256 | if (!isDirecional(c) || isInside(candles, i)) { |
| 336 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1)); |
257 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1)); |
| 337 | continue; |
258 | continue; |
| 338 | }
|
259 | }
|
| 339 | 260 | ||
| 340 | lastIndex = i;
|
261 | lastIndex = i;
|
| 341 | 262 | ||
| 342 | // Primeiro checa G1: vendedor que rompe fundo do candidatoRef
|
- | |
| - | 263 | // G1
|
|
| 343 | if (c.isCandleVendedor() |
264 | if (c.isCandleVendedor() |
| 344 | && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
- | |
| - | 265 | && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
|
| - | 266 | ||
| 345 | g1 = c;
|
267 | g1 = c;
|
| 346 | idxG1 = i;
|
268 | idxG1 = i;
|
| 347 | log(String.format("[VENDER] G1 encontrado em [%d], rompendo fundo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1)); |
- | |
| - | 269 | log(String.format("[VENDER] G1 encontrado em [%d], rompendo fundo de candRef[%d]", |
|
| - | 270 | idxG1 + 1, idxCandidatoRef + 1)); |
|
| 348 | break; |
271 | break; |
| 349 | }
|
272 | }
|
| 350 | 273 | ||
| 351 | // Se ainda não encontrou G1, atualiza candidatoRef se máxima maior
|
- | |
| - | 274 | // Atualização do GR dinâmico
|
|
| 352 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
275 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
| 353 | candidatoRef = c;
|
276 | candidatoRef = c;
|
| 354 | idxCandidatoRef = i;
|
277 | idxCandidatoRef = i;
|
| 355 | log(String.format("[VENDER] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1)); |
278 | log(String.format("[VENDER] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1)); |
| 356 | }
|
279 | }
|
| 357 | }
|
280 | }
|
| 358 | 281 | ||
| 359 | if (g1 == null) { |
282 | if (g1 == null) { |
| 360 | // Não formou G1; não existe GR definitivo; recomeça a partir do próximo candle após A.
|
- | |
| - | 283 | log("[VENDER] Não formou G1; recomeçando após A."); |
|
| 361 | return new ResultadoPadrao(null, lastIndex, idxA + 1); |
284 | return new ResultadoPadrao(null, lastIndex, idxA + 1); |
| 362 | }
|
285 | }
|
| 363 | 286 | ||
| 364 | Candle gr = candidatoRef;
|
287 | Candle gr = candidatoRef;
|
| 365 | int idxGR = idxCandidatoRef; |
288 | int idxGR = idxCandidatoRef; |
| - | 289 | ||
| 366 | log(String.format("[VENDER] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1)); |
290 | log(String.format("[VENDER] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1)); |
| 367 | 291 | ||
| 368 | // 3) Regra de saída do G1 – Fibonacci 200% (origem = máxG1, destino = mínG1)
|
- | |
| - | 292 | // --------------------------------------------------------------
|
|
| - | 293 | // Fibonacci 200% (origem = máxG1, destino = mínG1)
|
|
| - | 294 | // --------------------------------------------------------------
|
|
| 369 | BigDecimal fib200 = fibExtend(g1.getMaxima(), g1.getMinima(), new BigDecimal("2")); |
295 | BigDecimal fib200 = fibExtend(g1.getMaxima(), g1.getMinima(), new BigDecimal("2")); |
| 370 | log(String.format("[VENDER] Fibo200 G1[%d] = %s", idxG1, fib200.toPlainString())); |
- | |
| - | 296 | log(String.format("[VENDER] Fibo200 G1[%d] = %s", idxG1 + 1, fib200.toPlainString())); |
|
| 371 | 297 | ||
| 372 | // 4) Busca G2 dinâmico e G3
|
- | |
| - | 298 | // --------------------------------------------------------------
|
|
| - | 299 | // Busca G2 e G3
|
|
| - | 300 | // --------------------------------------------------------------
|
|
| 373 | Candle g2 = null; |
301 | Candle g2 = null; |
| 374 | int idxG2 = -1; |
302 | int idxG2 = -1; |
| 375 | Candle g3 = null; |
303 | Candle g3 = null; |
| 376 | int idxG3 = -1; |
304 | int idxG3 = -1; |
| 377 | 305 | ||
| 378 | for (int i = idxG1 + 1; i < n; i++) { |
306 | for (int i = idxG1 + 1; i < n; i++) { |
| - | 307 | ||
| 379 | Candle c = candles.get(i); |
308 | Candle c = candles.get(i); |
| - | 309 | ||
| 380 | if (!isDirecional(c) || isInside(candles, i)) { |
310 | if (!isDirecional(c) || isInside(candles, i)) { |
| 381 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1)); |
311 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1)); |
| 382 | continue; |
312 | continue; |
| 383 | }
|
313 | }
|
| 384 | 314 | ||
| 385 | lastIndex = i;
|
315 | lastIndex = i;
|
| 386 | 316 | ||
| 387 | // --- Regras de DESCARTE após G1 (devem reiniciar do candle após o GR) ---
|
- | |
| - | 317 | // DESCARTES:
|
|
| 388 | 318 | ||
| 389 | // 4.1) Se candle romper TOPO do GR => descarta operação
|
- | |
| - | 319 | // Rompe topo do GR
|
|
| 390 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
320 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
| 391 | log(String.format("[VENDER] GR[%d]: candle[%d] rompeu topo do GR. Descartando padrão.", idxGR + 1, i + 1)); |
- | |
| - | 321 | log(String.format("[VENDER] GR[%d]: candle[%d] rompeu topo do GR. Descartando padrão.", |
|
| - | 322 | idxGR + 1, i + 1)); |
|
| 392 | return new ResultadoPadrao(null, i, idxGR + 1); |
323 | return new ResultadoPadrao(null, i, idxGR + 1); |
| 393 | }
|
324 | }
|
| 394 | 325 | ||
| 395 | // 4.2) Regra Fib 200% do G1: se mínima <= fib200 => descarta
|
- | |
| - | 326 | // Rompe nível Fib 200%
|
|
| 396 | if (BigDecimalUtils.ehMenorOuIgualQue(c.getMinima(), fib200)) { |
327 | if (BigDecimalUtils.ehMenorOuIgualQue(c.getMinima(), fib200)) { |
| 397 | log(String.format("[VENDER] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.", |
328 | log(String.format("[VENDER] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.", |
| 398 | idxGR + 1, idxG1 + 1, i + 1)); |
329 | idxGR + 1, idxG1 + 1, i + 1)); |
| 399 | return new ResultadoPadrao(null, i, idxGR + 1); |
330 | return new ResultadoPadrao(null, i, idxGR + 1); |
| 400 | }
|
331 | }
|
| 401 | 332 | ||
| 402 | // --- Construção de G2 dinâmico (COMPRADOR com FECHAMENTO dentro da região do GR) ---
|
- | |
| 403 | if (c.isCandleComprador() && fechamentoDentroRegiaoGR(c, gr)) { |
- | |
| - | 333 | // ==========================
|
|
| - | 334 | // G2
|
|
| - | 335 | // ==========================
|
|
| - | 336 | if (c.isCandleComprador() |
|
| - | 337 | && fechamentoDentroRegiaoGR(c, gr)) { |
|
| - | 338 | ||
| 404 | if (g2 == null) { |
339 | if (g2 == null) { |
| 405 | g2 = c;
|
340 | g2 = c;
|
| 406 | idxG2 = i;
|
341 | idxG2 = i;
|
| 407 | log(String.format("[VENDER] GR[%d], G1[%d]: candidato G2 em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1)); |
- | |
| 408 | } else { |
- | |
| 409 | // Atualização dinâmica: máxima maior e fechamento ainda dentro da região
|
- | |
| 410 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima())) { |
- | |
| 411 | g2 = c;
|
- | |
| 412 | idxG2 = i;
|
- | |
| 413 | log(String.format("[VENDER] GR[%d], G1[%d]: G2 atualizado em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1)); |
- | |
| 414 | }
|
- | |
| - | 342 | log(String.format("[VENDER] GR[%d], G1[%d]: candidato G2 em [%d]", |
|
| - | 343 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
|
| - | 344 | ||
| - | 345 | } else if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima())) { |
|
| - | 346 | g2 = c;
|
|
| - | 347 | idxG2 = i;
|
|
| - | 348 | log(String.format("[VENDER] GR[%d], G1[%d]: G2 atualizado em [%d]", |
|
| - | 349 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
|
| 415 | }
|
350 | }
|
| - | 351 | ||
| 416 | continue; |
352 | continue; |
| 417 | }
|
353 | }
|
| 418 | 354 | ||
| 419 | // --- G3: só pode ser avaliado após existir candidato G2 ---
|
- | |
| - | 355 | // ==========================
|
|
| - | 356 | // G3
|
|
| - | 357 | // ==========================
|
|
| 420 | if (g2 != null && c.isCandleVendedor()) { |
358 | if (g2 != null && c.isCandleVendedor()) { |
| - | 359 | ||
| 421 | boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima()); |
360 | boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima()); |
| 422 | boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima()); |
361 | boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima()); |
| 423 | 362 | ||
| 424 | if (rompeFundoG2 && topoMenorOuIgualGR) { |
363 | if (rompeFundoG2 && topoMenorOuIgualGR) { |
| 425 | g3 = c;
|
364 | g3 = c;
|
| Line 429... | Line 368... | ||
| 429 | break; |
368 | break; |
| 430 | }
|
369 | }
|
| 431 | }
|
370 | }
|
| 432 | }
|
371 | }
|
| 433 | 372 | ||
| - | 373 | // Nenhum G3 → padrão parcial
|
|
| 434 | if (g3 == null) { |
374 | if (g3 == null) { |
| 435 | // Padrão só até G2 (se G2 existir), senão nada.
|
- | |
| - | 375 | ||
| 436 | if (g2 != null) { |
376 | if (g2 != null) { |
| 437 | log(String.format("[VENDER] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", idxGR + 1, idxG1 + 1, idxG2 + 1)); |
- | |
| - | 377 | log(String.format("[VENDER] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", |
|
| - | 378 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
|
| 438 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR); |
379 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR); |
| 439 | }
|
380 | }
|
| 440 | // Já houve GR e G1, mas não houve G2/G3 -> recomeça após GR.
|
- | |
| 441 | log(String.format("[VENDER] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", idxGR + 1, idxG1 + 1)); |
- | |
| - | 381 | ||
| - | 382 | log(String.format("[VENDER] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", |
|
| - | 383 | idxGR + 1, idxG1 + 1)); |
|
| 442 | return new ResultadoPadrao(null, lastIndex, idxGR + 1); |
384 | return new ResultadoPadrao(null, lastIndex, idxGR + 1); |
| 443 | }
|
385 | }
|
| 444 | 386 | ||
| 445 | // Finaliza padrão no G3
|
- | |
| - | 387 | // --------------------------------------------------------------
|
|
| - | 388 | // PADRÃO COMPLETO (COM G3)
|
|
| - | 389 | // --------------------------------------------------------------
|
|
| 446 | PadraoGatilho padrao = new PadraoGatilho(); |
390 | PadraoGatilho padrao = new PadraoGatilho(); |
| 447 | padrao.setReferencia(gr); |
391 | padrao.setReferencia(gr); |
| 448 | padrao.setGatilho1(g1); |
392 | padrao.setGatilho1(g1); |
| 449 | padrao.setGatilho2(g2); |
393 | padrao.setGatilho2(g2); |
| 450 | padrao.setGatilho3(g3); |
394 | padrao.setGatilho3(g3); |
| 451 | padrao.setGatilho4(null); |
395 | padrao.setGatilho4(null); |
| - | 396 | padrao.setTipoPadrao(TipoPadrao.COMPLETO_G3); |
|
| - | 397 | ||
| - | 398 | log(String.format("[VENDER] Padrão COMPLETO_G3: GR[%d], G1[%d], G2[%d], G3[%d]", |
|
| - | 399 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
|
| 452 | 400 | ||
| 453 | // Padrão completo -> próxima busca deve começar após o GR
|
- | |
| 454 | return new ResultadoPadrao(padrao, idxG3, idxGR + 1); |
401 | return new ResultadoPadrao(padrao, idxG3, idxGR + 1); |
| 455 | }
|
402 | }
|
| 456 | 403 | ||
| 457 | // =====================================================================
|
- | |
| 458 | // PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
|
- | |
| 459 | // =====================================================================
|
- | |
| 460 | - | ||
| - | 404 | // ================================================================
|
|
| - | 405 | // PADRÃO VENDEDOR (COMPRA)
|
|
| - | 406 | // ================================================================
|
|
| 461 | private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) { |
407 | private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) { |
| - | 408 | ||
| 462 | int n = candles.size(); |
409 | int n = candles.size(); |
| 463 | Candle candleA = candles.get(idxA); |
410 | Candle candleA = candles.get(idxA); |
| 464 | if (!candleA.isCandleVendedor()) { |
- | |
| 465 | // Não pode ser A vendedor -> reinicia no próximo candle.
|
- | |
| - | 411 | ||
| - | 412 | if (!candleA.isCandleVendedor()) |
|
| 466 | return new ResultadoPadrao(null, idxA, idxA + 1); |
413 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 467 | }
|
- | |
| 468 | 414 | ||
| 469 | int lastIndex = idxA; |
415 | int lastIndex = idxA; |
| 470 | log(String.format("[COMPRAR] Iniciando busca a partir de A[%d]", idxA + 1)); |
- | |
| 471 | 416 | ||
| 472 | // 1) Verifica se o próximo candle direcional (não-inside) muda a direção
|
- | |
| - | 417 | log("[COMPRAR] Iniciando em A[" + (idxA + 1) + "] " + candleA.getDataHora()); |
|
| - | 418 | ||
| - | 419 | // --------------------------------------------------------------
|
|
| - | 420 | // Busca B direcional
|
|
| - | 421 | // --------------------------------------------------------------
|
|
| 473 | int idxProxDirecional = -1; |
422 | int idxProxDirecional = -1; |
| - | 423 | ||
| 474 | for (int i = idxA + 1; i < n; i++) { |
424 | for (int i = idxA + 1; i < n; i++) { |
| - | 425 | ||
| 475 | Candle c = candles.get(i); |
426 | Candle c = candles.get(i); |
| - | 427 | ||
| 476 | if (!isDirecional(c) || isInside(candles, i)) { |
428 | if (!isDirecional(c) || isInside(candles, i)) { |
| 477 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside)", i + 1)); |
429 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside)", i + 1)); |
| 478 | continue; |
430 | continue; |
| 479 | }
|
431 | }
|
| - | 432 | ||
| 480 | idxProxDirecional = i;
|
433 | idxProxDirecional = i;
|
| 481 | break; |
434 | break; |
| 482 | }
|
435 | }
|
| 483 | 436 | ||
| 484 | if (idxProxDirecional == -1) { |
437 | if (idxProxDirecional == -1) { |
| 485 | // Não houve candle B; A não vira candidato; recomeça do próximo candle.
|
- | |
| - | 438 | log("[COMPRAR] Nenhum candle direcional após A; abortando padrão."); |
|
| 486 | return new ResultadoPadrao(null, idxA, idxA + 1); |
439 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 487 | }
|
440 | }
|
| 488 | 441 | ||
| 489 | Candle prox = candles.get(idxProxDirecional); |
442 | Candle prox = candles.get(idxProxDirecional); |
| - | 443 | ||
| 490 | if (!prox.isCandleComprador()) { |
444 | if (!prox.isCandleComprador()) { |
| 491 | // Não houve mudança direcional imediata -> A não vira candidato
|
- | |
| - | 445 | log(String.format("[COMPRAR] Próximo direcional [%d] não é comprador; A não vira referência.", |
|
| - | 446 | idxProxDirecional + 1)); |
|
| 492 | return new ResultadoPadrao(null, idxA, idxA + 1); |
447 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 493 | }
|
448 | }
|
| 494 | 449 | ||
| 495 | // A vira candidato à referência
|
- | |
| - | 450 | // GR dinâmico
|
|
| 496 | Candle candidatoRef = candleA;
|
451 | Candle candidatoRef = candleA;
|
| 497 | int idxCandidatoRef = idxA; |
452 | int idxCandidatoRef = idxA; |
| 498 | log(String.format("[COMPRAR] CandidatoRef inicial = idx %d", idxCandidatoRef + 1)); |
- | |
| 499 | 453 | ||
| 500 | // 2) Busca G1, com referência dinâmica até G1
|
- | |
| 501 | Candle g1 = null; |
454 | Candle g1 = null; |
| 502 | int idxG1 = -1; |
455 | int idxG1 = -1; |
| 503 | 456 | ||
| - | 457 | log(String.format("[COMPRAR] CandidatoRef inicial = idx %d", idxCandidatoRef + 1)); |
|
| - | 458 | ||
| - | 459 | // --------------------------------------------------------------
|
|
| - | 460 | // Busca G1 e GR
|
|
| - | 461 | // --------------------------------------------------------------
|
|
| 504 | for (int i = idxA + 1; i < n; i++) { |
462 | for (int i = idxA + 1; i < n; i++) { |
| - | 463 | ||
| 505 | Candle c = candles.get(i); |
464 | Candle c = candles.get(i); |
| 506 | if (!isDirecional(c) || isInside(candles, i)) { |
465 | if (!isDirecional(c) || isInside(candles, i)) { |
| 507 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1)); |
466 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1)); |
| 508 | continue; |
467 | continue; |
| 509 | }
|
468 | }
|
| 510 | 469 | ||
| 511 | lastIndex = i;
|
470 | lastIndex = i;
|
| 512 | 471 | ||
| 513 | // Primeiro checa G1: comprador que rompe topo do candidatoRef
|
- | |
| 514 | if (c.isCandleComprador() |
472 | if (c.isCandleComprador() |
| 515 | && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
- | |
| - | 473 | && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
|
| - | 474 | ||
| 516 | g1 = c;
|
475 | g1 = c;
|
| 517 | idxG1 = i;
|
476 | idxG1 = i;
|
| 518 | log(String.format("[COMPRAR] G1 encontrado em [%d], rompendo topo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1)); |
- | |
| - | 477 | log(String.format("[COMPRAR] G1 encontrado em [%d], rompendo topo de candRef[%d]", |
|
| - | 478 | idxG1 + 1, idxCandidatoRef + 1)); |
|
| 519 | break; |
479 | break; |
| 520 | }
|
480 | }
|
| 521 | 481 | ||
| 522 | // Se ainda não encontrou G1, atualiza candidatoRef se mínima menor
|
- | |
| 523 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
482 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
| 524 | candidatoRef = c;
|
483 | candidatoRef = c;
|
| 525 | idxCandidatoRef = i;
|
484 | idxCandidatoRef = i;
|
| 526 | log(String.format("[COMPRAR] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1)); |
485 | log(String.format("[COMPRAR] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1)); |
| 527 | }
|
486 | }
|
| 528 | }
|
487 | }
|
| 529 | 488 | ||
| 530 | if (g1 == null) { |
489 | if (g1 == null) { |
| 531 | // Não formou G1; não existe GR definitivo; recomeça a partir do próximo candle após A.
|
- | |
| - | 490 | log("[COMPRAR] Não formou G1; recomeçando após A."); |
|
| 532 | return new ResultadoPadrao(null, lastIndex, idxA + 1); |
491 | return new ResultadoPadrao(null, lastIndex, idxA + 1); |
| 533 | }
|
492 | }
|
| 534 | 493 | ||
| 535 | Candle gr = candidatoRef;
|
494 | Candle gr = candidatoRef;
|
| 536 | int idxGR = idxCandidatoRef; |
495 | int idxGR = idxCandidatoRef; |
| - | 496 | ||
| 537 | log(String.format("[COMPRAR] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1)); |
497 | log(String.format("[COMPRAR] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1)); |
| 538 | 498 | ||
| 539 | // 3) Regra de saída do G1 – Fibonacci 200% (origem = mínG1, destino = máxG1)
|
- | |
| - | 499 | // --------------------------------------------------------------
|
|
| - | 500 | // Fibonacci -100% (aqui usando fator 2 entre mín e máx)
|
|
| - | 501 | // --------------------------------------------------------------
|
|
| 540 | BigDecimal fib200MinMax = fibExtend(g1.getMinima(), g1.getMaxima(), new BigDecimal("2")); |
502 | BigDecimal fib200MinMax = fibExtend(g1.getMinima(), g1.getMaxima(), new BigDecimal("2")); |
| 541 | log(String.format("[COMPRAR] Fibo200 G1[%d] = %s", idxG1 + 1, fib200MinMax.toPlainString())); |
503 | log(String.format("[COMPRAR] Fibo200 G1[%d] = %s", idxG1 + 1, fib200MinMax.toPlainString())); |
| 542 | 504 | ||
| 543 | // 4) Busca G2 dinâmico e G3
|
- | |
| - | 505 | // --------------------------------------------------------------
|
|
| - | 506 | // Busca G2 e G3
|
|
| - | 507 | // --------------------------------------------------------------
|
|
| 544 | Candle g2 = null; |
508 | Candle g2 = null; |
| 545 | int idxG2 = -1; |
509 | int idxG2 = -1; |
| 546 | Candle g3 = null; |
510 | Candle g3 = null; |
| 547 | int idxG3 = -1; |
511 | int idxG3 = -1; |
| 548 | 512 | ||
| 549 | for (int i = idxG1 + 1; i < n; i++) { |
513 | for (int i = idxG1 + 1; i < n; i++) { |
| - | 514 | ||
| 550 | Candle c = candles.get(i); |
515 | Candle c = candles.get(i); |
| 551 | if (!isDirecional(c) || isInside(candles, i)) { |
516 | if (!isDirecional(c) || isInside(candles, i)) { |
| 552 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1)); |
517 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1)); |
| 553 | continue; |
518 | continue; |
| 554 | }
|
519 | }
|
| 555 | 520 | ||
| 556 | lastIndex = i;
|
521 | lastIndex = i;
|
| 557 | 522 | ||
| 558 | // --- Regras de DESCARTE após G1 (reiniciar após GR) ---
|
- | |
| - | 523 | // DESCARTES
|
|
| 559 | 524 | ||
| 560 | // 4.1) Se candle romper FUNDO do GR => descarta operação
|
- | |
| 561 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
525 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
| 562 | log(String.format("[COMPRAR] GR[%d]: candle[%d] rompeu fundo do GR. Descartando padrão.", idxGR + 1, i + 1)); |
- | |
| - | 526 | log(String.format("[COMPRAR] GR[%d]: candle[%d] rompeu fundo do GR. Descartando padrão.", |
|
| - | 527 | idxGR + 1, i + 1)); |
|
| 563 | return new ResultadoPadrao(null, i, idxGR + 1); |
528 | return new ResultadoPadrao(null, i, idxGR + 1); |
| 564 | }
|
529 | }
|
| 565 | 530 | ||
| 566 | // 4.2) Regra Fib 200% do G1: se mínima > fib200MinMax => descarta
|
- | |
| 567 | if (BigDecimalUtils.ehMaiorQue(c.getMinima(), fib200MinMax)) { |
531 | if (BigDecimalUtils.ehMaiorQue(c.getMinima(), fib200MinMax)) { |
| 568 | log(String.format("[COMPRAR] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.", |
532 | log(String.format("[COMPRAR] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.", |
| 569 | idxGR + 1, idxG1 + 1, i + 1)); |
533 | idxGR + 1, idxG1 + 1, i + 1)); |
| 570 | return new ResultadoPadrao(null, i, idxGR + 1); |
534 | return new ResultadoPadrao(null, i, idxGR + 1); |
| 571 | }
|
535 | }
|
| 572 | 536 | ||
| 573 | // --- Construção de G2 dinâmico (VENDEDOR com FECHAMENTO dentro da região do GR) ---
|
- | |
| - | 537 | // ==========================
|
|
| - | 538 | // G2
|
|
| - | 539 | // ==========================
|
|
| 574 | if (c.isCandleVendedor() && fechamentoDentroRegiaoGR(c, gr)) { |
540 | if (c.isCandleVendedor() && fechamentoDentroRegiaoGR(c, gr)) { |
| - | 541 | ||
| 575 | if (g2 == null) { |
542 | if (g2 == null) { |
| 576 | g2 = c;
|
543 | g2 = c;
|
| 577 | idxG2 = i;
|
544 | idxG2 = i;
|
| 578 | log(String.format("[COMPRAR] GR[%d], G1[%d]: candidato G2 em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1)); |
- | |
| 579 | } else { |
- | |
| 580 | // Atualização dinâmica: mínima menor e fechamento ainda dentro da região
|
- | |
| 581 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima())) { |
- | |
| 582 | g2 = c;
|
- | |
| 583 | idxG2 = i;
|
- | |
| 584 | log(String.format("[COMPRAR] GR[%d], G1[%d]: G2 atualizado em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1)); |
- | |
| 585 | }
|
- | |
| - | 545 | log(String.format("[COMPRAR] GR[%d], G1[%d]: candidato G2 em [%d]", |
|
| - | 546 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
|
| - | 547 | ||
| - | 548 | } else if (BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima())) { |
|
| - | 549 | g2 = c;
|
|
| - | 550 | idxG2 = i;
|
|
| - | 551 | log(String.format("[COMPRAR] GR[%d], G1[%d]: G2 atualizado em [%d]", |
|
| - | 552 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
|
| 586 | }
|
553 | }
|
| - | 554 | ||
| 587 | continue; |
555 | continue; |
| 588 | }
|
556 | }
|
| 589 | 557 | ||
| 590 | // --- G3: só pode ser avaliado após existir candidato G2 ---
|
- | |
| - | 558 | // ==========================
|
|
| - | 559 | // G3
|
|
| - | 560 | // ==========================
|
|
| 591 | if (g2 != null && c.isCandleComprador()) { |
561 | if (g2 != null && c.isCandleComprador()) { |
| 592 | boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima()); |
- | |
| 593 | boolean fundoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMinima(), gr.getMinima()); |
- | |
| 594 | 562 | ||
| 595 | if (rompeTopoG2 && fundoMenorOuIgualGR) { |
- | |
| - | 563 | boolean rompeTopo = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima()); |
|
| - | 564 | boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima()); |
|
| - | 565 | ||
| - | 566 | if (rompeTopo && fundoMaiorOuIgualGR) { |
|
| 596 | g3 = c;
|
567 | g3 = c;
|
| 597 | idxG3 = i;
|
568 | idxG3 = i;
|
| 598 | log(String.format("[COMPRAR] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)", |
569 | log(String.format("[COMPRAR] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)", |
| 599 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
570 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
| 600 | break; |
571 | break; |
| 601 | }
|
572 | }
|
| 602 | }
|
573 | }
|
| 603 | }
|
574 | }
|
| 604 | 575 | ||
| - | 576 | // ====================================
|
|
| - | 577 | // Nenhum G3 → padrão parcial
|
|
| - | 578 | // ====================================
|
|
| 605 | if (g3 == null) { |
579 | if (g3 == null) { |
| 606 | // Padrão só até G2 (se G2 existir), senão nada
|
- | |
| - | 580 | ||
| 607 | if (g2 != null) { |
581 | if (g2 != null) { |
| 608 | log(String.format("[COMPRAR] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", idxGR + 1, idxG1 + 1, idxG2 + 1)); |
- | |
| - | 582 | log(String.format("[COMPRAR] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", |
|
| - | 583 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
|
| 609 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR); |
584 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR); |
| 610 | }
|
585 | }
|
| 611 | // Já houve GR e G1, mas não houve G2/G3 -> recomeça após GR.
|
- | |
| 612 | log(String.format("[COMPRAR] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", idxGR + 1, idxG1 + 1)); |
- | |
| - | 586 | ||
| - | 587 | log(String.format("[COMPRAR] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", |
|
| - | 588 | idxGR + 1, idxG1 + 1)); |
|
| 613 | return new ResultadoPadrao(null, lastIndex, idxGR + 1); |
589 | return new ResultadoPadrao(null, lastIndex, idxGR + 1); |
| 614 | }
|
590 | }
|
| 615 | 591 | ||
| 616 | // Finaliza padrão no G3
|
- | |
| - | 592 | // ====================================
|
|
| - | 593 | // PADRÃO COMPLETO (COM G3)
|
|
| - | 594 | // ====================================
|
|
| 617 | PadraoGatilho padrao = new PadraoGatilho(); |
595 | PadraoGatilho padrao = new PadraoGatilho(); |
| 618 | padrao.setReferencia(gr); |
596 | padrao.setReferencia(gr); |
| 619 | padrao.setGatilho1(g1); |
597 | padrao.setGatilho1(g1); |
| 620 | padrao.setGatilho2(g2); |
598 | padrao.setGatilho2(g2); |
| 621 | padrao.setGatilho3(g3); |
599 | padrao.setGatilho3(g3); |
| 622 | padrao.setGatilho4(null); |
600 | padrao.setGatilho4(null); |
| - | 601 | padrao.setTipoPadrao(TipoPadrao.COMPLETO_G3); |
|
| - | 602 | ||
| - | 603 | log(String.format("[COMPRAR] Padrão COMPLETO_G3: GR[%d], G1[%d], G2[%d], G3[%d]", |
|
| - | 604 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
|
| 623 | 605 | ||
| 624 | // Padrão completo -> próxima busca deve começar após o GR
|
- | |
| 625 | return new ResultadoPadrao(padrao, idxG3, idxGR + 1); |
606 | return new ResultadoPadrao(padrao, idxG3, idxGR + 1); |
| 626 | }
|
607 | }
|
| 627 | 608 | ||
| 628 | // =====================================================================
|
- | |
| 629 | // MODO TEMPO REAL
|
- | |
| 630 | // =====================================================================
|
- | |
| 631 | - | ||
| - | 609 | // ================================================================
|
|
| - | 610 | // TEMPO REAL
|
|
| - | 611 | // ================================================================
|
|
| 632 | public void resetTempoReal() { |
612 | public void resetTempoReal() { |
| 633 | this.idxProximaAnaliseTempoReal = 0; |
- | |
| - | 613 | idxProximaAnaliseTempoReal = 0; |
|
| 634 | }
|
614 | }
|
| 635 | 615 | ||
| 636 | /**
|
- | |
| 637 | * Deve ser chamado SEMPRE que um novo candle for adicionado à lista.
|
- | |
| 638 | *
|
- | |
| 639 | * Exemplo:
|
- | |
| 640 | * candles.add(novoCandle);
|
- | |
| 641 | * PadraoGatilho padrao = detector.processarCandleTempoReal(candles);
|
- | |
| 642 | *
|
- | |
| 643 | * if (padrao != null) {
|
- | |
| 644 | * // padrão completo (até G3) encontrado
|
- | |
| 645 | * }
|
- | |
| 646 | */
|
- | |
| 647 | public PadraoGatilho processarCandleTempoReal(List<Candle> candles) { |
616 | public PadraoGatilho processarCandleTempoReal(List<Candle> candles) { |
| - | 617 | ||
| 648 | int n = candles.size(); |
618 | int n = candles.size(); |
| 649 | if (n < 4) return null; |
619 | if (n < 4) return null; |
| 650 | 620 | ||
| 651 | while (idxProximaAnaliseTempoReal < n - 3) { |
621 | while (idxProximaAnaliseTempoReal < n - 3) { |
| 652 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal); |
- | |
| 653 | 622 | ||
| 654 | if (resultado == null) { |
- | |
| - | 623 | ResultadoPadrao r = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal); |
|
| - | 624 | ||
| - | 625 | if (r == null) { |
|
| 655 | idxProximaAnaliseTempoReal++;
|
626 | idxProximaAnaliseTempoReal++;
|
| 656 | continue; |
627 | continue; |
| 657 | }
|
628 | }
|
| 658 | 629 | ||
| 659 | int proximoInicio = resultado.proximoInicio; |
- | |
| 660 | if (proximoInicio <= idxProximaAnaliseTempoReal) { |
- | |
| 661 | proximoInicio = idxProximaAnaliseTempoReal + 1; |
- | |
| 662 | }
|
- | |
| 663 | idxProximaAnaliseTempoReal = proximoInicio;
|
- | |
| - | 630 | int next = Math.max(r.proximoInicio, idxProximaAnaliseTempoReal + 1); |
|
| - | 631 | idxProximaAnaliseTempoReal = next;
|
|
| 664 | 632 | ||
| 665 | if (resultado.padrao != null) { |
- | |
| 666 | return resultado.padrao; |
- | |
| 667 | }
|
- | |
| - | 633 | if (r.padrao != null) |
|
| - | 634 | return r.padrao; |
|
| 668 | }
|
635 | }
|
| 669 | 636 | ||
| 670 | return null; |
637 | return null; |
| 671 | }
|
638 | }
|
| 672 | 639 | ||
| 673 | // =====================================================================
|
- | |
| 674 | // DEBUG / RELATÓRIO
|
- | |
| 675 | // =====================================================================
|
- | |
| 676 | - | ||
| - | 640 | // ================================================================
|
|
| - | 641 | // DEBUG - para usar em JSF
|
|
| - | 642 | // ================================================================
|
|
| 677 | /**
|
643 | /**
|
| 678 | * Roda a lógica de detecção a partir de um índice específico e devolve
|
644 | * Roda a lógica de detecção a partir de um índice específico e devolve
|
| 679 | * um "relatório" em forma de lista de strings com tudo que aconteceu.
|
645 | * um "relatório" em forma de lista de strings com tudo que aconteceu.
|
| 680 | *
|
646 | *
|
| 681 | * NÃO altera idxProximaAnaliseTempoReal.
|
647 | * NÃO altera idxProximaAnaliseTempoReal.
|
| 682 | */
|
648 | */
|
| 683 | public List<String> debugarAPartirDoIndice(List<Candle> candles, int idxInicio) { |
649 | public List<String> debugarAPartirDoIndice(List<Candle> candles, int idxInicio) { |
| - | 650 | ||
| 684 | List<String> relatorio = new ArrayList<>(); |
651 | List<String> relatorio = new ArrayList<>(); |
| 685 | List<String> antigoBuffer = this.bufferDebug; |
- | |
| 686 | this.bufferDebug = relatorio; |
- | |
| - | 652 | ||
| - | 653 | List<String> antigo = bufferDebug; |
|
| - | 654 | bufferDebug = relatorio;
|
|
| - | 655 | ||
| 687 | try { |
656 | try { |
| 688 | log("===================================================="); |
- | |
| 689 | log(String.format("DEBUG: iniciando debugarAPartirDoIndice(%d)", idxInicio)); |
- | |
| - | 657 | log("============= DEBUG ============="); |
|
| - | 658 | log("Iniciando análise no índice " + idxInicio); |
|
| 690 | detectarPadraoAPartir(candles, idxInicio); |
659 | detectarPadraoAPartir(candles, idxInicio); |
| 691 | log(String.format("DEBUG: fim da análise a partir do índice %d", idxInicio)); |
- | |
| 692 | log("===================================================="); |
- | |
| 693 | } finally { |
- | |
| 694 | this.bufferDebug = antigoBuffer; |
- | |
| - | 660 | log("Fim da análise"); |
|
| - | 661 | log("================================"); |
|
| 695 | }
|
662 | }
|
| - | 663 | finally { |
|
| - | 664 | bufferDebug = antigo;
|
|
| - | 665 | }
|
|
| - | 666 | ||
| 696 | return relatorio; |
667 | return relatorio; |
| 697 | }
|
668 | }
|
| 698 | 669 | ||
| 699 | }
|
670 | }
|