Subversion Repositories Integrator Subversion

Rev

Rev 762 | Rev 767 | 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
 
764 blopes 198
            // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
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
 
764 blopes 239
            // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
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
        // =====================================================
270
        // A PARTIR DAQUI (G2, G3, G4) NÃO VERIFICAMOS MAIS OUTSIDE
271
        // =====================================================
760 blopes 272
 
764 blopes 273
        // 2) Encontrar G2 (retorno comprador à região do GR)
274
        int idxPrimeiroComprador = -1;
275
        for (int i = idxG1 + 1; i < n; i++) {
276
            Candle c = candles.get(i);
760 blopes 277
 
764 blopes 278
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
279
                continue;
280
            }
281
            if (c.isCandleComprador()) {
282
                idxPrimeiroComprador = i;
283
                break;
284
            } else {
285
                lastIndex = i;
286
            }
287
        }
760 blopes 288
 
764 blopes 289
        if (idxPrimeiroComprador == -1) {
290
            return new ResultadoPadrao(null, lastIndex);
291
        }
760 blopes 292
 
764 blopes 293
        boolean rompeuTopoRef = false;
294
        Candle ultimoCompradorTrend = null;
295
        int idxUltimoCompradorTrend = -1;
760 blopes 296
 
764 blopes 297
        for (int i = idxPrimeiroComprador; i < n; i++) {
298
            Candle c = candles.get(i);
760 blopes 299
 
764 blopes 300
            if (!c.isCandleComprador()) {
301
                break;
302
            }
760 blopes 303
 
764 blopes 304
            ultimoCompradorTrend = c;
305
            idxUltimoCompradorTrend = i;
306
            lastIndex = i;
760 blopes 307
 
764 blopes 308
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
309
                rompeuTopoRef = true;
310
                break;
311
            }
312
        }
760 blopes 313
 
764 blopes 314
        if (rompeuTopoRef || idxUltimoCompradorTrend == -1) {
315
            return new ResultadoPadrao(null, lastIndex);
316
        }
760 blopes 317
 
764 blopes 318
        boolean fechamentoDentroRegiaoRef =
319
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMinima()) &&
320
                BigDecimalUtils.ehMenorOuIgualQue(ultimoCompradorTrend.getFechamento(), ref.getMaxima());
760 blopes 321
 
764 blopes 322
        if (!fechamentoDentroRegiaoRef) {
323
            return new ResultadoPadrao(null, lastIndex);
324
        }
760 blopes 325
 
764 blopes 326
        Candle g2 = ultimoCompradorTrend;
327
        int idxG2 = idxUltimoCompradorTrend;
328
        log(String.format("RefC[%d], G1[%d] => G2 (comprador) em [%d]", idxRef, idxG1, idxG2));
760 blopes 329
 
764 blopes 330
        // 3) Encontrar G3 (vendedor rompendo fundo de G2, topo <= topo do GR)
331
        int idxPrimeiroVendedorAposG2 = -1;
332
        for (int i = idxG2 + 1; i < n; i++) {
333
            Candle c = candles.get(i);
760 blopes 334
 
764 blopes 335
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
336
                continue;
337
            }
760 blopes 338
 
764 blopes 339
            if (c.isCandleVendedor()) {
340
                idxPrimeiroVendedorAposG2 = i;
341
                break;
342
            } else {
343
                lastIndex = i;
344
                // pela sua regra, o próximo após G2 deveria ser vendedor
345
                // => padrão chegou até G2, mas não evoluiu corretamente para G3
346
                return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
347
            }
348
        }
760 blopes 349
 
764 blopes 350
        if (idxPrimeiroVendedorAposG2 == -1) {
351
            // padrão com GR, G1, G2, mas sem sequência adequada para G3
352
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
353
        }
760 blopes 354
 
764 blopes 355
        int idxG3 = -1;
356
        Candle g3 = null;
760 blopes 357
 
764 blopes 358
        for (int i = idxPrimeiroVendedorAposG2; i < n; i++) {
359
            Candle c = candles.get(i);
360
            if (!c.isCandleVendedor()) {
361
                lastIndex = i - 1;
362
                break;
363
            }
760 blopes 364
 
764 blopes 365
            lastIndex = i;
760 blopes 366
 
764 blopes 367
            boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
368
            boolean topoMenorOuIgualRef = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), ref.getMaxima());
760 blopes 369
 
764 blopes 370
            if (rompeFundoG2 && topoMenorOuIgualRef) {
371
                idxG3 = i;
372
                g3 = c;
373
                break;
374
            }
375
        }
760 blopes 376
 
764 blopes 377
        if (idxG3 == -1 || g3 == null) {
378
            // Chegou até G2 mas não encontrou G3
379
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
380
        }
