refactor: implement database-only position tracking with lazy context injection

This commit migrates the system to database-only position storage,
eliminating file-based position.jsonl dependencies and fixing
ContextInjector initialization timing issues.

Key Changes:

1. ContextInjector Lifecycle Refactor:
   - Remove ContextInjector creation from BaseAgent.__init__()
   - Add BaseAgent.set_context() method for post-initialization injection
   - Update ModelDayExecutor to create ContextInjector with correct trading day date
   - Ensures ContextInjector receives actual trading date instead of init_date
   - Includes session_id injection for proper database linking

2. Database Position Functions:
   - Implement get_today_init_position_from_db() for querying previous positions
   - Implement add_no_trade_record_to_db() for no-trade day handling
   - Both functions query SQLite directly (positions + holdings tables)
   - Handle first trading day case with initial cash return
   - Include comprehensive error handling and logging

3. System Integration:
   - Update get_agent_system_prompt() to use database queries
   - Update _handle_trading_result() to write no-trade records to database
   - Remove dependencies on position.jsonl file reading/writing
   - Use deployment_config for automatic prod/dev database resolution

Data Flow:
- ModelDayExecutor creates runtime config and trading session
- Agent initialized without context
- ContextInjector created with (signature, date, job_id, session_id)
- Context injected via set_context()
- System prompt queries database for yesterday's position
- Trade tools write directly to database
- No-trade handler creates database records

Fixes:
- ContextInjector no longer receives None values
- No FileNotFoundError for missing position.jsonl files
- Database is single source of truth for position tracking
- Session linking maintained across all position records

Design: docs/plans/2025-02-11-database-position-tracking-design.md
This commit is contained in:
2025-11-02 22:20:01 -05:00
parent 7a734d265b
commit 027b4bd8e4
4 changed files with 239 additions and 26 deletions

View File

@@ -173,21 +173,13 @@ class BaseAgent:
print("⚠️ OpenAI base URL not set, using default")
try:
# Get job_id from runtime config if available (API mode)
from tools.general_tools import get_config_value
job_id = get_config_value("JOB_ID") # Returns None if not in API mode
# Context injector will be set later via set_context() method
self.context_injector = None
# Create context injector for injecting signature and today_date into tool calls
self.context_injector = ContextInjector(
signature=self.signature,
today_date=self.init_date, # Will be updated per trading session
job_id=job_id # Will be None in standalone mode, populated in API mode
)
# Create MCP client with interceptor
# Create MCP client without interceptors initially
self.client = MultiServerMCPClient(
self.mcp_config,
tool_interceptors=[self.context_injector]
tool_interceptors=[]
)
# Get tools
@@ -229,6 +221,30 @@ class BaseAgent:
print(f"✅ Agent {self.signature} initialization completed")
def set_context(self, context_injector: "ContextInjector") -> None:
"""
Inject ContextInjector after initialization.
This allows the ContextInjector to be created with the correct
trading day date and session_id after the agent is initialized.
Args:
context_injector: Configured ContextInjector instance with
correct signature, today_date, job_id, session_id
"""
self.context_injector = context_injector
# Recreate MCP client with the interceptor
# Note: We need to recreate because MultiServerMCPClient doesn't have add_interceptor()
self.client = MultiServerMCPClient(
self.mcp_config,
tool_interceptors=[context_injector]
)
print(f"✅ Context injected: signature={context_injector.signature}, "
f"date={context_injector.today_date}, job_id={context_injector.job_id}, "
f"session_id={context_injector.session_id}")
def _capture_message(self, role: str, content: str, tool_name: str = None, tool_input: str = None) -> None:
"""
Capture a message in conversation history.
@@ -429,18 +445,32 @@ Summary:"""
await self._handle_trading_result(today_date)
async def _handle_trading_result(self, today_date: str) -> None:
"""Handle trading results"""
"""Handle trading results with database writes."""
from tools.price_tools import add_no_trade_record_to_db
if_trade = get_config_value("IF_TRADE")
if if_trade:
write_config_value("IF_TRADE", False)
print("✅ Trading completed")
else:
print("📊 No trading, maintaining positions")
try:
add_no_trade_record(today_date, self.signature)
except NameError as e:
print(f"❌ NameError: {e}")
raise
# Get context from runtime config
job_id = get_config_value("JOB_ID")
session_id = self.context_injector.session_id if self.context_injector else None
if not job_id or not session_id:
raise ValueError("Missing JOB_ID or session_id for no-trade record")
# Write no-trade record to database
add_no_trade_record_to_db(
today_date,
self.signature,
job_id,
session_id
)
write_config_value("IF_TRADE", False)
def register_agent(self) -> None: