Subversion Repositories Integrator Subversion

Rev

Rev 764 | Rev 771 | 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
 
762 blopes 3
import java.math.BigDecimal;
760 blopes 4
import java.util.ArrayList;
764 blopes 5
import java.util.List;
760 blopes 6
 
764 blopes 7
import br.com.sl.domain.model.Candle;
8
import br.com.sl.domain.util.BigDecimalUtils;
9
 
760 blopes 10
/**
764 blopes 11
 * Detector de padrões de gatilhos (GR, G1, G2, G3, G4)
12
 * trabalhando tanto em modo backtest quanto em modo tempo real.
760 blopes 13
 *
764 blopes 14
 * Regras resumidas:
760 blopes 15
 *
764 blopes 16
 * GR comprador (A):
17
 *   - Candle comprador.
18
 *   - Último candle da tendência compradora.
19
 *   - Maior topo da pernada compradora.
20
 *   - Após ele, tendência muda para vendedora.
21
 *   - Um vendedor subsequente rompe o fundo de A => G1 vendedor, A vira GR.
760 blopes 22
 *
764 blopes 23
 * GR vendedor (B):
24
 *   - Candle vendedor.
25
 *   - Último candle da tendência vendedora.
26
 *   - Menor fundo da pernada vendedora.
27
 *   - Após ele, tendência muda para compradora.
28
 *   - Um comprador subsequente rompe o topo de B => G1 comprador, B vira GR.
760 blopes 29
 *
764 blopes 30
 * G2 comprador (a partir de GR comprador):
31
 *   - Após G1 (vendedor), surge nova tendência compradora.
32
 *   - O ÚLTIMO candle dessa tendência compradora tem fechamento dentro da região do GR.
33
 *   - Se nessa tendência compradora alguma barra romper o topo do GR, o padrão é descartado.
34
 *   - Se a tendência terminar sem que o último comprador feche dentro da região do GR, descarta.
760 blopes 35
 *
764 blopes 36
 * G3 comprador:
37
 *   - Após G2, o próximo candle deve ser vendedor.
38
 *   - Em uma sequência vendedora, algum vendedor:
39
 *       * rompe o fundo de G2 (mínima < mínima de G2) e
40
 *       * tem topo <= topo do GR
41
 *     => esse candle é G3.
760 blopes 42
 *
764 blopes 43
 * G4 comprador:
44
 *   - Próximo candle após G3.
45
 *   - Se romper o topo do GR (máxima > máxima do GR) => G4.
46
 *   - Desarma a operação, padrão é encerrado, nova análise começa após esse candle.
47
 *
48
 * G2 vendedor (a partir de GR vendedor):
49
 *   - Após G1 (comprador), surge nova tendência vendedora.
50
 *   - O ÚLTIMO candle dessa tendência vendedora tem fechamento dentro da região do GR.
51
 *   - Se nessa tendência vendedora alguma barra romper o fundo do GR, o padrão é descartado.
52
 *
53
 * G3 vendedor:
54
 *   - Após G2, o próximo candle deve ser comprador.
55
 *   - Em uma sequência compradora, algum comprador:
56
 *       * rompe o topo de G2 (máxima > máxima de G2) e
57
 *       * tem fundo >= fundo do GR
58
 *     => esse candle é G3.
59
 *
60
 * G4 vendedor:
61
 *   - Próximo candle após G3.
62
 *   - Se romper o fundo do GR (mínima < mínima do GR) => G4.
63
 *
64
 * Outside em relação ao GR:
65
 *   - Qualquer candle C após o GR que:
66
 *       * C.maxima > GR.maxima E
67
 *       * C.minima < GR.minima
68
 *     => padrão atual é descartado, nova análise começa após C.
69
 *
70
 * Obs.: Quando um ciclo termina (padrão encontrado ou descartado),
71
 * a próxima análise SEMPRE começa após o último candle usado.
760 blopes 72
 */