760 blopes 381
 
764 blopes 382
        log(String.format("RefC[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d]",
383
                idxRef, idxG1, idxG2, idxG3));
760 blopes 384
 
764 blopes 385
        // 4) G4 – próximo candle rompe topo do GR
386
        Candle g4 = null;
387
        int proxIdx = idxG3 + 1;
388
        if (proxIdx < n) {
389
            Candle c = candles.get(proxIdx);
390
            lastIndex = proxIdx;
760 blopes 391
 
764 blopes 392
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
393
                g4 = c;
394
                log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
395
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
396
            } else {
397
                log(String.format("RefC[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
398
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
399
            }
400
        }
760 blopes 401
 
764 blopes 402
        PadraoGatilho padrao = new PadraoGatilho();
403
        padrao.setReferencia(ref);
404
        padrao.setGatilho1(g1);
405
        padrao.setGatilho2(g2);
406
        padrao.setGatilho3(g3);
407
        padrao.setGatilho4(g4);
760 blopes 408
 
764 blopes 409
        return new ResultadoPadrao(padrao, lastIndex);
410
    }
760 blopes 411
 
764 blopes 412
    // =====================================================================
413
    // CASO 2 – GR VENDEDOR (candle B)
414
    // =====================================================================
760 blopes 415
 
764 blopes 416
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxRef) {
417
        int n = candles.size();
418
        Candle ref = candles.get(idxRef); // candidato a GR vendedor
419
        int lastIndex = idxRef;
760 blopes 420
 
764 blopes 421
        // 0) Validar se idxRef pode ser GR vendedor (B)
422
        if (!ref.isCandleVendedor()) {
423
            return new ResultadoPadrao(null, idxRef);
424
        }
760 blopes 425
 
764 blopes 426
        // 0.1 – início da pernada vendedora que termina em B
427
        int inicioTrendVenda = idxRef;
428
        for (int i = idxRef - 1; i >= 0; i--) {
429
            Candle c = candles.get(i);
430
            if (!c.isCandleVendedor()) {
431
                break;
432
            }
433
            inicioTrendVenda = i;
434
        }
760 blopes 435
 
764 blopes 436
        // 0.2 – primeiro candle direcional após B
437
        int idxPrimeiroDirecionalAposRef = -1;
438
        for (int i = idxRef + 1; i < n; i++) {
439
            Candle c = candles.get(i);
440
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
441
                continue;
442
            }
760 blopes 443
 
764 blopes 444
            // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
445
            if (isOutsideReferencia(ref, c)) {
446
                lastIndex = i;
447
                log(String.format(
448
                        "RefV[%d]: OUTSIDE em [%d] antes da mudança de tendência. Padrão descartado.",
449
                        idxRef, i));
450
                return new ResultadoPadrao(null, lastIndex);
451
            }
760 blopes 452
 
764 blopes 453
            idxPrimeiroDirecionalAposRef = i;
454
            break;
455
        }
760 blopes 456
 
764 blopes 457
        if (idxPrimeiroDirecionalAposRef == -1) {
458
            return new ResultadoPadrao(null, lastIndex);
459
        }
760 blopes 460
 
764 blopes 461
        Candle primeiroDirecional = candles.get(idxPrimeiroDirecionalAposRef);
462
        if (!primeiroDirecional.isCandleComprador()) {
463
            // primeiro direcional após B ainda é vendedor
464
            return new ResultadoPadrao(null, idxRef);
465
        }
760 blopes 466
 
764 blopes 467
        // 0.3 – B tem o MENOR fundo da pernada vendedora
468
        BigDecimal fundoRef = ref.getMinima();
469
        for (int i = inicioTrendVenda; i <= idxRef; i++) {
470
            Candle c = candles.get(i);
471
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), fundoRef)) {
472
                return new ResultadoPadrao(null, idxRef);
473
            }
474
        }
760 blopes 475
 
764 blopes 476
        int idxPrimeiroComprador = idxPrimeiroDirecionalAposRef;
760 blopes 477
 
764 blopes 478
        // 1) Encontrar G1 (comprador que rompe topo do GR)
479
        int idxG1 = -1;
480
        Candle g1 = null;
760 blopes 481
 
