Subversion Repositories Integrator Subversion

Rev

Rev 771 | Rev 774 | 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
 
3
import java.util.ArrayList;
764 blopes 4
import java.util.List;
760 blopes 5
 
764 blopes 6
import br.com.sl.domain.model.Candle;
7
import br.com.sl.domain.util.BigDecimalUtils;
8
 
760 blopes 9
/**
773 blopes 10
 * Detector de padrões de gatilhos (GR, G1, G2, G3)
764 blopes 11
 * trabalhando tanto em modo backtest quanto em modo tempo real.
760 blopes 12
 *
773 blopes 13
 * IMPORTANTE:
14
 *  - Este detector FINALIZA o padrão assim que identifica o G3.
15
 *  - O G4 será identificado apenas na camada de sinais de trade, fora desta classe.
760 blopes 16
 *
773 blopes 17
 * REGRAS IMPLEMENTADAS (com GR DINÂMICO invertido)
18
 * ================================================
19
 *
771 blopes 20
 * COMPRADOR
21
 * ---------
773 blopes 22
 * Ponto de partida: um candle COMPRADOR é candidato à referência.
760 blopes 23
 *
773 blopes 24
 * G1:
25
 *   - Enquanto não houver G1:
26
 *       * O candidato à referência pode ser atualizado dinamicamente:
27
 *         QUALQUER candle posterior, antes do G1, que fizer uma MÁXIMA
28
 *         MAIOR que o candidato atual passa a ser o novo candidato.
29
 *       * Se algum candle VENDEDOR subsequente ROMPER o FUNDO
30
 *         do candle candidato à referência (mínima < mínima do candidato),
31
 *         esse candle será o gatilho tipo 1 [G1] e o candidato vigente
32
 *         torna-se o candle referência [GR].
760 blopes 33
 *
773 blopes 34
 * G2:
35
 *   - Após o G1 (com GR definido):
36
 *       * Assim que tiver nova tendência COMPRADORA:
37
 *           - O último candle dessa sequência compradora deve ter o FECHAMENTO
38
 *             dentro da região total (mínima..máxima) do GR.
39
 *           - Se mudar para vendedor sem nenhum candle fechar dentro da região
40
 *             do GR → descarta o padrão.
41
 *       * Se qualquer candle (após o GR) romper o TOPO do GR
42
 *         (máxima > máxima GR) → descarta o padrão.
43
 *       * Se válido, esse último comprador é o gatilho tipo 2 [G2].
760 blopes 44
 *
773 blopes 45
 * G3:
46
 *   - Após o G2:
47
 *       * O próximo direcional deve ser VENDEDOR; se for comprador,
48
 *         o padrão vai apenas até G2 (parcial).
49
 *       * Numa sequência vendedora:
50
 *           - Se algum candle VENDEDOR posterior ROMPER o FUNDO do G2
51
 *             (mínima < mínima G2) e tiver TOPO <= topo do GR
52
 *             (máxima <= máxima GR), sem mudança de tendência
53
 *             (sem compradores no meio), será o gatilho tipo 3 [G3].
760 blopes 54
 *
773 blopes 55
 * Regra global comprador:
56
 *   - Se algum candle posterior ao GR ROMPER o TOPO do GR
57
 *     (máxima > máxima GR), desconsiderar o padrão.
764 blopes 58
 *
773 blopes 59
 * -----------------------------------------------------------
764 blopes 60
 *
771 blopes 61
 * VENDEDOR
62
 * --------
773 blopes 63
 * Ponto de partida: um candle VENDEDOR é candidato à referência.
764 blopes 64
 *
773 blopes 65
 * G1:
66
 *   - Enquanto não houver G1:
67
 *       * O candidato à referência pode ser atualizado dinamicamente:
68
 *         QUALQUER candle posterior, antes do G1, que fizer uma MÍNIMA
69
 *         MENOR que a do candidato atual passa a ser o novo candidato.
70
 *       * Se algum candle COMPRADOR subsequente ROMPER o TOPO
71
 *         do candidato (máxima > máxima do candidato), esse candle
72
 *         será o gatilho tipo 1 [G1] e o candidato vigente torna-se o GR.
764 blopes 73
 *
773 blopes 74
 * Regra global vendedor:
75
 *   - Se algum candle posterior ao GR ROMPER o FUNDO do GR
76
 *     (mínima < mínima GR), desconsiderar o padrão.
764 blopes 77
 *
773 blopes 78
 * G2:
79
 *   - Após o G1 (com GR definido):
80
 *       * Assim que tiver nova tendência VENDEDORA:
81
 *           - O último candle dessa sequência vendedora deve ter FECHAMENTO
82
 *             dentro da região total (mínima..máxima) do GR.
83
 *           - Se mudar para comprador sem nenhum candle fechar dentro
84
 *             da região do GR → descarta o padrão.
85
 *       * Se romper o FUNDO do GR em qualquer momento após o GR
86
 *         → descarta o padrão.
87
 *       * Se válido, esse último vendedor é o gatilho tipo 2 [G2].
771 blopes 88
 *
773 blopes 89
 * G3:
90
 *   - Após o G2:
91
 *       * O próximo direcional deve ser COMPRADOR; se for vendedor,
92
 *         o padrão vai até G2 (parcial).
93
 *       * Numa sequência compradora:
94
 *           - Se algum COMPRADOR posterior ROMPER o TOPO de G2
95
 *             (máxima > máxima G2) e tiver FUNDO >= fundo do GR
96
 *             (mínima >= mínima GR), sem mudança de tendência
97
 *             (sem vendedores no meio), será o gatilho tipo 3 [G3].
771 blopes 98
 *
773 blopes 99
 * Regra global adicional (texto original repetia):
100
 *   - Mantida a regra principal: vendedor é invalidado
101
 *     quando rompem o FUNDO do GR.
771 blopes 102
 *
773 blopes 103
 * -----------------------------------------------------------
104
 *
771 blopes 105
 * OUTSIDE (ambos os lados):
773 blopes 106
 *   - Candle cuja máxima > máxima do candle anterior
107
 *     E mínima < mínima do candle anterior.
108
 *   - Se houver um OUTSIDE antes de identificar o G3:
109
 *       * Desconsidera o padrão atual (GR+G1+G2) e recomeça
110
 *         a busca a partir do GR (na prática, aborta o padrão).
111
 *
112
 * OBS:
113
 *   - Os gatilhos devem seguir ORDEM cronológica:
114
 *     G1 -> G2 -> G3, sempre em candles diferentes.
760 blopes 115
 */
