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
);
}
}
}