Subversion Repositories Integrator Subversion

Rev

Rev 774 | Rev 776 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 774 Rev 775
Line 1... Line 1...
1
package br.com.kronus.core;
1
package br.com.kronus.core;
2
2
-
 
3
import java.math.BigDecimal;
3
import java.util.ArrayList;
4
import java.util.ArrayList;
4
import java.util.List;
5
import java.util.List;
5
6
6
import br.com.sl.domain.model.Candle;
7
import br.com.sl.domain.model.Candle;
7
import br.com.sl.domain.util.BigDecimalUtils;
8
import br.com.sl.domain.util.BigDecimalUtils;
8
9
9
/**
10
/**
10
 * Detector de padrões de gatilhos (GR, G1, G2, G3)
11
 * Detector de padrões de gatilhos (GR, G1, G2, G3)
11
 * trabalhando tanto em modo backtest quanto em modo tempo real.
-
 
-
 
12
 * para o modelo Kronus.
12
 *
13
 *
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.
-
 
-
 
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.
16
 *
19
 *
17
 * REGRAS IMPLEMENTADAS (versão alinhada)
-
 
18
 * ======================================
-
 
-
 
20
 * -------------------------------------------------------------
-
 
21
 * PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
-
 
22
 * -------------------------------------------------------------
19
 *
23
 *
20
 * 1) PADRÃO COMPRADOR
-
 
21
 * --------------------
-
 
22
 * Ponto de partida:
-
 
23
 *   - Um candle COMPRADOR é tomado como CANDIDATO À REFERÊNCIA.
-
 
-
 
24
 * 1) Referência (GR) e G1
24
 *
25
 *
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.
-
 
-
 
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.
30
 *
29
 *
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).
-
 
-
 
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.
38
 *
34
 *
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.
-
 
-
 
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.
54
 *
39
 *
55
 * G3 (compra) – NOVA REGRA:
-
 
56
 *   - Após o G2:
-
 
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.
-
 
-
 
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.
66
 *
46
 *
67
 * 2) PADRÃO VENDEDOR
-
 
68
 * -------------------
-
 
69
 * Ponto de partida:
-
 
70
 *   - Um candle VENDEDOR é tomado como CANDIDATO À REFERÊNCIA.
-
 
-
 
47
 * 2) G2 (após G1)
71
 *
48
 *
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.
-
 
-
 
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.
77
 *
51
 *
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).
-
 
-
 
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.
85
 *
56
 *
86
 * Regra global vendedor:
-
 
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.
-
 
-
 
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.
90
 *
62
 *
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).
-
 
-
 
63
 * 3) G3 (após ter G2)
104
 *
64
 *
105
 * G3 (venda) – NOVA REGRA:
-
 
106
 *   - Após o G2:
-
 
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.
-
 
-
 
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.
116
 *
70
 *
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.
-
 
-
 
71
 *    - Ao encontrar G3, o padrão COMPRADOR é considerado COMPLETO (GR,G1,G2,G3).
124
 *
72
 *
125
 * 4) ORDEM CRONOLÓGICA
-
 
126
 * ---------------------
-
 
127
 *   - Sempre GR -> G1 -> G2 -> G3.
-
 
128
 *   - Todos em candles diferentes, na ordem do tempo.
-
 
-
 
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.
129
 */
139
 */
