Introdução
Esta seção visa apresentar outros exemplos de trechos de códigos que não se enquadram nas categorias anteriores.
Você pode acessar os Snippets diretamente pelo menu lateral direito, ou fazendo CRL+F (CTRL+L) para localizar algum texto específico na página, uma vez que o conteúdo tende a crescer ao longo do tempo, dificultando a navegação pelo menu.
Caso tenham sugestões de código para acrescentar à lista, gentileza deixar o código nos comentários com o link para seu perfil em rede social (para devido crédito de autoria).
Snippets
Escrevendo no Console com Timestamp para facilitar Debug
A partir da versão beta 215, tornou-se possível no Backtesting escrever texto no Console, o que facilita a depuração de estratégias.
Segue abaixo um snippet no qual há uma função que retorna o timestamp da barra. O corpo principal da estratégia apenas gera na aba “Compilação” o log com mensagem “Teste” precedido do timestamp no formato “DD/MM/AAAA HH:MM”.
var
horario: integer;
timestamp : string;
//---------------------------------------------------
function getTimestamp: string;
var
dia, mes_ano, hora: string;
begin
if Month(Date) < 10 then
mes_ano := "0" + Month(Date) + "/" + Year(Date)
else
mes_ano := Month(Date) + "/" + Year(Date);
if DayofMonth(Date) < 10 then
dia := "0" + DayofMonth(Date) + "/" + mes_ano
else
dia := DayofMonth(Date) + "/" + mes_ano;
if Floor(Time/100) < 10 then
hora := "0" + Floor(Time/100)
else
hora := Floor(Time/100);
if Mod(Time,100) < 10 then
hora := hora + ":" + "0" + Mod(Time,100)
else
hora := hora + ":" + Mod(Time,100);
Result := dia + " " + hora + " - ";
end;
//---------------------------------------------------
begin
if horario <> Time then
begin
timestamp := getTimestamp;
ConsoleLog(getTimestamp() + "Teste", clBlue);
horario := Time;
end;
end;
Arrendondamento de valores para N casas decimais
Como o Profit não tem nativamente uma função para arrendondar um float para uma determinada quantidade de casas decimais (N), a função abaixo foi implementada com esta finalidade.
var
fValorOriginal: float;
fValorArred: float;
function NTB_ArredondarFloat(Value: float; nCasas: integer):float;
var
fValor: float;
fParteInteira: float;
fParteDecimal: float;
begin
fValor := Value;
fParteInteira := IntPortion(fValor);
fParteDecimal := Round((fValor - fParteInteira)*Power(10,nCasas));
Result := fParteInteira + fParteDecimal/Power(10,nCasas);
end;
begin
fValorOriginal := 12.345819;
fValorArred := NTB_ArredondarFloat(fValorOriginal,2);
if LastBarOnChart then
PlotText(fValorArred,clWhite, - 1,8);
end;
Restrição de estratégia pelo código do ativo
O código abaixo visa identificar o código do ativo no qual a estratégia ou robô está sendo aplicado. A estratégia apenas é executada se o ticker do ativo for igual a constante “cAtivoEstrategia”.
Isto evita que as estratégias sejam rodadas de forma equivocada em ativos para os quais os parâmetros da estratégia não foram otimizados.
const
cAtivoEstrategia = "PETR4";
begin
if GetAsset = cAtivoEstrategia then
begin
//TODO CÓDIGO FONTE DA SUA ESTRATÉGIA DEVE ESTAR AQUI DENTRO
PaintBar(clGreen);
end
else
if LastBarOnChart then
plotText("Usar em " + cAtivoEstrategia,clWhite,-1,12);
end
Restrição de estratégia pelo tipo/frequência do gráfico
O código abaixo visa identificar o tipo de gráfico no qual a estratégia ou robô está sendo aplicado. Caso não corresponda ao tipo de gráfico para o qual a estratégia foi otimizada, é exibida mensagem com informação sobre o tipo de gráfico adequado.
Isto evita que as estratégias sejam rodadas de forma equivocada para tipos de gráficos e frequências temporais diferentes daquelas para as quais foi validada.
var
bStarted, bGraficoOK: boolean;
iTipoGrafico: integer;
strMensagemErro: string;
begin
if Not bStarted then
begin
bStarted := true;
iTipoGrafico := GraphicInterval;
//Verificação de Renko 3R
if (iTipoGrafico = itRenko) and (GraphicOffset = 3) then bGraficoOK := true
else strMensagemErro := "Estratégia para 3R";
//Verificação de Gráfico de 5 minutos
//if (iTipoGrafico = itMinute) and (GraphicOffset = 5) then bGraficoOK := true
//else strMensagemErro := "Estratégia para 5 Minutos";
end;
// Robô/Estratégia só irá ser executada se o gráfico estiver configurado
// adequadamente
if bGraficoOK then
begin
end
// Se gráfico não estiver na configuração adequada, pinta todas as barras de vermelho
// e exibe texto na última barra
else
begin
paintBar(clRed);
if LastBarOnChart then
plotText(strMensagemErro,clWhite,-1,9);
end;
end
Ignorar boxes de Gap em gráficos de Renko
É bastante comum, principalmente na operação de contratos futuros, a ocorrência de gaps na abertura. Quando se está em um gráfico de Renko, o código da estratégia é processado para cada box incluído no gráfico devido ao gap.
Este código apresenta uma forma simples de não executar lógica sobre boxes referentes à gap. Os boxes de gap possuem duração igual a zero. Para realizar a verificação da duração temporal em minutos de um box, basta utilizar o comando BarDurationF.
var
bLocalizouBoxesGap : boolean;
iDataUltimoBox, iUltimoBoxDiaAnterior, iBoxDeGAP: integer;
begin
// Código é aplicável apenas à gráficos do tipo Renko
if GraphicInterval = itRenko then
begin
// Identificação de Box relacionados a GAP
if Date <> iDataUltimoBox then
begin
iUltimoBoxDiaAnterior := CurrentBar - 1;
iBoxDeGAP := CurrentBar;
bLocalizouBoxesGap := false;
end;
iDataUltimoBox := Date;
if Not bLocalizouBoxesGap then
if BarDurationF = 0 then
begin
iBoxDeGAP := CurrentBar;
PaintBar(clRed);
end
else
begin
//Aqui foi identificado o primeiro box que não é de GAP
bLocalizouBoxesGAP := true;
end;
//Aqui executa a estratégia em boxes que não são devido à
//gap de abertura
if bLocalizouBoxesGAP then
begin
PaintBar(clGreen);
end;
end
// O Gráfico não é do tipo Renko!
else
if LastBarOnChart then PlotText("Aplicável apenas a Renko",clWhite,-1,10);
end
Cálculo de Gap em gráficos de Renko em termos de boxes
É bastante comum, principalmente na operação de contratos futuros, a ocorrência de gaps na abertura. Este código visa calcular o tamanho do gap nos gráficos de Renko em termos da quantidade de boxes.
OBS: É importante ressaltar que devido à própria natureza do gráfico de Renko, o cálculo de gap não é preciso quanto o cálculo de gap pelo gráfico de Candle.
OBS2: Os comandos OpenD(0) e CloseD(1) também podem ser utilizados em gráficos de renko, reduzindo o código abaixo a gap := (OpenD(0) – CloseD(1))/((GraphicOffset-1)*MinPriceIncrement). No entanto, os valores encontrados divergem em alguns dias de gap por 1 box. Pela minha apuração, o código abaixo apresenta os resultados corretos.
var
bLocalizouBoxesGap : boolean;
fTamanhoBox : float;
iDataUltimoBox, iUltimoBoxDiaAnterior, iBoxDeGAP: integer;
fGAPdoDiaBox, fAberturaBox, fFechamentoDiaAnteriorBox: float;
begin
// Código é aplicável apenas à gráficos do tipo Renko
if GraphicInterval = itRenko then
begin
fTamanhoBox := (GraphicOffset-1)*MinPriceIncrement;
// Identificação de Box relacionados a GAP
if Date <> iDataUltimoBox then
begin
iUltimoBoxDiaAnterior := CurrentBar - 1;
iBoxDeGAP := CurrentBar;
bLocalizouBoxesGap := false;
end;
iDataUltimoBox := Date;
if Not bLocalizouBoxesGap then
if BarDurationF = 0 then iBoxDeGAP := CurrentBar
else
begin
//Aqui foi identificado o primeiro box que não é de GAP
bLocalizouBoxesGAP := true;
//Coloque abaixo o que deseja fazer no primero box do dia que não é GAP
// --------------------------------------------------------------------
if (Open < Close[1]) Or (Open > Close[1]) then fAberturaBox := Open[1]
else fAberturaBox := Close[1];
fFechamentoDiaAnteriorBox := Close[CurrentBar - iUltimoBoxDiaAnterior];
fGAPdoDiaBox := (fAberturaBox - fFechamentoDiaAnteriorBox)/fTamanhoBox;
if fGAPdoDiaBox <> 0 then
begin
if fGAPdoDiaBox > 0 then
PlotText("GAP:" + Floor(fGAPdoDiaBox) + " Box",clGreen,-1,9)
else
PlotText("GAP:" + Floor(fGAPdoDiaBox) + " Box",clRed,-1,9);
PaintBar(clRed);
end;
// --------------------------------------------------------------------
end;
end
// O Gráfico não é do tipo Renko!
else
if LastBarOnChart then PlotText("Aplicável apenas a Renko",clWhite,-1,10);
end
Execução de estratégia em frequência superamostrada
É de conhecimento de quem opera gráficos com tempo gráfico maior os impactos de se executar ordens apenas no fechamento dos candles/boxes.
Este Snippet visa tornar as ordens mais tempestivas, exemplificando uma forma de executar uma estratégia/robô em um tempo gráfico menor do que o original. Naturalmente que isso exige um código fonte mais complexo para tratar de forma adequada os cálculos de indicadores utilizados na estratégia.
O código de exemplo abaixo, demonstra como utilizar uma média móvel em um tempo gráfico superamostrado equivalente ao tempo gráfico original da estratégia (e com valor superior). Bem como também demonstra como seria o cálculo para obter a média de fechamento em tempos gráficos superiores ao gráfico supermostrado.
OBS: Embora não seja um requisito, sugere-se que o tempo gráfico original seja um múltiplo do tempo gráfico superamostrado.
//LIMITAÇÃO IMPORTANTE: Não dá para fazer em escala de segundos
//devido a função time não retornar segundos
const
// Estratégia originalmente rodava em gráfico de 15 min
cTempoGraficoOriginal = 15;
//Qtde de periodos no tempo gráfico original
cQtdePeriodosMedia = 20;
//Horario inicial de negociação do ativo
//0900 para futuros, 1000 para mercado a vista
cHoraInicioNegociacao = 0900;
var
bStarted: boolean;
iTempoGraficoAtual: integer;
iQtdePeriodosEquivalente: integer;
fMediaSuperAmostradaEquivalente: float;
fInicializacaoMedia: float;
fMediaTempoGraficoOriginal: float;
iQtdePeriodosCalculados: integer;
iHorarioEmMinutos: integer;
iHorarioGrafSuperior: integer;
bCalculouTempoGrafSuperior: boolean;
begin
//1a opção: Dar preferencia sempre que possível
// Identifica tempo gráfico utilizado e qtde de periodos equivalente para média superamostrada
iTempoGraficoAtual := GraphicOffset;
iQtdePeriodosEquivalente := Floor(cTempoGraficoOriginal/iTempoGraficoAtual)*cQtdePeriodosMedia;
// Obtem média superamostrada equivalente
fMediaSuperAmostradaEquivalente := Media(iQtdePeriodosEquivalente,Close);
//Plot da série para inspeção
PlotN(1,fMediaSuperAmostradaEquivalente);
SetPlotColor(1, clWhite);
SetPlotWidth(1,2);
SetPlotStyle(1, psDash);
//2a opção: Exige mais linhas de código para gerenciar execução
//Amostrando apenas os valores de fechamento do Tempo gráfico original
iHorarioEmMinutos := Floor(Time/100)*60+Mod(Time,100)
- (Floor(cHoraInicioNegociacao/100)*60+Mod(cHoraInicioNegociacao,100));
if Not bStarted then
begin
iHorarioGrafSuperior := CalcTime(Time,-Mod(iHorarioEmMinutos, cTempoGraficoOriginal));
bStarted := true;
end;
// Bloco para criar mecanismo de calcular apenas uma vez para o gráfico superior
if (CalcTime(Time,-Mod(iHorarioEmMinutos, cTempoGraficoOriginal)) <> iHorarioGrafSuperior) then
begin
iHorarioGrafSuperior := CalcTime(Time,-Mod(iHorarioEmMinutos, cTempoGraficoOriginal));
bCalculouTempoGrafSuperior := false;
end;
// Cálculo da média no tempo gráfico original
if (Mod(iHorarioEmMinutos, cTempoGraficoOriginal) = 0) and Not bCalculouTempoGrafSuperior then
begin
bCalculouTempoGrafSuperior := true;
if (iQtdePeriodosCalculados < cQtdePeriodosMedia) then
begin
iQtdePeriodosCalculados := iQtdePeriodosCalculados + 1;
fInicializacaoMedia := fInicializacaoMedia + Close[1];
end
else
if (iQtdePeriodosCalculados = cQtdePeriodosMedia) then
begin
fMediaTempoGraficoOriginal := fInicializacaoMedia/iQtdePeriodosCalculados;
iQtdePeriodosCalculados := iQtdePeriodosCalculados + 1;
end
else
fMediaTempoGraficoOriginal := (fMediaTempoGraficoOriginal*cQtdePeriodosMedia
- Close[1+cQtdePeriodosMedia*Floor(cTempoGraficoOriginal/iTempoGraficoAtual)]
+ Close[1])/cQtdePeriodosMedia;
end;
//Plot da série para inspeção
PlotN(2,fMediaTempoGraficoOriginal);
SetPlotColor(2, clRed);
SetPlotWidth(2,2);
SetPlotStyle(2, psSolid);
end
Como utilizar dados de preço de outros ativos
O código abaixo apenas exemplifica como obter os dados OHLCV de outras ativos em uma estratégia, o que pode ser feito via parâmetro ou constante. Caso o ativo desejado seja uma ação, deve-se atentar para modificar o feedBMF para feedBovespa.
const
cAtivo1 = Asset("INDFUT",feedBMF);
input
pAtivo2("WINFUT",feedBMF);
pQtdePeriodos(50);
var
fMediaAtivo1, fMediaAtivo2: float;
begin
fMediaAtivo1 := Media(pQtdePeriodos,cAtivo1.Close);
fMediaAtivo2 := Media(Floor(pQtdePeriodos*1/3),pAtivo2.Close);
plot(fMediaAtivo1);
plot2(fMediaAtivo2);
end;
Fantástico! quanto mais pesquiso nos snippets mais me impressiono.