Rev 762 | Rev 767 | 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 | |||
| 762 | blopes | 3 | import java.math.BigDecimal; |
| 760 | blopes | 4 | import java.util.ArrayList; |
| 764 | blopes | 5 | import java.util.List; |
| 760 | blopes | 6 | |
| 764 | blopes | 7 | import br.com.sl.domain.model.Candle; |
| 8 | import br.com.sl.domain.util.BigDecimalUtils; |
||
| 9 | |||
| 760 | blopes | 10 | /** |
| 764 | blopes | 11 | * Detector de padrões de gatilhos (GR, G1, G2, G3, G4) |
| 12 | * trabalhando tanto em modo backtest quanto em modo tempo real. |
||
| 760 | blopes | 13 | * |
| 764 | blopes | 14 | * Regras resumidas: |
| 760 | blopes | 15 | * |
| 764 | blopes | 16 | * GR comprador (A): |
| 17 | * - Candle comprador. |
||
| 18 | * - Último candle da tendência compradora. |
||
| 19 | * - Maior topo da pernada compradora. |
||
| 20 | * - Após ele, tendência muda para vendedora. |
||
| 21 | * - Um vendedor subsequente rompe o fundo de A => G1 vendedor, A vira GR. |
||
| 760 | blopes | 22 | * |
| 764 | blopes | 23 | * GR vendedor (B): |
| 24 | * - Candle vendedor. |
||
| 25 | * - Último candle da tendência vendedora. |
||
| 26 | * - Menor fundo da pernada vendedora. |
||
| 27 | * - Após ele, tendência muda para compradora. |
||
| 28 | * - Um comprador subsequente rompe o topo de B => G1 comprador, B vira GR. |
||
| 760 | blopes | 29 | * |
| 764 | blopes | 30 | * G2 comprador (a partir de GR comprador): |
| 31 | * - Após G1 (vendedor), surge nova tendência compradora. |
||
| 32 | * - O ÚLTIMO candle dessa tendência compradora tem fechamento dentro da região do GR. |
||
| 33 | * - Se nessa tendência compradora alguma barra romper o topo do GR, o padrão é descartado. |
||
| 34 | * - Se a tendência terminar sem que o último comprador feche dentro da região do GR, descarta. |
||
| 760 | blopes | 35 | * |
| 764 | blopes | 36 | * G3 comprador: |
| 37 | * - Após G2, o próximo candle deve ser vendedor. |
||
| 38 | * - Em uma sequência vendedora, algum vendedor: |
||
| 39 | * * rompe o fundo de G2 (mínima < mínima de G2) e |
||
| 40 | * * tem topo <= topo do GR |
||
| 41 | * => esse candle é G3. |
||
| 760 | blopes | 42 | * |
| 764 | blopes | 43 | * G4 comprador: |
| 44 | * - Próximo candle após G3. |
||
| 45 | * - Se romper o topo do GR (máxima > máxima do GR) => G4. |
||
| 46 | * - Desarma a operação, padrão é encerrado, nova análise começa após esse candle. |
||
| 47 | * |
||
| 48 | * G2 vendedor (a partir de GR vendedor): |
||
| 49 | * - Após G1 (comprador), surge nova tendência vendedora. |
||
| 50 | * - O ÚLTIMO candle dessa tendência vendedora tem fechamento dentro da região do GR. |
||
| 51 | * - Se nessa tendência vendedora alguma barra romper o fundo do GR, o padrão é descartado. |
||
| 52 | * |
||
| 53 | * G3 vendedor: |
||
| 54 | * - Após G2, o próximo candle deve ser comprador. |
||
| 55 | * - Em uma sequência compradora, algum comprador: |
||
| 56 | * * rompe o topo de G2 (máxima > máxima de G2) e |
||
| 57 | * * tem fundo >= fundo do GR |
||
| 58 | * => esse candle é G3. |
||
| 59 | * |
||
| 60 | * G4 vendedor: |
||
| 61 | * - Próximo candle após G3. |
||
| 62 | * - Se romper o fundo do GR (mínima < mínima do GR) => G4. |
||
| 63 | * |
||
| 64 | * Outside em relação ao GR: |
||
| 65 | * - Qualquer candle C após o GR que: |
||
| 66 | * * C.maxima > GR.maxima E |
||
| 67 | * * C.minima < GR.minima |
||
| 68 | * => padrão atual é descartado, nova análise começa após C. |
||
| 69 | * |
||
| 70 | * Obs.: Quando um ciclo termina (padrão encontrado ou descartado), |
||
| 71 | * a próxima análise SEMPRE começa após o último candle usado. |
||
| 760 | blopes | 72 | */ |
| 73 | public class DetectorGatilhos { |
||
| 74 | |||
| 764 | blopes | 75 | private final boolean logAtivo; |
| 760 | blopes | 76 | |
| 764 | blopes | 77 | // Estado apenas para o modo tempo real |
| 78 | private int idxProximaAnaliseTempoReal = 0; |
||
| 760 | blopes | 79 | |
| 764 | blopes | 80 | public DetectorGatilhos() { |
| 81 | this(false); |
||
| 82 | } |
||
| 760 | blopes | 83 | |
| 764 | blopes | 84 | public DetectorGatilhos(boolean logAtivo) { |
| 85 | this.logAtivo = logAtivo; |
||
| 86 | } |
||
| 760 | blopes | 87 | |
| 764 | blopes | 88 | private void log(String msg) { |
| 89 | if (logAtivo) { |
||
| 90 | System.out.println(msg); |
||
| 91 | } |
||
| 92 | } |
||
| 760 | blopes | 93 | |
| 764 | blopes | 94 | private static class ResultadoPadrao { |
| 95 | PadraoGatilho padrao; // pode ser null (quando não houve padrão válido) |
||
| 96 | int lastIndex; // último índice de candle usado no ciclo |
||
| 760 | blopes | 97 | |
| 764 | blopes | 98 | ResultadoPadrao(PadraoGatilho padrao, int lastIndex) { |
| 99 | this.padrao = padrao; |
||
| 100 | this.lastIndex = lastIndex; |
||
| 101 | } |
||
| 102 | } |
||
| 760 | blopes | 103 | |
| 764 | blopes | 104 | /** |
| 105 | * Cria um resultado com padrão PARCIAL (até G2). |
||
| 106 | * Usado quando GR, G1 e G2 são válidos, mas G3 não se forma. |
||
| 107 | */ |
||
| 108 | private ResultadoPadrao criarResultadoParcialComG2(Candle ref, |
||
| 109 | Candle g1, |
||
| 110 | Candle g2, |
||
| 111 | int lastIndex) { |
||
| 112 | PadraoGatilho padrao = new PadraoGatilho(); |
||
| 113 | padrao.setReferencia(ref); |
||
| 114 | padrao.setGatilho1(g1); |
||
| 115 | padrao.setGatilho2(g2); |
||
| 116 | padrao.setGatilho3(null); |
||
| 117 | padrao.setGatilho4(null); |
||
| 118 | return new ResultadoPadrao(padrao, lastIndex); |
||
| 119 | } |
||
| 760 | blopes | 120 | |
| 764 | blopes | 121 | // ===================================================================== |
| 122 | // API PRINCIPAL – BACKTEST (lista completa de candles) |
||
| 123 | // ===================================================================== |
||
| 760 | blopes | 124 | |
| 764 | blopes | 125 | public List<PadraoGatilho> identificarPadroes(List<Candle> candles) { |
| 126 | List<PadraoGatilho> padroes = new ArrayList<>(); |
||
| 127 | int n = candles.size(); |
||
| 128 | if (n < 4) { |
||
| 129 | return padroes; |
||
| 130 | } |
||
| 760 | blopes | 131 | |
| 764 | blopes | 132 | int idxRef = 0; |
| 760 | blopes | 133 | |
| 764 | blopes | 134 | while (idxRef < n - 3) { |
| 135 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef); |
||
| 760 | blopes | 136 | |
| 764 | blopes | 137 | if (resultado == null) { |
| 138 | idxRef++; |
||
| 139 | continue; |
||
| 140 | } |
||
| 760 | blopes | 141 | |
| 764 | blopes | 142 | if (resultado.padrao != null) { |
| 143 | padroes.add(resultado.padrao); |
||
| 144 | } |
||
| 760 | blopes | 145 | |
| 764 | blopes | 146 | // Próxima análise sempre começa após o último candle usado no ciclo |
| 147 | idxRef = Math.max(resultado.lastIndex + 1, idxRef + 1); |
||
| 148 | } |
||
| 760 | blopes | 149 | |
| 764 | blopes | 150 | return padroes; |
| 151 | } |
||
| 760 | blopes | 152 | |
| 764 | blopes | 153 | private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) { |
| 154 | Candle ref = candles.get(idxRef); |
||
| 760 | blopes | 155 | |
| 764 | blopes | 156 | if (ref.isCandleComprador()) { |
| 157 | return detectarPadraoComprador(candles, idxRef); |
||
| 158 | } else if (ref.isCandleVendedor()) { |
||
| 159 | return detectarPadraoVendedor(candles, idxRef); |
||
| 160 | } else { |
||
| 161 | // candle sem tendência não serve como GR |
||
| 162 | return new ResultadoPadrao(null, idxRef); |
||
| 163 | } |
||
| 164 | } |
||
| 760 | blopes | 165 | |
| 764 | blopes | 166 | // ===================================================================== |
| 167 | // CASO 1 – GR COMPRADOR (candle A) |
||
| 168 | // ===================================================================== |
||
| 760 | blopes | 169 | |
| 764 | blopes | 170 | private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxRef) { |
| 171 | int n = candles.size(); |
||
| 172 | Candle ref = candles.get(idxRef); // candidato a GR |
||
| 173 | int lastIndex = idxRef; |
||
| 760 | blopes | 174 | |
| 764 | blopes | 175 | // 0) Validar se idxRef pode ser GR comprador (A) |
| 176 | if (!ref.isCandleComprador()) { |
||
| 177 | return new ResultadoPadrao(null, idxRef); |
||
| 178 | } |
||
| 760 | blopes | 179 | |
| 764 | blopes | 180 | // 0.1 – início da pernada compradora que termina em A |
| 181 | int inicioTrendCompra = idxRef; |
||
| 182 | for (int i = idxRef - 1; i >= 0; i--) { |
||
| 183 | Candle c = candles.get(i); |
||
| 184 | if (!c.isCandleComprador()) { |
||
| 185 | break; |
||
| 186 | } |
||
| 187 | inicioTrendCompra = i; |
||
| 188 | } |
||
| 760 | blopes | 189 | |
| 764 | blopes | 190 | // 0.2 – primeiro candle direcional após A |
| 191 | int idxPrimeiroDirecionalAposRef = -1; |
||
| 192 | for (int i = idxRef + 1; i < n; i++) { |
||
| 193 | Candle c = candles.get(i); |
||
| 194 | if (!c.isCandleComprador() && !c.isCandleVendedor()) { |
||
| 195 | continue; |
||
| 196 | } |
||
| 760 | blopes | 197 | |
| 764 | blopes | 198 | // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1) |
| 199 | if (isOutsideReferencia(ref, c)) { |
||
| 200 | lastIndex = i; |
||
| 201 | log(String.format( |
||
| 202 | "RefC[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.", |
||
| 203 | idxRef, i)); |
||
| 204 | return new ResultadoPadrao(null, lastIndex); |
||
| 205 | } |
||
| 760 | blopes | 206 | |
| 764 | blopes | 207 | idxPrimeiroDirecionalAposRef = i; |
| 208 | break; |
||
| 209 | } |
||
| 760 | blopes | 210 | |
| 764 | blopes | 211 | if (idxPrimeiroDirecionalAposRef == -1) { |
| 212 | return new ResultadoPadrao(null, lastIndex); |
||
| 213 | } |
||
| 760 | blopes | 214 | |
| 764 | blopes | 215 | Candle primeiroDirecional = candles.get(idxPrimeiroDirecionalAposRef); |
| 216 | if (!primeiroDirecional.isCandleVendedor()) { |
||
| 217 | // primeiro direcional após A ainda é comprador → A não é último da pernada |
||
| 218 | return new ResultadoPadrao(null, idxRef); |
||
| 219 | } |
||
| 760 | blopes | 220 | |
| 764 | blopes | 221 | // 0.3 – A tem o maior topo da pernada compradora |
| 222 | BigDecimal topoRef = ref.getMaxima(); |
||
| 223 | for (int i = inicioTrendCompra; i <= idxRef; i++) { |
||
| 224 | Candle c = candles.get(i); |
||
| 225 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), topoRef)) { |
||
| 226 | return new ResultadoPadrao(null, idxRef); |
||
| 227 | } |
||
| 228 | } |
||
| 760 | blopes | 229 | |
| 764 | blopes | 230 | int idxPrimeiroVendedor = idxPrimeiroDirecionalAposRef; |
| 760 | blopes | 231 | |
| 764 | blopes | 232 | // 1) Encontrar G1 (vendedor que rompe o fundo do GR) |
| 233 | int idxG1 = -1; |
||
| 234 | Candle g1 = null; |
||
| 760 | blopes | 235 | |
| 764 | blopes | 236 | for (int i = idxPrimeiroVendedor; i < n; i++) { |
| 237 | Candle c = candles.get(i); |
||
| 760 | blopes | 238 | |
| 764 | blopes | 239 | // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1) |
| 240 | if (isOutsideReferencia(ref, c)) { |
||
| 241 | lastIndex = i; |
||
| 242 | log(String.format( |
||
| 243 | "RefC[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.", |
||
| 244 | idxRef, i)); |
||
| 245 | return new ResultadoPadrao(null, lastIndex); |
||
| 246 | } |
||
| 760 | blopes | 247 | |
| 764 | blopes | 248 | if (!c.isCandleVendedor()) { |
| 249 | // tendência vendedora terminou antes do rompimento |
||
| 250 | lastIndex = i - 1; |
||
| 251 | return new ResultadoPadrao(null, lastIndex); |
||
| 252 | } |
||
| 760 | blopes | 253 | |
| 764 | blopes | 254 | lastIndex = i; |
| 760 | blopes | 255 | |
| 764 | blopes | 256 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) { |
| 257 | idxG1 = i; |
||
| 258 | g1 = c; |
||
| 259 | break; |
||
| 260 | } |
||
| 261 | } |
||
| 760 | blopes | 262 | |
| 764 | blopes | 263 | if (idxG1 == -1) { |
| 264 | return new ResultadoPadrao(null, lastIndex); |
||
| 265 | } |
||
| 760 | blopes | 266 | |
| 764 | blopes | 267 | log(String.format("RefC[%d] (GR) => G1 (vendedor) em [%d]", idxRef, idxG1)); |
| 760 | blopes | 268 | |
| 764 | blopes | 269 | // ===================================================== |
| 270 | // A PARTIR DAQUI (G2, G3, G4) NÃO VERIFICAMOS MAIS OUTSIDE |
||
| 271 | // ===================================================== |
||
| 760 | blopes | 272 | |
| 764 | blopes | 273 | // 2) Encontrar G2 (retorno comprador à região do GR) |
| 274 | int idxPrimeiroComprador = -1; |
||
| 275 | for (int i = idxG1 + 1; i < n; i++) { |
||
| 276 | Candle c = candles.get(i); |
||
| 760 | blopes | 277 | |
| 764 | blopes | 278 | if (!c.isCandleComprador() && !c.isCandleVendedor()) { |
| 279 | continue; |
||
| 280 | } |
||
| 281 | if (c.isCandleComprador()) { |
||
| 282 | idxPrimeiroComprador = i; |
||
| 283 | break; |
||
| 284 | } else { |
||
| 285 | lastIndex = i; |
||
| 286 | } |
||
| 287 | } |
||
| 760 | blopes | 288 | |
| 764 | blopes | 289 | if (idxPrimeiroComprador == -1) { |
| 290 | return new ResultadoPadrao(null, lastIndex); |
||
| 291 | } |
||
| 760 | blopes | 292 | |
| 764 | blopes | 293 | boolean rompeuTopoRef = false; |
| 294 | Candle ultimoCompradorTrend = null; |
||
| 295 | int idxUltimoCompradorTrend = -1; |
||
| 760 | blopes | 296 | |
| 764 | blopes | 297 | for (int i = idxPrimeiroComprador; i < n; i++) { |
| 298 | Candle c = candles.get(i); |
||
| 760 | blopes | 299 | |
| 764 | blopes | 300 | if (!c.isCandleComprador()) { |
| 301 | break; |
||
| 302 | } |
||
| 760 | blopes | 303 | |
| 764 | blopes | 304 | ultimoCompradorTrend = c; |
| 305 | idxUltimoCompradorTrend = i; |
||
| 306 | lastIndex = i; |
||
| 760 | blopes | 307 | |
| 764 | blopes | 308 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) { |
| 309 | rompeuTopoRef = true; |
||
| 310 | break; |
||
| 311 | } |
||
| 312 | } |
||
| 760 | blopes | 313 | |
| 764 | blopes | 314 | if (rompeuTopoRef || idxUltimoCompradorTrend == -1) { |
| 315 | return new ResultadoPadrao(null, lastIndex); |
||
| 316 | } |
||
| 760 | blopes | 317 | |
| 764 | blopes | 318 | boolean fechamentoDentroRegiaoRef = |
| 319 | BigDecimalUtils.ehMaiorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMinima()) && |
||
| 320 | BigDecimalUtils.ehMenorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMaxima()); |
||
| 760 | blopes | 321 | |
| 764 | blopes | 322 | if (!fechamentoDentroRegiaoRef) { |
| 323 | return new ResultadoPadrao(null, lastIndex); |
||
| 324 | } |
||
| 760 | blopes | 325 | |
| 764 | blopes | 326 | Candle g2 = ultimoCompradorTrend; |
| 327 | int idxG2 = idxUltimoCompradorTrend; |
||
| 328 | log(String.format("RefC[%d], G1[%d] => G2 (comprador) em [%d]", idxRef, idxG1, idxG2)); |
||
| 760 | blopes | 329 | |
| 764 | blopes | 330 | // 3) Encontrar G3 (vendedor rompendo fundo de G2, topo <= topo do GR) |
| 331 | int idxPrimeiroVendedorAposG2 = -1; |
||
| 332 | for (int i = idxG2 + 1; i < n; i++) { |
||
| 333 | Candle c = candles.get(i); |
||
| 760 | blopes | 334 | |
| 764 | blopes | 335 | if (!c.isCandleComprador() && !c.isCandleVendedor()) { |
| 336 | continue; |
||
| 337 | } |
||
| 760 | blopes | 338 | |
| 764 | blopes | 339 | if (c.isCandleVendedor()) { |
| 340 | idxPrimeiroVendedorAposG2 = i; |
||
| 341 | break; |
||
| 342 | } else { |
||
| 343 | lastIndex = i; |
||
| 344 | // pela sua regra, o próximo após G2 deveria ser vendedor |
||
| 345 | // => padrão chegou até G2, mas não evoluiu corretamente para G3 |
||
| 346 | return criarResultadoParcialComG2(ref, g1, g2, lastIndex); |
||
| 347 | } |
||
| 348 | } |
||
| 760 | blopes | 349 | |
| 764 | blopes | 350 | if (idxPrimeiroVendedorAposG2 == -1) { |
| 351 | // padrão com GR, G1, G2, mas sem sequência adequada para G3 |
||
| 352 | return criarResultadoParcialComG2(ref, g1, g2, lastIndex); |
||
| 353 | } |
||
| 760 | blopes | 354 | |
| 764 | blopes | 355 | int idxG3 = -1; |
| 356 | Candle g3 = null; |
||
| 760 | blopes | 357 | |
| 764 | blopes | 358 | for (int i = idxPrimeiroVendedorAposG2; i < n; i++) { |
| 359 | Candle c = candles.get(i); |
||
| 360 | if (!c.isCandleVendedor()) { |
||
| 361 | lastIndex = i - 1; |
||
| 362 | break; |
||
| 363 | } |
||
| 760 | blopes | 364 | |
| 764 | blopes | 365 | lastIndex = i; |
| 760 | blopes | 366 | |
| 764 | blopes | 367 | boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima()); |
| 368 | boolean topoMenorOuIgualRef = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), ref.getMaxima()); |
||
| 760 | blopes | 369 | |
| 764 | blopes | 370 | if (rompeFundoG2 && topoMenorOuIgualRef) { |
| 371 | idxG3 = i; |
||
| 372 | g3 = c; |
||
| 373 | break; |
||
| 374 | } |
||
| 375 | } |
||
| 760 | blopes | 376 | |
| 764 | blopes | 377 | if (idxG3 == -1 || g3 == null) { |
| 378 | // Chegou até G2 mas não encontrou G3 |
||
| 379 | return criarResultadoParcialComG2(ref, g1, g2, lastIndex); |
||
| 380 | } |
||
| 760 | blopes | 381 | |
| 764 | blopes | 382 | log(String.format("RefC[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d]", |
| 383 | idxRef, idxG1, idxG2, idxG3)); |
||
| 760 | blopes | 384 | |
| 764 | blopes | 385 | // 4) G4 – próximo candle rompe topo do GR |
| 386 | Candle g4 = null; |
||
| 387 | int proxIdx = idxG3 + 1; |
||
| 388 | if (proxIdx < n) { |
||
| 389 | Candle c = candles.get(proxIdx); |
||
| 390 | lastIndex = proxIdx; |
||
| 760 | blopes | 391 | |
| 764 | blopes | 392 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) { |
| 393 | g4 = c; |
||
| 394 | log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]", |
||
| 395 | idxRef, idxG1, idxG2, idxG3, proxIdx)); |
||
| 396 | } else { |
||
| 397 | log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.", |
||
| 398 | idxRef, idxG1, idxG2, idxG3, proxIdx)); |
||
| 399 | } |
||
| 400 | } |
||
| 760 | blopes | 401 | |
| 764 | blopes | 402 | PadraoGatilho padrao = new PadraoGatilho(); |
| 403 | padrao.setReferencia(ref); |
||
| 404 | padrao.setGatilho1(g1); |
||
| 405 | padrao.setGatilho2(g2); |
||
| 406 | padrao.setGatilho3(g3); |
||
| 407 | padrao.setGatilho4(g4); |
||
| 760 | blopes | 408 | |
| 764 | blopes | 409 | return new ResultadoPadrao(padrao, lastIndex); |
| 410 | } |
||
| 760 | blopes | 411 | |
| 764 | blopes | 412 | // ===================================================================== |
| 413 | // CASO 2 – GR VENDEDOR (candle B) |
||
| 414 | // ===================================================================== |
||
| 760 | blopes | 415 | |
| 764 | blopes | 416 | private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxRef) { |
| 417 | int n = candles.size(); |
||
| 418 | Candle ref = candles.get(idxRef); // candidato a GR vendedor |
||
| 419 | int lastIndex = idxRef; |
||
| 760 | blopes | 420 | |
| 764 | blopes | 421 | // 0) Validar se idxRef pode ser GR vendedor (B) |
| 422 | if (!ref.isCandleVendedor()) { |
||
| 423 | return new ResultadoPadrao(null, idxRef); |
||
| 424 | } |
||
| 760 | blopes | 425 | |
| 764 | blopes | 426 | // 0.1 – início da pernada vendedora que termina em B |
| 427 | int inicioTrendVenda = idxRef; |
||
| 428 | for (int i = idxRef - 1; i >= 0; i--) { |
||
| 429 | Candle c = candles.get(i); |
||
| 430 | if (!c.isCandleVendedor()) { |
||
| 431 | break; |
||
| 432 | } |
||
| 433 | inicioTrendVenda = i; |
||
| 434 | } |
||
| 760 | blopes | 435 | |
| 764 | blopes | 436 | // 0.2 – primeiro candle direcional após B |
| 437 | int idxPrimeiroDirecionalAposRef = -1; |
||
| 438 | for (int i = idxRef + 1; i < n; i++) { |
||
| 439 | Candle c = candles.get(i); |
||
| 440 | if (!c.isCandleComprador() && !c.isCandleVendedor()) { |
||
| 441 | continue; |
||
| 442 | } |
||
| 760 | blopes | 443 | |
| 764 | blopes | 444 | // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1) |
| 445 | if (isOutsideReferencia(ref, c)) { |
||
| 446 | lastIndex = i; |
||
| 447 | log(String.format( |
||
| 448 | "RefV[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.", |
||
| 449 | idxRef, i)); |
||
| 450 | return new ResultadoPadrao(null, lastIndex); |
||
| 451 | } |
||
| 760 | blopes | 452 | |
| 764 | blopes | 453 | idxPrimeiroDirecionalAposRef = i; |
| 454 | break; |
||
| 455 | } |
||
| 760 | blopes | 456 | |
| 764 | blopes | 457 | if (idxPrimeiroDirecionalAposRef == -1) { |
| 458 | return new ResultadoPadrao(null, lastIndex); |
||
| 459 | } |
||
| 760 | blopes | 460 | |
| 764 | blopes | 461 | Candle primeiroDirecional = candles.get(idxPrimeiroDirecionalAposRef); |
| 462 | if (!primeiroDirecional.isCandleComprador()) { |
||
| 463 | // primeiro direcional após B ainda é vendedor |
||
| 464 | return new ResultadoPadrao(null, idxRef); |
||
| 465 | } |
||
| 760 | blopes | 466 | |
| 764 | blopes | 467 | // 0.3 – B tem o MENOR fundo da pernada vendedora |
| 468 | BigDecimal fundoRef = ref.getMinima(); |
||
| 469 | for (int i = inicioTrendVenda; i <= idxRef; i++) { |
||
| 470 | Candle c = candles.get(i); |
||
| 471 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), fundoRef)) { |
||
| 472 | return new ResultadoPadrao(null, idxRef); |
||
| 473 | } |
||
| 474 | } |
||
| 760 | blopes | 475 | |
| 764 | blopes | 476 | int idxPrimeiroComprador = idxPrimeiroDirecionalAposRef; |
| 760 | blopes | 477 | |
| 764 | blopes | 478 | // 1) Encontrar G1 (comprador que rompe topo do GR) |
| 479 | int idxG1 = -1; |
||
| 480 | Candle g1 = null; |
||
| 760 | blopes | 481 | |
| 764 | blopes | 482 | for (int i = idxPrimeiroComprador; i < n; i++) { |
| 483 | Candle c = candles.get(i); |
||
| 760 | blopes | 484 | |
| 764 | blopes | 485 | // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1) |
| 486 | if (isOutsideReferencia(ref, c)) { |
||
| 487 | lastIndex = i; |
||
| 488 | log(String.format( |
||
| 489 | "RefV[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.", |
||
| 490 | idxRef, i)); |
||
| 491 | return new ResultadoPadrao(null, lastIndex); |
||
| 492 | } |
||
| 760 | blopes | 493 | |
| 764 | blopes | 494 | if (!c.isCandleComprador()) { |
| 495 | lastIndex = i - 1; |
||
| 496 | return new ResultadoPadrao(null, lastIndex); |
||
| 497 | } |
||
| 760 | blopes | 498 | |
| 764 | blopes | 499 | lastIndex = i; |
| 760 | blopes | 500 | |
| 764 | blopes | 501 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) { |
| 502 | idxG1 = i; |
||
| 503 | g1 = c; |
||
| 504 | break; |
||
| 505 | } |
||
| 506 | } |
||
| 760 | blopes | 507 | |
| 764 | blopes | 508 | if (idxG1 == -1) { |
| 509 | return new ResultadoPadrao(null, lastIndex); |
||
| 510 | } |
||
| 760 | blopes | 511 | |
| 764 | blopes | 512 | log(String.format("RefV[%d] (GR) => G1 (comprador) em [%d]", idxRef, idxG1)); |
| 760 | blopes | 513 | |
| 764 | blopes | 514 | // ===================================================== |
| 515 | // A PARTIR DAQUI (G2, G3, G4) NÃO VERIFICAMOS MAIS OUTSIDE |
||
| 516 | // ===================================================== |
||
| 760 | blopes | 517 | |
| 764 | blopes | 518 | // 2) Encontrar G2 (retorno vendedor à região do GR) |
| 519 | int idxPrimeiroVendedor = -1; |
||
| 520 | for (int i = idxG1 + 1; i < n; i++) { |
||
| 521 | Candle c = candles.get(i); |
||
| 760 | blopes | 522 | |
| 764 | blopes | 523 | if (!c.isCandleComprador() && !c.isCandleVendedor()) { |
| 524 | continue; |
||
| 525 | } |
||
| 760 | blopes | 526 | |
| 764 | blopes | 527 | if (c.isCandleVendedor()) { |
| 528 | idxPrimeiroVendedor = i; |
||
| 529 | break; |
||
| 530 | } else { |
||
| 531 | lastIndex = i; |
||
| 532 | } |
||
| 533 | } |
||
| 760 | blopes | 534 | |
| 764 | blopes | 535 | if (idxPrimeiroVendedor == -1) { |
| 536 | return new ResultadoPadrao(null, lastIndex); |
||
| 537 | } |
||
| 760 | blopes | 538 | |
| 764 | blopes | 539 | boolean rompeuFundoRef = false; |
| 540 | Candle ultimoVendedorTrend = null; |
||
| 541 | int idxUltimoVendedorTrend = -1; |
||
| 760 | blopes | 542 | |
| 764 | blopes | 543 | for (int i = idxPrimeiroVendedor; i < n; i++) { |
| 544 | Candle c = candles.get(i); |
||
| 760 | blopes | 545 | |
| 764 | blopes | 546 | if (!c.isCandleVendedor()) { |
| 547 | break; |
||
| 548 | } |
||
| 760 | blopes | 549 | |
| 764 | blopes | 550 | ultimoVendedorTrend = c; |
| 551 | idxUltimoVendedorTrend = i; |
||
| 552 | lastIndex = i; |
||
| 760 | blopes | 553 | |
| 764 | blopes | 554 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) { |
| 555 | rompeuFundoRef = true; |
||
| 556 | break; |
||
| 557 | } |
||
| 558 | } |
||
| 760 | blopes | 559 | |
| 764 | blopes | 560 | if (rompeuFundoRef || idxUltimoVendedorTrend == -1) { |
| 561 | return new ResultadoPadrao(null, lastIndex); |
||
| 562 | } |
||
| 760 | blopes | 563 | |
| 764 | blopes | 564 | boolean fechamentoDentroRegiaoRef = |
| 565 | BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMinima()) && |
||
| 566 | BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMaxima()); |
||
| 760 | blopes | 567 | |
| 764 | blopes | 568 | if (!fechamentoDentroRegiaoRef) { |
| 569 | return new ResultadoPadrao(null, lastIndex); |
||
| 570 | } |
||
| 760 | blopes | 571 | |
| 764 | blopes | 572 | Candle g2 = ultimoVendedorTrend; |
| 573 | int idxG2 = idxUltimoVendedorTrend; |
||
| 574 | log(String.format("RefV[%d], G1[%d] => G2 (vendedor) em [%d]", idxRef, idxG1, idxG2)); |
||
| 760 | blopes | 575 | |
| 764 | blopes | 576 | // 3) Encontrar G3 (comprador rompendo topo de G2, fundo >= fundo do GR) |
| 577 | int idxPrimeiroCompradorAposG2 = -1; |
||
| 578 | for (int i = idxG2 + 1; i < n; i++) { |
||
| 579 | Candle c = candles.get(i); |
||
| 760 | blopes | 580 | |
| 764 | blopes | 581 | if (!c.isCandleComprador() && !c.isCandleVendedor()) { |
| 582 | continue; |
||
| 583 | } |
||
| 760 | blopes | 584 | |
| 764 | blopes | 585 | if (c.isCandleComprador()) { |
| 586 | idxPrimeiroCompradorAposG2 = i; |
||
| 587 | break; |
||
| 588 | } else { |
||
| 589 | lastIndex = i; |
||
| 590 | // esperado comprador; padrão segue apenas até G2 |
||
| 591 | return criarResultadoParcialComG2(ref, g1, g2, lastIndex); |
||
| 592 | } |
||
| 593 | } |
||
| 760 | blopes | 594 | |
| 764 | blopes | 595 | if (idxPrimeiroCompradorAposG2 == -1) { |
| 596 | // não teve sequência adequada após G2 |
||
| 597 | return criarResultadoParcialComG2(ref, g1, g2, lastIndex); |
||
| 598 | } |
||
| 760 | blopes | 599 | |
| 764 | blopes | 600 | int idxG3 = -1; |
| 601 | Candle g3 = null; |
||
| 760 | blopes | 602 | |
| 764 | blopes | 603 | for (int i = idxPrimeiroCompradorAposG2; i < n; i++) { |
| 604 | Candle c = candles.get(i); |
||
| 605 | if (!c.isCandleComprador()) { |
||
| 606 | lastIndex = i - 1; |
||
| 607 | break; |
||
| 608 | } |
||
| 760 | blopes | 609 | |
| 764 | blopes | 610 | lastIndex = i; |
| 760 | blopes | 611 | |
| 764 | blopes | 612 | boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima()); |
| 613 | boolean fundoMaiorOuIgualRef = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), ref.getMinima()); |
||
| 760 | blopes | 614 | |
| 764 | blopes | 615 | if (rompeTopoG2 && fundoMaiorOuIgualRef) { |
| 616 | idxG3 = i; |
||
| 617 | g3 = c; |
||
| 618 | break; |
||
| 619 | } |
||
| 620 | } |
||
| 760 | blopes | 621 | |
| 764 | blopes | 622 | if (idxG3 == -1 || g3 == null) { |
| 623 | // padrão só até G2 |
||
| 624 | return criarResultadoParcialComG2(ref, g1, g2, lastIndex); |
||
| 625 | } |
||
| 760 | blopes | 626 | |
| 764 | blopes | 627 | log(String.format("RefV[%d], G1[%d], G2[%d] => G3 (comprador) em [%d]", |
| 628 | idxRef, idxG1, idxG2, idxG3)); |
||
| 760 | blopes | 629 | |
| 764 | blopes | 630 | // 4) G4 – próximo candle rompe fundo do GR |
| 631 | Candle g4 = null; |
||
| 632 | int proxIdx = idxG3 + 1; |
||
| 633 | if (proxIdx < n) { |
||
| 634 | Candle c = candles.get(proxIdx); |
||
| 635 | lastIndex = proxIdx; |
||
| 760 | blopes | 636 | |
| 764 | blopes | 637 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) { |
| 638 | g4 = c; |
||
| 639 | log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]", |
||
| 640 | idxRef, idxG1, idxG2, idxG3, proxIdx)); |
||
| 641 | } else { |
||
| 642 | log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.", |
||
| 643 | idxRef, idxG1, idxG2, idxG3, proxIdx)); |
||
| 644 | } |
||
| 645 | } |
||
| 760 | blopes | 646 | |
| 764 | blopes | 647 | PadraoGatilho padrao = new PadraoGatilho(); |
| 648 | padrao.setReferencia(ref); |
||
| 649 | padrao.setGatilho1(g1); |
||
| 650 | padrao.setGatilho2(g2); |
||
| 651 | padrao.setGatilho3(g3); |
||
| 652 | padrao.setGatilho4(g4); |
||
| 760 | blopes | 653 | |
| 764 | blopes | 654 | return new ResultadoPadrao(padrao, lastIndex); |
| 655 | } |
||
| 760 | blopes | 656 | |
| 764 | blopes | 657 | // ===================================================================== |
| 658 | // HELPER – Candle outside em relação ao GR |
||
| 659 | // ===================================================================== |
||
| 760 | blopes | 660 | |
| 764 | blopes | 661 | /** |
| 662 | * Outside em relação ao GR: |
||
| 663 | * - atual.max > GR.max e atual.min < GR.min |
||
| 664 | */ |
||
| 665 | private boolean isOutsideReferencia(Candle referencia, Candle atual) { |
||
| 666 | if (referencia == null || atual == null) return false; |
||
| 667 | return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), referencia.getMaxima()) |
||
| 668 | && BigDecimalUtils.ehMenorQue(atual.getMinima(), referencia.getMinima()); |
||
| 669 | } |
||
| 760 | blopes | 670 | |
| 764 | blopes | 671 | // ===================================================================== |
| 672 | // MODO TEMPO REAL (candle a candle) |
||
| 673 | // ===================================================================== |
||
| 760 | blopes | 674 | |
| 764 | blopes | 675 | /** |
| 676 | * Reinicia o estado do modo tempo real. |
||
| 677 | */ |
||
| 678 | public void resetTempoReal() { |
||
| 679 | this.idxProximaAnaliseTempoReal = 0; |
||
| 680 | } |
||
| 760 | blopes | 681 | |
| 764 | blopes | 682 | /** |
| 683 | * Deve ser chamado SEMPRE que um novo candle for adicionado à lista. |
||
| 684 | * |
||
| 685 | * Exemplo: |
||
| 686 | * candles.add(novoCandle); |
||
| 687 | * PadraoGatilho padrao = detector.processarCandleTempoReal(candles); |
||
| 688 | * |
||
| 689 | * if (padrao != null) { |
||
| 690 | * // padrão completo encontrado |
||
| 691 | * } |
||
| 692 | */ |
||
| 693 | public PadraoGatilho processarCandleTempoReal(List<Candle> candles) { |
||
| 694 | int n = candles.size(); |
||
| 695 | if (n < 4) { |
||
| 696 | return null; |
||
| 697 | } |
||
| 760 | blopes | 698 | |
| 764 | blopes | 699 | while (idxProximaAnaliseTempoReal < n - 3) { |
| 700 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal); |
||
| 760 | blopes | 701 | |
| 764 | blopes | 702 | if (resultado == null) { |
| 703 | idxProximaAnaliseTempoReal++; |
||
| 704 | continue; |
||
| 705 | } |
||
| 760 | blopes | 706 | |
| 764 | blopes | 707 | int proximoInicio = resultado.lastIndex + 1; |
| 708 | if (proximoInicio <= idxProximaAnaliseTempoReal) { |
||
| 709 | proximoInicio = idxProximaAnaliseTempoReal + 1; |
||
| 710 | } |
||
| 711 | idxProximaAnaliseTempoReal = proximoInicio; |
||
| 760 | blopes | 712 | |
| 764 | blopes | 713 | if (resultado.padrao != null) { |
| 714 | return resultado.padrao; |
||
| 715 | } |
||
| 716 | } |
||
| 760 | blopes | 717 | |
| 764 | blopes | 718 | return null; |
| 719 | } |
||
| 760 | blopes | 720 | |
| 764 | blopes | 721 | } |