116
public class DetectorGatilhos {
117
 
764 blopes 118
    private final boolean logAtivo;
119
    private int idxProximaAnaliseTempoReal = 0;
760 blopes 120
 
764 blopes 121
    public DetectorGatilhos() {
122
        this(false);
123
    }
760 blopes 124
 
764 blopes 125
    public DetectorGatilhos(boolean logAtivo) {
126
        this.logAtivo = logAtivo;
127
    }
760 blopes 128
 
764 blopes 129
    private void log(String msg) {
130
        if (logAtivo) {
131
            System.out.println(msg);
132
        }
133
    }
760 blopes 134
 
773 blopes 135
    // -----------------------------------------------------------------
136
    // Estrutura interna de retorno
137
    // -----------------------------------------------------------------
764 blopes 138
    private static class ResultadoPadrao {
771 blopes 139
        PadraoGatilho padrao;
140
        int lastIndex;
773 blopes 141
 
764 blopes 142
        ResultadoPadrao(PadraoGatilho padrao, int lastIndex) {
143
            this.padrao = padrao;
144
            this.lastIndex = lastIndex;
145
        }
146
    }
760 blopes 147
 
764 blopes 148
    private ResultadoPadrao criarResultadoParcialComG2(Candle ref,
149
                                                       Candle g1,
150
                                                       Candle g2,
151
                                                       int lastIndex) {
152
        PadraoGatilho padrao = new PadraoGatilho();
153
        padrao.setReferencia(ref);
154
        padrao.setGatilho1(g1);
155
        padrao.setGatilho2(g2);
156
        padrao.setGatilho3(null);
773 blopes 157
        padrao.setGatilho4(null); // G4 só na camada de trade
764 blopes 158
        return new ResultadoPadrao(padrao, lastIndex);
159
    }
760 blopes 160
 
764 blopes 161
    // =====================================================================
771 blopes 162
    // API PRINCIPAL – BACKTEST
764 blopes 163
    // =====================================================================
760 blopes 164
 
764 blopes 165
    public List<PadraoGatilho> identificarPadroes(List<Candle> candles) {
166
        List<PadraoGatilho> padroes = new ArrayList<>();
167
        int n = candles.size();
771 blopes 168
        if (n < 4) return padroes;
760 blopes 169
 
764 blopes 170
        int idxRef = 0;
171
        while (idxRef < n - 3) {
172
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef);
760 blopes 173
 
764 blopes 174
            if (resultado == null) {
175
                idxRef++;
176
                continue;
177
            }
760 blopes 178
 
764 blopes 179
            if (resultado.padrao != null) {
180
                padroes.add(resultado.padrao);
181
            }
760 blopes 182
 
764 blopes 183
            idxRef = Math.max(resultado.lastIndex + 1, idxRef + 1);
184
        }
760 blopes 185
 
764 blopes 186
        return padroes;
187
    }
