TradeReady.io
REST API

Training Runs

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

Download .md

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:

  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

MethodPathAuthDescription
POST/training/runsRequiredRegister a new training run
POST/training/runs/{run_id}/episodesRequiredReport a completed episode
POST/training/runs/{run_id}/completeRequiredMark a run as complete
GET/training/runsRequiredList all runs
GET/training/runs/{run_id}RequiredFull run detail with learning curve
GET/training/runs/{run_id}/learning-curveRequiredLearning curve data for charting
GET/training/compareRequiredCompare 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:

FieldTypeRequiredDescription
run_idUUID stringYesClient-generated UUID for this run
configobjectNoTraining hyperparameters and environment settings (stored as-is)
strategy_idUUID stringNoOptional UUID of an associated strategy

Response: HTTP 201

FieldTypeDescription
run_idUUID stringTraining run identifier (echoed back)
statusstring"running"
configobject | nullStored training configuration
episodes_totalinteger | nullTotal episodes planned (null until known)
episodes_completedintegerEpisodes reported so far (0)
started_atISO-8601 datetime | nullStart timestamp
completed_atISO-8601 datetime | nullNull 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:

ParameterTypeDescription
run_idUUIDTraining run identifier

Request body:

FieldTypeRequiredDescription
episode_numberinteger (≥ 1)YesSequential episode number
session_idUUID stringNoBacktest session UUID if using the platform sandbox
roi_pctfloatNoEpisode return on investment (%)
sharpe_ratiofloatNoEpisode Sharpe ratio
max_drawdown_pctfloatNoEpisode maximum drawdown (%)
total_tradesintegerNoNumber of trades placed in this episode
reward_sumfloatNoTotal 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 200TrainingRunResponse 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)  # datetime

GET /training/runs

List all training runs for the authenticated account.

Auth required: Yes

Query parameters:

ParameterTypeDefaultDescription
statusstringFilter: "running", "completed", or "failed"
limitinteger (1–100)50Page size
offsetinteger0Pagination 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:

FieldTypeDescription
learning_curveobject | nullPre-computed learning curve (metric name → arrays)
aggregate_statsobject | nullAggregate statistics across all completed episodes
episodesarrayPer-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:

ParameterTypeDefaultDescription
metricstring"roi_pct"Metric to plot: "roi_pct", "sharpe_ratio", "max_drawdown_pct", "reward_sum"
windowinteger (1–100)10Moving average smoothing window

Response: HTTP 200

FieldTypeDescription
episode_numbersinteger arrayX-axis: episode numbers
raw_valuesfloat arrayRaw metric values per episode
smoothed_valuesfloat arrayMoving-average smoothed values
metricstringMetric name
windowintegerSmoothing 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:

ParameterTypeRequiredDescription
run_idsstringYesComma-separated list of run UUIDs

Response: HTTP 200

FieldTypeDescription
runsarrayList of run comparison objects

Run comparison object:

FieldTypeDescription
run_idUUID stringTraining run identifier
statusstringRun status
episodes_completedintegerEpisodes completed
aggregate_statsobject | nullAggregate statistics (avg ROI, Sharpe, etc.)
started_atstring | nullStart timestamp
completed_atstring | nullCompletion 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.


On this page