73
public class DetectorGatilhos {
74
 
764 blopes 75
    private final boolean logAtivo;
760 blopes 76
 
764 blopes 77
    // Estado apenas para o modo tempo real
78
    private int idxProximaAnaliseTempoReal = 0;
760 blopes 79
 
764 blopes 80
    public DetectorGatilhos() {
81
        this(false);
82
    }
760 blopes 83
 
764 blopes 84
    public DetectorGatilhos(boolean logAtivo) {
85
        this.logAtivo = logAtivo;
86
    }
760 blopes 87
 
764 blopes 88
    private void log(String msg) {
89
        if (logAtivo) {
90
            System.out.println(msg);
91
        }
92
    }
760 blopes 93
 
764 blopes 94
    private static class ResultadoPadrao {
95
        PadraoGatilho padrao; // pode ser null (quando não houve padrão válido)
96
        int lastIndex;        // último índice de candle usado no ciclo
760 blopes 97
 
764 blopes 98
        ResultadoPadrao(PadraoGatilho padrao, int lastIndex) {
99
            this.padrao = padrao;
100
            this.lastIndex = lastIndex;
101
        }
102
    }
760 blopes 103
 
764 blopes 104
    /**
105
     * Cria um resultado com padrão PARCIAL (até G2).
106
     * Usado quando GR, G1 e G2 são válidos, mas G3 não se forma.
107
     */
108
    private ResultadoPadrao criarResultadoParcialComG2(Candle ref,
109
                                                       Candle g1,
110
                                                       Candle g2,
111
                                                       int lastIndex) {
112
        PadraoGatilho padrao = new PadraoGatilho();
113
        padrao.setReferencia(ref);
114
        padrao.setGatilho1(g1);
115
        padrao.setGatilho2(g2);
116
        padrao.setGatilho3(null);
117
        padrao.setGatilho4(null);
118
        return new ResultadoPadrao(padrao, lastIndex);
119
    }
760 blopes 120
 
764 blopes 121
    // =====================================================================
122
    // API PRINCIPAL – BACKTEST (lista completa de candles)
123
    // =====================================================================
760 blopes 124
 
764 blopes 125
    public List<PadraoGatilho> identificarPadroes(List<Candle> candles) {
126
        List<PadraoGatilho> padroes = new ArrayList<>();
127
        int n = candles.size();
128
        if (n < 4) {
129
            return padroes;
130
        }
760 blopes 131
 
764 blopes 132
        int idxRef = 0;
760 blopes 133
 
764 blopes 134
        while (idxRef < n - 3) {
135
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef);
760 blopes 136
 
764 blopes 137
            if (resultado == null) {
138
                idxRef++;
139
                continue;
140
            }
760 blopes 141
 
764 blopes 142
            if (resultado.padrao != null) {
143
                padroes.add(resultado.padrao);
144
            }
760 blopes 145
 
764 blopes 146
            // Próxima análise sempre começa após o último candle usado no ciclo
147
            idxRef = Math.max(resultado.lastIndex + 1, idxRef + 1);
148
        }
760 blopes 149
 
764 blopes 150
        return padroes;
151
    }
760 blopes 152
 
764 blopes 153
    private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) {
154
        Candle ref = candles.get(idxRef);
760 blopes 155
 
764 blopes 156
        if (ref.isCandleComprador()) {
157
            return detectarPadraoComprador(candles, idxRef);
158
        } else if (ref.isCandleVendedor()) {
159
            return detectarPadraoVendedor(candles, idxRef);
160
        } else {
161
            // candle sem tendência não serve como GR
162
            return new ResultadoPadrao(null, idxRef);
163
        }
164
    }
760 blopes 165
 
764 blopes 166
    // =====================================================================
167
    // CASO 1 – GR COMPRADOR (candle A)
168
    // =====================================================================
760 blopes 169
 
