<!-- Generated from TradeReady.io docs. Visit https://tradeready.io/docs for the full experience. -->

---
title: Strategy Testing
description: Run multi-episode backtests against your strategy definitions and compare versions to find the best configuration.
---

Strategy testing lets you validate a strategy definition against historical market data across multiple independent episodes before deploying it to live trading. Instead of a single backtest run, the engine draws many episodes from a date range and aggregates the results — giving you a statistically meaningful view of how your strategy performs across different market conditions.

> **Info:**
> All strategy test endpoints are under `/api/v1/strategies/{strategy_id}/` and require authentication via `X-API-Key` or `Authorization: Bearer`.

---

## How Multi-Episode Testing Works

A single backtest run can look good or bad depending on the time period you happened to pick. Multi-episode testing solves this:

1. You define a **date range** (e.g. all of 2025) and an **episode duration** (e.g. 30 days)
2. The engine draws `N` episodes from that range — either randomly or sequentially
3. Each episode is an independent backtest with its own starting balance
4. Results are **aggregated** across all episodes: average ROI, average Sharpe, win rate across episodes
5. The engine generates **recommendations** based on the aggregated metrics

The result tells you not just "did this strategy make money?" but "does this strategy make money consistently?"

---

## Endpoint Summary

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/strategies/{strategy_id}/test` | Start a new test run |
| `GET` | `/strategies/{strategy_id}/tests` | List all test runs for a strategy |
| `GET` | `/strategies/{strategy_id}/tests/{test_id}` | Get test status and results |
| `POST` | `/strategies/{strategy_id}/tests/{test_id}/cancel` | Cancel a running test |
| `GET` | `/strategies/{strategy_id}/test-results` | Get the latest completed results |
| `GET` | `/strategies/{strategy_id}/compare-versions` | Compare two versions side-by-side |

---

## POST /strategies/{strategy_id}/test

Start a new asynchronous test run. Tests run in the background — poll `GET /tests/{test_id}` to check progress.

**Auth required:** Yes

**Path parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `strategy_id` | UUID | Strategy identifier |

**Request body:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `version` | integer (≥ 1) | Yes | — | Strategy version to test |
| `episodes` | integer (1–100) | No | 10 | Number of independent episodes to run |
| `date_range.start` | ISO-8601 datetime | Yes | — | Start of the candidate date pool |
| `date_range.end` | ISO-8601 datetime | Yes | — | End of the candidate date pool |
| `randomize_dates` | boolean | No | true | Randomly sample episode start dates within the range |
| `episode_duration_days` | integer (1–365) | No | 30 | Length of each episode in days |
| `starting_balance` | string | No | `"10000"` | USDT starting balance per episode |

**Response: HTTP 201**

| Field | Type | Description |
|-------|------|-------------|
| `test_run_id` | UUID string | Test run identifier — use to poll for results |
| `status` | string | `"queued"` or `"running"` |
| `episodes_total` | integer | Total episodes requested |
| `episodes_completed` | integer | Episodes completed so far |
| `progress_pct` | float | Completion percentage (0–100) |
| `version` | integer | Strategy version being tested |

**curl:**

```bash
curl -X POST https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/test \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ak_live_..." \
  -d '{
    "version": 1,
    "episodes": 20,
    "date_range": {
      "start": "2025-01-01T00:00:00Z",
      "end": "2025-12-31T23:59:59Z"
    },
    "randomize_dates": true,
    "episode_duration_days": 30,
    "starting_balance": "10000"
  }'
```
**Python SDK:**

```python
from agentexchange import AgentExchangeClient

with AgentExchangeClient(api_key="ak_live_...") as client:
    test = client.start_strategy_test(
        strategy_id="your-strategy-uuid",
        version=1,
        episodes=20,
        date_range_start="2025-01-01T00:00:00Z",
        date_range_end="2025-12-31T23:59:59Z",
        randomize_dates=True,
        episode_duration_days=30,
        starting_balance="10000",
    )
    print(test.test_run_id)  # save this to poll for results
    print(test.status)       # "queued"
