From eae310e6ce38905eaac226cdf3dba16dfd6a7132 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 4 Nov 2025 07:33:20 -0500 Subject: [PATCH] docs: update API reference and database schema for new results endpoint --- API_REFERENCE.md | 331 ++++++++++++++++++++++++------ docs/developer/database-schema.md | 177 ++++++++++++---- 2 files changed, 404 insertions(+), 104 deletions(-) diff --git a/API_REFERENCE.md b/API_REFERENCE.md index 68c42c3..7aaa67b 100644 --- a/API_REFERENCE.md +++ b/API_REFERENCE.md @@ -343,7 +343,7 @@ Poll every 10-30 seconds until `status` is `completed`, `partial`, or `failed`. ### GET /results -Query simulation results with optional filters. +Get trading results grouped by day with daily P&L metrics and AI reasoning. **Query Parameters:** @@ -352,112 +352,315 @@ Query simulation results with optional filters. | `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):** +**Response (200 OK) - Default (no reasoning):** ```json { + "count": 2, "results": [ { - "id": 1, - "job_id": "550e8400-e29b-41d4-a716-446655440000", - "date": "2025-01-16", + "date": "2025-01-15", "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} - ] + "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 }, { - "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} + "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..." + } ] } - ], - "count": 2 + ] } ``` **Response Fields:** +**Top-level:** | Field | Type | Description | |-------|------|-------------| -| `results` | array[object] | Array of position records | -| `count` | integer | Number of results returned | - -**Position Record Fields:** +| `count` | integer | Number of trading days returned | +| `results` | array[object] | Array of day-level trading results | +**Day-level 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:** +| `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 | |-------|------|-------------| -| `symbol` | string | Stock symbol or "CASH" | -| `quantity` | float | Shares owned (or cash amount) | +| `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: +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: +Results for a specific date with summary reasoning: ```bash -curl "http://localhost:8080/results?date=2025-01-16" +curl "http://localhost:8080/results?date=2025-01-16&reasoning=summary" ``` -Results for a specific model: +Results for a specific model with full reasoning: ```bash -curl "http://localhost:8080/results?model=gpt-4" +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" +curl "http://localhost:8080/results?job_id=550e8400-e29b-41d4-a716-446655440000&date=2025-01-16&model=gpt-4&reasoning=summary" ``` --- diff --git a/docs/developer/database-schema.md b/docs/developer/database-schema.md index 0834299..746c01b 100644 --- a/docs/developer/database-schema.md +++ b/docs/developer/database-schema.md @@ -42,26 +42,45 @@ CREATE TABLE job_details ( ); ``` -### positions -Trading position records with P&L. +### trading_days + +Core table for each model-day execution with daily P&L metrics. ```sql -CREATE TABLE positions ( +CREATE TABLE trading_days ( id INTEGER PRIMARY KEY AUTOINCREMENT, - job_id TEXT, - date TEXT, - model TEXT, - action_id INTEGER, - action_type TEXT, - symbol TEXT, - amount INTEGER, - price REAL, - cash REAL, - portfolio_value REAL, - daily_profit REAL, - daily_return_pct REAL, - created_at TEXT + job_id TEXT NOT NULL, + model TEXT NOT NULL, + date TEXT NOT NULL, + + -- Starting position (cash only, holdings from previous day) + starting_cash REAL NOT NULL, + starting_portfolio_value REAL NOT NULL, + + -- Daily performance metrics + daily_profit REAL NOT NULL, + daily_return_pct REAL NOT NULL, + + -- Ending state (cash only, holdings in separate table) + ending_cash REAL NOT NULL, + ending_portfolio_value REAL NOT NULL, + + -- Reasoning + reasoning_summary TEXT, + reasoning_full TEXT, + + -- Metadata + total_actions INTEGER DEFAULT 0, + session_duration_seconds REAL, + days_since_last_trading INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP, + + UNIQUE(job_id, model, date), + FOREIGN KEY (job_id) REFERENCES jobs(job_id) ); + +CREATE INDEX idx_trading_days_lookup ON trading_days(job_id, model, date); ``` **Column Descriptions:** @@ -70,45 +89,123 @@ CREATE TABLE positions ( |--------|------|-------------| | id | INTEGER | Primary key, auto-incremented | | job_id | TEXT | Foreign key to jobs table | -| date | TEXT | Trading date (YYYY-MM-DD) | | model | TEXT | Model signature/identifier | -| action_id | INTEGER | Sequential action ID for the day (0 = start-of-day baseline) | -| action_type | TEXT | Type of action: 'no_trade', 'buy', or 'sell' | -| symbol | TEXT | Stock symbol (null for no_trade) | -| amount | INTEGER | Number of shares traded (null for no_trade) | -| price | REAL | Price per share (null for no_trade) | -| cash | REAL | Cash balance after action | -| portfolio_value | REAL | Total portfolio value (cash + holdings value) | -| daily_profit | REAL | **Daily profit/loss compared to start-of-day portfolio value (action_id=0).** Calculated as: `current_portfolio_value - start_of_day_portfolio_value`. This shows the actual gain/loss from price movements and trading decisions, not affected by merely buying/selling stocks. | -| daily_return_pct | REAL | **Daily return percentage compared to start-of-day portfolio value.** Calculated as: `(daily_profit / start_of_day_portfolio_value) * 100` | -| created_at | TEXT | ISO 8601 timestamp with 'Z' suffix | +| date | TEXT | Trading date (YYYY-MM-DD) | +| starting_cash | REAL | Cash balance at start of day | +| starting_portfolio_value | REAL | Total portfolio value at start (includes holdings valued at current prices) | +| daily_profit | REAL | Dollar P&L from previous close (portfolio appreciation/depreciation) | +| daily_return_pct | REAL | Percentage return from previous close | +| ending_cash | REAL | Cash balance at end of day | +| ending_portfolio_value | REAL | Total portfolio value at end | +| reasoning_summary | TEXT | AI-generated 2-3 sentence summary of trading strategy | +| reasoning_full | TEXT | JSON array of complete conversation log | +| total_actions | INTEGER | Number of trades executed during the day | +| session_duration_seconds | REAL | AI session duration in seconds | +| days_since_last_trading | INTEGER | Days since previous trading day (1=normal, 3=weekend, 0=first day) | +| created_at | TIMESTAMP | Record creation timestamp | +| completed_at | TIMESTAMP | Session completion timestamp | **Important Notes:** -- **Position tracking flow:** Positions are written by trade tools (`buy()`, `sell()` in `agent_tools/tool_trade.py`) and no-trade records (`add_no_trade_record_to_db()` in `tools/price_tools.py`). Each trade creates a new position record. +- **Day-centric structure:** Each row represents one complete trading day for one model +- **First trading day:** `daily_profit = 0`, `daily_return_pct = 0`, `days_since_last_trading = 0` +- **Subsequent days:** Daily P&L calculated by valuing previous day's holdings at current prices +- **Weekend gaps:** System handles multi-day gaps automatically (e.g., Monday following Friday shows `days_since_last_trading = 3`) +- **Starting holdings:** Derived from previous day's ending holdings (not stored in this table, see `holdings` table) +- **Unique constraint:** One record per (job_id, model, date) combination -- **Action ID sequence:** - - `action_id=0`: Start-of-day position (created by `ModelDayExecutor._initialize_starting_position()` on first day only) - - `action_id=1+`: Each trade or no-trade action increments the action_id +**Daily P&L Calculation:** -- **Profit calculation:** Daily profit is calculated by comparing current portfolio value to the **start-of-day** portfolio value (action_id=0 for the current date). This ensures that: - - Buying stocks doesn't show as a loss (cash ↓, stock value ↑ equally) - - Selling stocks doesn't show as a gain (cash ↑, stock value ↓ equally) - - Only actual price movements and strategic trading show as profit/loss +Daily profit accurately reflects portfolio appreciation from price movements: + +1. Get previous day's ending holdings and cash +2. Value those holdings at current day's opening prices +3. `daily_profit = current_value - previous_value` +4. `daily_return_pct = (daily_profit / previous_value) * 100` + +This ensures buying/selling stocks doesn't affect P&L - only price changes do. + +--- ### holdings -Portfolio holdings breakdown per position. + +Portfolio holdings snapshots (ending positions only). ```sql CREATE TABLE holdings ( id INTEGER PRIMARY KEY AUTOINCREMENT, - position_id INTEGER, - symbol TEXT, - quantity REAL, - FOREIGN KEY (position_id) REFERENCES positions(id) ON DELETE CASCADE + trading_day_id INTEGER NOT NULL, + symbol TEXT NOT NULL, + quantity INTEGER NOT NULL, + + FOREIGN KEY (trading_day_id) REFERENCES trading_days(id) ON DELETE CASCADE, + UNIQUE(trading_day_id, symbol) ); + +CREATE INDEX idx_holdings_day ON holdings(trading_day_id); ``` +**Column Descriptions:** + +| Column | Type | Description | +|--------|------|-------------| +| id | INTEGER | Primary key, auto-incremented | +| trading_day_id | INTEGER | Foreign key to trading_days table | +| symbol | TEXT | Stock symbol | +| quantity | INTEGER | Number of shares held at end of day | + +**Important Notes:** + +- **Ending positions only:** This table stores only the final holdings at end of day +- **Starting positions:** Derived by querying holdings for previous day's trading_day_id +- **Cascade deletion:** Holdings are automatically deleted when parent trading_day is deleted +- **Unique constraint:** One row per (trading_day_id, symbol) combination +- **No cash:** Cash is stored directly in trading_days table (`ending_cash`) + +--- + +### actions + +Trade execution ledger. + +```sql +CREATE TABLE actions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + trading_day_id INTEGER NOT NULL, + + action_type TEXT NOT NULL, + symbol TEXT, + quantity INTEGER, + price REAL, + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + FOREIGN KEY (trading_day_id) REFERENCES trading_days(id) ON DELETE CASCADE +); + +CREATE INDEX idx_actions_day ON actions(trading_day_id); +``` + +**Column Descriptions:** + +| Column | Type | Description | +|--------|------|-------------| +| id | INTEGER | Primary key, auto-incremented | +| trading_day_id | INTEGER | Foreign key to trading_days table | +| action_type | TEXT | Trade type: 'buy', 'sell', or 'no_trade' | +| symbol | TEXT | Stock symbol (NULL for no_trade) | +| quantity | INTEGER | Number of shares traded (NULL for no_trade) | +| price | REAL | Execution price per share (NULL for no_trade) | +| created_at | TIMESTAMP | Timestamp of trade execution | + +**Important Notes:** + +- **Trade ledger:** Sequential log of all trades executed during a trading day +- **No_trade actions:** Recorded when agent decides not to trade +- **Cascade deletion:** Actions are automatically deleted when parent trading_day is deleted +- **Execution order:** Use `created_at` to determine trade execution sequence +- **Price snapshot:** Records actual execution price at time of trade + ### price_data Cached historical price data.