764 blopes 170
    private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxRef) {
171
        int n = candles.size();
172
        Candle ref = candles.get(idxRef); // candidato a GR
173
        int lastIndex = idxRef;
760 blopes 174
 
764 blopes 175
        // 0) Validar se idxRef pode ser GR comprador (A)
176
        if (!ref.isCandleComprador()) {
177
            return new ResultadoPadrao(null, idxRef);
178
        }
760 blopes 179
 
764 blopes 180
        // 0.1 – início da pernada compradora que termina em A
181
        int inicioTrendCompra = idxRef;
182
        for (int i = idxRef - 1; i >= 0; i--) {
183
            Candle c = candles.get(i);
184
            if (!c.isCandleComprador()) {
185
                break;
186
            }
187
            inicioTrendCompra = i;
188
        }
760 blopes 189
 
764 blopes 190
        // 0.2 – primeiro candle direcional após A
191
        int idxPrimeiroDirecionalAposRef = -1;
192
        for (int i = idxRef + 1; i < n; i++) {
193
            Candle c = candles.get(i);
194
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
195
                continue;
196
            }
760 blopes 197
 
767 blopes 198
            // OUTSIDE em relação ao GR (até G3)
764 blopes 199
            if (isOutsideReferencia(ref, c)) {
200
                lastIndex = i;
201
                log(String.format(
202
                        "RefC[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.",
203
                        idxRef, i));
204
                return new ResultadoPadrao(null, lastIndex);
205
            }
760 blopes 206
 
764 blopes 207
            idxPrimeiroDirecionalAposRef = i;
208
            break;
209
        }
760 blopes 210
 
764 blopes 211
        if (idxPrimeiroDirecionalAposRef == -1) {
212
            return new ResultadoPadrao(null, lastIndex);
213
        }
760 blopes 214
 
764 blopes 215
        Candle primeiroDirecional = candles.get(idxPrimeiroDirecionalAposRef);
216
        if (!primeiroDirecional.isCandleVendedor()) {
217
            // primeiro direcional após A ainda é comprador → A não é último da pernada
218
            return new ResultadoPadrao(null, idxRef);
219
        }
760 blopes 220
 
764 blopes 221
        // 0.3 – A tem o maior topo da pernada compradora
222
        BigDecimal topoRef = ref.getMaxima();
223
        for (int i = inicioTrendCompra; i <= idxRef; i++) {
224
            Candle c = candles.get(i);
225
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), topoRef)) {
226
                return new ResultadoPadrao(null, idxRef);
227
            }
228
        }
760 blopes 229
 
764 blopes 230
        int idxPrimeiroVendedor = idxPrimeiroDirecionalAposRef;
760 blopes 231
 
764 blopes 232
        // 1) Encontrar G1 (vendedor que rompe o fundo do GR)
233
        int idxG1 = -1;
234
        Candle g1 = null;
760 blopes 235
 
764 blopes 236
        for (int i = idxPrimeiroVendedor; i < n; i++) {
237
            Candle c = candles.get(i);
760 blopes 238
 
767 blopes 239
            // OUTSIDE em relação ao GR (até G3)
764 blopes 240
            if (isOutsideReferencia(ref, c)) {
241
                lastIndex = i;
242
                log(String.format(
243
                        "RefC[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.",
244
                        idxRef, i));
245
                return new ResultadoPadrao(null, lastIndex);
246
            }
760 blopes 247
 
764 blopes 248
            if (!c.isCandleVendedor()) {
249
                // tendência vendedora terminou antes do rompimento
250
                lastIndex = i - 1;
251
                return new ResultadoPadrao(null, lastIndex);
252
            }
760 blopes 253
 
764 blopes 254
            lastIndex = i;
760 blopes 255
 
764 blopes 256
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
257
                idxG1 = i;
258
                g1 = c;
259
                break;
260
            }
261
        }
760 blopes 262
 
764 blopes 263
        if (idxG1 == -1) {
264
            return new ResultadoPadrao(null, lastIndex);
265
        }
760 blopes 266
 
764 blopes 267
        log(String.format("RefC[%d] (GR) => G1 (vendedor) em [%d]", idxRef, idxG1));
760 blopes 268
 
764 blopes 269
        // 2) Encontrar G2 (retorno comprador à região do GR)
270
        int idxPrimeiroComprador = -1;
