mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
2cec08bc91
- 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>
163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
"""
|
|
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
|