Strategy Testing
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.
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:
- You define a date range (e.g. all of 2025) and an episode duration (e.g. 30 days)
- The engine draws
Nepisodes from that range — either randomly or sequentially - Each episode is an independent backtest with its own starting balance
- Results are aggregated across all episodes: average ROI, average Sharpe, win rate across episodes
- 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 -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"
}'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:
{
"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 https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/tests \
-H "X-API-Key: ak_live_..."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 |
# Poll until completed
curl https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/tests/TEST_ID \
-H "X-API-Key: ak_live_..."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):
{
"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 -X POST https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/tests/TEST_ID/cancel \
-H "X-API-Key: ak_live_..."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 https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/test-results \
-H "X-API-Key: ak_live_..."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 "https://api.tradeready.io/api/v1/strategies/STRATEGY_ID/compare-versions?v1=1&v2=2" \
-H "X-API-Key: ak_live_..."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:
{
"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 |
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:
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}'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 — create and manage strategies
- Backtesting — run individual backtests
- Errors — error code reference