271
        for (int i = idxG1 + 1; i < n; i++) {
272
            Candle c = candles.get(i);
760 blopes 273
 
767 blopes 274
            // OUTSIDE em relação ao GR (até G3)
275
            if (isOutsideReferencia(ref, c)) {
276
                lastIndex = i;
277
                log(String.format(
278
                        "RefC[%d]: OUTSIDE em [%d] antes da tendência de G2. Padrão descartado.",
279
                        idxRef, i));
280
                return new ResultadoPadrao(null, lastIndex);
281
            }
282
 
764 blopes 283
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
284
                continue;
285
            }
286
            if (c.isCandleComprador()) {
287
                idxPrimeiroComprador = i;
288
                break;
289
            } else {
290
                lastIndex = i;
291
            }
292
        }
760 blopes 293
 
764 blopes 294
        if (idxPrimeiroComprador == -1) {
295
            return new ResultadoPadrao(null, lastIndex);
296
        }
760 blopes 297
 
764 blopes 298
        boolean rompeuTopoRef = false;
299
        Candle ultimoCompradorTrend = null;
300
        int idxUltimoCompradorTrend = -1;
760 blopes 301
 
764 blopes 302
        for (int i = idxPrimeiroComprador; i < n; i++) {
303
            Candle c = candles.get(i);
760 blopes 304
 
767 blopes 305
            // OUTSIDE em relação ao GR (até G3)
306
            if (isOutsideReferencia(ref, c)) {
307
                lastIndex = i;
308
                log(String.format(
309
                        "RefC[%d]: OUTSIDE em [%d] durante formação de G2. Padrão descartado.",
310
                        idxRef, i));
311
                return new ResultadoPadrao(null, lastIndex);
312
            }
313
 
764 blopes 314
            if (!c.isCandleComprador()) {
315
                break;
316
            }
760 blopes 317
 
764 blopes 318
            ultimoCompradorTrend = c;
319
            idxUltimoCompradorTrend = i;
320
            lastIndex = i;
760 blopes 321
 
764 blopes 322
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
323
                rompeuTopoRef = true;
324
                break;
325
            }
326
        }
760 blopes 327
 
764 blopes 328
        if (rompeuTopoRef || idxUltimoCompradorTrend == -1) {
329
            return new ResultadoPadrao(null, lastIndex);
330
        }
760 blopes 331
 
764 blopes 332
        boolean fechamentoDentroRegiaoRef =
333
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMinima()) &&
334
                BigDecimalUtils.ehMenorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMaxima());
760 blopes 335
 
764 blopes 336
        if (!fechamentoDentroRegiaoRef) {
337
            return new ResultadoPadrao(null, lastIndex);
338
        }
760 blopes 339
 
764 blopes 340
        Candle g2 = ultimoCompradorTrend;
341
        int idxG2 = idxUltimoCompradorTrend;
342
        log(String.format("RefC[%d], G1[%d] => G2 (comprador) em [%d]", idxRef, idxG1, idxG2));
760 blopes 343
 
764 blopes 344
        // 3) Encontrar G3 (vendedor rompendo fundo de G2, topo <= topo do GR)
345
        int idxPrimeiroVendedorAposG2 = -1;
346
        for (int i = idxG2 + 1; i < n; i++) {
347
            Candle c = candles.get(i);
760 blopes 348
 
767 blopes 349
            // OUTSIDE em relação ao GR (até G3)
350
            if (isOutsideReferencia(ref, c)) {
351
                lastIndex = i;
352
                log(String.format(
353
                        "RefC[%d]: OUTSIDE em [%d] antes da sequência vendedora de G3. Padrão descartado.",
354
                        idxRef, i));
355
                return new ResultadoPadrao(null, lastIndex);
356
            }
357
 
764 blopes 358
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
359
                continue;
360
            }
760 blopes 361
 
764 blopes 362
            if (c.isCandleVendedor()) {
363
                idxPrimeiroVendedorAposG2 = i;
364
                break;
365
            } else {
366
                lastIndex = i;
367
                // pela sua regra, o próximo após G2 deveria ser vendedor
368
                // => padrão chegou até G2, mas não evoluiu corretamente para G3
369
                return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
370
            }
371
        }
760 blopes 372
 
764 blopes 373
        if (idxPrimeiroVendedorAposG2 == -1) {
374
            // padrão com GR, G1, G2, mas sem sequência adequada para G3
375
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
376
        }
