Before attaching a strategy to a live agent, you can validate it against historical data using the backtesting API. You submit a StrategySpec — a structured description of your entry signals, exit signals, regime filter, and position parameters — and Halyrd queues an asynchronous backtest run. Once finished, the result includes a full set of performance metrics and a passed_gate boolean that tells you whether the strategy meets the minimum quality bar required before it can be promoted to the agent. You can also query the GET /indicators endpoint to discover which indicators are available for use in your StrategySpec.
POST /backtests
Queues a new backtest run. The request body must contain a complete StrategySpec. The endpoint returns 202 Accepted immediately with a backtest_id you can use to poll for results.
POST http://localhost:8000/backtests
Content-Type: application/json
Request body
{
"strategy_spec": {
"market_mode": "spot",
"name": "mean-reversion-v1",
"signals": {
"entry": {
"all_of": [
{ "indicator": "price_vs_sma", "op": "<=", "value": -0.05, "window": 48 },
{ "indicator": "rsi", "op": "<=", "value": 25, "window": 14 },
{ "indicator": "fear_greed", "op": "<=", "value": 40 }
]
},
"exit": {
"any_of": [
{ "indicator": "price_vs_sma", "op": ">=", "value": 0.0 },
{ "indicator": "rsi", "op": ">=", "value": 65 }
]
}
},
"regime_filter": {
"stand_aside_when": [
{ "indicator": "funding_rate", "op": ">=", "value": 0.03 }
]
},
"params": {
"position_size_pct": 90,
"max_concurrent": 1
}
}
}
StrategySpec fields
| Field | Description |
|---|
market_mode | Trading mode for this strategy — currently spot. |
name | Human-readable strategy name for identification in results. |
signals.entry | Entry condition using all_of (all conditions must be true) or any_of (at least one must be true). |
signals.exit | Exit condition using the same all_of / any_of structure. |
regime_filter.stand_aside_when | Conditions that, when met, suppress new entries entirely (e.g. high funding rate). |
params.position_size_pct | Percentage of available equity to deploy per trade. |
params.max_concurrent | Maximum number of positions the strategy may hold simultaneously. |
Each signal condition contains an indicator name, a comparison operator (op), a threshold value, and an optional window (lookback periods for time-series indicators).
Response — 202 Accepted
{
"backtest_id": "bt_abc123"
}
GET /backtests/
Returns the current status and, once finished, the full metrics for a backtest run.
GET http://localhost:8000/backtests/bt_abc123
Response
{
"id": "bt_abc123",
"status": "finished",
"metrics": {
"net_return_pct": 4.2,
"max_drawdown_pct": 8.1,
"expectancy": 0.0042,
"trades_per_day": 1.3,
"win_rate": 0.62,
"ratio_avg_win_loss": 1.4
},
"passed_gate": true
}
Status values
| Value | Description |
|---|
queued | The backtest is waiting for a worker to pick it up. |
running | The backtest is actively processing historical data. |
finished | The backtest completed successfully; metrics and passed_gate are populated. |
failed | The backtest encountered an error; inspect the error field for details. |
Metrics fields
| Field | Description |
|---|
net_return_pct | Total percentage return over the backtest period, net of fees. |
max_drawdown_pct | Largest peak-to-trough portfolio decline observed during the backtest, as a percentage. |
expectancy | Average expected profit per trade as a fraction of trade size. Positive values indicate an edge. |
trades_per_day | Average number of trades closed per calendar day over the backtest period. |
win_rate | Fraction of trades that closed with a positive PnL (e.g. 0.62 = 62% win rate). |
ratio_avg_win_loss | Ratio of average winning trade size to average losing trade size. Values above 1.0 indicate wins are larger than losses on average. |
passed_gate
passed_gate is true when all three of the following conditions hold simultaneously:
expectancy > 0 — the strategy has a measurable positive edge
- Closed trades during the backtest period ≥ the configured
trade_floor — sufficient sample size
max_drawdown_pct ≤ the configured drawdown cap — risk is within acceptable bounds
A strategy must pass the gate before it can be attached to an agent and promoted to live.
WebSocket progress events
While a backtest is running, you can subscribe to real-time progress over the WebSocket connection. Two event types are emitted:
{ "event": "backtest_progress", "backtest_id": "bt_abc123", "pct": 45 }
{ "event": "backtest_done", "backtest_id": "bt_abc123", "status": "finished" }
| Event | Description |
|---|
backtest_progress | Emitted periodically while the backtest is running. pct is the estimated completion percentage (0–100). |
backtest_done | Emitted once when the backtest reaches a terminal state (finished or failed). Poll GET /backtests/{id} after receiving this to retrieve the full result. |
Backtests typically complete in under a minute. There is no cancel endpoint — once submitted, a run processes to completion. Backtest sessions persist on the server, so you can retrieve results via GET /backtests/{id} at any time after the run finishes, even after reconnecting.
GET /indicators
Returns the list of indicators available for use in StrategySpec signal and regime filter conditions.
GET http://localhost:8000/indicators
Response
[
{ "name": "rsi", "description": "Relative Strength Index", "params": ["window"] },
{ "name": "price_vs_sma", "description": "Price deviation from simple moving average", "params": ["window"] },
{ "name": "fear_greed", "description": "Crypto Fear & Greed Index (0–100)", "params": [] },
{ "name": "funding_rate", "description": "Perpetual futures funding rate", "params": [] }
]
Use the name values from this response directly as the indicator field in your StrategySpec signal conditions.