Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
776 blopes 1
package br.com.kronus.core;
2
 
3
import java.math.BigDecimal;
4
import java.util.ArrayList;
5
import java.util.List;
6
 
7
import br.com.sl.domain.model.Candle;
8
import br.com.sl.domain.util.BigDecimalUtils;
9
 
10
/**
11
 * Detector de padrões de gatilhos (GR, G1, G2, G3)
12
 * para o modelo Kronus.
13
 *
14
 * REGRAS GERAIS:
15
 * - Não usar candles INSIDE (range totalmente dentro do candle anterior).
16
 * - Gatilhos sempre em ordem cronológica: GR -> G1 -> G2 -> G3.
17
 * - Um candle não pode ser usado para dois gatilhos.
18
 * - Não pode haver G2 antes de G1.
19
 *
20
 * -------------------------------------------------------------
21
 * PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
22
 * -------------------------------------------------------------
23
 *
24
 * 1) Referência (GR) e G1
25
 *
26
 * A) Tenho um candle A COMPRADOR.
27
 *    Se o PRÓXIMO candle direcional (não-inside) tiver mudança direcional
28
 *    (for VENDEDOR), então A passa a ser CANDIDATO À REFERÊNCIA.
29
 *
30
 * B) Referência dinâmica (antes do G1):
31
 *    Enquanto G1 ainda não apareceu, entre o índice do candidato e o G1:
32
 *    - Se algum candle posterior fizer MÁXIMA MAIOR que a máxima do candidato,
33
 *      esse candle passa a ser o NOVO candidato à referência.
34
 *
35
 * C) G1:
36
 *    - Se algum candle VENDEDOR subsequente romper o FUNDO do candidato à referência
37
 *      (minima < minimaCandRef), esse candle será o G1.
38
 *    - O candidato atual torna-se o GR definitivo.
39
 *
40
 * D) Regra de saída pelo G1:
41
 *    - Considerar a Fibo do G1 com origem na MÁXIMA do G1 e destino na MÍNIMA do G1.
42
 *    - Calcular o nível 200% dessa Fibo.
43
 *    - Se ALGUM candle após o G1 ATINGIR ou PASSAR ESSE NÍVEL (mínima <= nível200)
44
 *      ANTES de identificar G3, descartar a operação:
45
 *        -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
46
 *
47
 * 2) G2 (após G1)
48
 *
49
 *    - Após o G1, QUALQUER candle COMPRADOR que tiver FECHAMENTO DENTRO da região
50
 *      do GR (minGR <= fechamento <= maxGR) passa a ser CANDIDATO a G2.
51
 *
52
 *    - Regra de descarte:
53
 *        Se ALGUM candle posterior ao G1 ROMPER o TOPO do GR (máxima > maxGR),
54
 *        descartar a operação:
55
 *          -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
56
 *
57
 *    - G2 dinâmico (entre o primeiro G2 candidato e o G3):
58
 *        Enquanto G3 não tiver aparecido:
59
 *        - Se surgir outro candle COMPRADOR com FECHAMENTO dentro da região do GR
60
 *          e MÁXIMA MAIOR que a máxima do candidato atual a G2,
61
 *          esse novo candle passa a ser o NOVO G2.
62
 *
63
 * 3) G3 (após ter G2)
64
 *
65
 *    - Após existir um candidato a G2:
66
 *      Se algum candle VENDEDOR posterior:
67
 *        * romper o FUNDO do G2 (minima < minimaG2) e
68
 *        * tiver TOPO MENOR OU IGUAL ao TOPO do GR (max <= maxGR)
69
 *      => esse candle será o G3, confirmando o padrão.
70
 *
71
 *    - Ao encontrar G3, o padrão COMPRADOR é considerado COMPLETO (GR,G1,G2,G3).
72
 *
73
 * -------------------------------------------------------------
74
 * PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
75
 * -------------------------------------------------------------
76
 *
77
 * 1) Referência (GR) e G1
78
 *
79
 * A) Tenho um candle A VENDEDOR.
80
 *    Se o PRÓXIMO candle direcional (não-inside) tiver mudança direcional
81
 *    (for COMPRADOR), então A passa a ser CANDIDATO À REFERÊNCIA.
82
 *
83
 * B) Referência dinâmica (antes do G1):
84
 *    Enquanto G1 ainda não apareceu, entre o índice do candidato e o G1:
85
 *    - Se algum candle posterior fizer MÍNIMA MENOR que a mínima do candidato,
86
 *      esse candle passa a ser o NOVO candidato à referência.
87
 *
88
 * C) G1:
89
 *    - Se algum candle COMPRADOR subsequente romper o TOPO do candidato à referência
90
 *      (máxima > máximaCandRef), esse candle será o G1.
91
 *    - O candidato atual torna-se o GR definitivo.
92
 *
93
 * D) Regra de saída pelo G1:
94
 *    - Considerar a Fibo do G1 com origem na MÍNIMA do G1 e destino na MÁXIMA do G1.
95
 *    - Calcular o nível -100% dessa Fibo.
96
 *    - Se ALGUM candle após o G1 ATINGIR ou PASSAR ESSE NÍVEL para baixo
97
 *      (mínima <= nível -100%) ANTES de identificar G3,
98
 *      descartar a operação:
99
 *        -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
100
 *
101
 * 2) G2 (após G1)
102
 *
103
 *    - Após o G1, QUALQUER candle VENDEDOR que tiver FECHAMENTO DENTRO da região
104
 *      do GR (minGR <= fechamento <= maxGR) passa a ser CANDIDATO a G2.
105
 *
106
 *    - Regra de descarte:
107
 *        Se ALGUM candle posterior ao G1 ROMPER o FUNDO do GR (mínima < minGR),
108
 *        descartar a operação:
109
 *          -> descartar padrão e reiniciar análise a partir do candle APÓS o GR.
110
 *
111
 *    - G2 dinâmico (entre o primeiro G2 candidato e o G3):
112
 *        Enquanto G3 não tiver aparecido:
113
 *        - Se surgir outro candle VENDEDOR com FECHAMENTO dentro da região do GR
114
 *          e MÍNIMA MENOR que a mínima do candidato a G2,
115
 *          esse novo candle passa a ser o NOVO G2.
116
 *
117
 * 3) G3 (após ter G2)
118
 *
119
 *    - Após existir um candidato a G2:
120
 *      Se algum candle COMPRADOR posterior:
121
 *        * romper o TOPO do G2 (máxima > máximaG2) e
122
 *        * tiver FUNDO MENOR OU IGUAL ao FUNDO do GR (mínima <= minGR)
123
 *      => esse candle será o G3, confirmando o padrão.
124
 *
125
 *    - Ao encontrar G3, o padrão VENDEDOR é considerado COMPLETO (GR,G1,G2,G3).
126
 *
127
 * -------------------------------------------------------------
128
 * BACKTEST:
129
 *  - identificarPadroes: varre a lista inteira e retorna todos os padrões
130
 *    completos ou parciais (até G2).
131
 *
132
 * TEMPO REAL:
133
 *  - processarCandleTempoReal: chamado a cada novo candle, retorna um padrão
134
 *    assim que ele for identificado (até G3).
135
 *
136
 * DEBUG:
137
 *  - debugarAPartirDoIndice: roda a detecção a partir de um índice e
138
 *    retorna um relatório (List<String>) com tudo o que aconteceu.
139
 */