130
public class DetectorGatilhos {
140
public class DetectorGatilhos {
131
141
132
    private final boolean logAtivo;
142
    private final boolean logAtivo;
133
    private int idxProximaAnaliseTempoReal = 0;
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;
134
150
135
    public DetectorGatilhos() {
151
    public DetectorGatilhos() {
136
        this(false);
-
 
-
 
152
        this(true);
137
    }
153
    }
138
154
139
    public DetectorGatilhos(boolean logAtivo) {
155
    public DetectorGatilhos(boolean logAtivo) {
140
        this.logAtivo = logAtivo;
156
        this.logAtivo = logAtivo;
141
    }
157
    }
142
158
143
    private void log(String msg) {
159
    private void log(String msg) {
144
        if (logAtivo) {
160
        if (logAtivo) {
145
            System.out.println(msg);
161
            System.out.println(msg);
-
 
162
        }
-
 
163
        if (bufferDebug != null) {
-
 
164
            bufferDebug.add(msg);
146
        }
165
        }
147
    }
166
    }
148
167
149
    // -------------------------------------------------------------
-
 
150
    // Estrutura interna de retorno
-
 
151
    // -------------------------------------------------------------
-
 
-
 
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
     */
152
    private static class ResultadoPadrao {
177
    private static class ResultadoPadrao {
153
        PadraoGatilho padrao;
178
        PadraoGatilho padrao;
154
        int lastIndex;
179
        int lastIndex;
-
 
180
        int proximoInicio;
155
181
156
        ResultadoPadrao(PadraoGatilho padrao, int lastIndex) {
-
 
-
 
182
        ResultadoPadrao(PadraoGatilho padrao, int lastIndex, int proximoInicio) {
157
            this.padrao = padrao;
183
            this.padrao = padrao;
158
            this.lastIndex = lastIndex;
184
            this.lastIndex = lastIndex;
-
 
185
            this.proximoInicio = proximoInicio;
159
        }
186
        }
160
    }
187
    }
161
188
162
    private ResultadoPadrao criarResultadoParcialComG2(Candle gr,
-
 
-
 
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,
163
                                                       Candle g1,
194
                                                       Candle g1,
164
                                                       Candle g2,
195
                                                       Candle g2,
165
                                                       int lastIndex) {
-
 
-
 
196
                                                       int lastIndex,
-
 
197
                                                       int idxGR) {
166
        PadraoGatilho padrao = new PadraoGatilho();
198
        PadraoGatilho padrao = new PadraoGatilho();
167
        padrao.setReferencia(gr);
-
 
-
 
199
        padrao.setReferencia(ref);
168
        padrao.setGatilho1(g1);
200
        padrao.setGatilho1(g1);
169
        padrao.setGatilho2(g2);
201
        padrao.setGatilho2(g2);
170
        padrao.setGatilho3(null);
202
        padrao.setGatilho3(null);
171
        padrao.setGatilho4(null); // G4 só na camada de trade
-
 
172
        return new ResultadoPadrao(padrao, lastIndex);
-
 
-
 
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));
173
    }
235
    }
174
236
175
    // =====================================================================
237
    // =====================================================================
176
    // API PRINCIPAL – BACKTEST
238
    // API PRINCIPAL – BACKTEST
177
    // =====================================================================
239
    // =====================================================================
Line 192... Line 254...
192
254
193
            if (resultado.padrao != null) {
255
            if (resultado.padrao != null) {
194
                padroes.add(resultado.padrao);
256
                padroes.add(resultado.padrao);
195
            }
257
            }
196
258
197
            idxRef = Math.max(resultado.lastIndex + 1, idxRef + 1);
-
 
-
 
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;
198
        }
268
        }
199
269
200
        return padroes;
270
        return padroes;
201
    }
271
    }
202
272
203
    private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) {
273
    private ResultadoPadrao detectarPadraoAPartir(List<Candle> candles, int idxRef) {
204
        Candle ref = candles.get(idxRef);
274
        Candle ref = candles.get(idxRef);
-
 
275
205
        if (ref.isCandleComprador()) {
276
        if (ref.isCandleComprador()) {
206
            return detectarPadraoComprador(candles, idxRef);
277
            return detectarPadraoComprador(candles, idxRef);
207
        } else if (ref.isCandleVendedor()) {
278
        } else if (ref.isCandleVendedor()) {
208
            return detectarPadraoVendedor(candles, idxRef);
279
            return detectarPadraoVendedor(candles, idxRef);
209
        } else {
280
        } else {
210
            return new ResultadoPadrao(null, idxRef);
-
 
-
 
281
            // Candle neutro não é A; não existe GR, avança 1 candle.
-
 
282
            return new ResultadoPadrao(null, idxRef, idxRef + 1);
211
        }
283
        }
212
    }
284
    }
213
285
214
    // =====================================================================
286
    // =====================================================================
215
    // HELPERS
-
 
-
 
287
    // PADRÃO COMPRADOR (OPERAÇÃO VENDEDORA)
216
    // =====================================================================
288
    // =====================================================================
217
289
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
    }
-
 
-
 
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
        }
225
297
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
    }
-
 
-
 
298
        int lastIndex = idxA;
-
 
299
        log(String.format("[VENDER] Iniciando busca a partir de A[%d]", idxA + 1));
233
300
234
    private boolean isDirecional(Candle c) {
-
 
235
        return c.isCandleComprador() || c.isCandleVendedor();
-
 
236
    }
-
 
-
 
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
        }
237
312
238
    // =====================================================================
-
 
