mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-02 09:37:23 -04:00
- Replaced old table references (positions, reasoning_logs) with new schema tables - Updated table list: trading_days, holdings, actions - Added link to complete database schema documentation
1233 lines
34 KiB
Markdown
1233 lines
34 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 or empty array, 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 |
|
|
| `warnings` | array[string] | Optional array of non-fatal warning messages |
|
|
|
|
**Job Status Values:**
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `pending` | Job created, waiting to start |
|
|
| `downloading_data` | Preparing price data (downloading if needed) |
|
|
| `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) |
|
|
|
|
**Warnings Field:**
|
|
|
|
The optional `warnings` array contains non-fatal warning messages about the job execution:
|
|
|
|
- **Rate limit warnings**: Price data download hit API rate limits
|
|
- **Skipped dates**: Some dates couldn't be processed due to incomplete price data
|
|
- **Other issues**: Non-fatal problems that don't prevent job completion
|
|
|
|
**Example response with warnings:**
|
|
|
|
```json
|
|
{
|
|
"job_id": "019a426b-1234-5678-90ab-cdef12345678",
|
|
"status": "completed",
|
|
"progress": {
|
|
"total_model_days": 10,
|
|
"completed": 8,
|
|
"failed": 0,
|
|
"pending": 0
|
|
},
|
|
"warnings": [
|
|
"Rate limit reached - downloaded 12/15 symbols",
|
|
"Skipped 2 dates due to incomplete price data: ['2025-10-02', '2025-10-05']"
|
|
]
|
|
}
|
|
```
|
|
|
|
If no warnings occurred, the field will be `null` or omitted.
|
|
|
|
**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
|
|
|
|
Get trading results grouped by day with daily P&L metrics and AI reasoning.
|
|
|
|
**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 |
|
|
| `reasoning` | string | No | Include AI reasoning: `none` (default), `summary`, or `full` |
|
|
|
|
**Response (200 OK) - Default (no reasoning):**
|
|
|
|
```json
|
|
{
|
|
"count": 2,
|
|
"results": [
|
|
{
|
|
"date": "2025-01-15",
|
|
"model": "gpt-4",
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"starting_position": {
|
|
"holdings": [],
|
|
"cash": 10000.0,
|
|
"portfolio_value": 10000.0
|
|
},
|
|
"daily_metrics": {
|
|
"profit": 0.0,
|
|
"return_pct": 0.0,
|
|
"days_since_last_trading": 0
|
|
},
|
|
"trades": [
|
|
{
|
|
"action_type": "buy",
|
|
"symbol": "AAPL",
|
|
"quantity": 10,
|
|
"price": 150.0,
|
|
"created_at": "2025-01-15T14:30:00Z"
|
|
}
|
|
],
|
|
"final_position": {
|
|
"holdings": [
|
|
{"symbol": "AAPL", "quantity": 10}
|
|
],
|
|
"cash": 8500.0,
|
|
"portfolio_value": 10000.0
|
|
},
|
|
"metadata": {
|
|
"total_actions": 1,
|
|
"session_duration_seconds": 45.2,
|
|
"completed_at": "2025-01-15T14:31:00Z"
|
|
},
|
|
"reasoning": null
|
|
},
|
|
{
|
|
"date": "2025-01-16",
|
|
"model": "gpt-4",
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"starting_position": {
|
|
"holdings": [
|
|
{"symbol": "AAPL", "quantity": 10}
|
|
],
|
|
"cash": 8500.0,
|
|
"portfolio_value": 10100.0
|
|
},
|
|
"daily_metrics": {
|
|
"profit": 100.0,
|
|
"return_pct": 1.0,
|
|
"days_since_last_trading": 1
|
|
},
|
|
"trades": [
|
|
{
|
|
"action_type": "buy",
|
|
"symbol": "MSFT",
|
|
"quantity": 5,
|
|
"price": 200.0,
|
|
"created_at": "2025-01-16T14:30:00Z"
|
|
}
|
|
],
|
|
"final_position": {
|
|
"holdings": [
|
|
{"symbol": "AAPL", "quantity": 10},
|
|
{"symbol": "MSFT", "quantity": 5}
|
|
],
|
|
"cash": 7500.0,
|
|
"portfolio_value": 10100.0
|
|
},
|
|
"metadata": {
|
|
"total_actions": 1,
|
|
"session_duration_seconds": 52.1,
|
|
"completed_at": "2025-01-16T14:31:00Z"
|
|
},
|
|
"reasoning": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response (200 OK) - With Summary Reasoning:**
|
|
|
|
```json
|
|
{
|
|
"count": 1,
|
|
"results": [
|
|
{
|
|
"date": "2025-01-15",
|
|
"model": "gpt-4",
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"starting_position": {
|
|
"holdings": [],
|
|
"cash": 10000.0,
|
|
"portfolio_value": 10000.0
|
|
},
|
|
"daily_metrics": {
|
|
"profit": 0.0,
|
|
"return_pct": 0.0,
|
|
"days_since_last_trading": 0
|
|
},
|
|
"trades": [
|
|
{
|
|
"action_type": "buy",
|
|
"symbol": "AAPL",
|
|
"quantity": 10,
|
|
"price": 150.0,
|
|
"created_at": "2025-01-15T14:30:00Z"
|
|
}
|
|
],
|
|
"final_position": {
|
|
"holdings": [
|
|
{"symbol": "AAPL", "quantity": 10}
|
|
],
|
|
"cash": 8500.0,
|
|
"portfolio_value": 10000.0
|
|
},
|
|
"metadata": {
|
|
"total_actions": 1,
|
|
"session_duration_seconds": 45.2,
|
|
"completed_at": "2025-01-15T14:31:00Z"
|
|
},
|
|
"reasoning": "Analyzed AAPL earnings report showing strong Q4 results. Bought 10 shares at $150 based on positive revenue guidance and expanding margins."
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response (200 OK) - With Full Reasoning:**
|
|
|
|
```json
|
|
{
|
|
"count": 1,
|
|
"results": [
|
|
{
|
|
"date": "2025-01-15",
|
|
"model": "gpt-4",
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"starting_position": {
|
|
"holdings": [],
|
|
"cash": 10000.0,
|
|
"portfolio_value": 10000.0
|
|
},
|
|
"daily_metrics": {
|
|
"profit": 0.0,
|
|
"return_pct": 0.0,
|
|
"days_since_last_trading": 0
|
|
},
|
|
"trades": [
|
|
{
|
|
"action_type": "buy",
|
|
"symbol": "AAPL",
|
|
"quantity": 10,
|
|
"price": 150.0,
|
|
"created_at": "2025-01-15T14:30:00Z"
|
|
}
|
|
],
|
|
"final_position": {
|
|
"holdings": [
|
|
{"symbol": "AAPL", "quantity": 10}
|
|
],
|
|
"cash": 8500.0,
|
|
"portfolio_value": 10000.0
|
|
},
|
|
"metadata": {
|
|
"total_actions": 1,
|
|
"session_duration_seconds": 45.2,
|
|
"completed_at": "2025-01-15T14:31:00Z"
|
|
},
|
|
"reasoning": [
|
|
{
|
|
"role": "user",
|
|
"content": "You are a trading agent. Current date: 2025-01-15..."
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "I'll analyze market conditions for AAPL..."
|
|
},
|
|
{
|
|
"role": "tool",
|
|
"name": "search",
|
|
"content": "AAPL Q4 earnings beat expectations..."
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Based on positive earnings, I'll buy AAPL..."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response Fields:**
|
|
|
|
**Top-level:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `count` | integer | Number of trading days returned |
|
|
| `results` | array[object] | Array of day-level trading results |
|
|
|
|
**Day-level fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `date` | string | Trading date (YYYY-MM-DD) |
|
|
| `model` | string | Model signature |
|
|
| `job_id` | string | Simulation job UUID |
|
|
| `starting_position` | object | Portfolio state at start of day |
|
|
| `daily_metrics` | object | Daily performance metrics |
|
|
| `trades` | array[object] | All trades executed during the day |
|
|
| `final_position` | object | Portfolio state at end of day |
|
|
| `metadata` | object | Session metadata |
|
|
| `reasoning` | null\|string\|array | AI reasoning (based on `reasoning` parameter) |
|
|
|
|
**starting_position fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `holdings` | array[object] | Stock positions at start of day (from previous day's ending) |
|
|
| `cash` | float | Cash balance at start of day |
|
|
| `portfolio_value` | float | Total portfolio value at start (cash + holdings valued at current prices) |
|
|
|
|
**daily_metrics fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `profit` | float | Dollar amount gained/lost from previous close (portfolio appreciation/depreciation) |
|
|
| `return_pct` | float | Percentage return from previous close |
|
|
| `days_since_last_trading` | integer | Number of calendar days since last trading day (1=normal, 3=weekend, 0=first day) |
|
|
|
|
**trades fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `action_type` | string | Trade type: `buy`, `sell`, or `no_trade` |
|
|
| `symbol` | string\|null | Stock symbol (null for `no_trade`) |
|
|
| `quantity` | integer\|null | Number of shares (null for `no_trade`) |
|
|
| `price` | float\|null | Execution price per share (null for `no_trade`) |
|
|
| `created_at` | string | ISO 8601 timestamp of trade execution |
|
|
|
|
**final_position fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `holdings` | array[object] | Stock positions at end of day |
|
|
| `cash` | float | Cash balance at end of day |
|
|
| `portfolio_value` | float | Total portfolio value at end (cash + holdings valued at closing prices) |
|
|
|
|
**metadata fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `total_actions` | integer | Number of trades executed during the day |
|
|
| `session_duration_seconds` | float\|null | AI session duration in seconds |
|
|
| `completed_at` | string\|null | ISO 8601 timestamp of session completion |
|
|
|
|
**holdings object:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `symbol` | string | Stock symbol |
|
|
| `quantity` | integer | Number of shares held |
|
|
|
|
**reasoning field:**
|
|
- `null` when `reasoning=none` (default) - no reasoning included
|
|
- `string` when `reasoning=summary` - AI-generated 2-3 sentence summary of trading strategy
|
|
- `array` when `reasoning=full` - Complete conversation log with all messages, tool calls, and responses
|
|
|
|
**Daily P&L Calculation:**
|
|
|
|
Daily profit/loss is calculated by valuing the previous day's ending holdings at current day's opening prices:
|
|
|
|
1. **First trading day**: `daily_profit = 0`, `daily_return_pct = 0` (no previous holdings to appreciate/depreciate)
|
|
2. **Subsequent days**:
|
|
- Value yesterday's ending holdings at today's opening prices
|
|
- `daily_profit = today_portfolio_value - yesterday_portfolio_value`
|
|
- `daily_return_pct = (daily_profit / yesterday_portfolio_value) * 100`
|
|
|
|
This accurately captures portfolio appreciation from price movements, not just trading decisions.
|
|
|
|
**Weekend Gap Handling:**
|
|
|
|
The system correctly handles multi-day gaps (weekends, holidays):
|
|
- `days_since_last_trading` shows actual calendar days elapsed (e.g., 3 for Monday following Friday)
|
|
- Daily P&L reflects cumulative price changes over the gap period
|
|
- Holdings chain remains consistent (Monday starts with Friday's ending positions)
|
|
|
|
**Examples:**
|
|
|
|
All results for a specific job (no reasoning):
|
|
```bash
|
|
curl "http://localhost:8080/results?job_id=550e8400-e29b-41d4-a716-446655440000"
|
|
```
|
|
|
|
Results for a specific date with summary reasoning:
|
|
```bash
|
|
curl "http://localhost:8080/results?date=2025-01-16&reasoning=summary"
|
|
```
|
|
|
|
Results for a specific model with full reasoning:
|
|
```bash
|
|
curl "http://localhost:8080/results?model=gpt-4&reasoning=full"
|
|
```
|
|
|
|
Combine filters:
|
|
```bash
|
|
curl "http://localhost:8080/results?job_id=550e8400-e29b-41d4-a716-446655440000&date=2025-01-16&model=gpt-4&reasoning=summary"
|
|
```
|
|
|
|
---
|
|
|
|
### 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
|
|
- **trading_days** - Day-centric trading results with daily P&L metrics
|
|
- **holdings** - Portfolio holdings snapshots (ending positions only)
|
|
- **actions** - Trade execution ledger
|
|
- **tool_usage** - MCP tool usage statistics
|
|
- **price_data** - Historical price data cache
|
|
- **price_coverage** - Data availability tracking
|
|
|
|
See [docs/developer/database-schema.md](docs/developer/database-schema.md) for complete schema reference.
|
|
|
|
### 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
|
|
|
|
### Configuration Override System
|
|
|
|
**Default config:** `/app/configs/default_config.json` (baked into image)
|
|
|
|
**Custom config:** `/app/user-configs/config.json` (optional, via volume mount)
|
|
|
|
**Merge behavior:**
|
|
- Custom config sections completely replace default sections (root-level merge)
|
|
- If no custom config exists, defaults are used
|
|
- Validation occurs at container startup (before API starts)
|
|
- Invalid config causes immediate exit with detailed error message
|
|
|
|
**Example custom config** (overrides models only):
|
|
```json
|
|
{
|
|
"models": [
|
|
{"name": "gpt-5", "basemodel": "openai/gpt-5", "signature": "gpt-5", "enabled": true}
|
|
]
|
|
}
|
|
```
|
|
|
|
All other sections (`agent_config`, `log_config`, etc.) inherited from default.
|
|
|
|
---
|
|
|
|
## 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()
|
|
|
|
def get_reasoning(self, job_id=None, date=None, model=None, include_full_conversation=False):
|
|
"""Query reasoning logs with optional filters."""
|
|
params = {}
|
|
if job_id:
|
|
params["job_id"] = job_id
|
|
if date:
|
|
params["date"] = date
|
|
if model:
|
|
params["model"] = model
|
|
if include_full_conversation:
|
|
params["include_full_conversation"] = "true"
|
|
|
|
response = requests.get(f"{self.base_url}/reasoning", 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"])
|
|
|
|
# Get reasoning logs (summaries only)
|
|
reasoning = client.get_reasoning(job_id=job["job_id"])
|
|
|
|
# Get reasoning logs with full conversation
|
|
full_reasoning = client.get_reasoning(
|
|
job_id=job["job_id"],
|
|
date="2025-01-16",
|
|
include_full_conversation=True
|
|
)
|
|
```
|
|
|
|
### 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();
|
|
}
|
|
|
|
async getReasoning(filters: {
|
|
jobId?: string;
|
|
date?: string;
|
|
model?: string;
|
|
includeFullConversation?: boolean;
|
|
} = {}) {
|
|
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);
|
|
if (filters.includeFullConversation) params.set("include_full_conversation", "true");
|
|
|
|
const response = await fetch(
|
|
`${this.baseUrl}/reasoning?${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 });
|
|
|
|
// Get reasoning logs (summaries only)
|
|
const reasoning = await client.getReasoning({ jobId: job1.job_id });
|
|
|
|
// Get reasoning logs with full conversation
|
|
const fullReasoning = await client.getReasoning({
|
|
jobId: job1.job_id,
|
|
date: "2025-01-16",
|
|
includeFullConversation: true
|
|
});
|
|
```
|