Remove debug logging and update CHANGELOG for v0.4.2 release.
Fixed critical bug where trades calculated from initial $10,000 capital
instead of accumulating, allowing over-spending and negative cash balances.
Key changes:
- Extract position dict from CallToolResult.structuredContent
- Enable MCP service logging for better debugging
- Update tests to match production MCP behavior
All tests passing. Ready for production release.
Add debug logging to diagnose negative cash position issue where trades
calculate from initial $10,000 instead of accumulating.
Issue: After 11 trades, final cash shows -$8,768.68. Each trade appears
to calculate from $10,000 starting position instead of previous trade's
ending position.
Hypothesis: ContextInjector._current_position not updating after trades,
possibly due to MCP result type mismatch in isinstance(result, dict) check.
Debug logging added:
- agent/context_injector.py: Log MCP result type, content, and whether
_current_position updates after each trade
- agent_tools/tool_trade.py: Log whether injected position is used vs
DB query, and full contents of returned position dict
This will help identify:
1. What type is returned by MCP tool (dict vs other)
2. Whether _current_position is None on subsequent trades
3. What keys are present in returned position dicts
Related to issue where reasoning summary claims no trades executed
despite 4 sell orders being recorded.
- Remove job_id filter from get_current_position_from_db()
- Position queries now search across all jobs for the model
- Prevents portfolio reset when new jobs run overlapping dates
- Add test coverage for cross-job position continuity
Resolves issue where sell proceeds were not immediately available for
subsequent buy orders within the same trading session.
Problem:
- Both buy() and sell() independently queried database for starting position
- Multiple trades within same day all saw pre-trade cash balance
- Agents couldn't rebalance portfolios (sell + buy) in single session
Solution:
- ContextInjector maintains in-memory position state during trading session
- Position updates accumulate after each successful trade
- Position state injected into buy/sell via _current_position parameter
- Reset position state at start of each trading day
Changes:
- agent/context_injector.py: Add position tracking with reset_position()
- agent_tools/tool_trade.py: Accept _current_position in buy/sell functions
- agent/base_agent/base_agent.py: Reset position state daily
- tests: Add 13 comprehensive tests for position tracking
All new tests pass. Backward compatible, no schema changes required.
**Problem:**
Subsequent trading days were not retrieving starting holdings correctly.
The API showed empty starting_position and final_position even after
executing multiple buy trades.
**Root Cause:**
get_current_position_from_db() used `date <= ?` which returned the
CURRENT day's trading_day record instead of the PREVIOUS day's ending.
Since holdings are written at END of trading day, querying the current
day's record would return incomplete/empty holdings.
**Timeline on Day 1 (2025-10-02):**
1. Start: Create trading_day with empty holdings
2. Trade: Execute 8 buy trades (recorded in actions table)
3. End: Call get_current_position_from_db(date='2025-10-02')
- Query: `date <= 2025-10-02` returns TODAY's record
- Holdings: EMPTY (not written yet)
- Saves: Empty holdings to database ❌
**Solution:**
Changed query to use `date < ?` to retrieve PREVIOUS day's ending
position, which becomes the current day's starting position.
**Impact:**
- Day 1: Correctly saves ending holdings after trades
- Day 2+: Correctly retrieves previous day's ending as starting position
- Holdings now persist between trading days as expected
**Tests Added:**
- test_get_position_retrieves_previous_day_not_current: Verifies query
returns previous day when multiple days exist
- Updated existing tests to align with new behavior
Fixes holdings persistence bug identified in API response showing
empty starting_position/final_position despite successful trades.
Changes:
- Write TRADING_DAY_ID to runtime config after creating trading_day record in BaseAgent
- Fix datetime deprecation warnings by replacing datetime.utcnow() with datetime.now(timezone.utc)
- Add test for trading_day_id=None fallback path to verify runtime config lookup works correctly
This ensures trade tools can access trading_day_id from runtime config when not explicitly passed.
This commit implements Task 1 from the schema migration plan:
- Trade tools (buy/sell) now write to actions table instead of old positions table
- Added trading_day_id parameter to buy/sell functions
- Updated ContextInjector to inject trading_day_id
- Updated RuntimeConfigManager to include TRADING_DAY_ID in config
- Removed P&L calculation from trade functions (now done at trading_days level)
- Added tests verifying correct behavior with new schema
Changes:
- agent_tools/tool_trade.py: Modified _buy_impl and _sell_impl to write to actions table
- agent/context_injector.py: Added trading_day_id parameter and injection logic
- api/model_day_executor.py: Updated to read trading_day_id from runtime config
- api/runtime_manager.py: Added trading_day_id to config initialization
- tests/unit/test_trade_tools_new_schema.py: New tests for new schema compliance
All tests passing.
- Created comprehensive E2E test in tests/e2e/test_full_simulation_workflow.py
- Tests new trading_days schema with manually populated data
- Verifies database helper methods work correctly
- Tests Results API structure and filtering
- Validates holdings chain across multiple days
- Checks daily P&L calculation and storage
- Verifies reasoning summary/full retrieval
- Fixed database index creation for backward compatibility with old schema
- Added migration script for cleaning old positions table
- Test uses dependency override to ensure API uses correct database
NOTE: Test does not run full simulation since model_day_executor
has not yet been migrated to new schema. Instead directly populates
trading_days table and validates API layer works correctly.
Test verifies Task 9 requirements from implementation plan.
Previously, profit calculations compared portfolio value to the previous
day's final value. This caused trades to appear as losses since buying
stocks decreases cash and increases stock value equally (net zero change).
Now profit calculations compare to the start-of-day portfolio value
(action_id=0 for current date), which accurately reflects gains/losses
from price movements and trading decisions.
Changes:
- agent_tools/tool_trade.py: Fixed profit calc in _buy_impl() and _sell_impl()
- tools/price_tools.py: Fixed profit calc in add_no_trade_record_to_db()
Test: test_profit_calculation_accuracy now passes
Root cause: FastMCP uses inspect module to generate tool schemas from function
signatures. **kwargs prevents FastMCP from determining parameter types, causing
tool registration to fail.
Fix: Keep explicit parameters with defaults (signature=None, today_date=None, etc.)
but document in docstring that they are auto-injected.
This preserves:
- ContextInjector always overrides values (defense-in-depth from v0.3.0-alpha.40)
- FastMCP can generate proper tool schema
- Parameters visible to AI, but with clear documentation they're automatic
Trade-off: AI can still see the parameters, but documentation instructs not to provide them.
Combined with ContextInjector override, AI-provided values are ignored anyway.
Fixes TradeTools service crash on startup.
Prevent AI hallucination of runtime parameters by hiding them from the tool schema.
Architecture:
- Public tool functions (buy/sell) only expose symbol and amount to AI
- Use **kwargs to accept hidden parameters (signature, job_id, today_date, session_id)
- Internal _impl functions contain the actual business logic
- ContextInjector injects parameters into kwargs (invisible to AI)
Benefits:
- AI cannot see or hallucinate signature/job_id/session_id parameters
- Cleaner tool schema focuses on trading-relevant parameters only
- Defense-in-depth: ContextInjector still overrides any provided values
- More maintainable: clear separation of public API vs internal implementation
Example AI sees:
buy(symbol: str, amount: int) -> dict
Actual execution:
buy(symbol="AAPL", amount=10, signature="gpt-5", job_id="...", ...)
Fixes #TBD
The get_db_path() function requires a base_db_path argument
to properly resolve PROD vs DEV database paths. Updated all
calls to pass "data/jobs.db" as the base path.
Changes:
- agent_tools/tool_trade.py: Fix 3 occurrences (lines 33, 113, 236)
- tools/price_tools.py: Fix 2 occurrences in new database functions
- Remove unused get_db_path import from tool_trade.py
This fixes TypeError when running simulations:
get_db_path() missing 1 required positional argument: 'base_db_path'
The get_db_connection() function internally calls get_db_path()
to resolve the correct database path based on DEPLOYMENT_MODE.
Complete rewrite of position management in MCP trade tools:
**Trade Tools (agent_tools/tool_trade.py)**
- Replace file-based position.jsonl reads with SQLite queries
- Add get_current_position_from_db() to query positions and holdings tables
- Rewrite buy() and sell() to write directly to database
- Calculate portfolio value and P&L metrics in tools
- Accept job_id and session_id parameters via ContextInjector
- Return errors with proper context for debugging
- Use deployment-aware database path resolution
**Context Injection (agent/context_injector.py)**
- Add job_id and session_id to constructor
- Inject job_id and session_id into buy/sell tool calls
- Support optional parameters (None in standalone mode)
**BaseAgent (agent/base_agent/base_agent.py)**
- Read JOB_ID from runtime config
- Pass job_id to ContextInjector during initialization
- Enable automatic context injection for API mode
**ModelDayExecutor (api/model_day_executor.py)**
- Add _initialize_starting_position() method
- Create initial position record before agent runs
- Load initial_cash from config
- Update context_injector.session_id after session creation
- Link positions to sessions automatically
**Architecture Changes:**
- Eliminates file-based position tracking entirely
- Single source of truth: SQLite database
- Positions automatically linked to trading sessions
- Concurrent execution safe (no file system conflicts)
- Deployment mode aware (prod vs dev databases)
This completes the migration to database-only position storage.
File-based position.jsonl is no longer used or created.
Fixes context injection errors in concurrent simulations.
Add proper exception handling around get_latest_position() calls in both
buy() and sell() functions. Previously, exceptions were caught but code
continued execution with undefined variables, causing "variable referenced
before assignment" errors.
Now returns error dict with context when position lookup fails.
Related to context injection implementation for concurrent simulations.
Resolves issue where MCP trade tools couldn't access SIGNATURE and TODAY_DATE
during concurrent API simulations, causing "SIGNATURE environment variable is
not set" errors.
Problem:
- MCP services run as separate HTTP processes
- Multiple simulations execute concurrently via ThreadPoolExecutor
- Environment variables from executor process not accessible to MCP services
Solution:
- Add ContextInjector that implements ToolCallInterceptor
- Automatically injects signature and today_date into buy/sell tool calls
- Trade tools accept optional parameters, falling back to config/env
- BaseAgent creates interceptor and updates today_date per session
Changes:
- agent/context_injector.py: New interceptor for context injection
- agent/base_agent/base_agent.py: Create and use ContextInjector
- agent_tools/tool_trade.py: Add optional signature/today_date parameters
Benefits:
- Supports concurrent multi-model simulations
- Maintains backward compatibility with CLI mode
- AI model unaware of injected parameters