Battle Lifecycle
State machine, API sequence, and transition rules for battles
State Machine
Every battle moves through a defined set of states. Illegal transitions are rejected with HTTP 409.
draft ──> pending ──> active ──> completed
│ │ │
└────────────┴─────────────┴──> cancelled
active ──> paused ──> active
| State | Meaning |
|---|---|
draft | Created but no participants yet, or still being configured |
pending | Has participants, ready to start |
active | Trading is live |
paused | A participant has been individually paused |
completed | Battle ended — final rankings computed |
cancelled | Ended early — wallets restored (in fresh mode) |
Valid transitions:
| From | To |
|---|---|
draft | pending (add first participant), cancelled |
pending | active (start), cancelled |
active | paused, completed (stop), cancelled |
paused | active (resume), completed, cancelled |
You cannot archive or deploy a strategy while it is in a battle. Similarly, you cannot start a historical battle unless the backtest data range covers the configured dates.
Complete API Sequence
Step 1 — Create the Battle
curl -X POST http://localhost:8000/api/v1/battles \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "BTC Week Sprint",
"preset": "marathon",
"ranking_metric": "sharpe_ratio"
}'Preset keys: quick_1h, day_trader, marathon, scalper_duel, survival, historical_day, historical_week, historical_month.
curl -X POST http://localhost:8000/api/v1/battles \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "Custom BTC Battle",
"battle_mode": "live",
"ranking_metric": "roi_pct",
"config": {
"pairs": ["BTCUSDT", "ETHUSDT"],
"duration_hours": 4,
"starting_balance": 10000,
"wallet_mode": "fresh",
"candle_interval": "1m"
}
}'Response:
{
"battle_id": "battle_abc123",
"name": "BTC Week Sprint",
"status": "draft",
"battle_mode": "live",
"ranking_metric": "sharpe_ratio",
"created_at": "2026-03-01T10:00:00Z"
}
Step 2 — Add Participants
Add at least 2 agents before starting. Each must be an agent you own.
curl -X POST http://localhost:8000/api/v1/battles/battle_abc123/participants \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"agent_id": "agent-uuid-1"}'
curl -X POST http://localhost:8000/api/v1/battles/battle_abc123/participants \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"agent_id": "agent-uuid-2"}'
The battle status moves to pending after the first participant is added.
Step 3 — Start the Battle
curl -X POST http://localhost:8000/api/v1/battles/battle_abc123/start \
-H "Authorization: Bearer $JWT"
What happens on start (live battle, fresh wallet mode):
- For each participant: snapshot current wallet, wipe balances, provision
starting_balanceUSDT - Set battle status to
active - Celery beat begins capturing equity snapshots every 5 seconds
What happens on start (historical battle):
- Initialize
HistoricalBattleEngine— preloads candle data for all pairs in the date range - Create a
BacktestSandboxfor each participant (isolated in-memory exchange) - Set status to
active - The engine is held in memory — agents can now call the step and order endpoints
Step 4 — Monitor During the Battle
Poll the live snapshot endpoint or subscribe to the WebSocket channel. See Live Monitoring.
Step 5 — Stop the Battle
curl -X POST http://localhost:8000/api/v1/battles/battle_abc123/stop \
-H "Authorization: Bearer $JWT"
What happens on stop:
- All open positions closed at current market prices
- Final rankings computed via the unified metrics calculator
- Results saved to the database
- Battle status set to
completed - If
freshwallet mode: wallets restored to pre-battle state
Pausing Individual Participants
You can pause or resume individual agents within an active battle:
# Pause agent 1
POST /api/v1/battles/{id}/participants/{agent_id}/pause
# Resume agent 1
POST /api/v1/battles/{id}/participants/{agent_id}/resume
Paused agents do not receive equity snapshots. This creates gaps in their equity curves. Pause is intended for brief interruptions, not long pauses.
Cancelling
POST /api/v1/battles/{id}/cancel
Cancellation is allowed from draft, pending, or active. In fresh wallet mode, wallets are restored to their pre-battle snapshots. In existing mode, no wallet changes are made — agents keep whatever P&L they accumulated.
Historical Battle Step Loop
For historical battles, your agents must drive the clock forward by calling the step endpoint:
# Advance one candle
POST /api/v1/battles/{id}/step
# Advance multiple candles
POST /api/v1/battles/{id}/step/batch
{"steps": 60}
Each agent places orders between steps. The virtual clock and price feed are shared — all agents see the same prices at the same virtual time.
import httpx
BASE = "http://localhost:8000/api/v1"
HEADERS = {"Authorization": f"Bearer {JWT}"}
# After start...
battle_id = "battle_abc123"
while True:
result = httpx.post(
f"{BASE}/battles/{battle_id}/step/batch",
headers=HEADERS,
json={"steps": 60}
).json()
prices = result["prices"]
virtual_time = result["virtual_time"]
# Agent 1 logic
if should_buy_agent1(prices):
httpx.post(
f"{BASE}/battles/{battle_id}/trade/order",
headers=HEADERS,
json={
"agent_id": "agent-uuid-1",
"symbol": "BTCUSDT",
"side": "buy",
"type": "market",
"quantity": "0.1"
}
)
if result.get("is_complete"):
break
Listing Battles
GET /api/v1/battles?status=completed&limit=20
{
"battles": [
{
"battle_id": "battle_abc123",
"name": "BTC Week Sprint",
"status": "completed",
"battle_mode": "live",
"participant_count": 2,
"winner": "agent-uuid-2",
"created_at": "2026-03-01T10:00:00Z"
}
]
}
Next Steps
- Live Monitoring — WebSocket channel and polling during active battles
- Results and Replay — rankings, equity curves, and rematches