239
    // CASO COMPRADOR – GR DINÂMICO PELA MÁXIMA
-
 
240
    // =====================================================================
-
 
241
-
 
242
    private ResultadoPadrao detectarPadraoComprador(List<Candle> candles, int idxInicio) {
-
 
243
        int n = candles.size();
-
 
244
        int lastIndex = idxInicio;
-
 
-
 
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
        }
245
317
246
        Candle candidatoRef = candles.get(idxInicio);
-
 
247
        if (!candidatoRef.isCandleComprador()) {
-
 
248
            return new ResultadoPadrao(null, idxInicio);
-
 
-
 
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);
249
        }
322
        }
250
323
251
        int idxCandidatoRef = idxInicio;
-
 
-
 
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));
252
328
253
        // --------------------
-
 
254
        // 1) Buscar G1: QUALQUER vendedor subsequente que rompa o fundo do candidatoRef
-
 
255
        //    (com GR dinâmico pela MÁXIMA antes de G1)
-
 
256
        // --------------------
-
 
-
 
329
        // 2) Busca G1, com referência dinâmica até G1
257
        Candle g1 = null;
330
        Candle g1 = null;
258
        int idxG1 = -1;
331
        int idxG1 = -1;
259
        Candle gr = null;
-
 
260
        int idxGR = -1;
-
 
261
332
262
        for (int i = idxInicio + 1; i < n; i++) {
-
 
-
 
333
        for (int i = idxA + 1; i < n; i++) {
263
            Candle c = candles.get(i);
334
            Candle c = candles.get(i);
264
            if (isInside(candles, i) || !isDirecional(c)) {
-
 
-
 
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));
265
                continue;
337
                continue;
266
            }
338
            }
267
339
268
            // OUTSIDE antes de G1 => descarta padrão
-
 
269
            if (isOutside(candles, i)) {
-
 
270
                lastIndex = i;
-
 
271
                log(String.format("Comprador: OUTSIDE em [%d] antes de G1. Abortando padrão.", i));
-
 
272
                return new ResultadoPadrao(null, lastIndex);
-
 
273
            }
-
 
274
-
 
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
            }
-
 
-
 
340
            lastIndex = i;
283
341
284
            // G1: QUALQUER vendedor subsequente que rompa o fundo do candidatoRef
-
 
-
 
342
            // Primeiro checa G1: vendedor que rompe fundo do candidatoRef
285
            if (c.isCandleVendedor()
343
            if (c.isCandleVendedor()
286
                    && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
344
                    && BigDecimalUtils.ehMenorQue(c.getMinima(), candidatoRef.getMinima())) {
287
                g1 = c;
345
                g1 = c;
288
                idxG1 = i;
346
                idxG1 = i;
289
                gr = candidatoRef;
-
 
290
                idxGR = idxCandidatoRef;
-
 
291
                lastIndex = i;
-
 
-
 
347
                log(String.format("[VENDER] G1 encontrado em [%d], rompendo fundo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1));
292
                break;
348
                break;
293
            }
349
            }
294
350
295
            lastIndex = i;
-
 
-
 
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
            }
296
        }
357
        }
297
358
298
        if (g1 == null || gr == null) {
-
 
299
            log(String.format("Comprador: não foi possível formar G1 a partir de idx %d.", idxInicio));
-
 
300
            return new ResultadoPadrao(null, lastIndex);
-
 
-
 
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);
301
        }
362
        }
302
363
303
        log(String.format("GR COMPRADOR em [%d], G1 (vendedor) em [%d].", idxGR, idxG1));
-
 
-
 
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()));
304
371
305
        // --------------------
-
 
306
        // 2) Buscar G2 – tendência COMPRADORA com fechamento dentro do GR
-
 
307
        // --------------------
-
 
-
 
372
        // 4) Busca G2 dinâmico e G3
308
        Candle g2 = null;
373
        Candle g2 = null;
309
        int idxG2 = -1;
374
        int idxG2 = -1;
-
 
375
        Candle g3 = null;
-
 
376
        int idxG3 = -1;
310
377
311
        int idxPrimeiroComprador = -1;
-
 
312
        for (int i = idxG1 + 1; i < n; i++) {
378
        for (int i = idxG1 + 1; i < n; i++) {
313
            Candle c = candles.get(i);
379
            Candle c = candles.get(i);
314
            if (isInside(candles, i) || !isDirecional(c))
-
 
-
 
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));
315
                continue;
382
                continue;
