mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 23:47:46 +00:00
fix: close log file handle, fix FTMO equity double-count, remove bare except
This commit is contained in:
@@ -13,6 +13,7 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(Path(__file__).parent / ".env")
|
||||
|
||||
import typer
|
||||
@@ -114,7 +115,7 @@ def _ensure_kronos_factor_in_pool(con) -> None:
|
||||
color = "green" if abs(ic) > 0.01 else "yellow"
|
||||
con.print(
|
||||
f" [bold {color}]Kronos Factor ready:[/bold {color}] IC={ic:.4f}, "
|
||||
f"Hit-Rate={hit_rate:.1%} — added to strategy pool"
|
||||
f"Hit-Rate={hit_rate:.1%} — added to strategy pool",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -201,9 +202,9 @@ def quant(
|
||||
predix health - Check system health and configuration
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
|
||||
# ---- Parallel Run Isolation ----
|
||||
# When run_id > 0, isolate all outputs (logs, results, workspace)
|
||||
@@ -226,10 +227,9 @@ def quant(
|
||||
console.print(f" [dim]Log: {log_file}[/dim]")
|
||||
console.print(f" [dim]Results: results/runs/run{run_id}/[/dim]")
|
||||
console.print(f" [dim]Workspace: {workspace_dir.name}/[/dim]")
|
||||
else:
|
||||
# Single run mode: default log file
|
||||
if log_file is None:
|
||||
log_file = "fin_quant.log"
|
||||
# Single run mode: default log file
|
||||
elif log_file is None:
|
||||
log_file = "fin_quant.log"
|
||||
|
||||
# ---- Log File Setup (daily-rotated) ----
|
||||
from datetime import datetime as _dt
|
||||
@@ -237,10 +237,14 @@ def quant(
|
||||
_daily_dir = Path(__file__).parent / "logs" / _today
|
||||
_daily_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
_log_f = None
|
||||
_orig_stdout = sys.stdout
|
||||
_orig_stderr = sys.stderr
|
||||
|
||||
if log_file.lower() != "none":
|
||||
log_path = _daily_dir / log_file
|
||||
# Open log file for appending (raw stdout/stderr capture)
|
||||
log_f = open(log_path, "a", encoding="utf-8")
|
||||
_log_f = open(log_path, "a", encoding="utf-8")
|
||||
|
||||
# Redirect stdout and stderr to both console and log file
|
||||
class TeeWriter:
|
||||
@@ -252,18 +256,18 @@ def quant(
|
||||
try:
|
||||
s.write(data)
|
||||
s.flush()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
for s in self._streams:
|
||||
try:
|
||||
s.flush()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sys.stdout = TeeWriter(sys.__stdout__, log_f)
|
||||
sys.stderr = TeeWriter(sys.__stderr__, log_f)
|
||||
sys.stdout = TeeWriter(_orig_stdout, _log_f)
|
||||
sys.stderr = TeeWriter(_orig_stderr, _log_f)
|
||||
|
||||
console.print(f"\n[dim]📝 Logging to: logs/{_today}/{log_file}[/dim]")
|
||||
else:
|
||||
@@ -276,7 +280,7 @@ def quant(
|
||||
if not api_key:
|
||||
console.print("\n[bold red]❌ OPENROUTER_API_KEY not set in .env[/bold red]")
|
||||
console.print("[yellow]Add your API key to .env:[/yellow]")
|
||||
console.print(' OPENROUTER_API_KEY=sk-or-your-key-here')
|
||||
console.print(" OPENROUTER_API_KEY=sk-or-your-key-here")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
# Setup both API keys for load balancing
|
||||
@@ -289,7 +293,7 @@ def quant(
|
||||
os.environ["LITELLM_PARALLEL_CALLS"] = "2"
|
||||
console.print(f"\n[bold blue]🌐 Using OpenRouter (2 API Keys):[/bold blue] [cyan]{os.environ['CHAT_MODEL']}[/cyan]")
|
||||
console.print(f" [dim]Keys: {api_key[:15]}*** + {api_key_2[:15]}***[/dim]")
|
||||
console.print(f" [dim]Parallel: 2 concurrent requests[/dim]")
|
||||
console.print(" [dim]Parallel: 2 concurrent requests[/dim]")
|
||||
else:
|
||||
os.environ["OPENAI_API_KEY"] = api_key
|
||||
console.print(f"\n[bold blue]🌐 Using OpenRouter:[/bold blue] [cyan]{os.environ['CHAT_MODEL']}[/cyan]")
|
||||
@@ -307,7 +311,7 @@ def quant(
|
||||
# ---- Dashboards ----
|
||||
if dashboard:
|
||||
def start_web_dashboard():
|
||||
console.print(f"\n[bold green]🚀 Web Dashboard: http://localhost:5000[/bold green]")
|
||||
console.print("\n[bold green]🚀 Web Dashboard: http://localhost:5000[/bold green]")
|
||||
subprocess.run(
|
||||
["python", "web/dashboard_api.py"],
|
||||
cwd=str(Path(__file__).parent),
|
||||
@@ -332,7 +336,7 @@ def quant(
|
||||
from rdagent.app.qlib_rd_loop.quant import main as fin_quant
|
||||
from rdagent.log.daily_log import session as _daily_session
|
||||
|
||||
console.print(f"\n[bold cyan]📊 Starting EURUSD Trading Loop...[/bold cyan]\n")
|
||||
console.print("\n[bold cyan]📊 Starting EURUSD Trading Loop...[/bold cyan]\n")
|
||||
|
||||
_ctx = {"model": model}
|
||||
if run_id:
|
||||
@@ -342,11 +346,17 @@ def quant(
|
||||
if step_n:
|
||||
_ctx["steps"] = step_n
|
||||
|
||||
with _daily_session("fin_quant", **_ctx):
|
||||
fin_quant(
|
||||
step_n=step_n,
|
||||
loop_n=loop_n,
|
||||
)
|
||||
try:
|
||||
with _daily_session("fin_quant", **_ctx):
|
||||
fin_quant(
|
||||
step_n=step_n,
|
||||
loop_n=loop_n,
|
||||
)
|
||||
finally:
|
||||
if _log_f is not None:
|
||||
sys.stdout = _orig_stdout
|
||||
sys.stderr = _orig_stderr
|
||||
_log_f.close()
|
||||
|
||||
|
||||
@app.command()
|
||||
@@ -415,8 +425,8 @@ def evaluate(
|
||||
predix portfolio - Select a diversified portfolio of uncorrelated factors
|
||||
predix quant - Generate new factors via LLM trading loop
|
||||
"""
|
||||
from rich.panel import Panel
|
||||
from rdagent.log.daily_log import session as _daily_session
|
||||
from rich.panel import Panel
|
||||
|
||||
console.print(Panel(
|
||||
"[bold cyan]📊 Predix Factor Evaluator[/bold cyan]\n"
|
||||
@@ -496,11 +506,12 @@ def top(
|
||||
predix portfolio - Select diversified portfolio from top factors
|
||||
predix build-strategies - Combine factors into trading strategies
|
||||
"""
|
||||
import json
|
||||
import glob as glob_module
|
||||
import json
|
||||
|
||||
import numpy as np
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
factors_dir = Path(__file__).parent / "results" / "factors"
|
||||
if not factors_dir.exists():
|
||||
@@ -642,16 +653,16 @@ def portfolio(
|
||||
predix top - View top factors before portfolio selection
|
||||
predix build-strategies - Build strategies from selected factors
|
||||
"""
|
||||
import json
|
||||
import glob as glob_module
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
import numpy as np
|
||||
|
||||
import pandas as pd
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
||||
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
|
||||
from rich.table import Table
|
||||
|
||||
factors_dir = Path(__file__).parent / "results" / "factors"
|
||||
if not factors_dir.exists():
|
||||
@@ -683,12 +694,12 @@ def portfolio(
|
||||
# 2. Evaluate candidates to get time-series values for correlation
|
||||
# We need to run the factor code to get the series of values.
|
||||
# We do this sequentially to avoid OOM.
|
||||
|
||||
|
||||
# Locate data file
|
||||
data_file = Path(__file__).parent / "git_ignore_folder" / "factor_implementation_source_data" / "intraday_pv.h5"
|
||||
if not data_file.exists():
|
||||
data_file = Path(__file__).parent / "git_ignore_folder" / "factor_implementation_source_data_debug" / "intraday_pv.h5"
|
||||
|
||||
|
||||
if not data_file.exists():
|
||||
console.print("[red]Source data file (intraday_pv.h5) not found.[/red]")
|
||||
return
|
||||
@@ -705,11 +716,11 @@ def portfolio(
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task(f"Computing values for {len(candidates)} factors...", total=len(candidates))
|
||||
|
||||
|
||||
for cand in candidates:
|
||||
fname = cand.get("factor_name", "unknown")
|
||||
fcode = cand.get("factor_code", "")
|
||||
|
||||
|
||||
if not fcode:
|
||||
errors.append((fname, "No code in JSON"))
|
||||
progress.advance(task)
|
||||
@@ -725,10 +736,10 @@ def portfolio(
|
||||
# If symlink fails, copy the file
|
||||
import shutil
|
||||
shutil.copy(str(data_file), str(tmp_path / "intraday_pv.h5"))
|
||||
|
||||
|
||||
# Write code
|
||||
(tmp_path / "factor.py").write_text(fcode)
|
||||
|
||||
|
||||
try:
|
||||
# Run factor
|
||||
result = subprocess.run(
|
||||
@@ -736,16 +747,16 @@ def portfolio(
|
||||
cwd=tmp_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120 # 2 min timeout per factor
|
||||
timeout=120, # 2 min timeout per factor
|
||||
)
|
||||
|
||||
|
||||
# Read result
|
||||
res_file = tmp_path / "result.h5"
|
||||
if res_file.exists():
|
||||
df = pd.read_hdf(str(res_file), key="data")
|
||||
# Get the series (first column)
|
||||
series = df.iloc[:, 0]
|
||||
|
||||
|
||||
# Count non-NaN values
|
||||
non_nan = series.count()
|
||||
if non_nan < 1000:
|
||||
@@ -765,7 +776,7 @@ def portfolio(
|
||||
except Exception as e:
|
||||
errors.append((fname, str(e)[:100]))
|
||||
progress.update(task, description=f"{fname} ❌ (Error)")
|
||||
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# Show summary of errors
|
||||
@@ -779,7 +790,7 @@ def portfolio(
|
||||
if len(factor_series) < 3:
|
||||
console.print("[red]Not enough valid factor series to build portfolio (need at least 3).[/red]")
|
||||
console.print("[yellow]Tip: Factors might be producing mostly NaN values or failing execution.[/yellow]")
|
||||
|
||||
|
||||
# Fallback: Show top factors by IC without diversification
|
||||
console.print("\n[dim]Showing top factors by IC instead:[/dim]")
|
||||
table = Table(
|
||||
@@ -797,50 +808,49 @@ def portfolio(
|
||||
str(i),
|
||||
cand.get("factor_name", "unknown")[:38],
|
||||
f"{cand.get('ic', 0):.6f}",
|
||||
f"{cand.get('sharpe', 0):.4f}" if cand.get('sharpe') else "N/A",
|
||||
f"{cand.get('sharpe', 0):.4f}" if cand.get("sharpe") else "N/A",
|
||||
)
|
||||
|
||||
|
||||
console.print(table)
|
||||
return
|
||||
|
||||
# 3. Build Correlation Matrix
|
||||
console.print(f"\n[dim]Building correlation matrix from {len(factor_series)} factors...[/dim]")
|
||||
|
||||
|
||||
# Align indices and drop NaN
|
||||
combined = pd.DataFrame(factor_series).dropna()
|
||||
|
||||
|
||||
if combined.empty or len(combined) < 100:
|
||||
console.print("[red]Not enough valid overlapping data to compute correlation.[/red]")
|
||||
console.print("[dim]This means the factors produce values at different times or have too many NaN values.[/dim]")
|
||||
return
|
||||
|
||||
corr_matrix = combined.corr().fillna(0)
|
||||
ic_map = {cand['factor_name']: cand.get('ic', 0) for cand in candidates}
|
||||
ic_map = {cand["factor_name"]: cand.get("ic", 0) for cand in candidates}
|
||||
|
||||
# 4. Greedy Selection
|
||||
selected = []
|
||||
remaining = list(corr_matrix.columns)
|
||||
|
||||
|
||||
# Sort remaining by IC to prioritize high IC factors
|
||||
remaining.sort(key=lambda x: abs(ic_map.get(x, 0)), reverse=True)
|
||||
|
||||
for factor in remaining:
|
||||
if len(selected) >= target:
|
||||
break
|
||||
|
||||
|
||||
# If it's the first one, just take it
|
||||
if not selected:
|
||||
selected.append(factor)
|
||||
continue
|
||||
|
||||
|
||||
# Check correlation with already selected
|
||||
# We want max(|corr|) < max_corr
|
||||
max_c = 0
|
||||
for sel in selected:
|
||||
c = abs(corr_matrix.loc[factor, sel])
|
||||
if c > max_c:
|
||||
max_c = c
|
||||
|
||||
max_c = max(max_c, c)
|
||||
|
||||
if max_c < max_corr:
|
||||
selected.append(factor)
|
||||
|
||||
@@ -858,23 +868,23 @@ def portfolio(
|
||||
|
||||
for i, fname in enumerate(selected, 1):
|
||||
# Find original data for display
|
||||
data = next((c for c in candidates if c['factor_name'] == fname), {})
|
||||
ic = data.get('ic')
|
||||
sharpe = data.get('sharpe')
|
||||
|
||||
data = next((c for c in candidates if c["factor_name"] == fname), {})
|
||||
ic = data.get("ic")
|
||||
sharpe = data.get("sharpe")
|
||||
|
||||
# Calculate max corr with other selected factors
|
||||
max_c_val = 0
|
||||
for s in selected:
|
||||
if s != fname:
|
||||
val = abs(corr_matrix.loc[fname, s])
|
||||
if val > max_c_val: max_c_val = val
|
||||
max_c_val = max(max_c_val, val)
|
||||
|
||||
table.add_row(
|
||||
str(i),
|
||||
fname[:38],
|
||||
f"{ic:.6f}" if ic is not None else "N/A",
|
||||
f"{sharpe:.4f}" if sharpe is not None else "N/A",
|
||||
f"{max_c_val:.4f}" if max_c_val > 0 else "-"
|
||||
f"{max_c_val:.4f}" if max_c_val > 0 else "-",
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
@@ -884,20 +894,20 @@ def portfolio(
|
||||
"selected_factors": selected,
|
||||
"max_correlation": max_corr,
|
||||
"pool_size": top,
|
||||
"timestamp": pd.Timestamp.now().isoformat()
|
||||
"timestamp": pd.Timestamp.now().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
out_dir = Path(__file__).parent / "results" / "portfolio"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
out_file = out_dir / "selected_factors.json"
|
||||
|
||||
|
||||
with open(out_file, "w") as f:
|
||||
json.dump(portfolio_data, f, indent=2)
|
||||
|
||||
|
||||
console.print(Panel(
|
||||
f"[bold]Portfolio saved to results/portfolio/selected_factors.json[/bold]\n"
|
||||
f"Selected {len(selected)} unique factors from {top} candidates.",
|
||||
border_style="green"
|
||||
border_style="green",
|
||||
))
|
||||
|
||||
|
||||
@@ -943,13 +953,12 @@ def portfolio_simple(
|
||||
predix top - View top factors before portfolio selection
|
||||
predix build-strategies - Build strategies from selected factors
|
||||
"""
|
||||
import json
|
||||
import glob as glob_module
|
||||
import re
|
||||
import numpy as np
|
||||
import json
|
||||
|
||||
import pandas as pd
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
factors_dir = Path(__file__).parent / "results" / "factors"
|
||||
if not factors_dir.exists():
|
||||
@@ -993,14 +1002,14 @@ def portfolio_simple(
|
||||
for cand in candidates:
|
||||
fname = cand.get("factor_name", "").lower()
|
||||
assigned = False
|
||||
|
||||
|
||||
# Check each category's keywords
|
||||
for cat, keywords in categories.items():
|
||||
if any(kw in fname for kw in keywords):
|
||||
categorized[cat].append(cand)
|
||||
assigned = True
|
||||
break
|
||||
|
||||
|
||||
if not assigned:
|
||||
categorized["other"].append(cand)
|
||||
|
||||
@@ -1011,7 +1020,7 @@ def portfolio_simple(
|
||||
best = categorized[cat][0] # Already sorted by IC
|
||||
selected.append({
|
||||
"factor": best,
|
||||
"category": cat.capitalize() if cat != "other" else "Other"
|
||||
"category": cat.capitalize() if cat != "other" else "Other",
|
||||
})
|
||||
|
||||
# 5. Display Results
|
||||
@@ -1034,7 +1043,7 @@ def portfolio_simple(
|
||||
cand.get("factor_name", "unknown")[:38],
|
||||
cat,
|
||||
f"{cand.get('ic', 0):.6f}",
|
||||
f"{cand.get('sharpe', 0):.4f}" if cand.get('sharpe') else "N/A",
|
||||
f"{cand.get('sharpe', 0):.4f}" if cand.get("sharpe") else "N/A",
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
@@ -1044,7 +1053,7 @@ def portfolio_simple(
|
||||
"selected_factors": [item["factor"]["factor_name"] for item in selected],
|
||||
"categories": {item["category"]: item["factor"]["factor_name"] for item in selected},
|
||||
"method": "simple_keyword_categorization",
|
||||
"timestamp": str(pd.Timestamp.now().isoformat())
|
||||
"timestamp": str(pd.Timestamp.now().isoformat()),
|
||||
}
|
||||
|
||||
out_dir = Path(__file__).parent / "results" / "portfolio"
|
||||
@@ -1057,7 +1066,7 @@ def portfolio_simple(
|
||||
console.print(Panel(
|
||||
f"[bold]Simple Portfolio saved to results/portfolio/portfolio_simple.json[/bold]\n"
|
||||
f"Selected {len(selected)} factors across {len([c for c in categorized if categorized[c]])} categories.",
|
||||
border_style="green"
|
||||
border_style="green",
|
||||
))
|
||||
|
||||
|
||||
@@ -1120,12 +1129,10 @@ def build_strategies(
|
||||
predix portfolio - Select diversified factors before combining
|
||||
predix top - View top factors before building strategies
|
||||
"""
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyBuilder
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
console.print(Panel(
|
||||
"[bold cyan]🏗️ Predix Strategy Builder[/bold cyan]\n"
|
||||
@@ -1281,9 +1288,10 @@ def build_strategies_ai(
|
||||
predix quant - Generate new alpha factors via LLM trading loop
|
||||
predix evaluate - Evaluate factors before strategy building
|
||||
"""
|
||||
from rich.panel import Panel
|
||||
from pathlib import Path
|
||||
|
||||
from rich.panel import Panel
|
||||
|
||||
console.print(Panel(
|
||||
"[bold cyan]🧠 StrategyCoSTEER - AI Strategy Builder[/bold cyan]\n"
|
||||
"Generating trading strategies from existing factors\n"
|
||||
@@ -1309,7 +1317,7 @@ def build_strategies_ai(
|
||||
# Setup LLM environment (same as quant command)
|
||||
api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENAI_API_KEY", "")
|
||||
api_key_2 = os.getenv("OPENROUTER_API_KEY_2", "")
|
||||
|
||||
|
||||
if api_key and not api_key.startswith("sk-or-"):
|
||||
# OPENROUTER_API_KEY not set, try to use what we have
|
||||
api_key = os.getenv("OPENROUTER_API_KEY", api_key)
|
||||
@@ -1336,8 +1344,8 @@ def build_strategies_ai(
|
||||
return
|
||||
|
||||
# Load evaluated factors
|
||||
import json
|
||||
import glob as glob_module
|
||||
import json
|
||||
|
||||
factors = []
|
||||
for f in glob_module.glob(str(factors_dir / "*.json")):
|
||||
@@ -1421,15 +1429,15 @@ def build_strategies_ai(
|
||||
|
||||
for i, r in enumerate(results, 1):
|
||||
# Monthly return: use real backtest if available, else estimate
|
||||
rb = r.get('real_backtest', {})
|
||||
if isinstance(rb, dict) and rb.get('status') == 'success':
|
||||
monthly_pct = rb.get('monthly_return_pct', r.get('monthly_return_pct', 0))
|
||||
n_trades = rb.get('n_trades', '-')
|
||||
real_ic = rb.get('ic', 0)
|
||||
rb = r.get("real_backtest", {})
|
||||
if isinstance(rb, dict) and rb.get("status") == "success":
|
||||
monthly_pct = rb.get("monthly_return_pct", r.get("monthly_return_pct", 0))
|
||||
n_trades = rb.get("n_trades", "-")
|
||||
real_ic = rb.get("ic", 0)
|
||||
else:
|
||||
monthly_pct = r.get('monthly_return_pct', r.get('real_monthly_return', 0))
|
||||
n_trades = '-'
|
||||
real_ic = rb.get('ic', 0) if isinstance(rb, dict) else 0
|
||||
monthly_pct = r.get("monthly_return_pct", r.get("real_monthly_return", 0))
|
||||
n_trades = "-"
|
||||
real_ic = rb.get("ic", 0) if isinstance(rb, dict) else 0
|
||||
|
||||
table.add_row(
|
||||
str(i),
|
||||
@@ -1522,7 +1530,7 @@ def status():
|
||||
# Process check
|
||||
result = subprocess.run(
|
||||
["pgrep", "-f", "fin_quant"],
|
||||
capture_output=True, text=True
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
console.print("[bold green]✅ Trading Loop: RUNNING[/bold green]")
|
||||
@@ -1540,7 +1548,7 @@ def status():
|
||||
factors = c.fetchone()[0]
|
||||
conn.close()
|
||||
|
||||
console.print(f"\n📊 Results:")
|
||||
console.print("\n📊 Results:")
|
||||
console.print(f" Backtest runs: {runs}")
|
||||
console.print(f" Factors: {factors}")
|
||||
|
||||
@@ -1617,6 +1625,7 @@ def best(
|
||||
$ predix best -n 50 --export /tmp/top.json
|
||||
"""
|
||||
import json
|
||||
|
||||
from rich.table import Table
|
||||
|
||||
items = _load_strategies()
|
||||
@@ -1733,7 +1742,7 @@ def kronos_factor(
|
||||
console.print("Run data conversion first — see README Data Setup section.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"[bold]Kronos Factor Generator[/bold]")
|
||||
console.print("[bold]Kronos Factor Generator[/bold]")
|
||||
console.print(f" Context: [cyan]{context}[/cyan] bars | Pred: [cyan]{pred}[/cyan] bars | Device: [cyan]{_device}[/cyan]")
|
||||
|
||||
from rdagent.components.coder.kronos_adapter import build_kronos_factor
|
||||
@@ -1816,7 +1825,7 @@ def kronos_eval(
|
||||
console.print(f"[red]ERROR: Data not found at {data_path}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"[bold]Kronos Model Evaluator[/bold] (alongside LightGBM)")
|
||||
console.print("[bold]Kronos Model Evaluator[/bold] (alongside LightGBM)")
|
||||
console.print(f" Context: [cyan]{context}[/cyan] bars | Pred: [cyan]{pred}[/cyan] bars | Device: [cyan]{_device}[/cyan]")
|
||||
console.print(" Running evaluation...")
|
||||
|
||||
@@ -1831,12 +1840,12 @@ def kronos_eval(
|
||||
batch_size=batch_size,
|
||||
)
|
||||
|
||||
console.print(f"\n[bold]Kronos-mini Results[/bold]")
|
||||
console.print("\n[bold]Kronos-mini Results[/bold]")
|
||||
console.print(f" Predictions: [cyan]{metrics['n_predictions']}[/cyan]")
|
||||
console.print(f" IC (mean): [{'green' if metrics['IC_mean'] > 0.02 else 'yellow'}]{metrics['IC_mean']:.4f}[/]")
|
||||
console.print(f" IC IR: [{'green' if metrics['IC_IR'] > 0.5 else 'yellow'}]{metrics['IC_IR']:.4f}[/] (>0.5 = strong signal)")
|
||||
console.print(f" Hit Rate: [{'green' if metrics['hit_rate'] > 0.52 else 'yellow'}]{metrics['hit_rate']:.2%}[/] (>50% = directionally useful)")
|
||||
console.print(f"\n[dim]Reference: LightGBM baseline IC typically 0.01–0.05 on 1-min EUR/USD[/dim]")
|
||||
console.print("\n[dim]Reference: LightGBM baseline IC typically 0.01–0.05 on 1-min EUR/USD[/dim]")
|
||||
|
||||
import json as _json
|
||||
out_dir = Path("results/kronos")
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
"""
|
||||
Predix Risk Management - Korrelation, Portfolio-Optimierung
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
|
||||
class CorrelationAnalyzer:
|
||||
def __init__(self, lookback: int = 60):
|
||||
self.lookback = lookback
|
||||
|
||||
|
||||
def calculate_matrix(self, returns: pd.DataFrame) -> pd.DataFrame:
|
||||
return returns.dropna().corr()
|
||||
|
||||
def find_uncorrelated(self, corr: pd.DataFrame, threshold: float = 0.3) -> List[str]:
|
||||
|
||||
def find_uncorrelated(self, corr: pd.DataFrame, threshold: float = 0.3) -> list[str]:
|
||||
result = []
|
||||
for f in corr.columns:
|
||||
others = [x for x in corr.columns if x != f]
|
||||
@@ -28,9 +26,9 @@ class PortfolioOptimizer:
|
||||
try:
|
||||
w = np.linalg.inv(cov.values) @ exp_ret.values
|
||||
return w / np.sum(w)
|
||||
except:
|
||||
except np.linalg.LinAlgError:
|
||||
return np.ones(len(exp_ret)) / len(exp_ret)
|
||||
|
||||
|
||||
def risk_parity(self, cov: pd.DataFrame, max_iter: int = 100) -> np.ndarray:
|
||||
n = cov.shape[0]
|
||||
w = np.ones(n) / n
|
||||
@@ -53,36 +51,36 @@ class AdvancedRiskManager:
|
||||
self.max_dd = max_dd
|
||||
self.corr_analyzer = CorrelationAnalyzer()
|
||||
self.optimizer = PortfolioOptimizer()
|
||||
|
||||
def check_limits(self, weights: np.ndarray, vol: float, dd: float) -> Dict[str, bool]:
|
||||
|
||||
def check_limits(self, weights: np.ndarray, vol: float, dd: float) -> dict[str, bool]:
|
||||
return {
|
||||
'position_limit': np.max(np.abs(weights)) <= self.max_pos,
|
||||
'leverage_limit': np.sum(np.abs(weights)) <= self.max_lev,
|
||||
'drawdown_limit': abs(dd) <= self.max_dd,
|
||||
"position_limit": np.max(np.abs(weights)) <= self.max_pos,
|
||||
"leverage_limit": np.sum(np.abs(weights)) <= self.max_lev,
|
||||
"drawdown_limit": abs(dd) <= self.max_dd,
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Risk Test ===")
|
||||
np.random.seed(42)
|
||||
n, names = 252, ['Mom', 'MeanRev', 'Vol', 'Volu', 'ML']
|
||||
n, names = 252, ["Mom", "MeanRev", "Vol", "Volu", "ML"]
|
||||
ret = pd.DataFrame(np.random.randn(n, 5), columns=names)
|
||||
|
||||
|
||||
corr = CorrelationAnalyzer().calculate_matrix(ret)
|
||||
print("Korrelationsmatrix:")
|
||||
print(corr.round(2))
|
||||
|
||||
|
||||
opt = PortfolioOptimizer()
|
||||
exp_ret = pd.Series([0.1, 0.08, 0.06, 0.07, 0.12], index=names)
|
||||
cov = ret.cov() * 252
|
||||
|
||||
|
||||
mv = opt.mean_variance(exp_ret, cov)
|
||||
print("\nMean-Variance:")
|
||||
for n, w in zip(names, mv): print(f" {n}: {w:.2%}")
|
||||
|
||||
|
||||
rp = opt.risk_parity(cov)
|
||||
print("\nRisk Parity:")
|
||||
for n, w in zip(names, rp): print(f" {n}: {w:.2%}")
|
||||
|
||||
|
||||
rm = AdvancedRiskManager()
|
||||
checks = rm.check_limits(mv, 0.15, -0.08)
|
||||
print(f"\nLimits OK: {all(checks.values())}")
|
||||
|
||||
@@ -19,7 +19,7 @@ Design goals
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -69,7 +69,7 @@ def _cross_check_with_vbt(
|
||||
txn_cost: float,
|
||||
manual_total_return: float,
|
||||
freq: str,
|
||||
) -> Optional[float]:
|
||||
) -> float | None:
|
||||
"""Run a vectorbt simulation and return its total_return for comparison."""
|
||||
if not VBT_AVAILABLE:
|
||||
return None
|
||||
@@ -95,9 +95,9 @@ def backtest_signal(
|
||||
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
||||
freq: str = "1min",
|
||||
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
||||
forward_returns: Optional[pd.Series] = None,
|
||||
forward_returns: pd.Series | None = None,
|
||||
cross_check: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Run a single-asset backtest from a position signal.
|
||||
|
||||
@@ -204,7 +204,7 @@ def backtest_signal(
|
||||
calmar = ann_return_arith / abs(max_dd) if max_dd < 0 else 0.0
|
||||
|
||||
trade_pnl = _compute_trade_pnl(position, strategy_returns)
|
||||
n_trades = int(len(trade_pnl))
|
||||
n_trades = len(trade_pnl)
|
||||
n_position_changes = int((position.diff().fillna(0) != 0).sum())
|
||||
|
||||
if n_trades > 0:
|
||||
@@ -216,7 +216,7 @@ def backtest_signal(
|
||||
win_rate = 0.0
|
||||
profit_factor = 0.0
|
||||
|
||||
ic: Optional[float] = None
|
||||
ic: float | None = None
|
||||
if forward_returns is not None:
|
||||
fwd = pd.to_numeric(forward_returns, errors="coerce")
|
||||
common = signal.index.intersection(fwd.dropna().index)
|
||||
@@ -227,7 +227,7 @@ def backtest_signal(
|
||||
ic_val = float(s.corr(f))
|
||||
ic = ic_val if np.isfinite(ic_val) else None
|
||||
|
||||
result: Dict[str, Any] = {
|
||||
result: dict[str, Any] = {
|
||||
"status": "success",
|
||||
"sharpe": sharpe,
|
||||
"sortino": sortino,
|
||||
@@ -244,7 +244,7 @@ def backtest_signal(
|
||||
"volatility": volatility,
|
||||
"n_trades": n_trades,
|
||||
"n_position_changes": n_position_changes,
|
||||
"n_bars": int(len(strategy_returns)),
|
||||
"n_bars": len(strategy_returns),
|
||||
"n_months": float(n_months),
|
||||
"signal_long": int((signal > 0).sum()),
|
||||
"signal_short": int((signal < 0).sum()),
|
||||
@@ -293,7 +293,7 @@ def _apply_ftmo_mask(
|
||||
|
||||
daily_breaches = 0
|
||||
total_breached = False
|
||||
total_breach_ts: Optional[pd.Timestamp] = None
|
||||
total_breach_ts: pd.Timestamp | None = None
|
||||
current_day = None
|
||||
day_start_eq = FTMO_INITIAL_CAPITAL
|
||||
|
||||
@@ -308,11 +308,8 @@ def _apply_ftmo_mask(
|
||||
pos_i = float(signal.at[ts]) * leverage
|
||||
ret_i = float(bar_ret.get(ts, 0.0))
|
||||
cost_i = abs(pos_i - pos_prev) * txn_cost
|
||||
ret_net = pos_prev * ret_i - cost_i
|
||||
equity = equity * (1.0 + ret_net / FTMO_INITIAL_CAPITAL * FTMO_INITIAL_CAPITAL / equity
|
||||
if equity > 0 else 1.0)
|
||||
# Simpler: track as fraction
|
||||
equity += FTMO_INITIAL_CAPITAL * ret_net
|
||||
ret_frac = pos_prev * ret_i - cost_i
|
||||
equity *= 1.0 + ret_frac if equity > 0 else 1.0
|
||||
pos_prev = pos_i
|
||||
|
||||
if total_breached:
|
||||
@@ -399,7 +396,7 @@ def walk_forward_rolling(
|
||||
is_years: int = WF_IS_YEARS,
|
||||
oos_years: int = WF_OOS_YEARS,
|
||||
step_years: int = WF_STEP_YEARS,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Rolling walk-forward validation: multiple IS/OOS windows shifted by ``step_years``.
|
||||
|
||||
@@ -433,7 +430,7 @@ def walk_forward_rolling(
|
||||
yr += step_years
|
||||
continue
|
||||
|
||||
window: Dict[str, Any] = {
|
||||
window: dict[str, Any] = {
|
||||
"is_start": str(is_start.date()),
|
||||
"is_end": str(is_end.date()),
|
||||
"oos_start": str(is_end.date()),
|
||||
@@ -475,11 +472,11 @@ def backtest_signal_ftmo(
|
||||
stop_pips: float = FTMO_STOP_PIPS,
|
||||
max_leverage: float = FTMO_MAX_LEVERAGE,
|
||||
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
||||
forward_returns: Optional[pd.Series] = None,
|
||||
oos_start: Optional[str] = OOS_START_DEFAULT,
|
||||
forward_returns: pd.Series | None = None,
|
||||
oos_start: str | None = OOS_START_DEFAULT,
|
||||
wf_rolling: bool = False,
|
||||
mc_n_permutations: int = 0,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
FTMO-compliant backtest of a strategy signal on EUR/USD.
|
||||
|
||||
@@ -547,7 +544,7 @@ def backtest_signal_ftmo(
|
||||
is_mask = close.index < oos_ts
|
||||
oos_mask = close.index >= oos_ts
|
||||
|
||||
def _split_bt(mask: "pd.Series[bool]", prefix: str) -> None:
|
||||
def _split_bt(mask: pd.Series[bool], prefix: str) -> None:
|
||||
if mask.sum() < 100:
|
||||
return
|
||||
close_s = close.loc[mask]
|
||||
@@ -602,7 +599,7 @@ def backtest_from_forward_returns(
|
||||
forward_returns: pd.Series,
|
||||
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
||||
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Backtest a factor using sign(factor) as signal against forward returns.
|
||||
|
||||
@@ -640,7 +637,7 @@ def backtest_from_forward_returns(
|
||||
ic = ic_val if np.isfinite(ic_val) else 0.0
|
||||
|
||||
trade_pnl = _compute_trade_pnl(position, strategy_returns)
|
||||
n_trades = int(len(trade_pnl))
|
||||
n_trades = len(trade_pnl)
|
||||
win_rate = float((trade_pnl > 0).mean()) if n_trades > 0 else 0.0
|
||||
|
||||
ann_return = float(strategy_returns.mean() * bars_per_year)
|
||||
@@ -656,7 +653,7 @@ def backtest_from_forward_returns(
|
||||
"win_rate": win_rate,
|
||||
"n_trades": n_trades,
|
||||
"ic": ic,
|
||||
"n_bars": int(len(strategy_returns)),
|
||||
"n_bars": len(strategy_returns),
|
||||
"txn_cost_bps": txn_cost_bps,
|
||||
"bars_per_year": bars_per_year,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user