phase 7-8 完成 + warmup 修复 + 产物结构化重组
主要内容: - Phase 8 PROMOTE: finalist #1 (trial #324) registry 条目,自动生成 - Optuna objective warmup bug 修复 (shared/optimizer/objective.py) - studies/ 目录按用途重组为 optuna/ + finalists/ + features/ 三层 - reports/ 加入 Optuna 中文 dashboard (5 主图 + 18 slice + 15 contour) - 新增 PROJECT_GUIDE.md 项目说明文档 - 新增 build_registry_entry.py / build_optuna_dashboard.py / build_feature_datasets.py - .gitignore: 允许提交 studies/*.db (Optuna DB) 和 reports/*.html (MT5 + dashboard)
This commit is contained in:
@@ -17,6 +17,9 @@ from pathlib import Path
|
||||
from typing import Any, Mapping
|
||||
|
||||
SET_FILE_ENCODING = "utf-16-le"
|
||||
# MT5's Strategy Tester silently ignores .set files without a UTF-16-LE BOM.
|
||||
# Python's "utf-16-le" codec does NOT emit a BOM, so prepend one explicitly.
|
||||
UTF16_LE_BOM = b"\xff\xfe"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -68,4 +71,4 @@ def write_set_file(
|
||||
if extra_lines:
|
||||
lines.extend(extra_lines)
|
||||
text = "\n".join(lines) + "\n"
|
||||
out.write_bytes(text.encode(SET_FILE_ENCODING))
|
||||
out.write_bytes(UTF16_LE_BOM + text.encode(SET_FILE_ENCODING))
|
||||
|
||||
@@ -77,6 +77,20 @@ class ObjectiveConfig:
|
||||
# strategy pipe tunable point values into its engine without the optimizer
|
||||
# knowing about strategy-specific config objects. None → no extra kwargs.
|
||||
build_engine_kwargs: Optional[Callable[[dict], dict]] = None
|
||||
# M1 bars for tick-level exit simulation. MANDATORY if the EA moves its
|
||||
# SL intra-trade (break-even / trailing / basket trailing) — bar-level
|
||||
# exit simulation produces a −40% to −50% net gap on those EAs (doc 03 §7
|
||||
# measured failure mode). Leave None only for clean-directional setups
|
||||
# that don't move the SL. The bars must cover the same window as ``bars``.
|
||||
m1_bars: Optional[pd.DataFrame] = None
|
||||
# Indicator-warmup bars (doc 03 §8 / reeval_finalist_forward.py pattern).
|
||||
# When set, the objective builds signals on this FULL history (so EMA/RSI/
|
||||
# ATR are already stable before the eval window starts — matching MT5
|
||||
# Strategy Tester's pre-test chart-history warmup), then slices the signal
|
||||
# arrays to ``bars``' time range before running the engine. Must be a
|
||||
# contiguous superset of ``bars`` (same OHLC source, same timestamps).
|
||||
# Leave None to build signals directly on ``bars`` (legacy behaviour).
|
||||
signals_full_bars: Optional[pd.DataFrame] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -141,14 +155,43 @@ def build_objective(cfg: ObjectiveConfig):
|
||||
def objective(trial) -> float:
|
||||
sampled = suggest_params(trial, cfg.search_space, cfg.int_params)
|
||||
merged = {**cfg.frozen_baseline, **sampled}
|
||||
pack = cfg.build_signals(merged, cfg.bars, cfg.instrument)
|
||||
if cfg.signals_full_bars is not None:
|
||||
# Warmup mode (doc 03 §8 / reeval_finalist_forward.py pattern):
|
||||
# build signals on the FULL bars so indicators are already stable
|
||||
# at the eval window start — mirrors MT5 Strategy Tester's pre-test
|
||||
# chart-history warmup. Then slice the signal arrays to cfg.bars'
|
||||
# time range. cfg.bars must be a contiguous sub-range of
|
||||
# signals_full_bars (same OHLC source, same timestamps).
|
||||
pack = cfg.build_signals(merged, cfg.signals_full_bars, cfg.instrument)
|
||||
eval_start = pd.Timestamp(cfg.bars["timestamp"].iloc[0])
|
||||
full_ts = pd.to_datetime(cfg.signals_full_bars["timestamp"].to_numpy())
|
||||
lo = int(full_ts.searchsorted(eval_start, side="left"))
|
||||
hi = lo + len(cfg.bars)
|
||||
sig_long = pack.signals_long[lo:hi]
|
||||
sig_short = pack.signals_short[lo:hi]
|
||||
sl_p = pack.sl_prices[lo:hi]
|
||||
tp_p = pack.tp_prices[lo:hi]
|
||||
else:
|
||||
# Legacy mode: build signals directly on the (already-trimmed)
|
||||
# cfg.bars. Indicators warm up at the eval window start — fine for
|
||||
# short-period indicators but produces ~14h of EMA-stabilization
|
||||
# noise at the start of long-period EMA strategies.
|
||||
pack = cfg.build_signals(merged, cfg.bars, cfg.instrument)
|
||||
sig_long = pack.signals_long
|
||||
sig_short = pack.signals_short
|
||||
sl_p = pack.sl_prices
|
||||
tp_p = pack.tp_prices
|
||||
extra = cfg.build_engine_kwargs(merged) if cfg.build_engine_kwargs else {}
|
||||
# If M1 bars are wired up, pass them so the engine switches to tick-
|
||||
# level exit simulation (mandatory for trailing/BE EAs — doc 03 §7).
|
||||
if cfg.m1_bars is not None:
|
||||
extra = {**extra, "m1_bars": cfg.m1_bars}
|
||||
result = cfg.engine.run(
|
||||
cfg.bars,
|
||||
pack.signals_long,
|
||||
pack.signals_short,
|
||||
pack.sl_prices,
|
||||
pack.tp_prices,
|
||||
sig_long,
|
||||
sig_short,
|
||||
sl_p,
|
||||
tp_p,
|
||||
cfg.instrument,
|
||||
cfg.sizing,
|
||||
cfg.initial_deposit,
|
||||
|
||||
Reference in New Issue
Block a user