Subversion Repositories Integrator Subversion

Rev

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

package br.com.kronus.core;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import br.com.ec.web.util.TipoOperacao;
import br.com.kronus.core.ResultadoSinalGatilho3.Status;
import br.com.sl.domain.dto.robo.SinalTradeGatilho3;
import br.com.sl.domain.model.Candle;
import br.com.sl.domain.model.tipos.TipoSinal;
import br.com.sl.domain.util.BigDecimalUtils;

/**
 * Faz o backtest dos sinais gerados pela EstrategiaGatilhoTipo3Sinais,
 * informando para cada sinal se deu alvo/stop/não acionou etc.
 *
 * Regras principais:
 *  - A avaliação começa a partir do candle seguinte ao G3.
 *  - Se o primeiro alvo (alvo1) for atingido ANTES de qualquer entrada,
 *    a operação é DESCARTADA (Status.DESCARTADO).
 *  - Stop é calculado combinando stopMenos100 e stopAlternativo:
 *      * COMPRA: stop = max(stopMenos100, stopAlternativo) -> stop mais apertado (mais próximo da entrada)
 *      * VENDA:  stop = min(stopMenos100, stopAlternativo) -> stop mais apertado (mais próximo da entrada)
 *  - Após a entrada, a posição é encerrada no primeiro evento: STOP, ALVO2 ou ALVO1.
 */

public class AvaliadorSinaisGatilho3 {

    /**
     * Avalia uma lista de sinais sobre uma lista de candles.
     */

    public List<ResultadoSinalGatilho3> avaliarTodos(List<SinalTradeGatilho3> sinais, List<Candle> candles) {
        List<ResultadoSinalGatilho3> resultados = new ArrayList<>();
        if (sinais == null || sinais.isEmpty()) {
            return resultados;
        }

        for (SinalTradeGatilho3 sinal : sinais) {
            ResultadoSinalGatilho3 r = avaliarSinal(sinal, candles);
            resultados.add(r);
        }

        return resultados;
    }

    /**
     * Avalia UM sinal específico no histórico de candles.
     *
     * Lógica:
     *  - Começa a análise a partir do candle do G3 (gatilho 3) do padrão do sinal.
     *  - Primeiro verifica se o ALVO1 é atingido ANTES de qualquer entrada -> DESCARTADO.
     *  - Depois, se aciona a entrada (precoEntrada1).
     *  - Após a entrada, vê quem vem primeiro: STOP, ALVO2 ou ALVO1.
     */

