35 Commits

Author SHA1 Message Date
e20dce7432 fix: enable intra-day position tracking for sell-then-buy trades
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.
2025-11-05 06:56:54 -05:00
462de3adeb fix: extract tool messages before checking FINISH_SIGNAL
**Critical Bug:**
When agent returns FINISH_SIGNAL, the code breaks immediately (line 640)
BEFORE extracting tool messages (lines 642-650). This caused tool messages
to never be captured when agent completes in single step.

**Timeline:**
1. Agent calls buy tools (MSFT, AMZN, NVDA)
2. Agent returns response with <FINISH_SIGNAL>
3. Code detects signal → break (line 640)
4. Lines 642-650 NEVER EXECUTE
5. Tool messages not captured → summarizer sees 0 tools

**Evidence from logs:**
- Console: 'Bought NVDA 10 shares'
- API: 3 trades executed (MSFT 5, AMZN 15, NVDA 10)
- Debug: 'Tool messages: 0' 

**Fix:**
Move tool extraction BEFORE stop signal check.
Agent can call tools AND return FINISH_SIGNAL in same response,
so we must process tools first.

**Impact:**
Now tool messages will be captured even when agent finishes in
single step. Summarizer will see actual trades executed.

This is the true root cause of empty tool messages in conversation_history.
2025-11-05 00:57:22 -05:00
31e346ecbb debug: add logging to verify conversation history capture
Added debug output to confirm:
- How many messages are in conversation_history
- How many assistant vs tool messages
- Preview of first assistant message content
- What the summarizer receives

This will verify that the full detailed reasoning (like portfolio
analysis, trade execution details) is being captured and passed
to the summarizer.

Output will show:
[DEBUG] Generating summary from N messages
[DEBUG] Assistant messages: X, Tool messages: Y
[DEBUG] First assistant message preview: ...
[DEBUG ReasoningSummarizer] Formatting N messages
[DEBUG ReasoningSummarizer] Breakdown: X assistant, Y tool
2025-11-05 00:46:30 -05:00
abb9cd0726 fix: capture tool messages in conversation history for summarizer
**Root Cause:**
The summarizer was not receiving tool execution results (buy/sell trades)
because they were never captured to conversation_history.

**What was captured:**
- User: 'Please analyze positions'
- Assistant: 'I will buy/sell...'
- Assistant: 'Done <FINISH_SIGNAL>'

**What was MISSING:**
- Tool: buy 14 NVDA at $185.24
- Tool: sell 1 GOOGL at $245.15

**Changes:**
- Added tool message capture in trading loop (line 649)
- Extract tool_name and tool_content from each tool message
- Capture to conversation_history before processing
- Changed message['tool_name'] to message['name'] for consistency

**Impact:**
Now the summarizer sees the actual tool results, not just the AI's
intentions. Combined with alpha.8's prompt improvements, summaries
will accurately reflect executed trades.

Fixes reasoning summaries that contradicted actual trades.
2025-11-05 00:44:24 -05:00
6d126db03c fix: improve reasoning summary to explicitly mention trades
The reasoning summary was not accurately reflecting actual trades.
For example, 2 sell trades were summarized as 'maintain core holdings'.

Changes:
- Updated prompt to require explicit mention of trades executed
- Added emphasis on buy/sell tool calls in formatted log
- Trades now highlighted at top of log with TRADES EXECUTED section
- Prompt instructs: state specific trades (symbols, quantities, action)

Example before: 'chose to maintain core holdings'
Example after: 'sold 1 GOOGL and 1 AMZN to reduce exposure'

This ensures reasoning field accurately describes what the AI actually did.
2025-11-05 00:41:59 -05:00
1e7bdb509b chore: remove debug logging from ContextInjector
Removed noisy debug print statements that were added during
troubleshooting. The context injection is now working correctly
and no longer needs diagnostic output.

