Files
NexQuant/test/backtesting/test_kronos_adapter.py
T
TPTBusiness 3f3a23bd38 feat: integrate Kronos-mini OHLCV foundation model (Option A + B)
Add Kronos-mini (4.1M params, AAAI 2026, MIT) as:
- Option A: predicted-return alpha factor via rolling daily inference
  (kronos_factor_gen.py — stride=96 bars/day, ~2k inference calls)
- Option B: standalone model evaluator alongside LightGBM
  (kronos_model_eval.py — IC / hit-rate vs actual realized returns)

KronosAdapter wraps NeoQuasar/Kronos-mini + Kronos-Tokenizer-2k,
auto-detects GPU, gracefully degrades if ~/Kronos repo is missing.
Factor output: MultiIndex (datetime, instrument) with KronosPredReturn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 09:49:25 +02:00

103 lines
3.8 KiB
Python

"""Tests for KronosAdapter — mock-based, no real model download needed."""
import numpy as np
import pandas as pd
import pytest
def _make_ohlcv(n: int = 600) -> pd.DataFrame:
"""Synthetic 1-min OHLCV DataFrame."""
idx = pd.date_range("2024-01-01", periods=n, freq="1min")
close = 1.1000 + np.cumsum(np.random.randn(n) * 0.0001)
df = pd.DataFrame({
"open": close + np.random.randn(n) * 0.00005,
"high": close + np.abs(np.random.randn(n) * 0.0001),
"low": close - np.abs(np.random.randn(n) * 0.0001),
"close": close,
"volume": np.abs(np.random.randn(n) * 100),
}, index=idx)
return df
def test_ohlcv_conversion():
"""_ohlcv_from_predix renames $ columns correctly."""
from rdagent.components.coder.kronos_adapter import _ohlcv_from_predix
idx = pd.MultiIndex.from_arrays(
[pd.date_range("2024-01-01", periods=3, freq="1min"), ["EURUSD"] * 3],
names=["datetime", "instrument"],
)
predix_df = pd.DataFrame({
"$open": [1.1, 1.2, 1.3],
"$high": [1.15, 1.25, 1.35],
"$low": [1.05, 1.15, 1.25],
"$close": [1.12, 1.22, 1.32],
"$volume": [100.0, 200.0, 300.0],
}, index=idx)
ohlcv = _ohlcv_from_predix(predix_df)
assert "close" in ohlcv.columns
assert "$close" not in ohlcv.columns
assert list(ohlcv.columns) == ["open", "high", "low", "close", "volume"]
def test_kronos_adapter_load_skipped_without_repo(tmp_path, monkeypatch):
"""KronosAdapter gracefully reports unavailable when repo is missing."""
import rdagent.components.coder.kronos_adapter as mod
monkeypatch.setattr(mod, "KRONOS_REPO", tmp_path / "nonexistent")
monkeypatch.setattr(mod, "_KRONOS_AVAILABLE", None)
from rdagent.components.coder.kronos_adapter import KronosAdapter, _ensure_kronos
available = _ensure_kronos()
assert available is False
def test_build_kronos_factor_mock(tmp_path, monkeypatch):
"""build_kronos_factor produces correct MultiIndex output with mocked predictor."""
import rdagent.components.coder.kronos_adapter as mod
# Mock the adapter so no real Kronos load happens
class MockAdapter:
def load(self): return self
def predict_next_bars(self, ohlcv_df, context_bars, pred_bars, **kw):
idx = pd.date_range(ohlcv_df.index[-1], periods=pred_bars + 1, freq="1min")[1:]
last_close = float(ohlcv_df["close"].iloc[-1])
return pd.DataFrame({
"open": last_close * (1 + np.random.randn(pred_bars) * 0.001),
"close": last_close * (1 + np.random.randn(pred_bars) * 0.001),
"high": last_close * 1.001,
"low": last_close * 0.999,
"volume": 100.0,
}, index=idx)
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: MockAdapter())
# Write minimal HDF5
n = 300
idx = pd.MultiIndex.from_arrays(
[pd.date_range("2024-01-01", periods=n, freq="1min"), ["EURUSD"] * n],
names=["datetime", "instrument"],
)
df = pd.DataFrame({
"$open": np.random.rand(n).astype("float32") + 1.1,
"$close": np.random.rand(n).astype("float32") + 1.1,
"$high": np.random.rand(n).astype("float32") + 1.11,
"$low": np.random.rand(n).astype("float32") + 1.09,
"$volume": np.random.rand(n).astype("float32") * 100,
}, index=idx)
h5_path = tmp_path / "intraday_pv.h5"
df.to_hdf(h5_path, key="data", mode="w")
result = mod.build_kronos_factor(
hdf5_path=h5_path,
context_bars=100,
pred_bars=20,
stride_bars=20,
device="cpu",
)
assert isinstance(result, pd.DataFrame)
assert result.index.names == ["datetime", "instrument"]
assert "KronosPredReturn" in result.columns
assert result["KronosPredReturn"].notna().sum() > 0