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 /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:** **Query Parameters:**
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| |-----------|------|----------|-------------|
| `job_id` | string | No | Filter by job UUID | | `start_date` | string | No | Start date (YYYY-MM-DD). If provided alone, acts as single date. If omitted, defaults to 30 days ago. |
| `date` | string | No | Filter by trading date (YYYY-MM-DD) | | `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 | | `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 ```json
{ {
"count": 2, "count": 1,
"results": [ "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", "date": "2025-01-16",
"model": "gpt-4", "model": "gpt-4",
"job_id": "550e8400-e29b-41d4-a716-446655440000", "job_id": "550e8400-...",
"starting_position": { "starting_position": {
"holdings": [ "holdings": [{"symbol": "AAPL", "quantity": 10}],
{"symbol": "AAPL", "quantity": 10}
],
"cash": 8500.0, "cash": 8500.0,
"portfolio_value": 10100.0 "portfolio_value": 10000.0
}, },
"daily_metrics": { "daily_metrics": {
"profit": 100.0, "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 ```json
{ {
"count": 1, "count": 1,
"results": [ "results": [
{ {
"date": "2025-01-15",
"model": "gpt-4", "model": "gpt-4",
"job_id": "550e8400-e29b-41d4-a716-446655440000", "start_date": "2025-01-16",
"starting_position": { "end_date": "2025-01-20",
"holdings": [], "daily_portfolio_values": [
"cash": 10000.0, {"date": "2025-01-16", "portfolio_value": 10100.0},
"portfolio_value": 10000.0 {"date": "2025-01-17", "portfolio_value": 10250.0},
}, {"date": "2025-01-20", "portfolio_value": 10500.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": { "period_metrics": {
"holdings": [ "starting_portfolio_value": 10000.0,
{"symbol": "AAPL", "quantity": 10} "ending_portfolio_value": 10500.0,
], "period_return_pct": 5.0,
"cash": 8500.0, "annualized_return_pct": 45.6,
"portfolio_value": 10000.0 "calendar_days": 5,
}, "trading_days": 3
"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:** **Period Metrics Calculations:**
```json - `period_return_pct` = ((ending - starting) / starting) × 100
{ - `annualized_return_pct` = ((ending / starting) ^ (365 / calendar_days) - 1) × 100
"count": 1, - `calendar_days` = Calendar days from start_date to end_date (inclusive)
"results": [ - `trading_days` = Number of actual trading days with data
{
"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:** **Edge Trimming:**
**Top-level:** If requested range extends beyond available data, the response is trimmed to actual data boundaries:
| Field | Type | Description |
|-------|------|-------------|
| `count` | integer | Number of trading days returned |
| `results` | array[object] | Array of day-level trading results |
**Day-level fields:** - Request: `start_date=2025-01-10&end_date=2025-01-20`
| Field | Type | Description | - Available: 2025-01-15, 2025-01-16, 2025-01-17
|-------|------|-------------| - Response: `start_date=2025-01-15`, `end_date=2025-01-17`
| `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:** **Error Responses:**
| 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:** | Status | Scenario | Response |
| Field | Type | Description | |--------|----------|----------|
|-------|------|-------------| | 404 | No data matches filters | `{"detail": "No trading data found for the specified filters"}` |
| `profit` | float | Dollar amount gained/lost from previous close (portfolio appreciation/depreciation) | | 400 | Invalid date format | `{"detail": "Invalid date format. Expected YYYY-MM-DD"}` |
| `return_pct` | float | Percentage return from previous close | | 400 | start_date > end_date | `{"detail": "start_date must be <= end_date"}` |
| `days_since_last_trading` | integer | Number of calendar days since last trading day (1=normal, 3=weekend, 0=first day) | | 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."}` |
**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:** **Examples:**
All results for a specific job (no reasoning): Single date query:
```bash ```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 ```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 ```bash
curl "http://localhost:8080/results?model=gpt-4&reasoning=full" curl "http://localhost:8080/results"
``` ```
Combine filters: With filters:
```bash ```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 return status
time.sleep(poll_interval) time.sleep(poll_interval)
def get_results(self, job_id=None, date=None, model=None): def get_results(self, start_date=None, end_date=None, job_id=None, model=None, reasoning="none"):
"""Query results with optional filters.""" """Query results with optional filters and date range.
params = {}
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: if job_id:
params["job_id"] = job_id params["job_id"] = job_id
if date:
params["date"] = date
if model: if model:
params["model"] = 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"]) result = client.wait_for_completion(job["job_id"])
results = client.get_results(job_id=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) # Get reasoning logs (summaries only)
reasoning = client.get_reasoning(job_id=job["job_id"]) reasoning = client.get_reasoning(job_id=job["job_id"])
@@ -1161,13 +1004,17 @@ class AITraderServerClient {
async getResults(filters: { async getResults(filters: {
jobId?: string; jobId?: string;
date?: string; startDate?: string;
endDate?: string;
model?: string; model?: string;
reasoning?: string;
} = {}) { } = {}) {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (filters.jobId) params.set("job_id", filters.jobId); 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.model) params.set("model", filters.model);
if (filters.reasoning) params.set("reasoning", filters.reasoning);
const response = await fetch( const response = await fetch(
`${this.baseUrl}/results?${params.toString()}` `${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 result = await client.waitForCompletion(job1.job_id);
const results = await client.getResults({ jobId: 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) // Get reasoning logs (summaries only)
const reasoning = await client.getReasoning({ jobId: job1.job_id }); const reasoning = await client.getReasoning({ jobId: job1.job_id });