O projeto final reúne em um arquivo os recursos estudados no livro: médias móveis, inputs, condições, função própria, ordens e exibição. O objetivo é criar um programa em que você consiga relacionar cada decisão aos dados e às regras descritas.

Descreva o programa sem código

A estratégia observa os cruzamentos de duas EMAs. Com uma terceira média, uma SMA mais longa, também verifica se o fechamento está acima dela.

Para entrar comprado, a EMA rápida deve passar de baixo para cima da lenta, e o fechamento deve estar acima da SMA do filtro de tendência. A ordem dos três comprimentos deve ser rápido < lento < tendência. Se os comprimentos informados não atenderem a essa relação, o programa não enviará ordens de entrada.

A posição comprada recebe uma ordem de encerramento a mercado no cruzamento das EMAs para baixo. A entrada também tem um stop e um alvo definidos em ticks. A simulação usa quantidade fixa de uma unidade, comissão de 0,1 por cento e derrapagem de dois ticks.

Essas são as regras do exercício. Não concluímos a partir delas que o programa será lucrativo em algum mercado. Para conferir o funcionamento, usamos os dados fixos de demonstração do livro.

O programa completo

Crie o arquivo final_strategy.py:

"""
@pyne edge
"""
from pynecore.lib import close, color, input, location, plot, plotshape, script, shape, strategy, ta


def lengths_are_ordered(fast: int, slow: int, trend: int) -> bool:
    return fast < slow and slow < trend


@script.strategy(
    "Filtered Crossing Strategy",
    overlay=True,
    initial_capital=10000,
    default_qty_type=strategy.fixed,
    default_qty_value=1,
    pyramiding=1,
    commission_type=strategy.commission.percent,
    commission_value=0.1,
    slippage=2,
)
def main(
    fast_length: int = input.int(9, title="Fast EMA length", minval=1, maxval=200),
    slow_length: int = input.int(21, title="Slow EMA length", minval=1, maxval=200),
    trend_length: int = input.int(50, title="Trend SMA length", minval=1, maxval=200),
    use_trend_filter: bool = input.bool(True, title="Use trend filter"),
    stop_ticks: int = input.int(100, title="Stop distance (ticks)", minval=1),
    target_ticks: int = input.int(200, title="Target distance (ticks)", minval=1),
):
    fast_average = ta.ema(close, fast_length)
    slow_average = ta.ema(close, slow_length)
    trend_average = ta.sma(close, trend_length)
    crosses_up = ta.crossover(fast_average, slow_average)
    crosses_down = ta.crossunder(fast_average, slow_average)
    valid_lengths = lengths_are_ordered(fast_length, slow_length, trend_length)
    trend_passes = not use_trend_filter or close > trend_average
    entry_signal = valid_lengths and crosses_up and trend_passes
    if entry_signal:
        strategy.entry("Long", strategy.long)
    if crosses_down:
        strategy.close("Long")
    strategy.exit("Bracket", "Long", loss=stop_ticks, profit=target_ticks)
    plot(fast_average, "Fast EMA", color=color.blue, linewidth=2)
    plot(slow_average, "Slow EMA", color=color.orange, linewidth=2)
    plot(trend_average, "Trend SMA", color=color.gray, linewidth=2)
    plotshape(entry_signal, "Entry signal", style=shape.triangleup,
              location=location.belowbar, color=color.green)

Salve e execute com os dados de demonstração. O gráfico mostra três médias e triângulos verdes que indicam a condição de entrada. As entradas e saídas efetivas aparecem nas marcações de operações da estratégia.

O gráfico de final_strategy.py mostra EMAs rápida e lenta, uma SMA de filtro de tendência, sinais de entrada e operações executadas.
O projeto final usa três médias, um filtro que pode ser ativado e desativado e regras de saída associadas às entradas.

O que é calculado em cada candle?

As três médias e os dois cruzamentos são calculados antes das condições. Calculamos todos mesmo que o filtro impeça a entrada. Assim, seu estado de cálculo acompanha a sequência completa de candles.

A função própria lengths_are_ordered examina três números e devolve um valor lógico. Ela não tem estado próprio entre candles. valid_lengths armazena o resultado atual da verificação para uso nas expressões seguintes.

trend_passes é verdadeiro quando o filtro está desativado ou quando o fechamento está acima da SMA. input.bool fornece uma entrada lógica que pode ser ativada e desativada. not use_trend_filter é verdadeiro quando o filtro está desligado; nesse caso, a segunda condição não é necessária.

Mesmo com o filtro desativado, continuamos calculando e desenhando a média. A opção altera a verificação de entrada, não a exibição da linha.

Uma relação inválida entre comprimentos

Os limites individuais dos inputs apenas exigem que cada comprimento esteja entre 1 e 200. Ainda é possível informar 30 para o rápido e 10 para o lento. Isso viola a ordem exigida pelo exercício.

