mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-11-07 09:57:17 +02:00
fix(backtest): give each sweep started without a run_id its own run (#1549)
- the stamped run directory is claimed atomically; a second sweep in the same second takes the next suffix instead of resuming the first
This commit is contained in:
@@ -155,6 +155,23 @@ def test_the_analyst_set_under_test_is_the_one_that_runs(tmp_path):
|
||||
assert _FakeGraph.instances[-1].analysts == ["market", "news"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_sweeps_started_in_the_same_second_get_their_own_runs(tmp_path, monkeypatch):
|
||||
import tradingagents.backtest as backtest
|
||||
|
||||
class _Frozen(backtest.datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return cls(2026, 1, 20, 9, 30, 0)
|
||||
|
||||
monkeypatch.setattr(backtest, "datetime", _Frozen)
|
||||
first = run_backtest(["NVDA"], ["2026-01-05"], _config(tmp_path))
|
||||
second = run_backtest(["NVDA"], ["2026-01-05"], _config(tmp_path))
|
||||
|
||||
assert first.run_id == "20260120_093000" and second.run_id == "20260120_093000_2"
|
||||
assert first.cells_run == second.cells_run == 1
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_a_run_id_cannot_escape_the_results_directory(tmp_path):
|
||||
"""run_id becomes a path segment, so it is validated like a ticker is."""
|
||||
|
||||
@@ -16,6 +16,7 @@ cell rather than a position carried forward.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
@@ -126,6 +127,19 @@ class BacktestSummary:
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _new_run(runs: Path) -> str:
|
||||
"""A new run's id, stamped now, and its directory, claimed so no other sweep shares it."""
|
||||
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
runs.mkdir(parents=True, exist_ok=True)
|
||||
for n in itertools.count(1):
|
||||
run_id = stamp if n == 1 else f"{stamp}_{n}"
|
||||
try:
|
||||
(runs / run_id).mkdir()
|
||||
return run_id
|
||||
except FileExistsError:
|
||||
continue
|
||||
|
||||
|
||||
def run_backtest(
|
||||
tickers: list[str],
|
||||
dates: list[str],
|
||||
@@ -143,11 +157,15 @@ def run_backtest(
|
||||
interrupted sweep resumes by being run again. ``progress(done, total,
|
||||
ticker, date)`` is called before each cell that runs.
|
||||
"""
|
||||
# run_id becomes a path segment, so it is validated like a ticker: an
|
||||
# absolute or dotted value would otherwise place the run outside results_dir.
|
||||
run_id = safe_ticker_component(run_id or datetime.now().strftime("%Y%m%d_%H%M%S"))
|
||||
run_dir = Path(config["results_dir"]) / "backtest" / run_id
|
||||
run_dir.mkdir(parents=True, exist_ok=True)
|
||||
runs = Path(config["results_dir"]) / "backtest"
|
||||
if run_id:
|
||||
# run_id becomes a path segment, so it is validated like a ticker: an
|
||||
# absolute or dotted value would otherwise place the run outside results_dir.
|
||||
run_id = safe_ticker_component(run_id)
|
||||
(runs / run_id).mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
run_id = _new_run(runs)
|
||||
run_dir = runs / run_id
|
||||
run_config = {**config, "results_dir": str(run_dir),
|
||||
"memory_log_path": str(run_dir / "trading_memory.md")}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user