回测基本一致
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""Interactive run-settings wizard (doc 05 §5).
|
||||
|
||||
Before a run, a small interactive wizard asks the settings that change
|
||||
run-to-run and writes them to ``wizard-answers.yaml``. That YAML *is* the
|
||||
reproducibility record: anyone can see exactly what produced an iteration's
|
||||
numbers. Give every question a sensible default so a fast run is just
|
||||
pressing Enter.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
@dataclass
|
||||
class WizardQuestion:
|
||||
"""One wizard question with a default and optional validator."""
|
||||
|
||||
key: str
|
||||
prompt: str
|
||||
default: Any
|
||||
cast: Callable[[str], Any] = str
|
||||
help: str = ""
|
||||
|
||||
def ask(self) -> Any:
|
||||
"""Prompt the user, returning the cast value (default on empty)."""
|
||||
suffix = f" [{self.help}]" if self.help else ""
|
||||
raw = input(f"{self.prompt} (default: {self.default}){suffix}: ").strip()
|
||||
if raw == "":
|
||||
return self.default
|
||||
try:
|
||||
return self.cast(raw)
|
||||
except (ValueError, TypeError):
|
||||
print(f" invalid value, using default {self.default!r}")
|
||||
return self.default
|
||||
|
||||
|
||||
# Base set of common questions (doc 05 §5). Extend per-strategy via extra.
|
||||
DEFAULT_QUESTIONS: list[WizardQuestion] = [
|
||||
WizardQuestion("period_start", "Backtest start date (YYYY-MM-DD)", "2020-01-01"),
|
||||
WizardQuestion("period_end", "Backtest end date (YYYY-MM-DD)", "2026-01-01"),
|
||||
WizardQuestion("instrument_profile", "Instrument profile (real/worst_case/best_case)", "real"),
|
||||
WizardQuestion("trials", "Optuna trial budget", 300, cast=int),
|
||||
WizardQuestion("max_dd_ccy", "Hard drawdown cap (account currency)", 3500.0, cast=float),
|
||||
WizardQuestion("max_dd_pct", "Hard drawdown cap (fraction of deposit)", 0.35, cast=float),
|
||||
WizardQuestion("top_n_verify", "Finalists to send to MT5", 3, cast=int),
|
||||
WizardQuestion("initial_deposit", "Initial deposit", 10000.0, cast=float),
|
||||
WizardQuestion("n_jobs", "Optuna parallel workers", 4, cast=int),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class WizardAnswers:
|
||||
"""A bag of answered wizard questions, serializable to YAML."""
|
||||
|
||||
answers: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
return self.answers.get(key, default)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return dict(self.answers)
|
||||
|
||||
|
||||
def run_wizard(
|
||||
questions: Optional[list[WizardQuestion]] = None,
|
||||
*,
|
||||
extra: Optional[list[WizardQuestion]] = None,
|
||||
) -> WizardAnswers:
|
||||
"""Run the interactive wizard and return :class:`WizardAnswers`.
|
||||
|
||||
``questions`` defaults to :data:`DEFAULT_QUESTIONS`; ``extra`` lets a
|
||||
strategy add its own (e.g. a grid wizard adds depth/multiplier defaults).
|
||||
"""
|
||||
qs = list(questions or DEFAULT_QUESTIONS)
|
||||
if extra:
|
||||
qs.extend(extra)
|
||||
answers: dict[str, Any] = {}
|
||||
print("=== Pre-run wizard ===")
|
||||
for q in qs:
|
||||
answers[q.key] = q.ask()
|
||||
print("=== Wizard complete ===")
|
||||
return WizardAnswers(answers=answers)
|
||||
|
||||
|
||||
def save_answers(answers: WizardAnswers, path: str | Path) -> None:
|
||||
"""Write answers to a YAML file next to the iteration."""
|
||||
p = Path(path)
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
with p.open("w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(answers.to_dict(), f, sort_keys=False, allow_unicode=True)
|
||||
|
||||
|
||||
def load_answers(path: str | Path) -> WizardAnswers:
|
||||
"""Load answers from a previously-written YAML (for re-runs)."""
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
raise FileNotFoundError(f"wizard answers not found: {p}")
|
||||
with p.open("r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
return WizardAnswers(answers=dict(data))
|
||||
Reference in New Issue
Block a user