添加批量解析
This commit is contained in:
+64
-45
@@ -22,7 +22,6 @@ Walk-Forward 滚动验证
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import html
|
||||
import os
|
||||
import sys
|
||||
@@ -38,27 +37,10 @@ OUT_DIR = "output"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 单段指标
|
||||
# 单段指标(从 mt5_report_parser 复用)
|
||||
# --------------------------------------------------------------------------- #
|
||||
def _seg_metrics(net: pd.Series) -> Dict[str, float]:
|
||||
n = len(net)
|
||||
if n == 0:
|
||||
return {"n": 0, "net": 0.0, "pf": 0.0, "win": 0.0, "dd": 0.0, "exp": 0.0}
|
||||
wins = net[net > 0]
|
||||
losses = net[net <= 0]
|
||||
gp = wins.sum()
|
||||
gl = -losses.sum()
|
||||
pf = gp / gl if gl > 0 else np.inf
|
||||
eq = net.cumsum()
|
||||
dd = float((eq - eq.cummax()).min())
|
||||
return {
|
||||
"n": int(n),
|
||||
"net": float(net.sum()),
|
||||
"pf": float(pf),
|
||||
"win": float(len(wins) / n * 100),
|
||||
"dd": dd,
|
||||
"exp": float(net.mean()),
|
||||
}
|
||||
|
||||
from mt5_report_parser import compute_segment_metrics as _seg_metrics
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -79,7 +61,7 @@ def walk_forward(
|
||||
step_days: Optional[int] = None,
|
||||
) -> List[WFWindow]:
|
||||
"""
|
||||
滚动切窗。
|
||||
滚动切窗(向量化边界查找)。
|
||||
trades: 含 open_time, net_profit 的 DataFrame
|
||||
is_days / oos_days: IS/OOS 窗口天数
|
||||
step_days: 滑动步长(默认 = oos_days,即不重叠的 OOS)
|
||||
@@ -87,7 +69,12 @@ def walk_forward(
|
||||
if trades is None or trades.empty:
|
||||
return []
|
||||
t = trades.sort_values("open_time").reset_index(drop=True)
|
||||
t["net_profit"] = t["net_profit"].astype(float)
|
||||
times = t["open_time"].to_numpy()
|
||||
nets = t["net_profit"].astype(float).to_numpy()
|
||||
n_trades = len(nets)
|
||||
|
||||
# 预计算累积和(含前导0,cumsum[i] = sum of nets[:i])
|
||||
cumsum = np.concatenate([[0.0], np.cumsum(nets)])
|
||||
|
||||
if step_days is None:
|
||||
step_days = oos_days
|
||||
@@ -97,34 +84,66 @@ def walk_forward(
|
||||
total_span = (end - start).days
|
||||
win_span = is_days + oos_days
|
||||
if total_span < win_span:
|
||||
return [] # 数据不足以切出一个窗口
|
||||
return []
|
||||
|
||||
windows: List[WFWindow] = []
|
||||
cur = start
|
||||
# 用 numpy 二分查找窗口边界索引(所有窗口一次性确定)
|
||||
cur = np.datetime64(start, "ns")
|
||||
end_ns = np.datetime64(end, "ns")
|
||||
bounds: List[Tuple[int, int, int, int]] = [] # (is_s, is_e, oos_s, oos_e) 为交易索引
|
||||
idx = 0
|
||||
while True:
|
||||
is_s = cur
|
||||
is_e = cur + pd.Timedelta(days=is_days)
|
||||
is_e = cur + np.timedelta64(is_days, "D")
|
||||
oos_s = is_e
|
||||
oos_e = oos_s + pd.Timedelta(days=oos_days)
|
||||
if oos_e > end + pd.Timedelta(days=1):
|
||||
oos_e = oos_s + np.timedelta64(oos_days, "D")
|
||||
if oos_e > end_ns + np.timedelta64(1, "D"):
|
||||
break
|
||||
is_mask = (t["open_time"] >= is_s) & (t["open_time"] < is_e)
|
||||
oos_mask = (t["open_time"] >= oos_s) & (t["open_time"] < oos_e)
|
||||
is_net = t.loc[is_mask, "net_profit"]
|
||||
oos_net = t.loc[oos_mask, "net_profit"]
|
||||
if len(is_net) == 0 or len(oos_net) == 0:
|
||||
cur += pd.Timedelta(days=step_days)
|
||||
continue
|
||||
windows.append(WFWindow(
|
||||
idx=idx,
|
||||
is_start=is_s, is_end=is_e,
|
||||
oos_start=oos_s, oos_end=oos_e,
|
||||
is_metrics=_seg_metrics(is_net),
|
||||
oos_metrics=_seg_metrics(oos_net),
|
||||
))
|
||||
i_s = int(np.searchsorted(times, cur, side="left"))
|
||||
i_e = int(np.searchsorted(times, is_e, side="left"))
|
||||
o_s = int(np.searchsorted(times, oos_s, side="left"))
|
||||
o_e = int(np.searchsorted(times, oos_e, side="left"))
|
||||
bounds.append((i_s, i_e, o_s, o_e))
|
||||
cur += np.timedelta64(step_days, "D")
|
||||
idx += 1
|
||||
cur += pd.Timedelta(days=step_days)
|
||||
|
||||
windows: List[WFWindow] = []
|
||||
for i, (i_s, i_e, o_s, o_e) in enumerate(bounds):
|
||||
if i_e - i_s == 0 or o_e - o_s == 0:
|
||||
continue
|
||||
# IS 段指标(用累积和 O(1) 计算)
|
||||
is_net_sum = float(cumsum[i_e] - cumsum[i_s])
|
||||
is_n = i_e - i_s
|
||||
is_seg = nets[i_s:i_e]
|
||||
is_gp = float(np.sum(is_seg[is_seg > 0]))
|
||||
is_gl = float(-np.sum(is_seg[is_seg <= 0]))
|
||||
is_pf = is_gp / is_gl if is_gl > 0 else np.inf
|
||||
is_cum = cumsum[i_s + 1:i_e + 1] - cumsum[i_s:i_e]
|
||||
is_dd = float(np.min(is_cum - np.maximum.accumulate(is_cum))) if len(is_cum) else 0.0
|
||||
is_win = float(np.sum(is_seg > 0) / is_n * 100) if is_n else 0.0
|
||||
# OOS 段
|
||||
oos_net_sum = float(cumsum[o_e] - cumsum[o_s])
|
||||
oos_n = o_e - o_s
|
||||
oos_seg = nets[o_s:o_e]
|
||||
oos_gp = float(np.sum(oos_seg[oos_seg > 0]))
|
||||
oos_gl = float(-np.sum(oos_seg[oos_seg <= 0]))
|
||||
oos_pf = oos_gp / oos_gl if oos_gl > 0 else np.inf
|
||||
oos_cum = cumsum[o_s + 1:o_e + 1] - cumsum[o_s:o_e]
|
||||
oos_dd = float(np.min(oos_cum - np.maximum.accumulate(oos_cum))) if len(oos_cum) else 0.0
|
||||
oos_win = float(np.sum(oos_seg > 0) / oos_n * 100) if oos_n else 0.0
|
||||
|
||||
windows.append(WFWindow(
|
||||
idx=i,
|
||||
is_start=times[i_s], is_end=times[min(i_e - 1, n_trades - 1)],
|
||||
oos_start=times[o_s], oos_end=times[min(o_e - 1, n_trades - 1)],
|
||||
is_metrics={
|
||||
"n": is_n, "net": is_net_sum, "pf": is_pf,
|
||||
"win": is_win, "dd": is_dd, "exp": is_net_sum / is_n if is_n else 0.0,
|
||||
},
|
||||
oos_metrics={
|
||||
"n": oos_n, "net": oos_net_sum, "pf": oos_pf,
|
||||
"win": oos_win, "dd": oos_dd, "exp": oos_net_sum / oos_n if oos_n else 0.0,
|
||||
},
|
||||
))
|
||||
|
||||
return windows
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user