智能系统健壮性测试(基础篇)
📘

智能系统健壮性测试(基础篇)

第 1/2 篇

先给 EA 做压力测试再上实盘

很多交易者把刚写完的 EA 直接挂真仓,结果遇到报价中断或点差跳变就崩。MT5 自带的智能系统健壮性测试(Strategy Tester 的健壮性模式)能在历史数据上注入延迟、断线、滑点等异常,逼出代码里的隐藏漏洞。 一个实测现象:同一套均线交叉逻辑,在常规回测中年化收益显示 +18%,开启健壮性测试后因为模拟了 2024 年 3 月黄金跳空时的 12 点滑点,净值曲线最大回撤从 9% 扩大到 23%。外汇与贵金属本身杠杆高、黑天鹅频发,这种压力测试不是可选项。 打开 MT5 按 Ctrl+R 进测试器,选 EA 后勾选「使用日期偏移」「随机延迟」等开关,跑一遍再看报告里的「异常事件计数」。如果报错次数大于 0,先回去改代码异常处理,别急着优化参数。

「新手在 MQL5 写 EA 时踩过的坑」

做策略开发时,大量细节彼此缠绕,刚入门的交易者往往直接略过,等实盘亏了才回头补课。我见过太多人——包括早年的自己——为这些疏忽交过学费。 这篇内容只盯一个现象:多数初学者在 MQL5 上搭 EA 时反复掉进同一批陷阱。把这批坑点列出来,顺手给几段可跑的代码和验证办法,你就能在买别人 EA 或自己写逻辑时,先筛掉明显不稳健的那一拨。 外汇与贵金属杠杆高、滑点跳空频繁,回测漂亮不等于实盘能活。用下面思路自测,至少能把‘一眼假’的策略挡在门外。

◍ EA 市场里的几处硬伤信号

扫一遍 MQL5 市场就能发现,现在铺天盖地都是拿 ChatGPT、Gemini 当卖点的 AI 交易系统。这类说辞基本站不住脚:商业大模型做回测几乎不可能避开前瞻偏差,而且真要和 MQL5 接大模型通常得走 WebRequest 联网。卖家若绝口不提 EA 需要网络或 prompt,合规度就先打个问号。 AI 不等于大模型。无监督、监督、强化学习都算 AI,所以标榜「人工智能 EA」本身不算虚假宣传,问题出在多数产品根本没跑神经网络。一个粗筛办法是看有没有附 ONNX 文件——如果只有单个 ex5,那模型参数只能硬编码进程序,文件体积通常会破 1 MB。 几个红色警示很具体:止损比止盈宽太多,靠指标把回测亏损单过滤掉伪造高胜率,实盘一次亏就能吞掉好几笔盈利;信号账户只投 10–100 美元,卖的 EA 却要 1000 美元以上,像是预判迟早爆仓先套现;新开账户没几笔交易就敢标 100% 胜率;再用马丁、网格、摊薄成本,亏损后翻倍加仓,回撤时同向加量,这类 20 世纪老套路长期期望回报 ≤0。 数学上很直白:若主干策略没有明显优势(p 显著 >0.5),期望回报就 ≤0,只是亏还没落袋。网格和马丁即便贴在「能盈利」的入场点旁,单笔也可能亏光全部余额,不像正常风控只伤一小块。外汇和贵金属杠杆高,碰这类 EA 前最好先想清楚自己扛不扛得住大幅回撤。 点明这些不是劝退 MQL5 上的 EA 买卖。恰恰相反,用靠谱营销和真能打的 EA 去挤掉水货,市场信誉才能起来。

用随机数据看清参数挑食的陷阱

过度拟合在交易模型里是个老毛病:策略在历史样本上漂亮,碰到没见过的数据就垮。核心诱因往往是参数选得太窄——只盯着某几个特征组合反复调,等于逼模型去适配一小片数据切片。 下面这段 Python 演示很直白:先随机生成 1000 笔交易,每笔带三个类别特征(各取 a/b/c、d/e/f、g/h/i 之一),结果 0/1 随机抛。固定随机种子 42 保证可复现。

