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

---
title: Training Runs
description: Track reinforcement learning training runs, report episode metrics, and visualise learning curves from the tradeready-gym Gymnasium wrapper.
---

The training API provides server-side observation of reinforcement learning training runs. It is designed to work with the `tradeready-gym` Gymnasium wrapper, which calls the write endpoints automatically. Use the read endpoints to monitor progress from a dashboard, notebook, or CLI.

> **Info:**
> The `tradeready-gym` Gymnasium wrapper calls `POST /training/runs`, `POST /runs/{id}/episodes`, and `POST /runs/{id}/complete` automatically. You only need to call these endpoints directly if you are building your own training loop.

---

## Client-Provided UUID Pattern

Unlike most platform resources, training runs use a **client-generated UUID**. This means:

1. Your training process generates the UUID before the run starts: `run_id = str(uuid.uuid4())`
2. You pass it to `POST /training/runs` in the request body
3. All subsequent episode reports and the final `complete` call reference the same UUID

This allows the training process to be authoritative about its identity, which avoids race conditions when retrying registration on network errors. If you call `POST /training/runs` twice with the same `run_id`, the second call is idempotent.

---

## Endpoint Summary

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `POST` | `/training/runs` | Required | Register a new training run |
| `POST` | `/training/runs/{run_id}/episodes` | Required | Report a completed episode |
| `POST` | `/training/runs/{run_id}/complete` | Required | Mark a run as complete |
| `GET` | `/training/runs` | Required | List all runs |
| `GET` | `/training/runs/{run_id}` | Required | Full run detail with learning curve |
| `GET` | `/training/runs/{run_id}/learning-curve` | Required | Learning curve data for charting |
| `GET` | `/training/compare` | Required | Compare multiple runs side-by-side |

---

## POST /training/runs

Register a new training run. Call this once when training begins. The `tradeready-gym` wrapper calls this automatically on environment creation.

**Auth required:** Yes

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `run_id` | UUID string | Yes | Client-generated UUID for this run |
| `config` | object | No | Training hyperparameters and environment settings (stored as-is) |
| `strategy_id` | UUID string | No | Optional UUID of an associated strategy |

**Response: HTTP 201**

| Field | Type | Description |
|-------|------|-------------|
| `run_id` | UUID string | Training run identifier (echoed back) |
| `status` | string | `"running"` |
| `config` | object \| null | Stored training configuration |
| `episodes_total` | integer \| null | Total episodes planned (null until known) |
| `episodes_completed` | integer | Episodes reported so far (0) |
| `started_at` | ISO-8601 datetime \| null | Start timestamp |
| `completed_at` | ISO-8601 datetime \| null | Null until complete |

**curl:**

```bash
curl -X POST https://api.tradeready.io/api/v1/training/runs \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ak_live_..." \
  -d '{
    "run_id": "550e8400-e29b-41d4-a716-446655440000",
    "config": {
      "algorithm": "PPO",
      "learning_rate": 0.0003,
      "n_steps": 2048,
      "total_timesteps": 1000000,
      "pairs": ["BTCUSDT", "ETHUSDT"],
      "episode_duration_days": 30,
      "starting_balance": "10000"
    },
    "strategy_id": "your-strategy-uuid"
  }'
```
**Python SDK:**

```python
import uuid
from agentexchange import AgentExchangeClient

run_id = str(uuid.uuid4())

with AgentExchangeClient(api_key="ak_live_...") as client:
    run = client.register_training_run(
        run_id=run_id,
        config={
            "algorithm": "PPO",
            "learning_rate": 0.0003,
            "total_timesteps": 1_000_000,
            "pairs": ["BTCUSDT", "ETHUSDT"],
        },
        strategy_id="your-strategy-uuid",
    )
    print(run.run_id)    # your UUID
    print(run.status)    # "running"
```

**Response example:**

```json
{
  "run_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "running",
  "config": {
    "algorithm": "PPO",
    "learning_rate": 0.0003,
    "total_timesteps": 1000000,
    "pairs": ["BTCUSDT", "ETHUSDT"]
  },
  "episodes_total": null,
  "episodes_completed": 0,
  "started_at": "2026-03-19T10:00:00Z",
  "completed_at": null
}
```

---

## POST /training/runs/{run_id}/episodes

Report a completed training episode with performance metrics. Call this after every episode in your training loop.

**Auth required:** Yes

**Path parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `run_id` | UUID | Training run identifier |

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `episode_number` | integer (≥ 1) | Yes | Sequential episode number |
| `session_id` | UUID string | No | Backtest session UUID if using the platform sandbox |
| `roi_pct` | float | No | Episode return on investment (%) |
| `sharpe_ratio` | float | No | Episode Sharpe ratio |
| `max_drawdown_pct` | float | No | Episode maximum drawdown (%) |
| `total_trades` | integer | No | Number of trades placed in this episode |
| `reward_sum` | float | No | Total RL reward accumulated in the episode |

