修复缺失json导致的崩溃

This commit is contained in:
2026-07-10 06:52:49 +00:00
parent 29428b415b
commit def8cf8d84
+57 -39
View File
@@ -20,37 +20,22 @@ logger = logging.getLogger(__name__)
class MT5AutoRunner: class MT5AutoRunner:
def __init__(self, source): def __init__(self, config_path):
""" if isinstance(config_path, dict):
source: YAML 文件路径 str 或 已加载的 config dict(GUI 内存直接传入用)。 self.config = config_path
"""
if isinstance(source, dict):
self.config = source
else: else:
self.config = self._load_config(source) self.config = self._load_config(config_path)
# 占位符解析(不做自动探测)
from mt5_paths import resolve_mt5_settings
self.config["mt5_settings"] = resolve_mt5_settings(self.config.get("mt5_settings", {}))
self.mt5_path = self.config["mt5_settings"]["terminal_path"] self.mt5_path = self.config["mt5_settings"]["terminal_path"]
self.data_dir = self.config["mt5_settings"].get("data_dir", "") self.data_dir = self.config["mt5_settings"].get("data_dir", "")
self.ini_dir = self.config["mt5_settings"]["ini_dir"]
self.reports_dir = os.path.abspath(self.config["mt5_settings"].get("reports_dir", "reports")) self.reports_dir = os.path.abspath(self.config["mt5_settings"].get("reports_dir", "reports"))
self.ini_dir = os.path.abspath(self.config["mt5_settings"].get("ini_dir", execution_cfg = self.config.get("execution", {}) or {}
os.path.join(os.path.dirname(self.reports_dir), "config", "generated"))) self.timeout_per_test = int(execution_cfg.get("timeout_per_test", 30))
self.kill_between = bool(execution_cfg.get("kill_between", True))
self.skip_existing = bool(execution_cfg.get("skip_existing", True))
self.execution_log = []
os.makedirs("logs", exist_ok=True)
os.makedirs(self.reports_dir, exist_ok=True) os.makedirs(self.reports_dir, exist_ok=True)
os.makedirs(self.ini_dir, exist_ok=True)
exec_cfg = self.config.get("execution", {}) or {}
self.kill_between = bool(exec_cfg.get("kill_between", True))
self.skip_existing = bool(exec_cfg.get("skip_existing", True))
self.timeout_per_test = int(exec_cfg.get("timeout_per_test", 30))
if not self.mt5_path or not os.path.isfile(self.mt5_path):
raise FileNotFoundError(
f"MT5 终端路径未配置或不存在: {self.mt5_path!r}\n"
"请在 GUI '回测配置' 标签页设置 terminal64.exe 的完整路径后点 '保存配置'"
)
def _load_config(self, config_path: str) -> Dict: def _load_config(self, config_path: str) -> Dict:
import yaml import yaml
@@ -61,8 +46,15 @@ class MT5AutoRunner:
log_file = os.path.join(self.ini_dir, "..", "results", "execution_log.json") log_file = os.path.join(self.ini_dir, "..", "results", "execution_log.json")
log_file = os.path.normpath(log_file) log_file = os.path.normpath(log_file)
if os.path.exists(log_file): if os.path.exists(log_file):
with open(log_file, 'r', encoding='utf-8') as f: try:
return json.load(f) with open(log_file, 'r', encoding='utf-8') as f:
content = f.read().strip()
if not content:
return []
return json.loads(content)
except (json.JSONDecodeError, OSError) as e:
logger.warning(f"Corrupt execution_log.json, ignoring: {e}")
return []
return [] return []
def _save_execution_log(self, log: List[Dict]): def _save_execution_log(self, log: List[Dict]):
@@ -74,7 +66,35 @@ class MT5AutoRunner:
def _get_report_path(self, ini_name: str) -> str: def _get_report_path(self, ini_name: str) -> str:
base_name = os.path.splitext(ini_name)[0] base_name = os.path.splitext(ini_name)[0]
return os.path.join(self.data_dir, f"{base_name}.htm") if self.data_dir else f"{base_name}.htm"
project_report_path = os.path.join(self.reports_dir, f"{base_name}.htm")
if os.path.exists(project_report_path):
return project_report_path
if self.data_dir and os.path.isdir(self.data_dir):
target_names = {f"{base_name}.htm", f"{base_name}.xml", base_name}
for root, dirs, files in os.walk(self.data_dir):
for f in files:
if f in target_names:
found = os.path.join(root, f)
logger.info(f"Report found at: {found}")
return found
logger.warning(f"Report not found for {ini_name}, base={base_name}, data_dir={self.data_dir}")
return project_report_path
def _get_report_siblings(self, src_path: str) -> list:
parent = os.path.dirname(src_path)
base = os.path.splitext(os.path.basename(src_path))[0]
siblings = []
try:
for f in os.listdir(parent):
name, ext = os.path.splitext(f)
if name == base and ext.lower() in (".htm", ".html", ".xml", ".png", ".gif", ".csv"):
siblings.append(os.path.join(parent, f))
except OSError:
pass
return siblings
def _copy_report_to_project(self, ini_name: str) -> str: def _copy_report_to_project(self, ini_name: str) -> str:
base_name = os.path.splitext(ini_name)[0] base_name = os.path.splitext(ini_name)[0]
@@ -85,6 +105,9 @@ class MT5AutoRunner:
return None return None
dest_path = os.path.join(self.reports_dir, f"{base_name}.htm") dest_path = os.path.join(self.reports_dir, f"{base_name}.htm")
if os.path.abspath(src_path) == os.path.abspath(dest_path):
return dest_path
try: try:
shutil.copy2(src_path, dest_path) shutil.copy2(src_path, dest_path)
logger.info(f"Report copied to: {dest_path}") logger.info(f"Report copied to: {dest_path}")
@@ -93,6 +116,7 @@ class MT5AutoRunner:
logger.error(f"Failed to copy report: {e}") logger.error(f"Failed to copy report: {e}")
return None return None
def _kill_mt5(self): def _kill_mt5(self):
try: try:
subprocess.run(['taskkill', '/F', '/IM', 'terminal64.exe'], subprocess.run(['taskkill', '/F', '/IM', 'terminal64.exe'],
@@ -170,15 +194,16 @@ class MT5AutoRunner:
end_time = time.time() end_time = time.time()
result["duration_sec"] = end_time - start_time result["duration_sec"] = end_time - start_time
if os.path.exists(expected_report): actual_report = self._get_report_path(ini_name)
if os.path.exists(actual_report):
result["status"] = "completed" result["status"] = "completed"
result["report_found"] = True result["report_found"] = True
copied_path = self._copy_report_to_project(ini_name) copied_path = self._copy_report_to_project(ini_name)
result["report_path"] = copied_path or expected_report result["report_path"] = copied_path or actual_report
logger.info(f"Success: {ini_name} ({result['duration_sec']:.0f}s)") logger.info(f"Success: {ini_name} ({result['duration_sec']:.0f}s)")
else: else:
result["status"] = "failed" result["status"] = "failed"
logger.warning(f"Failed: {ini_name}") logger.warning(f"Failed: {ini_name}, looked at: {actual_report}")
result["end_time"] = datetime.now().isoformat() result["end_time"] = datetime.now().isoformat()
@@ -225,13 +250,6 @@ class MT5AutoRunner:
logger.info(f"Total INI files: {total}") logger.info(f"Total INI files: {total}")
# First run must also start from a clean tester state. Otherwise the first test
# can inherit an already-open terminal's previous deposit/delay/symbol settings,
# while later tests look correct only because kill_between_tests runs after them.
if kill_between_tests:
self._kill_mt5()
time.sleep(2)
for idx, ini_path in enumerate(ini_files, 1): for idx, ini_path in enumerate(ini_files, 1):
logger.info(f"[{idx}/{total}] {os.path.basename(ini_path)}") logger.info(f"[{idx}/{total}] {os.path.basename(ini_path)}")