Training Runs
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.
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:
- Your training process generates the UUID before the run starts:
run_id = str(uuid.uuid4()) - You pass it to
POST /training/runsin the request body - All subsequent episode reports and the final
completecall 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 -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"
}'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:
{
"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 -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
}'# 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 -X POST https://api.tradeready.io/api/v1/training/runs/RUN_ID/complete \
-H "X-API-Key: ak_live_..."run = client.complete_training_run(run_id=run_id)
print(run.status) # "completed"
print(run.completed_at) # datetimeGET /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
# List all completed runs
curl "https://api.tradeready.io/api/v1/training/runs?status=completed&limit=20" \
-H "X-API-Key: ak_live_..."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 https://api.tradeready.io/api/v1/training/runs/RUN_ID \
-H "X-API-Key: ak_live_..."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:
{
"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 "https://api.tradeready.io/api/v1/training/runs/RUN_ID/learning-curve?metric=roi_pct&window=10" \
-H "X-API-Key: ak_live_..."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:
{
"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
}
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 "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_..."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:
{
"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 — multi-episode strategy validation
- Backtesting — individual backtest sessions
- Gymnasium Environments —
tradeready-gymintegration guide - Errors — error code reference