```

**Response example:**

```json
{
  "test_run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "queued",
  "episodes_total": 20,
  "episodes_completed": 0,
  "progress_pct": 0,
  "version": 1
}
```

---

## GET /strategies/{strategy_id}/tests

List all test runs for a strategy, ordered newest-first.

**Auth required:** Yes

**Response: HTTP 200** — array of test run objects (same shape as `POST` response above)

**curl:**

```bash
curl https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/tests \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
tests = client.list_strategy_tests(strategy_id="your-strategy-uuid")
for test in tests:
    print(f"{test.test_run_id}: {test.status} ({test.progress_pct:.1f}%)")
```

---

## GET /strategies/{strategy_id}/tests/{test_id}

Get the full status and results for a test run. Poll this endpoint until `status` is `"completed"` or `"failed"`.

**Auth required:** Yes

**Path parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `strategy_id` | UUID | Strategy identifier |
| `test_id` | UUID | Test run identifier |

**Response: HTTP 200**

All test run fields plus:

| Field | Type | Description |
|-------|------|-------------|
| `results` | object \| null | Aggregated results (present when `status` is `"completed"`) |
| `recommendations` | string array \| null | Improvement suggestions generated from results |
| `config` | object \| null | Snapshot of the test configuration used |

**Results object (when completed):**

| Field | Type | Description |
|-------|------|-------------|
| `avg_roi_pct` | float | Average ROI across all episodes |
| `avg_sharpe` | float | Average Sharpe ratio across episodes |
| `avg_max_drawdown_pct` | float | Average maximum drawdown |
| `win_rate_pct` | float | Percentage of episodes that ended with positive ROI |
| `total_trades` | integer | Total trades executed across all episodes |
| `episodes_completed` | integer | Number of episodes included in aggregation |

**curl:**

```bash
# Poll until completed
curl https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/tests/TEST_ID \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
import time

# Poll until done
while True:
    test = client.get_strategy_test(
        strategy_id="your-strategy-uuid",
        test_id="your-test-uuid",
    )
    print(f"Progress: {test.progress_pct:.1f}% ({test.episodes_completed}/{test.episodes_total})")
    if test.status in ("completed", "failed", "cancelled"):
        break
    time.sleep(5)

if test.status == "completed":
    print(f"Avg ROI: {test.results['avg_roi_pct']:.2f}%")
    print(f"Avg Sharpe: {test.results['avg_sharpe']:.2f}")
    print(f"Episode win rate: {test.results['win_rate_pct']:.1f}%")
    print("Recommendations:")
    for rec in test.recommendations or []:
        print(f"  - {rec}")
