diff --git a/API_REFERENCE.md b/API_REFERENCE.md index 9f70a53..2357578 100644 --- a/API_REFERENCE.md +++ b/API_REFERENCE.md @@ -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 });