760 blopes 377
 
764 blopes 378
        int idxG3 = -1;
379
        Candle g3 = null;
760 blopes 380
 
764 blopes 381
        for (int i = idxPrimeiroVendedorAposG2; i < n; i++) {
382
            Candle c = candles.get(i);
767 blopes 383
 
384
            // OUTSIDE em relação ao GR (ATÉ IDENTIFICAR G3)
385
            if (isOutsideReferencia(ref, c)) {
386
                lastIndex = i;
387
                log(String.format(
388
                        "RefC[%d]: OUTSIDE em [%d] durante formação de G3. Padrão descartado.",
389
                        idxRef, i));
390
                return new ResultadoPadrao(null, lastIndex);
391
            }
392
 
764 blopes 393
            if (!c.isCandleVendedor()) {
394
                lastIndex = i - 1;
395
                break;
396
            }
760 blopes 397
 
764 blopes 398
            lastIndex = i;
760 blopes 399
 
764 blopes 400
            boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
401
            boolean topoMenorOuIgualRef = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), ref.getMaxima());
760 blopes 402
 
764 blopes 403
            if (rompeFundoG2 && topoMenorOuIgualRef) {
404
                idxG3 = i;
405
                g3 = c;
406
                break;
407
            }
408
        }
760 blopes 409
 
764 blopes 410
        if (idxG3 == -1 || g3 == null) {
411
            // Chegou até G2 mas não encontrou G3
412
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
413
        }
760 blopes 414
 
764 blopes 415
        log(String.format("RefC[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d]",
416
                idxRef, idxG1, idxG2, idxG3));
760 blopes 417
 
767 blopes 418
        // 4) G4 – próximo candle rompe topo do GR (aqui já não precisamos mais checar outside)
764 blopes 419
        Candle g4 = null;
420
        int proxIdx = idxG3 + 1;
421
        if (proxIdx < n) {
422
            Candle c = candles.get(proxIdx);
423
            lastIndex = proxIdx;
760 blopes 424
 
764 blopes 425
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
426
                g4 = c;
427
                log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
428
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
429
            } else {
430
                log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
431
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
432
            }
433
        }
760 blopes 434
 
764 blopes 435
        PadraoGatilho padrao = new PadraoGatilho();
436
        padrao.setReferencia(ref);
437
        padrao.setGatilho1(g1);
438
        padrao.setGatilho2(g2);
439
        padrao.setGatilho3(g3);
440
        padrao.setGatilho4(g4);
760 blopes 441
 
764 blopes 442
        return new ResultadoPadrao(padrao, lastIndex);
443
    }
760 blopes 444
 
767 blopes 445
 
764 blopes 446
    // =====================================================================
447
    // CASO 2 – GR VENDEDOR (candle B)
448
    // =====================================================================
760 blopes 449
 
764 blopes 450
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxRef) {
451
        int n = candles.size();
452
        Candle ref = candles.get(idxRef); // candidato a GR vendedor
453
        int lastIndex = idxRef;
760 blopes 454
 
764 blopes 455
        // 0) Validar se idxRef pode ser GR vendedor (B)
456
        if (!ref.isCandleVendedor()) {
457
            return new ResultadoPadrao(null, idxRef);
458
        }
760 blopes 459
 
764 blopes 460
        // 0.1 – início da pernada vendedora que termina em B
461
        int inicioTrendVenda = idxRef;
462
        for (int i = idxRef - 1; i >= 0; i--) {
463
            Candle c = candles.get(i);
464
            if (!c.isCandleVendedor()) {
465
                break;
466
            }
467
            inicioTrendVenda = i;
468
        }
760 blopes 469
 
764 blopes 470
        // 0.2 – primeiro candle direcional após B
471
        int idxPrimeiroDirecionalAposRef = -1;
472
        for (int i = idxRef + 1; i < n; i++) {
473
            Candle c = candles.get(i);
474
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
475
                continue;
476
            }
760 blopes 477
 
767 blopes 478
            // OUTSIDE em relação ao GR (até G3)
764 blopes 479
            if (isOutsideReferencia(ref, c)) {
480
                lastIndex = i;
481
                log(String.format(
482
                        "RefV[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.",
483
                        idxRef, i));
484
                return new ResultadoPadrao(null, lastIndex);
485
            }
760 blopes 486
 
764 blopes 487
            idxPrimeiroDirecionalAposRef = i;
488
            break;
489
        }
