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 | |||
| 775 | blopes | 3 | import java.math.BigDecimal; |
| 760 | blopes | 4 | import java.util.ArrayList; |
| 764 | blopes | 5 | import java.util.List; |
| 760 | blopes | 6 | |
| 776 | blopes | 7 | import br.com.kronus.core.PadraoGatilho.TipoPadrao; |
| 764 | blopes | 8 | import br.com.sl.domain.model.Candle; |
| 9 | import br.com.sl.domain.util.BigDecimalUtils; |
||
| 10 | |||
| 760 | blopes | 11 | /** |
| 773 | blopes | 12 | * Detector de padrões de gatilhos (GR, G1, G2, G3) |
| 776 | blopes | 13 | * completamente integrado com enum TipoPadrao. |
| 760 | blopes | 14 | */ |
| 15 | public class DetectorGatilhos { |
||
| 16 | |||
| 764 | blopes | 17 | private final boolean logAtivo; |
| 18 | private int idxProximaAnaliseTempoReal = 0; |
||
| 760 | blopes | 19 | |
| 775 | blopes | 20 | /** |
| 21 | * Buffer opcional para capturar logs em memória (modo debug). |
||
| 22 | * Se for null, não acumula; se não for null, log() adiciona aqui também. |
||
| 23 | */ |
||
| 24 | private List<String> bufferDebug; |
||
| 25 | |||
| 764 | blopes | 26 | public DetectorGatilhos() { |
| 776 | blopes | 27 | this(false); |
| 764 | blopes | 28 | } |
| 760 | blopes | 29 | |
| 764 | blopes | 30 | public DetectorGatilhos(boolean logAtivo) { |
| 31 | this.logAtivo = logAtivo; |
||
| 32 | } |
||
| 760 | blopes | 33 | |
| 764 | blopes | 34 | private void log(String msg) { |
| 35 | if (logAtivo) { |
||
| 36 | System.out.println(msg); |
||
| 37 | } |
||
| 775 | blopes | 38 | if (bufferDebug != null) { |
| 39 | bufferDebug.add(msg); |
||
| 40 | } |
||
| 764 | blopes | 41 | } |
| 760 | blopes | 42 | |
| 776 | blopes | 43 | // ================================================================ |
| 44 | // Estrutura interna de retorno |
||
| 45 | // ================================================================ |
||
| 764 | blopes | 46 | private static class ResultadoPadrao { |
| 771 | blopes | 47 | PadraoGatilho padrao; |
| 48 | int lastIndex; |
||
| 775 | blopes | 49 | int proximoInicio; |
| 773 | blopes | 50 | |
| 775 | blopes | 51 | ResultadoPadrao(PadraoGatilho padrao, int lastIndex, int proximoInicio) { |
| 764 | blopes | 52 | this.padrao = padrao; |
| 53 | this.lastIndex = lastIndex; |
||
| 775 | blopes | 54 | this.proximoInicio = proximoInicio; |
| 764 | blopes | 55 | } |
| 56 | } |
||
| 760 | blopes | 57 | |
| 776 | blopes | 58 | // ================================================================ |
| 59 | // Criador de padrão parcial |
||
| 60 | // ================================================================ |
||
| 775 | blopes | 61 | private ResultadoPadrao criarResultadoParcialComG2(Candle ref, |
| 764 | blopes | 62 | Candle g1, |
| 63 | Candle g2, |
||
| 775 | blopes | 64 | int lastIndex, |
| 65 | int idxGR) { |
||
| 776 | blopes | 66 | |
| 764 | blopes | 67 | PadraoGatilho padrao = new PadraoGatilho(); |
| 775 | blopes | 68 | padrao.setReferencia(ref); |
| 764 | blopes | 69 | padrao.setGatilho1(g1); |
| 70 | padrao.setGatilho2(g2); |
||
| 71 | padrao.setGatilho3(null); |
||
| 776 | blopes | 72 | padrao.setGatilho4(null); |
| 73 | padrao.setTipoPadrao(TipoPadrao.PARCIAL_G2); // ENUM |
||
| 74 | |||
| 75 | log(String.format("Padrão PARCIAL_G2: GR[%s], G1[%s], G2[%s]", |
||
| 76 | ref.getDataHora(), g1.getDataHora(), g2.getDataHora())); |
||
| 77 | |||
| 775 | blopes | 78 | return new ResultadoPadrao(padrao, lastIndex, idxGR + 1); |
| 764 | blopes | 79 | } |
| 760 | blopes | 80 | |
| 776 | blopes | 81 | // ================================================================ |
| 775 | blopes | 82 | // HELPERS |
| 776 | blopes | 83 | // ================================================================ |
| 775 | blopes | 84 | private boolean isInside(List<Candle> candles, int idx) { |
| 85 | if (idx <= 0) return false; |
||
| 776 | blopes | 86 | |
| 775 | blopes | 87 | Candle atual = candles.get(idx); |
| 88 | Candle anterior = candles.get(idx - 1); |
||
| 776 | blopes | 89 | |
| 775 | blopes | 90 | return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima()) |
| 776 | blopes | 91 | && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima()); |
| 775 | blopes | 92 | } |
| 93 | |||
| 94 | private boolean isDirecional(Candle c) { |
||
| 95 | return c.isCandleComprador() || c.isCandleVendedor(); |
||
| 96 | } |
||
| 97 | |||
| 98 | private boolean fechamentoDentroRegiaoGR(Candle c, Candle gr) { |
||
| 99 | return BigDecimalUtils.ehMaiorOuIgualQue(c.getFechamento(), gr.getMinima()) |
||
| 776 | blopes | 100 | && BigDecimalUtils.ehMenorOuIgualQue(c.getFechamento(), gr.getMaxima()); |
| 775 | blopes | 101 | } |
| 102 | |||
| 103 | private BigDecimal fibExtend(BigDecimal origem, BigDecimal destino, BigDecimal fator) { |
||
| 776 | blopes | 104 | return origem.add(destino.subtract(origem).multiply(fator)); |
| 775 | blopes | 105 | } |
| 106 | |||
| 776 | blopes | 107 | // ================================================================ |
| 771 | blopes | 108 | // API PRINCIPAL – BACKTEST |
| 776 | blopes | 109 | // ================================================================ |
| 110 | public List<PadraoGatilho> identificarPadroes(List<Candle> candles) { |
||
| 760 | blopes | 111 | |
| 776 | blopes | 112 | List<PadraoGatilho> padroes = new ArrayList<>(); |
| 113 | int n = candles.size(); |
||
| 114 | if (n < 4) return padroes; |
||
| 760 | blopes | 115 | |
| 776 | blopes | 116 | log("===== INÍCIO BACKTEST (varrendo todos os candles como possível A) ====="); |
| 760 | blopes | 117 | |
| 776 | blopes | 118 | for (int idxRef = 0; idxRef < n - 3; idxRef++) { |
| 760 | blopes | 119 | |
| 776 | blopes | 120 | log(String.format("---- Nova tentativa: idxRef = %d (candle #%d) ----", idxRef, idxRef + 1)); |
| 760 | blopes | 121 | |
| 776 | blopes | 122 | ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef); |
| 775 | blopes | 123 | |
| 776 | blopes | 124 | if (resultado == null) { |
| 125 | log(String.format("idxRef=%d: ResultadoPadrao null, seguindo para próximo candle.", idxRef)); |
||
| 126 | continue; |
||
| 127 | } |
||
| 760 | blopes | 128 | |
| 776 | blopes | 129 | if (resultado.padrao != null) { |
| 130 | PadraoGatilho p = resultado.padrao; |
||
| 131 | Candle gr = p.getReferencia(); |
||
| 132 | Candle g1 = p.getGatilho1(); |
||
| 133 | Candle g2 = p.getGatilho2(); |
||
| 134 | Candle g3 = p.getGatilho3(); |
||
| 760 | blopes | 135 | |
| 776 | blopes | 136 | log(String.format( |
| 137 | ">> PADRÃO ENCONTRADO a partir de idxRef=%d: GR[%s], G1[%s], G2[%s], G3[%s], tipo=%s", |
||
| 138 | idxRef, |
||
| 139 | (gr != null ? gr.getDataHora() : "null"), |
||
| 140 | (g1 != null ? g1.getDataHora() : "null"), |
||
| 141 | (g2 != null ? g2.getDataHora() : "null"), |
||
| 142 | (g3 != null ? g3.getDataHora() : "null"), |
||
| 143 | p.getTipoPadrao() |
||
| 144 | )); |
||
| 145 | |||
| 146 | padroes.add(p); |
||
| 147 | } else { |
||
| 148 | log(String.format("idxRef=%d: nenhuma formação de padrão (sem GR/G1/G2/G3 válidos).", idxRef)); |
||
| 149 | } |
||
| 150 | } |
||
| 151 | |||
| 152 | log("===== FIM BACKTEST (varredura completa) ====="); |
||
| 153 | return padroes; |
||
| 154 | } |
||
| 155 | |||
| 156 | |||
| 764 | blopes | 157 | private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) { |
| 776 | blopes | 158 | |
| 764 | blopes | 159 | Candle ref = candles.get(idxRef); |
| 775 | blopes | 160 | |
| 764 | blopes | 161 | if (ref.isCandleComprador()) { |
| 162 | return detectarPadraoComprador(candles, idxRef); |
||
| 776 | blopes | 163 | |
| 764 | blopes | 164 | } else if (ref.isCandleVendedor()) { |
| 165 | return detectarPadraoVendedor(candles, idxRef); |
||
| 776 | blopes | 166 | |
| 764 | blopes | 167 | } else { |
| 776 | blopes | 168 | log(String.format("Candle[%d] neutro; avançando.", idxRef + 1)); |
| 775 | blopes | 169 | return new ResultadoPadrao(null, idxRef, idxRef + 1); |
| 764 | blopes | 170 | } |
| 171 | } |
||
| 776 | blopes | 172 | |
| 173 | // ================================================================ |
||
| 174 | // DEBUG BACKTEST COMPLETO |
||
| 175 | // ================================================================ |
||
| 176 | public List<String> debugarBacktestCompleto(List<Candle> candles) { |
||
| 177 | List<String> relatorio = new ArrayList<>(); |
||
| 178 | List<String> antigo = this.bufferDebug; |
||
| 179 | |||
| 180 | this.bufferDebug = relatorio; |
||
| 181 | try { |
||
| 182 | log("####################################################"); |
||
| 183 | log("DEBUG BACKTEST COMPLETO - iniciar identificarPadroes()"); |
||
| 184 | identificarPadroes(candles); |
||
| 185 | log("DEBUG BACKTEST COMPLETO - fim identificarPadroes()"); |
||
| 186 | log("####################################################"); |
||
| 187 | } finally { |
||
| 188 | this.bufferDebug = antigo; |
||
| 189 | } |
||
| 190 | |||
| 191 | return relatorio; |
||
| 192 | } |
||
| 760 | blopes | 193 | |
| 194 | |||
| 776 | blopes | 195 | // ================================================================ |
| 196 | // PADRÃO COMPRADOR (VENDA) |
||
| 197 | // ================================================================ |
||
| 775 | blopes | 198 | private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxA) { |
| 776 | blopes | 199 | |
| 775 | blopes | 200 | int n = candles.size(); |
| 201 | Candle candleA = candles.get(idxA); |
||
| 776 | blopes | 202 | |
| 775 | blopes | 203 | if (!candleA.isCandleComprador()) { |
| 204 | return new ResultadoPadrao(null, idxA, idxA + 1); |
||
| 205 | } |
||
| 760 | blopes | 206 | |
| 775 | blopes | 207 | int lastIndex = idxA; |
| 760 | blopes | 208 | |
| 776 | blopes | 209 | log("[VENDER] Iniciando em A[" + (idxA + 1) + "] " + candleA.getDataHora()); |
| 210 | |||
| 211 | // -------------------------------------------------------------- |
||
| 212 | // Busca candle B direcional (mudança de direção) |
||
| 213 | // -------------------------------------------------------------- |
||
| 775 | blopes | 214 | int idxProxDirecional = -1; |
| 776 | blopes | 215 | |
| 775 | blopes | 216 | for (int i = idxA + 1; i < n; i++) { |
| 776 | blopes | 217 | |
| 775 | blopes | 218 | Candle c = candles.get(i); |
| 776 | blopes | 219 | |
| 775 | blopes | 220 | if (!isDirecional(c) || isInside(candles, i)) { |
| 221 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside)", i + 1)); |
||
| 222 | continue; |
||
| 223 | } |
||
| 776 | blopes | 224 | |
| 775 | blopes | 225 | idxProxDirecional = i; |
| 226 | break; |
||
| 227 | } |
||
| 773 | blopes | 228 | |
| 775 | blopes | 229 | if (idxProxDirecional == -1) { |
| 776 | blopes | 230 | log("[VENDER] Nenhum candle direcional após A; abortando padrão."); |
| 775 | blopes | 231 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 232 | } |
||
| 771 | blopes | 233 | |
| 775 | blopes | 234 | Candle prox = candles.get(idxProxDirecional); |
| 776 | blopes | 235 | |
| 775 | blopes | 236 | if (!prox.isCandleVendedor()) { |
| 776 | blopes | 237 | log(String.format("[VENDER] Próximo direcional [%d] não é vendedor; A não vira referência.", idxProxDirecional + 1)); |
| 775 | blopes | 238 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 764 | blopes | 239 | } |
| 760 | blopes | 240 | |
| 776 | blopes | 241 | // Candidato à referência |
| 775 | blopes | 242 | Candle candidatoRef = candleA; |
| 243 | int idxCandidatoRef = idxA; |
||
| 244 | log(String.format("[VENDER] CandidatoRef inicial = idx %d", idxCandidatoRef + 1)); |
||
| 760 | blopes | 245 | |
| 771 | blopes | 246 | Candle g1 = null; |
| 764 | blopes | 247 | int idxG1 = -1; |
| 773 | blopes | 248 | |
| 776 | blopes | 249 | // -------------------------------------------------------------- |
| 250 | // Busca GR dinâmico e G1 |
||
| 251 | // -------------------------------------------------------------- |
||
| 775 | blopes | 252 | for (int i = idxA + 1; i < n; i++) { |
| 776 | blopes | 253 | |
| 764 | blopes | 254 | Candle c = candles.get(i); |
| 776 | blopes | 255 | |
| 775 | blopes | 256 | if (!isDirecional(c) || isInside(candles, i)) { |
| 257 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1)); |
||
| 771 | blopes | 258 | continue; |
| 774 | blopes | 259 | } |
| 760 | blopes | 260 | |
| 775 | blopes | 261 | lastIndex = i; |
| 760 | blopes | 262 | |
| 776 | blopes | 263 | // G1 |
| 773 | blopes | 264 | if (c.isCandleVendedor() |
| 776 | blopes | 265 | && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
| 266 | |||
| 771 | blopes | 267 | g1 = c; |
| 764 | blopes | 268 | idxG1 = i; |
| 776 | blopes | 269 | log(String.format("[VENDER] G1 encontrado em [%d], rompendo fundo de candRef[%d]", |
| 270 | idxG1 + 1, idxCandidatoRef + 1)); |
||
| 764 | blopes | 271 | break; |
| 272 | } |
||
| 773 | blopes | 273 | |
| 776 | blopes | 274 | // Atualização do GR dinâmico |
| 775 | blopes | 275 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
| 276 | candidatoRef = c; |
||
| 277 | idxCandidatoRef = i; |
||
| 278 | log(String.format("[VENDER] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1)); |
||
| 279 | } |
||
| 764 | blopes | 280 | } |
| 760 | blopes | 281 | |
| 775 | blopes | 282 | if (g1 == null) { |
| 776 | blopes | 283 | log("[VENDER] Não formou G1; recomeçando após A."); |
| 775 | blopes | 284 | return new ResultadoPadrao(null, lastIndex, idxA + 1); |
| 773 | blopes | 285 | } |
| 760 | blopes | 286 | |
| 775 | blopes | 287 | Candle gr = candidatoRef; |
| 288 | int idxGR = idxCandidatoRef; |
||
| 776 | blopes | 289 | |
| 775 | blopes | 290 | log(String.format("[VENDER] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1)); |
| 760 | blopes | 291 | |
| 776 | blopes | 292 | // -------------------------------------------------------------- |
| 293 | // Fibonacci 200% (origem = máxG1, destino = mínG1) |
||
| 294 | // -------------------------------------------------------------- |
||
| 775 | blopes | 295 | BigDecimal fib200 = fibExtend(g1.getMaxima(), g1.getMinima(), new BigDecimal("2")); |
| 776 | blopes | 296 | log(String.format("[VENDER] Fibo200 G1[%d] = %s", idxG1 + 1, fib200.toPlainString())); |
| 775 | blopes | 297 | |
| 776 | blopes | 298 | // -------------------------------------------------------------- |
| 299 | // Busca G2 e G3 |
||
| 300 | // -------------------------------------------------------------- |
||
| 771 | blopes | 301 | Candle g2 = null; |
| 302 | int idxG2 = -1; |
||
| 775 | blopes | 303 | Candle g3 = null; |
| 304 | int idxG3 = -1; |
||
| 771 | blopes | 305 | |
| 764 | blopes | 306 | for (int i = idxG1 + 1; i < n; i++) { |
| 776 | blopes | 307 | |
| 764 | blopes | 308 | Candle c = candles.get(i); |
| 776 | blopes | 309 | |
| 775 | blopes | 310 | if (!isDirecional(c) || isInside(candles, i)) { |
| 311 | log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1)); |
||
| 771 | blopes | 312 | continue; |
| 773 | blopes | 313 | } |
| 314 | |||
| 775 | blopes | 315 | lastIndex = i; |
| 773 | blopes | 316 | |
| 776 | blopes | 317 | // DESCARTES: |
| 760 | blopes | 318 | |
| 776 | blopes | 319 | // Rompe topo do GR |
| 773 | blopes | 320 | if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) { |
| 776 | blopes | 321 | log(String.format("[VENDER] GR[%d]: candle[%d] rompeu topo do GR. Descartando padrão.", |
| 322 | idxGR + 1, i + 1)); |
||
| 775 | blopes | 323 | return new ResultadoPadrao(null, i, idxGR + 1); |
| 767 | blopes | 324 | } |
| 325 | |||
| 776 | blopes | 326 | // Rompe nível Fib 200% |
| 775 | blopes | 327 | if (BigDecimalUtils.ehMenorOuIgualQue(c.getMinima(), fib200)) { |
| 328 | log(String.format("[VENDER] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.", |
||
| 329 | idxGR + 1, idxG1 + 1, i + 1)); |
||
| 330 | return new ResultadoPadrao(null, i, idxGR + 1); |
||
| 764 | blopes | 331 | } |
| 760 | blopes | 332 | |
| 776 | blopes | 333 | // ========================== |
| 334 | // G2 |
||
| 335 | // ========================== |
||
| 336 | if (c.isCandleComprador() |
||
| 337 | && fechamentoDentroRegiaoGR(c, gr)) { |
||
| 338 | |||
| 775 | blopes | 339 | if (g2 == null) { |
| 340 | g2 = c; |
||
| 341 | idxG2 = i; |
||
| 776 | blopes | 342 | log(String.format("[VENDER] GR[%d], G1[%d]: candidato G2 em [%d]", |
| 343 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
||
| 344 | |||
| 345 | } else if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima())) { |
||
| 346 | g2 = c; |
||
| 347 | idxG2 = i; |
||
| 348 | log(String.format("[VENDER] GR[%d], G1[%d]: G2 atualizado em [%d]", |
||
| 349 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
||
| 775 | blopes | 350 | } |
| 776 | blopes | 351 | |
| 764 | blopes | 352 | continue; |
| 774 | blopes | 353 | } |
| 760 | blopes | 354 | |
| 776 | blopes | 355 | // ========================== |
| 356 | // G3 |
||
| 357 | // ========================== |
||
| 775 | blopes | 358 | if (g2 != null && c.isCandleVendedor()) { |
| 776 | blopes | 359 | |
| 774 | blopes | 360 | boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima()); |
| 361 | boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima()); |
||
| 775 | blopes | 362 | |
| 774 | blopes | 363 | if (rompeFundoG2 && topoMenorOuIgualGR) { |
| 364 | g3 = c; |
||
| 365 | idxG3 = i; |
||
| 775 | blopes | 366 | log(String.format("[VENDER] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)", |
| 367 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
||
| 774 | blopes | 368 | break; |
| 369 | } |
||
| 764 | blopes | 370 | } |
| 371 | } |
||
| 773 | blopes | 372 | |
| 776 | blopes | 373 | // Nenhum G3 → padrão parcial |
| 773 | blopes | 374 | if (g3 == null) { |
| 776 | blopes | 375 | |
| 775 | blopes | 376 | if (g2 != null) { |
| 776 | blopes | 377 | log(String.format("[VENDER] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", |
| 378 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
||
| 775 | blopes | 379 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR); |
| 380 | } |
||
| 776 | blopes | 381 | |
| 382 | log(String.format("[VENDER] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", |
||
| 383 | idxGR + 1, idxG1 + 1)); |
||
| 775 | blopes | 384 | return new ResultadoPadrao(null, lastIndex, idxGR + 1); |
| 773 | blopes | 385 | } |
| 760 | blopes | 386 | |
| 776 | blopes | 387 | // -------------------------------------------------------------- |
| 388 | // PADRÃO COMPLETO (COM G3) |
||
| 389 | // -------------------------------------------------------------- |
||
| 764 | blopes | 390 | PadraoGatilho padrao = new PadraoGatilho(); |
| 771 | blopes | 391 | padrao.setReferencia(gr); |
| 764 | blopes | 392 | padrao.setGatilho1(g1); |
| 393 | padrao.setGatilho2(g2); |
||
| 394 | padrao.setGatilho3(g3); |
||
| 773 | blopes | 395 | padrao.setGatilho4(null); |
| 776 | blopes | 396 | padrao.setTipoPadrao(TipoPadrao.COMPLETO_G3); |
| 760 | blopes | 397 | |
| 776 | blopes | 398 | log(String.format("[VENDER] Padrão COMPLETO_G3: GR[%d], G1[%d], G2[%d], G3[%d]", |
| 399 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
||
| 400 | |||
| 775 | blopes | 401 | return new ResultadoPadrao(padrao, idxG3, idxGR + 1); |
| 764 | blopes | 402 | } |
| 760 | blopes | 403 | |
| 776 | blopes | 404 | // ================================================================ |
| 405 | // PADRÃO VENDEDOR (COMPRA) |
||
| 406 | // ================================================================ |
||
| 407 | private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) { |
||
| 760 | blopes | 408 | |
| 764 | blopes | 409 | int n = candles.size(); |
| 775 | blopes | 410 | Candle candleA = candles.get(idxA); |
| 776 | blopes | 411 | |
| 412 | if (!candleA.isCandleVendedor()) |
||
| 775 | blopes | 413 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 760 | blopes | 414 | |
| 775 | blopes | 415 | int lastIndex = idxA; |
| 416 | |||
| 776 | blopes | 417 | log("[COMPRAR] Iniciando em A[" + (idxA + 1) + "] " + candleA.getDataHora()); |
| 418 | |||
| 419 | // -------------------------------------------------------------- |
||
| 420 | // Busca B direcional |
||
| 421 | // -------------------------------------------------------------- |
||
| 775 | blopes | 422 | int idxProxDirecional = -1; |
| 776 | blopes | 423 | |
| 775 | blopes | 424 | for (int i = idxA + 1; i < n; i++) { |
| 776 | blopes | 425 | |
| 775 | blopes | 426 | Candle c = candles.get(i); |
| 776 | blopes | 427 | |
| 775 | blopes | 428 | if (!isDirecional(c) || isInside(candles, i)) { |
| 429 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside)", i + 1)); |
||
| 430 | continue; |
||
| 431 | } |
||
| 776 | blopes | 432 | |
| 775 | blopes | 433 | idxProxDirecional = i; |
| 434 | break; |
||
| 764 | blopes | 435 | } |
| 760 | blopes | 436 | |
| 775 | blopes | 437 | if (idxProxDirecional == -1) { |
| 776 | blopes | 438 | log("[COMPRAR] Nenhum candle direcional após A; abortando padrão."); |
| 775 | blopes | 439 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 440 | } |
||
| 760 | blopes | 441 | |
| 775 | blopes | 442 | Candle prox = candles.get(idxProxDirecional); |
| 776 | blopes | 443 | |
| 775 | blopes | 444 | if (!prox.isCandleComprador()) { |
| 776 | blopes | 445 | log(String.format("[COMPRAR] Próximo direcional [%d] não é comprador; A não vira referência.", |
| 446 | idxProxDirecional + 1)); |
||
| 775 | blopes | 447 | return new ResultadoPadrao(null, idxA, idxA + 1); |
| 448 | } |
||
| 449 | |||
| 776 | blopes | 450 | // GR dinâmico |
| 775 | blopes | 451 | Candle candidatoRef = candleA; |
| 452 | int idxCandidatoRef = idxA; |
||
| 453 | |||
| 771 | blopes | 454 | Candle g1 = null; |
| 764 | blopes | 455 | int idxG1 = -1; |
| 773 | blopes | 456 | |
| 776 | blopes | 457 | log(String.format("[COMPRAR] CandidatoRef inicial = idx %d", idxCandidatoRef + 1)); |
| 458 | |||
| 459 | // -------------------------------------------------------------- |
||
| 460 | // Busca G1 e GR |
||
| 461 | // -------------------------------------------------------------- |
||
| 775 | blopes | 462 | for (int i = idxA + 1; i < n; i++) { |
| 776 | blopes | 463 | |
| 764 | blopes | 464 | Candle c = candles.get(i); |
| 775 | blopes | 465 | if (!isDirecional(c) || isInside(candles, i)) { |
| 466 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1)); |
||
| 771 | blopes | 467 | continue; |
| 774 | blopes | 468 | } |
| 760 | blopes | 469 | |
| 775 | blopes | 470 | lastIndex = i; |
| 760 | blopes | 471 | |
| 773 | blopes | 472 | if (c.isCandleComprador() |
| 776 | blopes | 473 | && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) { |
| 474 | |||
| 771 | blopes | 475 | g1 = c; |
| 764 | blopes | 476 | idxG1 = i; |
| 776 | blopes | 477 | log(String.format("[COMPRAR] G1 encontrado em [%d], rompendo topo de candRef[%d]", |
| 478 | idxG1 + 1, idxCandidatoRef + 1)); |
||
| 764 | blopes | 479 | break; |
| 480 | } |
||
| 773 | blopes | 481 | |
| 775 | blopes | 482 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) { |
| 483 | candidatoRef = c; |
||
| 484 | idxCandidatoRef = i; |
||
| 485 | log(String.format("[COMPRAR] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1)); |
||
| 486 | } |
||
| 764 | blopes | 487 | } |
| 760 | blopes | 488 | |
| 775 | blopes | 489 | if (g1 == null) { |
| 776 | blopes | 490 | log("[COMPRAR] Não formou G1; recomeçando após A."); |
| 775 | blopes | 491 | return new ResultadoPadrao(null, lastIndex, idxA + 1); |
| 773 | blopes | 492 | } |
| 760 | blopes | 493 | |
| 775 | blopes | 494 | Candle gr = candidatoRef; |
| 495 | int idxGR = idxCandidatoRef; |
||
| 776 | blopes | 496 | |
| 775 | blopes | 497 | log(String.format("[COMPRAR] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1)); |
| 773 | blopes | 498 | |
| 776 | blopes | 499 | // -------------------------------------------------------------- |
| 500 | // Fibonacci -100% (aqui usando fator 2 entre mín e máx) |
||
| 501 | // -------------------------------------------------------------- |
||
| 775 | blopes | 502 | BigDecimal fib200MinMax = fibExtend(g1.getMinima(), g1.getMaxima(), new BigDecimal("2")); |
| 503 | log(String.format("[COMPRAR] Fibo200 G1[%d] = %s", idxG1 + 1, fib200MinMax.toPlainString())); |
||
| 504 | |||
| 776 | blopes | 505 | // -------------------------------------------------------------- |
| 506 | // Busca G2 e G3 |
||
| 507 | // -------------------------------------------------------------- |
||
| 771 | blopes | 508 | Candle g2 = null; |
| 509 | int idxG2 = -1; |
||
| 775 | blopes | 510 | Candle g3 = null; |
| 511 | int idxG3 = -1; |
||
| 760 | blopes | 512 | |
| 764 | blopes | 513 | for (int i = idxG1 + 1; i < n; i++) { |
| 776 | blopes | 514 | |
| 764 | blopes | 515 | Candle c = candles.get(i); |
| 775 | blopes | 516 | if (!isDirecional(c) || isInside(candles, i)) { |
| 517 | log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1)); |
||
| 518 | continue; |
||
| 773 | blopes | 519 | } |
| 520 | |||
| 775 | blopes | 521 | lastIndex = i; |
| 773 | blopes | 522 | |
| 776 | blopes | 523 | // DESCARTES |
| 760 | blopes | 524 | |
| 773 | blopes | 525 | if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) { |
| 776 | blopes | 526 | log(String.format("[COMPRAR] GR[%d]: candle[%d] rompeu fundo do GR. Descartando padrão.", |
| 527 | idxGR + 1, i + 1)); |
||
| 775 | blopes | 528 | return new ResultadoPadrao(null, i, idxGR + 1); |
| 767 | blopes | 529 | } |
| 530 | |||
| 775 | blopes | 531 | if (BigDecimalUtils.ehMaiorQue(c.getMinima(), fib200MinMax)) { |
| 532 | log(String.format("[COMPRAR] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.", |
||
| 533 | idxGR + 1, idxG1 + 1, i + 1)); |
||
| 534 | return new ResultadoPadrao(null, i, idxGR + 1); |
||
| 764 | blopes | 535 | } |
| 760 | blopes | 536 | |
| 776 | blopes | 537 | // ========================== |
| 538 | // G2 |
||
| 539 | // ========================== |
||
| 775 | blopes | 540 | if (c.isCandleVendedor() && fechamentoDentroRegiaoGR(c, gr)) { |
| 776 | blopes | 541 | |
| 775 | blopes | 542 | if (g2 == null) { |
| 543 | g2 = c; |
||
| 544 | idxG2 = i; |
||
| 776 | blopes | 545 | log(String.format("[COMPRAR] GR[%d], G1[%d]: candidato G2 em [%d]", |
| 546 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
||
| 547 | |||
| 548 | } else if (BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima())) { |
||
| 549 | g2 = c; |
||
| 550 | idxG2 = i; |
||
| 551 | log(String.format("[COMPRAR] GR[%d], G1[%d]: G2 atualizado em [%d]", |
||
| 552 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
||
| 775 | blopes | 553 | } |
| 776 | blopes | 554 | |
| 764 | blopes | 555 | continue; |
| 774 | blopes | 556 | } |
| 760 | blopes | 557 | |
| 776 | blopes | 558 | // ========================== |
| 559 | // G3 |
||
| 560 | // ========================== |
||
| 775 | blopes | 561 | if (g2 != null && c.isCandleComprador()) { |
| 773 | blopes | 562 | |
| 776 | blopes | 563 | boolean rompeTopo = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima()); |
| 564 | boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima()); |
||
| 565 | |||
| 566 | if (rompeTopo && fundoMaiorOuIgualGR) { |
||
| 774 | blopes | 567 | g3 = c; |
| 568 | idxG3 = i; |
||
| 775 | blopes | 569 | log(String.format("[COMPRAR] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)", |
| 570 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
||
| 774 | blopes | 571 | break; |
| 572 | } |
||
| 764 | blopes | 573 | } |
| 574 | } |
||
| 773 | blopes | 575 | |
| 776 | blopes | 576 | // ==================================== |
| 577 | // Nenhum G3 → padrão parcial |
||
| 578 | // ==================================== |
||
| 773 | blopes | 579 | if (g3 == null) { |
| 776 | blopes | 580 | |
| 775 | blopes | 581 | if (g2 != null) { |
| 776 | blopes | 582 | log(String.format("[COMPRAR] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", |
| 583 | idxGR + 1, idxG1 + 1, idxG2 + 1)); |
||
| 775 | blopes | 584 | return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR); |
| 585 | } |
||
| 776 | blopes | 586 | |
| 587 | log(String.format("[COMPRAR] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", |
||
| 588 | idxGR + 1, idxG1 + 1)); |
||
| 775 | blopes | 589 | return new ResultadoPadrao(null, lastIndex, idxGR + 1); |
| 773 | blopes | 590 | } |
| 760 | blopes | 591 | |
| 776 | blopes | 592 | // ==================================== |
| 593 | // PADRÃO COMPLETO (COM G3) |
||
| 594 | // ==================================== |
||
| 764 | blopes | 595 | PadraoGatilho padrao = new PadraoGatilho(); |
| 771 | blopes | 596 | padrao.setReferencia(gr); |
| 764 | blopes | 597 | padrao.setGatilho1(g1); |
| 598 | padrao.setGatilho2(g2); |
||
| 599 | padrao.setGatilho3(g3); |
||
| 773 | blopes | 600 | padrao.setGatilho4(null); |
| 776 | blopes | 601 | padrao.setTipoPadrao(TipoPadrao.COMPLETO_G3); |
| 760 | blopes | 602 | |
| 776 | blopes | 603 | log(String.format("[COMPRAR] Padrão COMPLETO_G3: GR[%d], G1[%d], G2[%d], G3[%d]", |
| 604 | idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1)); |
||
| 605 | |||
| 775 | blopes | 606 | return new ResultadoPadrao(padrao, idxG3, idxGR + 1); |
| 764 | blopes | 607 | } |
| 760 | blopes | 608 | |
| 776 | blopes | 609 | // ================================================================ |
| 610 | // TEMPO REAL |
||
| 611 | // ================================================================ |
||
| 764 | blopes | 612 | public void resetTempoReal() { |
| 776 | blopes | 613 | idxProximaAnaliseTempoReal = 0; |
| 764 | blopes | 614 | } |
| 760 | blopes | 615 | |
| 764 | blopes | 616 | public PadraoGatilho processarCandleTempoReal(List<Candle> candles) { |
| 776 | blopes | 617 | |
| 764 | blopes | 618 | int n = candles.size(); |
| 771 | blopes | 619 | if (n < 4) return null; |
| 760 | blopes | 620 | |
| 764 | blopes | 621 | while (idxProximaAnaliseTempoReal < n - 3) { |
| 760 | blopes | 622 | |
| 776 | blopes | 623 | ResultadoPadrao r = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal); |
| 624 | |||
| 625 | if (r == null) { |
||
| 764 | blopes | 626 | idxProximaAnaliseTempoReal++; |
| 627 | continue; |
||
| 628 | } |
||
| 760 | blopes | 629 | |
| 776 | blopes | 630 | int next = Math.max(r.proximoInicio, idxProximaAnaliseTempoReal + 1); |
| 631 | idxProximaAnaliseTempoReal = next; |
||
| 760 | blopes | 632 | |
| 776 | blopes | 633 | if (r.padrao != null) |
| 634 | return r.padrao; |
||
| 764 | blopes | 635 | } |
| 760 | blopes | 636 | |
| 764 | blopes | 637 | return null; |
| 638 | } |
||
| 760 | blopes | 639 | |
| 776 | blopes | 640 | // ================================================================ |
| 641 | // DEBUG - para usar em JSF |
||
| 642 | // ================================================================ |
||
| 775 | blopes | 643 | /** |
| 644 | * Roda a lógica de detecção a partir de um índice específico e devolve |
||
| 645 | * um "relatório" em forma de lista de strings com tudo que aconteceu. |
||
| 646 | * |
||
| 647 | * NÃO altera idxProximaAnaliseTempoReal. |
||
| 648 | */ |
||
| 649 | public List<String> debugarAPartirDoIndice(List<Candle> candles, int idxInicio) { |
||
| 776 | blopes | 650 | |
| 775 | blopes | 651 | List<String> relatorio = new ArrayList<>(); |
| 776 | blopes | 652 | |
| 653 | List<String> antigo = bufferDebug; |
||
| 654 | bufferDebug = relatorio; |
||
| 655 | |||
| 775 | blopes | 656 | try { |
| 776 | blopes | 657 | log("============= DEBUG ============="); |
| 658 | log("Iniciando análise no índice " + idxInicio); |
||
| 775 | blopes | 659 | detectarPadraoAPartir(candles, idxInicio); |
| 776 | blopes | 660 | log("Fim da análise"); |
| 661 | log("================================"); |
||
| 775 | blopes | 662 | } |
| 776 | blopes | 663 | finally { |
| 664 | bufferDebug = antigo; |
||
| 665 | } |
||
| 666 | |||
| 775 | blopes | 667 | return relatorio; |
| 668 | } |
||
| 669 | |||
| 764 | blopes | 670 | } |