Por isso, a função de verificação examina a relação separadamente. Com uma ordem incorreta, as linhas continuam sendo desenhadas, mas entry_signal não pode ser verdadeiro. Assim, o programa não passa a gerar entradas com uma relação de comprimentos diferente da proposta.

Você pode conferir isso diretamente no depurador pela variável valid_lengths. A condição permanece falsa durante toda a execução quando os inputs salvos são inadequados.

Um sinal nem sempre gera uma nova operação

O triângulo verde indica a condição de entrada. A estratégia envia a ordem ao simulador. A execução também pode depender de outras condições: por exemplo, haver um próximo candle, capital suficiente e uma quantidade permitida.

Por causa de pyramiding=1, o simulador pode não permitir outra entrada comprada quando já existe uma posição comprada aberta. Por isso, não presuma que a quantidade de sinais e a de operações sejam iguais.

No início da série, a SMA de cinquenta do filtro de tendência ainda está ausente. Com o filtro ligado, não podemos considerar verdadeiro que o preço esteja acima dela; portanto, a entrada também não atende a essa condição.

Confira o funcionamento

Primeiro, trabalhe com os valores padrão e confira a quantidade, os custos e o modo de execução na tela Properties. Uma substituição salva anteriormente pode alterar a configuração definida no código-fonte.

Encontre um sinal verde. No depurador ou pelos valores do gráfico, examine as duas EMAs no candle atual e no anterior. Confira o cruzamento da média rápida com a lenta, o filtro de SMA e a relação entre os comprimentos. As três partes devem ser verdadeiras.

Depois, encontre na tabela Trades a entrada que pode ser associada ao sinal. Veja em qual candle e a que preço ela foi executada. Na saída, confira se o encerramento é explicado por cruzamento, stop ou alvo.

Experimente casos previsíveis

Para conferir o funcionamento, faça alterações cujos resultados você consiga prever:

AlteraçãoO que conferir?
Comprimentos rápido e lento iguaisA verificação dos comprimentos impede novos sinais de entrada
Filtro de tendência desligadoO cruzamento das EMAs não exige preço acima da SMA
Distâncias de stop e alvo maioresA diferença nos níveis pode produzir uma saída em outro candle
Quantidade alteradaO sinal de preço é igual, mas o tamanho monetário da operação pode mudar

Faça apenas uma dessas alterações por execução. Em cada teste, anote primeiro sua previsão e depois o resultado efetivo. Se não coincidirem, procure no depurador a primeira decisão diferente.

Compare os resultados

Ao comparar o filtro ligado e desligado, mantenha os mesmos dados, quantidade, custos e comprimentos. Examine a curva de patrimônio de Performance junto com a tabela Trades. Poucas operações ou uma posição aberta no fim também afetam o resumo.

Se testar o programa com dados históricos reais, defina o intervalo usado para escolher as configurações e um intervalo separado de verificação antes de olhar os resultados. Se você alterar os parâmetros depois de examinar o intervalo de verificação, esse intervalo também passará a fazer parte do ajuste.

O início dos dados precisa conter candles suficientes para o aquecimento. Ao executar separadamente um intervalo recortado, você reconstrói as médias móveis e o estado da posição. Por isso, o início dessa execução pode ser diferente do resultado do mesmo intervalo no meio de uma série mais longa.

Tente por conta própria

Acrescente ao programa uma opção Use crossings exit. Quando ativada, ela deve manter o encerramento por cruzamento para baixo; quando desativada, apenas o stop e o alvo devem encerrar a posição. Calcule o cruzamento para baixo nos dois casos.

Solução do exercício

Na lista de parâmetros de main():

use_crossing_exit: bool = input.bool(True, title="Use crossings exit"),

O ramo de encerramento:

if use_crossing_exit and crosses_down:
    strategy.close("Long")

Mantenha a linha crosses_down = ta.crossunder(...) antes das condições. A chamada strategy.exit deve permanecer nas duas posições da opção.

Com o encerramento por cruzamento desligado, a posição pode continuar aberta depois de um cruzamento das EMAs para baixo. O próximo sinal de entrada ainda pode aparecer, mesmo sem abrir uma nova operação por já existir uma posição.

Crie sua própria versão

Copie o programa com outro nome e descreva uma única regra nova que deseja acrescentar. Defina quais dados ela usa, em qual candle decide, como conferir o resultado à mão e qual input a modifica.

Calcule a nova regra primeiro em uma variável separada. Desenhe-a ou examine-a no depurador antes de torná-la uma condição de entrada ou saída. Assim, você pode procurar um erro no cálculo antes de tentar explicar o resultado da estratégia inteira.

O código-fonte, as configurações salvas e os dados usados descrevem juntos seu experimento. Preservando os três, você consegue reproduzir e conferir a execução.