All metric fields are optional — report only what your environment provides.

**Response: HTTP 200** — updated `TrainingRunResponse` with incremented `episodes_completed`

**curl:**

```bash
curl -X POST https://api.tradeready.io/api/v1/training/runs/RUN_ID/episodes \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ak_live_..." \
  -d '{
    "episode_number": 42,
    "roi_pct": 3.7,
    "sharpe_ratio": 1.2,
    "max_drawdown_pct": 6.8,
    "total_trades": 18,
    "reward_sum": 245.3
  }'
```
**Python SDK:**

```python
# Call after each episode in your training loop
run = client.report_training_episode(
    run_id=run_id,
    episode_number=episode_num,
    roi_pct=episode_roi,
    sharpe_ratio=episode_sharpe,
    max_drawdown_pct=episode_drawdown,
    total_trades=num_trades,
    reward_sum=total_reward,
)
print(f"Episodes completed: {run.episodes_completed}")
```

---

## POST /training/runs/{run_id}/complete

Mark a training run as complete. Call this when training finishes normally or is stopped early. The platform computes aggregate statistics and the final learning curve.

**Auth required:** Yes

**Request body:** None

**Response: HTTP 200** — `TrainingRunResponse` with `status: "completed"` and `completed_at` set

**curl:**

```bash
curl -X POST https://api.tradeready.io/api/v1/training/runs/RUN_ID/complete \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
run = client.complete_training_run(run_id=run_id)
print(run.status)        # "completed"
print(run.completed_at)  # datetime
```

---

## GET /training/runs

List all training runs for the authenticated account.

**Auth required:** Yes

**Query parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `status` | string | — | Filter: `"running"`, `"completed"`, or `"failed"` |
| `limit` | integer (1–100) | 50 | Page size |
| `offset` | integer | 0 | Pagination offset |

**Response: HTTP 200** — array of `TrainingRunResponse` objects

**curl:**

```bash
# List all completed runs
curl "https://api.tradeready.io/api/v1/training/runs?status=completed&limit=20" \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
runs = client.list_training_runs(status="completed", limit=20)
for run in runs:
    print(f"{run.run_id}: {run.episodes_completed} episodes")
```

---

## GET /training/runs/{run_id}

Get full training run detail including the pre-computed learning curve, aggregate statistics, and per-episode records.

**Auth required:** Yes

**Response: HTTP 200**

All `TrainingRunResponse` fields plus:

| Field | Type | Description |
|-------|------|-------------|
| `learning_curve` | object \| null | Pre-computed learning curve (metric name → arrays) |
| `aggregate_stats` | object \| null | Aggregate statistics across all completed episodes |
| `episodes` | array | Per-episode records: `episode_number`, `metrics`, `created_at` |

**curl:**

```bash
curl https://api.tradeready.io/api/v1/training/runs/RUN_ID \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
detail = client.get_training_run(run_id=run_id)
print(f"Status: {detail.status}")
print(f"Total episodes: {detail.episodes_completed}")

if detail.aggregate_stats:
    print(f"Avg ROI: {detail.aggregate_stats.get('avg_roi_pct', 'N/A'):.2f}%")
    print(f"Best episode ROI: {detail.aggregate_stats.get('max_roi_pct', 'N/A'):.2f}%")

# Walk through episodes
for ep in detail.episodes:
    metrics = ep["metrics"]
    print(f"Episode {ep['episode_number']}: ROI={metrics.get('roi_pct', 'N/A')}")
```

**Response example:**

```json
{
  "run_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "config": {"algorithm": "PPO", "learning_rate": 0.0003},
  "episodes_total": null,
  "episodes_completed": 100,
  "started_at": "2026-03-19T10:00:00Z",
  "completed_at": "2026-03-19T14:30:00Z",
  "learning_curve": {
    "roi_pct": [1.2, 1.8, 2.1, 2.6, 3.1, 3.7],
    "sharpe_ratio": [0.4, 0.6, 0.7, 0.9, 1.0, 1.2]
  },
  "aggregate_stats": {
    "avg_roi_pct": 3.7,
    "avg_sharpe_ratio": 1.1,
    "avg_max_drawdown_pct": 7.2,
    "max_roi_pct": 8.4,
    "min_roi_pct": -2.1
  },
  "episodes": [
    {
      "episode_number": 1,
      "metrics": {"roi_pct": 1.2, "sharpe_ratio": 0.4, "total_trades": 12, "reward_sum": 45.2},
      "created_at": "2026-03-19T10:05:00Z"
    }
  ]
}
```

---

## GET /training/runs/{run_id}/learning-curve

Get learning curve data points ready for charting, with optional moving-average smoothing.

**Auth required:** Yes

**Query parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `metric` | string | `"roi_pct"` | Metric to plot: `"roi_pct"`, `"sharpe_ratio"`, `"max_drawdown_pct"`, `"reward_sum"` |
| `window` | integer (1–100) | 10 | Moving average smoothing window |

**Response: HTTP 200**