760 blopes 188
 
764 blopes 189
    private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) {
190
        Candle ref = candles.get(idxRef);
191
        if (ref.isCandleComprador()) {
192
            return detectarPadraoComprador(candles, idxRef);
193
        } else if (ref.isCandleVendedor()) {
194
            return detectarPadraoVendedor(candles, idxRef);
195
        } else {
196
            return new ResultadoPadrao(null, idxRef);
197
        }
198
    }
760 blopes 199
 
764 blopes 200
    // =====================================================================
771 blopes 201
    // HELPERS
764 blopes 202
    // =====================================================================
760 blopes 203
 
771 blopes 204
    private boolean isInside(List<Candle> candles, int idx) {
205
        if (idx <= 0) return false;
206
        Candle atual = candles.get(idx);
207
        Candle anterior = candles.get(idx - 1);
208
        return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima())
209
                && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima());
210
    }
760 blopes 211
 
771 blopes 212
    private boolean isOutside(List<Candle> candles, int idx) {
213
        if (idx <= 0) return false;
214
        Candle atual = candles.get(idx);
215
        Candle anterior = candles.get(idx - 1);
216
        return BigDecimalUtils.ehMaiorQue(atual.getMaxima(), anterior.getMaxima())
217
                && BigDecimalUtils.ehMenorQue(atual.getMinima(), anterior.getMinima());
218
    }
760 blopes 219
 
773 blopes 220
    private boolean isDirecional(Candle c) {
221
        return c.isCandleComprador() || c.isCandleVendedor();
222
    }
223
 
771 blopes 224
    // =====================================================================
773 blopes 225
    // CASO COMPRADOR – GR DINÂMICO PELA MÁXIMA
771 blopes 226
    // =====================================================================
227
 
773 blopes 228
    private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxInicio) {
771 blopes 229
        int n = candles.size();
773 blopes 230
        int lastIndex = idxInicio;
760 blopes 231
 
773 blopes 232
        Candle candidatoRef = candles.get(idxInicio);
233
        if (!candidatoRef.isCandleComprador()) {
234
            return new ResultadoPadrao(null, idxInicio);
764 blopes 235
        }
760 blopes 236
 
773 blopes 237
        int idxCandidatoRef = idxInicio;
760 blopes 238
 
773 blopes 239
        // --------------------
240
        // 1) Buscar G1 (vendedor rompe fundo do candidato)
241
        // --------------------
771 blopes 242
        Candle g1 = null;
764 blopes 243
        int idxG1 = -1;
773 blopes 244
        boolean temGR = false;
245
        Candle gr = null;
246
        int idxGR = -1;
247
 
248
        for (int i = idxInicio + 1; i < n; i++) {
764 blopes 249
            Candle c = candles.get(i);
773 blopes 250
            if (isInside(candles, i) || !isDirecional(c))
771 blopes 251
                continue;
760 blopes 252
 
773 blopes 253
            // Outside antes de G3 (aqui ainda antes de G1)
254
            if (isOutside(candles, i)) {
255
                lastIndex = i;
256
                log(String.format("Comprador: OUTSIDE em [%d] antes de G1. Abortando padrão.", i));
764 blopes 257
                return new ResultadoPadrao(null, lastIndex);
258
            }
760 blopes 259
 
773 blopes 260
            // G1: vendedor rompe fundo do candidato
261
            if (c.isCandleVendedor()
262
                    && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
771 blopes 263
                g1 = c;
764 blopes 264
                idxG1 = i;
773 blopes 265
                gr = candidatoRef;
266
                idxGR = idxCandidatoRef;
267
                temGR = true;
268
                lastIndex = i;
764 blopes 269
                break;
270
            }
773 blopes 271
 
272
            // GR DINÂMICO (compra): atualiza pelo TOPO mais alto antes de G1
273
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
274
                candidatoRef = c;
275
                idxCandidatoRef = i;
276
                lastIndex = i;
277
                continue;
278
            }
279
 
280
            lastIndex = i;
764 blopes 281
        }
760 blopes 282
 
773 blopes 283
        if (g1 == null || !temGR) {
284
            log(String.format("Comprador: não foi possível formar G1 a partir de idx %d.", idxInicio));
285
            return new ResultadoPadrao(null, lastIndex);
286
        }
