Subversion Repositories Integrator Subversion

Rev

Blame | Last modification | View Log | Download | RSS feed

package br.com.kronus.core;

import br.com.kronus.core.Candle;
import br.com.kronus.core.SinalDeTrade;
import br.com.kronus.strategy.Strategy;

import java.util.ArrayList;
import java.util.List;

public class BacktestEngine {

    /**
     * Roda o backtest: gera sinais com a estratégia e simula trade por trade.
     */

    public BacktestSummary runBacktest(List<Candle> candles, Strategy strategy) {
        BacktestSummary summary = new BacktestSummary();

        if (candles == null || candles.isEmpty()) {
            return summary;
        }

        List<SinalDeTrade> sinais = strategy.gerarSinais(candles);

        for (SinalDeTrade sinal : sinais) {
            BacktestTradeResult result = simularTrade(candles, sinal);
            summary.addTrade(result);
        }

        summary.calcularResumo();
        return summary;
    }

    /**
     * Simula um trade a partir de um sinal de compra/venda.
     * Aqui implementado para COMPRA; para VENDA é só adaptar as condições (mirror).
     */

    private BacktestTradeResult simularTrade(List<Candle> candles, SinalDeTrade sinal) {
        BacktestTradeResult result = new BacktestTradeResult(sinal);

        // Só implementei compra aqui – se precisar de venda também, dá pra expandir
        if (sinal.getDirecao() == SinalDeTrade.Direcao.COMPRA) {
            simularCompra(candles, sinal, result);
        } else {
            // TODO: implementar lógica espelhada para venda, se você usar
            result.setExitReason(BacktestTradeResult.ExitReason.NAO_EXECUTADO);
        }

        return result;
    }

    private void simularCompra(List<Candle> candles, SinalDeTrade sinal, BacktestTradeResult result) {
        double precoEntrada = sinal.getPrecoEntrada();
        double stop = sinal.getStopLoss();
        double alvo = sinal.getAlvo();

        // 1) Descobrir a partir de qual índice de candle começar (>= horário do sinal)
        int startIdx = encontrarIndiceInicial(candles, sinal);

        if (startIdx == -1) {
            // Não há candles após o sinal
            result.setExitReason(BacktestTradeResult.ExitReason.NAO_EXECUTADO);
            return;
        }

        // 2) Procurar candle onde a entrada é acionada (touch na faixa [min, max])
        int idxEntrada = -1;
        for (int i = startIdx; i < candles.size(); i++) {
            Candle c = candles.get(i);
            if (c.getMinima() <= precoEntrada && c.getMaxima() >= precoEntrada) {
                idxEntrada = i;
                break;
            }
        }

        if (idxEntrada == -1) {
            // Preço nunca tocou na entrada
            result.setExitReason(BacktestTradeResult.ExitReason.NAO_EXECUTADO);
            return;
        }

        // Marca entrada
        Candle candleEntrada = candles.get(idxEntrada);
        result.setExecutado(true);
        result.setEntryPrice(precoEntrada);
        result.setEntryCandle(candleEntrada);

        // 3) A partir do candle de entrada, checar stop/alvo
        for (int i = idxEntrada; i < candles.size(); i++) {
            Candle c = candles.get(i);

            boolean tocouStop = c.getMinima() <= stop;
            boolean tocouAlvo = c.getMaxima() >= alvo;

            if (tocouStop && tocouAlvo) {
                // Mesma barra: por conservadorismo, considerar que o STOP veio primeiro
                result.setExitPrice(stop);
                result.setPnl(stop - precoEntrada);
                result.setExitCandle(c);
                result.setExitReason(BacktestTradeResult.ExitReason.STOP);
                return;
            } else if (tocouStop) {
                result.setExitPrice(stop);
                result.setPnl(stop - precoEntrada);
                result.setExitCandle(c);
                result.setExitReason(BacktestTradeResult.ExitReason.STOP);
                return;
            } else if (tocouAlvo) {
                result.setExitPrice(alvo);
                result.setPnl(alvo - precoEntrada);
                result.setExitCandle(c);
                result.setExitReason(BacktestTradeResult.ExitReason.ALVO);
                return;
            }
        }

        // 4) Se chegou aqui, nem stop nem alvo foram atingidos: encerra no último candle (expirado)
        Candle ultimo = candles.get(candles.size() - 1);
        double exitPrice = ultimo.getFechamento();
        result.setExitPrice(exitPrice);
        result.setPnl(exitPrice - precoEntrada);
        result.setExitCandle(ultimo);
        result.setExitReason(BacktestTradeResult.ExitReason.EXPIRADO);
    }

    /**
     * Procura primeiro candle com horário >= horário do sinal.
     */

    private int encontrarIndiceInicial(List<Candle> candles, SinalDeTrade sinal) {
        for (int i = 0; i < candles.size(); i++) {
            if (!candles.get(i).getTime().isBefore(sinal.getTime())) {
                return i;
            }
        }
        return -1;
    }
}