| Field | Type | Description |
|-------|------|-------------|
| `episode_numbers` | integer array | X-axis: episode numbers |
| `raw_values` | float array | Raw metric values per episode |
| `smoothed_values` | float array | Moving-average smoothed values |
| `metric` | string | Metric name |
| `window` | integer | Smoothing window used |

**curl:**

```bash
curl "https://api.tradeready.io/api/v1/training/runs/RUN_ID/learning-curve?metric=roi_pct&window=10" \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
curve = client.get_learning_curve(
    run_id=run_id,
    metric="roi_pct",
    window=10,
)
# Plot with matplotlib
import matplotlib.pyplot as plt
plt.plot(curve.episode_numbers, curve.raw_values, alpha=0.3, label="raw")
plt.plot(curve.episode_numbers, curve.smoothed_values, label="smoothed (10-ep MA)")
plt.xlabel("Episode")
plt.ylabel("ROI %")
plt.legend()
plt.show()
```

**Response example:**

```json
{
  "episode_numbers": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  "raw_values": [1.2, -0.3, 2.1, 3.4, 1.8, 4.2, 2.9, 3.7, 5.1, 4.8],
  "smoothed_values": [null, null, null, null, null, null, null, null, null, 2.89],
  "metric": "roi_pct",
  "window": 10
}
```

> **Info:**
> The first `window - 1` values in `smoothed_values` are `null` because there are not enough data points for the moving average. Plot `smoothed_values` starting from index `window - 1`.

---

## GET /training/compare

Compare multiple training runs side-by-side using their aggregate statistics.

**Auth required:** Yes

**Query parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `run_ids` | string | Yes | Comma-separated list of run UUIDs |

**Response: HTTP 200**

| Field | Type | Description |
|-------|------|-------------|
| `runs` | array | List of run comparison objects |

**Run comparison object:**

| Field | Type | Description |
|-------|------|-------------|
| `run_id` | UUID string | Training run identifier |
| `status` | string | Run status |
| `episodes_completed` | integer | Episodes completed |
| `aggregate_stats` | object \| null | Aggregate statistics (avg ROI, Sharpe, etc.) |
| `started_at` | string \| null | Start timestamp |
| `completed_at` | string \| null | Completion timestamp |

**curl:**

```bash
curl "https://api.tradeready.io/api/v1/training/compare?run_ids=RUN_ID_1,RUN_ID_2,RUN_ID_3" \
  -H "X-API-Key: ak_live_..."
```
**Python SDK:**

```python
comparison = client.compare_training_runs(
    run_ids=["run-uuid-1", "run-uuid-2", "run-uuid-3"],
)
for run in comparison.runs:
    stats = run.aggregate_stats or {}
    print(f"{run.run_id}: avg ROI={stats.get('avg_roi_pct', 'N/A'):.2f}%  "
          f"Sharpe={stats.get('avg_sharpe_ratio', 'N/A'):.2f}  "
          f"episodes={run.episodes_completed}")
```

**Response example:**

```json
{
  "runs": [
    {
      "run_id": "550e8400-...",
      "status": "completed",
      "episodes_completed": 100,
      "aggregate_stats": {
        "avg_roi_pct": 3.7,
        "avg_sharpe_ratio": 1.1,
        "avg_max_drawdown_pct": 7.2
      },
      "started_at": "2026-03-19T10:00:00Z",
      "completed_at": "2026-03-19T14:30:00Z"
    },
    {
      "run_id": "660f9511-...",
      "status": "completed",
      "episodes_completed": 150,
      "aggregate_stats": {
        "avg_roi_pct": 5.2,
        "avg_sharpe_ratio": 1.4,
        "avg_max_drawdown_pct": 6.1
      },
      "started_at": "2026-03-18T09:00:00Z",
      "completed_at": "2026-03-18T16:00:00Z"
    }
  ]
}
```

---

## Incremental Episode Reporting Flow

The full lifecycle of a training run that uses the platform sandbox for episodes:

```
1. Generate run_id  →  POST /training/runs        (register)
                              │
                    ┌─────────▼──────────┐
                    │  for each episode:  │
                    │                    │
                    │  POST /backtest/create
                    │  POST /backtest/{id}/start
                    │  ... step loop ...
                    │  GET /backtest/{id}/results → extract metrics
                    │                    │
                    │  POST /training/runs/{id}/episodes
                    └─────────┬──────────┘
                              │
                    POST /training/runs/{id}/complete
                              │
                    GET /training/runs/{id}/learning-curve
```

The `tradeready-gym` Gymnasium wrapper handles everything in the first column automatically. Use the query endpoints for observability.

---

## Related Pages

- [Strategy Testing](/docs/api/strategy-testing) — multi-episode strategy validation
- [Backtesting](/docs/api/backtesting) — individual backtest sessions
- [Gymnasium Environments](/docs/gym) — `tradeready-gym` integration guide
- [Errors](/docs/api/errors) — error code reference