316
-
 
317
            // OUTSIDE antes de G2 => descarta padrão
-
 
318
            if (isOutside(candles, i)) {
-
 
319
                lastIndex = idxGR;
-
 
320
                log(String.format("GR[%d]: OUTSIDE em [%d] antes da tendência compradora de G2.", idxGR, i));
-
 
321
                return new ResultadoPadrao(null, lastIndex);
-
 
322
            }
383
            }
323
384
324
            // Regra global comprador: se romper topo do GR => descarta
-
 
325
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
-
 
326
                lastIndex = i;
-
 
327
                log(String.format("GR[%d]: candle[%d] rompeu TOPO do GR antes/na formação de G2.", idxGR, i));
-
 
328
                return new ResultadoPadrao(null, lastIndex);
-
 
329
            }
-
 
-
 
385
            lastIndex = i;
330
386
331
            if (c.isCandleComprador()) {
-
 
332
                idxPrimeiroComprador = i;
-
 
333
                break;
-
 
334
            } else {
-
 
335
                lastIndex = i;
-
 
336
            }
-
 
337
        }
-
 
-
 
387
            // --- Regras de DESCARTE após G1 (devem reiniciar do candle após o GR) ---
338
388
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
        }
-
 
343
-
 
344
        Candle ultimoComprador = null;
-
 
345
        int idxUltimoComprador = -1;
-
 
346
-
 
347
        for (int i = idxPrimeiroComprador; i < n; i++) {
-
 
348
            Candle c = candles.get(i);
-
 
349
            if (isInside(candles, i) || !isDirecional(c))
-
 
350
                continue;
-
 
351
-
 
352
            // OUTSIDE durante G2 => descarta padrão
-
 
353
            if (isOutside(candles, i)) {
-
 
354
                lastIndex = idxGR;
-
 
355
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (comprador).", idxGR, i));
-
 
356
                return new ResultadoPadrao(null, lastIndex);
-
 
357
            }
-
 
358
-
 
359
            // Regra global comprador: se romper topo do GR => descarta
-
 
-
 
389
            // 4.1) Se candle romper TOPO do GR => descarta operação
360
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
390
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
361
                lastIndex = i;
-
 
362
                log(String.format("GR[%d]: candle[%d] rompeu TOPO do GR durante G2.", idxGR, i));
-
 
363
                return new ResultadoPadrao(null, lastIndex);
-
 
-
 
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);
364
            }
393
            }
365
394
366
            if (c.isCandleComprador()) {
-
 
367
                ultimoComprador = c;
-
 
368
                idxUltimoComprador = i;
-
 
369
                lastIndex = i;
-
 
370
            } else if (c.isCandleVendedor()) {
-
 
371
                lastIndex = i - 1;
-
 
372
                break;
-
 
-
 
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);
373
            }
400
            }
374
        }
-
 
375
401
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
        }
-
 
380
-
 
381
        boolean fechamentoDentroRegiaoGR =
-
 
382
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoComprador.getFechamento(), gr.getMinima()) &&
-
 
383
                BigDecimalUtils.ehMenorOuIgualQue(ultimoComprador.getFechamento(), gr.getMaxima());
-
 
384
-
 
385
        if (!fechamentoDentroRegiaoGR) {
-
 
386
            log(String.format(
-
 
387
                    "GR[%d], G1[%d]: último comprador[%d] NÃO fechou dentro da região do GR.",
-
 
388
                    idxGR, idxG1, idxUltimoComprador
-
 
389
            ));
-
 
390
            return new ResultadoPadrao(null, lastIndex);
-
 
391
        }
-
 
392
-
 
393
        g2 = ultimoComprador;
-
 
394
        idxG2 = idxUltimoComprador;
-
 
395
        log(String.format("GR[%d], G1[%d] => G2 (comprador) em [%d].", idxGR, idxG1, idxG2));
-
 
396
-
 
397
        // --------------------
-
 
398
        // 3) Buscar G3 – NOVA REGRA:
-
 
399
        //    QUALQUER vendedor subsequente que rompa fundo de G2
-
 
400
        //    e tenha topo <= topo do GR.
-
 
401
        // --------------------
-
 
402
        Candle g3 = null;
-
 
403
        int idxG3 = -1;
-
 
404
-
 
405
        for (int i = idxG2 + 1; i < n; i++) {
-
 
406
            Candle c = candles.get(i);
-
 
407
            if (!isDirecional(c) || isInside(candles, i)) {
-
 
-
 
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
                }
408
                continue;
416
                continue;
409
            }