760 blopes 287
 
773 blopes 288
        log(String.format("GR COMPRADOR em [%d], G1 (vendedor) em [%d].", idxGR, idxG1));
760 blopes 289
 
773 blopes 290
        // --------------------
291
        // 2) Buscar G2 (tendência compradora com fechamento dentro do GR)
292
        // --------------------
771 blopes 293
        Candle g2 = null;
294
        int idxG2 = -1;
295
 
764 blopes 296
        int idxPrimeiroComprador = -1;
297
        for (int i = idxG1 + 1; i < n; i++) {
298
            Candle c = candles.get(i);
773 blopes 299
            if (isInside(candles, i) || !isDirecional(c))
771 blopes 300
                continue;
760 blopes 301
 
773 blopes 302
            // Outside antes de G3
303
            if (isOutside(candles, i)) {
304
                lastIndex = idxGR;
305
                log(String.format("GR[%d]: OUTSIDE em [%d] antes da perna compradora de G2.", idxGR, i));
306
                return new ResultadoPadrao(null, lastIndex);
307
            }
308
 
309
            // Regra global: romper topo do GR invalida
310
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
311
                lastIndex = i;
312
                log(String.format("GR[%d]: candle[%d] rompeu topo do GR antes de G2.", idxGR, i));
313
                return new ResultadoPadrao(null, lastIndex);
314
            }
315
 
764 blopes 316
            if (c.isCandleComprador()) {
317
                idxPrimeiroComprador = i;
318
                break;
319
            } else {
320
                lastIndex = i;
321
            }
322
        }
760 blopes 323
 
773 blopes 324
        if (idxPrimeiroComprador == -1) {
325
            log(String.format("GR[%d], G1[%d]: não houve nova tendência compradora para G2.", idxGR, idxG1));
326
            return new ResultadoPadrao(null, lastIndex);
327
        }
760 blopes 328
 
771 blopes 329
        Candle ultimoComprador = null;
330
        int idxUltimoComprador = -1;
760 blopes 331
 
764 blopes 332
        for (int i = idxPrimeiroComprador; i < n; i++) {
333
            Candle c = candles.get(i);
773 blopes 334
            if (isInside(candles, i) || !isDirecional(c))
335
                continue;
760 blopes 336
 
773 blopes 337
            // Outside durante G2
771 blopes 338
            if (isOutside(candles, i)) {
773 blopes 339
                lastIndex = idxGR;
340
                log(String.format("GR[%d]: OUTSIDE em [%d] durante G2 (comprador).", idxGR, i));
341
                return new ResultadoPadrao(null, lastIndex);
342
            }
343
 
344
            // Regra global: romper topo do GR invalida
345
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
767 blopes 346
                lastIndex = i;
773 blopes 347
                log(String.format("GR[%d]: candle[%d] rompeu topo do GR durante G2.", idxGR, i));
767 blopes 348
                return new ResultadoPadrao(null, lastIndex);
349
            }
350
 
771 blopes 351
            if (c.isCandleComprador()) {
352
                ultimoComprador = c;
353
                idxUltimoComprador = i;
354
                lastIndex = i;
355
            } else if (c.isCandleVendedor()) {
356
                lastIndex = i - 1;
764 blopes 357
                break;
358
            }
359
        }
760 blopes 360
 
773 blopes 361
        if (ultimoComprador == null) {
362
            log(String.format("GR[%d], G1[%d]: não houve comprador válido para G2.", idxGR, idxG1));
363
            return new ResultadoPadrao(null, lastIndex);
364
        }
760 blopes 365
 
771 blopes 366
        boolean fechamentoDentroRegiaoGR =
367
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoComprador.getFechamento(), gr.getMinima()) &&
368
                BigDecimalUtils.ehMenorOuIgualQue(ultimoComprador.getFechamento(), gr.getMaxima());
760 blopes 369
 
771 blopes 370
        if (!fechamentoDentroRegiaoGR) {
773 blopes 371
            log(String.format(
372
                    "GR[%d], G1[%d]: último comprador[%d] não fechou dentro da região do GR (fech=%s, faixa=[%s,%s]).",
373
                    idxGR, idxG1, idxUltimoComprador,
374
                    ultimoComprador.getFechamento().toPlainString(),
375
                    gr.getMinima().toPlainString(),
376
                    gr.getMaxima().toPlainString()
377
            ));
764 blopes 378
            return new ResultadoPadrao(null, lastIndex);
379
        }
760 blopes 380
 
771 blopes 381
        g2 = ultimoComprador;