```

**Response example (completed):**

```json
{
  "test_run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "episodes_total": 20,
  "episodes_completed": 20,
  "progress_pct": 100.0,
  "version": 1,
  "created_at": "2026-03-19T10:00:00Z",
  "started_at": "2026-03-19T10:00:01Z",
  "completed_at": "2026-03-19T10:02:30Z",
  "results": {
    "avg_roi_pct": 3.2,
    "avg_sharpe": 0.81,
    "avg_max_drawdown_pct": 8.4,
    "win_rate_pct": 60.0,
    "total_trades": 248,
    "episodes_completed": 20
  },
  "recommendations": [
    "Consider tightening the stop-loss: average drawdown of 8.4% suggests positions are held too long through losses.",
    "Win rate of 60% across episodes is solid — the entry conditions are identifying good opportunities.",
    "Low trade count per episode (12 average) may indicate the entry conditions are too restrictive."
  ],
  "config": {
    "episodes": 20,
    "date_range": {"start": "2025-01-01T00:00:00Z", "end": "2025-12-31T23:59:59Z"},
    "randomize_dates": true,
    "episode_duration_days": 30,
    "starting_balance": "10000"
  }
}
```

---

## POST /strategies/{strategy_id}/tests/{test_id}/cancel

Cancel a running or queued test run. Partial results are discarded.

**Auth required:** Yes

**Request body:** None

**Response: HTTP 200** — test run object with `status: "cancelled"`

**curl:**

```bash
curl -X POST https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/tests/TEST_ID/cancel \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
test = client.cancel_strategy_test(
    strategy_id="your-strategy-uuid",
    test_id="your-test-uuid",
)
print(test.status)  # "cancelled"
```

---

## GET /strategies/{strategy_id}/test-results

Shortcut to get the latest completed test results without knowing the test run ID. Returns `404` if no completed test runs exist.

**Auth required:** Yes

**Response: HTTP 200** — same shape as `GET /tests/{test_id}` when completed

**curl:**

```bash
curl https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/test-results \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
results = client.get_latest_test_results(strategy_id="your-strategy-uuid")
print(f"Latest test avg ROI: {results.results['avg_roi_pct']:.2f}%")
```

---

## GET /strategies/{strategy_id}/compare-versions

Compare the latest completed test results for two different strategy versions side-by-side.

**Auth required:** Yes

**Query parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `v1` | integer (≥ 1) | Yes | First version number |
| `v2` | integer (≥ 1) | Yes | Second version number |

**Response: HTTP 200**

| Field | Type | Description |
|-------|------|-------------|
| `v1` | object | Metrics for version 1 |
| `v2` | object | Metrics for version 2 |
| `improvements` | object | Delta values (positive = v2 is better): `roi_pct`, `sharpe` |
| `verdict` | string | Plain-English summary of which version performs better |

**Version metrics object:**

| Field | Type | Description |
|-------|------|-------------|
| `version` | integer | Version number |
| `avg_roi_pct` | float \| null | Average ROI (null if no completed tests) |
| `avg_sharpe` | float \| null | Average Sharpe ratio |
| `avg_max_drawdown_pct` | float \| null | Average max drawdown |
| `total_trades` | integer | Total trades across episodes |
| `episodes_completed` | integer | Number of episodes used |

**curl:**

```bash
curl "https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/compare-versions?v1=1&v2=2" \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
comparison = client.compare_strategy_versions(
    strategy_id="your-strategy-uuid",
    v1=1,
    v2=2,
)
print(comparison.verdict)
print(f"ROI improvement: {comparison.improvements.get('roi_pct', 0):+.2f}%")
print(f"Sharpe improvement: {comparison.improvements.get('sharpe', 0):+.2f}")
```

**Response example:**

```json
{
  "v1": {
    "version": 1,
    "avg_roi_pct": 3.2,
    "avg_sharpe": 0.81,
    "avg_max_drawdown_pct": 8.4,
    "total_trades": 248,
    "episodes_completed": 20
  },
  "v2": {
    "version": 2,
    "avg_roi_pct": 5.1,
    "avg_sharpe": 1.12,
    "avg_max_drawdown_pct": 6.8,
    "total_trades": 196,
    "episodes_completed": 20
  },
  "improvements": {
    "roi_pct": 1.9,
    "sharpe": 0.31
  },
  "verdict": "Version 2 improves on version 1 across both ROI and Sharpe ratio."
}
```

---

## Recommendation Rules

The platform generates up to 11 types of recommendations based on aggregated test results. These are plain-English suggestions that identify the most impactful improvements:

| Rule trigger | Recommendation category |
|---|---|
| `avg_max_drawdown_pct > 15%` | Tighten stop-loss or reduce position size |
| `win_rate_pct < 40%` | Entry conditions may be too permissive — add filters |
| `win_rate_pct > 80%` | Consider larger position sizes or more aggressive take-profits |
| `avg_roi_pct < 0` | Strategy is net-negative — review entry/exit logic |
| `avg_sharpe < 0.5` | Returns are not commensurate with risk — review sizing |
| `avg_sharpe > 2.0` | Very strong risk-adjusted returns — consider deployment |
| Total trades per episode < 5 | Entry conditions may be too restrictive |
| Total trades per episode > 100 | Over-trading — entry conditions may be too loose |
| `avg_max_drawdown_pct < 5%` | Risk management is conservative — could accept more risk for higher returns |
| Episodes with positive ROI < 50% | Strategy is inconsistent across market conditions |
| `avg_roi_pct > 10%` per 30-day episode | Strong performance — ready for version promotion |

> **Info:**
> Recommendations are advisory, not automatic. You decide whether to act on them by creating a new strategy version and running another test.

---

## Complete Testing Workflow

The typical strategy development loop:

**curl:**

```bash
STRATEGY_ID="your-strategy-uuid"
API_KEY="ak_live_..."

