Skip to content

Backtests

Test trading strategies against historical data to evaluate performance before live trading.

MethodEndpointAuthDescription
POST/backtestsYesCreate backtest
GET/backtestsYesList backtests
GET/backtests/{id}YesGet backtest details
POST/backtests/{id}/runYesExecute backtest
GET/backtests/{id}/tradesYesGet trade history
DELETE/backtests/{id}YesDelete backtest
GET/backtests/comparisonYesCompare strategies

POST /api/v1/backtests
Authorization: Bearer <token>
Content-Type: application/json
{
"strategy_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "AAPL RSI Backtest 2024",
"symbols": ["AAPL"],
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"initial_capital": 100000,
"interval": "1d"
}
FieldTypeRequiredDescription
strategy_idUUIDYesStrategy to backtest
namestringYesDescriptive name
symbolsarrayYesStock symbols to test
start_datedateYesBacktest start date
end_datedateYesBacktest end date
initial_capitalnumberYesStarting capital ($)
intervalstringNoData interval (default: 1d)
IntervalBest For
1mScalping strategies
5m, 15mDay trading
1hIntraday swing
1dSwing/position trading

Execute a pending backtest.

POST /api/v1/backtests/660e8400-e29b-41d4-a716-446655440001/run
Authorization: Bearer <token>
StatusDescription
PENDINGCreated, not yet run
RUNNINGCurrently executing
COMPLETEDSuccessfully finished
FAILEDError during execution

MetricDescriptionGood Value
total_returnAbsolute profit/loss ($)Positive
total_return_pctPercentage return> 0%
win_rateWinning trades / total trades> 50%
profit_factorGross profit / gross loss> 1.5
max_drawdownLargest peak-to-trough decline< 20%
sharpe_ratioRisk-adjusted returns> 1.0
total_tradesNumber of completed tradesStrategy-dependent
avg_trade_durationAverage holding periodStrategy-dependent

Retrieve the complete trade history for a backtest.

GET /api/v1/backtests/660e8400-e29b-41d4-a716-446655440001/trades
Authorization: Bearer <token>
FieldDescription
timestampWhen trade occurred
actionBUY or SELL
symbolStock symbol
priceExecution price
quantityShares traded
position_before/afterPosition size change
cash_before/afterCash balance change
pnlProfit/loss on this trade
signal_reasonWhy trade was triggered

Compare multiple strategies on the same symbol and time period.

GET /api/v1/backtests/comparison?symbol=AAPL&start_date=2024-01-01&end_date=2024-12-31
Authorization: Bearer <token>

Run multiple strategy-symbol combinations efficiently.

POST /api/v1/backtests/batch
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Q4 2024 Strategy Comparison",
"strategy_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
],
"symbols": ["AAPL", "MSFT", "GOOGL"],
"start_date": "2024-10-01",
"end_date": "2024-12-31",
"initial_capital": 100000,
"interval": "1d"
}

This creates 6 backtests (2 strategies × 3 symbols).

GET /api/v1/backtests/batch/{batch_id}/progress
Authorization: Bearer <token>
{
"batch_id": "880e8400-...",
"name": "Q4 2024 Strategy Comparison",
"status": "RUNNING",
"total_backtests": 6,
"completed_backtests": 4,
"failed_backtests": 0,
"progress_pct": 66.7
}
GET /api/v1/backtests/matrix?batch_id={batch_id}
Authorization: Bearer <token>
{
"strategies": ["RSI Mean Reversion", "MACD Crossover"],
"symbols": ["AAPL", "MSFT", "GOOGL"],
"matrix": {
"RSI Mean Reversion": {
"AAPL": { "return_pct": 15.2, "sharpe": 1.42 },
"MSFT": { "return_pct": 12.1, "sharpe": 1.15 },
"GOOGL": { "return_pct": 18.5, "sharpe": 1.65 }
},
"MACD Crossover": {
"AAPL": { "return_pct": 12.8, "sharpe": 1.28 },
"MSFT": { "return_pct": 10.5, "sharpe": 1.05 },
"GOOGL": { "return_pct": 14.2, "sharpe": 1.35 }
}
}
}

