mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-02 01:27:24 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f09fa5501 | |||
| 31d6818130 | |||
| 4638c073e3 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.4.3] - 2025-11-07
|
||||
|
||||
### Fixed
|
||||
- **Critical:** Fixed cross-job portfolio continuity bug where subsequent jobs reset to initial position
|
||||
- Root cause: Two database query functions (`get_previous_trading_day()` and `get_starting_holdings()`) filtered by `job_id`, preventing them from finding previous day's position when queried from a different job
|
||||
- Impact: New jobs on consecutive dates would start with $10,000 cash and empty holdings instead of continuing from previous job's ending position (e.g., Job 2 on 2025-10-08 started with $10,000 instead of $329.825 cash and lost all stock holdings from Job 1 on 2025-10-07)
|
||||
- Solution: Removed `job_id` filters from SQL queries to enable cross-job position lookups, matching the existing design in `get_current_position_from_db()` which already supported cross-job continuity
|
||||
- Fix ensures complete portfolio continuity (both cash and holdings) across jobs for the same model
|
||||
- Added comprehensive test coverage with `test_get_previous_trading_day_across_jobs` and `test_get_starting_holdings_across_jobs`
|
||||
- Locations: `api/database.py:622-630` (get_previous_trading_day), `api/database.py:674-681` (get_starting_holdings), `tests/unit/test_database_helpers.py:133-169,265-316`
|
||||
|
||||
## [0.4.2] - 2025-11-07
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -611,6 +611,10 @@ class Database:
|
||||
|
||||
Handles weekends/holidays by finding actual previous trading day.
|
||||
|
||||
NOTE: Queries across ALL jobs for the given model to enable portfolio
|
||||
continuity even when new jobs are created with overlapping date ranges.
|
||||
The job_id parameter is kept for API compatibility but not used in the query.
|
||||
|
||||
Returns:
|
||||
dict with keys: id, date, ending_cash, ending_portfolio_value
|
||||
or None if no previous day exists
|
||||
@@ -619,11 +623,11 @@ class Database:
|
||||
"""
|
||||
SELECT id, date, ending_cash, ending_portfolio_value
|
||||
FROM trading_days
|
||||
WHERE job_id = ? AND model = ? AND date < ?
|
||||
WHERE model = ? AND date < ?
|
||||
ORDER BY date DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(job_id, model, current_date)
|
||||
(model, current_date)
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
@@ -657,6 +661,9 @@ class Database:
|
||||
def get_starting_holdings(self, trading_day_id: int) -> list:
|
||||
"""Get starting holdings from previous day's ending holdings.
|
||||
|
||||
NOTE: Queries across ALL jobs for the given model to enable portfolio
|
||||
continuity even when new jobs are created with overlapping date ranges.
|
||||
|
||||
Returns:
|
||||
List of dicts with keys: symbol, quantity
|
||||
Empty list if first trading day
|
||||
@@ -667,7 +674,6 @@ class Database:
|
||||
SELECT td_prev.id
|
||||
FROM trading_days td_current
|
||||
JOIN trading_days td_prev ON
|
||||
td_prev.job_id = td_current.job_id AND
|
||||
td_prev.model = td_current.model AND
|
||||
td_prev.date < td_current.date
|
||||
WHERE td_current.id = ?
|
||||
|
||||
@@ -130,6 +130,44 @@ class TestDatabaseHelpers:
|
||||
assert previous is not None
|
||||
assert previous["date"] == "2025-01-17"
|
||||
|
||||
def test_get_previous_trading_day_across_jobs(self, db):
|
||||
"""Test retrieving previous trading day from different job (cross-job continuity)."""
|
||||
# Setup: Create two jobs
|
||||
db.connection.execute(
|
||||
"INSERT INTO jobs (job_id, status, config_path, date_range, models, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
("job-1", "completed", "config.json", "2025-10-07,2025-10-07", "deepseek-chat-v3.1", "2025-11-07T00:00:00Z")
|
||||
)
|
||||
db.connection.execute(
|
||||
"INSERT INTO jobs (job_id, status, config_path, date_range, models, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
("job-2", "running", "config.json", "2025-10-08,2025-10-08", "deepseek-chat-v3.1", "2025-11-07T01:00:00Z")
|
||||
)
|
||||
|
||||
# Day 1 in job-1
|
||||
db.create_trading_day(
|
||||
job_id="job-1",
|
||||
model="deepseek-chat-v3.1",
|
||||
date="2025-10-07",
|
||||
starting_cash=10000.0,
|
||||
starting_portfolio_value=10000.0,
|
||||
daily_profit=214.58,
|
||||
daily_return_pct=2.15,
|
||||
ending_cash=123.59,
|
||||
ending_portfolio_value=10214.58
|
||||
)
|
||||
|
||||
# Test: Get previous day from job-2 on next date
|
||||
# Should find job-1's record (cross-job continuity)
|
||||
previous = db.get_previous_trading_day(
|
||||
job_id="job-2",
|
||||
model="deepseek-chat-v3.1",
|
||||
current_date="2025-10-08"
|
||||
)
|
||||
|
||||
assert previous is not None
|
||||
assert previous["date"] == "2025-10-07"
|
||||
assert previous["ending_cash"] == 123.59
|
||||
assert previous["ending_portfolio_value"] == 10214.58
|
||||
|
||||
def test_get_ending_holdings(self, db):
|
||||
"""Test retrieving ending holdings for a trading day."""
|
||||
db.connection.execute(
|
||||
@@ -224,6 +262,59 @@ class TestDatabaseHelpers:
|
||||
assert holdings[0]["symbol"] == "AAPL"
|
||||
assert holdings[0]["quantity"] == 10
|
||||
|
||||
def test_get_starting_holdings_across_jobs(self, db):
|
||||
"""Test starting holdings retrieval across different jobs (cross-job continuity)."""
|
||||
# Setup: Create two jobs
|
||||
db.connection.execute(
|
||||
"INSERT INTO jobs (job_id, status, config_path, date_range, models, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
("job-1", "completed", "config.json", "2025-10-07,2025-10-07", "deepseek-chat-v3.1", "2025-11-07T00:00:00Z")
|
||||
)
|
||||
db.connection.execute(
|
||||
"INSERT INTO jobs (job_id, status, config_path, date_range, models, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
("job-2", "running", "config.json", "2025-10-08,2025-10-08", "deepseek-chat-v3.1", "2025-11-07T01:00:00Z")
|
||||
)
|
||||
|
||||
# Day 1 in job-1 with holdings
|
||||
day1_id = db.create_trading_day(
|
||||
job_id="job-1",
|
||||
model="deepseek-chat-v3.1",
|
||||
date="2025-10-07",
|
||||
starting_cash=10000.0,
|
||||
starting_portfolio_value=10000.0,
|
||||
daily_profit=214.58,
|
||||
daily_return_pct=2.15,
|
||||
ending_cash=329.825,
|
||||
ending_portfolio_value=10666.135
|
||||
)
|
||||
db.create_holding(day1_id, "AAPL", 10)
|
||||
db.create_holding(day1_id, "AMD", 4)
|
||||
db.create_holding(day1_id, "MSFT", 8)
|
||||
db.create_holding(day1_id, "NVDA", 12)
|
||||
db.create_holding(day1_id, "TSLA", 1)
|
||||
|
||||
# Day 2 in job-2 (different job)
|
||||
day2_id = db.create_trading_day(
|
||||
job_id="job-2",
|
||||
model="deepseek-chat-v3.1",
|
||||
date="2025-10-08",
|
||||
starting_cash=329.825,
|
||||
starting_portfolio_value=10609.475,
|
||||
daily_profit=-56.66,
|
||||
daily_return_pct=-0.53,
|
||||
ending_cash=33.62,
|
||||
ending_portfolio_value=329.825
|
||||
)
|
||||
|
||||
# Test: Day 2 should get Day 1's holdings from different job
|
||||
holdings = db.get_starting_holdings(day2_id)
|
||||
|
||||
assert len(holdings) == 5
|
||||
assert {"symbol": "AAPL", "quantity": 10} in holdings
|
||||
assert {"symbol": "AMD", "quantity": 4} in holdings
|
||||
assert {"symbol": "MSFT", "quantity": 8} in holdings
|
||||
assert {"symbol": "NVDA", "quantity": 12} in holdings
|
||||
assert {"symbol": "TSLA", "quantity": 1} in holdings
|
||||
|
||||
def test_create_action(self, db):
|
||||
"""Test creating an action record."""
|
||||
db.connection.execute(
|
||||
|
||||
Reference in New Issue
Block a user