140
public class DetectorGatilhosAnterior {
141
 
142
    private final boolean logAtivo;
143
    private int idxProximaAnaliseTempoReal = 0;
144
 
145
    /**
146
     * Buffer opcional para capturar logs em memória (modo debug).
147
     * Se for null, não acumula; se não for null, log() adiciona aqui também.
148
     */
149
    private List<String> bufferDebug;
150
 
151
    public DetectorGatilhosAnterior() {
152
        this(true);
153
    }
154
 
155
    public DetectorGatilhosAnterior(boolean logAtivo) {
156
        this.logAtivo = logAtivo;
157
    }
158
 
159
    private void log(String msg) {
160
        if (logAtivo) {
161
            System.out.println(msg);
162
        }
163
        if (bufferDebug != null) {
164
            bufferDebug.add(msg);
165
        }
166
    }
167
 
168
    /**
169
     * Estrutura interna para carregar o resultado da detecção
170
     * iniciando em um índice específico.
171
     *
172
     * lastIndex      = último índice efetivamente analisado na busca daquele padrão.
173
     * proximoInicio  = índice sugerido para o PRÓXIMO início de análise:
174
     *                  - Se NÃO existiu GR: idxA + 1
175
     *                  - Se existiu GR: idxGR + 1 (recomeça após a referência)
176
     */
177
    private static class ResultadoPadrao {
178
        PadraoGatilho padrao;
179
        int lastIndex;
180
        int proximoInicio;
181
 
182
        ResultadoPadrao(PadraoGatilho padrao, int lastIndex, int proximoInicio) {
183
            this.padrao = padrao;
184
            this.lastIndex = lastIndex;
185
            this.proximoInicio = proximoInicio;
186
        }
187
    }
188
 
189
    /**
190
     * Cria um resultado com padrão PARCIAL (até G2).
191
     * Sempre que há GR, o próximo início deve ser após o GR.
192
     */
193
    private ResultadoPadrao criarResultadoParcialComG2(Candle ref,
194
                                                       Candle g1,
195
                                                       Candle g2,
196
                                                       int lastIndex,
197
                                                       int idxGR) {
198
        PadraoGatilho padrao = new PadraoGatilho();
199
        padrao.setReferencia(ref);
200
        padrao.setGatilho1(g1);
201
        padrao.setGatilho2(g2);
202
        padrao.setGatilho3(null);
203
        padrao.setGatilho4(null); // G4 será tratado na camada de estratégia, não aqui.
204
        return new ResultadoPadrao(padrao, lastIndex, idxGR + 1);
205
    }
206
 
207
    // =====================================================================
208
    // HELPERS
209
    // =====================================================================
210
 
211
    private boolean isInside(List<Candle> candles, int idx) {
212
        if (idx <= 0) return false;
213
        Candle atual = candles.get(idx);
214
        Candle anterior = candles.get(idx - 1);
215
        return BigDecimalUtils.ehMenorOuIgualQue(atual.getMaxima(), anterior.getMaxima())
216
                && BigDecimalUtils.ehMaiorOuIgualQue(atual.getMinima(), anterior.getMinima());
217
    }
218
 
219
    private boolean isDirecional(Candle c) {
220
        return c.isCandleComprador() || c.isCandleVendedor();
221
    }
222
 
223
    private boolean fechamentoDentroRegiaoGR(Candle c, Candle gr) {
224
        return BigDecimalUtils.ehMaiorOuIgualQue(c.getFechamento(), gr.getMinima())
225
                && BigDecimalUtils.ehMenorOuIgualQue(c.getFechamento(), gr.getMaxima());
226
    }
227
 
228
    /**
229
     * Extensão de Fibonacci simples:
230
     * origem + (destino - origem) * fator
231
     */
232
    private BigDecimal fibExtend(BigDecimal origem, BigDecimal destino, BigDecimal fator) {
233
        BigDecimal diff = destino.subtract(origem);
234
        return origem.add(diff.multiply(fator));
235
    }
236
 
237
    // =====================================================================
238
    // API PRINCIPAL – BACKTEST
239
    // =====================================================================
240
 
241
    public List<PadraoGatilho> identificarPadroes(List<Candle> candles) {
242
        List<PadraoGatilho> padroes = new ArrayList<>();
243
        int n = candles.size();
244
        if (n < 4) return padroes;
245
 
246
        int idxRef = 0;
247
        while (idxRef < n - 3) {
248
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef);
249
 
250
            if (resultado == null) {
251
                idxRef++;
252
                continue;
253
            }
254
 
255
            if (resultado.padrao != null) {
256
                padroes.add(resultado.padrao);
257
            }
258
 
259
            // Regra de avanço:
260
            // - Se houve GR, proximoInicio = idxGR + 1
261
            // - Se não houve GR, proximoInicio = idxRef + 1
262
            int proximoInicio = resultado.proximoInicio;
263
            if (proximoInicio <= idxRef) {
264
                proximoInicio = idxRef + 1;
265
            }
266
 
267
            idxRef = proximoInicio;
268
        }
269
 
270
        return padroes;
271
    }