# 1. Create version 1 with initial conditions
curl -X POST https://api.tradeready.io/api/v1/strategies \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{
    "name": "RSI Mean Reversion",
    "definition": {
      "pairs": ["BTCUSDT", "ETHUSDT"],
      "timeframe": "1h",
      "entry_conditions": {"rsi_below": 30},
      "exit_conditions": {"take_profit_pct": 5, "stop_loss_pct": 2},
      "position_size_pct": 10,
      "max_positions": 3
    }
  }'

# 2. Run a 20-episode test on all of 2025
curl -X POST https://api.tradeready.io/api/v1/strategies/$STRATEGY_ID/test \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{
    "version": 1,
    "episodes": 20,
    "date_range": {"start": "2025-01-01T00:00:00Z", "end": "2025-12-31T23:59:59Z"},
    "episode_duration_days": 30
  }'

# 3. Poll until done
curl https://api.tradeready.io/api/v1/strategies/$STRATEGY_ID/test-results \
  -H "X-API-Key: $API_KEY"

# 4. Create version 2 with improvements based on recommendations
curl -X POST https://api.tradeready.io/api/v1/strategies/$STRATEGY_ID/versions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{
    "definition": {
      "pairs": ["BTCUSDT", "ETHUSDT"],
      "timeframe": "1h",
      "entry_conditions": {"rsi_below": 30, "adx_above": 20},
      "exit_conditions": {"take_profit_pct": 5, "stop_loss_pct": 1.5, "trailing_stop_pct": 3},
      "position_size_pct": 8,
      "max_positions": 3
    },
    "change_notes": "Added ADX filter, tighter stop-loss, added trailing stop"
  }'

# 5. Test version 2 and compare
curl -X POST https://api.tradeready.io/api/v1/strategies/$STRATEGY_ID/test \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{"version": 2, "episodes": 20, "date_range": {"start": "2025-01-01T00:00:00Z", "end": "2025-12-31T23:59:59Z"}, "episode_duration_days": 30}'

curl "https://api.tradeready.io/api/v1/strategies/$STRATEGY_ID/compare-versions?v1=1&v2=2" \
  -H "X-API-Key: $API_KEY"

# 6. Deploy the better version
curl -X POST https://api.tradeready.io/api/v1/strategies/$STRATEGY_ID/deploy \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{"version": 2}'
```
**Python SDK:**

```python
import time
from agentexchange import AgentExchangeClient

with AgentExchangeClient(api_key="ak_live_...") as client:
    # 1. Create strategy
    strategy = client.create_strategy(
        name="RSI Mean Reversion",
        definition={
            "pairs": ["BTCUSDT", "ETHUSDT"],
            "timeframe": "1h",
            "entry_conditions": {"rsi_below": 30},
            "exit_conditions": {"take_profit_pct": 5, "stop_loss_pct": 2},
            "position_size_pct": 10,
            "max_positions": 3,
        },
    )
    sid = strategy.strategy_id

    # 2. Run 20 episodes over all of 2025
    test = client.start_strategy_test(
        strategy_id=sid,
        version=1,
        episodes=20,
        date_range_start="2025-01-01T00:00:00Z",
        date_range_end="2025-12-31T23:59:59Z",
        episode_duration_days=30,
    )

    # 3. Poll until done
    while test.status not in ("completed", "failed", "cancelled"):
        time.sleep(10)
        test = client.get_strategy_test(sid, test.test_run_id)
        print(f"Progress: {test.progress_pct:.0f}%")

    # 4. Read recommendations
    for rec in test.recommendations or []:
        print(f"Recommendation: {rec}")

    # 5. Create v2, test, and compare
    v2 = client.create_strategy_version(
        strategy_id=sid,
        definition={
            "pairs": ["BTCUSDT", "ETHUSDT"],
            "timeframe": "1h",
            "entry_conditions": {"rsi_below": 30, "adx_above": 20},
            "exit_conditions": {"take_profit_pct": 5, "stop_loss_pct": 1.5},
            "position_size_pct": 8,
            "max_positions": 3,
        },
        change_notes="Added ADX filter, tighter stop",
    )

    comparison = client.compare_strategy_versions(sid, v1=1, v2=2)
    print(comparison.verdict)
```

---

## Related Pages

- [Strategies](/docs/api/strategies) — create and manage strategies
- [Backtesting](/docs/api/backtesting) — run individual backtests
- [Errors](/docs/api/errors) — error code reference
