Documentation

Execution Engine

Como o engine de backtest avalia estratégias — lógica de entrada, o modelo de risco configurável, gestão de posição e regras de determinismo.

#Visão geral

O engine de backtest avalia sua estratégia contra dados históricos OHLCV, candle por candle, em ordem cronológica. Pra cada candle, ele roda o pipeline de avaliação completo: computa inputs, avalia conditions, soma scores, checa a regra de decision, e abre um trade se a regra for atendida.

O engine é determinístico. Dada a mesma estratégia, par de ativo e intervalo de datas, ele sempre produz o mesmo resultado.

#Pipeline de Avaliação

Pra cada candle, o engine roda nessa ordem:

1
Computar inputsToda expressão de input é avaliada usando dados do candle atual e passados. Indicadores são computados com todo o lookback necessário.
2
Avaliar conditionsCada condition é avaliada como um booleano usando os inputs computados e operadores de comparação.
3
Somar scoreOs scores de todas as conditions true são somados pra produzir o score total do candle.
4
Checar decisionAs expressões de entry e exit são avaliadas. Se entry resolve pra true, um novo trade é aberto.
5
Atualizar posições abertasTodo trade atualmente aberto é checado contra a máxima e mínima do candle pra ver se SL ou TP foi atingido, depois contra o sinal de saída.

#Execução de Entrada

Entradas são executadas no fechamento do candle onde a regra de decision avalia pra true. Não há lookahead — o sinal é gerado usando dados até e incluindo o fechamento atual, e o preço de entrada é esse fechamento.

Entrada no fechamento significa que a estratégia não pode reagir mais rápido que um intervalo de candle. Numa estratégia de 1H, o tempo mínimo de resposta a um evento de mercado é uma hora.

#Modelo de Risco (Unidades de R)

O stop-loss e take-profit de todo trade são calculados na entrada pela configuração riskManagement da estratégia. É opcional — se uma estratégia a omite, o engine usa um padrão:

  • Stop-loss1% do preço de entrada (padrão).
  • Take-profit3% do preço de entrada — razão risco-retorno 1:3 (padrão).
  • Ganho (em R)+3.0R por trade vencedor (padrão).
  • Perda (em R)-1.0R por trade perdedor (padrão).

Resultados são medidos em R, não em valores de moeda. Isso torna resultados de estratégia comparáveis independente do tamanho da conta, preço do ativo, ou método de dimensionamento de posição.

Configurando riskManagement

Pra sobrescrever o padrão, adicione um objeto riskManagement na estratégia com um bloco stopLoss e/ou takeProfit.

SeçãoTipoParâmetros
stopLosspercentpercent — distância da entrada, ex: 1.0 pra 1%
stopLossatrperiod, multiplier — distância do stop = ATR(period) × multiplier
takeProfitriskRewardmultiple — distância do take-profit = distância do stop-loss × multiple
takeProfitpercentpercent — distância da entrada, independente do stop-loss
riskManagement
"riskManagement": {
  "stopLoss":   { "type": "atr", "period": 14, "multiplier": 1.5 },
  "takeProfit": { "type": "percent", "percent": 2.0 }
}
Com um riskManagement customizado, as magnitudes de R de ganho/perda deixam de ser sempre exatamente +3.0R / -1.0R — um stop-loss baseado em ATR, por exemplo, varia a distância de risco (e portanto o valor de R de cada trade) baseado na volatilidade no momento da entrada.

Por que unidades de R?

Unidades de R removem a ilusão de lucros em dólar. Uma estratégia com pnlR: +50 retornou 50 unidades de risco ao longo do período do backtest. Se cada unidade era $10 ou $1.000 é uma decisão de dimensionamento de posição tomada separadamente. Esse é o jeito correto de avaliar edge estatístico.

#Direction

Por padrão toda estratégia é long — ela compra na entrada e lucra quando o preço sobe. Defina configuration.direction pra "short" pra operar o outro lado: a estratégia vende na entrada e lucra quando o preço cai. Uma única rodada de backtest é uma direção ou a outra, nunca as duas — decision.entry/exit, conditions e score mantêm exatamente o mesmo significado dos dois jeitos.

configuration
"configuration": {
  "timeframe": "1H",
  "direction": "short"
}

direction só muda como a posição é precificada e pontuada, ambos espelhados em torno do preço de entrada:

Stop-lossColocado acima da entrada em vez de abaixo — um preço subindo estopa o trade.
Take-profitColocado abaixo da entrada em vez de acima — um preço caindo realiza lucro.
Sinal do PnLLucro quando o preço de saída está abaixo da entrada; perda quando está acima — a imagem espelhada do long.
Todo tipo de stop-loss/take-profit (percent, atr, riskReward) e a ordem de checagem no mesmo candle descrita em Exits abaixo não são afetados por direction — só o posicionamento de preço e o sinal do PnL se invertem.

O campo direction de nível superior do resultado ("long" ou "short") registra qual lado a rodada operou, então resultados se autodescrevem sem precisar cruzar referência com a estratégia de entrada.

#Exits

Uma posição pode fechar de três formas: stop-loss, take-profit, ou uma saída por sinal (decision.exit, veja Strategy System). Cada posição aberta é checada contra as três, nessa ordem, todo candle:

Stop-lossMínima do candle alcança o preço de stop.
Take-profitMáxima do candle alcança o preço de take.
Saída por sinal"decision.exit" avalia pra true.
A primeira dessas três checagens que dispara num candle fecha a posição — as outras não são avaliadas pra essa posição naquele candle. Concretamente: se o stop-loss (ou take-profit) de um candle é atingido no mesmo candle em que decision.exit também vira true, o stop-loss/take-profit vence. Uma saída por sinal preenche no fechamento do candle, igual à entrada; stop-loss/ take-profit preenchem nos respectivos níveis de preço.

O campo exitReason de cada trade fechado nos resultados registra qual dos três o fechou: 0 (stop-loss), 1 (take-profit), ou 2 (saída por sinal). decision.exit é opcional — estratégias que a omitem não são afetadas e só fecham via stop-loss ou take-profit, exatamente como antes.

#Múltiplas Posições

Por padrão, o engine suporta múltiplas posições abertas simultaneamente. Todo candle onde a regra de decision avalia pra true abre um novo trade independente — mesmo que outros trades já estejam abertos.

Não há simulação de dimensionamento de posição ou alocação de capital. Cada trade é totalmente independente. Se 5 trades estão abertos simultaneamente e todos atingem SL, o resultado é -5R. O engine não simula juros compostos ou esgotamento de conta.

Isso é intencional: o objetivo é avaliar o edge estatístico do sinal da estratégia em si, não o comportamento do portfólio sob qualquer esquema específico de dimensionamento de posição.

Limitando posições concorrentes

Defina configuration.maxOpenPositions pra limitar quantos trades podem estar abertos ao mesmo tempo. Quando o limite é atingido, novos sinais de entrada são ignorados até uma posição fechar. Omita (ou deixe null) pra posições concorrentes ilimitadas — o comportamento padrão, inalterado.

configuration
"configuration": {
  "timeframe": "1H",
  "maxOpenPositions": 1
}

maxOpenPositions: 1 dá o modo de posição única — a aproximação mais próxima de como um trader gerenciando uma posição por vez rodaria a estratégia.

#Ambiguidade no Mesmo Candle

Quando tanto o nível de stop-loss quanto o de take-profit são atingidos dentro do mesmo candle (ou seja, a mínima do candle está abaixo do SL e sua máxima está acima do TP), o engine assume conservadoramente que o stop-loss disparou primeiro.

A contagem desses trades ambíguos é reportada no campo bothHit dos resultados. Uma contagem alta de bothHit em relação ao total de trades pode indicar que a estratégia está sendo usada num timeframe grosso demais pra precisão de entrada pretendida.

#Período de Warmup

Indicadores exigem um número mínimo de candles pra computar (o período de lookback). Por exemplo, ema(close, 21) exige pelo menos 21 candles de histórico antes do valor fazer sentido — antes disso, ainda está convergindo (ou, pra indicadores baseados em janela como rsi e atr, indefinido).

O engine não infere isso automaticamente. Defina configuration.warmupBars pro maior período de lookback usado por qualquer indicador na sua estratégia. Candles dentro da janela de warmup ainda são incluídos nos resultados (então você pode inspecionar valores de indicador enquanto estabilizam), mas entry é forçado pra false e score pra 0 nesse intervalo — nenhum trade pode abrir durante o warmup.

configuration
"configuration": {
  "timeframe": "1H",
  "warmupBars": 21
}
warmupBars usa 0 por padrão. Se sua estratégia usa um indicador com lookback maior que o warmup configurado (ou você o omite por completo), candles iniciais podem gerar sinais a partir de valores de indicador que ainda não estabilizaram completamente.

#Diagnósticos de Estratégia

Além de métricas de performance, o engine rastreia dois conjuntos de dados de diagnóstico críticos pra análise de qualidade de estratégia:

conditionsDistributionPct

Pra cada condition na estratégia, a fração de todos os candles avaliados onde ela era true. Uma condition que é true em 95% dos candles adiciona quase nenhum poder de discriminação e pode estar desperdiçando peso de score.

scoreDistributionPct

Pra cada valor de score possível, a fração de candles que alcançou esse score. Isso mostra com que frequência sua estratégia está "perto de disparar" vs. totalmente alinhada, e ajuda a ajustar seu limiar de entrada.

Use esses diagnósticos pra identificar risco de overfitting. Se seu limiar de entrada está mal acima do score mais comum, você pode estar disparando com frequência demais em sinais marginais.

#Limitações

  • Sem simulação de slippage. Entradas e saídas são nos preços exatos de close/SL/TP.
  • Nenhuma taxa ou comissão de trading é deduzida dos resultados.
  • Sem juros compostos de capital. Cada trade é dimensionado independentemente em 1R.
  • Um único backtest opera uma direção (long ou short via configuration.direction) — uma estratégia não pode manter posições long e short na mesma rodada.
  • Dados são limitados aos pares de ativo suportados e seu intervalo histórico disponível.
  • O engine processa candles sequencialmente — não há simulação de execução intracandle.
Essas limitações são intencionais. O engine é construído pra avaliar edge estatístico, não pra simular trading ao vivo. A Live Execution API (beta) roda a mesma estratégia contra dados de mercado reais e ao vivo — sem simulação, preços reais — mas não executa ordens em seu nome; ela só emite sinais de Entry/Exit.