382
        idxG2 = idxUltimoComprador;
773 blopes 383
        log(String.format("GR[%d], G1[%d] => G2 (comprador) em [%d].", idxGR, idxG1, idxG2));
760 blopes 384
 
773 blopes 385
        // --------------------
386
        // 3) Buscar G3 (vendedor rompe fundo de G2 com topo <= topo GR)
387
        // --------------------
771 blopes 388
        Candle g3 = null;
389
        int idxG3 = -1;
390
 
764 blopes 391
        int idxPrimeiroVendedorAposG2 = -1;
392
        for (int i = idxG2 + 1; i < n; i++) {
393
            Candle c = candles.get(i);
773 blopes 394
            if (isInside(candles, i) || !isDirecional(c))
764 blopes 395
                continue;
760 blopes 396
 
773 blopes 397
            if (isOutside(candles, i)) {
398
                lastIndex = idxGR;
399
                log(String.format("GR[%d]: OUTSIDE em [%d] antes da sequência vendedora de G3.", idxGR, i));
400
                return new ResultadoPadrao(null, lastIndex);
401
            }
402
 
403
            // Regra global: romper topo do GR invalida
404
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
405
                lastIndex = i;
406
                log(String.format("GR[%d]: candle[%d] rompeu topo do GR antes da perna vendedora de G3.", idxGR, i));
407
                return new ResultadoPadrao(null, lastIndex);
408
            }
409
 
764 blopes 410
            if (c.isCandleVendedor()) {
411
                idxPrimeiroVendedorAposG2 = i;
412
                break;
413
            } else {
773 blopes 414
                // primeiro direcional após G2 é comprador → padrão só até G2
764 blopes 415
                lastIndex = i;
773 blopes 416
                log(String.format("GR[%d], G1[%d], G2[%d]: primeiro direcional após G2 é comprador (idx %d).",
417
                        idxGR, idxG1, idxG2, i));
771 blopes 418
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
764 blopes 419
            }
420
        }
773 blopes 421
 
422
        if (idxPrimeiroVendedorAposG2 == -1) {
423
            log(String.format("GR[%d], G1[%d], G2[%d]: não houve vendedor após G2.", idxGR, idxG1, idxG2));
771 blopes 424
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
773 blopes 425
        }
760 blopes 426
 
764 blopes 427
        for (int i = idxPrimeiroVendedorAposG2; i < n; i++) {
428
            Candle c = candles.get(i);
773 blopes 429
            if (isInside(candles, i) || !isDirecional(c))
430
                continue;
767 blopes 431
 
771 blopes 432
            if (isOutside(candles, i)) {
773 blopes 433
                lastIndex = idxGR;
771 blopes 434
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (vendedor).", idxGR, i));
767 blopes 435
                return new ResultadoPadrao(null, lastIndex);
436
            }
437
 
773 blopes 438
            // Regra global: romper topo do GR invalida
771 blopes 439
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
440
                lastIndex = i;
773 blopes 441
                log(String.format("GR[%d]: candle[%d] rompeu topo do GR durante busca de G3.", idxGR, i));
771 blopes 442
                return new ResultadoPadrao(null, lastIndex);
443
            }
444
 
764 blopes 445
            if (!c.isCandleVendedor()) {
773 blopes 446
                // apareceu comprador antes de G3 → padrão só até G2
764 blopes 447
                lastIndex = i - 1;
773 blopes 448
                log(String.format("GR[%d], G1[%d], G2[%d]: comprador em [%d] antes de G3.", idxGR, idxG1, idxG2, i));
449
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
764 blopes 450
            }
760 blopes 451
 
764 blopes 452
            lastIndex = i;
453
            boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
771 blopes 454
            boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima());
455
            if (rompeFundoG2 && topoMenorOuIgualGR) {
456
                g3 = c;
764 blopes 457
                idxG3 = i;
458
                break;
459
            }
460
        }
760 blopes 461
 
773 blopes 462
        if (g3 == null) {
463
            log(String.format("GR[%d], G1[%d], G2[%d]: não houve G3, padrão parcial.", idxGR, idxG1, idxG2));
771 blopes 464
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
773 blopes 465
        }
760 blopes 466
 
773 blopes 467
        lastIndex = idxG3;
468
        log(String.format("GR[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d].", idxGR, idxG1, idxG2, idxG3));
760 blopes 469
 
764 blopes 470
        PadraoGatilho padrao = new PadraoGatilho();
771 blopes 471
        padrao.setReferencia(gr);
764 blopes 472
        padrao.setGatilho1(g1);
