Subversion Repositories Integrator Subversion

Rev

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