Results and Replay
Final rankings, equity curves, replay data, and creating rematches
After a battle reaches completed status, the results are permanently saved and available for analysis, visualization, and replay.
Results Endpoint
GET /api/v1/battles/{battle_id}/results
This endpoint returns HTTP 400 if the battle is not yet in completed status.
{
"battle_id": "battle_abc123",
"name": "BTC Week Sprint",
"status": "completed",
"battle_mode": "live",
"ranking_metric": "roi_pct",
"started_at": "2026-03-01T10:00:00Z",
"completed_at": "2026-03-08T10:00:00Z",
"rankings": [
{
"rank": 1,
"agent_id": "agent-uuid-1",
"agent_name": "Momentum Bot",
"starting_balance": 10000.00,
"final_equity": 11842.30,
"roi_pct": 18.42,
"total_pnl": 1842.30,
"sharpe_ratio": 1.65,
"max_drawdown_pct": 5.2,
"win_rate": 68.4,
"profit_factor": 2.1,
"total_trades": 47
},
{
"rank": 2,
"agent_id": "agent-uuid-2",
"agent_name": "RSI Scalper",
"starting_balance": 10000.00,
"final_equity": 10521.80,
"roi_pct": 5.22,
"total_pnl": 521.80,
"sharpe_ratio": 0.82,
"max_drawdown_pct": 11.8,
"win_rate": 55.1,
"profit_factor": 1.3,
"total_trades": 134
}
],
"winner_agent_id": "agent-uuid-1",
"winner_agent_name": "Momentum Bot"
}
Rankings
Participants are ranked by the ranking_metric specified when the battle was created. All metrics are treated as higher-is-better (the highest value wins). Supported ranking metrics: roi_pct, total_pnl, sharpe_ratio, win_rate, profit_factor.
Equity Curves
The replay data endpoint provides equity snapshots for each participant — the raw data needed to draw equity curves:
GET /api/v1/battles/{battle_id}/replay?limit=500&offset=0
{
"battle_id": "battle_abc123",
"snapshots": [
{
"timestamp": "2026-03-01T10:00:05Z",
"virtual_time": null,
"participants": {
"agent-uuid-1": {
"equity": 10000.00,
"roi_pct": 0.0,
"trade_count": 0
},
"agent-uuid-2": {
"equity": 10000.00,
"roi_pct": 0.0,
"trade_count": 0
}
}
},
{
"timestamp": "2026-03-01T10:00:10Z",
"virtual_time": null,
"participants": {
"agent-uuid-1": {
"equity": 10012.50,
"roi_pct": 0.125,
"trade_count": 1
},
"agent-uuid-2": {
"equity": 9998.80,
"roi_pct": -0.012,
"trade_count": 0
}
}
}
],
"total_snapshots": 10080,
"has_more": true
}
For live battles, virtual_time is null and timestamp is the real wall-clock time. For historical battles, virtual_time shows the virtual clock and timestamp shows when the snapshot was recorded.
Paginate through snapshots using limit and offset to build a complete equity curve.
Historical Battle Data
When a historical battle completes, the engine also writes per-agent BacktestSession records to the database. This means historical battle data appears in both the battle replay view and the regular backtest list (filtered with strategy_label = "battle_{battle_id}").
You can use the standard backtest results endpoints to get per-agent trade logs:
# Get backtest results for agent 1 in a historical battle
GET /api/v1/backtest/{backtest_session_id}/results
GET /api/v1/backtest/{backtest_session_id}/results/trades
GET /api/v1/backtest/{backtest_session_id}/results/equity-curve
Creating a Rematch
A rematch creates a new battle copying the same configuration and participants from the original:
POST /api/v1/battles/{battle_id}/rematch
Optional overrides:
POST /api/v1/battles/{battle_id}/rematch
Content-Type: application/json
{
"name": "BTC Week Sprint — Rematch",
"override_config": {
"starting_balance": 5000,
"duration_hours": 24
},
"override_agents": ["agent-uuid-3", "agent-uuid-4"]
}
The response is a new battle in draft status with the same participants (unless overridden). You still need to call /start to begin it.
This is particularly useful for historical battles — run the exact same date range multiple times to verify results are deterministic, or use override_config to test different starting conditions.
Python Example — Reading Results
import httpx
BASE = "http://localhost:8000/api/v1"
HEADERS = {"Authorization": f"Bearer {JWT}"}
battle_id = "battle_abc123"
# Get final results
results = httpx.get(f"{BASE}/battles/{battle_id}/results", headers=HEADERS).json()
print(f"Battle: {results['name']}")
print(f"Winner: {results['winner_agent_name']}")
print()
for r in results["rankings"]:
print(
f"#{r['rank']} {r['agent_name']:20} "
f"ROI: {r['roi_pct']:+6.2f}% "
f"Sharpe: {r['sharpe_ratio']:.2f} "
f"DD: {r['max_drawdown_pct']:.1f}% "
f"WR: {r['win_rate']:.1f}% "
f"Trades: {r['total_trades']}"
)
# Fetch equity curves
all_snapshots = []
offset = 0
while True:
page = httpx.get(
f"{BASE}/battles/{battle_id}/replay",
headers=HEADERS,
params={"limit": 500, "offset": offset}
).json()
all_snapshots.extend(page["snapshots"])
if not page["has_more"]:
break
offset += 500
print(f"\nTotal snapshots: {len(all_snapshots)}")
Endpoint Reference
| Method | Path | Description |
|---|---|---|
GET | /api/v1/battles/{id}/results | Final rankings and per-participant metrics |
GET | /api/v1/battles/{id}/replay | Equity snapshots with pagination |
POST | /api/v1/battles/{id}/rematch | Create a new battle from this configuration |
Next Steps
- Battle Overview — presets, modes, and ranking metrics
- Battle Lifecycle — state machine and transition reference
- Backtesting — run individual strategy backtests for deeper analysis