473
        padrao.setGatilho2(g2);
474
        padrao.setGatilho3(g3);
773 blopes 475
        padrao.setGatilho4(null);
760 blopes 476
 
764 blopes 477
        return new ResultadoPadrao(padrao, lastIndex);
478
    }
760 blopes 479
 
764 blopes 480
    // =====================================================================
773 blopes 481
    // CASO VENDEDOR – GR DINÂMICO PELA MÍNIMA
764 blopes 482
    // =====================================================================
760 blopes 483
 
773 blopes 484
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxInicio) {
764 blopes 485
        int n = candles.size();
773 blopes 486
        int lastIndex = idxInicio;
760 blopes 487
 
773 blopes 488
        Candle candidatoRef = candles.get(idxInicio);
489
        if (!candidatoRef.isCandleVendedor()) {
490
            return new ResultadoPadrao(null, idxInicio);
764 blopes 491
        }
760 blopes 492
 
773 blopes 493
        int idxCandidatoRef = idxInicio;
760 blopes 494
 
773 blopes 495
        // --------------------
496
        // 1) Buscar G1 (comprador rompe topo do candidato)
497
        // --------------------
771 blopes 498
        Candle g1 = null;
764 blopes 499
        int idxG1 = -1;
773 blopes 500
        boolean temGR = false;
501
        Candle gr = null;
502
        int idxGR = -1;
503
 
504
        for (int i = idxInicio + 1; i < n; i++) {
764 blopes 505
            Candle c = candles.get(i);
773 blopes 506
            if (isInside(candles, i) || !isDirecional(c))
771 blopes 507
                continue;
760 blopes 508
 
773 blopes 509
            // Outside antes de G1
510
            if (isOutside(candles, i)) {
511
                lastIndex = i;
512
                log(String.format("Vendedor: OUTSIDE em [%d] antes de G1. Abortando padrão.", i));
764 blopes 513
                return new ResultadoPadrao(null, lastIndex);
514
            }
760 blopes 515
 
773 blopes 516
            // G1: comprador rompe topo do candidato
517
            if (c.isCandleComprador()
518
                    && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
771 blopes 519
                g1 = c;
764 blopes 520
                idxG1 = i;
773 blopes 521
                gr = candidatoRef;
522
                idxGR = idxCandidatoRef;
523
                temGR = true;
524
                lastIndex = i;
764 blopes 525
                break;
526
            }
773 blopes 527
 
528
            // GR DINÂMICO (venda): atualiza pelo FUNDO mais baixo antes de G1
529
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
530
                candidatoRef = c;
531
                idxCandidatoRef = i;
532
                lastIndex = i;
533
                continue;
534
            }
535
 
536
            lastIndex = i;
764 blopes 537
        }
760 blopes 538
 
773 blopes 539
        if (g1 == null || !temGR) {
540
            log(String.format("Vendedor: não foi possível formar G1 a partir de idx %d.", idxInicio));
541
            return new ResultadoPadrao(null, lastIndex);
542
        }
760 blopes 543
 
773 blopes 544
        log(String.format("GR VENDEDOR em [%d], G1 (comprador) em [%d].", idxGR, idxG1));
545
 
546
        // --------------------
547
        // 2) Buscar G2 (tendência vendedora com fechamento dentro do GR)
548
        // --------------------
771 blopes 549
        Candle g2 = null;
550
        int idxG2 = -1;
760 blopes 551
 
764 blopes 552
        int idxPrimeiroVendedor = -1;
553
        for (int i = idxG1 + 1; i < n; i++) {
554
            Candle c = candles.get(i);
773 blopes 555
            if (isInside(candles, i) || !isDirecional(c))
764 blopes 556
                continue;
760 blopes 557
 
773 blopes 558
            // Outside antes de G3
559
            if (isOutside(candles, i)) {
560
                lastIndex = idxGR;
561
                log(String.format("GR[%d]: OUTSIDE em [%d] antes da perna vendedora de G2.", idxGR, i));
562
                return new ResultadoPadrao(null, lastIndex);
563
            }
564
 
565
            // Regra global vendedor: romper fundo do GR invalida
566
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
567
                lastIndex = i;
568
                log(String.format("GR[%d]: candle[%d] rompeu fundo do GR antes de G2.", idxGR, i));
569
                return new ResultadoPadrao(null, lastIndex);
570
            }
571
 
764 blopes 572
            if (c.isCandleVendedor()) {
573
                idxPrimeiroVendedor = i;
574
                break;
575
            } else {
576
                lastIndex = i;
577
            }
578
        }
760 blopes 579
 
