Subversion Repositories Integrator Subversion

Rev

Rev 776 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
776 blopes 1
package br.com.kronus.core;
2
 
3
import java.math.BigDecimal;
4
import java.util.ArrayList;
5
import java.util.List;
6
 
779 blopes 7
import br.com.ec.web.util.TipoOperacao;
776 blopes 8
import br.com.kronus.core.ResultadoSinalGatilho3.Status;
9
import br.com.sl.domain.dto.robo.SinalTradeGatilho3;
10
import br.com.sl.domain.model.Candle;
779 blopes 11
import br.com.sl.domain.model.tipos.TipoSinal;
776 blopes 12
import br.com.sl.domain.util.BigDecimalUtils;
13
 
14
/**
15
 * Faz o backtest dos sinais gerados pela EstrategiaGatilhoTipo3Sinais,
16
 * informando para cada sinal se deu alvo/stop/não acionou etc.
17
 *
18
 * Regras principais:
19
 *  - A avaliação começa a partir do candle seguinte ao G3.
20
 *  - Se o primeiro alvo (alvo1) for atingido ANTES de qualquer entrada,
21
 *    a operação é DESCARTADA (Status.DESCARTADO).
22
 *  - Stop é calculado combinando stopMenos100 e stopAlternativo:
23
 *      * COMPRA: stop = max(stopMenos100, stopAlternativo) -> stop mais apertado (mais próximo da entrada)
24
 *      * VENDA:  stop = min(stopMenos100, stopAlternativo) -> stop mais apertado (mais próximo da entrada)
25
 *  - Após a entrada, a posição é encerrada no primeiro evento: STOP, ALVO2 ou ALVO1.
26
 */