272
 
273
    private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) {
274
        Candle ref = candles.get(idxRef);
275
 
276
        if (ref.isCandleComprador()) {
277
            return detectarPadraoComprador(candles, idxRef);
278
        } else if (ref.isCandleVendedor()) {
279
            return detectarPadraoVendedor(candles, idxRef);
280
        } else {
281
            // Candle neutro não é A; não existe GR, avança 1 candle.
282
            return new ResultadoPadrao(null, idxRef, idxRef + 1);
283
        }
284
    }
285
 
286
    // =====================================================================
287
    // PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
288
    // =====================================================================
289
 
290
    private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxA) {
291
        int n = candles.size();
292
        Candle candleA = candles.get(idxA);
293
        if (!candleA.isCandleComprador()) {
294
            // Não pode ser A comprador -> reinicia no próximo candle.
295
            return new ResultadoPadrao(null, idxA, idxA + 1);
296
        }
297
 
298
        int lastIndex = idxA;
299
        log(String.format("[VENDER] Iniciando busca a partir de A[%d]", idxA + 1));
300
 
301
        // 1) Verifica se o próximo candle direcional (não-inside) muda a direção
302
        int idxProxDirecional = -1;
303
        for (int i = idxA + 1; i < n; i++) {
304
            Candle c = candles.get(i);
305
            if (!isDirecional(c) || isInside(candles, i)) {
306
                log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside)", i + 1));
307
                continue;
308
            }
309
            idxProxDirecional = i;
310
            break;
311
        }
