Subversion Repositories Integrator Subversion

Rev

Rev 774 | Rev 776 | 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
 
775 blopes 3
import java.math.BigDecimal;
760 blopes 4
import java.util.ArrayList;
764 blopes 5
import java.util.List;
760 blopes 6
 
764 blopes 7
import br.com.sl.domain.model.Candle;
8
import br.com.sl.domain.util.BigDecimalUtils;
9
 
760 blopes 10
/**
773 blopes 11
 * Detector de padrões de gatilhos (GR, G1, G2, G3)
775 blopes 12
 * para o modelo Kronus.
760 blopes 13
 *
775 blopes 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.
760 blopes 19
 *
775 blopes 20
 * -------------------------------------------------------------
21
 * PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
22
 * -------------------------------------------------------------
773 blopes 23
 *
775 blopes 24
 * 1) Referência (GR) e G1
760 blopes 25
 *
775 blopes 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.
760 blopes 29
 *
775 blopes 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.
760 blopes 34
 *
775 blopes 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.
774 blopes 39
 *
775 blopes 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.
760 blopes 46
 *
775 blopes 47
 * 2) G2 (após G1)
764 blopes 48
 *
775 blopes 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.
764 blopes 51
 *
775 blopes 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.
764 blopes 56
 *
775 blopes 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.
764 blopes 62
 *
775 blopes 63
 * 3) G3 (após ter G2)
771 blopes 64
 *
775 blopes 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.
771 blopes 70
 *
775 blopes 71
 *    - Ao encontrar G3, o padrão COMPRADOR é considerado COMPLETO (GR,G1,G2,G3).
771 blopes 72
 *
775 blopes 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.
760 blopes 139
 */
140
public class DetectorGatilhos {
141
 
764 blopes 142
    private final boolean logAtivo;
143
    private int idxProximaAnaliseTempoReal = 0;
760 blopes 144
 
775 blopes 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
 
764 blopes 151
    public DetectorGatilhos() {
775 blopes 152
        this(true);
764 blopes 153
    }
760 blopes 154
 
764 blopes 155
    public DetectorGatilhos(boolean logAtivo) {
156
        this.logAtivo = logAtivo;
157
    }
760 blopes 158
 
764 blopes 159
    private void log(String msg) {
160
        if (logAtivo) {
161
            System.out.println(msg);
162
        }
775 blopes 163
        if (bufferDebug != null) {
164
            bufferDebug.add(msg);
165
        }
764 blopes 166
    }
760 blopes 167
 
775 blopes 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
     */
764 blopes 177
    private static class ResultadoPadrao {
771 blopes 178
        PadraoGatilho padrao;
179
        int lastIndex;
775 blopes 180
        int proximoInicio;
773 blopes 181
 
775 blopes 182
        ResultadoPadrao(PadraoGatilho padrao, int lastIndex, int proximoInicio) {
764 blopes 183
            this.padrao = padrao;
184
            this.lastIndex = lastIndex;
775 blopes 185
            this.proximoInicio = proximoInicio;
764 blopes 186
        }
187
    }
760 blopes 188
 
775 blopes 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,
764 blopes 194
                                                       Candle g1,
195
                                                       Candle g2,
775 blopes 196
                                                       int lastIndex,
197
                                                       int idxGR) {
764 blopes 198
        PadraoGatilho padrao = new PadraoGatilho();
775 blopes 199
        padrao.setReferencia(ref);
764 blopes 200
        padrao.setGatilho1(g1);
201
        padrao.setGatilho2(g2);
202
        padrao.setGatilho3(null);
775 blopes 203
        padrao.setGatilho4(null); // G4 será tratado na camada de estratégia, não aqui.
204
        return new ResultadoPadrao(padrao, lastIndex, idxGR + 1);
764 blopes 205
    }
760 blopes 206
 
764 blopes 207
    // =====================================================================
775 blopes 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
    // =====================================================================
771 blopes 238
    // API PRINCIPAL – BACKTEST
764 blopes 239
    // =====================================================================
760 blopes 240
 
764 blopes 241
    public List<PadraoGatilho> identificarPadroes(List<Candle> candles) {
242
        List<PadraoGatilho> padroes = new ArrayList<>();
243
        int n = candles.size();
771 blopes 244
        if (n < 4) return padroes;
760 blopes 245
 
764 blopes 246
        int idxRef = 0;
247
        while (idxRef < n - 3) {
248
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxRef);
760 blopes 249
 
764 blopes 250
            if (resultado == null) {
251
                idxRef++;
252
                continue;
253
            }
760 blopes 254
 
764 blopes 255
            if (resultado.padrao != null) {
256
                padroes.add(resultado.padrao);
257
            }
760 blopes 258
 
775 blopes 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;
764 blopes 268
        }
760 blopes 269
 