773 blopes 580
        if (idxPrimeiroVendedor == -1) {
581
            log(String.format("GR[%d], G1[%d]: não houve nova tendência vendedora para G2.", idxGR, idxG1));
582
            return new ResultadoPadrao(null, lastIndex);
583
        }
584
 
771 blopes 585
        Candle ultimoVendedor = null;
586
        int idxUltimoVendedor = -1;
760 blopes 587
 
764 blopes 588
        for (int i = idxPrimeiroVendedor; i < n; i++) {
589
            Candle c = candles.get(i);
773 blopes 590
            if (isInside(candles, i) || !isDirecional(c))
591
                continue;
760 blopes 592
 
773 blopes 593
            // Outside durante G2
771 blopes 594
            if (isOutside(candles, i)) {
773 blopes 595
                lastIndex = idxGR;
596
                log(String.format("GR[%d]: OUTSIDE em [%d] durante G2 (vendedor).", idxGR, i));
597
                return new ResultadoPadrao(null, lastIndex);
598
            }
599
 
600
            // Regra global vendedor: romper fundo do GR invalida
601
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
767 blopes 602
                lastIndex = i;
773 blopes 603
                log(String.format("GR[%d]: candle[%d] rompeu fundo do GR durante G2.", idxGR, i));
767 blopes 604
                return new ResultadoPadrao(null, lastIndex);
605
            }
606
 
771 blopes 607
            if (c.isCandleVendedor()) {
608
                ultimoVendedor = c;
609
                idxUltimoVendedor = i;
610
                lastIndex = i;
611
            } else if (c.isCandleComprador()) {
612
                lastIndex = i - 1;
764 blopes 613
                break;
614
            }
615
        }
760 blopes 616
 
773 blopes 617
        if (ultimoVendedor == null) {
618
            log(String.format("GR[%d], G1[%d]: não houve vendedor válido para G2.", idxGR, idxG1));
764 blopes 619
            return new ResultadoPadrao(null, lastIndex);
773 blopes 620
        }
760 blopes 621
 
771 blopes 622
        boolean fechamentoDentroRegiaoGR =
623
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMinima()) &&
624
                BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMaxima());
760 blopes 625
 
773 blopes 626
        if (!fechamentoDentroRegiaoGR) {
627
            log(String.format(
628
                    "GR[%d], G1[%d]: último vendedor[%d] não fechou dentro da região do GR (fech=%s, faixa=[%s,%s]).",
629
                    idxGR, idxG1, idxUltimoVendedor,
630
                    ultimoVendedor.getFechamento().toPlainString(),
631
                    gr.getMinima().toPlainString(),
632
                    gr.getMaxima().toPlainString()
633
            ));
764 blopes 634
            return new ResultadoPadrao(null, lastIndex);
773 blopes 635
        }
760 blopes 636
 
771 blopes 637
        g2 = ultimoVendedor;
638
        idxG2 = idxUltimoVendedor;
773 blopes 639
        log(String.format("GR[%d], G1[%d] => G2 (vendedor) em [%d].", idxGR, idxG1, idxG2));
760 blopes 640
 
773 blopes 641
        // --------------------
642
        // 3) Buscar G3 (comprador rompe topo de G2 com fundo >= fundo GR)
643
        // --------------------
771 blopes 644
        Candle g3 = null;
645
        int idxG3 = -1;
646
 
764 blopes 647
        int idxPrimeiroCompradorAposG2 = -1;
648
        for (int i = idxG2 + 1; i < n; i++) {
649
            Candle c = candles.get(i);
773 blopes 650
            if (isInside(candles, i) || !isDirecional(c))
764 blopes 651
                continue;
760 blopes 652
 
773 blopes 653
            if (isOutside(candles, i)) {
654
                lastIndex = idxGR;
655
                log(String.format("GR[%d]: OUTSIDE em [%d] antes da sequência compradora de G3.", idxGR, i));
656
                return new ResultadoPadrao(null, lastIndex);
657
            }
658
 
659
            // Regra global vendedor: romper fundo do GR antes de G3 invalida
660
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
661
                lastIndex = i;
662
                log(String.format("GR[%d]: candle[%d] rompeu fundo do GR antes de G3.", idxGR, i));
663
                return new ResultadoPadrao(null, lastIndex);
664
            }
665
 