const api = axios.create({
baseURL: 'http://localhost:8501/api/v1',
headers: { 'Authorization': `Bearer ${token}` }
});
// Create and run backtest
async function runBacktest(strategyId, symbol, startDate, endDate) {
// Create backtest
const { data: backtest } = await api.post('/backtests', {
strategy_id: strategyId,
name: `${symbol} Backtest`,
symbols: [symbol],
start_date: startDate,
end_date: endDate,
initial_capital: 100000,
interval: '1d'
});
console.log(`Created backtest: ${backtest.id}`);
// Run backtest
const { data: result } = await api.post(`/backtests/${backtest.id}/run`);
// Display results
console.log(`\nResults for ${symbol}:`);
console.log(` Return: ${result.results.total_return_pct.toFixed(2)}%`);
console.log(` Win Rate: ${result.results.win_rate.toFixed(1)}%`);
console.log(` Sharpe: ${result.results.sharpe_ratio.toFixed(2)}`);
console.log(` Max Drawdown: ${result.results.max_drawdown.toFixed(1)}%`);
console.log(` Trades: ${result.results.total_trades}`);
return result;
}
// Analyze trades
async function analyzeTrades(backtestId) {
const { data } = await api.get(`/backtests/${backtestId}/trades`);
const trades = data.trades;
const wins = trades.filter(t => t.pnl > 0);
const losses = trades.filter(t => t.pnl < 0);
console.log(`\nTrade Analysis:`);
console.log(` Total trades: ${trades.length}`);
console.log(` Wins: ${wins.length} (${(wins.length/trades.length*100).toFixed(1)}%)`);
console.log(` Losses: ${losses.length}`);
// Average win/loss
if (wins.length > 0) {
const avgWin = wins.reduce((sum, t) => sum + t.pnl, 0) / wins.length;
console.log(` Avg Win: $${avgWin.toFixed(2)}`);
}
if (losses.length > 0) {
const avgLoss = losses.reduce((sum, t) => sum + t.pnl, 0) / losses.length;
console.log(` Avg Loss: $${avgLoss.toFixed(2)}`);
}
return trades;
}
// Run batch comparison
async function compareStrategies(strategyIds, symbols) {
// Create batch
const { data: batch } = await api.post('/backtests/batch', {
name: 'Strategy Comparison',
strategy_ids: strategyIds,
symbols: symbols,
start_date: '2024-01-01',
end_date: '2024-12-31',
initial_capital: 100000
});
// Run batch
await api.post(`/backtests/batch/${batch.id}/run`);
// Poll for completion
let progress;
do {
await new Promise(r => setTimeout(r, 2000));
const { data } = await api.get(`/backtests/batch/${batch.id}/progress`);
progress = data;
console.log(`Progress: ${progress.progress_pct.toFixed(0)}%`);
} while (progress.status === 'RUNNING');
// Get matrix
const { data: matrix } = await api.get(`/backtests/matrix?batch_id=${batch.id}`);
return matrix;
}
import requests
import time
from typing import List, Dict
class BacktestRunner:
def __init__(self, base_url: str, token: str):
self.base_url = base_url
self.headers = {"Authorization": f"Bearer {token}"}
def create_and_run(
self,
strategy_id: str,
symbol: str,
start_date: str,
end_date: str,
initial_capital: float = 100000
) -> Dict:
"""Create and execute a backtest."""
# Create
response = requests.post(
f"{self.base_url}/backtests",
headers=self.headers,
json={
"strategy_id": strategy_id,
"name": f"{symbol} Backtest",
"symbols": [symbol],
"start_date": start_date,
"end_date": end_date,
"initial_capital": initial_capital,
"interval": "1d"
}
)
response.raise_for_status()
backtest = response.json()
# Run
response = requests.post(
f"{self.base_url}/backtests/{backtest['id']}/run",
headers=self.headers
)
response.raise_for_status()
return response.json()
def run_batch(
self,
strategy_ids: List[str],
symbols: List[str],
start_date: str,
end_date: str
) -> Dict:
"""Run batch backtest and wait for completion."""
# Create batch
response = requests.post(
f"{self.base_url}/backtests/batch",
headers=self.headers,
json={
"name": "Batch Comparison",
"strategy_ids": strategy_ids,
"symbols": symbols,
"start_date": start_date,
"end_date": end_date,
"initial_capital": 100000
}
)
response.raise_for_status()
batch = response.json()
# Run
requests.post(
f"{self.base_url}/backtests/batch/{batch['id']}/run",
headers=self.headers
)
# Wait for completion
while True:
response = requests.get(
f"{self.base_url}/backtests/batch/{batch['id']}/progress",
headers=self.headers
)
progress = response.json()
print(f"Progress: {progress['progress_pct']:.0f}%")
if progress['status'] != 'RUNNING':
break
time.sleep(2)
# Get matrix
response = requests.get(
f"{self.base_url}/backtests/matrix",
headers=self.headers,
params={"batch_id": batch['id']}
)
return response.json()
# Usage
runner = BacktestRunner("http://localhost:8501/api/v1", token)
# Single backtest
result = runner.create_and_run(
strategy_id="550e8400-...",
symbol="AAPL",
start_date="2024-01-01",
end_date="2024-12-31"
)
print(f"Return: {result['results']['total_return_pct']:.2f}%")
print(f"Sharpe: {result['results']['sharpe_ratio']:.2f}")
# Batch comparison
matrix = runner.run_batch(
strategy_ids=["strategy1", "strategy2"],
symbols=["AAPL", "MSFT", "GOOGL"],
start_date="2024-01-01",
end_date="2024-12-31"
)
# Find best strategy-symbol combo
best = None
best_return = float('-inf')
for strategy, symbols in matrix['matrix'].items():
for symbol, metrics in symbols.items():
if metrics['return_pct'] > best_return:
best_return = metrics['return_pct']
best = (strategy, symbol)
print(f"Best: {best[0]} on {best[1]} ({best_return:.1f}%)")

Sufficient Data

Use at least 1 year of data for meaningful results. More for stable strategies.

Walk-Forward Testing

Use optimization with walk-forward analysis to avoid overfitting.

Multiple Symbols

Test on multiple symbols to ensure strategy robustness.

Compare Metrics

Don’t focus only on returns. Consider Sharpe ratio and max drawdown.