TradeReady.io
REST API

Strategy Testing

Run multi-episode backtests against your strategy definitions and compare versions to find the best configuration.

Download .md

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:

  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

MethodPathDescription
POST/strategies/{strategy_id}/testStart a new test run
GET/strategies/{strategy_id}/testsList 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}/cancelCancel a running test
GET/strategies/{strategy_id}/test-resultsGet the latest completed results
GET/strategies/{strategy_id}/compare-versionsCompare 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:

ParameterTypeDescription
strategy_idUUIDStrategy identifier

Request body:

FieldTypeRequiredDefaultDescription
versioninteger (≥ 1)YesStrategy version to test
episodesinteger (1–100)No10Number of independent episodes to run
date_range.startISO-8601 datetimeYesStart of the candidate date pool
date_range.endISO-8601 datetimeYesEnd of the candidate date pool
randomize_datesbooleanNotrueRandomly sample episode start dates within the range
episode_duration_daysinteger (1–365)No30Length of each episode in days
starting_balancestringNo"10000"USDT starting balance per episode

Response: HTTP 201

FieldTypeDescription
test_run_idUUID stringTest run identifier — use to poll for results
statusstring"queued" or "running"
episodes_totalintegerTotal episodes requested
episodes_completedintegerEpisodes completed so far
progress_pctfloatCompletion percentage (0–100)
versionintegerStrategy 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:

ParameterTypeDescription
strategy_idUUIDStrategy identifier
test_idUUIDTest run identifier

Response: HTTP 200

All test run fields plus:

FieldTypeDescription
resultsobject | nullAggregated results (present when status is "completed")
recommendationsstring array | nullImprovement suggestions generated from results
configobject | nullSnapshot of the test configuration used

Results object (when completed):

FieldTypeDescription
avg_roi_pctfloatAverage ROI across all episodes
avg_sharpefloatAverage Sharpe ratio across episodes
avg_max_drawdown_pctfloatAverage maximum drawdown
win_rate_pctfloatPercentage of episodes that ended with positive ROI
total_tradesintegerTotal trades executed across all episodes
episodes_completedintegerNumber 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:

ParameterTypeRequiredDescription
v1integer (≥ 1)YesFirst version number
v2integer (≥ 1)YesSecond version number

Response: HTTP 200

FieldTypeDescription
v1objectMetrics for version 1
v2objectMetrics for version 2
improvementsobjectDelta values (positive = v2 is better): roi_pct, sharpe
verdictstringPlain-English summary of which version performs better

Version metrics object:

FieldTypeDescription
versionintegerVersion number
avg_roi_pctfloat | nullAverage ROI (null if no completed tests)
avg_sharpefloat | nullAverage Sharpe ratio
avg_max_drawdown_pctfloat | nullAverage max drawdown
total_tradesintegerTotal trades across episodes
episodes_completedintegerNumber 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 triggerRecommendation 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 < 0Strategy is net-negative — review entry/exit logic
avg_sharpe < 0.5Returns are not commensurate with risk — review sizing
avg_sharpe > 2.0Very strong risk-adjusted returns — consider deployment
Total trades per episode < 5Entry conditions may be too restrictive
Total trades per episode > 100Over-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 episodeStrong 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)

On this page