312
 
313
        if (idxProxDirecional == -1) {
314
            // Não houve candle B; A não vira candidato; recomeça do próximo candle.
315
            return new ResultadoPadrao(null, idxA, idxA + 1);
316
        }
317
 
318
        Candle prox = candles.get(idxProxDirecional);
319
        if (!prox.isCandleVendedor()) {
320
            // Não houve mudança direcional imediata -> A não vira candidato
321
            return new ResultadoPadrao(null, idxA, idxA + 1);
322
        }
323
 
324
        // A vira candidato à referência
325
        Candle candidatoRef = candleA;
326
        int idxCandidatoRef = idxA;
327
        log(String.format("[VENDER] CandidatoRef inicial = idx %d", idxCandidatoRef + 1));
328
 
329
        // 2) Busca G1, com referência dinâmica até G1
330
        Candle g1 = null;
331
        int idxG1 = -1;
332
 
333
        for (int i = idxA + 1; i < n; i++) {
334
            Candle c = candles.get(i);
335
            if (!isDirecional(c) || isInside(candles, i)) {
336
                log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1));
337
                continue;
338
            }
339
 
340
            lastIndex = i;
341
 
342
            // Primeiro checa G1: vendedor que rompe fundo do candidatoRef
343
            if (c.isCandleVendedor()
344
                    && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
345
                g1 = c;
346
                idxG1 = i;
347
                log(String.format("[VENDER] G1 encontrado em [%d], rompendo fundo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1));
348
                break;
349
            }
350
 
351
            // Se ainda não encontrou G1, atualiza candidatoRef se máxima maior
352
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
353
                candidatoRef = c;
354
                idxCandidatoRef = i;
355
                log(String.format("[VENDER] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1));
356
            }
357
        }
358
 
359
        if (g1 == null) {
360
            // Não formou G1; não existe GR definitivo; recomeça a partir do próximo candle após A.
361
            return new ResultadoPadrao(null, lastIndex, idxA + 1);
362
        }
363
 
364
        Candle gr = candidatoRef;
365
        int idxGR = idxCandidatoRef;
366
        log(String.format("[VENDER] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1));
367
 
368
        // 3) Regra de saída do G1 – Fibonacci 200% (origem = máxG1, destino = mínG1)
369
        BigDecimal fib200 = fibExtend(g1.getMaxima(), g1.getMinima(), new BigDecimal("2"));
370
        log(String.format("[VENDER] Fibo200 G1[%d] = %s", idxG1, fib200.toPlainString()));
371
 
372
        // 4) Busca G2 dinâmico e G3
373
        Candle g2 = null;
374
        int idxG2 = -1;
375
        Candle g3 = null;
376
        int idxG3 = -1;
377
 
378
        for (int i = idxG1 + 1; i < n; i++) {
379
            Candle c = candles.get(i);
380
            if (!isDirecional(c) || isInside(candles, i)) {
381
                log(String.format("[VENDER] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1));
382
                continue;
383
            }
384
 
385
            lastIndex = i;
386
 
387
            // --- Regras de DESCARTE após G1 (devem reiniciar do candle após o GR) ---
388
 
389
            // 4.1) Se candle romper TOPO do GR => descarta operação
390
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
391
                log(String.format("[VENDER] GR[%d]: candle[%d] rompeu topo do GR. Descartando padrão.", idxGR + 1, i + 1));
392
                return new ResultadoPadrao(null, i, idxGR + 1);
393
            }
394
 
395
            // 4.2) Regra Fib 200% do G1: se mínima <= fib200 => descarta
396
            if (BigDecimalUtils.ehMenorOuIgualQue(c.getMinima(), fib200)) {
397
                log(String.format("[VENDER] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.",
398
                        idxGR + 1, idxG1 + 1, i + 1));
399
                return new ResultadoPadrao(null, i, idxGR + 1);
400
            }
401
 
402
            // --- Construção de G2 dinâmico (COMPRADOR com FECHAMENTO dentro da região do GR) ---
403
            if (c.isCandleComprador() && fechamentoDentroRegiaoGR(c, gr)) {
404
                if (g2 == null) {
405
                    g2 = c;
406
                    idxG2 = i;
407
                    log(String.format("[VENDER] GR[%d], G1[%d]: candidato G2 em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1));
408
                } else {
409
                    // Atualização dinâmica: máxima maior e fechamento ainda dentro da região
410
                    if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima())) {
411
                        g2 = c;
412
                        idxG2 = i;
413
                        log(String.format("[VENDER] GR[%d], G1[%d]: G2 atualizado em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1));
414
                    }
415
                }
416
                continue;
417
            }
418
 
419
            // --- G3: só pode ser avaliado após existir candidato G2 ---
420
            if (g2 != null && c.isCandleVendedor()) {
421
                boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
422
                boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima());
423
 
424
                if (rompeFundoG2 && topoMenorOuIgualGR) {
425
                    g3 = c;
426
                    idxG3 = i;
427
                    log(String.format("[VENDER] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)",
428
                            idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1));
429
                    break;
430
                }
431
            }
432
        }
433
 
434
        if (g3 == null) {
435
            // Padrão só até G2 (se G2 existir), senão nada.
436
            if (g2 != null) {
437
                log(String.format("[VENDER] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", idxGR + 1, idxG1 + 1, idxG2 + 1));
438
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR);
439
            }
440
            // Já houve GR e G1, mas não houve G2/G3 -> recomeça após GR.
441
            log(String.format("[VENDER] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", idxGR + 1, idxG1 + 1));
442
            return new ResultadoPadrao(null, lastIndex, idxGR + 1);
443
        }
444
 
445
        // Finaliza padrão no G3
446
        PadraoGatilho padrao = new PadraoGatilho();
447
        padrao.setReferencia(gr);
448
        padrao.setGatilho1(g1);
449
        padrao.setGatilho2(g2);
450
        padrao.setGatilho3(g3);
451
        padrao.setGatilho4(null);
452
 
453
        // Padrão completo -> próxima busca deve começar após o GR
454
        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
455
    }
456
 
457
    // =====================================================================
458
    // PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
459
    // =====================================================================
460
 
461
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) {
462
        int n = candles.size();
463
        Candle candleA = candles.get(idxA);
464
        if (!candleA.isCandleVendedor()) {
465
            // Não pode ser A vendedor -> reinicia no próximo candle.
466
            return new ResultadoPadrao(null, idxA, idxA + 1);
467
        }
468
 
469
        int lastIndex = idxA;
470
        log(String.format("[COMPRAR] Iniciando busca a partir de A[%d]", idxA + 1));
471
 
472
        // 1) Verifica se o próximo candle direcional (não-inside) muda a direção
473
        int idxProxDirecional = -1;
474
        for (int i = idxA + 1; i < n; i++) {
475
            Candle c = candles.get(i);
476
            if (!isDirecional(c) || isInside(candles, i)) {
477
                log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside)", i + 1));
478
                continue;
479
            }
480
            idxProxDirecional = i;
481
            break;
482
        }
483
 
484
        if (idxProxDirecional == -1) {
485
            // Não houve candle B; A não vira candidato; recomeça do próximo candle.
486
            return new ResultadoPadrao(null, idxA, idxA + 1);
487
        }
488
 
489
        Candle prox = candles.get(idxProxDirecional);
490
        if (!prox.isCandleComprador()) {
491
            // Não houve mudança direcional imediata -> A não vira candidato
492
            return new ResultadoPadrao(null, idxA, idxA + 1);
493
        }
494
 
495
        // A vira candidato à referência
496
        Candle candidatoRef = candleA;
497
        int idxCandidatoRef = idxA;
498
        log(String.format("[COMPRAR] CandidatoRef inicial = idx %d", idxCandidatoRef + 1));
499
 
500
        // 2) Busca G1, com referência dinâmica até G1
501
        Candle g1 = null;
502
        int idxG1 = -1;
503
 
504
        for (int i = idxA + 1; i < n; i++) {
505
            Candle c = candles.get(i);
506
            if (!isDirecional(c) || isInside(candles, i)) {
507
                log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) antes do G1", i + 1));
508
                continue;
509
            }
510
 
511
            lastIndex = i;
512
 
513
            // Primeiro checa G1: comprador que rompe topo do candidatoRef
514
            if (c.isCandleComprador()
515
                    && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
516
                g1 = c;
517
                idxG1 = i;
518
                log(String.format("[COMPRAR] G1 encontrado em [%d], rompendo topo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1));
519
                break;
520
            }
521
 
522
            // Se ainda não encontrou G1, atualiza candidatoRef se mínima menor
523
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
524
                candidatoRef = c;
525
                idxCandidatoRef = i;
526
                log(String.format("[COMPRAR] CandidatoRef atualizado dinamicamente para idx %d", idxCandidatoRef + 1));
527
            }
528
        }
529
 
530
        if (g1 == null) {
531
            // Não formou G1; não existe GR definitivo; recomeça a partir do próximo candle após A.
532
            return new ResultadoPadrao(null, lastIndex, idxA + 1);
533
        }
534
 
535
        Candle gr = candidatoRef;
536
        int idxGR = idxCandidatoRef;
537
        log(String.format("[COMPRAR] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1));
538
 
539
        // 3) Regra de saída do G1 – Fibonacci 200% (origem = mínG1, destino = máxG1)
540
        BigDecimal fib200MinMax = fibExtend(g1.getMinima(), g1.getMaxima(), new BigDecimal("2"));
541
        log(String.format("[COMPRAR] Fibo200 G1[%d] = %s", idxG1 + 1, fib200MinMax.toPlainString()));
542
 
543
        // 4) Busca G2 dinâmico e G3
544
        Candle g2 = null;
545
        int idxG2 = -1;
546
        Candle g3 = null;
547
        int idxG3 = -1;
548
 
549
        for (int i = idxG1 + 1; i < n; i++) {
550
            Candle c = candles.get(i);
551
            if (!isDirecional(c) || isInside(candles, i)) {
552
                log(String.format("[COMPRAR] Candle[%d] ignorado (não direcional ou inside) após G1", i + 1));
553
                continue;
554
            }
555
 
556
            lastIndex = i;
557
 
558
            // --- Regras de DESCARTE após G1 (reiniciar após GR) ---
559
 
560
            // 4.1) Se candle romper FUNDO do GR => descarta operação
561
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
562
                log(String.format("[COMPRAR] GR[%d]: candle[%d] rompeu fundo do GR. Descartando padrão.", idxGR + 1, i + 1));
563
                return new ResultadoPadrao(null, i, idxGR + 1);
564
            }
565
 
566
            // 4.2) Regra Fib 200% do G1: se mínima > fib200MinMax => descarta
567
            if (BigDecimalUtils.ehMaiorQue(c.getMinima(), fib200MinMax)) {
568
                log(String.format("[COMPRAR] GR[%d], G1[%d]: candle[%d] atingiu 200%% da fibo G1. Descartando padrão.",
569
                        idxGR + 1, idxG1 + 1, i + 1));
570
                return new ResultadoPadrao(null, i, idxGR + 1);
571
            }
572
 
573
            // --- Construção de G2 dinâmico (VENDEDOR com FECHAMENTO dentro da região do GR) ---
574
            if (c.isCandleVendedor() && fechamentoDentroRegiaoGR(c, gr)) {
575
                if (g2 == null) {
576
                    g2 = c;
577
                    idxG2 = i;
578
                    log(String.format("[COMPRAR] GR[%d], G1[%d]: candidato G2 em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1));
579
                } else {
580
                    // Atualização dinâmica: mínima menor e fechamento ainda dentro da região
581
                    if (BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima())) {
582
                        g2 = c;
583
                        idxG2 = i;
584
                        log(String.format("[COMPRAR] GR[%d], G1[%d]: G2 atualizado em [%d]", idxGR + 1, idxG1 + 1, idxG2 + 1));
585
                    }
586
                }
587
                continue;
588
            }
589
 
590
            // --- G3: só pode ser avaliado após existir candidato G2 ---
591
            if (g2 != null && c.isCandleComprador()) {
592
                boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
593
                boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima());
594
 
595
                if (rompeTopoG2 && fundoMaiorOuIgualGR) {
596
                    g3 = c;
597
                    idxG3 = i;
598
                    log(String.format("[COMPRAR] GR[%d], G1[%d], G2[%d]: G3 em [%d] (padrão confirmado)",
599
                            idxGR + 1, idxG1 + 1, idxG2 + 1, idxG3 + 1));
600
                    break;
601
                }
602
            }
603
        }
604
 
605
        if (g3 == null) {
606
            // Padrão só até G2 (se G2 existir), senão nada
607
            if (g2 != null) {
608
                log(String.format("[COMPRAR] Padrão parcial (GR[%d], G1[%d], G2[%d]) sem G3.", idxGR + 1, idxG1 + 1, idxG2 + 1));
609
                return criarResultadoParcialComG2(gr, g1, g2, lastIndex, idxGR);
610
            }
611
            // Já houve GR e G1, mas não houve G2/G3 -> recomeça após GR.
612
            log(String.format("[COMPRAR] GR[%d], G1[%d] sem G2/G3. Recomeçando após GR.", idxGR + 1, idxG1 + 1));
613
            return new ResultadoPadrao(null, lastIndex, idxGR + 1);
614
        }
615
 
616
        // Finaliza padrão no G3
617
        PadraoGatilho padrao = new PadraoGatilho();
618
        padrao.setReferencia(gr);
619
        padrao.setGatilho1(g1);
620
        padrao.setGatilho2(g2);
621
        padrao.setGatilho3(g3);
622
        padrao.setGatilho4(null);
623
 
624
        // Padrão completo -> próxima busca deve começar após o GR
625
        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
626
    }
627
 
628
    // =====================================================================
629
    // MODO TEMPO REAL
630
    // =====================================================================
631
 
632
    public void resetTempoReal() {
633
        this.idxProximaAnaliseTempoReal = 0;
634
    }
635
 
636
    /**
637
     * Deve ser chamado SEMPRE que um novo candle for adicionado à lista.
638
     *
639
     * Exemplo:
640
     *   candles.add(novoCandle);
641
     *   PadraoGatilho padrao = detector.processarCandleTempoReal(candles);
642
     *
643
     *   if (padrao != null) {
644
     *       // padrão completo (até G3) encontrado
645
     *   }
646
     */
647
    public PadraoGatilho processarCandleTempoReal(List<Candle> candles) {
648
        int n = candles.size();
649
        if (n < 4) return null;
650
 
651
        while (idxProximaAnaliseTempoReal < n - 3) {
652
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal);
653
 
654
            if (resultado == null) {
655
                idxProximaAnaliseTempoReal++;
656
                continue;
657
            }
658
 
659
            int proximoInicio = resultado.proximoInicio;
660
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
661
                proximoInicio = idxProximaAnaliseTempoReal + 1;
662
            }
663
            idxProximaAnaliseTempoReal = proximoInicio;
664
 
665
            if (resultado.padrao != null) {
666
                return resultado.padrao;
667
            }
668
        }
669
 
670
        return null;
671
    }
672
 
673
    // =====================================================================
674
    // DEBUG / RELATÓRIO
675
    // =====================================================================
676
 
677
    /**
678
     * Roda a lógica de detecção a partir de um índice específico e devolve
679
     * um "relatório" em forma de lista de strings com tudo que aconteceu.
680
     *
681
     * NÃO altera idxProximaAnaliseTempoReal.
682
     */
683
    public List<String> debugarAPartirDoIndice(List<Candle> candles, int idxInicio) {
684
        List<String> relatorio = new ArrayList<>();
685
        List<String> antigoBuffer = this.bufferDebug;
686
        this.bufferDebug = relatorio;
687
        try {
688
            log("====================================================");
689
            log(String.format("DEBUG: iniciando debugarAPartirDoIndice(%d)", idxInicio));
690
            detectarPadraoAPartir(candles, idxInicio);
691
            log(String.format("DEBUG: fim da análise a partir do índice %d", idxInicio));
692
            log("====================================================");
693
        } finally {
694
            this.bufferDebug = antigoBuffer;
695
        }
696
        return relatorio;
697
    }
698
 
699
}