417
            }
410
418
411
            // OUTSIDE antes/durante busca de G3 => descarta padrão
-
 
412
            if (isOutside(candles, i)) {
-
 
413
                lastIndex = idxGR;
-
 
414
                log(String.format("GR[%d]: OUTSIDE em [%d] durante busca de G3 (vendedor).", idxGR, i));
-
 
415
                return new ResultadoPadrao(null, lastIndex);
-
 
416
            }
-
 
417
-
 
418
            // Regra global comprador: romper topo do GR => descarta
-
 
419
            if (BigDecimalUtils.ehMaiorQue(c.getMaxima(), gr.getMaxima())) {
-
 
420
                lastIndex = i;
-
 
421
                log(String.format("GR[%d]: candle[%d] rompeu TOPO do GR durante busca de G3.", idxGR, i));
-
 
422
                return new ResultadoPadrao(null, lastIndex);
-
 
423
            }
-
 
424
-
 
425
            if (c.isCandleVendedor()) {
-
 
-
 
419
            // --- G3: só pode ser avaliado após existir candidato G2 ---
-
 
420
            if (g2 != null && c.isCandleVendedor()) {
426
                boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
421
                boolean rompeFundoG2 = BigDecimalUtils.ehMenorQue(c.getMinima(), g2.getMinima());
427
                boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima());
422
                boolean topoMenorOuIgualGR = BigDecimalUtils.ehMenorOuIgualQue(c.getMaxima(), gr.getMaxima());
-
 
423
428
                if (rompeFundoG2 && topoMenorOuIgualGR) {
424
                if (rompeFundoG2 && topoMenorOuIgualGR) {
429
                    g3 = c;
425
                    g3 = c;
430
                    idxG3 = i;
426
                    idxG3 = i;
431
                    lastIndex = 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));
432
                    break;
429
                    break;
433
                }
430
                }
434
            }
431
            }
435
        }
432
        }
436
433
437
        if (g3 == null) {
434
        if (g3 == null) {
438
            log(String.format("GR[%d], G1[%d], G2[%d]: não houve G3 (comprador -> padrão parcial).",
-
 
439
                    idxGR, idxG1, idxG2));
-
 
440
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
-
 
-
 
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);
441
        }
443
        }
442
444
443
        log(String.format("GR[%d], G1[%d], G2[%d] => G3 (vendedor) em [%d].",
-
 
444
                idxGR, idxG1, idxG2, idxG3));
-
 
445
-
 
-
 
445
        // Finaliza padrão no G3
446
        PadraoGatilho padrao = new PadraoGatilho();
446
        PadraoGatilho padrao = new PadraoGatilho();
447
        padrao.setReferencia(gr);
447
        padrao.setReferencia(gr);
448
        padrao.setGatilho1(g1);
448
        padrao.setGatilho1(g1);
449
        padrao.setGatilho2(g2);
449
        padrao.setGatilho2(g2);
450
        padrao.setGatilho3(g3);
450
        padrao.setGatilho3(g3);
451
        padrao.setGatilho4(null);
451
        padrao.setGatilho4(null);
452
452
453
        return new ResultadoPadrao(padrao, lastIndex);
-
 
-
 
453
        // Padrão completo -> próxima busca deve começar após o GR
-
 
454
        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
454
    }
455
    }
455
456
456
    // =====================================================================
457
    // =====================================================================
457
    // CASO VENDEDOR – GR DINÂMICO PELA MÍNIMA
-
 
-
 
458
    // PADRÃO VENDEDOR (OPERAÇÃO COMPRADORA)
458
    // =====================================================================
459
    // =====================================================================
459
460
460
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxInicio) {
-
 
-
 
461
    private ResultadoPadrao detectarPadraoVendedor(List<Candle> candles, int idxA) {
461
        int n = candles.size();
462
        int n = candles.size();
462
        int lastIndex = idxInicio;
-
 
-
 
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
        }
463
468
464
        Candle candidatoRef = candles.get(idxInicio);
-
 
465
        if (!candidatoRef.isCandleVendedor()) {
-
 
466
            return new ResultadoPadrao(null, idxInicio);
-
 
-
 
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);
467
        }
493
        }
468
494
469
        int idxCandidatoRef = idxInicio;
-
 
-
 
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));
470
499
471
        // --------------------
-
 