Cleaned up:
- Entry point logging
- Before/after injection logging
- Tool name and args logging
2025-11-05 00:31:16 -05:00
a8d912bb4b fix: calculate final holdings from actions instead of querying database
**Problem:**
Final positions showed empty holdings despite executing 15+ trades.
The issue persisted even after fixing the get_current_position_from_db query.

**Root Cause:**
At end of trading day, base_agent.py line 672 called
_get_current_portfolio_state() which queried the database for current
position. On the FIRST trading day, this query returns empty holdings
because there's no previous day's record.

**Why the Previous Fix Wasn't Enough:**
The previous fix (date < instead of date <=) correctly retrieves
STARTING position for subsequent days, but didn't address END-OF-DAY
position calculation, which needs to account for trades executed
during the current session.

**Solution:**
Added new method _calculate_final_position_from_actions() that:
1. Gets starting holdings from previous day (via get_starting_holdings)
2. Gets all actions from actions table for current trading day
3. Applies each buy/sell to calculate final state:
   - Buy: holdings[symbol] += qty, cash -= qty * price
   - Sell: holdings[symbol] -= qty, cash += qty * price
4. Returns accurate final holdings and cash

**Impact:**
- First trading day: Correctly saves all executed trades as final holdings
- Subsequent days: Final position reflects all trades from that day
- Holdings now persist correctly across all trading days

**Tests:**
- test_calculate_final_position_first_day_with_trades: 15 trades on first day
- test_calculate_final_position_with_previous_holdings: Multi-day scenario
- test_calculate_final_position_no_trades: No-trade edge case

All tests pass 
2025-11-04 23:51:54 -05:00
05620facc2 fix: update context_injector with trading_day_id after creation
Changes:
- Update context_injector.trading_day_id after trading_day record is created

Root Cause:
- ContextInjector was created before trading_day record existed
- trading_day_id was None when context_injector was initialized
- Even though trading_day_id was written to runtime config, the
  context_injector's attribute was never updated
- MCP tools use the injected trading_day_id parameter, not runtime config

Flow:
1. ModelDayExecutor creates ContextInjector (trading_day_id=None)
2. Agent.run_trading_session() creates trading_day record
3. NEW: Update context_injector.trading_day_id = trading_day_id
4. MCP tools receive trading_day_id via context injection

Impact:
- Fixes: "Trade failed: trading_day_id not found in runtime config"
- Trading tools (buy/sell) can now record actions properly
- Actions are linked to correct trading_day record

Related: agent/base_agent/base_agent.py:541-543
2025-11-04 23:04:47 -05:00
7c71a047bc fix: update position queries to use new trading_days schema
Changes:
- Update get_today_init_position_from_db to query trading_days table
- Remove obsolete add_no_trade_record_to_db calls from BaseAgent
- Simplify _handle_trading_result (trading_day record handles both scenarios)

Root Cause:
- Code was still querying old positions table after schema migration
- The add_no_trade_record_to_db function is obsolete in new schema

New Schema Behavior:
- trading_day record created at session start (regardless of trading)
- trading_day record updated at session end with final results
- No separate "no-trade" record needed

Impact:
- Fixes: "no such table: positions" error in get_today_init_position_from_db
- Removes unnecessary database writes for no-trade scenarios
- Simplifies codebase by removing obsolete function calls

Related: tools/price_tools.py:340-364, agent/base_agent/base_agent.py:661-673
2025-11-04 22:49:01 -05:00
e7fe0ab51b fix: add TRADING_DAY_ID write to runtime config and improve test coverage
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.
2025-11-04 09:32:07 -05:00
7d9d093d6c feat: migrate trade tools to write to actions table (new schema)
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.
2025-11-04 09:18:35 -05:00
f770a2fe84 fix: resolve critical integration issues in BaseAgent P&L calculation
Critical fixes:
1. Fixed api/database.py import - use get_db_path() instead of non-existent get_database_path()
2. Fixed state management - use database queries instead of reading from position.jsonl file
3. Fixed action counting - track during trading loop execution instead of retroactively from conversation history
4. Completed integration test to verify P&L calculation works correctly

