Subversion Repositories Integrator Subversion

Rev

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
}