Backtesting API
Referência completa da Backtesting API — endpoints, schema de request, schema de response, métricas e códigos de erro.
#Visão geral
A Backtesting API expõe cinco endpoints. Submeta uma requisição de execução de estratégia — contra um único par de ativo, ou um batch inteiro de uma vez — faça polling do resultado usando o ID retornado, e — separadamente — pagine pela lista completa de trades.
URL base: https://backtest.emidlabs.com/api/public/v1
x-api-key com uma API key válida. Keys são geradas no Console.#POST /backtest — Submeter
/backtestSubmete um novo backtest pra execução.
O corpo deve ser application/json. A estratégia é embutida como um objeto JSON aninhado dentro de strategySnapshotJson.
Corpo da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| strategySnapshotJson | object | Sim | Objeto de estratégia com configuration, inputs, conditions, score, decision, e (opcional) riskManagement. |
| assetPair | string | Sim | Par de negociação. Ex: "BTC-USDC", "ETH-USDC". |
| initialDate | string | Sim | Data inicial em formato ISO: YYYY-MM-DD. |
| finalDate | string | Sim | Data final em formato ISO: YYYY-MM-DD. |
Pares de ativo suportados
Por enquanto, só mercados cripto são suportados, com origem na Coinbase e cotados em USDC. Exemplos:
BTC-USDCETH-USDCSOL-USDCXRP-USDCADA-USDCVeja a página de Referência de Dados pra lista completa de pares suportados e os ativos disponíveis no Console. Intervalos de data devem cair dentro dos dados históricos disponíveis pro ativo selecionado.
Resposta do submit
| Campo | Tipo | Descrição |
|---|---|---|
| id | string (UUID) | Identificador único do backtest. |
| status | string | "Running" imediatamente após a submissão. |
| assetPair | string | O par de ativo usado. |
| initialDate | string | Data inicial do período do backtest. |
| finalDate | string | Data final do período do backtest. |
| createdAtUtc | string | Timestamp de criação em UTC. |
| canViewResult | boolean | Se o resultado está acessível (depende de créditos). |
#POST /backtest/batch — Submeter Batch
/backtest/batchSubmete UMA estratégia contra VÁRIOS pares de ativo de uma vez.
Mesma ideia de POST /backtest, mas em vez de uma string assetPair, envie um array assetPairs — todo ativo recebe exatamente o mesmo strategySnapshotJson e intervalo de datas. Útil pra triar uma estratégia em vários mercados sem uma requisição por ativo.
Best-effort por item: um par de ativo desconhecido ou um intervalo de datas fora da cobertura desse ativo aparece como um error só naquele item — nunca falha o resto do batch.
Corpo da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| strategySnapshotJson | object | Sim | Mesmo formato do endpoint de submissão única — aplicado a todo ativo no batch. |
| assetPairs | array de string | Sim | Ex: ["BTC-USDC", "ETH-USDC", "SOL-USDC"]. Limitado a 200 por batch. |
| initialDate | string | Sim | Data inicial em formato ISO: YYYY-MM-DD. |
| finalDate | string | Sim | Data final em formato ISO: YYYY-MM-DD. |
Resposta do submit batch
| Campo | Tipo | Descrição |
|---|---|---|
| batchId | string (UUID) | Passe isso pra GET /backtest/batch/:batchId/results pra buscar o resultado de todo ativo, paginado. |
| items | array | Uma entrada por par de ativo requisitado — veja abaixo. |
| items[].assetPair | string | O par de ativo desse item. |
| items[].id | string | null | O próprio id do backtest — null se esse par de ativo falhou na submissão. |
| items[].status | string | null | "Queued" em caso de sucesso, null em caso de falha. |
| items[].error | string | null | Definido só se esse item falhou na submissão. Os outros itens não são afetados. |
GET /backtest/batch/:batchId/results também. Guarde essa resposta se você precisar do quadro completo de sucesso+falha depois.#GET /backtest/:id — Buscar Resultados
/backtest/{id}Busca o status e resultados de um backtest submetido.
result aqui é só métricas agregadas — nenhum detalhe trade-a-trade. Use GET /backtest/:id/trades abaixo pra isso.
Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
| id | string | UUID do backtest. |
| status | string | "Running", "Completed", ou "Failed". |
| strategySnapshotJson | string | A estratégia usada, serializada. |
| assetPair | string | Par de ativo. |
| initialDate / finalDate | string | Intervalo de datas. |
| createdAtUtc | string | Timestamp de criação (UTC). |
| errorMessage | string | null | Detalhes do erro se status for "Failed". |
| canViewResult | boolean | Se o resultado está acessível. |
| logsJson | string | null | Logs de execução serializados como string JSON. |
| result | object | null | Métricas agregadas de performance quando Completed. Sem detalhe trade-a-trade — veja GET /backtest/:id/trades. |
| recentTradeCount | number | null | Quantos dos trades fechados mais recentes recentAvgPnlR/recentOutcomes se baseiam (até 5). Só definido quando Completed. |
| recentAvgPnlR | number | null | pnlR médio dos recentTradeCount trades mais recentes — um sinal de recência, distinto do expectancyR de janela completa em result. |
| recentOutcomes | array de string | null | "Win"/"Loss" por trade recente, cronológico (mais antigo primeiro — o último elemento é o trade mais recente). Deixa você distinguir uma sequência de perdas real de uma alternância com a mesma média. |
#GET /backtest/batch/:batchId/results — Buscar Resultados do Batch
/backtest/batch/{batchId}/resultsBusca o resultado de todo item de uma submissão em batch, paginado.
Todo backtest criado por POST /backtest/batch é marcado com o mesmo batchId — esse endpoint consulta por esse campo diretamente, então não tem nada pra lembrar além do id que a chamada de submit deu pra você.
Parâmetros de query
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| page | number | 1 | Número da página, base 1. |
| pageSize | number | 20 | Itens por página. Limitado ao intervalo 1–100. |
Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
| batchId | string | Ecoa o batch requisitado. |
| totalCount | number | Total de itens no batch, em todas as páginas. |
| completedCount | number | Quantos itens terminaram com sucesso. |
| failedCount | number | Quantos itens terminaram sem sucesso (Failed, Cancelled, ou Expired). |
| pendingCount | number | Quantos itens ainda estão Queued ou Running. |
| page / pageSize / totalPages | number | Campos padrão de paginação. |
| items | array | Uma entrada por backtest nessa página — mesmo formato de GET /backtest/:id (assetPair, id, status, result, recentTradeCount, recentAvgPnlR, recentOutcomes), então quem chama vê campos idênticos independente de ter buscado um ativo individualmente ou como parte de um batch. |
totalCount/completedCount/failedCount/pendingCount voltam em toda página, não só na última — confira completedCount + failedCount === totalCount com uma chamada barata de pageSize=1 pra saber que o batch inteiro terminou, sem paginar por tudo.
#GET /backtest/:id/trades — Listar Trades
/backtest/{id}/tradesPagina por todo trade de um backtest completo, independente do resultado principal.
Trades são armazenados um-por-documento no servidor, então esse endpoint continua rápido independente de quantos trades um backtest produziu — não precisa buscar o resultado completo antes.
Parâmetros de query
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| page | number | 1 | Número da página, base 1. |
| pageSize | number | 20 | Itens por página. Limitado ao intervalo 1–100. |
| sortBy | string | "number" | Um de "number", "pnlR", "pnlPct", "entryTime", "exitTime". |
| sortDirection | string | "asc" | "asc" ou "desc". Valores não reconhecidos caem pro padrão em vez de dar erro. |
Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
| items | array | Objetos de trade pra página requisitada — veja "Campos de detalhe do trade" abaixo. |
| totalCount | number | Número total de trades em todas as páginas. |
| page | number | O número da página retornado (depois de limitado). |
| pageSize | number | O tamanho de página retornado (depois de limitado). |
| totalPages | number | ceil(totalCount / pageSize). |
#Confirmation Sources
Só conta um candidato de Entry/Exit como trade de verdade quando corroborado pelo próprio histórico de sinais de outra estratégia — ex: uma entrada de XRP só simulada como trade quando uma fonte de confirmação de BTC neutro concorda, ou quando a própria estratégia de tendência em timeframe superior do mesmo ativo concorda. O análogo ao vivo disso é o Confirmation Sources da Live Execution — mesma ideia, mesmo formato de requisição, resolvido aqui contra uma série histórica já totalmente conhecida em vez de um cache ao vivo.
ConfirmationSource, com o próprio fluxo de dois passos abaixo.Passo 1 — POST /confirmation-sources — Submeter
/confirmation-sourcesRoda uma estratégia de corroboração contra um intervalo histórico, sem simular nenhum trade.
Mesmo custo de execução e mesmo formato de requisição que POST /backtest — caminha os candles e avalia a DSL exatamente do mesmo jeito — mas o resultado é um histórico bruto de sinais, não um desfecho de trade.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| strategySnapshotJson | object | Sim | A estratégia de corroboração — mesmo formato do POST /backtest. Essa estratégia nunca é feita pra ser operada sozinha. |
| assetPair | string | Sim | Par de negociação. Ex: "BTC-USDC". |
| initialDate | string | Sim | Data inicial em formato ISO: YYYY-MM-DD. |
| finalDate | string | Sim | Data final em formato ISO: YYYY-MM-DD. |
Resposta do submit
| Campo | Tipo | Descrição |
|---|---|---|
| id | string (UUID) | Identificador único da confirmation source — esse é o sourceId que você referencia no submit_backtest assim que estiver Completed. |
| status | string | "Queued" imediatamente após a submissão. |
Passo 2 — GET /confirmation-sources/:id — Status
/confirmation-sources/{id}Consulta o status — mirror de GET /backtest/:id, sem objeto de resultado.
| Campo | Tipo | Descrição |
|---|---|---|
| id | string | UUID da confirmation source. |
| assetPair | string | Par de ativo. |
| timeframe | string | Resolvido a partir do próprio configuration.timeframe da estratégia. |
| initialDate / finalDate | string | Intervalo de datas. |
| status | string | "Queued", "Running", "Completed", ou "Failed". |
| errorMessage | string | null | Detalhes do erro se status for "Failed". |
| createdAtUtc | string | Timestamp de criação (UTC). |
| runtimeMs | number | null | Quanto tempo o analyser levou pra rodar isso, em milissegundos. Null até Completed. |
| candlesProcessed | number | null | Número de candles processados. Null até Completed. |
| unitsConsumed | number | null | Custo em unidades de execução — mesma pool de cobrança do POST /backtest. Null até Completed. |
Nunca tem campo result — uma confirmation source não tem desfecho de trade pra reportar. O payload de verdade dela é o histórico de sinais, buscado separadamente abaixo.
GET /confirmation-sources/:id/signals — Listar Sinais
/confirmation-sources/{id}/signalsPagina pelo histórico bruto de sinais que uma confirmation source produziu, independente do status.
Mesmo raciocínio de GET /backtest/:id/trades — sinais são armazenados um-por-documento no servidor, então isso continua rápido independente de quantos candles bateram com as conditions da estratégia.
Parâmetros de query
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| page | number | 1 | Número da página, base 1. |
| pageSize | number | 20 | Itens por página. Limitado ao intervalo 1–100. |
Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
| items | array | Objetos de sinal pra página requisitada — veja abaixo. |
| items[].candleOpenTime | number | Segundos unix — OpenTime do candle que produziu esse sinal. |
| items[].type | string | "Entry" ou "Exit". |
| items[].score | number | Soma dos pesos de toda condition que avaliou true nesse candle. |
| items[].conditions | object | Toda condition nomeada da estratégia, mapeada pra se era true nesse candle. |
| items[].scoreBreakdown | object | Peso por condition que contribuiu pro score. |
| totalCount | number | Número total de sinais em todas as páginas. |
| page / pageSize / totalPages | number | Campos padrão de paginação. |
confirmationSources no POST /backtest
Assim que uma confirmation source reporta Completed, referencie-a numa submissão normal de POST /backtest como campo irmão de strategySnapshotJson/assetPair/initialDate/finalDate — não faz parte do próprio objeto de estratégia.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| confirmationSources | array | Não | Omitido/vazio = comportamento atual, inalterado. Quando definido, todo candidato de Entry/Exit precisa ser corroborado por todas as fontes listadas (E lógico) antes de ser simulado como trade. |
| confirmationSources[].sourceId | string (UUID) | Sim* | O id de um submit_confirmation_source (Passo 1 acima), só da mesma conta. Diferente da versão desse mesmo mecanismo na Live Execution, um sourceId inválido (inexistente, ainda não Completed, ou de outra conta) falha a submissão imediatamente — isso é síncrono/batch, deixar passar geraria um resultado confuso de zero trades sem explicação. |
| confirmationSources[].signalType | string | Não | "entry" (padrão) ou "exit" — qual tipo de sinal da fonte conta como confirmação. |
| confirmationSources[].validityWindow.count | integer | Não | Padrão 1. Sempre em candles da própria timeframe da FONTE, não uma duração fixa — escala automaticamente com a estratégia que está confirmando. |
confirmedSignalsCount/unconfirmedSignalsCount na Referência de Métricas abaixo pra medir o efeito diretamente — rode o mesmo backtest com e sem confirmationSources no mesmo intervalo e compare.#Referência de Métricas
Quando status é Completed, o objeto result contém:
Métricas de lucratividade
| Campo | Tipo | Descrição |
|---|---|---|
| pnlR | number | Lucro/prejuízo líquido em unidades de R. Soma de todos os PnLs dos trades. |
| grossProfitR | number | Soma de todos os trades vencedores em unidades de R. |
| grossLossR | number | Soma de todos os trades perdedores em unidades de R (negativo). |
| profitFactor | number | null | grossProfitR / abs(grossLossR). >1 é lucrativo. null quando não há trades perdedores — a razão fica indefinida, não infinita. |
| expectancyR | number | R esperado médio por trade. (winRate × avgWinR) + (lossRate × avgLossR). |
| avgWinR | number | R médio nos trades vencedores. |
| avgLossR | number | R médio nos trades perdedores (-1.0 por padrão; varia se a estratégia sobrescreve riskManagement.stopLoss). |
| totalFeeR | number | R total subtraído em todos os trades por configuration.entryFeePct/exitFeePct (0 se nenhum foi definido). pnlR/expectancyR acima já são líquidos disso — totalFeeR é só quanto as taxas custaram, pra diagnóstico. |
Estatísticas de trade
| Campo | Tipo | Descrição |
|---|---|---|
| trades | number | Número total de trades executados. |
| wins | number | Número de trades vencedores. |
| losses | number | Número de trades perdedores. |
| winRate | number | wins / trades. Intervalo: 0–1. |
| bothHit | number | Trades onde SL e TP foram atingidos no mesmo candle (resolvido como SL). Um bothHit alto em relação a trades significa que muitos desfechos foram decididos pela regra de precedência stop-vence-empate do engine em vez de dados reais de trajetória de preço intracandle — trate resultados com mais ceticismo quanto maior essa razão. |
Métricas de drawdown
| Campo | Tipo | Descrição |
|---|---|---|
| maxDrawdownR | number | Pior queda de pico-a-vale entre os trades fechados, em unidades de R. 0 se o equity nunca caiu abaixo da própria máxima histórica. |
| currentDrawdownR | number | Quão abaixo do próprio pico a curva de equity está no final da janela do backtest, em unidades de R. 0 se a janela termina numa nova máxima. Inclui o PnL não realizado de qualquer posição ainda aberta — veja unrealizedPnlRAtEnd. |
| openPositionsAtEnd | number | Número de posições ainda abertas (nunca atingiram stop/take/sinal de saída) quando o intervalo de datas do backtest terminou. 0 no caso comum. |
| unrealizedPnlRAtEnd | number | Soma do PnL não realizado, em unidades de R, de todas as posições ainda abertas no fim da janela — marcado a mercado contra o fechamento do último candle disponível. 0 quando openPositionsAtEnd é 0. |
unrealizedPnlRAtEnd não inclui taxa de saída (nenhuma saída realmente aconteceu) e alimenta só currentDrawdownR/maxDrawdownR — nunca vaza pra pnlR, expectancyR, trades, ou qualquer outra métrica que descreve trades fechados e realizados.
Diagnósticos
| Campo | Tipo | Descrição |
|---|---|---|
| conditionsDistributionPct | object | Indexado por quantas conditions eram simultaneamente true (0, 1, 2...N), não pelo nome da condition — pra cada contagem, a fração de todos os candles onde exatamente esse número de conditions eram true ao mesmo tempo. |
| scoreDistributionPct | object | Pra cada valor de score possível: fração de candles com esse score. |
| confirmedSignalsCount | number | Só tem significado quando a requisição declarou confirmationSources — quantos candidatos passaram pela confirmação e viraram um dos trades acima. |
| unconfirmedSignalsCount | number | Só tem significado quando a requisição declarou confirmationSources — quantos candidatos foram descartados antes da simulação de trade por não terem sido corroborados. confirmedSignalsCount + unconfirmedSignalsCount é igual ao total de candidatos que um backtest sem confirmationSources teria produzido. |
Os campos conditionsDistributionPct e scoreDistributionPct são um diagnóstico de ajuste de estratégia, não uma métrica de performance — uma estratégia onde entradas raramente exigem várias conditions simultaneamente true é menos seletiva. Use isso pra diagnosticar e refinar a estrutura da sua estratégia. Veja Confirmation Sources acima pro que alimenta confirmedSignalsCount/unconfirmedSignalsCount.
Campos de detalhe do trade
Formato de cada item retornado por GET /backtest/:id/trades — o único lugar onde detalhe trade-a-trade está disponível.
| Campo | Tipo | Descrição |
|---|---|---|
| number | number | Número sequencial do trade. |
| entryPrice | number | Preço em que o trade foi aberto. |
| exitPrice | number | Preço em que o trade foi fechado. |
| pnlR | number | +3.0 pra um ganho, -1.0 pra uma perda com o riskManagement padrão; outros valores se a estratégia configura seu próprio stopLoss/takeProfit. Já líquido de configuration.entryFeePct/exitFeePct quando definido. |
| pnlPct | number | Ganho/perda percentual no trade. Já líquido de entryFeePct + exitFeePct quando definido. |
| feeR | number | Unidades de R subtraídas do pnlR desse trade por entryFeePct/exitFeePct (0 se nenhum foi definido). pnlR + feeR recupera o R bruto pré-taxa. Não diretamente comparável entre trades com riskDistance diferentes — veja o aviso abaixo. |
| stopPrice | number | O preço de stop-loss contra o qual esse trade foi gerenciado. |
| takePrice | number | O preço de take-profit contra o qual esse trade foi gerenciado. |
| riskDistance | number | Distância de preço entre entryPrice e stopPrice — a unidade em que pnlR/feeR são expressos. |
| holdingCandles | number | Contagem de candles independente de timeframe que a posição ficou aberta (índice do candle de saída menos índice do candle de entrada). Valor mínimo possível é 1, não 0 — uma posição aberta no candle i pode fechar no mais cedo no candle i+1. |
| exitReason | number | O que fechou o trade: 0 = stop-loss, 1 = take-profit, 2 = sinal de decision.exit. |
| totalScoreAtEntry | number | Score total que disparou a entrada. |
| conditionsAtEntry | object | Quais conditions eram true na entrada. |
| scoreBreakdownAtEntry | object | Score contribuído por cada condition na entrada. |
feeR escala inversamente com o próprio riskDistance de cada trade — um stop apertado transforma uma taxa pequena em vários R de custo, um stop largo torna a mesma taxa quase invisível em termos de R. Uma vez que entryFeePct/exitFeePct são definidos, expectancyR só é uma comparação justa dentro da própria convenção de stop de uma estratégia — prefira comparações baseadas em pnlPct entre estratégias ou timeframes com larguras de stop tipicamente diferentes.#Códigos de erro
Toda resposta de erro tem o mesmo formato: { "error": "<código>", "message": "<texto>" }.
| Status | Código | Descrição |
|---|---|---|
| 400 | invalid_payload | O corpo da requisição está malformado, faltando campos obrigatórios, ou o strategySnapshotJson falha na validação (intervalo de data ruim, ativo desconhecido, expressão de DSL inválida, etc.) — confira o campo message pros detalhes. |
| 401 | api_key_missing | O header x-api-key não foi enviado. |
| 401 | api_key_invalid | A API key fornecida é inválida, revogada ou inativa. |
| 402 | insufficient_credits | Créditos insuficientes pra executar esse backtest. |
| 429 | rate_limit_exceeded | Muitas requisições pra essa conta. Reduza o ritmo e tente de novo. |
| 500 | execution_failed | Erro de execução no servidor. Confira o campo errorMessage. |
400 não distinguem hoje um corpo de requisição malformado de uma estratégia inválida, um ativo não suportado, ou uma data fora do intervalo — todos esses compartilham o único código invalid_payload hoje, com o motivo específico só em message. Não ramifique num código mais granular pra esses casos; leia o texto da mensagem em vez disso.Quando um erro acontece no nível de execução (depois da submissão), o status do backtest é definido pra Failed e o campo errorMessage contém uma descrição do que deu errado.