27
public class AvaliadorSinaisGatilho3 {
28
 
29
    /**
30
     * Avalia uma lista de sinais sobre uma lista de candles.
31
     */
32
    public List<ResultadoSinalGatilho3> avaliarTodos(List<SinalTradeGatilho3> sinais, List<Candle> candles) {
33
        List<ResultadoSinalGatilho3> resultados = new ArrayList<>();
34
        if (sinais == null || sinais.isEmpty()) {
35
            return resultados;
36
        }
37
 
38
        for (SinalTradeGatilho3 sinal : sinais) {
39
            ResultadoSinalGatilho3 r = avaliarSinal(sinal, candles);
40
            resultados.add(r);
41
        }
42
 
43
        return resultados;
44
    }
45
 
46
    /**
47
     * Avalia UM sinal específico no histórico de candles.
48
     *
49
     * Lógica:
50
     *  - Começa a análise a partir do candle do G3 (gatilho 3) do padrão do sinal.
51
     *  - Primeiro verifica se o ALVO1 é atingido ANTES de qualquer entrada -> DESCARTADO.
52
     *  - Depois, se aciona a entrada (precoEntrada1).
53
     *  - Após a entrada, vê quem vem primeiro: STOP, ALVO2 ou ALVO1.
54
     */
55
    public ResultadoSinalGatilho3 avaliarSinal(SinalTradeGatilho3 sinal, List<Candle> candles) {
56
        if (sinal == null || candles == null || candles.isEmpty()) {
57
            return new ResultadoSinalGatilho3(
58
                    sinal,
59
                    Status.NAO_ACIONADO,
60
                    false,
61
                    -1,
62
                    -1,
63
                    null,
64
                    null
65
            );
66
        }
67
 
68
        // Descobre o índice do candle G3 dentro da lista de candles
69
        Candle candleG3 = sinal.getGatilho3();
70
        int idxG3 = candles.indexOf(candleG3);
71
 
72
        if (idxG3 < 0) {
73
            // Se por algum motivo o candle G3 não estiver na lista, considera que não deu para avaliar
74
            return new ResultadoSinalGatilho3(
75
                    sinal,
76
                    Status.NAO_ACIONADO,
77
                    false,
78
                    -1,
79
                    -1,
80
                    null,
81
                    null
82
            );
83
        }
84
 
85
        // Começamos a avaliar a partir do candle SEGUINTE ao G3
86
        int inicio = idxG3 + 1;
87
        if (inicio >= candles.size()) {
88
            // Não há candles após o G3
89
            return new ResultadoSinalGatilho3(
90
                    sinal,
91
                    Status.NAO_ACIONADO,
92
                    false,
93
                    -1,
94
                    -1,
95
                    null,
96
                    null
97
            );
98
        }
99
 
779 blopes 100
        TipoSinal tipo = sinal.getTipoOperacao();
776 blopes 101
        BigDecimal precoEntrada = sinal.getPrecoEntrada1(); // usando a 1ª entrada como referência
102
        BigDecimal alvo1 = sinal.getAlvo1();
103
        BigDecimal alvo2 = sinal.getAlvo2();
104
 
105
        // Combinação de stops: stopMenos100 + stopAlternativo
106
        BigDecimal stopMenos100 = sinal.getStopMenos100();
107
        BigDecimal stopAlternativo = sinal.getStopAlternativo();
108
        BigDecimal stop;
109
 
779 blopes 110
        if (tipo == TipoSinal.COMPRA_C) {
776 blopes 111
            // Em compra, ambos devem estar abaixo da entrada.
112
            // Stop mais apertado = o MAIOR dos dois (mais próximo da entrada).
113
            if (stopMenos100 != null && stopAlternativo != null) {
114
                stop = stopMenos100.max(stopAlternativo);
115
            } else if (stopMenos100 != null) {
116
                stop = stopMenos100;
117
            } else {
118
                stop = stopAlternativo; // pode ser null -> tratamos mais abaixo
119
            }
120
        } else { // VENDA
121
            // Em venda, ambos devem estar acima da entrada.
122
            // Stop mais apertado = o MENOR dos dois (mais próximo da entrada).
123
            if (stopMenos100 != null && stopAlternativo != null) {
124
                stop = stopMenos100.min(stopAlternativo);
125
            } else if (stopMenos100 != null) {
126
                stop = stopMenos100;
127
            } else {
128
                stop = stopAlternativo;
129
            }
130
        }
131
 
132
        // Se por alguma razão o stop ainda for null, é mais seguro considerar que não há stop definido.
133
        // Neste caso, a operação só encerrará por alvo (ou ficará em ABERTO até o fim do histórico).
134
        boolean temStop = (stop != null);
135
 
136
        boolean entradaAcionada = false;
137
        int idxEntrada = -1;
138
        BigDecimal precoEntradaEfetivo = null;
139
 
140
        // LOOP nos candles após o G3
141
        for (int i = inicio; i < candles.size(); i++) {
142
            Candle c = candles.get(i);
143
            BigDecimal max = c.getMaxima();
144
            BigDecimal min = c.getMinima();
145
 
146
            // ============================
147
            // 1) Antes de entrar na operação
148
            // ============================
149
            if (!entradaAcionada) {
150
 
151
                // 1.1) Regra: se o PRIMEIRO ALVO for atingido ANTES da entrada -> DESCARTAR OPERAÇÃO
779 blopes 152
                if (tipo == TipoSinal.COMPRA_C) {
776 blopes 153
                    // Para compra, alvo1 é para cima; se máxima >= alvo1, consideramos tocado
154
                    if (BigDecimalUtils.ehMaiorOuIgualQue(max, alvo1)) {
155
                        return new ResultadoSinalGatilho3(
156
                                sinal,
157
                                Status.DESCARTADO,
158
                                false,
159
                                -1,
160
                                i,
161
                                null,
162
                                alvo1
163
                        );
164
                    }
165
                } else { // VENDA
166
                    // Para venda, alvo1 é para baixo; se mínima <= alvo1, consideramos tocado
167
                    if (BigDecimalUtils.ehMenorOuIgualQue(min, alvo1)) {
168
                        return new ResultadoSinalGatilho3(
169
                                sinal,
170
                                Status.DESCARTADO,
171
                                false,
172
                                -1,
173
                                i,
174
                                null,
175
                                alvo1
176
                        );
177
                    }
178
                }
179
 
180
                // 1.2) Se não descartar, verifica se aciona a entrada
181
                boolean tocaEntrada =
182
                        BigDecimalUtils.ehMenorOuIgualQue(min, precoEntrada) &&
183
                        BigDecimalUtils.ehMaiorOuIgualQue(max, precoEntrada);
184
 
185
                if (tocaEntrada) {
186
                    entradaAcionada = true;
187
                    idxEntrada = i;
188
                    precoEntradaEfetivo = precoEntrada;
189
                }
190
 
191
                // Ainda não entrou -> segue para o próximo candle
192
                continue;
193
            }
194
 
195
            // ============================
196
            // 2) Após a entrada ter sido acionada
197
            // ============================
779 blopes 198
            if (tipo == TipoSinal.COMPRA_C) {
776 blopes 199
                // COMPRA: stop é para baixo, alvos para cima
200
 
201
                boolean bateStop = temStop && BigDecimalUtils.ehMenorOuIgualQue(min, stop);
202
                boolean bateAlvo2 = BigDecimalUtils.ehMaiorOuIgualQue(max, alvo2);
203
                boolean bateAlvo1 = BigDecimalUtils.ehMaiorOuIgualQue(max, alvo1);
204
 
205
                // Se, no MESMO candle, tanto stop quanto alvo forem possíveis,
206
                // aqui estamos sendo conservadores: prioriza STOP primeiro.
207
                if (bateStop) {
208
                    return new ResultadoSinalGatilho3(
209
                            sinal,
210
                            Status.STOP,
211
                            false,
212
                            idxEntrada,
213
                            i,
214
                            precoEntradaEfetivo,
215
                            stop
216
                    );
217
                }
218
 
219
                if (bateAlvo2) {
220
                    return new ResultadoSinalGatilho3(
221
                            sinal,
222
                            Status.ALVO2,
223
                            true,
224
                            idxEntrada,
225
                            i,
226
                            precoEntradaEfetivo,
227
                            alvo2
228
                    );
229
                }
230
 
231
                if (bateAlvo1) {
232
                    return new ResultadoSinalGatilho3(
233
                            sinal,
234
                            Status.ALVO1,
235
                            true,
236
                            idxEntrada,
237
                            i,
238
                            precoEntradaEfetivo,
239
                            alvo1
240
                    );
241
                }
242
 
243
            } else { // VENDA
244
 
245
                // VENDA: stop é para cima, alvos para baixo
246
                boolean bateStop = temStop && BigDecimalUtils.ehMaiorOuIgualQue(max, stop);
247
                boolean bateAlvo2 = BigDecimalUtils.ehMenorOuIgualQue(min, alvo2);
248
                boolean bateAlvo1 = BigDecimalUtils.ehMenorOuIgualQue(min, alvo1);
249
 
250
                // Conservador: se stop e alvo possíveis no mesmo candle, prioriza STOP.
251
                if (bateStop) {
252
                    return new ResultadoSinalGatilho3(
253
                            sinal,
254
                            Status.STOP,
255
                            false,
256
                            idxEntrada,
257
                            i,
258
                            precoEntradaEfetivo,
259
                            stop
260
                    );
261
                }
262
 
263
                if (bateAlvo2) {
264
                    return new ResultadoSinalGatilho3(
265
                            sinal,
266
                            Status.ALVO2,
267
                            true,
268
                            idxEntrada,
269
                            i,
270
                            precoEntradaEfetivo,
271
                            alvo2
272
                    );
273
                }
274
 
275
                if (bateAlvo1) {
276
                    return new ResultadoSinalGatilho3(
277
                            sinal,
278
                            Status.ALVO1,
279
                            true,
280
                            idxEntrada,
281
                            i,
282
                            precoEntradaEfetivo,
283
                            alvo1
284
                    );
285
                }
286
            }
287
        }
288
 
289
        // Saiu do loop sem bater alvo/stop
290
        if (!entradaAcionada) {
291
            return new ResultadoSinalGatilho3(
292
                    sinal,
293
                    Status.NAO_ACIONADO,
294
                    false,
295
                    -1,
296
                    -1,
297
                    null,
298
                    null
299
            );
300
        } else {
301
            // Entrada aconteceu, mas nem alvo nem stop foram atingidos até o fim do histórico
302
            return new ResultadoSinalGatilho3(
303
                    sinal,
304
                    Status.ABERTO,
305
                    false,
306
                    idxEntrada,
307
                    candles.size() - 1,
308
                    precoEntradaEfetivo,
309
                    null
310
            );
311
        }
312
    }
313
}