TradAI API Reference¶
Complete API reference for TradAI services. For interactive documentation, visit the Swagger UI at each service's /docs endpoint.
Services Overview¶
| Service | Port | Description |
|---|---|---|
| Backend | 8000 | Central gateway for backtest orchestration, strategy management |
| Strategy Service | 8003 | Strategy configuration, MLflow integration, hyperopt, A/B testing |
| Data Collection | 8002 | Market data fetching and storage |
Interactive API Documentation¶
Each service exposes Swagger UI for interactive exploration:
| Service | Swagger UI | ReDoc |
|---|---|---|
| Backend API | http://localhost:8000/docs | http://localhost:8000/redoc |
| Data Collection | http://localhost:8002/docs | http://localhost:8002/redoc |
| Strategy Service | http://localhost:8003/docs | http://localhost:8003/redoc |
Start services first
Run just up to start all services, then open the Swagger URLs in your browser.
Authentication¶
All services support optional JWT authentication via AWS Cognito.
# Include token in Authorization header
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/v1/backtests
Configuration¶
| Variable | Description |
|---|---|
{SERVICE}_COGNITO_USER_POOL_ID | Cognito User Pool ID |
{SERVICE}_COGNITO_CLIENT_ID | Cognito App Client ID |
{SERVICE}_COGNITO_REGION | AWS region (default: eu-central-1) |
Authentication is optional in development but enforced in production.
Response Headers¶
All responses include: - X-Correlation-ID: Unique request ID for tracing/debugging
Error Response Format¶
Validation Error (400)¶
{
"detail": [
{
"type": "missing",
"loc": ["body", "config", "strategy"],
"msg": "Field required",
"input": null
}
]
}
Not Found (404)¶
Service Unavailable (503)¶
Error Code Catalog¶
Standardized error codes for programmatic error handling.
Validation Errors (VAL)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| VAL001 | 400 | Invalid date range | Ensure start < end and dates are ISO 8601 format |
| VAL002 | 400 | Invalid symbol format | Use BASE/QUOTE:SETTLE format (e.g., BTC/USDT:USDT) |
| VAL003 | 400 | Invalid timeframe | Use valid timeframe: 1m, 5m, 15m, 1h, 4h, 1d |
| VAL004 | 400 | Missing required field | Check request body for required fields |
| VAL005 | 400 | Value out of range | Check min/max constraints in field documentation |
| VAL006 | 400 | Invalid config format | Validate JSON structure matches expected schema |
Authentication Errors (AUTH)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| AUTH001 | 401 | Token expired | Refresh JWT token via Cognito |
| AUTH002 | 401 | Invalid token | Verify token signature and issuer |
| AUTH003 | 401 | Missing token | Include Authorization: Bearer <token> header |
| AUTH004 | 403 | Insufficient permissions | Request elevated permissions or use different credentials |
| AUTH005 | 403 | Resource access denied | Verify resource ownership or sharing permissions |
Resource Errors (RES)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| RES001 | 404 | Backtest job not found | Verify job_id is correct and job exists |
| RES002 | 404 | Strategy not found | Check strategy name in /api/v1/strategies endpoint |
| RES003 | 404 | Model version not found | List versions via /api/v1/models/{name}/versions |
| RES004 | 404 | Config not found | Check available configs via /api/v1/strategies |
| RES005 | 409 | Resource conflict | Resource is in a conflicting state (e.g., already terminal) |
| RES006 | 410 | Resource expired | Resource has been archived or deleted |
Service Errors (SVC)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| SVC001 | 500 | Internal server error | Check logs, report if persistent |
| SVC002 | 502 | Bad gateway | Upstream service returned invalid response |
| SVC003 | 503 | Strategy service unavailable | Wait and retry, check service health |
| SVC004 | 503 | Data collection unavailable | Wait and retry, check service health |
| SVC005 | 503 | MLflow unavailable | Wait and retry, check MLflow health |
| SVC006 | 504 | Gateway timeout | Increase timeout or retry later |
Data Errors (DATA)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| DATA001 | 400 | Insufficient historical data | Extend date range or use different symbol |
| DATA002 | 400 | Data gap detected | Use /api/v1/data/freshness to check data availability |
| DATA003 | 404 | Symbol not available | Check /api/v1/data/symbols for available symbols |
| DATA004 | 503 | Exchange API error | Retry later, check exchange status |
| DATA005 | 503 | Data storage unavailable | Check ArcticDB health |
Example Error Response¶
{
"detail": {
"code": "VAL001",
"message": "Invalid date range: end_date must be >= start_date",
"field": "end_date",
"context": {
"start_date": "2024-03-01",
"end_date": "2024-01-01"
}
}
}
Error Handling Best Practices¶
import type { ErrorResponse } from './types/backend';
async function submitBacktest(config: BacktestConfig) {
const { data, error } = await client.POST('/api/v1/backtests', { body: config });
if (error) {
const errorCode = error.detail?.code;
switch (errorCode) {
case 'VAL001':
throw new Error('Please select a valid date range');
case 'AUTH001':
await refreshToken();
return submitBacktest(config); // Retry
case 'SVC003':
case 'SVC004':
await delay(5000);
return submitBacktest(config); // Retry with backoff
default:
throw new Error(`API Error: ${error.detail?.message || 'Unknown error'}`);
}
}
return data;
}
API Workflows¶
Backtest Lifecycle¶
stateDiagram-v2
[*] --> PENDING: POST /backtests
PENDING --> RUNNING: Executor picks up
RUNNING --> COMPLETED: Success
RUNNING --> FAILED: Error
RUNNING --> CANCELLED: Cancel request
PENDING --> CANCELLED: Cancel before start Step-by-Step Flow:
- Submit -
POST /api/v1/backtestscreates job, returnsjob_id - Monitor - Poll
GET /api/v1/backtests/{job_id}until terminal state - Results - On completion,
GET /api/v1/backtests/{job_id}/equityfor charts - Report -
GET /api/v1/backtests/{job_id}/report-datafor full analysis
Strategy Promotion Pipeline¶
flowchart LR
A[Backtest Validation] --> B[Register to MLflow]
B --> C["Stage (Dry-Run)"]
C --> D["A/B Test (Optional)"]
D --> E[Promote to Production]
E --> F["Deploy (Production)"] API Sequence:
# 1. Run backtest
POST /api/v1/backtests → job_id
# 2. Wait for completion
GET /api/v1/backtests/{job_id} → status: "completed"
# 3. Register to MLflow
POST /api/v1/strategies/{name}/register
--backtest_run_id <mlflow_run_id>
--docker_image_uri <ecr_uri>
# 4. Stage for validation
POST /api/v1/strategies/{name}/stage --version 3
# 5. (Optional) A/B test
POST /api/v1/models/{name}/challenges
--champion_version 2
--challenger_version 3
# 6. Promote to production
POST /api/v1/strategies/{name}/promote --version 3
Hyperopt Workflow¶
flowchart LR
A["Start Hyperopt\nPOST /hyperopt"] --> B["Monitor Trials\nGET /hyperopt/{id}"]
B --> C["Get Results\nGET /hyperopt/{id}/result"]
B --> D["Live Updates\n(WebSocket)"]
C --> E["Best Params\nApply Config"] Usage Example:
// 1. Start optimization
const { data: job } = await client.POST('/api/v1/hyperopt', {
body: {
strategy: 'TrendFollowingStrategy',
n_trials: 100,
objective_metric: 'sharpe_ratio'
}
});
// 2. Poll for progress
let status = 'running';
while (status === 'running') {
const { data } = await client.GET('/api/v1/hyperopt/{job_id}', {
params: { path: { job_id: job.job_id } }
});
status = data.status;
console.log(`Progress: ${data.current_trial}/${data.total_trials}`);
await delay(5000);
}
// 3. Get best parameters
const { data: result } = await client.GET('/api/v1/hyperopt/{job_id}/result', {
params: { path: { job_id: job.job_id } }
});
console.log('Best params:', result.best_params);
Champion/Challenger A/B Test¶
flowchart TD
A["Create Challenge\nPOST /challenges"] --> B["Route Traffic\nCanary: 5/95"]
B --> C["Log Trades\nPOST /trades"]
C --> D["Stage 1: 5%"]
D --> E["Stage 2: 10%"]
E --> F["Stage 3: 25%"]
F --> G["Stage 4: 50%"]
G --> H{Evaluate}
H --> I["PROMOTE\nChallenger Wins"]
H --> J["ROLLBACK\nKeep Champion"] Evaluation Criteria:
| Metric | Threshold | Description |
|---|---|---|
| p_value | < 0.05 | Statistical significance |
| improvement_pct | > 10% | Meaningful improvement |
| min_trades | >= 100 | Sufficient sample size |
Backend Service (Port 8000)¶
Base URL: http://localhost:8000/api/v1
Health Check¶
GET /health¶
Check service health status with dependency checks.
Response 200:
{
"status": "healthy",
"service": "backend",
"version": "0.1.0",
"dependencies": {
"strategy_service": "healthy",
"data_collection": "healthy",
"mlflow": "healthy"
}
}
Backtest Management¶
POST /api/v1/backtests¶
Submit a new backtest job.
Request:
{
"config": {
"strategy": "TrendFollowingStrategy",
"symbols": ["BTC/USDT:USDT", "ETH/USDT:USDT"],
"timeframe": "1h",
"start_date": "2024-01-01",
"end_date": "2024-03-01",
"stake_amount": 100.0
},
"experiment_name": "trend-following-btc-eth-q1-2024",
"priority": 5
}
Response 201:
Errors: 400 (validation), 503 (service unavailable)
GET /api/v1/backtests¶
List backtest jobs with pagination.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | limit | int | 20 | Max results per page (1-100) | | cursor | string | null | Pagination cursor | | status | string | null | Filter by status |
Response 200:
{
"items": [
{
"job_id": "bt-abc123def456",
"status": "completed",
"result": { ... },
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:00Z"
}
],
"next_cursor": "eyJsYXN0X2lkIjogImJ0LXh5eiJ9"
}
GET /api/v1/backtests/{job_id}¶
Get status of a specific backtest job.
Response 200:
{
"job_id": "bt-abc123def456",
"status": "completed",
"result": {
"total_trades": 150,
"winning_trades": 95,
"losing_trades": 55,
"total_profit": 2500.50,
"total_profit_pct": 25.05,
"sharpe_ratio": 1.85,
"profit_factor": 1.72,
"win_rate": 63.33,
"max_drawdown_pct": 12.5
},
"error": null,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:00Z"
}
Errors: 400 (invalid job_id), 404 (not found)
POST /api/v1/backtests/{job_id}/cancel¶
Cancel a running backtest job.
Response 200:
Errors: 400 (already terminal), 404 (not found)
GET /api/v1/backtests/{job_id}/equity¶
Get equity curve data for visualization.
Response 200:
{
"dates": ["2024-01-01", "2024-01-02", ...],
"equity": [10000.0, 10150.0, ...],
"drawdown": [0.0, 0.0, -1.2, ...],
"monthly_returns": {
"2024-01": 5.2,
"2024-02": 8.1
}
}
Errors: 400 (not completed), 404 (not found)
GET /api/v1/backtests/{job_id}/report-data¶
Get complete report data for HTML report generation.
Response 200:
Strategy Management¶
GET /api/v1/strategies¶
List available strategy configurations.
Response 200:
GET /api/v1/strategies/{config_name}¶
Get a specific strategy configuration.
Response 200:
{
"strategy": "TrendFollowingStrategy",
"pairs": ["BTC/USDT:USDT"],
"timeframe": "1h",
"stake_amount": 100.0,
"freqai": {
"enabled": true,
"model_type": "LightGBMClassifier"
}
}
POST /api/v1/strategies/{name}/stage¶
Stage a strategy model version for validation testing.
Request:
Response 200:
{
"success": true,
"message": "Version 3 staged successfully",
"previous_stage": "None",
"new_stage": "Staging"
}
POST /api/v1/strategies/{name}/promote¶
Promote a strategy model version to Production.
Request:
Response 200:
{
"success": true,
"message": "Version 3 promoted to Production",
"archived_versions": ["2"],
"previous_stage": "Staging",
"new_stage": "Production"
}
Model Operations¶
GET /api/v1/models/{model_name}/versions¶
List all versions of a registered model.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | include_archived | bool | true | Include archived versions |
Response 200:
{
"model_name": "TrendFollowingStrategy",
"versions": [
{
"version": "3",
"stage": "Production",
"created_at": "2024-01-15T10:30:00Z"
},
{
"version": "2",
"stage": "Archived",
"created_at": "2024-01-10T08:00:00Z"
}
],
"count": 2
}
POST /api/v1/models/{model_name}/rollback¶
Rollback a model to a previous version.
Request:
Response 200:
{
"success": true,
"message": "Rolled back to version 2",
"previous_production": "3",
"new_production": "2"
}
Strategy Catalog¶
GET /api/v1/catalog/strategies¶
List strategies from the catalog with filtering.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | stage | string | Filter by stage (Production, Staging) | | exchange | string | Filter by exchange | | timeframe | string | Filter by timeframe | | search | string | Text search | | min_sharpe | float | Minimum Sharpe ratio | | max_drawdown | float | Maximum drawdown % | | sort_field | string | Sort by (name, sharpe, drawdown) | | sort_order | string | asc or desc | | limit | int | Page size (1-100) | | offset | int | Page offset |
GET /api/v1/catalog/strategies/{name}/compare¶
Compare two versions of a strategy.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | version_a | string | First version (champion) | | version_b | string | Second version (challenger) |
Response 200:
{
"decision": "PROMOTE",
"confidence": 0.85,
"metrics": {
"sharpe_ratio": {"a": 1.5, "b": 1.8, "improvement": 20.0},
"profit_factor": {"a": 1.6, "b": 1.9, "improvement": 18.75}
},
"recommendation": "Challenger outperforms champion with high confidence"
}
Strategy Operations¶
POST /api/v1/strategies/{strategy_id}/run¶
Launch a strategy on ECS.
Request:
Response 201:
{
"deployment_id": "dep-abc123",
"instance_id": "task-xyz789",
"strategy_id": "pascal-v1",
"mode": "dry_run",
"status": "running"
}
Errors: 400 (invalid parameters), 503 (ECS not configured)
GET /api/v1/strategies/{strategy_id}/instances¶
List running instances for a strategy.
Response 200:
{
"strategy_id": "pascal-v1",
"instances": [
{
"instance_id": "task-xyz789",
"status": "running",
"mode": "dry_run",
"started_at": "2024-01-15T10:30:00Z"
}
],
"total": 1
}
POST /api/v1/strategies/{strategy_id}/instances/{instance_id}/stop¶
Stop a running strategy instance.
Response 200:
{
"instance_id": "task-xyz789",
"strategy_id": "pascal-v1",
"success": true,
"message": "Instance stopped"
}
Errors: 404 (instance not found), 503 (stop failed)
GET /api/v1/strategies/{strategy_id}/instances/{instance_id}/logs¶
Get logs for a strategy instance from CloudWatch.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | lines | int | 100 | Number of log lines (1-1000) |
Response 200:
{
"strategy_id": "pascal-v1",
"instance_id": "task-xyz789",
"entries": [
{"timestamp": "2024-01-15T10:30:00Z", "message": "Strategy started"}
],
"total": 1
}
Errors: 404 (log stream not found), 503 (CloudWatch query failed)
POST /api/v1/strategies/{strategy_id}/instances/{instance_id}/pause¶
Pause a running strategy instance. Updates trading state to PAUSED in DynamoDB. The trading container detects the change within 30s and pauses the Freqtrade engine (no new trades, existing positions managed gracefully).
Response 200:
Errors: 404 (instance not found), 503 (ECS/Freqtrade command failed)
POST /api/v1/strategies/{strategy_id}/instances/{instance_id}/resume¶
Resume a paused strategy instance. Updates trading state to RUNNING in DynamoDB. The trading container detects the change within 30s and resumes the Freqtrade engine.
Response 200:
{
"success": true,
"message": "Instance resumed",
"instance_id": "task-xyz789",
"status": "running"
}
Errors: 404 (instance not found), 503 (ECS/Freqtrade command failed)
GET /api/v1/trading/status¶
Get status of all active trading instances with summary statistics.
Response 200:
{
"instances": [
{
"strategy_id": "pascal-v1",
"instance_id": "task-xyz789",
"status": "running",
"mode": "dry_run",
"started_at": "2024-01-15T10:30:00Z",
"uptime_seconds": 3600
}
],
"summary": {
"total_running": 2,
"total_paused": 0
}
}
Errors: 503 (ECS query failed)
GET /api/v1/strategies/{name}/routing-info¶
Get current A/B test routing configuration for a strategy.
Response 200:
{
"active_test": true,
"champion_version": "2",
"challenger_version": "3",
"traffic_split": [0.95, 0.05],
"canary_stage": "stage_1"
}
Errors: 404 (strategy not found), 500 (routing info retrieval failed)
GET /api/v1/strategies/pnl¶
Get P&L metrics for strategies from the most recent heartbeat cycle.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | strategy_id | string | null | Filter by strategy ID |
Response 200:
{
"strategies": [
{
"strategy_id": "pascal-v1",
"total_profit_pct": 12.5,
"open_trades": 3
}
],
"portfolio_summary": {
"total_profit_pct": 12.5
}
}
Errors: 503 (service unavailable)
GET /api/v1/strategies/{strategy_id}/trades¶
Get open trades for a strategy from the most recent heartbeat cycle.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | limit | int | 20 | Max trades to return (1-100) |
Response 200:
{
"trades": [
{
"pair": "BTC/USDT:USDT",
"profit_pct": 2.5,
"open_date": "2024-01-15T10:30:00Z"
}
],
"total": 1
}
Errors: 404 (strategy not found), 503 (service unavailable)
WebSocket Ticket¶
POST /api/v1/ws/ticket¶
Exchange a valid JWT for a single-use WebSocket ticket. When auth is disabled (dev mode), generates an anonymous ticket so WebSocket flows still work.
Response 200:
The ticket is valid for 30 seconds and can only be used once. Pass it as a query parameter when connecting to the WebSocket:
Data Proxy¶
GET /api/v1/data/symbols¶
Get available trading symbols from exchange.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | exchange | string | binance_futures | Exchange key |
Response 200:
GET /api/v1/data/freshness¶
Check data freshness for symbols.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | symbols | list | required | Symbols to check | | stale_threshold_hours | int | 24 | Stale threshold |
Strategy Service (Port 8003)¶
Base URL: http://localhost:8003/api/v1
Hyperparameter Optimization¶
POST /hyperopt¶
Start Optuna hyperparameter optimization.
Request:
{
"strategy": "TrendFollowingStrategy",
"pairs": ["BTC/USDT:USDT"],
"timeframe": "1h",
"n_trials": 100,
"train_period_days": 180,
"backtest_period_days": 30,
"objective_metric": "sharpe_ratio",
"direction": "maximize",
"model_types": ["LightGBMClassifier", "XGBClassifier"],
"n_estimators_range": [100, 1000],
"learning_rate_range": [0.01, 0.3]
}
Response 200:
{
"job_id": "abc12345",
"status": "pending",
"message": "Hyperopt job started with 100 trials",
"n_trials": 100,
"strategy": "TrendFollowingStrategy"
}
GET /hyperopt/{job_id}¶
Get hyperopt job status.
Response 200:
{
"job_id": "abc12345",
"status": "running",
"started_at": "2024-01-15T10:30:00Z",
"progress": 45,
"current_trial": 45,
"total_trials": 100,
"best_value": 1.85
}
GET /hyperopt/{job_id}/result¶
Get hyperopt results when complete.
Response 200:
{
"best_params": {
"model_type": "LightGBMClassifier",
"n_estimators": 500,
"learning_rate": 0.05
},
"best_value": 2.1,
"n_trials_completed": 100,
"study_name": "abc12345"
}
Champion/Challenger A/B Testing¶
POST /api/v1/models/{model_name}/challenges¶
Create a champion/challenger A/B test.
Request:
{
"champion_version": "2",
"challenger_version": "3",
"min_trades": 100,
"max_duration_hours": 168,
"enable_canary": true
}
Response 201:
{
"challenge_id": "ch-abc123",
"model_name": "TrendFollowingStrategy",
"champion_version": "2",
"challenger_version": "3",
"status": "active",
"canary_stage": "stage_1",
"traffic_split": [0.95, 0.05]
}
GET /api/v1/models/{model_name}/challenges/active¶
Get active challenge for a model.
Response 200:
{
"challenge_id": "ch-abc123",
"model_name": "TrendFollowingStrategy",
"champion_version": "2",
"challenger_version": "3",
"status": "active",
"canary_stage": "stage_1",
"traffic_split": [0.95, 0.05]
}
Returns null if no active challenge exists.
GET /api/v1/challenges/{challenge_id}¶
Get challenge by ID.
Response 200:
{
"challenge_id": "ch-abc123",
"model_name": "TrendFollowingStrategy",
"champion_version": "2",
"challenger_version": "3",
"status": "active",
"canary_stage": "stage_1",
"traffic_split": [0.95, 0.05]
}
Errors: 404 (challenge not found)
POST /api/v1/challenges/{challenge_id}/trades¶
Add a trade result to a challenge.
Request:
Response 200: Updated ChallengeResponse with current state.
Errors: 400 (invalid variant), 404 (challenge not found)
GET /api/v1/challenges/{challenge_id}/evaluate¶
Evaluate challenge and get recommendation.
Response 200:
{
"challenge_id": "ch-abc123",
"status": "completed",
"recommendation": "PROMOTE_CHALLENGER",
"champion_mean": 1.2,
"challenger_mean": 1.8,
"improvement_pct": 50.0,
"p_value": 0.02,
"is_significant": true
}
Errors: 404 (challenge not found)
POST /api/v1/challenges/{challenge_id}/cancel¶
Cancel an ongoing challenge.
Response 200:
Errors: 404 (challenge not found)
Shadow Tests¶
POST /api/v1/models/{model_name}/shadow-tests¶
Create a shadow test for a model. Runs the candidate version in parallel with production, comparing trade signals without actual execution.
Request:
{
"production_version": "2",
"candidate_version": "3",
"duration_days": 30,
"metrics_to_compare": ["profit_pct", "win_rate"]
}
Response 201:
{
"test_id": "st-abc123",
"model_name": "TrendFollowingStrategy",
"production_version": "2",
"candidate_version": "3",
"status": "active",
"duration_days": 30
}
Errors: 400 (invalid parameters or active test exists), 404 (model version not found), 503 (MLflow unavailable)
GET /api/v1/models/{model_name}/shadow-tests/active¶
Get active shadow test for a model.
Response 200:
{
"test_id": "st-abc123",
"model_name": "TrendFollowingStrategy",
"production_version": "2",
"candidate_version": "3",
"status": "active",
"duration_days": 30
}
Returns null if no active shadow test exists.
GET /api/v1/shadow-tests/{test_id}¶
Get shadow test by ID.
Response 200:
{
"test_id": "st-abc123",
"model_name": "TrendFollowingStrategy",
"production_version": "2",
"candidate_version": "3",
"status": "active",
"duration_days": 30
}
Errors: 404 (shadow test not found)
POST /api/v1/shadow-tests/{test_id}/trades¶
Add a trade result to a shadow test.
Request:
Response 200: Updated ShadowTestResponse with current state.
Errors: 400 (invalid variant), 404 (shadow test not found)
GET /api/v1/shadow-tests/{test_id}/metrics¶
Get metrics for a shadow test including trade counts, mean profit, and win rates for both variants.
Response 200:
{
"test_id": "st-abc123",
"production_trades": 85,
"candidate_trades": 85,
"production_mean": 1.2,
"candidate_mean": 1.6,
"production_win_rate": 58.0,
"candidate_win_rate": 62.0,
"improvement_pct": 33.3,
"evaluated_at": "2024-02-15T10:30:00Z"
}
Errors: 404 (shadow test not found)
Strategy Registration¶
POST /api/v1/strategies/{name}/register¶
Register a strategy version after successful backtest.
Request:
{
"backtest_run_id": "run-abc123",
"docker_image_uri": "123456789.dkr.ecr.eu-central-1.amazonaws.com/strategy:v1.0.0",
"description": "Improved trend following with ML enhancements",
"skip_validation": false,
"thresholds": {
"min_sharpe": 1.0,
"min_profit_factor": 1.2,
"max_drawdown": 20.0
}
}
Response 201:
{
"success": true,
"message": "Strategy registered successfully",
"version": "4",
"model_name": "TrendFollowingStrategy"
}
TypeScript Integration¶
Generate Types¶
# Start services first
just up
# Generate TypeScript types from running services
npm run generate-types
Client Setup¶
import createClient from 'openapi-fetch';
import type { paths } from './types/backend';
// Create client with default configuration
const client = createClient<paths>({
baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
headers: {
'Content-Type': 'application/json',
},
});
// Add authentication interceptor
client.use({
async onRequest({ request }) {
const token = await getAuthToken();
if (token) {
request.headers.set('Authorization', `Bearer ${token}`);
}
return request;
},
async onResponse({ response }) {
// Extract correlation ID for debugging
const correlationId = response.headers.get('X-Correlation-ID');
if (correlationId) {
console.debug(`Request ${correlationId}`);
}
return response;
},
});
Basic Usage¶
// Type-safe API calls
const { data, error } = await client.POST('/api/v1/backtests', {
body: {
config: {
strategy: 'TrendFollowingStrategy',
symbols: ['BTC/USDT:USDT'],
timeframe: '1h',
start_date: '2024-01-01',
end_date: '2024-03-01',
stake_amount: 100.0,
},
experiment_name: 'my-backtest',
priority: 5,
},
});
if (error) {
console.error('Failed:', error.detail);
return;
}
console.log('Job ID:', data.job_id);
Error Handling¶
import type { ErrorDetail } from './types/backend';
// Retry configuration
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
async function withRetry<T>(
fn: () => Promise<{ data?: T; error?: { detail: ErrorDetail } }>,
retries = MAX_RETRIES
): Promise<T> {
const { data, error } = await fn();
if (error) {
const code = error.detail?.code;
// Non-retryable errors
if (code?.startsWith('VAL') || code?.startsWith('AUTH')) {
throw new ApiError(error.detail);
}
// Retryable service errors
if (code?.startsWith('SVC') && retries > 0) {
await delay(RETRY_DELAY * (MAX_RETRIES - retries + 1));
return withRetry(fn, retries - 1);
}
throw new ApiError(error.detail);
}
return data!;
}
// Usage
try {
const result = await withRetry(() =>
client.POST('/api/v1/backtests', { body: config })
);
console.log('Created:', result.job_id);
} catch (e) {
if (e instanceof ApiError) {
handleApiError(e);
}
}
Pagination¶
// Generic paginated fetcher
async function* fetchPaginated<T>(
endpoint: string,
params?: Record<string, unknown>
): AsyncGenerator<T, void, unknown> {
let cursor: string | null = null;
do {
const { data, error } = await client.GET(endpoint, {
params: {
query: { ...params, cursor, limit: 50 },
},
});
if (error) throw new ApiError(error.detail);
for (const item of data.items) {
yield item as T;
}
cursor = data.next_cursor;
} while (cursor);
}
// Usage: Iterate all backtests
for await (const backtest of fetchPaginated('/api/v1/backtests')) {
console.log(backtest.job_id, backtest.status);
}
// Or collect all into array
async function getAllBacktests(status?: string) {
const results = [];
for await (const bt of fetchPaginated('/api/v1/backtests', { status })) {
results.push(bt);
}
return results;
}
Polling for Job Completion¶
interface PollOptions {
interval?: number;
timeout?: number;
onProgress?: (status: string) => void;
}
async function pollBacktest(
jobId: string,
options: PollOptions = {}
): Promise<BacktestResult> {
const { interval = 5000, timeout = 600000, onProgress } = options;
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const { data, error } = await client.GET('/api/v1/backtests/{job_id}', {
params: { path: { job_id: jobId } },
});
if (error) throw new ApiError(error.detail);
onProgress?.(data.status);
if (data.status === 'completed') {
return data.result;
}
if (data.status === 'failed') {
throw new Error(`Backtest failed: ${data.error}`);
}
if (data.status === 'cancelled') {
throw new Error('Backtest was cancelled');
}
await delay(interval);
}
throw new Error(`Polling timeout after ${timeout}ms`);
}
// Usage
const result = await pollBacktest(jobId, {
interval: 3000,
timeout: 300000,
onProgress: (status) => console.log(`Status: ${status}`),
});
Complete Example: Submit and Monitor Backtest¶
import createClient from 'openapi-fetch';
import type { paths, components } from './types/backend';
type BacktestConfig = components['schemas']['BacktestConfig'];
type BacktestResult = components['schemas']['BacktestResult'];
const client = createClient<paths>({
baseUrl: 'http://localhost:8000',
});
async function runBacktest(config: BacktestConfig): Promise<BacktestResult> {
// 1. Submit backtest
const { data: job, error: submitError } = await client.POST('/api/v1/backtests', {
body: {
config,
experiment_name: `backtest-${Date.now()}`,
priority: 5,
},
});
if (submitError) {
throw new Error(`Submit failed: ${submitError.detail}`);
}
console.log(`Job submitted: ${job.job_id}`);
// 2. Poll until complete
const result = await pollBacktest(job.job_id, {
onProgress: (status) => console.log(`[${job.job_id}] ${status}`),
});
// 3. Get equity curve
const { data: equity } = await client.GET('/api/v1/backtests/{job_id}/equity', {
params: { path: { job_id: job.job_id } },
});
return { ...result, equity };
}
// Run it
const result = await runBacktest({
strategy: 'TrendFollowingStrategy',
symbols: ['BTC/USDT:USDT', 'ETH/USDT:USDT'],
timeframe: '1h',
start_date: '2024-01-01',
end_date: '2024-06-01',
stake_amount: 100,
});
console.log(`Sharpe: ${result.sharpe_ratio}, Profit: ${result.total_profit_pct}%`);
Rate Limiting¶
Rate limiting is not currently implemented. Consider implementing for production: - 100 requests/minute for authenticated users - 20 requests/minute for unauthenticated users
Versioning¶
API versioning is done via URL path (/api/v1/). Breaking changes will increment the version number.
Support¶
- Documentation:
/docson each service - Health Check:
/api/v1/healthon each service - Issues: https://github.com/tradai-bot/tradai/issues