764 blopes 482
        for (int i = idxPrimeiroComprador; i < n; i++) {
483
            Candle c = candles.get(i);
760 blopes 484
 
764 blopes 485
            // 🔹 OUTSIDE em relação ao GR (APENAS ATÉ G1)
486
            if (isOutsideReferencia(ref, c)) {
487
                lastIndex = i;
488
                log(String.format(
489
                        "RefV[%d]: OUTSIDE em [%d] durante busca de G1. Padrão descartado.",
490
                        idxRef, i));
491
                return new ResultadoPadrao(null, lastIndex);
492
            }
760 blopes 493
 
764 blopes 494
            if (!c.isCandleComprador()) {
495
                lastIndex = i - 1;
496
                return new ResultadoPadrao(null, lastIndex);
497
            }
760 blopes 498
 
764 blopes 499
            lastIndex = i;
760 blopes 500
 
764 blopes 501
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), ref.getMaxima())) {
502
                idxG1 = i;
503
                g1 = c;
504
                break;
505
            }
506
        }
760 blopes 507
 
764 blopes 508
        if (idxG1 == -1) {
509
            return new ResultadoPadrao(null, lastIndex);
510
        }
760 blopes 511
 
764 blopes 512
        log(String.format("RefV[%d] (GR) => G1 (comprador) em [%d]", idxRef, idxG1));
760 blopes 513
 
764 blopes 514
        // =====================================================
515
        // A PARTIR DAQUI (G2, G3, G4) NÃO VERIFICAMOS MAIS OUTSIDE
516
        // =====================================================
760 blopes 517
 
764 blopes 518
        // 2) Encontrar G2 (retorno vendedor à região do GR)
519
        int idxPrimeiroVendedor = -1;
520
        for (int i = idxG1 + 1; i < n; i++) {
521
            Candle c = candles.get(i);
760 blopes 522
 
764 blopes 523
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
524
                continue;
525
            }
760 blopes 526
 
764 blopes 527
            if (c.isCandleVendedor()) {
528
                idxPrimeiroVendedor = i;
529
                break;
530
            } else {
531
                lastIndex = i;
532
            }
533
        }
760 blopes 534
 
764 blopes 535
        if (idxPrimeiroVendedor == -1) {
536
            return new ResultadoPadrao(null, lastIndex);
537
        }
760 blopes 538
 
764 blopes 539
        boolean rompeuFundoRef = false;
540
        Candle ultimoVendedorTrend = null;
541
        int idxUltimoVendedorTrend = -1;
760 blopes 542
 
764 blopes 543
        for (int i = idxPrimeiroVendedor; i < n; i++) {
544
            Candle c = candles.get(i);
760 blopes 545
 
764 blopes 546
            if (!c.isCandleVendedor()) {
547
                break;
548
            }
760 blopes 549
 
764 blopes 550
            ultimoVendedorTrend = c;
551
            idxUltimoVendedorTrend = i;
552
            lastIndex = i;
760 blopes 553
 
764 blopes 554
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
555
                rompeuFundoRef = true;
556
                break;
557
            }
558
        }
760 blopes 559
 
764 blopes 560
        if (rompeuFundoRef || idxUltimoVendedorTrend == -1) {
561
            return new ResultadoPadrao(null, lastIndex);
562
        }
760 blopes 563
 
764 blopes 564
        boolean fechamentoDentroRegiaoRef =
565
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMinima()) &&
566
                BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedorTrend.getFechamento(), ref.getMaxima());
760 blopes 567
 
764 blopes 568
        if (!fechamentoDentroRegiaoRef) {
569
            return new ResultadoPadrao(null, lastIndex);
570
        }
760 blopes 571
 
764 blopes 572
        Candle g2 = ultimoVendedorTrend;
573
        int idxG2 = idxUltimoVendedorTrend;
574
        log(String.format("RefV[%d], G1[%d] => G2 (vendedor) em [%d]", idxRef, idxG1, idxG2));
760 blopes 575
 
764 blopes 576
        // 3) Encontrar G3 (comprador rompendo topo de G2, fundo >= fundo do GR)
577
        int idxPrimeiroCompradorAposG2 = -1;
578
        for (int i = idxG2 + 1; i < n; i++) {
579
            Candle c = candles.get(i);
760 blopes 580
 
764 blopes 581
            if (!c.isCandleComprador() && !c.isCandleVendedor()) {
582
                continue;
583
            }
760 blopes 584
 
764 blopes 585
            if (c.isCandleComprador()) {
586
                idxPrimeiroCompradorAposG2 = i;
587
                break;
588
            } else {
589
                lastIndex = i;
590
                // esperado comprador; padrão segue apenas até G2
591
                return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
592
            }
593
        }
760 blopes 594
 
764 blopes 595
        if (idxPrimeiroCompradorAposG2 == -1) {
596
            // não teve sequência adequada após G2
597
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
598
        }
760 blopes 599
 
764 blopes 600
        int idxG3 = -1;
601
        Candle g3 = null;
760 blopes 602
 
