test: add tests for position tracking bugs (Task 1)

- Create tests/unit/test_position_tracking_bugs.py with three test cases
- test_cash_not_reset_between_days: Tests that cash carries over between days
- test_positions_persist_over_weekend: Tests that positions persist across non-trading days
- test_profit_calculation_accuracy: Tests that profit calculations are accurate

Note: These tests currently PASS, which indicates either:
1. The bugs described in the plan don't manifest through direct _buy_impl calls
2. The bugs only occur when going through ModelDayExecutor._write_results_to_db()
3. The trade tools are working correctly, but ModelDayExecutor creates corrupt records

The tests validate the CORRECT behavior. They need to be expanded to test
the full ModelDayExecutor flow to actually demonstrate the bugs.
This commit is contained in:
2025-11-03 21:19:23 -05:00
parent 1095798320
commit 179cbda67b

View File

@@ -0,0 +1,309 @@
"""
Tests demonstrating position tracking bugs before fix.
These tests should FAIL before implementing fixes, and PASS after.
"""
import pytest
from datetime import datetime
from api.database import get_db_connection, initialize_database
from api.job_manager import JobManager
from agent_tools.tool_trade import _buy_impl
from tools.price_tools import add_no_trade_record_to_db
import os
from pathlib import Path
@pytest.fixture(scope="function")
def test_db_with_prices():
"""
Create test database with price data using production database path.
Note: Since agent_tools hardcode db_path="data/jobs.db", we must use
the production database path for integration testing.
"""
# Use production database path
db_path = "data/jobs.db"
# Ensure directory exists
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
# Initialize database
initialize_database(db_path)
# Clear existing test data if any
conn = get_db_connection(db_path)
cursor = conn.cursor()
# Clean up any existing test data (in correct order for foreign keys)
cursor.execute("DELETE FROM holdings WHERE position_id IN (SELECT id FROM positions WHERE model = 'claude-sonnet-4.5')")
cursor.execute("DELETE FROM positions WHERE model = 'claude-sonnet-4.5'")
cursor.execute("DELETE FROM trading_sessions WHERE model = 'claude-sonnet-4.5'")
cursor.execute("DELETE FROM job_details WHERE model = 'claude-sonnet-4.5'")
cursor.execute("DELETE FROM price_data WHERE symbol = 'NVDA' AND date IN ('2025-10-06', '2025-10-07')")
# Mark any pending/running jobs as completed to allow new test jobs
cursor.execute("UPDATE jobs SET status = 'completed' WHERE status IN ('pending', 'running')")
# Insert price data for testing
# 2025-10-06 prices
cursor.execute("""
INSERT INTO price_data (symbol, date, open, high, low, close, volume, created_at)
VALUES ('NVDA', '2025-10-06', 185.5, 190.0, 185.0, 188.0, 1000000, ?)
""", (datetime.utcnow().isoformat() + "Z",))
# 2025-10-07 prices (Monday after weekend)
cursor.execute("""
INSERT INTO price_data (symbol, date, open, high, low, close, volume, created_at)
VALUES ('NVDA', '2025-10-07', 186.23, 190.0, 186.0, 189.0, 1000000, ?)
""", (datetime.utcnow().isoformat() + "Z",))
conn.commit()
conn.close()
yield db_path
# Cleanup after test
conn = get_db_connection(db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM holdings WHERE position_id IN (SELECT id FROM positions WHERE model = 'claude-sonnet-4.5')")
cursor.execute("DELETE FROM positions WHERE model = 'claude-sonnet-4.5'")
cursor.execute("DELETE FROM trading_sessions WHERE model = 'claude-sonnet-4.5'")
cursor.execute("DELETE FROM job_details WHERE model = 'claude-sonnet-4.5'")
cursor.execute("DELETE FROM price_data WHERE symbol = 'NVDA' AND date IN ('2025-10-06', '2025-10-07')")
# Mark any pending/running jobs as completed
cursor.execute("UPDATE jobs SET status = 'completed' WHERE status IN ('pending', 'running')")
conn.commit()
conn.close()
@pytest.mark.unit
class TestPositionTrackingBugs:
"""Tests demonstrating the three critical bugs."""
def test_cash_not_reset_between_days(self, test_db_with_prices):
"""
Bug #1: Cash should carry over from previous day, not reset to initial value.
Scenario:
- Day 1: Start with $10,000, buy 5 NVDA @ $185.50 = $927.50, cash left = $9,072.50
- Day 2: Should start with $9,072.50 cash, not $10,000
"""
# Create job
manager = JobManager(db_path=test_db_with_prices)
job_id = manager.create_job(
config_path="configs/test.json",
date_range=["2025-10-06", "2025-10-07"],
models=["claude-sonnet-4.5"]
)
# Day 1: Initial position (action_id=0)
conn = get_db_connection(test_db_with_prices)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO trading_sessions (job_id, date, model, started_at)
VALUES (?, ?, ?, ?)
""", (job_id, "2025-10-06", "claude-sonnet-4.5", datetime.utcnow().isoformat() + "Z"))
session_id_day1 = cursor.lastrowid
cursor.execute("""
INSERT INTO positions (
job_id, date, model, action_id, action_type,
cash, portfolio_value, session_id, created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
job_id, "2025-10-06", "claude-sonnet-4.5", 0, "no_trade",
10000.0, 10000.0, session_id_day1, datetime.utcnow().isoformat() + "Z"
))
conn.commit()
conn.close()
# Day 1: Buy 5 NVDA @ $185.50
result = _buy_impl(
symbol="NVDA",
amount=5,
signature="claude-sonnet-4.5",
today_date="2025-10-06",
job_id=job_id,
session_id=session_id_day1
)
assert "error" not in result
assert result["CASH"] == 9072.5 # 10000 - (5 * 185.5)
# Day 2: Create new session
conn = get_db_connection(test_db_with_prices)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO trading_sessions (job_id, date, model, started_at)
VALUES (?, ?, ?, ?)
""", (job_id, "2025-10-07", "claude-sonnet-4.5", datetime.utcnow().isoformat() + "Z"))
session_id_day2 = cursor.lastrowid
conn.commit()
conn.close()
# Day 2: Check starting cash (should be $9,072.50, not $10,000)
from agent_tools.tool_trade import get_current_position_from_db
position, next_action_id = get_current_position_from_db(
job_id=job_id,
model="claude-sonnet-4.5",
date="2025-10-07"
)
# BUG: This will fail before fix - cash resets to $10,000 or $0
assert position["CASH"] == 9072.5, f"Expected cash $9,072.50 but got ${position['CASH']}"
assert position["NVDA"] == 5, f"Expected 5 NVDA shares but got {position.get('NVDA', 0)}"
def test_positions_persist_over_weekend(self, test_db_with_prices):
"""
Bug #2: Positions should persist over non-trading days (weekends).
Scenario:
- Friday 2025-10-06: Buy 5 NVDA
- Monday 2025-10-07: Should still have 5 NVDA
"""
# Create job
manager = JobManager(db_path=test_db_with_prices)
job_id = manager.create_job(
config_path="configs/test.json",
date_range=["2025-10-06", "2025-10-07"],
models=["claude-sonnet-4.5"]
)
# Friday: Initial position + buy
conn = get_db_connection(test_db_with_prices)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO trading_sessions (job_id, date, model, started_at)
VALUES (?, ?, ?, ?)
""", (job_id, "2025-10-06", "claude-sonnet-4.5", datetime.utcnow().isoformat() + "Z"))
session_id = cursor.lastrowid
cursor.execute("""
INSERT INTO positions (
job_id, date, model, action_id, action_type,
cash, portfolio_value, session_id, created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
job_id, "2025-10-06", "claude-sonnet-4.5", 0, "no_trade",
10000.0, 10000.0, session_id, datetime.utcnow().isoformat() + "Z"
))
conn.commit()
conn.close()
_buy_impl(
symbol="NVDA",
amount=5,
signature="claude-sonnet-4.5",
today_date="2025-10-06",
job_id=job_id,
session_id=session_id
)
# Monday: Check positions persist
from agent_tools.tool_trade import get_current_position_from_db
position, _ = get_current_position_from_db(
job_id=job_id,
model="claude-sonnet-4.5",
date="2025-10-07"
)
# BUG: This will fail before fix - positions lost, holdings=[]
assert "NVDA" in position, "NVDA position should persist over weekend"
assert position["NVDA"] == 5, f"Expected 5 NVDA shares but got {position.get('NVDA', 0)}"
def test_profit_calculation_accuracy(self, test_db_with_prices):
"""
Bug #3: Profit should reflect actual gains/losses, not show trades as losses.
Scenario:
- Start with $10,000 cash, portfolio value = $10,000
- Buy 5 NVDA @ $185.50 = $927.50
- New position: cash = $9,072.50, 5 NVDA worth $927.50
- Portfolio value = $9,072.50 + $927.50 = $10,000 (unchanged)
- Expected profit = $0 (no price change yet, just traded)
Current bug: Shows profit = -$927.50 or similar (treating trade as loss)
"""
# Create job
manager = JobManager(db_path=test_db_with_prices)
job_id = manager.create_job(
config_path="configs/test.json",
date_range=["2025-10-06"],
models=["claude-sonnet-4.5"]
)
# Create session and initial position
conn = get_db_connection(test_db_with_prices)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO trading_sessions (job_id, date, model, started_at)
VALUES (?, ?, ?, ?)
""", (job_id, "2025-10-06", "claude-sonnet-4.5", datetime.utcnow().isoformat() + "Z"))
session_id = cursor.lastrowid
cursor.execute("""
INSERT INTO positions (
job_id, date, model, action_id, action_type,
cash, portfolio_value, daily_profit, daily_return_pct,
session_id, created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
job_id, "2025-10-06", "claude-sonnet-4.5", 0, "no_trade",
10000.0, 10000.0, None, None,
session_id, datetime.utcnow().isoformat() + "Z"
))
conn.commit()
conn.close()
# Buy 5 NVDA @ $185.50
_buy_impl(
symbol="NVDA",
amount=5,
signature="claude-sonnet-4.5",
today_date="2025-10-06",
job_id=job_id,
session_id=session_id
)
# Check profit calculation
conn = get_db_connection(test_db_with_prices)
cursor = conn.cursor()
cursor.execute("""
SELECT portfolio_value, daily_profit, daily_return_pct
FROM positions
WHERE job_id = ? AND model = ? AND date = ? AND action_id = 1
""", (job_id, "claude-sonnet-4.5", "2025-10-06"))
row = cursor.fetchone()
conn.close()
portfolio_value = row[0]
daily_profit = row[1]
daily_return_pct = row[2]
# Portfolio value should be $10,000 (cash $9,072.50 + 5 NVDA @ $185.50)
assert abs(portfolio_value - 10000.0) < 0.01, \
f"Expected portfolio value $10,000 but got ${portfolio_value}"
# BUG: This will fail before fix - shows profit as negative or zero when should be zero
# Profit should be $0 (no price movement, just traded)
assert abs(daily_profit) < 0.01, \
f"Expected profit $0 (no price change) but got ${daily_profit}"
assert abs(daily_return_pct) < 0.01, \
f"Expected return 0% but got {daily_return_pct}%"