Rev 773 | Rev 775 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 760 | blopes | 1 | package br.com.kronus.core; |
| 2 | |||
| 3 | import java.util.ArrayList; |
||
| 764 | blopes | 4 | import java.util.List; |
| 760 | blopes | 5 | |
| 764 | blopes | 6 | import br.com.sl.domain.model.Candle; |
| 7 | import br.com.sl.domain.util.BigDecimalUtils; |
||
| 8 | |||
| 760 | blopes | 9 | /** |
| 773 | blopes | 10 | * Detector de padrões de gatilhos (GR, G1, G2, G3) |
| 764 | blopes | 11 | * trabalhando tanto em modo backtest quanto em modo tempo real. |
| 760 | blopes | 12 | * |
| 773 | blopes | 13 | * IMPORTANTE: |
| 14 | * - Este detector FINALIZA o padrão assim que identifica o G3. |
||
| 15 | * - O G4 será identificado apenas na camada de sinais de trade, fora desta classe. |
||
| 760 | blopes | 16 | * |
| 774 | blopes | 17 | * REGRAS IMPLEMENTADAS (versão alinhada) |
| 18 | * ====================================== |
||
| 773 | blopes | 19 | * |
| 774 | blopes | 20 | * 1) PADRÃO COMPRADOR |
| 21 | * -------------------- |
||
| 22 | * Ponto de partida: |
||
| 23 | * - Um candle COMPRADOR é tomado como CANDIDATO À REFERÊNCIA. |
||
| 760 | blopes | 24 | * |
| 774 | blopes | 25 | * GR DINÂMICO (compra): |
| 26 | * - Enquanto NÃO houver G1: |
||
| 27 | * * Qualquer candle DIRECIONAL posterior (comprador ou vendedor) |
||
| 28 | * que fizer uma MÁXIMA MAIOR que a do candidato atual |
||
| 29 | * passa a ser o NOVO candidato à referência. |
||
| 760 | blopes | 30 | * |
| 774 | blopes | 31 | * G1 (compra): |
| 32 | * - Pode ser QUALQUER candle vendedor subsequente (não precisa ser o primeiro), |
||
| 33 | * desde que ROMPA o FUNDO do candidato: |
||
| 34 | * mínima(candle vendedor) < mínima(candidatoRef). |
||
| 35 | * - No momento em que isso ocorrer: |
||
| 36 | * * Esse candle vendedor é o G1. |
||
| 37 | * * O candidato vigente passa a ser o GR (gatilho referência). |
||
| 760 | blopes | 38 | * |
| 774 | blopes | 39 | * G2 (compra): |
| 40 | * - Após o G1, com GR definido: |
||
| 41 | * * Procurar uma nova tendência COMPRADORA: |
||
| 42 | * - Achar o primeiro candle COMPRADOR após o G1. |
||
| 43 | * - Seguir a sequência compradora (ignorando INSIDE e neutros) |
||
| 44 | * até aparecer candle vendedor. |
||
| 45 | * * O ÚLTIMO candle dessa tendência compradora será candidato a G2. |
||
| 46 | * * Para ser G2: |
||
| 47 | * - O FECHAMENTO deve ficar DENTRO da região [mínGR, máxGR]. |
||
| 48 | * * Se mudar para vendedor sem nenhum candle fechar dentro da região do GR: |
||
| 49 | * - descarta o padrão. |
||
| 50 | * - Regra global comprador: |
||
| 51 | * * Se qualquer candle, após o GR, ROMPER o TOPO do GR |
||
| 52 | * (máxima > máximaGR) em qualquer ponto antes da conclusão: |
||
| 53 | * - descarta o padrão. |
||
| 54 | * |
||
| 55 | * G3 (compra) – NOVA REGRA: |
||
| 773 | blopes | 56 | * - Após o G2: |
| 774 | blopes | 57 | * * Se ALGUM candle vendedor subsequente: |
| 58 | * - romper o FUNDO do G2 (mínima < mínimaG2) E |
||
| 59 | * - tiver TOPO menor ou igual ao TOPO do GR (máxima ≤ máximaGR) |
||
| 60 | * então esse candle será o G3. |
||
| 61 | * - Não precisa ser o primeiro vendedor após o G2; pode haver mudança de tendência, |
||
| 62 | * candles inside, etc., desde que a regra acima seja satisfeita e não ocorra: |
||
| 63 | * * outside antes do G3, ou |
||
| 64 | * * rompimento do topo do GR (regra global). |
||
| 65 | * - Ao identificar G3, o padrão é finalizado. |
||
| 760 | blopes | 66 | * |
| 774 | blopes | 67 | * 2) PADRÃO VENDEDOR |
| 68 | * ------------------- |
||
| 69 | * Ponto de partida: |
||
| 70 | * - Um candle VENDEDOR é tomado como CANDIDATO À REFERÊNCIA. |
||
| 764 | blopes | 71 | * |
| 774 | blopes | 72 | * GR DINÂMICO (venda): |
| 73 | * - Enquanto NÃO houver G1: |
||
| 74 | * * Qualquer candle DIRECIONAL posterior (comprador ou vendedor) |
||
| 75 | * que fizer uma MÍNIMA MENOR que a do candidato atual |
||
| 76 | * passa a ser o NOVO candidato à referência. |
||
| 764 | blopes | 77 | * |
| 774 | blopes | 78 | * G1 (venda): |
| 79 | * - Pode ser QUALQUER candle comprador subsequente (não precisa ser o primeiro), |
||
| 80 | * desde que ROMPA o TOPO do candidato: |
||
| 81 | * máxima(candle comprador) > máxima(candidatoRef). |
||
| 82 | * - No momento em que isso ocorrer: |
||
| 83 | * * Esse candle comprador é o G1. |
||
| 84 | * * O candidato vigente passa a ser o GR (gatilho referência). |
||
| 764 | blopes | 85 | * |
| 773 | blopes | 86 | * Regra global vendedor: |
| 774 | blopes | 87 | * - Se qualquer candle posterior ao GR ROMPER o FUNDO do GR |
| 88 | * (mínima < mínimaGR) em qualquer momento antes da conclusão: |
||
| 89 | * - descarta o padrão. |
||
| 764 | blopes | 90 | * |
| 774 | blopes | 91 | * G2 (venda): |
| 92 | * - Após o G1, com GR definido: |
||
| 93 | * * Procurar uma nova tendência VENDEDORA: |
||
| 94 | * - Achar o primeiro VENDEDOR após o G1. |
||
| 95 | * - Seguir a sequência vendedora (ignorando INSIDE e neutros) |
||
| 96 | * até aparecer candle comprador. |
||
| 97 | * * O ÚLTIMO candle dessa tendência vendedora será candidato a G2. |
||
| 98 | * * Para ser G2: |
||
| 99 | * - O FECHAMENTO deve ficar DENTRO da região [mínGR, máxGR]. |
||
| 100 | * * Se mudar para comprador sem nenhum candle fechar dentro da região do GR: |
||
| 101 | * - descarta o padrão. |
||
| 102 | * * Se durante essa fase algum candle romper o FUNDO do GR |
||
| 103 | * (mínima < mínimaGR) → descarta o padrão (regra global). |
||
| 771 | blopes | 104 | * |
| 774 | blopes | 105 | * G3 (venda) – NOVA REGRA: |
| 773 | blopes | 106 | * - Após o G2: |
| 774 | blopes | 107 | * * Se ALGUM candle comprador subsequente: |
| 108 | * - romper o TOPO do G2 (máxima > máximaG2) E |
||
| 109 | * - tiver FUNDO maior ou igual ao FUNDO do GR (mínima ≥ mínimaGR) |
||
| 110 | * então esse candle será o G3. |
||
| 111 | * - Não precisa ser o primeiro comprador após o G2; pode haver mudança de tendência, |
||
| 112 | * candles inside, etc., desde que a regra acima seja satisfeita e não ocorra: |
||
| 113 | * * outside antes do G3, ou |
||
| 114 | * * rompimento do fundo do GR (regra global). |
||
| 115 | * - Ao identificar G3, o padrão é finalizado. |
||
| 771 | blopes | 116 | * |
| 774 | blopes | 117 | * 3) OUTSIDE (ambos os lados) |
| 118 | * --------------------------- |
||
| 119 | * - Candle cuja: |
||
| 120 | * máxima > máxima(candle anterior) |
||
| 121 | * E mínima < mínima(candle anterior). |
||
| 122 | * - Se houver OUTSIDE em qualquer estágio antes de G3: |
||
| 123 | * - o padrão atual (GR/G1/G2) é descartado. |
||
| 771 | blopes | 124 | * |
| 774 | blopes | 125 | * 4) ORDEM CRONOLÓGICA |
| 126 | * --------------------- |
||
| 127 | * - Sempre GR -> G1 -> G2 -> G3. |
||
| 128 | * - Todos em candles diferentes, na ordem do tempo. |
||
| 760 | blopes | 129 | */ |
| 130 | public class DetectorGatilhos { |
||
| 131 | |||
| 764 | blopes | 132 | private final boolean logAtivo; |
| 133 | private int idxProximaAnaliseTempoReal = 0; |
||
| 760 | blopes | 134 | |
| 764 | blopes | 135 | public DetectorGatilhos() { |
| 136 | this(false); |
||
| 137 | } |
||
| 760 | blopes | 138 | |
| 764 | blopes | 139 | public DetectorGatilhos(boolean logAtivo) { |
| 140 | this.logAtivo = logAtivo; |
||
| 141 | } |
||
| 760 | blopes | 142 | |
| 764 | blopes | 143 | private void log(String msg) { |
| 144 | if (logAtivo) { |
||
| 145 | System.out.println(msg); |
||
| 146 | } |
||
| 147 | } |
||
| 760 | blopes | 148 | |
| 774 | blopes | 149 | // ------------------------------------------------------------- |
| 773 | blopes | 150 | // Estrutura interna de retorno |
| 774 | blopes | 151 | // ------------------------------------------------------------- |
| 764 | blopes | 152 | private static class ResultadoPadrao { |
| 771 | blopes | 153 | PadraoGatilho padrao; |
| 154 | int lastIndex; |
||
| 773 | blopes | 155 | |
| 764 | blopes | 156 | ResultadoPadrao(PadraoGatilho padrao, int lastIndex) { |
| 157 | this.padrao = padrao; |
||
| 158 | this.lastIndex = lastIndex; |
||
| 159 | } |
||
| 160 | } |
||
| 760 | blopes | 161 | |
| 774 | blopes | 162 | private ResultadoPadrao criarResultadoParcialComG2(Candle gr, |
| 764 | blopes | 163 | Candle g1, |
| 164 | Candle g2, |
||
| 165 | int lastIndex) { |
||
| 166 | PadraoGatilho padrao = new PadraoGatilho(); |
||
| 774 | blopes | 167 | padrao.setReferencia(gr); |
| 764 | blopes | 168 | padrao.setGatilho1(g1); |
| 169 | padrao.setGatilho2(g2); |
||
| 170 | padrao.setGatilho3(null); |
||
| 773 | blopes | 171 | padrao.setGatilho4(null); // G4 só na camada de trade |
| 764 | blopes | 172 | return new ResultadoPadrao(padrao, lastIndex); |
| 173 | } |
||
| 760 | blopes | 174 | |
| 764 | blopes | 175 | // ===================================================================== |
| 771 | blopes | 176 | // API PRINCIPAL – BACKTEST |
| 764 | blopes | 177 | // ===================================================================== |
| 760 | blopes | 178 | |
| 764 | blopes | 179 | public List<PadraoGatilho> identificarPadroes(List<Candle> candles) { |
| 180 | List<PadraoGatilho> padroes = new ArrayList<>(); |
||
| 181 | int n = candles.size(); |
||
| 771 | blopes | 182 | if (n < 4) return padroes; |
| 760 | blopes | 183 | |
| 764 | blopes | 184 | int idxRef = 0; |
| 185 | while (idxRef < n - 3) { |
||
| 186 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef); |
||
| 760 | blopes | 187 | |
| 764 | blopes | 188 | if (resultado == null) { |
| 189 | idxRef++; |
||
| 190 | continue; |
||
| 191 | } |
||
| 760 | blopes | 192 | |
| 764 | blopes | 193 | if (resultado.padrao != null) { |
| 194 | padroes.add(resultado.padrao); |
||
| 195 | } |
||
| 760 | blopes | 196 | |
| 764 | blopes | 197 | idxRef = Math.max(resultado.lastIndex + 1, idxRef + 1); |
| 198 | } |
||
| 760 | blopes | 199 | |
| 764 | blopes | 200 | return padroes; |
| 201 | } |
||
| 760 | blopes | 202 | |
| 764 | blopes | 203 | private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) { |
| 204 | Candle ref = candles.get(idxRef); |
||
| 205 | if (ref.isCandleComprador()) { |
||
| 206 | return detectarPadraoComprador(candles, idxRef); |
||
| 207 | } else if (ref.isCandleVendedor()) { |
||
| 208 | return detectarPadraoVendedor(candles, idxRef); |
||
| 209 | } else { |
||
| 210 | return new ResultadoPadrao(null, idxRef); |
||
| 211 | } |
||
| 212 | } |
||
| 760 | blopes | 213 | |
| 764 | blopes | 214 | // ===================================================================== |
| 771 | blopes | 215 | // HELPERS |
| 764 | blopes | 216 | // ===================================================================== |
| 760 | blopes | 217 | |
| 771 | blopes | 218 | private boolean isInside(List<Candle> candles, int idx) { |
| 219 | if (idx <= 0) return false; |
||
| 220 | Candle atual = candles.get(idx); |
||
| 221 | Candle anterior = candles.get(idx - 1); |
||
| 222 | return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima()) |
||
| 223 | && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima()); |
||
| 224 | } |
||
| 760 | blopes | 225 | |
| 771 | blopes | 226 | private boolean isOutside(List<Candle> candles, int idx) { |
| 227 | if (idx <= 0) return false; |
||
| 228 | Candle atual = candles.get(idx); |
||
| 229 | Candle anterior = candles.get(idx - 1); |
||
| 230 | return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), anterior.getMaxima()) |
||
| 231 | && BigDecimalUtils.ehMenorQue(atual.getMinima(), anterior.getMinima()); |
||
| 232 | } |
||
| 760 | blopes | 233 | |
| 773 | blopes | 234 | private boolean isDirecional(Candle c) { |
| 235 | return c.isCandleComprador() || c.isCandleVendedor(); |
||
| 236 | } |
||
| 237 | |||
| 771 | blopes | 238 | // ===================================================================== |
| 773 | blopes | 239 | // CASO COMPRADOR – GR DINÂMICO PELA MÁXIMA |
| 771 | blopes | 240 | // ===================================================================== |
| 241 | |||
| 773 | blopes | 242 | private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxInicio) { |
| 771 | blopes | 243 | int n = candles.size(); |
| 773 | blopes | 244 | int lastIndex = idxInicio; |
| 760 | blopes | 245 | |
| 773 | blopes | 246 | Candle candidatoRef = candles.get(idxInicio); |
| 247 | if (!candidatoRef.isCandleComprador()) { |
||
| 248 | return new ResultadoPadrao(null, idxInicio); |
||
| 764 | blopes | 249 | } |
| 760 | blopes | 250 | |
| 773 | blopes | 251 | int idxCandidatoRef = idxInicio; |
| 760 | blopes | 252 | |
| 773 | blopes | 253 | // -------------------- |
| 774 | blopes | 254 | // 1) Buscar G1: QUALQUER vendedor subsequente que rompa o fundo do candidatoRef |
| 255 | // (com GR dinâmico pela MÁXIMA antes de G1) |
||
| 773 | blopes | 256 | // -------------------- |
| 771 | blopes | 257 | Candle g1 = null; |
| 764 | blopes | 258 | int idxG1 = -1; |
| 773 | blopes | 259 | Candle gr = null; |
| 260 | int idxGR = -1; |
||
| 261 | |||
| 262 | for (int i = idxInicio + 1; i < n; i++) { |
||
| 764 | blopes | 263 | Candle c = candles.get(i); |
| 774 | blopes | 264 | if (isInside(candles, i) || !isDirecional(c)) { |
| 771 | blopes | 265 | continue; |
| 774 | blopes | 266 | } |
| 760 | blopes | 267 | |
| 774 | blopes | 268 | // OUTSIDE antes de G1 => descarta padrão |
| 773 | blopes | 269 | if (isOutside(candles, i)) { |
| 270 | lastIndex = i; |
||
| 271 | log(String.format("Comprador: OUTSIDE em [%d] antes de G1. Abortando padrão.", i)); |
||
| 764 | blopes | 272 | return new ResultadoPadrao(null, lastIndex); |
| 273 | } |
||
| 760 | blopes | 274 | |
| 774 | blopes | 275 | // GR dinâmico (compra): atualiza candidatoRef pelo TOPO mais alto |
| 276 | // enquanto não existir G1 |
||
| 277 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
||
| 278 | candidatoRef = c; |
||
| 279 | idxCandidatoRef = i; |
||
| 280 | lastIndex = i; |
||
| 281 | continue; |
||
| 282 | } |
||
| 283 | |||
| 284 | // G1: QUALQUER vendedor subsequente que rompa o fundo do candidatoRef |
||
| 773 | blopes | 285 | if (c.isCandleVendedor() |
| 286 | && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
||
| 771 | blopes | 287 | g1 = c; |
| 764 | blopes | 288 | idxG1 = i; |
| 773 | blopes | 289 | gr = candidatoRef; |
| 290 | idxGR = idxCandidatoRef; |
||
| 291 | lastIndex = i; |
||
| 764 | blopes | 292 | break; |
| 293 | } |
||
| 773 | blopes | 294 | |
| 295 | lastIndex = i; |
||
| 764 | blopes | 296 | } |
| 760 | blopes | 297 | |
| 774 | blopes | 298 | if (g1 == null || gr == null) { |
| 773 | blopes | 299 | log(String.format("Comprador: não foi possível formar G1 a partir de idx %d.", idxInicio)); |
| 300 | return new ResultadoPadrao(null, lastIndex); |
||
| 301 | } |
||
| 760 | blopes | 302 | |
| 773 | blopes | 303 | log(String.format("GR COMPRADOR em [%d], G1 (vendedor) em [%d].", idxGR, idxG1)); |
| 760 | blopes | 304 | |
| 773 | blopes | 305 | // -------------------- |
| 774 | blopes | 306 | // 2) Buscar G2 – tendência COMPRADORA com fechamento dentro do GR |
| 773 | blopes | 307 | // -------------------- |
| 771 | blopes | 308 | Candle g2 = null; |
| 309 | int idxG2 = -1; |
||
| 310 | |||
| 764 | blopes | 311 | int idxPrimeiroComprador = -1; |
| 312 | for (int i = idxG1 + 1; i < n; i++) { |
||
| 313 | Candle c = candles.get(i); |
||
| 773 | blopes | 314 | if (isInside(candles, i) || !isDirecional(c)) |
| 771 | blopes | 315 | continue; |
| 760 | blopes | 316 | |
| 774 | blopes | 317 | // OUTSIDE antes de G2 => descarta padrão |
| 773 | blopes | 318 | if (isOutside(candles, i)) { |
| 319 | lastIndex = idxGR; |
||
| 774 | blopes | 320 | log(String.format("GR[%d]: OUTSIDE em [%d] antes da tendência compradora de G2.", idxGR, i)); |
| 773 | blopes | 321 | return new ResultadoPadrao(null, lastIndex); |
| 322 | } |
||
| 323 | |||
| 774 | blopes | 324 | // Regra global comprador: se romper topo do GR => descarta |
| 773 | blopes | 325 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
| 326 | lastIndex = i; |
||
| 774 | blopes | 327 | log(String.format("GR[%d]: candle[%d] rompeu TOPO do GR antes/na formação de G2.", idxGR, i)); |
| 773 | blopes | 328 | return new ResultadoPadrao(null, lastIndex); |
| 329 | } |
||
| 330 | |||
| 764 | blopes | 331 | if (c.isCandleComprador()) { |
| 332 | idxPrimeiroComprador = i; |
||
| 333 | break; |
||
| 334 | } else { |
||
| 335 | lastIndex = i; |
||
| 336 | } |
||
| 337 | } |
||
| 760 | blopes | 338 | |
| 773 | blopes | 339 | if (idxPrimeiroComprador == -1) { |
| 340 | log(String.format("GR[%d], G1[%d]: não houve nova tendência compradora para G2.", idxGR, idxG1)); |
||
| 341 | return new ResultadoPadrao(null, lastIndex); |
||
| 342 | } |
||
| 760 | blopes | 343 | |
| 771 | blopes | 344 | Candle ultimoComprador = null; |
| 345 | int idxUltimoComprador = -1; |
||
| 760 | blopes | 346 | |
| 764 | blopes | 347 | for (int i = idxPrimeiroComprador; i < n; i++) { |
| 348 | Candle c = candles.get(i); |
||
| 773 | blopes | 349 | if (isInside(candles, i) || !isDirecional(c)) |
| 350 | continue; |
||
| 760 | blopes | 351 | |
| 774 | blopes | 352 | // OUTSIDE durante G2 => descarta padrão |
| 771 | blopes | 353 | if (isOutside(candles, i)) { |
| 773 | blopes | 354 | lastIndex = idxGR; |
| 774 | blopes | 355 | log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (comprador).", idxGR, i)); |
| 773 | blopes | 356 | return new ResultadoPadrao(null, lastIndex); |
| 357 | } |
||
| 358 | |||
| 774 | blopes | 359 | // Regra global comprador: se romper topo do GR => descarta |
| 773 | blopes | 360 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
| 767 | blopes | 361 | lastIndex = i; |
| 774 | blopes | 362 | log(String.format("GR[%d]: candle[%d] rompeu TOPO do GR durante G2.", idxGR, i)); |
| 767 | blopes | 363 | return new ResultadoPadrao(null, lastIndex); |
| 364 | } |
||
| 365 | |||
| 771 | blopes | 366 | if (c.isCandleComprador()) { |
| 367 | ultimoComprador = c; |
||
| 368 | idxUltimoComprador = i; |
||
| 369 | lastIndex = i; |
||
| 370 | } else if (c.isCandleVendedor()) { |
||
| 371 | lastIndex = i - 1; |
||
| 764 | blopes | 372 | break; |
| 373 | } |
||
| 374 | } |
||
| 760 | blopes | 375 | |
| 773 | blopes | 376 | if (ultimoComprador == null) { |
| 377 | log(String.format("GR[%d], G1[%d]: não houve comprador válido para G2.", idxGR, idxG1)); |
||
| 378 | return new ResultadoPadrao(null, lastIndex); |
||
| 379 | } |
||
| 760 | blopes | 380 | |
| 771 | blopes | 381 | boolean fechamentoDentroRegiaoGR = |
| 382 | BigDecimalUtils.ehMaiorOuIgualQue(ultimoComprador.getFechamento(), gr.getMinima()) && |
||
| 383 | BigDecimalUtils.ehMenorOuIgualQue(ultimoComprador.getFechamento(), gr.getMaxima()); |
||
| 760 | blopes | 384 | |
| 771 | blopes | 385 | if (!fechamentoDentroRegiaoGR) { |
| 773 | blopes | 386 | log(String.format( |
| 774 | blopes | 387 | "GR[%d], G1[%d]: último comprador[%d] NÃO fechou dentro da região do GR.", |
| 388 | idxGR, idxG1, idxUltimoComprador |
||
| 773 | blopes | 389 | )); |
| 764 | blopes | 390 | return new ResultadoPadrao(null, lastIndex); |
| 391 | } |
||
| 760 | blopes | 392 | |
| 771 | blopes | 393 | g2 = ultimoComprador; |
| 394 | idxG2 = idxUltimoComprador; |
||
| 773 | blopes | 395 | log(String.format("GR[%d], G1[%d] => G2 (comprador) em [%d].", idxGR, idxG1, idxG2)); |
| 760 | blopes | 396 | |
| 773 | blopes | 397 | // -------------------- |
| 774 | blopes | 398 | // 3) Buscar G3 – NOVA REGRA: |
| 399 | // QUALQUER vendedor subsequente que rompa fundo de G2 |
||
| 400 | // e tenha topo <= topo do GR. |
||
| 773 | blopes | 401 | // -------------------- |
| 771 | blopes | 402 | Candle g3 = null; |
| 403 | int idxG3 = -1; |
||
| 404 | |||
| 764 | blopes | 405 | for (int i = idxG2 + 1; i < n; i++) { |
| 406 | Candle c = candles.get(i); |
||
| 774 | blopes | 407 | if (!isDirecional(c) || isInside(candles, i)) { |
| 764 | blopes | 408 | continue; |
| 774 | blopes | 409 | } |
| 760 | blopes | 410 | |
| 774 | blopes | 411 | // OUTSIDE antes/durante busca de G3 => descarta padrão |
| 773 | blopes | 412 | if (isOutside(candles, i)) { |
| 413 | lastIndex = idxGR; |
||
| 774 | blopes | 414 | log(String.format("GR[%d]: OUTSIDE em [%d] durante busca de G3 (vendedor).", idxGR, i)); |
| 773 | blopes | 415 | return new ResultadoPadrao(null, lastIndex); |
| 416 | } |
||
| 417 | |||
| 774 | blopes | 418 | // Regra global comprador: romper topo do GR => descarta |
| 773 | blopes | 419 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
| 420 | lastIndex = i; |
||
| 774 | blopes | 421 | log(String.format("GR[%d]: candle[%d] rompeu TOPO do GR durante busca de G3.", idxGR, i)); |
| 773 | blopes | 422 | return new ResultadoPadrao(null, lastIndex); |
| 423 | } |
||
| 424 | |||
| 764 | blopes | 425 | if (c.isCandleVendedor()) { |
| 774 | blopes | 426 | boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima()); |
| 427 | boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima()); |
||
| 428 | if (rompeFundoG2 && topoMenorOuIgualGR) { |
||
| 429 | g3 = c; |
||
| 430 | idxG3 = i; |
||
| 431 | lastIndex = i; |
||
| 432 | break; |
||
| 433 | } |
||
| 764 | blopes | 434 | } |
| 435 | } |
||
| 773 | blopes | 436 | |
| 437 | if (g3 == null) { |
||
| 774 | blopes | 438 | log(String.format("GR[%d], G1[%d], G2[%d]: não houve G3 (comprador -> padrão parcial).", |
| 439 | idxGR, idxG1, idxG2)); |
||
| 771 | blopes | 440 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
| 773 | blopes | 441 | } |
| 760 | blopes | 442 | |
| 774 | blopes | 443 | log(String.format("GR[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d].", |
| 444 | idxGR, idxG1, idxG2, idxG3)); |
||
| 760 | blopes | 445 | |
| 764 | blopes | 446 | PadraoGatilho padrao = new PadraoGatilho(); |
| 771 | blopes | 447 | padrao.setReferencia(gr); |
| 764 | blopes | 448 | padrao.setGatilho1(g1); |
| 449 | padrao.setGatilho2(g2); |
||
| 450 | padrao.setGatilho3(g3); |
||
| 773 | blopes | 451 | padrao.setGatilho4(null); |
| 760 | blopes | 452 | |
| 764 | blopes | 453 | return new ResultadoPadrao(padrao, lastIndex); |
| 454 | } |
||
| 760 | blopes | 455 | |
| 764 | blopes | 456 | // ===================================================================== |
| 773 | blopes | 457 | // CASO VENDEDOR – GR DINÂMICO PELA MÍNIMA |
| 764 | blopes | 458 | // ===================================================================== |
| 760 | blopes | 459 | |
| 773 | blopes | 460 | private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxInicio) { |
| 764 | blopes | 461 | int n = candles.size(); |
| 773 | blopes | 462 | int lastIndex = idxInicio; |
| 760 | blopes | 463 | |
| 773 | blopes | 464 | Candle candidatoRef = candles.get(idxInicio); |
| 465 | if (!candidatoRef.isCandleVendedor()) { |
||
| 466 | return new ResultadoPadrao(null, idxInicio); |
||
| 764 | blopes | 467 | } |
| 760 | blopes | 468 | |
| 773 | blopes | 469 | int idxCandidatoRef = idxInicio; |
| 760 | blopes | 470 | |
| 773 | blopes | 471 | // -------------------- |
| 774 | blopes | 472 | // 1) Buscar G1: QUALQUER comprador subsequente que rompa o topo do candidatoRef |
| 473 | // (com GR dinâmico pela MÍNIMA antes de G1) |
||
| 773 | blopes | 474 | // -------------------- |
| 771 | blopes | 475 | Candle g1 = null; |
| 764 | blopes | 476 | int idxG1 = -1; |
| 773 | blopes | 477 | Candle gr = null; |
| 478 | int idxGR = -1; |
||
| 479 | |||
| 480 | for (int i = idxInicio + 1; i < n; i++) { |
||
| 764 | blopes | 481 | Candle c = candles.get(i); |
| 774 | blopes | 482 | if (isInside(candles, i) || !isDirecional(c)) { |
| 771 | blopes | 483 | continue; |
| 774 | blopes | 484 | } |
| 760 | blopes | 485 | |
| 774 | blopes | 486 | // OUTSIDE antes de G1 => descarta padrão |
| 773 | blopes | 487 | if (isOutside(candles, i)) { |
| 488 | lastIndex = i; |
||
| 489 | log(String.format("Vendedor: OUTSIDE em [%d] antes de G1. Abortando padrão.", i)); |
||
| 764 | blopes | 490 | return new ResultadoPadrao(null, lastIndex); |
| 491 | } |
||
| 760 | blopes | 492 | |
| 774 | blopes | 493 | // GR dinâmico (venda): atualiza candidatoRef pelo FUNDO mais baixo |
| 494 | // enquanto não existir G1 |
||
| 495 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
||
| 496 | candidatoRef = c; |
||
| 497 | idxCandidatoRef = i; |
||
| 498 | lastIndex = i; |
||
| 499 | continue; |
||
| 500 | } |
||
| 501 | |||
| 502 | // G1: QUALQUER comprador subsequente que rompa o topo do candidatoRef |
||
| 773 | blopes | 503 | if (c.isCandleComprador() |
| 504 | && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
||
| 771 | blopes | 505 | g1 = c; |
| 764 | blopes | 506 | idxG1 = i; |
| 773 | blopes | 507 | gr = candidatoRef; |
| 508 | idxGR = idxCandidatoRef; |
||
| 509 | lastIndex = i; |
||
| 764 | blopes | 510 | break; |
| 511 | } |
||
| 773 | blopes | 512 | |
| 513 | lastIndex = i; |
||
| 764 | blopes | 514 | } |
| 760 | blopes | 515 | |
| 774 | blopes | 516 | if (g1 == null || gr == null) { |
| 773 | blopes | 517 | log(String.format("Vendedor: não foi possível formar G1 a partir de idx %d.", idxInicio)); |
| 518 | return new ResultadoPadrao(null, lastIndex); |
||
| 519 | } |
||
| 760 | blopes | 520 | |
| 773 | blopes | 521 | log(String.format("GR VENDEDOR em [%d], G1 (comprador) em [%d].", idxGR, idxG1)); |
| 522 | |||
| 523 | // -------------------- |
||
| 774 | blopes | 524 | // 2) Buscar G2 – tendência VENDEDORA com fechamento dentro do GR |
| 773 | blopes | 525 | // -------------------- |
| 771 | blopes | 526 | Candle g2 = null; |
| 527 | int idxG2 = -1; |
||
| 760 | blopes | 528 | |
| 764 | blopes | 529 | int idxPrimeiroVendedor = -1; |
| 530 | for (int i = idxG1 + 1; i < n; i++) { |
||
| 531 | Candle c = candles.get(i); |
||
| 774 | blopes | 532 | if (isInside(candles, i) || !isDirecional(c)) continue; |
| 760 | blopes | 533 | |
| 774 | blopes | 534 | // OUTSIDE antes de G2 => descarta padrão |
| 773 | blopes | 535 | if (isOutside(candles, i)) { |
| 536 | lastIndex = idxGR; |
||
| 774 | blopes | 537 | log(String.format("GR[%d]: OUTSIDE em [%d] antes da tendência vendedora de G2.", idxGR, i)); |
| 773 | blopes | 538 | return new ResultadoPadrao(null, lastIndex); |
| 539 | } |
||
| 540 | |||
| 774 | blopes | 541 | // Regra global vendedor: romper fundo do GR => descarta |
| 773 | blopes | 542 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
| 543 | lastIndex = i; |
||
| 774 | blopes | 544 | log(String.format("GR[%d]: candle[%d] rompeu FUNDO do GR antes/na formação de G2.", idxGR, i)); |
| 773 | blopes | 545 | return new ResultadoPadrao(null, lastIndex); |
| 546 | } |
||
| 547 | |||
| 764 | blopes | 548 | if (c.isCandleVendedor()) { |
| 549 | idxPrimeiroVendedor = i; |
||
| 550 | break; |
||
| 551 | } else { |
||
| 552 | lastIndex = i; |
||
| 553 | } |
||
| 554 | } |
||
| 760 | blopes | 555 | |
| 773 | blopes | 556 | if (idxPrimeiroVendedor == -1) { |
| 557 | log(String.format("GR[%d], G1[%d]: não houve nova tendência vendedora para G2.", idxGR, idxG1)); |
||
| 558 | return new ResultadoPadrao(null, lastIndex); |
||
| 559 | } |
||
| 560 | |||
| 771 | blopes | 561 | Candle ultimoVendedor = null; |
| 562 | int idxUltimoVendedor = -1; |
||
| 760 | blopes | 563 | |
| 764 | blopes | 564 | for (int i = idxPrimeiroVendedor; i < n; i++) { |
| 565 | Candle c = candles.get(i); |
||
| 774 | blopes | 566 | if (isInside(candles, i) || !isDirecional(c)) continue; |
| 760 | blopes | 567 | |
| 774 | blopes | 568 | // OUTSIDE durante G2 => descarta padrão |
| 771 | blopes | 569 | if (isOutside(candles, i)) { |
| 773 | blopes | 570 | lastIndex = idxGR; |
| 774 | blopes | 571 | log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (vendedor).", idxGR, i)); |
| 773 | blopes | 572 | return new ResultadoPadrao(null, lastIndex); |
| 573 | } |
||
| 574 | |||
| 774 | blopes | 575 | // Regra global vendedor: romper fundo do GR => descarta |
| 773 | blopes | 576 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
| 767 | blopes | 577 | lastIndex = i; |
| 774 | blopes | 578 | log(String.format("GR[%d]: candle[%d] rompeu FUNDO do GR durante G2.", idxGR, i)); |
| 767 | blopes | 579 | return new ResultadoPadrao(null, lastIndex); |
| 580 | } |
||
| 581 | |||
| 771 | blopes | 582 | if (c.isCandleVendedor()) { |
| 583 | ultimoVendedor = c; |
||
| 584 | idxUltimoVendedor = i; |
||
| 585 | lastIndex = i; |
||
| 586 | } else if (c.isCandleComprador()) { |
||
| 587 | lastIndex = i - 1; |
||
| 764 | blopes | 588 | break; |
| 589 | } |
||
| 590 | } |
||
| 760 | blopes | 591 | |
| 773 | blopes | 592 | if (ultimoVendedor == null) { |
| 593 | log(String.format("GR[%d], G1[%d]: não houve vendedor válido para G2.", idxGR, idxG1)); |
||
| 764 | blopes | 594 | return new ResultadoPadrao(null, lastIndex); |
| 773 | blopes | 595 | } |
| 760 | blopes | 596 | |
| 771 | blopes | 597 | boolean fechamentoDentroRegiaoGR = |
| 598 | BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMinima()) && |
||
| 599 | BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMaxima()); |
||
| 760 | blopes | 600 | |
| 773 | blopes | 601 | if (!fechamentoDentroRegiaoGR) { |
| 602 | log(String.format( |
||
| 774 | blopes | 603 | "GR[%d], G1[%d]: último vendedor[%d] NÃO fechou dentro da região do GR.", |
| 604 | idxGR, idxG1, idxUltimoVendedor |
||
| 773 | blopes | 605 | )); |
| 764 | blopes | 606 | return new ResultadoPadrao(null, lastIndex); |
| 773 | blopes | 607 | } |
| 760 | blopes | 608 | |
| 771 | blopes | 609 | g2 = ultimoVendedor; |
| 610 | idxG2 = idxUltimoVendedor; |
||
| 773 | blopes | 611 | log(String.format("GR[%d], G1[%d] => G2 (vendedor) em [%d].", idxGR, idxG1, idxG2)); |
| 760 | blopes | 612 | |
| 773 | blopes | 613 | // -------------------- |
| 774 | blopes | 614 | // 3) Buscar G3 – NOVA REGRA: |
| 615 | // QUALQUER comprador subsequente que rompa topo de G2 |
||
| 616 | // e tenha fundo >= fundo do GR. |
||
| 773 | blopes | 617 | // -------------------- |
| 771 | blopes | 618 | Candle g3 = null; |
| 619 | int idxG3 = -1; |
||
| 620 | |||
| 764 | blopes | 621 | for (int i = idxG2 + 1; i < n; i++) { |
| 622 | Candle c = candles.get(i); |
||
| 774 | blopes | 623 | if (!isDirecional(c) || isInside(candles, i)) { |
| 764 | blopes | 624 | continue; |
| 774 | blopes | 625 | } |
| 760 | blopes | 626 | |
| 774 | blopes | 627 | // OUTSIDE antes/durante busca de G3 => descarta padrão |
| 773 | blopes | 628 | if (isOutside(candles, i)) { |
| 629 | lastIndex = idxGR; |
||
| 774 | blopes | 630 | log(String.format("GR[%d]: OUTSIDE em [%d] durante busca de G3 (comprador).", idxGR, i)); |
| 773 | blopes | 631 | return new ResultadoPadrao(null, lastIndex); |
| 632 | } |
||
| 633 | |||
| 774 | blopes | 634 | // Regra global vendedor: romper fundo do GR => descarta |
| 773 | blopes | 635 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
| 636 | lastIndex = i; |
||
| 774 | blopes | 637 | log(String.format("GR[%d]: candle[%d] rompeu FUNDO do GR durante busca de G3.", idxGR, i)); |
| 773 | blopes | 638 | return new ResultadoPadrao(null, lastIndex); |
| 639 | } |
||
| 640 | |||
| 764 | blopes | 641 | if (c.isCandleComprador()) { |
| 774 | blopes | 642 | boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima()); |
| 643 | boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima()); |
||
| 644 | if (rompeTopoG2 && fundoMaiorOuIgualGR) { |
||
| 645 | g3 = c; |
||
| 646 | idxG3 = i; |
||
| 647 | lastIndex = i; |
||
| 648 | break; |
||
| 649 | } |
||
| 764 | blopes | 650 | } |
| 651 | } |
||
| 773 | blopes | 652 | |
| 653 | if (g3 == null) { |
||
| 774 | blopes | 654 | log(String.format("GR[%d], G1[%d], G2[%d]: não houve G3 (vendedor -> padrão parcial).", |
| 655 | idxGR, idxG1, idxG2)); |
||
| 771 | blopes | 656 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
| 773 | blopes | 657 | } |
| 760 | blopes | 658 | |
| 774 | blopes | 659 | log(String.format("GR[%d], G1[%d], G2[%d] => G3 (comprador) em [%d].", |
| 660 | idxGR, idxG1, idxG2, idxG3)); |
||
| 760 | blopes | 661 | |
| 764 | blopes | 662 | PadraoGatilho padrao = new PadraoGatilho(); |
| 771 | blopes | 663 | padrao.setReferencia(gr); |
| 764 | blopes | 664 | padrao.setGatilho1(g1); |
| 665 | padrao.setGatilho2(g2); |
||
| 666 | padrao.setGatilho3(g3); |
||
| 773 | blopes | 667 | padrao.setGatilho4(null); |
| 760 | blopes | 668 | |
| 764 | blopes | 669 | return new ResultadoPadrao(padrao, lastIndex); |
| 670 | } |
||
| 760 | blopes | 671 | |
| 764 | blopes | 672 | // ===================================================================== |
| 771 | blopes | 673 | // MODO TEMPO REAL |
| 764 | blopes | 674 | // ===================================================================== |
| 760 | blopes | 675 | |
| 764 | blopes | 676 | public void resetTempoReal() { |
| 677 | this.idxProximaAnaliseTempoReal = 0; |
||
| 678 | } |
||
| 760 | blopes | 679 | |
| 774 | blopes | 680 | /** |
| 681 | * Deve ser chamado SEMPRE que um novo candle for adicionado à lista. |
||
| 682 | * |
||
| 683 | * Exemplo: |
||
| 684 | * candles.add(novoCandle); |
||
| 685 | * PadraoGatilho padrao = detector.processarCandleTempoReal(candles); |
||
| 686 | * |
||
| 687 | * if (padrao != null) { |
||
| 688 | * // padrão completo (até G3) encontrado |
||
| 689 | * } |
||
| 690 | */ |
||
| 764 | blopes | 691 | public PadraoGatilho processarCandleTempoReal(List<Candle> candles) { |
| 692 | int n = candles.size(); |
||
| 771 | blopes | 693 | if (n < 4) return null; |
| 760 | blopes | 694 | |
| 764 | blopes | 695 | while (idxProximaAnaliseTempoReal < n - 3) { |
| 696 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal); |
||
| 760 | blopes | 697 | |
| 764 | blopes | 698 | if (resultado == null) { |
| 699 | idxProximaAnaliseTempoReal++; |
||
| 700 | continue; |
||
| 701 | } |
||
| 760 | blopes | 702 | |
| 764 | blopes | 703 | int proximoInicio = resultado.lastIndex + 1; |
| 704 | if (proximoInicio <= idxProximaAnaliseTempoReal) { |
||
| 705 | proximoInicio = idxProximaAnaliseTempoReal + 1; |
||
| 706 | } |
||
| 707 | idxProximaAnaliseTempoReal = proximoInicio; |
||
| 760 | blopes | 708 | |
| 764 | blopes | 709 | if (resultado.padrao != null) { |
| 710 | return resultado.padrao; |
||
| 711 | } |
||
| 712 | } |
||
| 760 | blopes | 713 | |
| 764 | blopes | 714 | return null; |
| 715 | } |
||
| 760 | blopes | 716 | |
| 764 | blopes | 717 | } |