Skip to content

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

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: us-east-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)

{
  "detail": "Backtest job 'abc123' not found"
}

Service Unavailable (503)

{
  "detail": "Strategy service unavailable: connection timeout"
}

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 /strategies endpoint
RES003 404 Model version not found List versions via /models/{name}/versions
RES004 404 Config not found Check available configs via /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 /data/freshness to check data availability
DATA003 404 Symbol not available Check /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: start must be before end",
    "field": "date_range",
    "context": {
      "start": "2024-03-01T00:00:00Z",
      "end": "2024-01-01T00:00:00Z"
    }
  }
}

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

                           ┌──────────────────────────────────────┐
                           │          1. Submit Backtest          │
                           │   POST /backtests                    │
                           └────────────────┬─────────────────────┘
                           ┌──────────────────────────────────────┐
                           │         2. Job Queued (SQS)          │
                           │   Status: "pending"                  │
                           └────────────────┬─────────────────────┘
                           ┌──────────────────────────────────────┐
                           │       3. ECS Task Started            │
                           │   Status: "running"                  │
                           └────────────────┬─────────────────────┘
                        ┌───────────────────┼───────────────────┐
                        │                   │                   │
                        ▼                   ▼                   ▼
           ┌────────────────────┐  ┌───────────────┐  ┌────────────────────┐
           │   4a. Completed    │  │ 4b. Failed    │  │  4c. Cancelled     │
           │   Results in S3    │  │ Error logged  │  │  User-initiated    │
           └────────────────────┘  └───────────────┘  └────────────────────┘

Step-by-Step Flow:

  1. Submit - POST /backtests creates job, returns job_id
  2. Monitor - Poll GET /backtests/{job_id} until terminal state
  3. Results - On completion, GET /backtests/{job_id}/equity for charts
  4. Report - GET /backtests/{job_id}/report-data for full analysis

Strategy Promotion Pipeline

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    Backtest     │ ──► │    Register     │ ──► │     Stage       │
│   Validation    │     │   to MLflow     │     │   (Dry-Run)     │
└─────────────────┘     └─────────────────┘     └─────────────────┘
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    Deploy       │ ◄── │    Promote      │ ◄── │    A/B Test     │
│  (Production)   │     │ to Production   │     │  (Optional)     │
└─────────────────┘     └─────────────────┘     └─────────────────┘

API Sequence:

# 1. Run backtest
POST /backtests  job_id

# 2. Wait for completion
GET /backtests/{job_id}  status: "completed"

# 3. Register to MLflow
POST /strategies/{name}/register
  --backtest_run_id <mlflow_run_id>
  --docker_image_uri <ecr_uri>

# 4. Stage for validation
POST /strategies/{name}/stage --version 3

# 5. (Optional) A/B test
POST /models/{name}/challenges
  --champion_version 2
  --challenger_version 3

# 6. Promote to production
POST /strategies/{name}/promote --version 3

Hyperopt Workflow

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Start Hyperopt │ ──► │  Monitor Trials │ ──► │   Get Results   │
│  POST /hyperopt │     │ GET /hyperopt/  │     │ GET /hyperopt/  │
│                 │     │   {id}          │     │   {id}/result   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                               │                        │
                               │ Progress: 45/100       │
                               │ Best: 1.85             │
                               ▼                        ▼
                        ┌─────────────────┐     ┌─────────────────┐
                        │   Live Updates  │     │   Best Params   │
                        │   (WebSocket)   │     │   Apply 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

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│ Create Challenge│ ──► │  Route Traffic  │ ──► │   Log Trades    │
│ POST /challenges│     │  Canary: 5/95   │     │ POST /trades    │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                        ┌───────────────────────────────┘
        ┌───────────────────────────────────────────────────────┐
        │                    Canary Stages                       │
        │  Stage 1: 5% ──► Stage 2: 10% ──► Stage 3: 25% ──► 50%│
        └───────────────────────────────────────────────────────┘
                ┌───────────────┴───────────────┐
                │                               │
                ▼                               ▼
        ┌───────────────┐               ┌───────────────┐
        │ PROMOTE       │               │ ROLLBACK      │
        │ Challenger    │               │ Keep Champion │
        │ Wins          │               │ Challenger    │
        └───────────────┘               └───────────────┘

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 /backtests

