Spaces:
Running
Running
| import sqlite3 | |
| import json | |
| from autotune import init_tuning_table, save_tuning, load_tuning | |
| def _params(**overrides): | |
| base = dict( | |
| lookback=200, pred_len=30, stride=30, | |
| sample_count=1, max_anchors=20, | |
| start_date="", end_date="", | |
| ) | |
| base.update(overrides) | |
| return base | |
| def test_init_tuning_table_creates_schema(tmp_db): | |
| init_tuning_table(tmp_db) | |
| with sqlite3.connect(tmp_db) as c: | |
| cols = [r[1] for r in c.execute("PRAGMA table_info(tuning_results)").fetchall()] | |
| assert "symbol" in cols | |
| assert "interval" in cols | |
| assert "best_T" in cols | |
| assert "best_top_p" in cols | |
| assert "best_pnl_pct" in cols | |
| assert "grid_json" in cols | |
| def test_init_tuning_table_unique_constraint(tmp_db): | |
| init_tuning_table(tmp_db) | |
| with sqlite3.connect(tmp_db) as c: | |
| c.execute("INSERT INTO tuning_results (ts, symbol, interval, best_T, best_top_p, best_pnl_pct, best_hit_rate, best_rmse, grid_json) " | |
| "VALUES ('t1','SPY','5min',1.0,0.9,5.0,0.6,1.2,'[]')") | |
| c.execute("INSERT INTO tuning_results (ts, symbol, interval, best_T, best_top_p, best_pnl_pct, best_hit_rate, best_rmse, grid_json) " | |
| "VALUES ('t2','SPY','5min',1.5,0.85,7.0,0.7,1.0,'[]')") | |
| rows = c.execute("SELECT best_T, best_top_p FROM tuning_results WHERE symbol='SPY' AND interval='5min'").fetchall() | |
| assert rows == [(1.5, 0.85)] | |
| def test_save_and_load_roundtrip(tmp_db): | |
| init_tuning_table(tmp_db) | |
| best = {"T": 1.0, "top_p": 0.85, "total_return_pct": 4.2, | |
| "hit_rate": 0.6, "mean_rmse": 1.1} | |
| results = [{"T": 1.0, "top_p": 0.85, "total_return_pct": 4.2}] | |
| save_tuning(tmp_db, "SPY", "5min", best, results, _params()) | |
| got = load_tuning(tmp_db, "SPY", "5min") | |
| assert got is not None | |
| assert got["best_T"] == 1.0 | |
| assert got["best_top_p"] == 0.85 | |
| assert got["best_pnl_pct"] == 4.2 | |
| assert json.loads(got["grid_json"]) == results | |
| def test_load_tuning_missing_returns_none(tmp_db): | |
| init_tuning_table(tmp_db) | |
| assert load_tuning(tmp_db, "ZZZ", "1min") is None | |
| def test_save_tuning_replaces_on_conflict(tmp_db): | |
| init_tuning_table(tmp_db) | |
| save_tuning(tmp_db, "SPY", "5min", | |
| {"T": 1.0, "top_p": 0.85, "total_return_pct": 1.0, | |
| "hit_rate": 0.5, "mean_rmse": 2.0}, | |
| [], _params()) | |
| save_tuning(tmp_db, "SPY", "5min", | |
| {"T": 1.5, "top_p": 0.7, "total_return_pct": 9.0, | |
| "hit_rate": 0.8, "mean_rmse": 0.5}, | |
| [], _params()) | |
| got = load_tuning(tmp_db, "SPY", "5min") | |
| assert got["best_T"] == 1.5 | |
| assert got["best_pnl_pct"] == 9.0 | |
| with sqlite3.connect(tmp_db) as c: | |
| n = c.execute("SELECT COUNT(*) FROM tuning_results WHERE symbol='SPY' AND interval='5min'").fetchone()[0] | |
| assert n == 1 | |
| import numpy as np | |
| import pandas as pd | |
| from autotune import backtest_core | |
| def _make_full(rows): | |
| ts = pd.date_range("2026-01-01", periods=rows, freq="5min") | |
| closes = np.linspace(100.0, 110.0, rows) | |
| df = pd.DataFrame({ | |
| "timestamps": ts, | |
| "open": closes, "high": closes + 0.1, "low": closes - 0.1, | |
| "close": closes, "volume": 1000.0, "amount": closes * 1000.0, | |
| }) | |
| return df | |
| def _stub_predict(rising): | |
| """Returns a predict fn that always forecasts a monotone path.""" | |
| def _f(df, x_timestamp, y_timestamp, pred_len, T, top_p, sample_count, verbose): | |
| last = float(df["close"].iloc[-1]) | |
| delta = 1.0 if rising else -1.0 | |
| path = np.linspace(last + delta, last + delta * pred_len, pred_len) | |
| return pd.DataFrame({ | |
| "open": path, "high": path + 0.1, "low": path - 0.1, | |
| "close": path, "volume": 1000.0, "amount": path * 1000.0, | |
| }, index=y_timestamp) | |
| return _f | |
| def test_backtest_core_perfect_long_when_price_rises_and_forecast_rises(): | |
| full = _make_full(rows=400) # rising | |
| fetch_fn = lambda symbol, interval, n_bars: full.copy() | |
| predict_fn = _stub_predict(rising=True) | |
| out = backtest_core(predict_fn, fetch_fn, | |
| symbol="X", interval="5min", | |
| start_date="", end_date="", | |
| lookback=100, pred_len=10, stride=10, | |
| T=1.0, top_p=0.9, | |
| sample_count=1, max_anchors=5) | |
| assert out["anchors"] == 5 | |
| assert out["hit_rate"] == 1.0 # forecast up, realized up — every anchor hits | |
| assert out["total_return_pct"] > 0 | |
| assert isinstance(out["per_anchor"], pd.DataFrame) | |
| assert len(out["per_anchor"]) == 5 | |
| def test_backtest_core_short_when_forecast_down_but_price_up(): | |
| full = _make_full(rows=400) | |
| fetch_fn = lambda *a, **k: full.copy() | |
| predict_fn = _stub_predict(rising=False) # forecast says down | |
| out = backtest_core(predict_fn, fetch_fn, | |
| symbol="X", interval="5min", | |
| start_date="", end_date="", | |
| lookback=100, pred_len=10, stride=10, | |
| T=1.0, top_p=0.9, | |
| sample_count=1, max_anchors=5) | |
| assert out["hit_rate"] == 0.0 # always wrong | |
| assert out["total_return_pct"] < 0 # short while market rises = loss | |
| def test_backtest_core_raises_when_lookback_plus_predlen_too_big(): | |
| full = _make_full(rows=400) | |
| fetch_fn = lambda *a, **k: full.copy() | |
| predict_fn = _stub_predict(rising=True) | |
| import pytest | |
| with pytest.raises(ValueError, match="≤ 512"): | |
| backtest_core(predict_fn, fetch_fn, | |
| symbol="X", interval="5min", | |
| start_date="", end_date="", | |
| lookback=400, pred_len=200, stride=50, | |
| T=1.0, top_p=0.9, | |
| sample_count=1, max_anchors=5) | |
| from unittest.mock import MagicMock | |
| from autotune import run_autotune, GRID_T, GRID_TOP_P | |
| def test_run_autotune_iterates_full_grid_and_picks_max(tmp_db, monkeypatch): | |
| init_tuning_table(tmp_db) | |
| calls = [] | |
| def fake_core(predict_fn, fetch_fn, *, symbol, interval, start_date, end_date, | |
| lookback, pred_len, stride, T, top_p, sample_count, max_anchors): | |
| calls.append((T, top_p)) | |
| # Make T=1.0, top_p=0.85 win clearly | |
| score = 9.0 if (T == 1.0 and top_p == 0.85) else 1.0 | |
| return { | |
| "anchors": 5, "mean_rmse": 1.0, | |
| "hit_rate": 0.5, | |
| "total_return_pct": score, | |
| "max_dd_pct": -1.0, "sharpe": 0.5, | |
| "per_anchor": pd.DataFrame(), | |
| } | |
| monkeypatch.setattr("autotune.backtest_core", fake_core) | |
| fig, summary, grid = run_autotune( | |
| predict_fn=MagicMock(), fetch_fn=MagicMock(), | |
| db_path=tmp_db, symbol="SPY", interval="5min", | |
| start_date="", end_date="", | |
| lookback=200, pred_len=30, stride=30, | |
| sample_count=1, max_anchors=5, | |
| ) | |
| assert sorted(calls) == sorted([(t, p) for t in GRID_T for p in GRID_TOP_P]) | |
| assert len(grid) == 9 | |
| best_row = summary[summary["Field"] == "Best T"].iloc[0]["Value"] | |
| assert float(best_row) == 1.0 | |
| persisted = load_tuning(tmp_db, "SPY", "5min") | |
| assert persisted["best_T"] == 1.0 | |
| assert persisted["best_top_p"] == 0.85 | |
| assert persisted["best_pnl_pct"] == 9.0 | |
| def test_run_autotune_skips_failed_cells(tmp_db, monkeypatch): | |
| init_tuning_table(tmp_db) | |
| def flaky_core(predict_fn, fetch_fn, *, T, top_p, **kw): | |
| if T == 0.5: | |
| raise RuntimeError("fmp blew up") | |
| return {"anchors": 5, "mean_rmse": 1.0, "hit_rate": 0.5, | |
| "total_return_pct": 2.0 + top_p, | |
| "max_dd_pct": -1.0, "sharpe": 0.5, | |
| "per_anchor": pd.DataFrame()} | |
| monkeypatch.setattr("autotune.backtest_core", flaky_core) | |
| fig, summary, grid = run_autotune( | |
| predict_fn=MagicMock(), fetch_fn=MagicMock(), | |
| db_path=tmp_db, symbol="X", interval="5min", | |
| start_date="", end_date="", | |
| lookback=200, pred_len=30, stride=30, | |
| sample_count=1, max_anchors=5, | |
| ) | |
| errored = grid[grid["error"].notna()] | |
| assert len(errored) == 3 # all T=0.5 rows | |
| valid = grid[grid["error"].isna()] | |
| assert len(valid) == 6 | |
| persisted = load_tuning(tmp_db, "X", "5min") | |
| assert persisted["best_T"] != 0.5 # never the failing T | |
| def test_run_autotune_raises_when_all_cells_fail(tmp_db, monkeypatch): | |
| init_tuning_table(tmp_db) | |
| def always_fail(*a, **kw): | |
| raise RuntimeError("everything is broken") | |
| monkeypatch.setattr("autotune.backtest_core", always_fail) | |
| import pytest | |
| with pytest.raises(RuntimeError, match="All grid cells failed"): | |
| run_autotune( | |
| predict_fn=MagicMock(), fetch_fn=MagicMock(), | |
| db_path=tmp_db, symbol="X", interval="5min", | |
| start_date="", end_date="", | |
| lookback=200, pred_len=30, stride=30, | |
| sample_count=1, max_anchors=5, | |
| ) | |
| assert load_tuning(tmp_db, "X", "5min") is None | |
| def test_apply_tuning_returns_values_when_cached_and_enabled(tmp_db): | |
| from autotune import apply_tuning | |
| init_tuning_table(tmp_db) | |
| save_tuning(tmp_db, "SPY", "5min", | |
| {"T": 1.5, "top_p": 0.7, "total_return_pct": 7.0, | |
| "hit_rate": 0.6, "mean_rmse": 0.9}, | |
| [], {"lookback": 200, "pred_len": 30, "stride": 30, | |
| "sample_count": 1, "max_anchors": 20, | |
| "start_date": "", "end_date": ""}) | |
| t_val, top_p_val = apply_tuning(tmp_db, "SPY", "5min", True) | |
| assert t_val == 1.5 | |
| assert top_p_val == 0.7 | |
| def test_apply_tuning_returns_none_when_disabled(tmp_db): | |
| from autotune import apply_tuning | |
| init_tuning_table(tmp_db) | |
| save_tuning(tmp_db, "SPY", "5min", | |
| {"T": 1.5, "top_p": 0.7, "total_return_pct": 7.0, | |
| "hit_rate": 0.6, "mean_rmse": 0.9}, | |
| [], {"lookback": 200, "pred_len": 30, "stride": 30, | |
| "sample_count": 1, "max_anchors": 20, | |
| "start_date": "", "end_date": ""}) | |
| assert apply_tuning(tmp_db, "SPY", "5min", False) == (None, None) | |
| def test_apply_tuning_returns_none_when_no_cache(tmp_db): | |
| from autotune import apply_tuning | |
| init_tuning_table(tmp_db) | |
| assert apply_tuning(tmp_db, "ZZZ", "1min", True) == (None, None) | |