Documentation

Backtesting API

Full reference for the Backtesting API — endpoints, request schema, response schema, metrics, and error codes.

Overview

The Backtesting API exposes two endpoints. You submit a strategy execution request and then poll for the result using the returned backtest ID.

Base URL: https://backtest.emidlabs.com/api/public/v1

All requests require the x-api-key header with a valid API key. Keys are generated in the Console.

#POST /backtest — Submit

POST/backtest

Submit a new backtest for execution.

The body must be application/json. The strategy is embedded as a nested JSON object inside strategySnapshotJson.

Request body

FieldTypeRequiredDescription
strategySnapshotJsonobjectYesStrategy object with configuration, inputs, conditions, score, decision, and (optional) riskManagement.
assetPairstringYesTrading pair. E.g. "BTC-USDC", "ETH-USDC".
initialDatestringYesStart date in ISO format: YYYY-MM-DD.
finalDatestringYesEnd date in ISO format: YYYY-MM-DD.

Supported asset pairs

For now, only crypto markets are supported, sourced from Coinbase and quoted in USDC. Examples:

BTC-USDCETH-USDCSOL-USDCXRP-USDCADA-USDC

See the Data Reference page for the full list of supported pairs and the assets available in the Console. Date ranges must fall within the available historical data for the selected asset.

Submit response

FieldTypeDescription
idstring (UUID)Unique identifier for the backtest.
statusstring"Running" immediately after submission.
assetPairstringThe asset pair used.
initialDatestringStart date of the backtest period.
finalDatestringEnd date of the backtest period.
createdAtUtcstringTimestamp of creation in UTC.
canViewResultbooleanWhether the result is accessible (depends on credits).

#GET /backtest/:id — Fetch Results

GET/backtest/{id}

Fetch the status and results of a submitted backtest.

Query parameters

ParameterTypeDefaultDescription
includeTradesbooleanfalseWhen true, includes the full tradesDetail array in result.
💡
Omit includeTrades or set it to false when you only need aggregate metrics. The trade list can be large for long date ranges, so only request it when you need per-trade analysis.

Response fields

FieldTypeDescription
idstringUUID of the backtest.
statusstring"Running", "Completed", or "Failed".
strategySnapshotJsonstringThe strategy used, serialized.
assetPairstringAsset pair.
initialDate / finalDatestringDate range.
createdAtUtcstringCreation timestamp (UTC).
errorMessagestring | nullError details if status is "Failed".
canViewResultbooleanWhether the result is accessible.
logsJsonstring | nullExecution logs serialized as JSON string.
resultobject | nullFull performance metrics when Completed.

#Metrics Reference

When status is Completed, the result object contains:

Profitability metrics

FieldTypeDescription
pnlRnumberNet profit/loss in R-units. Sum of all trade PnLs.
grossProfitRnumberSum of all winning trades in R-units.
grossLossRnumberSum of all losing trades in R-units (negative).
profitFactornumbergrossProfitR / abs(grossLossR). >1 is profitable.
expectancyRnumberAverage expected R per trade. (winRate × avgWinR) + (lossRate × avgLossR).
avgWinRnumberAverage R on winning trades.
avgLossRnumberAverage R on losing trades (-1.0 by default; varies if the strategy overrides riskManagement.stopLoss).

Trade statistics

FieldTypeDescription
tradesnumberTotal number of trades executed.
winsnumberNumber of winning trades.
lossesnumberNumber of losing trades.
winRatenumberwins / trades. Range: 0–1.
bothHitnumberTrades where both SL and TP were hit in the same candle (resolved as SL).

Diagnostics

FieldTypeDescription
conditionsDistributionPctobjectFor each condition: fraction of candles where it was true.
scoreDistributionPctobjectFor each possible score value: fraction of candles with that score.
💡
The conditionsDistributionPct and scoreDistributionPct fields are key for strategy quality analysis. A condition that is almost always true adds no discriminating power. Use these to diagnose and refine your strategy structure.

Trade detail fields (with includeTrades=true)

FieldTypeDescription
numbernumberSequential trade number.
entryPricenumberPrice at which the trade was entered.
exitPricenumberPrice at which the trade was closed.
pnlRnumber+3.0 for a win, -1.0 for a loss with the default riskManagement; other values if the strategy configures its own stopLoss/takeProfit.
pnlPctnumberPercentage gain/loss on the trade.
totalScoreAtEntrynumberTotal score that triggered the entry.
conditionsAtEntryobjectWhich conditions were true at entry.
scoreBreakdownAtEntryobjectScore contributed by each condition at entry.

#Error Codes

Every error response has the same shape: { "error": "<code>", "message": "<text>" }.

StatusCodeDescription
400invalid_payloadThe request body is malformed, missing required fields, or the strategySnapshotJson fails validation (bad date range, unknown asset, invalid DSL expression, etc.) — check the message field for specifics.
401api_key_missingThe x-api-key header wasn't provided.
401api_key_invalidThe provided API key is invalid, revoked, or inactive.
402insufficient_creditsNot enough credits to execute this backtest.
429rate_limit_exceededToo many requests for this account. Back off and retry.
500execution_failedServer-side execution error. Check errorMessage field.
400 responses don't currently distinguish a malformed request body from an invalid strategy, an unsupported asset, or an out-of-range date — all of these share the single invalid_payload code today, with the specific reason only in message. Don't branch on a more granular code for these cases; read the message text instead.

When an error occurs at the execution level (after submission), the backtest status is set to Failed and the errorMessage field contains a description of what went wrong.