Skip to main content

Backtesting Quick Start

End-to-end walk-through: submit a backtest, poll for completion, and read the metrics. All requests are signed per Authentication.

1. Submit a job

POST a strategy template, symbols, timeframe, and date range. The response contains the job ID you will poll on.

curl -X POST https://api.pipai.example/v1/backtest/jobs \
-H "X-PipAI-API-Key: $API_KEY" \
-H "X-PipAI-Timestamp: $TS" \
-H "X-PipAI-Signature: $SIG" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tpl_grid",
"params": {
"grid_levels": 8,
"upper_price": "72000",
"lower_price": "60000"
},
"symbols": ["BTCUSDT"],
"timeframe": "1h",
"start": "2025-01-01T00:00:00Z",
"end": "2025-03-31T23:59:59Z",
"capital": "10000.00",
"leverage": 1,
"fee_model": "fixed_bps",
"fee_bps": "5"
}'

Sample response:

{
"id": "bt_3c91ef",
"status": "queued",
"config": {
"template_id": "tpl_grid",
"params": { "grid_levels": 8, "upper_price": "72000", "lower_price": "60000" },
"symbols": ["BTCUSDT"],
"timeframe": "1h",
"start": "2025-01-01T00:00:00Z",
"end": "2025-03-31T23:59:59Z",
"capital": "10000.00",
"leverage": 1,
"fee_model": "fixed_bps",
"fee_bps": "5"
},
"submitted_at": "2026-04-29T10:15:00Z",
"started_at": null,
"finished_at": null,
"progress": 0,
"result": null,
"error": null
}

See Create Job for the full field reference.

2. Poll for completion

GET the job by ID. Poll no more often than every 5 seconds. Stop when status reaches a terminal value (done, failed, or cancelled).

curl -X GET https://api.pipai.example/v1/backtest/jobs/bt_3c91ef \
-H "X-PipAI-API-Key: $API_KEY" \
-H "X-PipAI-Timestamp: $TS" \
-H "X-PipAI-Signature: $SIG"

While the job is running, progress advances from 0 to 1 and result is null:

{
"id": "bt_3c91ef",
"status": "running",
"submitted_at": "2026-04-29T10:15:00Z",
"started_at": "2026-04-29T10:15:08Z",
"finished_at": null,
"progress": 0.45,
"result": null,
"error": null
}

Once finished, status is done and result is populated:

{
"id": "bt_3c91ef",
"status": "done",
"submitted_at": "2026-04-29T10:15:00Z",
"started_at": "2026-04-29T10:15:08Z",
"finished_at": "2026-04-29T10:17:42Z",
"progress": 1.0,
"result": {
"metrics": {
"sharpe": "1.84",
"sortino": "2.31",
"calmar": "1.12",
"max_drawdown": "-0.142",
"win_rate": "0.638",
"profit_factor": "1.92",
"cagr": "0.187",
"expected_value": "12.43",
"total_trades": 247,
"avg_trade_duration_seconds": 14820
},
"equity_curve": [ ["2025-01-01T00:00:00Z", "10000.00"], ["2025-01-02T00:00:00Z", "10031.50"] ],
"trades": [ /* ... */ ]
},
"error": null
}

See Get Result for the full response shape.

3. Read metrics

The returned result.metrics object summarizes the backtest. The fields you typically inspect first:

FieldQuick interpretation
sharpeRisk-adjusted return. > 1 is good, > 2 is excellent.
sortinoLike Sharpe but only penalizes downside volatility.
max_drawdownWorst peak-to-trough loss. Closer to 0 is better.
win_rateFraction of profitable trades.
profit_factorGross wins / gross losses. > 1.5 is healthy.
cagrCompound annual growth rate of equity.
expected_valueAverage PnL per trade in quote currency.

For example, with jq:

curl -s -X GET https://api.pipai.example/v1/backtest/jobs/bt_3c91ef \
-H "X-PipAI-API-Key: $API_KEY" \
-H "X-PipAI-Timestamp: $TS" \
-H "X-PipAI-Signature: $SIG" \
| jq '.result.metrics | {sharpe, max_drawdown, win_rate, cagr}'

Full definitions, formulas, and ranges are in Metrics.

Tips

  • Start narrow. Run a single symbol over a few months before scaling to multi-symbol, multi-year sweeps. Iteration is faster and surfaces config errors sooner.
  • Cancel runaway jobs. If a job is taking longer than expected, cancel it instead of letting it occupy a slot.
  • Mind the concurrency limit. A single user may have at most 5 concurrent jobs. Additional submissions are rejected with RATE_LIMITED until a slot frees up.
  • Export results within 90 days. Equity curve and trade lists are garbage-collected after the retention window; metrics summaries are kept indefinitely.