760 blopes 490
 
764 blopes 491
        if (idxPrimeiroDirecionalAposRef == -1) {
492
            return new ResultadoPadrao(null, lastIndex);
493
        }
760 blopes 494
 
764 blopes 495
        Candle primeiroDirecional = candles.get(idxPrimeiroDirecionalAposRef);
496
        if (!primeiroDirecional.isCandleComprador()) {
497
            // primeiro direcional após B ainda é vendedor
498
            return new ResultadoPadrao(null, idxRef);
499
        }
760 blopes 500
 
764 blopes 501
        // 0.3 – B tem o MENOR fundo da pernada vendedora
502
        BigDecimal fundoRef = ref.getMinima();
503
        for (int i = inicioTrendVenda; i <= idxRef; i++) {
504
            Candle c = candles.get(i);
505
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), fundoRef)) {
506
                return new ResultadoPadrao(null, idxRef);
507
            }
508
        }
760 blopes 509
 
764 blopes 510
        int idxPrimeiroComprador = idxPrimeiroDirecionalAposRef;
760 blopes 511
 
764 blopes 512
        // 1) Encontrar G1 (comprador que rompe topo do GR)
513
        int idxG1 = -1;
514
        Candle g1 = null;
760 blopes 515
 
764 blopes 516
        for (int i = idxPrimeiroComprador; i < n; i++) {
517
            Candle c = candles.get(i);
760 blopes 518
 
767 blopes 519
            // OUTSIDE em relação ao GR (até G3)
764 blopes 520
            if (isOutsideReferencia(ref, c)) {
521
                lastIndex = i;
522
                log(String.format(
523
                        "RefV[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.",
524
                        idxRef, i));
525
                return new ResultadoPadrao(null, lastIndex);
526
            }
760 blopes 527
 
764 blopes 528
            if (!c.isCandleComprador()) {
529
                lastIndex = i - 1;
530
                return new ResultadoPadrao(null, lastIndex);
531
            }
760 blopes 532
 
764 blopes 533
            lastIndex = i;
760 blopes 534
 
764 blopes 535
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
536
                idxG1 = i;
537
                g1 = c;
538
                break;
539
            }
540
        }
760 blopes 541
 
764 blopes 542
        if (idxG1 == -1) {
543
            return new ResultadoPadrao(null, lastIndex);
544
        }
760 blopes 545
 
764 blopes 546
        log(String.format("RefV[%d] (GR) => G1 (comprador) em [%d]", idxRef, idxG1));
760 blopes 547
 
764 blopes 548
        // 2) Encontrar G2 (retorno vendedor à região do GR)
549
        int idxPrimeiroVendedor = -1;
550
        for (int i = idxG1 + 1; i < n; i++) {
551
            Candle c = candles.get(i);
760 blopes 552
 
767 blopes 553
            // OUTSIDE em relação ao GR (até G3)
554
            if (isOutsideReferencia(ref, c)) {
555
                lastIndex = i;
556
                log(String.format(
557
                        "RefV[%d]: OUTSIDE em [%d] antes da tendência de G2. Padrão descartado.",
558
                        idxRef, i));
559
                return new ResultadoPadrao(null, lastIndex);
560
            }
561
 
764 blopes 562
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
563
                continue;
564
            }
760 blopes 565
 
764 blopes 566
            if (c.isCandleVendedor()) {
567
                idxPrimeiroVendedor = i;
568
                break;
569
            } else {
570
                lastIndex = i;
571
            }
572
        }
760 blopes 573
 
764 blopes 574
        if (idxPrimeiroVendedor == -1) {
575
            return new ResultadoPadrao(null, lastIndex);
576
        }
760 blopes 577
 
764 blopes 578
        boolean rompeuFundoRef = false;
579
        Candle ultimoVendedorTrend = null;
