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:
| Field | Quick interpretation |
|---|---|
sharpe | Risk-adjusted return. > 1 is good, > 2 is excellent. |
sortino | Like Sharpe but only penalizes downside volatility. |
max_drawdown | Worst peak-to-trough loss. Closer to 0 is better. |
win_rate | Fraction of profitable trades. |
profit_factor | Gross wins / gross losses. > 1.5 is healthy. |
cagr | Compound annual growth rate of equity. |
expected_value | Average 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_LIMITEDuntil 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.