Changes:
- agent/base_agent/base_agent.py:
  * Updated _get_current_portfolio_state() to query database via get_current_position_from_db()
  * Added today_date and job_id parameters to method signature
  * Count trade actions during trading loop instead of post-processing conversation history
  * Removed obsolete action counting logic

- api/database.py:
  * Fixed import to use get_db_path() from deployment_config
  * Pass correct default database path "data/trading.db"

- tests/integration/test_agent_pnl_integration.py:
  * Added proper mocks for dev mode and MCP client
  * Mocked get_current_position_from_db to return test data
  * Added comprehensive assertions to verify trading_day record fields
  * Test now actually validates P&L calculation integration

Test results:
- All unit tests passing (252 passed)
- All P&L integration tests passing (8 passed)
- No regressions detected
2025-11-03 23:34:10 -05:00
cd7e056120 feat: integrate P&L calculation and reasoning summary into BaseAgent
This implements Task 5 from the daily P&L results API refactor plan, bringing
together P&L calculation and reasoning summary into the BaseAgent trading session.

Changes:
- Add DailyPnLCalculator and ReasoningSummarizer to BaseAgent.__init__
- Modify run_trading_session() to:
  * Calculate P&L at start of day using current market prices
  * Create trading_day record with P&L metrics
  * Generate reasoning summary after trading using AI model
  * Save final holdings to database
  * Update trading_day with completion data (cash, portfolio value, summary, actions)
- Add helper methods:
  * _get_current_prices() - Get market prices for P&L calculation
  * _get_current_portfolio_state() - Read current state from position.jsonl
  * _calculate_portfolio_value() - Calculate total portfolio value

Integration test verifies:
- P&L calculation components exist and are importable
- DailyPnLCalculator correctly calculates zero P&L on first day
- ReasoningSummarizer can be instantiated with AI model

This maintains backward compatibility with position.jsonl while adding
comprehensive database tracking for the new results API.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 23:24:00 -05:00
197d3b7bf9 feat: add AI reasoning summary generator with fallback
- Implement ReasoningSummarizer class for generating 2-3 sentence AI summaries
- Add fallback to statistical summary when AI generation fails
- Format reasoning logs for summary prompt with truncation
- Handle empty reasoning logs with default message
- Add comprehensive unit tests with async mocking
2025-11-03 23:16:15 -05:00
5c19410f71 feat: add daily P&L calculator with weekend gap handling
Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 23:12:49 -05:00
6c395f740d fix: always override context parameters in ContextInjector
Root cause: AI models were hallucinating signature/job_id/today_date values
and passing them in tool calls. The ContextInjector was checking
"if param not in request.args" before injecting, which failed when AI
provided (incorrect) values.

Fix: Always override context parameters, never trust AI-provided values.

Evidence from logs:
- ContextInjector had correct values (self.signature=gpt-5, job_id=6dabd9e6...)
- But AI was passing signature=None or hallucinated values like "fundamental-bot-v1"
- After injection, args showed the AI's (wrong) values, not the interceptor's

This ensures runtime context is ALWAYS injected regardless of what the AI sends.

Fixes #TBD
2025-11-02 23:30:49 -05:00
618943b278 debug: add self attribute logging to ContextInjector.__call__
Log ContextInjector instance ID and attribute values at entry to __call__()
to diagnose why attributes appear as None during tool invocation despite
being set correctly during set_context().

This will reveal whether:
- Multiple ContextInjector instances exist
- Attributes are being overwritten/cleared
- Wrong instance is being invoked
2025-11-02 23:17:52 -05:00
1c19eea29a debug: add comprehensive diagnostic logging for ContextInjector flow
Add instrumentation at component boundaries to trace where ContextInjector values become None:
- ModelDayExecutor: Log ContextInjector creation and set_context() invocation
- BaseAgent.set_context(): Log entry, client creation, tool reload, completion
- Includes object IDs to verify instance identity across boundaries

