mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
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>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Option A: Generate Kronos predicted-return factor from EUR/USD 1-min data.
|
||||
|
||||
Runs Kronos-mini inference in daily strides (96 bars/day) over all available
|
||||
OHLCV data and saves the resulting factor for use in Predix's factor pipeline.
|
||||
|
||||
Usage:
|
||||
conda activate predix
|
||||
python scripts/kronos_factor_gen.py
|
||||
python scripts/kronos_factor_gen.py --context 512 --pred 96 --device cuda
|
||||
python scripts/kronos_factor_gen.py --device cpu # slower but no GPU needed
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(Path(__file__).parent.parent / ".env")
|
||||
|
||||
import pandas as pd
|
||||
import torch
|
||||
|
||||
DATA_PATH = Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
|
||||
OUTPUT_DIR = Path("results/factors")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate Kronos predicted-return factor")
|
||||
parser.add_argument("--context", type=int, default=512, help="Context window in bars (max 512 for Kronos-mini)")
|
||||
parser.add_argument("--pred", type=int, default=96, help="Prediction horizon in bars (default: 96 = 1 trading day)")
|
||||
parser.add_argument("--stride", type=int, default=None, help="Stride between windows (default: same as --pred)")
|
||||
parser.add_argument("--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu")
|
||||
parser.add_argument("--output", type=str, default=None, help="Output parquet path (default: auto)")
|
||||
args = parser.parse_args()
|
||||
|
||||
stride = args.stride or args.pred
|
||||
|
||||
print(f"Kronos Factor Generator")
|
||||
print(f" Data: {DATA_PATH}")
|
||||
print(f" Context: {args.context} bars")
|
||||
print(f" Pred: {args.pred} bars ({args.pred} min = {args.pred/96:.1f} trading days)")
|
||||
print(f" Stride: {stride} bars")
|
||||
print(f" Device: {args.device}")
|
||||
print()
|
||||
|
||||
if not DATA_PATH.exists():
|
||||
print(f"ERROR: Data not found at {DATA_PATH}")
|
||||
print("Run data conversion first — see README Data Setup section.")
|
||||
raise SystemExit(1)
|
||||
|
||||
from rdagent.components.coder.kronos_adapter import build_kronos_factor
|
||||
|
||||
factor_df = build_kronos_factor(
|
||||
hdf5_path=DATA_PATH,
|
||||
context_bars=args.context,
|
||||
pred_bars=args.pred,
|
||||
stride_bars=stride,
|
||||
device=args.device,
|
||||
)
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
out_path = args.output or OUTPUT_DIR / f"kronos_pred_return_p{args.pred}.parquet"
|
||||
factor_df.to_parquet(out_path)
|
||||
print(f"\nFactor saved to: {out_path}")
|
||||
print(f"Shape: {factor_df.shape}")
|
||||
print(f"Non-NaN: {factor_df['KronosPredReturn'].notna().sum()}")
|
||||
print(f"\nSample (first 5):")
|
||||
print(factor_df.head())
|
||||
|
||||
# Save metadata for predix.py top / best integration
|
||||
meta = {
|
||||
"factor_name": f"KronosPredReturn_p{args.pred}",
|
||||
"description": f"Kronos-mini predicted return, {args.pred}-bar horizon",
|
||||
"model": "NeoQuasar/Kronos-mini",
|
||||
"context_bars": args.context,
|
||||
"pred_bars": args.pred,
|
||||
"stride_bars": stride,
|
||||
"device": args.device,
|
||||
"generated_at": datetime.now().isoformat(),
|
||||
"n_bars": len(factor_df),
|
||||
"n_non_nan": int(factor_df["KronosPredReturn"].notna().sum()),
|
||||
"parquet_path": str(out_path),
|
||||
}
|
||||
meta_path = out_path.with_suffix(".json")
|
||||
with open(meta_path, "w") as f:
|
||||
json.dump(meta, f, indent=2)
|
||||
print(f"Metadata saved to: {meta_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Option B: Evaluate Kronos-mini as a model alongside LightGBM.
|
||||
|
||||
Computes IC (Information Coefficient) and hit rate for Kronos predictions
|
||||
vs actual realized returns. Results are printed for comparison with LightGBM.
|
||||
|
||||
Usage:
|
||||
conda activate predix
|
||||
python scripts/kronos_model_eval.py
|
||||
python scripts/kronos_model_eval.py --pred 30 --context 512 --device cuda
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(Path(__file__).parent.parent / ".env")
|
||||
|
||||
import torch
|
||||
|
||||
DATA_PATH = Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
|
||||
OUTPUT_DIR = Path("results/kronos")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Evaluate Kronos as model (alongside LightGBM)")
|
||||
parser.add_argument("--context", type=int, default=512, help="Context window in bars")
|
||||
parser.add_argument("--pred", type=int, default=30, help="Prediction horizon in bars")
|
||||
parser.add_argument("--stride", type=int, default=None, help="Stride between evaluations (default: pred)")
|
||||
parser.add_argument("--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu")
|
||||
args = parser.parse_args()
|
||||
|
||||
stride = args.stride or args.pred
|
||||
|
||||
print(f"Kronos Model Evaluator (alongside LightGBM)")
|
||||
print(f" Context: {args.context} bars | Pred: {args.pred} bars | Device: {args.device}")
|
||||
print()
|
||||
|
||||
if not DATA_PATH.exists():
|
||||
print(f"ERROR: Data not found at {DATA_PATH}")
|
||||
raise SystemExit(1)
|
||||
|
||||
from rdagent.components.coder.kronos_adapter import evaluate_kronos_model
|
||||
|
||||
print("Running evaluation (this may take several minutes)...")
|
||||
metrics = evaluate_kronos_model(
|
||||
hdf5_path=DATA_PATH,
|
||||
context_bars=args.context,
|
||||
pred_bars=args.pred,
|
||||
stride_bars=stride,
|
||||
device=args.device,
|
||||
)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Kronos-mini Model Evaluation Results")
|
||||
print("=" * 50)
|
||||
print(f" Predictions: {metrics['n_predictions']}")
|
||||
print(f" IC (mean): {metrics['IC_mean']:.4f}")
|
||||
print(f" IC (std): {metrics['IC_std']:.4f}")
|
||||
print(f" IC IR: {metrics['IC_IR']:.4f} (>0.5 = good)")
|
||||
print(f" Hit Rate: {metrics['hit_rate']:.2%} (>50% = directionally useful)")
|
||||
print("=" * 50)
|
||||
print()
|
||||
print("Reference: LightGBM baseline IC typically 0.01–0.05 on 1-min EUR/USD")
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
out = OUTPUT_DIR / f"kronos_eval_ctx{args.context}_pred{args.pred}.json"
|
||||
with open(out, "w") as f:
|
||||
json.dump({**metrics, "context_bars": args.context, "pred_bars": args.pred}, f, indent=2)
|
||||
print(f"\nResults saved to: {out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user