Submit a new backtest job.

Request:

{
  "config": {
    "strategy": "TrendFollowingStrategy",
    "pairs": ["BTC/USDT:USDT", "ETH/USDT:USDT"],
    "timeframe": "1h",
    "date_range": {
      "start": "2024-01-01T00:00:00Z",
      "end": "2024-03-01T00:00:00Z"
    },
    "stake_amount": 100.0,
    "dry_run_wallet": 10000.0
  },
  "experiment_name": "trend-following-btc-eth-q1-2024",
  "priority": 5
}

Response 201:

{
  "job_id": "bt-abc123def456",
  "status": "pending"
}

Errors: 400 (validation), 503 (service unavailable)


GET /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 /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 /backtests/{job_id}/cancel

Cancel a running backtest job.

Response 200:

{
  "job_id": "bt-abc123def456",
  "status": "cancelled"
}

Errors: 400 (already terminal), 404 (not found)


GET /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 /backtests/{job_id}/report-data

Get complete report data for HTML report generation.

Response 200:

{
  "result": { ... },
  "equity_curve": { ... },
  "trades": [...],
  "pair_performance": { ... }
}


Strategy Management

GET /strategies

List available strategy configurations.

Response 200:

{
  "configs": ["momentum-v1", "trend-following-v2", "mean-reversion"],
  "count": 3
}


GET /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 /strategies/{name}/stage

Stage a strategy model version for validation testing.

Request:

{
  "version": "3"
}

Response 200:

{
  "success": true,
  "message": "Version 3 staged successfully",
  "previous_stage": "None",
  "new_stage": "Staging"
}


POST /strategies/{name}/promote

Promote a strategy model version to Production.

Request:

{
  "version": "3",
  "archive_previous": true,
  "skip_validation": false
}

Response 200:

{
  "success": true,
  "message": "Version 3 promoted to Production",
  "archived_versions": ["2"],
  "previous_stage": "Staging",
  "new_stage": "Production"
}


Model Operations

GET /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 /models/{model_name}/rollback

Rollback a model to a previous version.

Request:

{
  "target_version": "2",
  "dry_run": false
}

Response 200:

{
  "success": true,
  "message": "Rolled back to version 2",
  "previous_production": "3",
  "new_production": "2"
}


Strategy Catalog

GET /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 /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"
}


Data Proxy

GET /data/symbols

Get available trading symbols from exchange.

Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | exchange | string | binance_futures | Exchange key |

Response 200:

{
  "exchange": "binance_futures",
  "symbols": ["BTC/USDT:USDT", "ETH/USDT:USDT", ...],
  "count": 150
}


GET /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 /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]
}


POST /challenges/{challenge_id}/trades

Add a trade result to a challenge.

Request:

{
  "variant": "challenger",
  "profit_pct": 2.5
}


GET /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
}


Strategy Registration

POST /strategies/{name}/register

Register a strategy version after successful backtest.

Request:

{
  "backtest_run_id": "run-abc123",
  "docker_image_uri": "123456789.dkr.ecr.us-east-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',
      pairs: ['BTC/USDT:USDT'],
      timeframe: '1h',
      date_range: {
        start: '2024-01-01T00:00:00Z',
        end: '2024-03-01T00:00:00Z',
      },
      stake_amount: 100.0,
      dry_run_wallet: 10000.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',
  pairs: ['BTC/USDT:USDT', 'ETH/USDT:USDT'],
  timeframe: '1h',
  date_range: {
    start: '2024-01-01T00:00:00Z',
    end: '2024-06-01T00:00:00Z',
  },
  stake_amount: 100,
  dry_run_wallet: 10000,
});

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: /docs on each service
  • Health Check: /api/v1/health on each service
  • Issues: https://github.com/tradai/tradai-uv/issues