    public ResultadoSinalGatilho3 avaliarSinal(SinalTradeGatilho3 sinal, List<Candle> candles) {
        if (sinal == null || candles == null || candles.isEmpty()) {
            return new ResultadoSinalGatilho3(
                    sinal,
                    Status.NAO_ACIONADO,
                    false,
                    -1,
                    -1,
                    null,
                    null
            );
        }

        // Descobre o índice do candle G3 dentro da lista de candles
        Candle candleG3 = sinal.getGatilho3();
        int idxG3 = candles.indexOf(candleG3);

        if (idxG3 < 0) {
            // Se por algum motivo o candle G3 não estiver na lista, considera que não deu para avaliar
            return new ResultadoSinalGatilho3(
                    sinal,
                    Status.NAO_ACIONADO,
                    false,
                    -1,
                    -1,
                    null,
                    null
            );
        }

        // Começamos a avaliar a partir do candle SEGUINTE ao G3
        int inicio = idxG3 + 1;
        if (inicio >= candles.size()) {
            // Não há candles após o G3
            return new ResultadoSinalGatilho3(
                    sinal,
                    Status.NAO_ACIONADO,
                    false,
                    -1,
                    -1,
                    null,
                    null
            );
        }

        TipoSinal tipo = sinal.getTipoOperacao();
        BigDecimal precoEntrada = sinal.getPrecoEntrada1(); // usando a 1ª entrada como referência
        BigDecimal alvo1 = sinal.getAlvo1();
        BigDecimal alvo2 = sinal.getAlvo2();

        // Combinação de stops: stopMenos100 + stopAlternativo
        BigDecimal stopMenos100 = sinal.getStopMenos100();
        BigDecimal stopAlternativo = sinal.getStopAlternativo();
        BigDecimal stop;

        if (tipo == TipoSinal.COMPRA_C) {
            // Em compra, ambos devem estar abaixo da entrada.
            // Stop mais apertado = o MAIOR dos dois (mais próximo da entrada).
            if (stopMenos100 != null && stopAlternativo != null) {
                stop = stopMenos100.max(stopAlternativo);
            } else if (stopMenos100 != null) {
                stop = stopMenos100;
            } else {
                stop = stopAlternativo; // pode ser null -> tratamos mais abaixo
            }
        } else { // VENDA
            // Em venda, ambos devem estar acima da entrada.
            // Stop mais apertado = o MENOR dos dois (mais próximo da entrada).
            if (stopMenos100 != null && stopAlternativo != null) {
                stop = stopMenos100.min(stopAlternativo);
            } else if (stopMenos100 != null) {
                stop = stopMenos100;
            } else {
                stop = stopAlternativo;
            }
        }

        // Se por alguma razão o stop ainda for null, é mais seguro considerar que não há stop definido.
        // Neste caso, a operação só encerrará por alvo (ou ficará em ABERTO até o fim do histórico).
        boolean temStop = (stop != null);

        boolean entradaAcionada = false;
        int idxEntrada = -1;
        BigDecimal precoEntradaEfetivo = null;

        // LOOP nos candles após o G3
        for (int i = inicio; i < candles.size(); i++) {
            Candle c = candles.get(i);
            BigDecimal max = c.getMaxima();
            BigDecimal min = c.getMinima();

            // ============================
            // 1) Antes de entrar na operação
            // ============================
            if (!entradaAcionada) {

                // 1.1) Regra: se o PRIMEIRO ALVO for atingido ANTES da entrada -> DESCARTAR OPERAÇÃO
                if (tipo == TipoSinal.COMPRA_C) {
                    // Para compra, alvo1 é para cima; se máxima >= alvo1, consideramos tocado
                    if (BigDecimalUtils.ehMaiorOuIgualQue(max, alvo1)) {
                        return new ResultadoSinalGatilho3(
                                sinal,
                                Status.DESCARTADO,
                                false,
                                -1,
                                i,
                                null,
                                alvo1
                        );
                    }
                } else { // VENDA
                    // Para venda, alvo1 é para baixo; se mínima <= alvo1, consideramos tocado
                    if (BigDecimalUtils.ehMenorOuIgualQue(min, alvo1)) {
                        return new ResultadoSinalGatilho3(
                                sinal,
                                Status.DESCARTADO,
                                false,
                                -1,
                                i,
                                null,
                                alvo1
                        );
                    }
                }

                // 1.2) Se não descartar, verifica se aciona a entrada
                boolean tocaEntrada =
                        BigDecimalUtils.ehMenorOuIgualQue(min, precoEntrada) &&
                        BigDecimalUtils.ehMaiorOuIgualQue(max, precoEntrada);

                if (tocaEntrada) {
                    entradaAcionada = true;
                    idxEntrada = i;
                    precoEntradaEfetivo = precoEntrada;
                }

                // Ainda não entrou -> segue para o próximo candle
                continue;
            }

            // ============================
            // 2) Após a entrada ter sido acionada
            // ============================
            if (tipo == TipoSinal.COMPRA_C) {
                // COMPRA: stop é para baixo, alvos para cima

                boolean bateStop = temStop && BigDecimalUtils.ehMenorOuIgualQue(min, stop);
                boolean bateAlvo2 = BigDecimalUtils.ehMaiorOuIgualQue(max, alvo2);
                boolean bateAlvo1 = BigDecimalUtils.ehMaiorOuIgualQue(max, alvo1);

                // Se, no MESMO candle, tanto stop quanto alvo forem possíveis,
                // aqui estamos sendo conservadores: prioriza STOP primeiro.
                if (bateStop) {
                    return new ResultadoSinalGatilho3(
                            sinal,
                            Status.STOP,
                            false,
                            idxEntrada,
                            i,
                            precoEntradaEfetivo,
                            stop
                    );
                }

                if (bateAlvo2) {
                    return new ResultadoSinalGatilho3(
                            sinal,
                            Status.ALVO2,
                            true,
                            idxEntrada,
                            i,
                            precoEntradaEfetivo,
                            alvo2
                    );
                }

                if (bateAlvo1) {
                    return new ResultadoSinalGatilho3(
                            sinal,
                            Status.ALVO1,
                            true,
                            idxEntrada,
                            i,
                            precoEntradaEfetivo,
                            alvo1
                    );
                }

            } else { // VENDA

                // VENDA: stop é para cima, alvos para baixo
                boolean bateStop = temStop && BigDecimalUtils.ehMaiorOuIgualQue(max, stop);
                boolean bateAlvo2 = BigDecimalUtils.ehMenorOuIgualQue(min, alvo2);
                boolean bateAlvo1 = BigDecimalUtils.ehMenorOuIgualQue(min, alvo1);

                // Conservador: se stop e alvo possíveis no mesmo candle, prioriza STOP.
                if (bateStop) {
                    return new ResultadoSinalGatilho3(
                            sinal,
                            Status.STOP,
                            false,
                            idxEntrada,
                            i,
                            precoEntradaEfetivo,
                            stop
                    );
                }

                if (bateAlvo2) {
                    return new ResultadoSinalGatilho3(
                            sinal,
                            Status.ALVO2,
                            true,
                            idxEntrada,
                            i,
                            precoEntradaEfetivo,
                            alvo2
                    );
                }

                if (bateAlvo1) {
                    return new ResultadoSinalGatilho3(
                            sinal,
                            Status.ALVO1,
                            true,
                            idxEntrada,
                            i,
                            precoEntradaEfetivo,
                            alvo1
                    );
                }
            }
        }

        // Saiu do loop sem bater alvo/stop
        if (!entradaAcionada) {
            return new ResultadoSinalGatilho3(
                    sinal,
                    Status.NAO_ACIONADO,
                    false,
                    -1,
                    -1,
                    null,
                    null
            );
        } else {
            // Entrada aconteceu, mas nem alvo nem stop foram atingidos até o fim do histórico
            return new ResultadoSinalGatilho3(
                    sinal,
                    Status.ABERTO,
                    false,
                    idxEntrada,
                    candles.size() - 1,
                    precoEntradaEfetivo,
                    null
            );
        }
    }
}