import json import os from backtesting import engine def test_run_backtest_caches_by_input_file_signature(monkeypatch, tmp_path): history = tmp_path / "history.json" thresholds = tmp_path / "thresholds.json" weights = tmp_path / "weights.json" cache = tmp_path / "cache.json" for path in (history, thresholds, weights, cache): path.write_text("{}") monkeypatch.setattr(engine, "HISTORY_PATH", str(history)) monkeypatch.setattr(engine, "_THRESH_PATH", str(thresholds)) monkeypatch.setattr(engine, "ML_WEIGHTS_PATH", str(weights)) monkeypatch.setattr(engine, "CACHE_PATH", str(cache)) calls = [] monkeypatch.setattr( engine, "_compute_backtest", lambda ml_mode=False: calls.append(ml_mode) or {"ml_mode": ml_mode, "calls": len(calls)}, ) engine.clear_backtest_cache() first = engine.run_backtest() second = engine.run_backtest() ml_first = engine.run_backtest(ml_mode=True) ml_second = engine.run_backtest(ml_mode=True) classic_after_ml = engine.run_backtest() assert first == second == {"ml_mode": False, "calls": 1} assert ml_first == ml_second == {"ml_mode": True, "calls": 2} assert classic_after_ml == first assert calls == [False, True] history.write_text('{"changed": true}') os.utime(history, None) invalidated = engine.run_backtest() assert invalidated == {"ml_mode": False, "calls": 3} def test_cached_backtest_results_are_isolated_from_caller_mutation(monkeypatch, tmp_path): history = tmp_path / "history.json" history.write_text("{}") monkeypatch.setattr(engine, "HISTORY_PATH", str(history)) monkeypatch.setattr(engine, "_THRESH_PATH", str(tmp_path / "missing-thresholds.json")) monkeypatch.setattr(engine, "ML_WEIGHTS_PATH", str(tmp_path / "missing-weights.json")) monkeypatch.setattr(engine, "CACHE_PATH", str(tmp_path / "missing-cache.json")) monkeypatch.setattr(engine, "_compute_backtest", lambda ml_mode=False: {"chart_data": [{"score": 10}]}) engine.clear_backtest_cache() first = engine.run_backtest() first["chart_data"][0]["score"] = 99 assert engine.run_backtest()["chart_data"][0]["score"] == 10