mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-02 09:37:23 -04:00
BREAKING CHANGE: end_date is now required and cannot be null/empty
New Features:
- Resume mode: Set start_date to null to continue from last completed date per model
- Idempotent by default: Skip already-completed dates with replace_existing=false
- Per-model independence: Each model resumes from its own last completed date
- Cold start handling: If no data exists in resume mode, runs only end_date as single day
API Changes:
- start_date: Now optional (null enables resume mode)
- end_date: Now REQUIRED (cannot be null or empty string)
- replace_existing: New optional field (default: false for idempotent behavior)
Implementation:
- Added JobManager.get_last_completed_date_for_model() method
- Added JobManager.get_completed_model_dates() method
- Updated create_job() to support model_day_filter for selective task creation
- Fixed bug with start_date=None in price data checks
Documentation:
- Updated API_REFERENCE.md with complete examples and behavior matrix
- Updated QUICK_START.md with resume mode examples
- Updated docs/user-guide/using-the-api.md
- Added CHANGELOG_NEW_API.md with migration guide
- Updated all integration tests for new schema
- Updated client library examples (Python, TypeScript)
Migration:
- Old: {"start_date": "2025-01-16"}
- New: {"start_date": "2025-01-16", "end_date": "2025-01-16"}
- Resume: {"start_date": null, "end_date": "2025-01-31"}
See CHANGELOG_NEW_API.md for complete details.
919 lines
25 KiB
Markdown
919 lines
25 KiB
Markdown
# AI-Trader-Server API Reference
|
|
|
|
Complete reference for the AI-Trader-Server REST API service.
|
|
|
|
**Base URL:** `http://localhost:8080` (default)
|
|
|
|
**API Version:** 1.0.0
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### POST /simulate/trigger
|
|
|
|
Trigger a new simulation job for a specified date range and models.
|
|
|
|
**Supports three operational modes:**
|
|
1. **Explicit date range**: Provide both `start_date` and `end_date`
|
|
2. **Single date**: Set `start_date` = `end_date`
|
|
3. **Resume mode**: Set `start_date` to `null` to continue from each model's last completed date
|
|
|
|
**Request Body:**
|
|
|
|
```json
|
|
{
|
|
"start_date": "2025-01-16",
|
|
"end_date": "2025-01-17",
|
|
"models": ["gpt-4", "claude-3.7-sonnet"],
|
|
"replace_existing": false
|
|
}
|
|
```
|
|
|
|
**Parameters:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `start_date` | string \| null | No | Start date in YYYY-MM-DD format. If `null`, enables resume mode (each model continues from its last completed date). Defaults to `null`. |
|
|
| `end_date` | string | **Yes** | End date in YYYY-MM-DD format. **Required** - cannot be null or empty. |
|
|
| `models` | array[string] | No | Model signatures to run. If omitted, uses all enabled models from server config. |
|
|
| `replace_existing` | boolean | No | If `false` (default), skips already-completed model-days (idempotent). If `true`, re-runs all dates even if previously completed. |
|
|
|
|
**Response (200 OK):**
|
|
|
|
```json
|
|
{
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"status": "pending",
|
|
"total_model_days": 4,
|
|
"message": "Simulation job created with 2 trading dates"
|
|
}
|
|
```
|
|
|
|
**Response Fields:**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `job_id` | string | Unique UUID for this simulation job |
|
|
| `status` | string | Job status: `pending`, `running`, `completed`, `partial`, or `failed` |
|
|
| `total_model_days` | integer | Total number of model-day combinations to execute |
|
|
| `message` | string | Human-readable status message |
|
|
|
|
**Error Responses:**
|
|
|
|
**400 Bad Request** - Invalid parameters or validation failure
|
|
```json
|
|
{
|
|
"detail": "Invalid date format: 2025-1-16. Expected YYYY-MM-DD"
|
|
}
|
|
```
|
|
|
|
**400 Bad Request** - Another job is already running
|
|
```json
|
|
{
|
|
"detail": "Another simulation job is already running or pending. Please wait for it to complete."
|
|
}
|
|
```
|
|
|
|
**500 Internal Server Error** - Server configuration issue
|
|
```json
|
|
{
|
|
"detail": "Server configuration file not found: configs/default_config.json"
|
|
}
|
|
```
|
|
|
|
**503 Service Unavailable** - Price data download failed
|
|
```json
|
|
{
|
|
"detail": "Failed to download any price data. Check ALPHAADVANTAGE_API_KEY."
|
|
}
|
|
```
|
|
|
|
**Validation Rules:**
|
|
|
|
- **Date format:** Must be YYYY-MM-DD
|
|
- **Date validity:** Must be valid calendar dates
|
|
- **Date order:** `start_date` must be <= `end_date` (when `start_date` is not null)
|
|
- **end_date required:** Cannot be null or empty string
|
|
- **Future dates:** Cannot simulate future dates (must be <= today)
|
|
- **Date range limit:** Maximum 30 days (configurable via `MAX_SIMULATION_DAYS`)
|
|
- **Model signatures:** Must match models defined in server configuration
|
|
- **Concurrency:** Only one simulation job can run at a time
|
|
|
|
**Behavior:**
|
|
|
|
1. Validates date range and parameters
|
|
2. Determines which models to run (from request or server config)
|
|
3. **Resume mode** (if `start_date` is null):
|
|
- For each model, queries last completed simulation date
|
|
- If no previous data exists (cold start), uses `end_date` as single-day simulation
|
|
- Otherwise, resumes from day after last completed date
|
|
- Each model can have different resume start dates
|
|
4. **Idempotent mode** (if `replace_existing=false`, default):
|
|
- Queries database for already-completed model-day combinations in date range
|
|
- Skips completed model-days, only creates tasks for gaps
|
|
- Returns error if all requested dates are already completed
|
|
5. Checks for missing price data in date range
|
|
6. Downloads missing data if `AUTO_DOWNLOAD_PRICE_DATA=true` (default)
|
|
7. Identifies trading dates with complete price data (all symbols available)
|
|
8. Creates job in database with status `pending` (only for model-days that will actually run)
|
|
9. Starts background worker thread
|
|
10. Returns immediately with job ID
|
|
|
|
**Examples:**
|
|
|
|
Single day, single model:
|
|
```bash
|
|
curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"start_date": "2025-01-16",
|
|
"end_date": "2025-01-16",
|
|
"models": ["gpt-4"]
|
|
}'
|
|
```
|
|
|
|
Date range, all enabled models:
|
|
```bash
|
|
curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"start_date": "2025-01-16",
|
|
"end_date": "2025-01-20"
|
|
}'
|
|
```
|
|
|
|
Resume from last completed date:
|
|
```bash
|
|
curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"start_date": null,
|
|
"end_date": "2025-01-31",
|
|
"models": ["gpt-4"]
|
|
}'
|
|
```
|
|
|
|
Idempotent simulation (skip already-completed dates):
|
|
```bash
|
|
curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"start_date": "2025-01-16",
|
|
"end_date": "2025-01-20",
|
|
"models": ["gpt-4"],
|
|
"replace_existing": false
|
|
}'
|
|
```
|
|
|
|
Re-run existing dates (force replace):
|
|
```bash
|
|
curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"start_date": "2025-01-16",
|
|
"end_date": "2025-01-20",
|
|
"models": ["gpt-4"],
|
|
"replace_existing": true
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
### GET /simulate/status/{job_id}
|
|
|
|
Get status and progress of a simulation job.
|
|
|
|
**URL Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `job_id` | string | Job UUID from trigger response |
|
|
|
|
**Response (200 OK):**
|
|
|
|
```json
|
|
{
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"status": "running",
|
|
"progress": {
|
|
"total_model_days": 4,
|
|
"completed": 2,
|
|
"failed": 0,
|
|
"pending": 2
|
|
},
|
|
"date_range": ["2025-01-16", "2025-01-17"],
|
|
"models": ["gpt-4", "claude-3.7-sonnet"],
|
|
"created_at": "2025-01-16T10:00:00Z",
|
|
"started_at": "2025-01-16T10:00:05Z",
|
|
"completed_at": null,
|
|
"total_duration_seconds": null,
|
|
"error": null,
|
|
"details": [
|
|
{
|
|
"model_signature": "gpt-4",
|
|
"trading_date": "2025-01-16",
|
|
"status": "completed",
|
|
"start_time": "2025-01-16T10:00:05Z",
|
|
"end_time": "2025-01-16T10:05:23Z",
|
|
"duration_seconds": 318.5,
|
|
"error": null
|
|
},
|
|
{
|
|
"model_signature": "claude-3.7-sonnet",
|
|
"trading_date": "2025-01-16",
|
|
"status": "completed",
|
|
"start_time": "2025-01-16T10:05:24Z",
|
|
"end_time": "2025-01-16T10:10:12Z",
|
|
"duration_seconds": 288.0,
|
|
"error": null
|
|
},
|
|
{
|
|
"model_signature": "gpt-4",
|
|
"trading_date": "2025-01-17",
|
|
"status": "running",
|
|
"start_time": "2025-01-16T10:10:13Z",
|
|
"end_time": null,
|
|
"duration_seconds": null,
|
|
"error": null
|
|
},
|
|
{
|
|
"model_signature": "claude-3.7-sonnet",
|
|
"trading_date": "2025-01-17",
|
|
"status": "pending",
|
|
"start_time": null,
|
|
"end_time": null,
|
|
"duration_seconds": null,
|
|
"error": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response Fields:**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `job_id` | string | Job UUID |
|
|
| `status` | string | Overall job status |
|
|
| `progress` | object | Progress summary |
|
|
| `progress.total_model_days` | integer | Total model-day combinations |
|
|
| `progress.completed` | integer | Successfully completed model-days |
|
|
| `progress.failed` | integer | Failed model-days |
|
|
| `progress.pending` | integer | Not yet started model-days |
|
|
| `date_range` | array[string] | Trading dates in this job |
|
|
| `models` | array[string] | Model signatures in this job |
|
|
| `created_at` | string | ISO 8601 timestamp when job was created |
|
|
| `started_at` | string | ISO 8601 timestamp when execution began |
|
|
| `completed_at` | string | ISO 8601 timestamp when job finished |
|
|
| `total_duration_seconds` | float | Total execution time in seconds |
|
|
| `error` | string | Error message if job failed |
|
|
| `details` | array[object] | Per model-day execution details |
|
|
|
|
**Job Status Values:**
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `pending` | Job created, waiting to start |
|
|
| `running` | Job currently executing |
|
|
| `completed` | All model-days completed successfully |
|
|
| `partial` | Some model-days completed, some failed |
|
|
| `failed` | All model-days failed |
|
|
|
|
**Model-Day Status Values:**
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `pending` | Not started yet |
|
|
| `running` | Currently executing |
|
|
| `completed` | Finished successfully |
|
|
| `failed` | Execution failed (see `error` field) |
|
|
|
|
**Error Response:**
|
|
|
|
**404 Not Found** - Job doesn't exist
|
|
```json
|
|
{
|
|
"detail": "Job 550e8400-e29b-41d4-a716-446655440000 not found"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```bash
|
|
curl http://localhost:8080/simulate/status/550e8400-e29b-41d4-a716-446655440000
|
|
```
|
|
|
|
**Polling Recommendation:**
|
|
|
|
Poll every 10-30 seconds until `status` is `completed`, `partial`, or `failed`.
|
|
|
|
---
|
|
|
|
### GET /results
|
|
|
|
Query simulation results with optional filters.
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|------|----------|-------------|
|
|
| `job_id` | string | No | Filter by job UUID |
|
|
| `date` | string | No | Filter by trading date (YYYY-MM-DD) |
|
|
| `model` | string | No | Filter by model signature |
|
|
|
|
**Response (200 OK):**
|
|
|
|
```json
|
|
{
|
|
"results": [
|
|
{
|
|
"id": 1,
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"date": "2025-01-16",
|
|
"model": "gpt-4",
|
|
"action_id": 1,
|
|
"action_type": "buy",
|
|
"symbol": "AAPL",
|
|
"amount": 10,
|
|
"price": 250.50,
|
|
"cash": 7495.00,
|
|
"portfolio_value": 10000.00,
|
|
"daily_profit": 0.00,
|
|
"daily_return_pct": 0.00,
|
|
"created_at": "2025-01-16T10:05:23Z",
|
|
"holdings": [
|
|
{"symbol": "AAPL", "quantity": 10},
|
|
{"symbol": "CASH", "quantity": 7495.00}
|
|
]
|
|
},
|
|
{
|
|
"id": 2,
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"date": "2025-01-16",
|
|
"model": "gpt-4",
|
|
"action_id": 2,
|
|
"action_type": "buy",
|
|
"symbol": "MSFT",
|
|
"amount": 5,
|
|
"price": 380.20,
|
|
"cash": 5594.00,
|
|
"portfolio_value": 10105.00,
|
|
"daily_profit": 105.00,
|
|
"daily_return_pct": 1.05,
|
|
"created_at": "2025-01-16T10:05:23Z",
|
|
"holdings": [
|
|
{"symbol": "AAPL", "quantity": 10},
|
|
{"symbol": "MSFT", "quantity": 5},
|
|
{"symbol": "CASH", "quantity": 5594.00}
|
|
]
|
|
}
|
|
],
|
|
"count": 2
|
|
}
|
|
```
|
|
|
|
**Response Fields:**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `results` | array[object] | Array of position records |
|
|
| `count` | integer | Number of results returned |
|
|
|
|
**Position Record Fields:**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `id` | integer | Unique position record ID |
|
|
| `job_id` | string | Job UUID this belongs to |
|
|
| `date` | string | Trading date (YYYY-MM-DD) |
|
|
| `model` | string | Model signature |
|
|
| `action_id` | integer | Action sequence number (1, 2, 3...) for this model-day |
|
|
| `action_type` | string | Action taken: `buy`, `sell`, or `hold` |
|
|
| `symbol` | string | Stock symbol traded (or null for `hold`) |
|
|
| `amount` | integer | Quantity traded (or null for `hold`) |
|
|
| `price` | float | Price per share (or null for `hold`) |
|
|
| `cash` | float | Cash balance after this action |
|
|
| `portfolio_value` | float | Total portfolio value (cash + holdings) |
|
|
| `daily_profit` | float | Profit/loss for this trading day |
|
|
| `daily_return_pct` | float | Return percentage for this day |
|
|
| `created_at` | string | ISO 8601 timestamp when recorded |
|
|
| `holdings` | array[object] | Current holdings after this action |
|
|
|
|
**Holdings Object:**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `symbol` | string | Stock symbol or "CASH" |
|
|
| `quantity` | float | Shares owned (or cash amount) |
|
|
|
|
**Examples:**
|
|
|
|
All results for a specific job:
|
|
```bash
|
|
curl "http://localhost:8080/results?job_id=550e8400-e29b-41d4-a716-446655440000"
|
|
```
|
|
|
|
Results for a specific date:
|
|
```bash
|
|
curl "http://localhost:8080/results?date=2025-01-16"
|
|
```
|
|
|
|
Results for a specific model:
|
|
```bash
|
|
curl "http://localhost:8080/results?model=gpt-4"
|
|
```
|
|
|
|
Combine filters:
|
|
```bash
|
|
curl "http://localhost:8080/results?job_id=550e8400-e29b-41d4-a716-446655440000&date=2025-01-16&model=gpt-4"
|
|
```
|
|
|
|
---
|
|
|
|
### GET /health
|
|
|
|
Health check endpoint for monitoring and orchestration services.
|
|
|
|
**Response (200 OK):**
|
|
|
|
```json
|
|
{
|
|
"status": "healthy",
|
|
"database": "connected",
|
|
"timestamp": "2025-01-16T10:00:00Z"
|
|
}
|
|
```
|
|
|
|
**Response Fields:**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `status` | string | Overall service health: `healthy` or `unhealthy` |
|
|
| `database` | string | Database connection status: `connected` or `disconnected` |
|
|
| `timestamp` | string | ISO 8601 timestamp of health check |
|
|
|
|
**Example:**
|
|
|
|
```bash
|
|
curl http://localhost:8080/health
|
|
```
|
|
|
|
**Usage:**
|
|
|
|
- Docker health checks: `HEALTHCHECK CMD curl -f http://localhost:8080/health`
|
|
- Monitoring systems: Poll every 30-60 seconds
|
|
- Orchestration services: Verify availability before triggering simulations
|
|
|
|
---
|
|
|
|
## Deployment Mode
|
|
|
|
All API responses include a `deployment_mode` field indicating whether the service is running in production or development mode.
|
|
|
|
### Response Format
|
|
|
|
```json
|
|
{
|
|
"job_id": "abc123",
|
|
"status": "completed",
|
|
"deployment_mode": "DEV",
|
|
"is_dev_mode": true,
|
|
"preserve_dev_data": false
|
|
}
|
|
```
|
|
|
|
**Fields:**
|
|
- `deployment_mode`: "PROD" or "DEV"
|
|
- `is_dev_mode`: Boolean flag
|
|
- `preserve_dev_data`: Null in PROD, boolean in DEV
|
|
|
|
### DEV Mode Behavior
|
|
|
|
When `DEPLOYMENT_MODE=DEV` is set:
|
|
- No AI API calls (mock responses)
|
|
- Separate dev database (`jobs_dev.db`)
|
|
- Separate data directory (`dev_agent_data/`)
|
|
- Database reset on startup (unless PRESERVE_DEV_DATA=true)
|
|
|
|
**Health Check Example:**
|
|
|
|
```bash
|
|
curl http://localhost:8080/health
|
|
```
|
|
|
|
Response in DEV mode:
|
|
```json
|
|
{
|
|
"status": "healthy",
|
|
"database": "connected",
|
|
"timestamp": "2025-01-16T10:00:00Z",
|
|
"deployment_mode": "DEV",
|
|
"is_dev_mode": true,
|
|
"preserve_dev_data": false
|
|
}
|
|
```
|
|
|
|
### Use Cases
|
|
|
|
- **Testing:** Validate orchestration without AI API costs
|
|
- **CI/CD:** Automated testing in pipelines
|
|
- **Development:** Rapid iteration on system logic
|
|
- **Configuration validation:** Test settings before production
|
|
|
|
---
|
|
|
|
## Common Workflows
|
|
|
|
### Trigger and Monitor a Simulation
|
|
|
|
1. **Trigger simulation:**
|
|
```bash
|
|
RESPONSE=$(curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"start_date": "2025-01-16", "end_date": "2025-01-17", "models": ["gpt-4"]}')
|
|
|
|
JOB_ID=$(echo $RESPONSE | jq -r '.job_id')
|
|
echo "Job ID: $JOB_ID"
|
|
```
|
|
|
|
Or use resume mode:
|
|
```bash
|
|
RESPONSE=$(curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"start_date": null, "end_date": "2025-01-31", "models": ["gpt-4"]}')
|
|
|
|
JOB_ID=$(echo $RESPONSE | jq -r '.job_id')
|
|
```
|
|
|
|
2. **Poll for completion:**
|
|
```bash
|
|
while true; do
|
|
STATUS=$(curl -s http://localhost:8080/simulate/status/$JOB_ID | jq -r '.status')
|
|
echo "Status: $STATUS"
|
|
|
|
if [[ "$STATUS" == "completed" ]] || [[ "$STATUS" == "partial" ]] || [[ "$STATUS" == "failed" ]]; then
|
|
break
|
|
fi
|
|
|
|
sleep 10
|
|
done
|
|
```
|
|
|
|
3. **Retrieve results:**
|
|
```bash
|
|
curl "http://localhost:8080/results?job_id=$JOB_ID" | jq '.'
|
|
```
|
|
|
|
### Scheduled Daily Simulations
|
|
|
|
Use a scheduler (cron, Airflow, etc.) to trigger simulations:
|
|
|
|
**Option 1: Resume mode (recommended)**
|
|
```bash
|
|
#!/bin/bash
|
|
# daily_simulation.sh - Resume from last completed date
|
|
|
|
# Calculate today's date
|
|
TODAY=$(date +%Y-%m-%d)
|
|
|
|
# Trigger simulation in resume mode
|
|
curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"start_date\": null, \"end_date\": \"$TODAY\", \"models\": [\"gpt-4\"]}"
|
|
```
|
|
|
|
**Option 2: Explicit yesterday's date**
|
|
```bash
|
|
#!/bin/bash
|
|
# daily_simulation.sh - Run specific date
|
|
|
|
# Calculate yesterday's date
|
|
DATE=$(date -d "yesterday" +%Y-%m-%d)
|
|
|
|
# Trigger simulation
|
|
curl -X POST http://localhost:8080/simulate/trigger \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"start_date\": \"$DATE\", \"end_date\": \"$DATE\", \"models\": [\"gpt-4\"]}"
|
|
```
|
|
|
|
Add to crontab:
|
|
```
|
|
0 6 * * * /path/to/daily_simulation.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
All endpoints return consistent error responses with HTTP status codes and detail messages.
|
|
|
|
### Common Error Codes
|
|
|
|
| Code | Meaning | Common Causes |
|
|
|------|---------|---------------|
|
|
| 400 | Bad Request | Invalid date format, invalid parameters, concurrent job running |
|
|
| 404 | Not Found | Job ID doesn't exist |
|
|
| 500 | Internal Server Error | Server misconfiguration, missing config file |
|
|
| 503 | Service Unavailable | Price data download failed, database unavailable |
|
|
|
|
### Error Response Format
|
|
|
|
```json
|
|
{
|
|
"detail": "Human-readable error message"
|
|
}
|
|
```
|
|
|
|
### Retry Recommendations
|
|
|
|
- **400 errors:** Fix request parameters, don't retry
|
|
- **404 errors:** Verify job ID, don't retry
|
|
- **500 errors:** Check server logs, investigate before retrying
|
|
- **503 errors:** Retry with exponential backoff (wait 1s, 2s, 4s, etc.)
|
|
|
|
---
|
|
|
|
## Rate Limits and Constraints
|
|
|
|
### Concurrency
|
|
|
|
- **Maximum concurrent jobs:** 1 (configurable via `MAX_CONCURRENT_JOBS`)
|
|
- **Attempting to start a second job returns:** 400 Bad Request
|
|
|
|
### Date Range Limits
|
|
|
|
- **Maximum date range:** 30 days (configurable via `MAX_SIMULATION_DAYS`)
|
|
- **Attempting longer range returns:** 400 Bad Request
|
|
|
|
### Price Data
|
|
|
|
- **Alpha Vantage API rate limit:** 5 requests/minute (free tier), 75 requests/minute (premium)
|
|
- **Automatic download:** Enabled by default (`AUTO_DOWNLOAD_PRICE_DATA=true`)
|
|
- **Behavior when rate limited:** Partial data downloaded, simulation continues with available dates
|
|
|
|
---
|
|
|
|
## Data Persistence
|
|
|
|
All simulation data is stored in SQLite database at `data/jobs.db`.
|
|
|
|
### Database Tables
|
|
|
|
- **jobs** - Job metadata and status
|
|
- **job_details** - Per model-day execution details
|
|
- **positions** - Trading position records
|
|
- **holdings** - Portfolio holdings breakdown
|
|
- **reasoning_logs** - AI decision reasoning (if enabled)
|
|
- **tool_usage** - MCP tool usage statistics
|
|
- **price_data** - Historical price data cache
|
|
- **price_coverage** - Data availability tracking
|
|
|
|
### Data Retention
|
|
|
|
- Job data persists indefinitely by default
|
|
- Results can be queried at any time after job completion
|
|
- Manual cleanup: Delete rows from `jobs` table (cascades to related tables)
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
API behavior is controlled via environment variables and server configuration file.
|
|
|
|
### Environment Variables
|
|
|
|
See [docs/reference/environment-variables.md](docs/reference/environment-variables.md) for complete reference.
|
|
|
|
**Key variables:**
|
|
|
|
- `API_PORT` - API server port (default: 8080)
|
|
- `MAX_CONCURRENT_JOBS` - Maximum concurrent simulations (default: 1)
|
|
- `MAX_SIMULATION_DAYS` - Maximum date range (default: 30)
|
|
- `AUTO_DOWNLOAD_PRICE_DATA` - Auto-download missing data (default: true)
|
|
- `ALPHAADVANTAGE_API_KEY` - Alpha Vantage API key (required)
|
|
|
|
### Server Configuration File
|
|
|
|
Server loads model definitions from configuration file (default: `configs/default_config.json`).
|
|
|
|
**Example config:**
|
|
```json
|
|
{
|
|
"models": [
|
|
{
|
|
"name": "GPT-4",
|
|
"basemodel": "openai/gpt-4",
|
|
"signature": "gpt-4",
|
|
"enabled": true
|
|
},
|
|
{
|
|
"name": "Claude 3.7 Sonnet",
|
|
"basemodel": "anthropic/claude-3.7-sonnet",
|
|
"signature": "claude-3.7-sonnet",
|
|
"enabled": true
|
|
}
|
|
],
|
|
"agent_config": {
|
|
"max_steps": 30,
|
|
"initial_cash": 10000.0
|
|
}
|
|
}
|
|
```
|
|
|
|
**Model fields:**
|
|
|
|
- `signature` - Unique identifier used in API requests
|
|
- `enabled` - Whether model runs when no models specified in request
|
|
- `basemodel` - Model identifier for AI provider
|
|
- `openai_base_url` - Optional custom API endpoint
|
|
- `openai_api_key` - Optional model-specific API key
|
|
|
|
---
|
|
|
|
## OpenAPI / Swagger Documentation
|
|
|
|
Interactive API documentation available at:
|
|
|
|
- Swagger UI: `http://localhost:8080/docs`
|
|
- ReDoc: `http://localhost:8080/redoc`
|
|
- OpenAPI JSON: `http://localhost:8080/openapi.json`
|
|
|
|
---
|
|
|
|
## Client Libraries
|
|
|
|
### Python
|
|
|
|
```python
|
|
import requests
|
|
import time
|
|
|
|
class AITraderServerClient:
|
|
def __init__(self, base_url="http://localhost:8080"):
|
|
self.base_url = base_url
|
|
|
|
def trigger_simulation(self, end_date, start_date=None, models=None, replace_existing=False):
|
|
"""
|
|
Trigger a simulation job.
|
|
|
|
Args:
|
|
end_date: End date (YYYY-MM-DD), required
|
|
start_date: Start date (YYYY-MM-DD) or None for resume mode
|
|
models: List of model signatures or None for all enabled models
|
|
replace_existing: If False, skip already-completed dates (idempotent)
|
|
"""
|
|
payload = {"end_date": end_date, "replace_existing": replace_existing}
|
|
if start_date is not None:
|
|
payload["start_date"] = start_date
|
|
if models:
|
|
payload["models"] = models
|
|
|
|
response = requests.post(
|
|
f"{self.base_url}/simulate/trigger",
|
|
json=payload
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
def get_status(self, job_id):
|
|
"""Get job status."""
|
|
response = requests.get(f"{self.base_url}/simulate/status/{job_id}")
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
def wait_for_completion(self, job_id, poll_interval=10):
|
|
"""Poll until job completes."""
|
|
while True:
|
|
status = self.get_status(job_id)
|
|
if status["status"] in ["completed", "partial", "failed"]:
|
|
return status
|
|
time.sleep(poll_interval)
|
|
|
|
def get_results(self, job_id=None, date=None, model=None):
|
|
"""Query results with optional filters."""
|
|
params = {}
|
|
if job_id:
|
|
params["job_id"] = job_id
|
|
if date:
|
|
params["date"] = date
|
|
if model:
|
|
params["model"] = model
|
|
|
|
response = requests.get(f"{self.base_url}/results", params=params)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
# Usage examples
|
|
client = AITraderServerClient()
|
|
|
|
# Single day simulation
|
|
job = client.trigger_simulation(end_date="2025-01-16", start_date="2025-01-16", models=["gpt-4"])
|
|
|
|
# Date range simulation
|
|
job = client.trigger_simulation(end_date="2025-01-20", start_date="2025-01-16")
|
|
|
|
# Resume mode (continue from last completed)
|
|
job = client.trigger_simulation(end_date="2025-01-31", models=["gpt-4"])
|
|
|
|
# Wait for completion and get results
|
|
result = client.wait_for_completion(job["job_id"])
|
|
results = client.get_results(job_id=job["job_id"])
|
|
```
|
|
|
|
### TypeScript/JavaScript
|
|
|
|
```typescript
|
|
class AITraderServerClient {
|
|
constructor(private baseUrl: string = "http://localhost:8080") {}
|
|
|
|
async triggerSimulation(
|
|
endDate: string,
|
|
options: {
|
|
startDate?: string | null;
|
|
models?: string[];
|
|
replaceExisting?: boolean;
|
|
} = {}
|
|
) {
|
|
const body: any = {
|
|
end_date: endDate,
|
|
replace_existing: options.replaceExisting ?? false
|
|
};
|
|
if (options.startDate !== undefined) {
|
|
body.start_date = options.startDate;
|
|
}
|
|
if (options.models) {
|
|
body.models = options.models;
|
|
}
|
|
|
|
const response = await fetch(`${this.baseUrl}/simulate/trigger`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body)
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
async getStatus(jobId: string) {
|
|
const response = await fetch(
|
|
`${this.baseUrl}/simulate/status/${jobId}`
|
|
);
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
async waitForCompletion(jobId: string, pollInterval: number = 10000) {
|
|
while (true) {
|
|
const status = await this.getStatus(jobId);
|
|
if (["completed", "partial", "failed"].includes(status.status)) {
|
|
return status;
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
}
|
|
}
|
|
|
|
async getResults(filters: {
|
|
jobId?: string;
|
|
date?: string;
|
|
model?: string;
|
|
} = {}) {
|
|
const params = new URLSearchParams();
|
|
if (filters.jobId) params.set("job_id", filters.jobId);
|
|
if (filters.date) params.set("date", filters.date);
|
|
if (filters.model) params.set("model", filters.model);
|
|
|
|
const response = await fetch(
|
|
`${this.baseUrl}/results?${params.toString()}`
|
|
);
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return response.json();
|
|
}
|
|
}
|
|
|
|
// Usage examples
|
|
const client = new AITraderServerClient();
|
|
|
|
// Single day simulation
|
|
const job1 = await client.triggerSimulation("2025-01-16", {
|
|
startDate: "2025-01-16",
|
|
models: ["gpt-4"]
|
|
});
|
|
|
|
// Date range simulation
|
|
const job2 = await client.triggerSimulation("2025-01-20", {
|
|
startDate: "2025-01-16"
|
|
});
|
|
|
|
// Resume mode (continue from last completed)
|
|
const job3 = await client.triggerSimulation("2025-01-31", {
|
|
startDate: null,
|
|
models: ["gpt-4"]
|
|
});
|
|
|
|
// Wait for completion and get results
|
|
const result = await client.waitForCompletion(job1.job_id);
|
|
const results = await client.getResults({ jobId: job1.job_id });
|
|
```
|