feat: add new day-centric results API endpoint

Implements new /results endpoint with day-centric data structure:
- Returns starting_position, daily_metrics, trades, and final_position
- Supports reasoning levels: none (default), summary, full
- Uses database helper methods from trading_days schema
- Replaces old positions-based endpoint

Changes:
- Created api/routes/results_v2.py with new endpoint
- Registered router in api/main.py
- Removed old /results endpoint (positions table)
- Added comprehensive integration tests

All tests pass.
This commit is contained in:
2025-11-03 23:43:52 -05:00
parent f770a2fe84
commit 93ba9deebb
4 changed files with 257 additions and 102 deletions

1
api/routes/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""API routes package."""

112
api/routes/results_v2.py Normal file
View File

@@ -0,0 +1,112 @@
"""New results API with day-centric structure."""
from fastapi import APIRouter, Query, Depends
from typing import Optional, Literal
import json
from api.database import Database
router = APIRouter()
def get_database() -> Database:
"""Dependency for database instance."""
return Database()
@router.get("/results")
async def get_results(
job_id: Optional[str] = None,
model: Optional[str] = None,
date: Optional[str] = None,
reasoning: Literal["none", "summary", "full"] = "none",
db: Database = Depends(get_database)
):
"""Get trading results grouped by day.
Args:
job_id: Filter by simulation job ID
model: Filter by model signature
date: Filter by trading date (YYYY-MM-DD)
reasoning: Include reasoning logs (none/summary/full)
db: Database instance (injected)
Returns:
JSON with day-centric trading results and performance metrics
"""
# Build query with filters
query = "SELECT * FROM trading_days WHERE 1=1"
params = []
if job_id:
query += " AND job_id = ?"
params.append(job_id)
if model:
query += " AND model = ?"
params.append(model)
if date:
query += " AND date = ?"
params.append(date)
query += " ORDER BY date ASC, model ASC"
# Execute query
cursor = db.connection.execute(query, params)
# Format results
formatted_results = []
for row in cursor.fetchall():
trading_day_id = row[0]
# Build response object
day_data = {
"date": row[3],
"model": row[2],
"job_id": row[1],
"starting_position": {
"holdings": db.get_starting_holdings(trading_day_id),
"cash": row[4], # starting_cash
"portfolio_value": row[5] # starting_portfolio_value
},
"daily_metrics": {
"profit": row[6], # daily_profit
"return_pct": row[7], # daily_return_pct
"days_since_last_trading": row[14] if len(row) > 14 else 1
},
"trades": db.get_actions(trading_day_id),
"final_position": {
"holdings": db.get_ending_holdings(trading_day_id),
"cash": row[8], # ending_cash
"portfolio_value": row[9] # ending_portfolio_value
},
"metadata": {
"total_actions": row[12] if row[12] is not None else 0,
"session_duration_seconds": row[13],
"completed_at": row[16] if len(row) > 16 else None
}
}
# Add reasoning if requested
if reasoning == "summary":
day_data["reasoning"] = row[10] # reasoning_summary
elif reasoning == "full":
reasoning_full = row[11] # reasoning_full
day_data["reasoning"] = json.loads(reasoning_full) if reasoning_full else []
else:
day_data["reasoning"] = None
formatted_results.append(day_data)
return {
"count": len(formatted_results),
"results": formatted_results
}