Subversion Repositories Integrator Subversion

Rev

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