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