764 blopes 603
        for (int i = idxPrimeiroCompradorAposG2; i < n; i++) {
604
            Candle c = candles.get(i);
605
            if (!c.isCandleComprador()) {
606
                lastIndex = i - 1;
607
                break;
608
            }
760 blopes 609
 
764 blopes 610
            lastIndex = i;
760 blopes 611
 
764 blopes 612
            boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
613
            boolean fundoMaiorOuIgualRef = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), ref.getMinima());
760 blopes 614
 
764 blopes 615
            if (rompeTopoG2 && fundoMaiorOuIgualRef) {
616
                idxG3 = i;
617
                g3 = c;
618
                break;
619
            }
620
        }
760 blopes 621
 
764 blopes 622
        if (idxG3 == -1 || g3 == null) {
623
            // padrão só até G2
624
            return criarResultadoParcialComG2(ref, g1, g2, lastIndex);
625
        }
760 blopes 626
 
764 blopes 627
        log(String.format("RefV[%d], G1[%d], G2[%d] => G3 (comprador) em [%d]",
628
                idxRef, idxG1, idxG2, idxG3));
760 blopes 629
 
764 blopes 630
        // 4) G4 – próximo candle rompe fundo do GR
631
        Candle g4 = null;
632
        int proxIdx = idxG3 + 1;
633
        if (proxIdx < n) {
634
            Candle c = candles.get(proxIdx);
635
            lastIndex = proxIdx;
760 blopes 636
 
764 blopes 637
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), ref.getMinima())) {
638
                g4 = c;
639
                log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d] => G4 em [%d]",
640
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
641
            } else {
642
                log(String.format("RefV[%d], G1[%d], G2[%d], G3[%d]: próximo candle [%d] não é G4.",
643
                        idxRef, idxG1, idxG2, idxG3, proxIdx));
644
            }
645
        }
760 blopes 646
 
764 blopes 647
        PadraoGatilho padrao = new PadraoGatilho();
648
        padrao.setReferencia(ref);
649
        padrao.setGatilho1(g1);
650
        padrao.setGatilho2(g2);
651
        padrao.setGatilho3(g3);
652
        padrao.setGatilho4(g4);
760 blopes 653
 
764 blopes 654
        return new ResultadoPadrao(padrao, lastIndex);
655
    }
760 blopes 656
 
764 blopes 657
    // =====================================================================
658
    // HELPER – Candle outside em relação ao GR
659
    // =====================================================================
760 blopes 660
 
764 blopes 661
    /**
662
     * Outside em relação ao GR:
663
     *  - atual.max > GR.max e atual.min < GR.min
664
     */
665
    private boolean isOutsideReferencia(Candle referencia, Candle atual) {
666
        if (referencia == null || atual == null) return false;
667
        return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), referencia.getMaxima())
668
                && BigDecimalUtils.ehMenorQue(atual.getMinima(), referencia.getMinima());
669
    }
760 blopes 670
 
764 blopes 671
    // =====================================================================
672
    // MODO TEMPO REAL (candle a candle)
673
    // =====================================================================
760 blopes 674
 
764 blopes 675
    /**
676
     * Reinicia o estado do modo tempo real.
677
     */
678
    public void resetTempoReal() {
679
        this.idxProximaAnaliseTempoReal = 0;
680
    }
760 blopes 681
 
764 blopes 682
    /**
683
     * Deve ser chamado SEMPRE que um novo candle for adicionado à lista.
684
     *
685
     * Exemplo:
686
     *   candles.add(novoCandle);
687
     *   PadraoGatilho padrao = detector.processarCandleTempoReal(candles);
688
     *
689
     *   if (padrao != null) {
690
     *       // padrão completo encontrado
691
     *   }
692
     */
693
    public PadraoGatilho processarCandleTempoReal(List<Candle> candles) {
694
        int n = candles.size();
695
        if (n < 4) {
696
            return null;
697
        }
760 blopes 698
 
764 blopes 699
        while (idxProximaAnaliseTempoReal < n - 3) {
700
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal);
760 blopes 701
 
764 blopes 702
            if (resultado == null) {
703
                idxProximaAnaliseTempoReal++;
704
                continue;
705
            }
760 blopes 706
 
764 blopes 707
            int proximoInicio = resultado.lastIndex + 1;
708
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
709
                proximoInicio = idxProximaAnaliseTempoReal + 1;
710
            }
711
            idxProximaAnaliseTempoReal = proximoInicio;
760 blopes 712
 
764 blopes 713
            if (resultado.padrao != null) {
714
                return resultado.padrao;
715
            }
716
        }
760 blopes 717
 
764 blopes 718
        return null;
719
    }
760 blopes 720
 
764 blopes 721
}