580
        int idxUltimoVendedorTrend = -1;
760 blopes 581
 
764 blopes 582
        for (int i = idxPrimeiroVendedor; i < n; i++) {
583
            Candle c = candles.get(i);
760 blopes 584
 
767 blopes 585
            // OUTSIDE em relação ao GR (até G3)
586
            if (isOutsideReferencia(ref, c)) {
587
                lastIndex = i;
588
                log(String.format(
589
                        "RefV[%d]: OUTSIDE em [%d] durante formação de G2. Padrão descartado.",
590
                        idxRef, i));
591
                return new ResultadoPadrao(null, lastIndex);
592
            }
593
 
764 blopes 594
            if (!c.isCandleVendedor()) {
595
                break;
596
            }
760 blopes 597
 
764 blopes 598
            ultimoVendedorTrend = c;
599
            idxUltimoVendedorTrend = i;
600
            lastIndex = i;
760 blopes 601
 
764 blopes 602
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
603
                rompeuFundoRef = true;
604
                break;
605
            }
606
        }
760 blopes 607
 
764 blopes 608
        if (rompeuFundoRef || idxUltimoVendedorTrend == -1) {
609
            return new ResultadoPadrao(null, lastIndex);
610
        }
760 blopes 611
 
764 blopes 612
        boolean fechamentoDentroRegiaoRef =
613
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMinima()) &&
614
                BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMaxima());
760 blopes 615
 
764 blopes 616
        if (!fechamentoDentroRegiaoRef) {
617
            return new ResultadoPadrao(null, lastIndex);
618
        }
760 blopes 619
 
764 blopes 620
        Candle g2 = ultimoVendedorTrend;
621
        int idxG2 = idxUltimoVendedorTrend;
622
        log(String.format("RefV[%d], G1[%d] => G2 (vendedor) em [%d]", idxRef, idxG1, idxG2));
760 blopes 623
 
764 blopes 624
        // 3) Encontrar G3 (comprador rompendo topo de G2, fundo >= fundo do GR)
625
        int idxPrimeiroCompradorAposG2 = -1;
626
        for (int i = idxG2 + 1; i < n; i++) {
627
            Candle c = candles.get(i);
760 blopes 628
 
767 blopes 629
            // OUTSIDE em relação ao GR (até G3)
630
            if (isOutsideReferencia(ref, c)) {
631
                lastIndex = i;
632
                log(String.format(
633
                        "RefV[%d]: OUTSIDE em [%d] antes da sequência compradora de G3. Padrão descartado.",
634
                        idxRef, i));
635
                return new ResultadoPadrao(null, lastIndex);
636
            }
637
 
764 blopes 638
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
639
                continue;
640
            }
760 blopes 641
 
764 blopes 642
            if (c.isCandleComprador()) {
643
                idxPrimeiroCompradorAposG2 = i;
644
                break;
645
            } else {
646
                lastIndex = i;
647
                // esperado comprador; padrão segue apenas até G2
648
                return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
649
            }
650
        }
760 blopes 651
 
764 blopes 652
        if (idxPrimeiroCompradorAposG2 == -1) {
653
            // não teve sequência adequada após G2
654
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
655
        }
760 blopes 656
 
764 blopes 657
        int idxG3 = -1;
658
        Candle g3 = null;
760 blopes 659
 
764 blopes 660
        for (int i = idxPrimeiroCompradorAposG2; i < n; i++) {
661
            Candle c = candles.get(i);
767 blopes 662
 
663
            // OUTSIDE em relação ao GR (ATÉ IDENTIFICAR G3)
664
            if (isOutsideReferencia(ref, c)) {
665
                lastIndex = i;
666
                log(String.format(
667
                        "RefV[%d]: OUTSIDE em [%d] durante formação de G3. Padrão descartado.",
668
                        idxRef, i));
669
                return new ResultadoPadrao(null, lastIndex);
670
            }
671
 
764 blopes 672
            if (!c.isCandleComprador()) {
673
                lastIndex = i - 1;
674
                break;
675
            }
760 blopes 676
 
764 blopes 677
            lastIndex = i;
760 blopes 678
 
764 blopes 679
            boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
680
            boolean fundoMaiorOuIgualRef = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), ref.getMinima());