472
        // 1) Buscar G1: QUALQUER comprador subsequente que rompa o topo do candidatoRef
-
 
473
        //    (com GR dinâmico pela MÍNIMA antes de G1)
-
 
474
        // --------------------
-
 
-
 
500
        // 2) Busca G1, com referência dinâmica até G1
475
        Candle g1 = null;
501
        Candle g1 = null;
476
        int idxG1 = -1;
502
        int idxG1 = -1;
477
        Candle gr = null;
-
 
478
        int idxGR = -1;
-
 
479
503
480
        for (int i = idxInicio + 1; i < n; i++) {
-
 
-
 
504
        for (int i = idxA + 1; i < n; i++) {
481
            Candle c = candles.get(i);
505
            Candle c = candles.get(i);
482
            if (isInside(candles, i) || !isDirecional(c)) {
-
 
-
 
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));
483
                continue;
508
                continue;
484
            }
509
            }
485
510
486
            // OUTSIDE antes de G1 => descarta padrão
-
 
487
            if (isOutside(candles, i)) {
-
 
488
                lastIndex = i;
-
 
489
                log(String.format("Vendedor: OUTSIDE em [%d] antes de G1. Abortando padrão.", i));
-
 
490
                return new ResultadoPadrao(null, lastIndex);
-
 
491
            }
-
 
492
-
 
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
            }
-
 
-
 
511
            lastIndex = i;
501
512
502
            // G1: QUALQUER comprador subsequente que rompa o topo do candidatoRef
-
 
-
 
513
            // Primeiro checa G1: comprador que rompe topo do candidatoRef
503
            if (c.isCandleComprador()
514
            if (c.isCandleComprador()
504
                    && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
515
                    && BigDecimalUtils.ehMaiorQue(c.getMaxima(), candidatoRef.getMaxima())) {
505
                g1 = c;
516
                g1 = c;
506
                idxG1 = i;
517
                idxG1 = i;
507
                gr = candidatoRef;
-
 
508
                idxGR = idxCandidatoRef;
-
 
509
                lastIndex = i;
-
 
-
 
518
                log(String.format("[COMPRAR] G1 encontrado em [%d], rompendo topo de candRef[%d]", idxG1 + 1, idxCandidatoRef + 1));
510
                break;
519
                break;
511
            }
520
            }
512
521
513
            lastIndex = i;
-
 
-
 
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
            }
514
        }
528
        }
515
529
516
        if (g1 == null || gr == null) {
-
 
517
            log(String.format("Vendedor: não foi possível formar G1 a partir de idx %d.", idxInicio));
-
 
518
            return new ResultadoPadrao(null, lastIndex);
-
 
-
 
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);
519
        }
533
        }
520
534
521
        log(String.format("GR VENDEDOR em [%d], G1 (comprador) em [%d].", idxGR, idxG1));
-
 
-
 
535
        Candle gr = candidatoRef;
-
 
536
        int idxGR = idxCandidatoRef;
-
 
537
        log(String.format("[COMPRAR] GR definido em [%d], G1 em [%d]", idxGR + 1, idxG1 + 1));
522
538
523
        // --------------------
-
 
524
        // 2) Buscar G2 – tendência VENDEDORA com fechamento dentro do GR
-
 
525
        // --------------------
-
 
-
 
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
526
        Candle g2 = null;
544
        Candle g2 = null;
527
        int idxG2 = -1;
545
        int idxG2 = -1;
-
 
546
        Candle g3 = null;
-
 
547
        int idxG3 = -1;
528
548
529
        int idxPrimeiroVendedor = -1;
-
 
530
        for (int i = idxG1 + 1; i < n; i++) {
549
        for (int i = idxG1 + 1; i < n; i++) {
531
            Candle c = candles.get(i);
550
            Candle c = candles.get(i);
532
            if (isInside(candles, i) || !isDirecional(c)) continue;
-
 
533
-
 
534
            // OUTSIDE antes de G2 => descarta padrão
-
 
535
            if (isOutside(candles, i)) {
-
 
536
                lastIndex = idxGR;
-
 
537
                log(String.format("GR[%d]: OUTSIDE em [%d] antes da tendência vendedora de G2.", idxGR, i));
-
 
538
                return new ResultadoPadrao(null, lastIndex);
-
 
-
 
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;
539
            }
554
            }
540
555
541
            // Regra global vendedor: romper fundo do GR => descarta
-
 
542
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
-
 
543
                lastIndex = i;
-
 
544
                log(String.format("GR[%d]: candle[%d] rompeu FUNDO do GR antes/na formação de G2.", idxGR, i));
-
 
545
                return new ResultadoPadrao(null, lastIndex);
-
 
546
            }
