Subversion Repositories Integrator Subversion

Rev

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
}