Subversion Repositories Integrator Subversion

Rev

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

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