-
 
-
 
556
            lastIndex = i;
547
557
548
            if (c.isCandleVendedor()) {
-
 
549
                idxPrimeiroVendedor = i;
-
 
550
                break;
-
 
551
            } else {
-
 
552
                lastIndex = i;
-
 
553
            }
-
 
554
        }
-
 
555
-
 
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
-
 
561
        Candle ultimoVendedor = null;
-
 
562
        int idxUltimoVendedor = -1;
-
 
563
-
 
564
        for (int i = idxPrimeiroVendedor; i < n; i++) {
-
 
565
            Candle c = candles.get(i);
-
 
566
            if (isInside(candles, i) || !isDirecional(c)) continue;
-
 
567
-
 
568
            // OUTSIDE durante G2 => descarta padrão
-
 
569
            if (isOutside(candles, i)) {
-
 
570
                lastIndex = idxGR;
-
 
571
                log(String.format("GR[%d]: OUTSIDE em [%d] durante formação de G2 (vendedor).", idxGR, i));
-
 
572
                return new ResultadoPadrao(null, lastIndex);
-
 
573
            }
-
 
-
 
558
            // --- Regras de DESCARTE após G1 (reiniciar após GR) ---
574
559
575
            // Regra global vendedor: romper fundo do GR => descarta
-
 
-
 
560
            // 4.1) Se candle romper FUNDO do GR => descarta operação
576
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
561
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
577
                lastIndex = i;
-
 
578
                log(String.format("GR[%d]: candle[%d] rompeu FUNDO do GR durante G2.", idxGR, i));
-
 
579
                return new ResultadoPadrao(null, lastIndex);
-
 
-
 
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);
580
            }
564
            }
581
565
582
            if (c.isCandleVendedor()) {
-
 
583
                ultimoVendedor = c;
-
 
584
                idxUltimoVendedor = i;
-
 
585
                lastIndex = i;
-
 
586
            } else if (c.isCandleComprador()) {
-
 
587
                lastIndex = i - 1;
-
 
588
                break;
-
 
-
 
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);
589
            }
571
            }
590
        }
-
 
591
572
592
        if (ultimoVendedor == null) {
-
 
593
            log(String.format("GR[%d], G1[%d]: não houve vendedor válido para G2.", idxGR, idxG1));
-
 
594
            return new ResultadoPadrao(null, lastIndex);
-
 
595
        }
-
 
596
-
 
597
        boolean fechamentoDentroRegiaoGR =
-
 
598
                BigDecimalUtils.ehMaiorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMinima()) &&
-
 
599
                BigDecimalUtils.ehMenorOuIgualQue(ultimoVendedor.getFechamento(), gr.getMaxima());
-
 
600
-
 
601
        if (!fechamentoDentroRegiaoGR) {
-
 
602
            log(String.format(
-
 
603
                    "GR[%d], G1[%d]: último vendedor[%d] NÃO fechou dentro da região do GR.",
-
 
604
                    idxGR, idxG1, idxUltimoVendedor
-
 
605
            ));
-
 
606
            return new ResultadoPadrao(null, lastIndex);
-
 
607
        }
-
 
608
-
 
609
        g2 = ultimoVendedor;
-
 
610
        idxG2 = idxUltimoVendedor;
-
 
611
        log(String.format("GR[%d], G1[%d] => G2 (vendedor) em [%d].", idxGR, idxG1, idxG2));
-
 
612
-
 
613
        // --------------------
-
 
614
        // 3) Buscar G3 – NOVA REGRA:
-
 
615
        //    QUALQUER comprador subsequente que rompa topo de G2
-
 
616
        //    e tenha fundo >= fundo do GR.
-
 
617
        // --------------------
-
 
618
        Candle g3 = null;
-
 
619
        int idxG3 = -1;
-
 
620
-
 
621
        for (int i = idxG2 + 1; i < n; i++) {
-
 
622
            Candle c = candles.get(i);
-
 
623
            if (!isDirecional(c) || isInside(candles, i)) {
-
 
-
 
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
                }
624
                continue;
587
                continue;
625
            }
588
            }
626
589
627
            // OUTSIDE antes/durante busca de G3 => descarta padrão
-
 