Part of systematic debugging investigation for issue #TBD.
2025-11-02 23:05:40 -05:00
e968434062 fix: reload tools after context injection and prevent database locking
Critical fixes for ContextInjector and database concurrency:

1. ContextInjector Not Working:
   - Made set_context() async to reload tools after recreating MCP client
   - Tools from old client (without interceptor) were still being used
   - Now tools are reloaded from new client with interceptor active
   - This ensures buy/sell calls properly receive injected parameters

2. Database Locking:
   - Closed main connection before _write_results_to_db() opens new one
   - SQLite doesn't handle concurrent write connections well
   - Prevents "database is locked" error during position writes

Changes:
- agent/base_agent/base_agent.py:
  - async def set_context() instead of def set_context()
  - Added: self.tools = await self.client.get_tools()
- api/model_day_executor.py:
  - await agent.set_context(context_injector)
  - conn.close() before _write_results_to_db()

Root Cause:
When recreating the MCP client with tool_interceptors, the old tools
were still cached in self.tools and being passed to the AI agent.
The interceptor was never invoked, so job_id/signature/date were missing.
2025-11-02 22:42:17 -05:00
027b4bd8e4 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
2025-11-02 22:20:01 -05:00
019c84fca8 refactor: migrate trade tools from file-based to SQLite position storage
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.
2025-11-02 21:36:57 -05:00
bf12e981fe debug: add logging to trace parameter injection 2025-11-02 20:29:35 -05:00
a16bac5d08 fix: use 'args' instead of 'arguments' in MCPToolCallRequest
MCPToolCallRequest has 'args' attribute, not 'arguments'. Fixed
attribute name to match the actual API.
2025-11-02 20:21:43 -05:00
81b92e293a fix: make ContextInjector async to match ToolCallInterceptor protocol
The interceptor __call__ method must be async and follow the proper
signature: async __call__(request, handler) -> result

Previous implementation was synchronous and had wrong signature, causing
'object function can't be used in await expression' error.
2025-11-02 20:11:20 -05:00
b1b486dcc4 fix: inject signature and today_date into trade tool calls 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
2025-11-02 20:01:32 -05:00
2f05418f42 refactor: remove JSONL logging code from BaseAgent
- Remove _log_message() and _setup_logging() methods
- Remove all calls to logging methods in run_trading_session()
- Update log_path parameter docstring for clarity
- Update integration test to verify conversation history instead of JSONL files
- Reasoning logs now stored exclusively in database via model_day_executor
- Conversation history tracking preserved in memory

Related: Task 6 of reasoning logs API feature
2025-11-02 18:16:06 -05:00
f83e4caf41 feat: add AI-powered summary generation to BaseAgent 2025-11-02 17:59:56 -05:00
837504aa17 feat: add conversation history tracking to BaseAgent
- Add conversation_history instance variable to BaseAgent.__init__
- Create _capture_message() method to capture messages with timestamps
- Create get_conversation_history() method to retrieve conversation
- Create clear_conversation_history() method to reset history
- Modify run_trading_session() to capture user prompts and AI responses
- Add comprehensive unit tests for conversation tracking
- Fix datetime deprecation warning by using timezone-aware datetime

All tests pass successfully.
2025-11-02 17:55:05 -05:00
b09e1b0b11 feat: integrate mock AI provider in BaseAgent for DEV mode
Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-01 11:25:49 -04:00
ab085e5545 fix: suppress unused parameter warnings in mock LangChain model 2025-11-01 11:16:51 -04:00
9ffd42481a feat: add LangChain-compatible mock chat model wrapper
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-01 11:15:59 -04:00
b6867c9c16 feat: add mock AI provider for dev mode with stock rotation 2025-11-01 11:07:46 -04:00
Mirza Samad
3e9cd5f35b changes 2025-10-29 09:42:37 +03:00
tianyufan
eaf9379c21 support multi url and key 2025-10-27 21:37:46 +08:00
tianyufan
df5c25c98d init update 2025-10-24 00:35:21 +08:00