764 blopes 270
        return padroes;
271
    }
760 blopes 272
 
764 blopes 273
    private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) {
274
        Candle ref = candles.get(idxRef);
775 blopes 275
 
764 blopes 276
        if (ref.isCandleComprador()) {
277
            return detectarPadraoComprador(candles, idxRef);
278
        } else if (ref.isCandleVendedor()) {
279
            return detectarPadraoVendedor(candles, idxRef);
280
        } else {
775 blopes 281
            // Candle neutro não é A; não existe GR, avança 1 candle.
282
            return new ResultadoPadrao(null, idxRef, idxRef + 1);
764 blopes 283
        }
284
    }
760 blopes 285
 
764 blopes 286
    // =====================================================================
775 blopes 287
    // PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
764 blopes 288
    // =====================================================================
760 blopes 289
 
775 blopes 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
        }
760 blopes 297
 
775 blopes 298
        int lastIndex = idxA;
299
        log(String.format("[VENDER] Iniciando busca a partir de A[%d]", idxA + 1));
760 blopes 300
 
775 blopes 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
        }
773 blopes 312
 
775 blopes 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
        }
771 blopes 317
 
775 blopes 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);
764 blopes 322
        }
760 blopes 323
 
775 blopes 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));
760 blopes 328
 
775 blopes 329
        // 2) Busca G1, com referência dinâmica até G1
771 blopes 330
        Candle g1 = null;
764 blopes 331
        int idxG1 = -1;
773 blopes 332
 
775 blopes 333
        for (int i = idxA + 1; i < n; i++) {
764 blopes 334
            Candle c = candles.get(i);
775 blopes 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));
771 blopes 337
                continue;
774 blopes 338
            }
760 blopes 339
 
775 blopes 340
            lastIndex = i;
760 blopes 341
 
775 blopes 342
            // Primeiro checa G1: vendedor que rompe fundo do candidatoRef
773 blopes 343
            if (c.isCandleVendedor()
344
                    && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
771 blopes 345
                g1 = c;
764 blopes 346
                idxG1 = i;
775 blopes 347
                log(String.format("[VENDER] G1 encontrado em [%d], rompendo fundo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1));
764 blopes 348
                break;
349
            }
773 blopes 350
 
775 blopes 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
            }
764 blopes 357
        }
760 blopes 358
 
775 blopes 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);
773 blopes 362
        }
760 blopes 363
 
775 blopes 364
        Candle gr = candidatoRef;
365
        int idxGR = idxCandidatoRef;
366
        log(String.format("[VENDER] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1));
760 blopes 367
 
775 blopes 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
771 blopes 373
        Candle g2 = null;
374
        int idxG2 = -1;
775 blopes 375
        Candle g3 = null;
376
        int idxG3 = -1;
771 blopes 377
 
764 blopes 378
        for (int i = idxG1 + 1; i < n; i++) {
379
            Candle c = candles.get(i);
775 blopes 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));
771 blopes 382
                continue;
773 blopes 383
            }
384
 
775 blopes 385
            lastIndex = i;
773 blopes 386
 
775 blopes 387
            // --- Regras de DESCARTE após G1 (devem reiniciar do candle após o GR) ---
760 blopes 388
 
775 blopes 389
            // 4.1) Se candle romper TOPO do GR => descarta operação
773 blopes 390
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
775 blopes 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);
767 blopes 393
            }
394
 
775 blopes 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);
764 blopes 400
            }
760 blopes 401
 
775 blopes 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
                }
764 blopes 416
                continue;
774 blopes 417
            }
760 blopes 418
 
775 blopes 419
            // --- G3: só pode ser avaliado após existir candidato G2 ---
420
            if (g2 != null && c.isCandleVendedor()) {
774 blopes 421
                boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
422
                boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima());
775 blopes 423
 
774 blopes 424
                if (rompeFundoG2 && topoMenorOuIgualGR) {
425
                    g3 = c;
426
                    idxG3 = i;
775 blopes 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));
774 blopes 429
                    break;
430
                }
764 blopes 431
            }
432
        }
773 blopes 433
 
434
        if (g3 == null) {
775 blopes 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);
773 blopes 443
        }
760 blopes 444
 
775 blopes 445
        // Finaliza padrão no G3
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
 
775 blopes 453
        // Padrão completo -> próxima busca deve começar após o GR
454
        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
764 blopes 455
    }
760 blopes 456
 
764 blopes 457
    // =====================================================================
775 blopes 458
    // PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
764 blopes 459
    // =====================================================================
760 blopes 460
 
775 blopes 461
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) {
764 blopes 462
        int n = candles.size();
775 blopes 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
        }
760 blopes 468
 
775 blopes 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;
764 blopes 482
        }
760 blopes 483
 