628
            if (isOutside(candles, i)) {
-
 
629
                lastIndex = idxGR;
-
 
630
                log(String.format("GR[%d]: OUTSIDE em [%d] durante busca de G3 (comprador).", idxGR, i));
-
 
631
                return new ResultadoPadrao(null, lastIndex);
-
 
632
            }
-
 
-
 
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());
633
594
634
            // Regra global vendedor: romper fundo do GR => descarta
-
 
635
            if (BigDecimalUtils.ehMenorQue(c.getMinima(), gr.getMinima())) {
-
 
636
                lastIndex = i;
-
 
637
                log(String.format("GR[%d]: candle[%d] rompeu FUNDO do GR durante busca de G3.", idxGR, i));
-
 
638
                return new ResultadoPadrao(null, lastIndex);
-
 
639
            }
-
 
640
-
 
641
            if (c.isCandleComprador()) {
-
 
642
                boolean rompeTopoG2 = BigDecimalUtils.ehMaiorQue(c.getMaxima(), g2.getMaxima());
-
 
643
                boolean fundoMaiorOuIgualGR = BigDecimalUtils.ehMaiorOuIgualQue(c.getMinima(), gr.getMinima());
-
 
644
                if (rompeTopoG2 && fundoMaiorOuIgualGR) {
-
 
-
 
595
                if (rompeTopoG2 && fundoMenorOuIgualGR) {
645
                    g3 = c;
596
                    g3 = c;
646
                    idxG3 = i;
597
                    idxG3 = i;
647
                    lastIndex = 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));
648
                    break;
600
                    break;
649
                }
601
                }
650
            }
602
            }
651
        }
603
        }
652
604
653
        if (g3 == null) {
605
        if (g3 == null) {
654
            log(String.format("GR[%d], G1[%d], G2[%d]: não houve G3 (vendedor -> padrão parcial).",
-
 
655
                    idxGR, idxG1, idxG2));
-
 
656
            return criarResultadoParcialComG2(gr, g1, g2, lastIndex);
-
 
-
 
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);
657
        }
614
        }
658
615
659
        log(String.format("GR[%d], G1[%d], G2[%d] => G3 (comprador) em [%d].",
-
 
660
                idxGR, idxG1, idxG2, idxG3));
-
 
661
-
 
-
 
616
        // Finaliza padrão no G3
662
        PadraoGatilho padrao = new PadraoGatilho();
617
        PadraoGatilho padrao = new PadraoGatilho();
663
        padrao.setReferencia(gr);
618
        padrao.setReferencia(gr);
664
        padrao.setGatilho1(g1);
619
        padrao.setGatilho1(g1);
665
        padrao.setGatilho2(g2);
620
        padrao.setGatilho2(g2);
666
        padrao.setGatilho3(g3);
621
        padrao.setGatilho3(g3);
667
        padrao.setGatilho4(null);
622
        padrao.setGatilho4(null);
668
623
669
        return new ResultadoPadrao(padrao, lastIndex);
-
 
-
 
624
        // Padrão completo -> próxima busca deve começar após o GR
-
 
625
        return new ResultadoPadrao(padrao, idxG3, idxGR + 1);
670
    }
626
    }
671
627
672
    // =====================================================================
628
    // =====================================================================
673
    // MODO TEMPO REAL
629
    // MODO TEMPO REAL
674
    // =====================================================================
630
    // =====================================================================
Line 698... Line 654...
698
            if (resultado == null) {
654
            if (resultado == null) {
699
                idxProximaAnaliseTempoReal++;
655
                idxProximaAnaliseTempoReal++;
700
                continue;
656
                continue;
701
            }
657
            }
702
658
703
            int proximoInicio = resultado.lastIndex + 1;
-
 
-
 
659
            int proximoInicio = resultado.proximoInicio;
704
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
660
            if (proximoInicio <= idxProximaAnaliseTempoReal) {
705
                proximoInicio = idxProximaAnaliseTempoReal + 1;
661
                proximoInicio = idxProximaAnaliseTempoReal + 1;
706
            }
662
            }
707
            idxProximaAnaliseTempoReal = proximoInicio;
663
            idxProximaAnaliseTempoReal = proximoInicio;
708
664
Line 710... Line 666...
710
                return resultado.padrao;
666
                return resultado.padrao;
711
            }
667
            }
712
        }
668
        }
713
669
714
        return null;
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;
715
    }
697
    }
716
698
717
}
699
}