764 blopes 666
            if (c.isCandleComprador()) {
667
                idxPrimeiroCompradorAposG2 = i;
668
                break;
669
            } else {
773 blopes 670
                // primeiro direcional não é comprador → padrão até G2
764 blopes 671
                lastIndex = i;
773 blopes 672
                log(String.format("GR[%d], G1[%d], G2[%d]: primeiro direcional após G2 não é comprador (idx %d).",
673
                        idxGR, idxG1, idxG2, i));
771 blopes 674
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
764 blopes 675
            }
676
        }
773 blopes 677
 
678
        if (idxPrimeiroCompradorAposG2 == -1) {
679
            log(String.format("GR[%d], G1[%d], G2[%d]: não houve comprador após G2.", idxGR, idxG1, idxG2));
771 blopes 680
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
773 blopes 681
        }
760 blopes 682
 
764 blopes 683
        for (int i = idxPrimeiroCompradorAposG2; i < n; i++) {
684
            Candle c = candles.get(i);
773 blopes 685
            if (isInside(candles, i) || !isDirecional(c))
686
                continue;
767 blopes 687
 
771 blopes 688
            if (isOutside(candles, i)) {
773 blopes 689
                lastIndex = idxGR;
771 blopes 690
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G3 (comprador).", idxGR, i));
767 blopes 691
                return new ResultadoPadrao(null, lastIndex);
692
            }
693
 
773 blopes 694
            // Regra global vendedor: romper fundo do GR invalida
771 blopes 695
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
696
                lastIndex = i;
773 blopes 697
                log(String.format("GR[%d]: candle[%d] rompeu fundo do GR durante busca de G3.", idxGR, i));
771 blopes 698
                return new ResultadoPadrao(null, lastIndex);
699
            }
700
 
764 blopes 701
            if (!c.isCandleComprador()) {
773 blopes 702
                // apareceu vendedor antes de G3 → padrão só até G2
764 blopes 703
                lastIndex = i - 1;
773 blopes 704
                log(String.format("GR[%d], G1[%d], G2[%d]: vendedor em [%d] antes de G3.", idxGR, idxG1, idxG2, i));
705
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
764 blopes 706
            }
760 blopes 707
 
764 blopes 708
            lastIndex = i;
709
            boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
771 blopes 710
            boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima());
711
            if (rompeTopoG2 && fundoMaiorOuIgualGR) {
712
                g3 = c;
764 blopes 713
                idxG3 = i;
714
                break;
715
            }
716
        }
760 blopes 717
 
773 blopes 718
        if (g3 == null) {
719
            log(String.format("GR[%d], G1[%d], G2[%d]: não houve G3, padrão parcial.", idxGR, idxG1, idxG2));
771 blopes 720
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
773 blopes 721
        }
760 blopes 722
 
773 blopes 723
        lastIndex = idxG3;
724
        log(String.format("GR[%d], G1[%d], G2[%d] => G3 (comprador) em [%d].", idxGR, idxG1, idxG2, idxG3));
760 blopes 725
 
764 blopes 726
        PadraoGatilho padrao = new PadraoGatilho();
771 blopes 727
        padrao.setReferencia(gr);
764 blopes 728
        padrao.setGatilho1(g1);
729
        padrao.setGatilho2(g2);
730
        padrao.setGatilho3(g3);
773 blopes 731
        padrao.setGatilho4(null);
760 blopes 732
 
764 blopes 733
        return new ResultadoPadrao(padrao, lastIndex);
734
    }
760 blopes 735
 
764 blopes 736
    // =====================================================================
771 blopes 737
    // MODO TEMPO REAL
764 blopes 738
    // =====================================================================
760 blopes 739
 
764 blopes 740
    public void resetTempoReal() {
741
        this.idxProximaAnaliseTempoReal = 0;
742
    }
760 blopes 743
 
764 blopes 744
    public PadraoGatilho processarCandleTempoReal(List<Candle> candles) {
745
        int n = candles.size();
771 blopes 746
        if (n < 4) return null;
760 blopes 747
 
764 blopes 748
        while (idxProximaAnaliseTempoReal < n - 3) {
749
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal);
760 blopes 750
 
764 blopes 751
            if (resultado == null) {
752
                idxProximaAnaliseTempoReal++;
753
                continue;
754
            }
760 blopes 755
 
764 blopes 756
            int proximoInicio = resultado.lastIndex + 1;
757
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
758
                proximoInicio = idxProximaAnaliseTempoReal + 1;
759
            }
760
            idxProximaAnaliseTempoReal = proximoInicio;
760 blopes 761
 
764 blopes 762
            if (resultado.padrao != null) {
763
                return resultado.padrao;
764
            }
765
        }
760 blopes 766
 
764 blopes 767
        return null;
768
    }
760 blopes 769
 
764 blopes 770
}