775 blopes 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
        }
760 blopes 488
 
775 blopes 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
771 blopes 501
        Candle g1 = null;
764 blopes 502
        int idxG1 = -1;
773 blopes 503
 
775 blopes 504
        for (int i = idxA + 1; i < n; i++) {
764 blopes 505
            Candle c = candles.get(i);
775 blopes 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));
771 blopes 508
                continue;
774 blopes 509
            }
760 blopes 510
 
775 blopes 511
            lastIndex = i;
760 blopes 512
 
775 blopes 513
            // Primeiro checa G1: comprador que rompe topo do candidatoRef
773 blopes 514
            if (c.isCandleComprador()
515
                    && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
771 blopes 516
                g1 = c;
764 blopes 517
                idxG1 = i;
775 blopes 518
                log(String.format("[COMPRAR] G1 encontrado em [%d], rompendo topo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1));
764 blopes 519
                break;
520
            }
773 blopes 521
 
775 blopes 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
            }
764 blopes 528
        }
760 blopes 529
 
775 blopes 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);
773 blopes 533
        }
760 blopes 534
 
775 blopes 535
        Candle gr = candidatoRef;
536
        int idxGR = idxCandidatoRef;
537
        log(String.format("[COMPRAR] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1));
773 blopes 538
 
775 blopes 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
771 blopes 544
        Candle g2 = null;
545
        int idxG2 = -1;
775 blopes 546
        Candle g3 = null;
547
        int idxG3 = -1;
760 blopes 548
 
764 blopes 549
        for (int i = idxG1 + 1; i < n; i++) {
550
            Candle c = candles.get(i);
775 blopes 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;
773 blopes 554
            }
555
 
775 blopes 556
            lastIndex = i;
773 blopes 557
 
775 blopes 558
            // --- Regras de DESCARTE após G1 (reiniciar após GR) ---
760 blopes 559
 
775 blopes 560
            // 4.1) Se candle romper FUNDO do GR => descarta operação
773 blopes 561
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
775 blopes 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);
767 blopes 564
            }
565
 
775 blopes 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);
764 blopes 571
            }
760 blopes 572
 
775 blopes 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
                }
764 blopes 587
                continue;
774 blopes 588
            }
760 blopes 589
 
775 blopes 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 fundoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMinima(), gr.getMinima());
773 blopes 594
 
775 blopes 595
                if (rompeTopoG2 && fundoMenorOuIgualGR) {
774 blopes 596
                    g3 = c;
597
                    idxG3 = i;
775 blopes 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));
774 blopes 600
                    break;
601
                }
764 blopes 602
            }
603
        }
773 blopes 604
 
605
        if (g3 == null) {
775 blopes 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);
773 blopes 614
        }
760 blopes 615
 
775 blopes 616
        // Finaliza padrão no G3
764 blopes 617
        PadraoGatilho padrao = new PadraoGatilho();
771 blopes 618
        padrao.setReferencia(gr);
764 blopes 619
        padrao.setGatilho1(g1);
620
        padrao.setGatilho2(g2);
621
        padrao.setGatilho3(g3);
773 blopes 622
        padrao.setGatilho4(null);
760 blopes 623
 
775 blopes 624
        // Padrão completo -> próxima busca deve começar após o GR
625
        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
764 blopes 626
    }
760 blopes 627
 
764 blopes 628
    // =====================================================================
771 blopes 629
    // MODO TEMPO REAL
764 blopes 630
    // =====================================================================
760 blopes 631
 
764 blopes 632
    public void resetTempoReal() {
633
        this.idxProximaAnaliseTempoReal = 0;
634
    }
760 blopes 635
 
774 blopes 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
     */
764 blopes 647
    public PadraoGatilho processarCandleTempoReal(List<Candle> candles) {
648
        int n = candles.size();
771 blopes 649
        if (n < 4) return null;
760 blopes 650
 
764 blopes 651
        while (idxProximaAnaliseTempoReal < n - 3) {
652
            ResultadoPadrao resultado = detectarPadraoAPartir(candles, idxProximaAnaliseTempoReal);
760 blopes 653
 
764 blopes 654
            if (resultado == null) {
655
                idxProximaAnaliseTempoReal++;
656
                continue;
657
            }
760 blopes 658
 
775 blopes 659
            int proximoInicio = resultado.proximoInicio;
764 blopes 660
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
661
                proximoInicio = idxProximaAnaliseTempoReal + 1;
662
            }
663
            idxProximaAnaliseTempoReal = proximoInicio;
760 blopes 664
 
764 blopes 665
            if (resultado.padrao != null) {
666
                return resultado.padrao;
667
            }
668
        }
760 blopes 669
 
764 blopes 670
        return null;
671
    }
760 blopes 672
 
775 blopes 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
 
764 blopes 699
}