Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

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