760 blopes 681
 
764 blopes 682
            if (rompeTopoG2 && fundoMaiorOuIgualRef) {
683
                idxG3 = i;
684
                g3 = c;
685
                break;
686
            }
687
        }
760 blopes 688
 
764 blopes 689
        if (idxG3 == -1 || g3 == null) {
690
            // padrão só até G2
691
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
692
        }
760 blopes 693
 
764 blopes 694
        log(String.format("RefV[%d], G1[%d], G2[%d] => G3 (comprador) em [%d]",
695
                idxRef, idxG1, idxG2, idxG3));
760 blopes 696
 
764 blopes 697
        // 4) G4 – próximo candle rompe fundo do GR
698
        Candle g4 = null;
699
        int proxIdx = idxG3 + 1;
700
        if (proxIdx < n) {
701
            Candle c = candles.get(proxIdx);
702
            lastIndex = proxIdx;
760 blopes 703
 
764 blopes 704
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
705
                g4 = c;
706
                log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
707
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
708
            } else {
709
                log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
710
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
711
            }
712
        }
760 blopes 713
 
764 blopes 714
        PadraoGatilho padrao = new PadraoGatilho();
715
        padrao.setReferencia(ref);
716
        padrao.setGatilho1(g1);
717
        padrao.setGatilho2(g2);
718
        padrao.setGatilho3(g3);
719
        padrao.setGatilho4(g4);
760 blopes 720
 
764 blopes 721
        return new ResultadoPadrao(padrao, lastIndex);
722
    }
760 blopes 723
 
767 blopes 724
 
764 blopes 725
    // =====================================================================
726
    // HELPER – Candle outside em relação ao GR
727
    // =====================================================================
760 blopes 728
 
764 blopes 729
    /**
730
     * Outside em relação ao GR:
731
     *  - atual.max > GR.max e atual.min < GR.min
732
     */
733
    private boolean isOutsideReferencia(Candle referencia, Candle atual) {
734
        if (referencia == null || atual == null) return false;
735
        return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), referencia.getMaxima())
736
                && BigDecimalUtils.ehMenorQue(atual.getMinima(), referencia.getMinima());
737
    }
760 blopes 738
 
764 blopes 739
    // =====================================================================
740
    // MODO TEMPO REAL (candle a candle)
741
    // =====================================================================
760 blopes 742
 
764 blopes 743
    /**
744
     * Reinicia o estado do modo tempo real.
745
     */
746
    public void resetTempoReal() {
747
        this.idxProximaAnaliseTempoReal = 0;
748
    }
760 blopes 749
 
764 blopes 750
    /**
751
     * Deve ser chamado SEMPRE que um novo candle for adicionado à lista.
752
     *
753
     * Exemplo:
754
     *   candles.add(novoCandle);
755
     *   PadraoGatilho padrao = detector.processarCandleTempoReal(candles);
756
     *
757
     *   if (padrao != null) {
758
     *       // padrão completo encontrado
759
     *   }
760
     */
761
    public PadraoGatilho processarCandleTempoReal(List<Candle> candles) {
762
        int n = candles.size();
763
        if (n < 4) {
764
            return null;
765
        }
760 blopes 766
 
764 blopes 767
        while (idxProximaAnaliseTempoReal < n - 3) {
768
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal);
760 blopes 769
 
764 blopes 770
            if (resultado == null) {
771
                idxProximaAnaliseTempoReal++;
772
                continue;
773
            }
760 blopes 774
 
764 blopes 775
            int proximoInicio = resultado.lastIndex + 1;
776
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
777
                proximoInicio = idxProximaAnaliseTempoReal + 1;
778
            }
779
            idxProximaAnaliseTempoReal = proximoInicio;
760 blopes 780
 
764 blopes 781
            if (resultado.padrao != null) {
782
                return resultado.padrao;
783
            }
784
        }
760 blopes 785
 
764 blopes 786
        return null;
787
    }
760 blopes 788
 
764 blopes 789
}