2026-05-10 20:48:07 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
"""
|
|
|
|
|
NexQuant Multi-Asset Data Pipeline — Download + Test on expanded universe.
|
|
|
|
|
Downloads DXY, Gold, S&P 500, Bund, EUR/USD extended history via yfinance.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json, sys, time
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
import pandas as pd
|
|
|
|
|
import yfinance as yf
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
|
|
|
|
|
|
DATA_DIR = Path("git_ignore_folder/factor_implementation_source_data")
|
|
|
|
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# Multi-asset tickers (free via Yahoo Finance)
|
|
|
|
|
ASSETS = {
|
|
|
|
|
"EURUSD": "EURUSD=X",
|
|
|
|
|
"DXY": "DX-Y.NYB", # US Dollar Index
|
|
|
|
|
"GOLD": "GC=F", # Gold Futures
|
|
|
|
|
"SPX": "^GSPC", # S&P 500
|
|
|
|
|
"BUND": "BUN24-EUX", # German Bund (approximate)
|
|
|
|
|
"GBPUSD": "GBPUSD=X",
|
|
|
|
|
"USDJPY": "USDJPY=X",
|
|
|
|
|
"OIL": "CL=F", # Crude Oil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def download_asset(name: str, ticker: str, period: str = "max") -> pd.DataFrame:
|
|
|
|
|
print(f" Downloading {name} ({ticker})...")
|
|
|
|
|
try:
|
|
|
|
|
data = yf.download(ticker, period=period, progress=False, auto_adjust=True)
|
|
|
|
|
if data.empty:
|
|
|
|
|
print(f" Empty — skipping")
|
|
|
|
|
return None
|
|
|
|
|
close = data["Close"]
|
|
|
|
|
if isinstance(close, pd.DataFrame):
|
|
|
|
|
close = close.iloc[:, 0]
|
|
|
|
|
close.name = name
|
|
|
|
|
print(f" {len(close):,} bars ({close.index[0].date()} - {close.index[-1].date()})")
|
|
|
|
|
return close
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f" Failed: {e}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
print(f"\n{'='*60}")
|
|
|
|
|
print(" NexQuant Multi-Asset Data Download")
|
|
|
|
|
print(f"{'='*60}\n")
|
|
|
|
|
|
|
|
|
|
all_data = {}
|
|
|
|
|
for name, ticker in ASSETS.items():
|
|
|
|
|
series = download_asset(name, ticker)
|
|
|
|
|
if series is not None and len(series) > 100:
|
|
|
|
|
all_data[name] = series
|
|
|
|
|
|
|
|
|
|
if not all_data:
|
|
|
|
|
print("No data downloaded!")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Build combined DataFrame
|
|
|
|
|
df = pd.DataFrame(all_data).dropna(how="all")
|
|
|
|
|
print(f"\nCombined data: {len(df):,} daily bars, {len(df.columns)} assets")
|
|
|
|
|
print(f"Date range: {df.index[0].date()} - {df.index[-1].date()}")
|
|
|
|
|
|
|
|
|
|
# Save to HDF5
|
|
|
|
|
h5_path = DATA_DIR / "multi_asset_daily.h5"
|
|
|
|
|
df.to_hdf(h5_path, key="data", mode="w")
|
|
|
|
|
print(f"Saved to {h5_path}")
|
|
|
|
|
|
|
|
|
|
# Quick strategy test
|
|
|
|
|
print(f"\n{'='*60}")
|
|
|
|
|
print(" Quick Daily Strategy Test on Multi-Asset")
|
|
|
|
|
print(f"{'='*60}")
|
|
|
|
|
|
2026-05-22 15:10:36 +02:00
|
|
|
from rdagent.components.backtesting.vbt_backtest import backtest_signal_risk
|
2026-05-10 20:48:07 +02:00
|
|
|
|
|
|
|
|
for asset in df.columns:
|
|
|
|
|
c = df[asset].dropna()
|
|
|
|
|
if len(c) < 500:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# SMA 10/30
|
|
|
|
|
f = c.rolling(10).mean()
|
|
|
|
|
s = c.rolling(30).mean()
|
|
|
|
|
sig = pd.Series(0.0, index=c.index)
|
|
|
|
|
sig[f > s] = 1
|
|
|
|
|
sig[f < s] = -1
|
|
|
|
|
|
2026-05-22 15:10:36 +02:00
|
|
|
r = backtest_signal_risk(c, sig.fillna(0), txn_cost_bps=2.14, wf_rolling=True)
|
2026-05-10 20:48:07 +02:00
|
|
|
oos = r.get("wf_oos_sharpe_mean") or r.get("oos_sharpe", -999)
|
|
|
|
|
oos_m = r.get("oos_monthly_return_pct", 0) or 0
|
|
|
|
|
status = "✅" if oos > 0 else " "
|
|
|
|
|
print(f" {asset:<10} SMA10/30: OOS={oos:+8.2f} Mon={oos_m:+6.2f}% {status}")
|
|
|
|
|
|
|
|
|
|
# Also test extended EUR/USD
|
|
|
|
|
eurusd = df["EURUSD"].dropna()
|
|
|
|
|
print(f"\n Extended EUR/USD: {len(eurusd):,} bars")
|
|
|
|
|
c = eurusd
|
|
|
|
|
f = c.rolling(10).mean()
|
|
|
|
|
s = c.rolling(30).mean()
|
|
|
|
|
sig = pd.Series(0.0, index=c.index)
|
|
|
|
|
sig[f > s] = 1
|
|
|
|
|
sig[f < s] = -1
|
2026-05-22 15:10:36 +02:00
|
|
|
r = backtest_signal_risk(c, sig.fillna(0), txn_cost_bps=2.14, wf_rolling=True)
|
2026-05-10 20:48:07 +02:00
|
|
|
oos = r.get("wf_oos_sharpe_mean") or r.get("oos_sharpe", -999)
|
|
|
|
|
print(f" SMA10/30 extended: OOS={oos:+8.2f} Mon={r.get('oos_monthly_return_pct',0):+.2f}%")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|