docs: update /results endpoint documentation for date range support

Update API_REFERENCE.md to reflect the new date range query functionality
in the /results endpoint:

- Replace 'date' parameter with 'start_date' and 'end_date'
- Document single-date vs date range response formats
- Add period metrics calculations (period return, annualized return)
- Document default behavior (last 30 days)
- Update error responses for new validation rules
- Update Python and TypeScript client examples
- Add edge trimming behavior documentation
This commit is contained in:
2025-11-07 19:34:43 -05:00
parent 2612b85431
commit c62c01e701

View File

@@ -343,70 +343,43 @@ 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.
Get trading results with optional date range and portfolio performance metrics.
**Query Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `job_id` | string | No | Filter by job UUID |
| `date` | string | No | Filter by trading date (YYYY-MM-DD) |
| `start_date` | string | No | Start date (YYYY-MM-DD). If provided alone, acts as single date. If omitted, defaults to 30 days ago. |
| `end_date` | string | No | End date (YYYY-MM-DD). If provided alone, acts as single date. If omitted, defaults to today. |
| `model` | string | No | Filter by model signature |
| `reasoning` | string | No | Include AI reasoning: `none` (default), `summary`, or `full` |
| `job_id` | string | No | Filter by job UUID |
| `reasoning` | string | No | Include reasoning: `none` (default), `summary`, or `full`. Ignored for date range queries. |
**Response (200 OK) - Default (no reasoning):**
**Breaking Change:**
- The `date` parameter has been removed. Use `start_date` and/or `end_date` instead.
- Requests using `date` will receive `422 Unprocessable Entity` error.
**Default Behavior:**
- If no dates provided: Returns last 30 days (configurable via `DEFAULT_RESULTS_LOOKBACK_DAYS`)
- If only `start_date`: Single-date query (end_date = start_date)
- If only `end_date`: Single-date query (start_date = end_date)
- If both provided and equal: Single-date query (detailed format)
- If both provided and different: Date range query (metrics format)
**Response - Single Date (detailed):**
```json
{
"count": 2,
"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": null
},
{
"date": "2025-01-16",
"model": "gpt-4",
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"job_id": "550e8400-...",
"starting_position": {
"holdings": [
{"symbol": "AAPL", "quantity": 10}
],
"holdings": [{"symbol": "AAPL", "quantity": 10}],
"cash": 8500.0,
"portfolio_value": 10100.0
"portfolio_value": 10000.0
},
"daily_metrics": {
"profit": 100.0,
@@ -441,226 +414,79 @@ Get trading results grouped by day with daily P&L metrics and AI reasoning.
}
```
**Response (200 OK) - With Summary Reasoning:**
**Response - Date Range (metrics):**
```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"
}
"start_date": "2025-01-16",
"end_date": "2025-01-20",
"daily_portfolio_values": [
{"date": "2025-01-16", "portfolio_value": 10100.0},
{"date": "2025-01-17", "portfolio_value": 10250.0},
{"date": "2025-01-20", "portfolio_value": 10500.0}
],
"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."
"period_metrics": {
"starting_portfolio_value": 10000.0,
"ending_portfolio_value": 10500.0,
"period_return_pct": 5.0,
"annualized_return_pct": 45.6,
"calendar_days": 5,
"trading_days": 3
}
}
]
}
```
**Response (200 OK) - With Full Reasoning:**
**Period Metrics Calculations:**
```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..."
}
]
}
]
}
```
- `period_return_pct` = ((ending - starting) / starting) × 100
- `annualized_return_pct` = ((ending / starting) ^ (365 / calendar_days) - 1) × 100
- `calendar_days` = Calendar days from start_date to end_date (inclusive)
- `trading_days` = Number of actual trading days with data
**Response Fields:**
**Edge Trimming:**
**Top-level:**
| Field | Type | Description |
|-------|------|-------------|
| `count` | integer | Number of trading days returned |
| `results` | array[object] | Array of day-level trading results |
If requested range extends beyond available data, the response is trimmed to actual data boundaries:
**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) |
- Request: `start_date=2025-01-10&end_date=2025-01-20`
- Available: 2025-01-15, 2025-01-16, 2025-01-17
- Response: `start_date=2025-01-15`, `end_date=2025-01-17`
**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) |
**Error Responses:**
**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)
| Status | Scenario | Response |
|--------|----------|----------|
| 404 | No data matches filters | `{"detail": "No trading data found for the specified filters"}` |
| 400 | Invalid date format | `{"detail": "Invalid date format. Expected YYYY-MM-DD"}` |
| 400 | start_date > end_date | `{"detail": "start_date must be <= end_date"}` |
| 400 | Future dates | `{"detail": "Cannot query future dates"}` |
| 422 | Using old `date` param | `{"detail": "Parameter 'date' has been removed. Use 'start_date' and/or 'end_date' instead."}` |
**Examples:**
All results for a specific job (no reasoning):
Single date query:
```bash
curl "http://localhost:8080/results?job_id=550e8400-e29b-41d4-a716-446655440000"
curl "http://localhost:8080/results?start_date=2025-01-16&model=gpt-4"
```
Results for a specific date with summary reasoning:
Date range query:
```bash
curl "http://localhost:8080/results?date=2025-01-16&reasoning=summary"
curl "http://localhost:8080/results?start_date=2025-01-16&end_date=2025-01-20&model=gpt-4"
```
Results for a specific model with full reasoning:
Default (last 30 days):
```bash
curl "http://localhost:8080/results?model=gpt-4&reasoning=full"
curl "http://localhost:8080/results"
```
Combine filters:
With filters:
```bash
curl "http://localhost:8080/results?job_id=550e8400-e29b-41d4-a716-446655440000&date=2025-01-16&model=gpt-4&reasoning=summary"
curl "http://localhost:8080/results?job_id=550e8400-...&start_date=2025-01-16&end_date=2025-01-20"
```
---
@@ -1049,13 +875,23 @@ class AITraderServerClient:
return status
time.sleep(poll_interval)
def get_results(self, job_id=None, date=None, model=None):
"""Query results with optional filters."""
params = {}
def get_results(self, start_date=None, end_date=None, job_id=None, model=None, reasoning="none"):
"""Query results with optional filters and date range.
Args:
start_date: Start date (YYYY-MM-DD) or None
end_date: End date (YYYY-MM-DD) or None
job_id: Job ID filter
model: Model signature filter
reasoning: Reasoning level (none/summary/full)
"""
params = {"reasoning": reasoning}
if start_date:
params["start_date"] = start_date
if end_date:
params["end_date"] = end_date
if job_id:
params["job_id"] = job_id
if date:
params["date"] = date
if model:
params["model"] = model
@@ -1095,6 +931,13 @@ job = client.trigger_simulation(end_date="2025-01-31", models=["gpt-4"])
result = client.wait_for_completion(job["job_id"])
results = client.get_results(job_id=job["job_id"])
# Get results for date range
range_results = client.get_results(
start_date="2025-01-16",
end_date="2025-01-20",
model="gpt-4"
)
# Get reasoning logs (summaries only)
reasoning = client.get_reasoning(job_id=job["job_id"])
@@ -1161,13 +1004,17 @@ class AITraderServerClient {
async getResults(filters: {
jobId?: string;
date?: string;
startDate?: string;
endDate?: string;
model?: string;
reasoning?: string;
} = {}) {
const params = new URLSearchParams();
if (filters.jobId) params.set("job_id", filters.jobId);
if (filters.date) params.set("date", filters.date);
if (filters.startDate) params.set("start_date", filters.startDate);
if (filters.endDate) params.set("end_date", filters.endDate);
if (filters.model) params.set("model", filters.model);
if (filters.reasoning) params.set("reasoning", filters.reasoning);
const response = await fetch(
`${this.baseUrl}/results?${params.toString()}`
@@ -1220,6 +1067,13 @@ const job3 = await client.triggerSimulation("2025-01-31", {
const result = await client.waitForCompletion(job1.job_id);
const results = await client.getResults({ jobId: job1.job_id });
// Get results for date range
const rangeResults = await client.getResults({
startDate: "2025-01-16",
endDate: "2025-01-20",
model: "gpt-4"
});
// Get reasoning logs (summaries only)
const reasoning = await client.getReasoning({ jobId: job1.job_id });