MQL5 / C++
class="kw">import numpy as np
class="kw">import pandas as pd
class="kw">import seaborn as sns
class="kw">import matplotlib.pyplot as plt
# Step class="num">1: Generate random samples
np.random.seed(class="num">42)  # For reproducibility
# Possible feature values
feature_1_values = [&class="macro">#x27;a&class="macro">#x27;, &class="macro">#x27;b&class="macro">#x27;, &class="macro">#x27;c&class="macro">#x27;]
feature_2_values = [&class="macro">#x27;d&class="macro">#x27;, &class="macro">#x27;e&class="macro">#x27;, &class="macro">#x27;f&class="macro">#x27;]
feature_3_values = [&class="macro">#x27;g&class="macro">#x27;, &class="macro">#x27;h&class="macro">#x27;, &class="macro">#x27;i&class="macro">#x27;]
# Generate random data
n_samples = class="num">1000
feature_1 = np.random.choice(feature_1_values, n_samples)
feature_2 = np.random.choice(feature_2_values, n_samples)
feature_3 = np.random.choice(feature_3_values, n_samples)
outcome = np.random.choice([class="num">0, class="num">1], n_samples)  # Random binary outcome
# Create a DataFrame
df = pd.DataFrame({
    &class="macro">#x27;feature_1&class="macro">#x27;: feature_1,
    &class="macro">#x27;feature_2&class="macro">#x27;: feature_2,
    &class="macro">#x27;feature_3&class="macro">#x27;: feature_3,
    &class="macro">#x27;outcome&class="macro">#x27;: outcome
})

def plot_filtered_distribution(df, feature_filters):
    # Filter the DataFrame based on the specified feature values
    filtered_df = df
    for feature, value in feature_filters.items():
        filtered_df = filtered_df[filtered_df[feature] == value]
    
    # Check if filtered dataframe is empty
    if filtered_df.empty:
        print("No data available for the selected feature combination.")
        class="kw">return
    # Plot the distribution of outcomes based on the filtered data
    sns.countplot(x=&class="macro">#x27;outcome&class="macro">#x27;, data=filtered_df, palette=&class="macro">#x27;Set2&class="macro">#x27;)
    plt.title(f&class="macro">#x27;Distribution of Outcomes(filtered by {", ".join([f"{key}={value}" for key, value in feature_filters.items()])})&class="macro">#x27;)
    plt.show()
# Example usage: Visualize the distribution of outcomes when filtering by feature_1 = &class="macro">#x27;a&class="macro">#x27;, feature_2 = &class="macro">#x27;d&class="macro">#x27;, feature_3 = &class="macro">#x27;g&class="macro">#x27;
plot_filtered_distribution(df, {&class="macro">#x27;feature_1&class="macro">#x27;: &class="macro">#x27;b&class="macro">#x27;, &class="macro">#x27;feature_2&class="macro">#x27;: &class="macro">#x27;d&class="macro">#x27;, &class="macro">#x27;feature_3&class="macro">#x27;: &class="macro">#x27;g&class="macro">#x27;})
代码逐行拆解:前 4 行引入 numpy、pandas、seaborn、matplotlib 四个库;seed(42) 锁死随机性,跑出来人人一样。feature_*_values 定义三列特征的候选值。n_samples=1000 决定样本量,np.random.choice 按均匀概率填三列特征和 0/1 结果。pd.DataFrame 把数据装进表。 plot_filtered_distribution 函数接收过滤条件字典,逐键用布尔索引缩窄数据;空了就提示退出,否则用 countplot 画 outcome 分布并弹窗。末尾调用时只留 feature_1='b'、feature_2='d'、feature_3='g' 的子集——在纯随机数据里,换一组三特征就可能从盈多变成亏多。 别把正态当圣经 你越拿特征去切数据,越容易在随机集里也捞出“人为高绩效”。外汇和贵金属市场高风险,任何靠堆参数精调出的历史曲线,未来普适性都倾向脆弱;真该盯的是宽样本下绩效一致,而不是窄切片里的漂亮数字。

MQL5 / C++
class="kw">import numpy as np
class="kw">import pandas as pd
class="kw">import seaborn as sns
class="kw">import matplotlib.pyplot as plt
# Step class="num">1: Generate random samples
np.random.seed(class="num">42)  # For reproducibility
# Possible feature values
feature_1_values = [&class="macro">#x27;a&class="macro">#x27;, &class="macro">#x27;b&class="macro">#x27;, &class="macro">#x27;c&class="macro">#x27;]
feature_2_values = [&class="macro">#x27;d&class="macro">#x27;, &class="macro">#x27;e&class="macro">#x27;, &class="macro">#x27;f&class="macro">#x27;]
feature_3_values = [&class="macro">#x27;g&class="macro">#x27;, &class="macro">#x27;h&class="macro">#x27;, &class="macro">#x27;i&class="macro">#x27;]
# Generate random data
n_samples = class="num">1000
feature_1 = np.random.choice(feature_1_values, n_samples)
feature_2 = np.random.choice(feature_2_values, n_samples)
feature_3 = np.random.choice(feature_3_values, n_samples)
outcome = np.random.choice([class="num">0, class="num">1], n_samples)  # Random binary outcome
# Create a DataFrame
df = pd.DataFrame({
    &class="macro">#x27;feature_1&class="macro">#x27;: feature_1,
    &class="macro">#x27;feature_2&class="macro">#x27;: feature_2,
    &class="macro">#x27;feature_3&class="macro">#x27;: feature_3,
    &class="macro">#x27;outcome&class="macro">#x27;: outcome
})

def plot_filtered_distribution(df, feature_filters):
    # Filter the DataFrame based on the specified feature values
    filtered_df = df
    for feature, value in feature_filters.items():
        filtered_df = filtered_df[filtered_df[feature] == value]
    
    # Check if filtered dataframe is empty
    if filtered_df.empty:
        print("No data available for the selected feature combination.")
        class="kw">return
    # Plot the distribution of outcomes based on the filtered data
    sns.countplot(x=&class="macro">#x27;outcome&class="macro">#x27;, data=filtered_df, palette=&class="macro">#x27;Set2&class="macro">#x27;)
    plt.title(f&class="macro">#x27;Distribution of Outcomes(filtered by {", ".join([f"{key}={value}" for key, value in feature_filters.items()])})&class="macro">#x27;)
    plt.show()
# Example usage: Visualize the distribution of outcomes when filtering by feature_1 = &class="macro">#x27;a&class="macro">#x27;, feature_2 = &class="macro">#x27;d&class="macro">#x27;, feature_3 = &class="macro">#x27;g&class="macro">#x27;
plot_filtered_distribution(df, {&class="macro">#x27;feature_1&class="macro">#x27;: &class="macro">#x27;b&class="macro">#x27;, &class="macro">#x27;feature_2&class="macro">#x27;: &class="macro">#x27;d&class="macro">#x27;, &class="macro">#x27;feature_3&class="macro">#x27;: &class="macro">#x27;g&class="macro">#x27;})

常见问题

先用历史数据做多次压力测试,故意拉长极端行情区间,看回撤和连续亏损是否超出承受范围,再考虑上实盘。
常见坑是没处理点差跳空、订单返回码不判断、以及过度拟合某段历史,导致换周期就失灵。
小布可以基于你贴出的策略逻辑或信号表现,指出订单处理缺失、参数过拟合等高风险点,省去手动排查。
用随机打乱的行情数据跑同一组参数,若收益剧烈波动或转亏,说明它只是记住了特定走势而非真逻辑。
宣传曲线无回撤、只给单一品种展示、参数说明含糊且不让调的,大概率是硬伤信号,实盘易崩。