使用MQL5和Python构建自优化EA(第三部分):破解Boom 1000算法(基础篇)
Boom 1000 的看涨突变与指标建模思路
Boom 1000 是 Deriv 合成市场中波动最极端、最不可预测的标的之一。它的 M1 图表典型特征是:连续 20 分钟左右的缓慢小幅看跌 K 线,随后在一根 K 线内完全收复跌幅,出现摩天楼般高度的暴力看涨 K 线。 这类突发看涨 K 线伴随的 tick 往往不会下发到客户终端,实盘里止损基本都会被突破且附带滑点。所以多数能稳定盈利的 Boom 1000 交易者,策略权重严重偏向只做买入设置,而非双向搏杀。 原文指出一个可验证现象:Boom 1000 在 M1 上可能阴跌 20 分钟,再单根 K 线拉回全部跌幅。这种超强看涨偏向,让买入信号的历史胜率显著高于卖出。 更硬核的切入点是:直接预测价格很难,因为 Deriv 的随机数生成器输入对我们不可见;但把指标(如均线、RSI)套到 Boom 1000 上后,指标依赖的 OHLC 输入值是可获取的。用指标读数作自变量、价格变化作因变量建模,可能比直接拟合价格获得更高近似准确率,且方向一致——指标看跌时价格也倾向跌。外汇/贵金属及合成市场杠杆极高,回测结论不代表实盘概率。
「用十万行M1数据验证RSI与Boom 1000的弱关联」
为评估策略可行性,我写了一个定制脚本从 MT5 终端拉取了 100000 行 M1 数据,并附带每个时间点的 RSI 读数。探索性分析显示:RSI 下行时,Boom 1000 价格水平同步下跌的比例约为 83%,反向误导的情况占 17%——这说明预测 RSI 对判断价格高低有参考意义,但远非可靠因果。 真正的问题在相关性上。RSI 与 Boom 1000 价格水平的相关系数只有 0.016,散点图无论二维还是高维投影都看不到明显结构,数据几乎无法被有效分离。外汇与合成指数类品种波动剧烈,这种弱相关本身就提示高风险,不能把 RSI 当方向依据。 后续把数据集按时间切为训练优化集与验证测试集,构造两个目标变量:一个抓价格变化,一个抓 RSI 变化。两个相同结构的深度神经网络分类器训下来,预测价格变化的准确率约 53%,预测 RSI 变化的约 63%,且后者误差方差更低,学得更稳。 可惜 DNN 始终调不出不过拟合的版本——5 折时间序列交叉验证(不打乱)里,验证集表现没超过默认网络。最终选了默认 RSI 模型导出 ONNX,在 MQL5 里搭了面向 Boom 1000 的 AI 驱动 EA,实盘前建议先用策略测试器跑一遍历史校验。
◍ 用脚本把 Boom 1000 报价和 RSI 拽出来
做价格行为分析前,先得把原始数据落到本地。针对 Boom 1000 这类合成指数,直接在 MT5 里跑一段 MQL5 脚本,就能把每根 K 线的时间戳、OHLC 以及 RSI 读数一次性导成 CSV,省去手动导表的麻烦。 关键坑在 RSI 缓冲区的顺序:MQL5 默认 CopyBuffer 取出来的数组是时间正序,若不调用 ArraySetAsSeries(rsi_buffer,true) 翻成序列数组,写进文件的 RSI 会逆时间排列,和 K 线对不上。脚本里先拷缓冲再设序列,顺序不能反。 下面这段脚本默认抓取 100000 根棒(size 输入参数),RSI 周期写死为 20、收盘价计算。实际跑的时候按品种流动性调小 size 可能更现实,Boom 1000 在 MT5 报价服务器上历史深度有限,拉满容易空 buffer。外汇与合成指数波动剧烈,导出后做统计请自担高风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ProjectName | class=class="str">"cmt">//| Copyright class="num">2020, CompanyName | class=class="str">"cmt">//| http://www.companyname.net | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Gamuchirai Zororo Ndawana" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script Inputs | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">int size = class="num">100000; class=class="str">"cmt">//How much data should we fetch? class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int rsi_handler; class="type">class="kw">double rsi_buffer[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| On start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Load indicator rsi_handler = iRSI(_Symbol,PERIOD_CURRENT,class="num">20,PRICE_CLOSE); CopyBuffer(rsi_handler,class="num">0,class="num">0,size,rsi_buffer); ArraySetAsSeries(rsi_buffer,true); class=class="str">"cmt">//--- File name class="type">class="kw">string file_name = "Market Data " + Symbol() + ".csv"; class=class="str">"cmt">//--- Write to file class="type">int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(class="type">int i= size;i>=class="num">0;i--) { if(i == size) { FileWrite(file_handle,"Time","Open","High","Low","Close","RSI"); } else { FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
把K线与RSI一起落盘
上面这段是写入循环的后半段,负责把每一根bar的OHLC和对应的RSI值打包进文件,再用Print打到日志里方便肉眼核对。 iOpen/iHigh/iLow/iClose都取PERIOD_CURRENT下的第i根,rsi_buffer[i]则是同一根bar算出来的RSI值,五个数作为一行写进句柄file_handle。 Print那行把iTime和iClose再打一遍,后面跟rsi_buffer[i],等于在MT5的“专家”标签页里能直接看到时间序列和指标值的对照,不用反复打开CSV。 循环结束后FileClose(file_handle)必须执行,否则文件句柄泄露,下次开盘再写可能返回无效句柄-1。外汇与贵金属波动剧烈,这类落盘只用于复盘验证,实盘信号须自行加风控。
iOpen(Symbol(),PERIOD_CURRENT,i), iHigh(Symbol(),PERIOD_CURRENT,i), iLow(Symbol(),PERIOD_CURRENT,i), iClose(Symbol(),PERIOD_CURRENT,i), rsi_buffer[i] ); Print("Time: ",iTime(Symbol(),PERIOD_CURRENT,i),"Close: ",iClose(Symbol(),PERIOD_CURRENT,i),"RSI",rsi_buffer[i]); } } class=class="str">"cmt">//--- Close the file FileClose(file_handle); } class=class="str">"cmt">//+------------------------------------------------------------------+
「给 Boom 1000 数据打上前瞻标签」
做价格行为类预测前,先把原始 CSV 清理成可训练结构。实测环境里 Pandas 2.2.1 配 Numpy 1.26.4 能直接跑通这套读取与打标流程,老版本可能在 shift 与 loc 链式赋值上报错。 读取 Boom 1000 指数的 OHLC 与 RSI 后,用 look_ahead=20 根 K 线作为前瞻窗口:把 20 根后的 Close、RSI 分别 shift 成 Price Target 与 RSI Target,再据此生成 0/1 的二元标签——价涨标 1、价跌标 0,RSI 同理。 打标完必须 dropna,否则末尾 20 行空标签会污染建模。下面这段代码就是数据清理的核心,复制进 Jupyter 或 MT5 的 Python 环境即可验证。 外汇与指数衍生品(如 Boom 1000)杠杆高、滑点跳空频繁,二元标签仅描述历史统计关系,对未来转折只有概率意义,实盘请控仓。
class="macro">#Import the libraries we need class="kw">import pandas as pd class="kw">import numpy as np class="macro">#Display library versions print(f"Pandas version {pd.__version__}") print(f"Numpy version {np.__version__}") class="macro">#Read in the data we need boom_1000 = pd.read_csv("Market Data Boom class="num">1000 Index.csv") class="macro">#Let&class="macro">#x27;s see the data boom_1000 class="macro">#Define how far into the future we should forecast look_ahead = class="num">20 class="macro">#Let&class="macro">#x27;s add targets and labels for plotting boom_1000["Price Target"] = boom_1000["Close"].shift(-look_ahead) boom_1000["RSI Target"] = boom_1000["RSI"].shift(-look_ahead) class="macro">#Let&class="macro">#x27;s also add binary targets for plotting purposes boom_1000["Price Binary Target"] = np.nan boom_1000["RSI Binary Target"] = np.nan class="macro">#Label the binary targets boom_1000.loc[boom_1000["Price Target"] < boom_1000["Close"],"Price Binary Target"] = class="num">0 boom_1000.loc[boom_1000["Price Target"] > boom_1000["Close"],"Price Binary Target"] = class="num">1 boom_1000.loc[boom_1000["RSI Target"] < boom_1000["RSI"],"RSI Binary Target"] = class="num">0 boom_1000.loc[boom_1000["RSI Target"] > boom_1000["RSI"],"RSI Binary Target"] = class="num">1 class="macro">#Drop na values boom_1000.dropna(inplace=True) class="macro">#Define the predictors and targets predictors = ["Open","High","Low","Close","RSI"] old_target = "Price Binary Target" new_target = "RSI Binary Target"
◍ RSI 信号纯度与三维可视化的边界
拿 Boom 1000 的二分目标做对照,RSI 二进制信号与价格二进制目标同向的比例约 83.69%。计算方式是统计两者不相等的行数,除以总行数,再用 1 减掉这个分歧占比。这个纯度不算低,给了继续做可视化的底气,但还远谈不上可靠。 分类图里 RSI 下跌组(0 列)大多压着价格跌的蓝点,上涨组(1 列)反过来以橙点为主,说明 RSI 在切分涨跌上有一定区分度。可惜二维散点图把 RSI 读数和收盘价摊开看,只剩一缕缕意大利面状轨迹,蓝橙交错毫无规律,暗示还有别的变量在拽价格。 把数据硬塞进三维空间(RSI、High、Close)也没救,前 10000 行点云仍是随机撒开,看不出能用的交互结构。相关矩阵热力图更直白:RSI 与价格类字段的相关性贴近 0。外汇与合成指数类品种波动受多重因子驱动,单靠 RSI 线性关系建模大概率会失效,后面得挂附加指标才够用。
class="macro">#Exploratory data analysis class="kw">import seaborn as sns print(f"Seaborn version {sns.__version__}") Seaborn version class="num">0.13.class="num">2 class="macro">#Let&class="macro">#x27;s assess the purity of the signals generated rsi_purity = class="num">1 - boom_1000.loc[boom_1000["RSI Binary Target"] != boom_1000["Price Binary Target"]].shape[class="num">0] / boom_1000.shape[class="num">0] print(f"Price and the RSI tend to move together {rsi_purity * class="num">100}% of the time") class="macro">#Let&class="macro">#x27;s see this purity level we just calculated sns.catplot(data=boom_1000,x="RSI Binary Target",y="Close",hue="Price Binary Target") class="macro">#Let us also observe a scatter plot of the two sns.scatterplot(data=boom_1000,x="RSI",y="Close",hue="Price Binary Target") class="macro">#Let&class="macro">#x27;s create 3D scatter plots class="kw">import matplotlib.pyplot as plt class="macro">#Define the plot end end = class="num">10000 class="macro">#Visualizing our data in 3D fig = plt.figure(figsize=(class="num">7,class="num">7)) ax = fig.add_subplot(class="num">111,projection=&class="macro">#x27;3d&class="macro">#x27;) colors = [&class="macro">#x27;blue&class="macro">#x27; if movement == class="num">0 else &class="macro">#x27;red&class="macro">#x27; for movement in boom_1000.loc[class="num">0:end,"Price Binary Target"]] ax.scatter(boom_1000.loc[class="num">0:end,"RSI"],boom_1000.loc[class="num">0:end,"High"],boom_1000.loc[class="num">0:end,"Close"],c=colors) ax.set_xlabel(&class="macro">#x27;Boom class="num">1000 RSI&class="macro">#x27;) ax.set_ylabel(&class="macro">#x27;Boom class="num">1000 High&class="macro">#x27;) ax.set_zlabel(&class="macro">#x27;Boom class="num">1000 Close&class="macro">#x27;) class="macro">#Let&class="macro">#x27;s analyze the correlation levels sns.heatmap(boom_1000.loc[:,predictors].corr(),annot=True)