Rev 767 | Rev 773 | 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 | /** |
| 764 | blopes | 10 | * Detector de padrões de gatilhos (GR, G1, G2, G3, G4) |
| 11 | * trabalhando tanto em modo backtest quanto em modo tempo real. |
||
| 760 | blopes | 12 | * |
| 771 | blopes | 13 | * Regras implementadas (texto alinhado): |
| 760 | blopes | 14 | * |
| 771 | blopes | 15 | * COMPRADOR |
| 16 | * --------- |
||
| 17 | * 1) Candle A comprador, candidato à referência. |
||
| 18 | * - Após A deve haver mudança de tendência (B). |
||
| 19 | * - A deve ser o último da tendência compradora: |
||
| 20 | * o primeiro candle direcional após A (ignorando inside/neutros) |
||
| 21 | * deve ser vendedor (B). Se for comprador, descarta A. |
||
| 760 | blopes | 22 | * |
| 771 | blopes | 23 | * 2) Ajuste da referência com B: |
| 24 | * - Se B (vendedor) tiver TOPO maior que o topo de A, |
||
| 25 | * B passa a ser o candidato à referência. |
||
| 26 | * Senão, A permanece como candidato. |
||
| 760 | blopes | 27 | * |
| 771 | blopes | 28 | * 3) G1: |
| 29 | * - A partir do candidato à referência, o primeiro candle vendedor |
||
| 30 | * subsequente que romper o FUNDO do candidato (mínima < mínima do candidato) |
||
| 31 | * será o G1. O candidato torna-se o GR. |
||
| 760 | blopes | 32 | * |
| 771 | blopes | 33 | * 4) G2: |
| 34 | * - Após o G1, assim que houver nova mudança de tendência para compradora: |
||
| 35 | * * encontrar o primeiro comprador após G1 (ignorando inside/neutros); |
||
| 36 | * * seguir a sequência compradora até aparecer vendedor. |
||
| 37 | * * durante essa sequência: |
||
| 38 | * - se houver OUTSIDE (atual rompe topo e fundo do anterior) => descarta. |
||
| 39 | * - se ALGUM candle romper o TOPO do GR (máxima > máxima GR) => descarta. |
||
| 40 | * * o ÚLTIMO candle dessa tendência compradora deve ter o fechamento |
||
| 41 | * dentro da região total do GR (mínimaGR <= fechamento <= máximaGR). |
||
| 42 | * * se virar vendedor sem atingir a região do GR => descarta. |
||
| 43 | * * caso válido, esse último comprador é o G2. |
||
| 760 | blopes | 44 | * |
| 771 | blopes | 45 | * 5) G3: |
| 46 | * - Após o G2, o PRÓXIMO candle direcional deve ser vendedor. |
||
| 47 | * se for comprador => padrão apenas até G2 (parcial). |
||
| 48 | * - numa sequência vendedora: |
||
| 49 | * * se houver OUTSIDE => descarta. |
||
| 50 | * * se algum candle vendedor romper o fundo de G2 (mínima < mínima G2) |
||
| 51 | * e tiver topo <= topo do GR => G3. |
||
| 52 | * * se surgir candle que rompa o TOPO do GR (máxima > máximaGR) ANTES do G3, |
||
| 53 | * o padrão é descartado. |
||
| 764 | blopes | 54 | * |
| 771 | blopes | 55 | * 6) G4: |
| 56 | * - Após o G3, se o PRÓXIMO candle romper o topo do GR (máxima > máximaGR), |
||
| 57 | * será o G4. Depois disso, o padrão é encerrado e um novo padrão é buscado. |
||
| 764 | blopes | 58 | * |
| 771 | blopes | 59 | * VENDEDOR |
| 60 | * -------- |
||
| 61 | * 1) Candle A vendedor, candidato à referência. |
||
| 62 | * - Após A deve haver mudança de tendência (B). |
||
| 63 | * - A deve ser o último da tendência vendedora: |
||
| 64 | * o primeiro candle direcional após A (ignorando inside/neutros) |
||
| 65 | * deve ser comprador (B). Se for vendedor, descarta A. |
||
| 764 | blopes | 66 | * |
| 771 | blopes | 67 | * 2) Ajuste da referência com B: |
| 68 | * - Se B (comprador) tiver FUNDO mais baixo (mínima menor) que o fundo de A, |
||
| 69 | * B passa a ser candidato à referência. |
||
| 70 | * Senão, A permanece como candidato. |
||
| 764 | blopes | 71 | * |
| 771 | blopes | 72 | * 3) G1: |
| 73 | * - A partir do candidato à referência, o primeiro candle comprador |
||
| 74 | * subsequente que romper o TOPO do candidato (máxima > máxima do candidato) |
||
| 75 | * será o G1. O candidato torna-se o GR. |
||
| 764 | blopes | 76 | * |
| 771 | blopes | 77 | * 4) G2: |
| 78 | * - Após o G1, nova mudança para tendência vendedora: |
||
| 79 | * * encontrar o primeiro vendedor após G1 (ignorando inside/neutros); |
||
| 80 | * * seguir a sequência vendedora até aparecer comprador. |
||
| 81 | * * durante essa sequência: |
||
| 82 | * - se houver OUTSIDE => descarta. |
||
| 83 | * - se algum candle romper o FUNDO do GR (mínima < mínimaGR) => descarta. |
||
| 84 | * * o ÚLTIMO candle dessa tendência vendedora deve ter o fechamento |
||
| 85 | * dentro da região total do GR (mínimaGR <= fechamento <= máximaGR). |
||
| 86 | * * se virar comprador sem atingir a região do GR => descarta. |
||
| 87 | * * caso válido, esse último vendedor é o G2. |
||
| 88 | * |
||
| 89 | * 5) G3: |
||
| 90 | * - Após o G2, o PRÓXIMO candle direcional deve ser comprador. |
||
| 91 | * se for vendedor => padrão apenas até G2 (parcial). |
||
| 92 | * - numa sequência compradora: |
||
| 93 | * * se houver OUTSIDE => descarta. |
||
| 94 | * * se algum candle comprador romper o topo de G2 (máxima > máxima G2) |
||
| 95 | * e tiver fundo >= fundo do GR => G3. |
||
| 96 | * * se algum candle romper o FUNDO do GR (mínima < mínimaGR) |
||
| 97 | * ANTES do G3 => descarta. |
||
| 98 | * |
||
| 99 | * 6) G4: |
||
| 100 | * - Após o G3, se o PRÓXIMO candle romper o fundo do GR (mínima < mínimaGR), |
||
| 101 | * será o G4. Depois disso, o padrão é encerrado e um novo padrão é buscado. |
||
| 102 | * |
||
| 103 | * OUTSIDE (ambos os lados): |
||
| 104 | * - Candle cuja máxima > máxima do candle anterior E mínima < mínima do candle anterior. |
||
| 105 | * - Se houver um outside antes de identificar o G3, o padrão é descartado. |
||
| 760 | blopes | 106 | */ |
| 107 | public class DetectorGatilhos { |
||
| 108 | |||
| 764 | blopes | 109 | private final boolean logAtivo; |
| 110 | private int idxProximaAnaliseTempoReal = 0; |
||
| 760 | blopes | 111 | |
| 764 | blopes | 112 | public DetectorGatilhos() { |
| 113 | this(false); |
||
| 114 | } |
||
| 760 | blopes | 115 | |
| 764 | blopes | 116 | public DetectorGatilhos(boolean logAtivo) { |
| 117 | this.logAtivo = logAtivo; |
||
| 118 | } |
||
| 760 | blopes | 119 | |
| 764 | blopes | 120 | private void log(String msg) { |
| 121 | if (logAtivo) { |
||
| 122 | System.out.println(msg); |
||
| 123 | } |
||
| 124 | } |
||
| 760 | blopes | 125 | |
| 764 | blopes | 126 | private static class ResultadoPadrao { |
| 771 | blopes | 127 | PadraoGatilho padrao; |
| 128 | int lastIndex; |
||
| 764 | blopes | 129 | ResultadoPadrao(PadraoGatilho padrao, int lastIndex) { |
| 130 | this.padrao = padrao; |
||
| 131 | this.lastIndex = lastIndex; |
||
| 132 | } |
||
| 133 | } |
||
| 760 | blopes | 134 | |
| 764 | blopes | 135 | private ResultadoPadrao criarResultadoParcialComG2(Candle ref, |
| 136 | Candle g1, |
||
| 137 | Candle g2, |
||
| 138 | int lastIndex) { |
||
| 139 | PadraoGatilho padrao = new PadraoGatilho(); |
||
| 140 | padrao.setReferencia(ref); |
||
| 141 | padrao.setGatilho1(g1); |
||
| 142 | padrao.setGatilho2(g2); |
||
| 143 | padrao.setGatilho3(null); |
||
| 144 | padrao.setGatilho4(null); |
||
| 145 | return new ResultadoPadrao(padrao, lastIndex); |
||
| 146 | } |
||
| 760 | blopes | 147 | |
| 764 | blopes | 148 | // ===================================================================== |
| 771 | blopes | 149 | // API PRINCIPAL – BACKTEST |
| 764 | blopes | 150 | // ===================================================================== |
| 760 | blopes | 151 | |
| 764 | blopes | 152 | public List<PadraoGatilho> identificarPadroes(List<Candle> candles) { |
| 153 | List<PadraoGatilho> padroes = new ArrayList<>(); |
||
| 154 | int n = candles.size(); |
||
| 771 | blopes | 155 | if (n < 4) return padroes; |
| 760 | blopes | 156 | |
| 764 | blopes | 157 | int idxRef = 0; |
| 158 | while (idxRef < n - 3) { |
||
| 159 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef); |
||
| 760 | blopes | 160 | |
| 764 | blopes | 161 | if (resultado == null) { |
| 162 | idxRef++; |
||
| 163 | continue; |
||
| 164 | } |
||
| 760 | blopes | 165 | |
| 764 | blopes | 166 | if (resultado.padrao != null) { |
| 167 | padroes.add(resultado.padrao); |
||
| 168 | } |
||
| 760 | blopes | 169 | |
| 764 | blopes | 170 | idxRef = Math.max(resultado.lastIndex + 1, idxRef + 1); |
| 171 | } |
||
| 760 | blopes | 172 | |
| 764 | blopes | 173 | return padroes; |
| 174 | } |
||
| 760 | blopes | 175 | |
| 764 | blopes | 176 | private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) { |
| 177 | Candle ref = candles.get(idxRef); |
||
| 178 | if (ref.isCandleComprador()) { |
||
| 179 | return detectarPadraoComprador(candles, idxRef); |
||
| 180 | } else if (ref.isCandleVendedor()) { |
||
| 181 | return detectarPadraoVendedor(candles, idxRef); |
||
| 182 | } else { |
||
| 183 | return new ResultadoPadrao(null, idxRef); |
||
| 184 | } |
||
| 185 | } |
||
| 760 | blopes | 186 | |
| 764 | blopes | 187 | // ===================================================================== |
| 771 | blopes | 188 | // HELPERS |
| 764 | blopes | 189 | // ===================================================================== |
| 760 | blopes | 190 | |
| 771 | blopes | 191 | private boolean isInside(List<Candle> candles, int idx) { |
| 192 | if (idx <= 0) return false; |
||
| 193 | Candle atual = candles.get(idx); |
||
| 194 | Candle anterior = candles.get(idx - 1); |
||
| 195 | return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima()) |
||
| 196 | && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima()); |
||
| 197 | } |
||
| 760 | blopes | 198 | |
| 771 | blopes | 199 | private boolean isOutside(List<Candle> candles, int idx) { |
| 200 | if (idx <= 0) return false; |
||
| 201 | Candle atual = candles.get(idx); |
||
| 202 | Candle anterior = candles.get(idx - 1); |
||
| 203 | return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), anterior.getMaxima()) |
||
| 204 | && BigDecimalUtils.ehMenorQue(atual.getMinima(), anterior.getMinima()); |
||
| 205 | } |
||
| 760 | blopes | 206 | |
| 771 | blopes | 207 | // ===================================================================== |
| 208 | // CASO 1 – PADRÃO A PARTIR DE CANDLE COMPRADOR |
||
| 209 | // ===================================================================== |
||
| 210 | |||
| 211 | private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxA) { |
||
| 212 | int n = candles.size(); |
||
| 213 | int lastIndex = idxA; |
||
| 214 | Candle candleA = candles.get(idxA); |
||
| 215 | if (!candleA.isCandleComprador()) { |
||
| 216 | return new ResultadoPadrao(null, idxA); |
||
| 764 | blopes | 217 | } |
| 760 | blopes | 218 | |
| 771 | blopes | 219 | // 1) A deve ser último da tendência compradora -> achar B vendedor |
| 220 | int idxB = -1; |
||
| 221 | for (int i = idxA + 1; i < n; i++) { |
||
| 764 | blopes | 222 | Candle c = candles.get(i); |
| 771 | blopes | 223 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) { |
| 764 | blopes | 224 | continue; |
| 225 | } |
||
| 771 | blopes | 226 | if (c.isCandleComprador()) { |
| 227 | // A não é o último comprador |
||
| 228 | return new ResultadoPadrao(null, idxA); |
||
| 764 | blopes | 229 | } |
| 771 | blopes | 230 | if (c.isCandleVendedor()) { |
| 231 | idxB = i; |
||
| 232 | break; |
||
| 233 | } |
||
| 764 | blopes | 234 | } |
| 771 | blopes | 235 | if (idxB == -1) return new ResultadoPadrao(null, idxA); |
| 760 | blopes | 236 | |
| 771 | blopes | 237 | Candle candleB = candles.get(idxB); |
| 760 | blopes | 238 | |
| 771 | blopes | 239 | // 2) Candidato à referência: A ou B (conforme topo) |
| 240 | Candle candidatoRef = candleA; |
||
| 241 | int idxCandidatoRef = idxA; |
||
| 242 | if (BigDecimalUtils.ehMaiorQue(candleB.getMaxima(), candleA.getMaxima())) { |
||
| 243 | candidatoRef = candleB; |
||
| 244 | idxCandidatoRef = idxB; |
||
| 764 | blopes | 245 | } |
| 760 | blopes | 246 | |
| 771 | blopes | 247 | // 3) G1 – primeiro vendedor que rompe fundo do candidatoRef |
| 248 | Candle g1 = null; |
||
| 764 | blopes | 249 | int idxG1 = -1; |
| 771 | blopes | 250 | for (int i = idxCandidatoRef + 1; i < n; i++) { |
| 764 | blopes | 251 | Candle c = candles.get(i); |
| 771 | blopes | 252 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) { |
| 253 | continue; |
||
| 764 | blopes | 254 | } |
| 760 | blopes | 255 | |
| 764 | blopes | 256 | if (!c.isCandleVendedor()) { |
| 257 | lastIndex = i - 1; |
||
| 258 | return new ResultadoPadrao(null, lastIndex); |
||
| 259 | } |
||
| 760 | blopes | 260 | |
| 764 | blopes | 261 | lastIndex = i; |
| 771 | blopes | 262 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
| 263 | g1 = c; |
||
| 764 | blopes | 264 | idxG1 = i; |
| 265 | break; |
||
| 266 | } |
||
| 267 | } |
||
| 760 | blopes | 268 | |
| 771 | blopes | 269 | if (g1 == null) return new ResultadoPadrao(null, lastIndex); |
| 760 | blopes | 270 | |
| 771 | blopes | 271 | Candle gr = candidatoRef; |
| 272 | int idxGR = idxCandidatoRef; |
||
| 273 | log(String.format("GR comprador em [%d], G1 (vendedor) em [%d]", idxGR, idxG1)); |
||
| 760 | blopes | 274 | |
| 771 | blopes | 275 | // 4) G2 – nova tendência compradora com fechamento dentro da região do GR |
| 276 | Candle g2 = null; |
||
| 277 | int idxG2 = -1; |
||
| 278 | |||
| 764 | blopes | 279 | int idxPrimeiroComprador = -1; |
| 280 | for (int i = idxG1 + 1; i < n; i++) { |
||
| 281 | Candle c = candles.get(i); |
||
| 771 | blopes | 282 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 283 | continue; |
||
| 760 | blopes | 284 | |
| 764 | blopes | 285 | if (c.isCandleComprador()) { |
| 286 | idxPrimeiroComprador = i; |
||
| 287 | break; |
||
| 288 | } else { |
||
| 771 | blopes | 289 | // ainda vendedor |
| 764 | blopes | 290 | lastIndex = i; |
| 291 | } |
||
| 292 | } |
||
| 760 | blopes | 293 | |
| 771 | blopes | 294 | if (idxPrimeiroComprador == -1) return new ResultadoPadrao(null, lastIndex); |
| 760 | blopes | 295 | |
| 771 | blopes | 296 | Candle ultimoComprador = null; |
| 297 | int idxUltimoComprador = -1; |
||
| 760 | blopes | 298 | |
| 764 | blopes | 299 | for (int i = idxPrimeiroComprador; i < n; i++) { |
| 300 | Candle c = candles.get(i); |
||
| 760 | blopes | 301 | |
| 771 | blopes | 302 | // outside antes de G3 descarta |
| 303 | if (isOutside(candles, i)) { |
||
| 767 | blopes | 304 | lastIndex = i; |
| 771 | blopes | 305 | log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (comprador).", idxGR, i)); |
| 767 | blopes | 306 | return new ResultadoPadrao(null, lastIndex); |
| 307 | } |
||
| 308 | |||
| 771 | blopes | 309 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 310 | continue; |
||
| 760 | blopes | 311 | |
| 771 | blopes | 312 | if (c.isCandleComprador()) { |
| 313 | // se romper topo do GR, descarta |
||
| 314 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
||
| 315 | lastIndex = i; |
||
| 316 | log(String.format("GR[%d]: candle [%d] rompeu topo do GR durante G2.", idxGR, i)); |
||
| 317 | return new ResultadoPadrao(null, lastIndex); |
||
| 318 | } |
||
| 319 | ultimoComprador = c; |
||
| 320 | idxUltimoComprador = i; |
||
| 321 | lastIndex = i; |
||
| 322 | } else if (c.isCandleVendedor()) { |
||
| 323 | // terminou tendência compradora |
||
| 324 | lastIndex = i - 1; |
||
| 764 | blopes | 325 | break; |
| 326 | } |
||
| 327 | } |
||
| 760 | blopes | 328 | |
| 771 | blopes | 329 | if (ultimoComprador == null) return new ResultadoPadrao(null, lastIndex); |
| 760 | blopes | 330 | |
| 771 | blopes | 331 | boolean fechamentoDentroRegiaoGR = |
| 332 | BigDecimalUtils.ehMaiorOuIgualQue(ultimoComprador.getFechamento(), gr.getMinima()) && |
||
| 333 | BigDecimalUtils.ehMenorOuIgualQue(ultimoComprador.getFechamento(), gr.getMaxima()); |
||
| 760 | blopes | 334 | |
| 771 | blopes | 335 | if (!fechamentoDentroRegiaoGR) { |
| 764 | blopes | 336 | return new ResultadoPadrao(null, lastIndex); |
| 337 | } |
||
| 760 | blopes | 338 | |
| 771 | blopes | 339 | g2 = ultimoComprador; |
| 340 | idxG2 = idxUltimoComprador; |
||
| 341 | log(String.format("GR[%d], G1[%d] => G2 (comprador) em [%d]", idxGR, idxG1, idxG2)); |
||
| 760 | blopes | 342 | |
| 771 | blopes | 343 | // 5) G3 – próximo direcional vendedor; vendedor que rompe fundo de G2 com topo <= topo GR |
| 344 | Candle g3 = null; |
||
| 345 | int idxG3 = -1; |
||
| 346 | |||
| 764 | blopes | 347 | int idxPrimeiroVendedorAposG2 = -1; |
| 348 | for (int i = idxG2 + 1; i < n; i++) { |
||
| 349 | Candle c = candles.get(i); |
||
| 771 | blopes | 350 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 764 | blopes | 351 | continue; |
| 760 | blopes | 352 | |
| 764 | blopes | 353 | if (c.isCandleVendedor()) { |
| 354 | idxPrimeiroVendedorAposG2 = i; |
||
| 355 | break; |
||
| 356 | } else { |
||
| 771 | blopes | 357 | // primeiro direcional não é vendedor => padrão só até G2 |
| 764 | blopes | 358 | lastIndex = i; |
| 771 | blopes | 359 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
| 764 | blopes | 360 | } |
| 361 | } |
||
| 771 | blopes | 362 | if (idxPrimeiroVendedorAposG2 == -1) |
| 363 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
||
| 760 | blopes | 364 | |
| 764 | blopes | 365 | for (int i = idxPrimeiroVendedorAposG2; i < n; i++) { |
| 366 | Candle c = candles.get(i); |
||
| 767 | blopes | 367 | |
| 771 | blopes | 368 | // outside antes de G3 descarta |
| 369 | if (isOutside(candles, i)) { |
||
| 767 | blopes | 370 | lastIndex = i; |
| 771 | blopes | 371 | log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (vendedor).", idxGR, i)); |
| 767 | blopes | 372 | return new ResultadoPadrao(null, lastIndex); |
| 373 | } |
||
| 374 | |||
| 771 | blopes | 375 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 376 | continue; |
||
| 377 | |||
| 378 | // se romper topo do GR antes de G3 => descarta |
||
| 379 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
||
| 380 | lastIndex = i; |
||
| 381 | log(String.format("GR[%d]: candle [%d] rompeu topo do GR antes de G3.", idxGR, i)); |
||
| 382 | return new ResultadoPadrao(null, lastIndex); |
||
| 383 | } |
||
| 384 | |||
| 764 | blopes | 385 | if (!c.isCandleVendedor()) { |
| 386 | lastIndex = i - 1; |
||
| 387 | break; |
||
| 388 | } |
||
| 760 | blopes | 389 | |
| 764 | blopes | 390 | lastIndex = i; |
| 391 | boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima()); |
||
| 771 | blopes | 392 | boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima()); |
| 393 | if (rompeFundoG2 && topoMenorOuIgualGR) { |
||
| 394 | g3 = c; |
||
| 764 | blopes | 395 | idxG3 = i; |
| 396 | break; |
||
| 397 | } |
||
| 398 | } |
||
| 760 | blopes | 399 | |
| 771 | blopes | 400 | if (g3 == null) |
| 401 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
||
| 760 | blopes | 402 | |
| 771 | blopes | 403 | log(String.format("GR[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d]", |
| 404 | idxGR, idxG1, idxG2, idxG3)); |
||
| 760 | blopes | 405 | |
| 771 | blopes | 406 | // 6) G4 – próximo candle rompe topo do GR |
| 764 | blopes | 407 | Candle g4 = null; |
| 408 | int proxIdx = idxG3 + 1; |
||
| 409 | if (proxIdx < n) { |
||
| 410 | Candle c = candles.get(proxIdx); |
||
| 411 | lastIndex = proxIdx; |
||
| 771 | blopes | 412 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
| 764 | blopes | 413 | g4 = c; |
| 771 | blopes | 414 | log(String.format("GR[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]", |
| 415 | idxGR, idxG1, idxG2, idxG3, proxIdx)); |
||
| 764 | blopes | 416 | } else { |
| 771 | blopes | 417 | log(String.format("GR[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.", |
| 418 | idxGR, idxG1, idxG2, idxG3, proxIdx)); |
||
| 764 | blopes | 419 | } |
| 420 | } |
||
| 760 | blopes | 421 | |
| 764 | blopes | 422 | PadraoGatilho padrao = new PadraoGatilho(); |
| 771 | blopes | 423 | padrao.setReferencia(gr); |
| 764 | blopes | 424 | padrao.setGatilho1(g1); |
| 425 | padrao.setGatilho2(g2); |
||
| 426 | padrao.setGatilho3(g3); |
||
| 427 | padrao.setGatilho4(g4); |
||
| 760 | blopes | 428 | |
| 764 | blopes | 429 | return new ResultadoPadrao(padrao, lastIndex); |
| 430 | } |
||
| 760 | blopes | 431 | |
| 764 | blopes | 432 | // ===================================================================== |
| 771 | blopes | 433 | // CASO 2 – PADRÃO A PARTIR DE CANDLE VENDEDOR |
| 764 | blopes | 434 | // ===================================================================== |
| 760 | blopes | 435 | |
| 771 | blopes | 436 | private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) { |
| 764 | blopes | 437 | int n = candles.size(); |
| 771 | blopes | 438 | int lastIndex = idxA; |
| 439 | Candle candleA = candles.get(idxA); |
||
| 440 | if (!candleA.isCandleVendedor()) { |
||
| 441 | return new ResultadoPadrao(null, idxA); |
||
| 764 | blopes | 442 | } |
| 760 | blopes | 443 | |
| 771 | blopes | 444 | // 1) A deve ser último da tendência vendedora -> achar B comprador |
| 445 | int idxB = -1; |
||
| 446 | for (int i = idxA + 1; i < n; i++) { |
||
| 764 | blopes | 447 | Candle c = candles.get(i); |
| 771 | blopes | 448 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 764 | blopes | 449 | continue; |
| 760 | blopes | 450 | |
| 771 | blopes | 451 | if (c.isCandleVendedor()) { |
| 452 | return new ResultadoPadrao(null, idxA); |
||
| 764 | blopes | 453 | } |
| 771 | blopes | 454 | if (c.isCandleComprador()) { |
| 455 | idxB = i; |
||
| 456 | break; |
||
| 457 | } |
||
| 764 | blopes | 458 | } |
| 771 | blopes | 459 | if (idxB == -1) return new ResultadoPadrao(null, idxA); |
| 760 | blopes | 460 | |
| 771 | blopes | 461 | Candle candleB = candles.get(idxB); |
| 760 | blopes | 462 | |
| 771 | blopes | 463 | // 2) Candidato à referência: A ou B (conforme fundo) |
| 464 | Candle candidatoRef = candleA; |
||
| 465 | int idxCandidatoRef = idxA; |
||
| 466 | if (BigDecimalUtils.ehMenorQue(candleB.getMinima(), candleA.getMinima())) { |
||
| 467 | candidatoRef = candleB; |
||
| 468 | idxCandidatoRef = idxB; |
||
| 764 | blopes | 469 | } |
| 760 | blopes | 470 | |
| 771 | blopes | 471 | // 3) G1 – primeiro comprador que rompe topo do candidatoRef |
| 472 | Candle g1 = null; |
||
| 764 | blopes | 473 | int idxG1 = -1; |
| 771 | blopes | 474 | for (int i = idxCandidatoRef + 1; i < n; i++) { |
| 764 | blopes | 475 | Candle c = candles.get(i); |
| 771 | blopes | 476 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 477 | continue; |
||
| 760 | blopes | 478 | |
| 764 | blopes | 479 | if (!c.isCandleComprador()) { |
| 480 | lastIndex = i - 1; |
||
| 481 | return new ResultadoPadrao(null, lastIndex); |
||
| 482 | } |
||
| 760 | blopes | 483 | |
| 764 | blopes | 484 | lastIndex = i; |
| 771 | blopes | 485 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
| 486 | g1 = c; |
||
| 764 | blopes | 487 | idxG1 = i; |
| 488 | break; |
||
| 489 | } |
||
| 490 | } |
||
| 771 | blopes | 491 | if (g1 == null) return new ResultadoPadrao(null, lastIndex); |
| 760 | blopes | 492 | |
| 771 | blopes | 493 | Candle gr = candidatoRef; |
| 494 | int idxGR = idxCandidatoRef; |
||
| 495 | log(String.format("GR vendedor em [%d], G1 (comprador) em [%d]", idxGR, idxG1)); |
||
| 760 | blopes | 496 | |
| 771 | blopes | 497 | // 4) G2 – nova tendência vendedora com fechamento dentro da região do GR |
| 498 | Candle g2 = null; |
||
| 499 | int idxG2 = -1; |
||
| 760 | blopes | 500 | |
| 764 | blopes | 501 | int idxPrimeiroVendedor = -1; |
| 502 | for (int i = idxG1 + 1; i < n; i++) { |
||
| 503 | Candle c = candles.get(i); |
||
| 771 | blopes | 504 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 764 | blopes | 505 | continue; |
| 760 | blopes | 506 | |
| 764 | blopes | 507 | if (c.isCandleVendedor()) { |
| 508 | idxPrimeiroVendedor = i; |
||
| 509 | break; |
||
| 510 | } else { |
||
| 511 | lastIndex = i; |
||
| 512 | } |
||
| 513 | } |
||
| 771 | blopes | 514 | if (idxPrimeiroVendedor == -1) return new ResultadoPadrao(null, lastIndex); |
| 760 | blopes | 515 | |
| 771 | blopes | 516 | Candle ultimoVendedor = null; |
| 517 | int idxUltimoVendedor = -1; |
||
| 518 | boolean rompeuFundoGR = false; |
||
| 760 | blopes | 519 | |
| 764 | blopes | 520 | for (int i = idxPrimeiroVendedor; i < n; i++) { |
| 521 | Candle c = candles.get(i); |
||
| 760 | blopes | 522 | |
| 771 | blopes | 523 | if (isOutside(candles, i)) { |
| 767 | blopes | 524 | lastIndex = i; |
| 771 | blopes | 525 | log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (vendedor).", idxGR, i)); |
| 767 | blopes | 526 | return new ResultadoPadrao(null, lastIndex); |
| 527 | } |
||
| 528 | |||
| 771 | blopes | 529 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 530 | continue; |
||
| 760 | blopes | 531 | |
| 771 | blopes | 532 | if (c.isCandleVendedor()) { |
| 533 | // se romper fundo do GR, descarta |
||
| 534 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
||
| 535 | rompeuFundoGR = true; |
||
| 536 | lastIndex = i; |
||
| 537 | break; |
||
| 538 | } |
||
| 539 | ultimoVendedor = c; |
||
| 540 | idxUltimoVendedor = i; |
||
| 541 | lastIndex = i; |
||
| 542 | } else if (c.isCandleComprador()) { |
||
| 543 | lastIndex = i - 1; |
||
| 764 | blopes | 544 | break; |
| 545 | } |
||
| 546 | } |
||
| 760 | blopes | 547 | |
| 771 | blopes | 548 | if (rompeuFundoGR || ultimoVendedor == null) |
| 764 | blopes | 549 | return new ResultadoPadrao(null, lastIndex); |
| 760 | blopes | 550 | |
| 771 | blopes | 551 | boolean fechamentoDentroRegiaoGR = |
| 552 | BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMinima()) && |
||
| 553 | BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMaxima()); |
||
| 760 | blopes | 554 | |
| 771 | blopes | 555 | if (!fechamentoDentroRegiaoGR) |
| 764 | blopes | 556 | return new ResultadoPadrao(null, lastIndex); |
| 760 | blopes | 557 | |
| 771 | blopes | 558 | g2 = ultimoVendedor; |
| 559 | idxG2 = idxUltimoVendedor; |
||
| 560 | log(String.format("GR[%d], G1[%d] => G2 (vendedor) em [%d]", idxGR, idxG1, idxG2)); |
||
| 760 | blopes | 561 | |
| 771 | blopes | 562 | // 5) G3 – próximo direcional comprador; comprador que rompe topo de G2 com fundo >= fundo GR |
| 563 | Candle g3 = null; |
||
| 564 | int idxG3 = -1; |
||
| 565 | |||
| 764 | blopes | 566 | int idxPrimeiroCompradorAposG2 = -1; |
| 567 | for (int i = idxG2 + 1; i < n; i++) { |
||
| 568 | Candle c = candles.get(i); |
||
| 771 | blopes | 569 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 764 | blopes | 570 | continue; |
| 760 | blopes | 571 | |
| 764 | blopes | 572 | if (c.isCandleComprador()) { |
| 573 | idxPrimeiroCompradorAposG2 = i; |
||
| 574 | break; |
||
| 575 | } else { |
||
| 576 | lastIndex = i; |
||
| 771 | blopes | 577 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
| 764 | blopes | 578 | } |
| 579 | } |
||
| 771 | blopes | 580 | if (idxPrimeiroCompradorAposG2 == -1) |
| 581 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
||
| 760 | blopes | 582 | |
| 764 | blopes | 583 | for (int i = idxPrimeiroCompradorAposG2; i < n; i++) { |
| 584 | Candle c = candles.get(i); |
||
| 767 | blopes | 585 | |
| 771 | blopes | 586 | if (isOutside(candles, i)) { |
| 767 | blopes | 587 | lastIndex = i; |
| 771 | blopes | 588 | log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (comprador).", idxGR, i)); |
| 767 | blopes | 589 | return new ResultadoPadrao(null, lastIndex); |
| 590 | } |
||
| 591 | |||
| 771 | blopes | 592 | if (isInside(candles, i) || (!c.isCandleComprador() && !c.isCandleVendedor())) |
| 593 | continue; |
||
| 594 | |||
| 595 | // se romper fundo do GR antes de G3 => descarta |
||
| 596 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
||
| 597 | lastIndex = i; |
||
| 598 | log(String.format("GR[%d]: candle [%d] rompeu fundo do GR antes de G3.", idxGR, i)); |
||
| 599 | return new ResultadoPadrao(null, lastIndex); |
||
| 600 | } |
||
| 601 | |||
| 764 | blopes | 602 | if (!c.isCandleComprador()) { |
| 603 | lastIndex = i - 1; |
||
| 604 | break; |
||
| 605 | } |
||
| 760 | blopes | 606 | |
| 764 | blopes | 607 | lastIndex = i; |
| 608 | boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima()); |
||
| 771 | blopes | 609 | boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima()); |
| 610 | if (rompeTopoG2 && fundoMaiorOuIgualGR) { |
||
| 611 | g3 = c; |
||
| 764 | blopes | 612 | idxG3 = i; |
| 613 | break; |
||
| 614 | } |
||
| 615 | } |
||
| 760 | blopes | 616 | |
| 771 | blopes | 617 | if (g3 == null) |
| 618 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex); |
||
| 760 | blopes | 619 | |
| 771 | blopes | 620 | log(String.format("GR[%d], G1[%d], G2[%d] => G3 (comprador) em [%d]", |
| 621 | idxGR, idxG1, idxG2, idxG3)); |
||
| 760 | blopes | 622 | |
| 771 | blopes | 623 | // 6) G4 – próximo candle rompe fundo do GR |
| 764 | blopes | 624 | Candle g4 = null; |
| 625 | int proxIdx = idxG3 + 1; |
||
| 626 | if (proxIdx < n) { |
||
| 627 | Candle c = candles.get(proxIdx); |
||
| 628 | lastIndex = proxIdx; |
||
| 771 | blopes | 629 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
| 764 | blopes | 630 | g4 = c; |
| 771 | blopes | 631 | log(String.format("GR[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]", |
| 632 | idxGR, idxG1, idxG2, idxG3, proxIdx)); |
||
| 764 | blopes | 633 | } else { |
| 771 | blopes | 634 | log(String.format("GR[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.", |
| 635 | idxGR, idxG1, idxG2, idxG3, proxIdx)); |
||
| 764 | blopes | 636 | } |
| 637 | } |
||
| 760 | blopes | 638 | |
| 764 | blopes | 639 | PadraoGatilho padrao = new PadraoGatilho(); |
| 771 | blopes | 640 | padrao.setReferencia(gr); |
| 764 | blopes | 641 | padrao.setGatilho1(g1); |
| 642 | padrao.setGatilho2(g2); |
||
| 643 | padrao.setGatilho3(g3); |
||
| 644 | padrao.setGatilho4(g4); |
||
| 760 | blopes | 645 | |
| 764 | blopes | 646 | return new ResultadoPadrao(padrao, lastIndex); |
| 647 | } |
||
| 760 | blopes | 648 | |
| 764 | blopes | 649 | // ===================================================================== |
| 771 | blopes | 650 | // MODO TEMPO REAL |
| 764 | blopes | 651 | // ===================================================================== |
| 760 | blopes | 652 | |
| 764 | blopes | 653 | public void resetTempoReal() { |
| 654 | this.idxProximaAnaliseTempoReal = 0; |
||
| 655 | } |
||
| 760 | blopes | 656 | |
| 764 | blopes | 657 | public PadraoGatilho processarCandleTempoReal(List<Candle> candles) { |
| 658 | int n = candles.size(); |
||
| 771 | blopes | 659 | if (n < 4) return null; |
| 760 | blopes | 660 | |
| 764 | blopes | 661 | while (idxProximaAnaliseTempoReal < n - 3) { |
| 662 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal); |
||
| 760 | blopes | 663 | |
| 764 | blopes | 664 | if (resultado == null) { |
| 665 | idxProximaAnaliseTempoReal++; |
||
| 666 | continue; |
||
| 667 | } |
||
| 760 | blopes | 668 | |
| 764 | blopes | 669 | int proximoInicio = resultado.lastIndex + 1; |
| 670 | if (proximoInicio <= idxProximaAnaliseTempoReal) { |
||
| 671 | proximoInicio = idxProximaAnaliseTempoReal + 1; |
||
| 672 | } |
||
| 673 | idxProximaAnaliseTempoReal = proximoInicio; |
||
| 760 | blopes | 674 | |
| 764 | blopes | 675 | if (resultado.padrao != null) { |
| 676 | return resultado.padrao; |
||
| 677 | } |
||
| 678 | } |
||
| 760 | blopes | 679 | |
| 764 | blopes | 680 | return null; |
| 681 | } |
||
| 760 | blopes | 682 | |
| 764 | blopes | 683 | } |