mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 23:47:46 +00:00
feat: add daily log rotation, llama health wait, factor auto-fixer, and README updates
- Add rdagent/log/daily_log.py: daily-rotating structured logs per command (fin_quant, strategies, evaluate, parallel) with loguru; all.log combined sink - predix.py: route TeeWriter output to logs/YYYY-MM-DD/ instead of root dir; wrap quant() and evaluate() in daily_log.session() for start/stop/duration tracking - rdagent/app/cli.py: fin_quant_cli waits for llama.cpp /health endpoint before starting pipeline (up to 300 s); daily_log integration for fin_quant, generate_strategies, eval_all, parallel commands - scripts/predix_gen_strategies_real_bt.py: daily_log integration with per-strategy ACCEPTED/REJECTED entries and summary on completion - rdagent/components/coder/factor_coder/auto_fixer.py: new module that patches common LLM-generated factor issues (min_periods, inf/NaN, groupby.transform, MultiIndex corrections) - rdagent/components/coder/factor_coder/prompts.yaml: add critical rules for EURUSD 1-min intraday factors (min_periods, inf handling, groupby, date range) - README.md: document --reasoning off and --n-gpu-layers 28 for llama-server; explain VRAM constraints when Ollama is running alongside llama.cpp - .bandit.yml: suppress B615 (HuggingFace unsafe download) for RL benchmark files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
Daily-rotating log for all Predix commands.
|
||||
|
||||
Automatically organizes logs by date:
|
||||
|
||||
logs/
|
||||
YYYY-MM-DD/
|
||||
fin_quant.log ← R&D loop (structured)
|
||||
strategies.log ← strategy generation
|
||||
strategies_bt.log ← parallel strategy generator script
|
||||
evaluate.log ← factor evaluation
|
||||
parallel.log ← parallel runs
|
||||
all.log ← every command combined
|
||||
|
||||
Usage:
|
||||
from rdagent.log.daily_log import setup, session
|
||||
|
||||
# One-shot setup (returns a bound logger):
|
||||
log = setup("fin_quant", model="local", loops=10)
|
||||
log.info("Loop started")
|
||||
|
||||
# Context manager (logs start + elapsed + stop automatically):
|
||||
with session("strategies", style="swing", count=10) as log:
|
||||
log.info("Generating…")
|
||||
run_generation()
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger as _root
|
||||
|
||||
# ── paths ─────────────────────────────────────────────────────────────────────
|
||||
LOGS_ROOT: Path = Path(__file__).parent.parent.parent / "logs"
|
||||
|
||||
# ── format ────────────────────────────────────────────────────────────────────
|
||||
_FILE_FMT = (
|
||||
"{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[cmd]: <18} | {message}"
|
||||
)
|
||||
|
||||
# ── internal state ─────────────────────────────────────────────────────────────
|
||||
_registered: set[str] = set() # command keys that already have a file sink
|
||||
_all_added: bool = False # whether the combined all.log sink is active
|
||||
|
||||
|
||||
# ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def _today_dir() -> Path:
|
||||
d = LOGS_ROOT / datetime.now().strftime("%Y-%m-%d")
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
return d
|
||||
|
||||
|
||||
def _fmt_td(td) -> str:
|
||||
s = int(td.total_seconds())
|
||||
h, r = divmod(s, 3600)
|
||||
m, sec = divmod(r, 60)
|
||||
if h:
|
||||
return f"{h}h {m:02d}m {sec:02d}s"
|
||||
if m:
|
||||
return f"{m}m {sec:02d}s"
|
||||
return f"{sec}s"
|
||||
|
||||
|
||||
def _banner(log, title: str, meta: dict[str, Any]) -> None:
|
||||
sep = "─" * 76
|
||||
log.info(sep)
|
||||
log.info(f" {title}")
|
||||
if meta:
|
||||
pairs = " ".join(f"{k}={v}" for k, v in meta.items())
|
||||
log.info(f" {pairs}")
|
||||
log.info(sep)
|
||||
|
||||
|
||||
# ── public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
def setup(command: str, **context: Any):
|
||||
"""
|
||||
Initialise daily log sinks for *command* and return a bound logger.
|
||||
|
||||
Idempotent — safe to call multiple times within the same process.
|
||||
|
||||
Args:
|
||||
command: Short slug, e.g. "fin_quant", "strategies", "evaluate".
|
||||
**context: Key/value pairs printed in the startup banner.
|
||||
|
||||
Returns:
|
||||
loguru.Logger bound with extra["cmd"] = command.upper().
|
||||
"""
|
||||
global _all_added
|
||||
|
||||
log_dir = _today_dir()
|
||||
key = command.lower()
|
||||
|
||||
if key not in _registered:
|
||||
# Per-command rotating file
|
||||
_root.add(
|
||||
str(log_dir / f"{key}.log"),
|
||||
format=_FILE_FMT,
|
||||
filter=lambda r, k=key: r["extra"].get("cmd", "").lower() == k,
|
||||
rotation="00:00", # new file at midnight
|
||||
retention="30 days",
|
||||
encoding="utf-8",
|
||||
enqueue=True,
|
||||
backtrace=False,
|
||||
diagnose=False,
|
||||
)
|
||||
_registered.add(key)
|
||||
|
||||
if not _all_added:
|
||||
# Combined log — all commands
|
||||
_root.add(
|
||||
str(log_dir / "all.log"),
|
||||
format=_FILE_FMT,
|
||||
filter=lambda r: "cmd" in r["extra"],
|
||||
rotation="00:00",
|
||||
retention="60 days",
|
||||
encoding="utf-8",
|
||||
enqueue=True,
|
||||
backtrace=False,
|
||||
diagnose=False,
|
||||
)
|
||||
_all_added = True
|
||||
|
||||
bound = _root.bind(cmd=command.upper())
|
||||
_banner(bound, f"▶ START {command.upper()}", context)
|
||||
return bound
|
||||
|
||||
|
||||
@contextmanager
|
||||
def session(command: str, **context: Any):
|
||||
"""
|
||||
Context manager: logs start, stop, and elapsed duration automatically.
|
||||
|
||||
Usage::
|
||||
|
||||
with daily_log.session("fin_quant", model="local", loops=10) as log:
|
||||
log.info("Step 1 complete")
|
||||
run_loop()
|
||||
|
||||
On success logs: ``◼ DONE FIN_QUANT (12m 34s)``
|
||||
On interrupt: ``⚠ INTERRUPTED FIN_QUANT (2m 01s)``
|
||||
On error: ``✖ FAILED FIN_QUANT (0s) — <exception>``
|
||||
"""
|
||||
log = setup(command, **context)
|
||||
t0 = datetime.now()
|
||||
try:
|
||||
yield log
|
||||
elapsed = datetime.now() - t0
|
||||
_banner(log, f"◼ DONE {command.upper()} ({_fmt_td(elapsed)})", {})
|
||||
except KeyboardInterrupt:
|
||||
elapsed = datetime.now() - t0
|
||||
_banner(log, f"⚠ INTERRUPTED {command.upper()} ({_fmt_td(elapsed)})", {})
|
||||
raise
|
||||
except Exception as exc:
|
||||
elapsed = datetime.now() - t0
|
||||
log.error(f"✖ FAILED {command.upper()} ({_fmt_td(elapsed)}) — {exc}")
|
||||
raise
|
||||
Reference in New Issue
Block a user