用于一组指标信号的朴素贝叶斯分类器·进阶篇
(2/3)· 接上篇理论铺垫,本篇直接落地代码框架与信号统计,让指标委员会跑起来
「用枚举把指标信号框架搭起来」
在 MT5 里做信号框架,第一步是把指标类型收进一个枚举。内置指标和 iCustom 都进表,名字后缀带提示:i 是一个缓冲区、d 是两个、t 是三个,纯给用户防手滑;填错缓冲区索引或参数数量,框架直接往日志吐错误,不静默崩。 每个指标除了在输入参数里挑类型,还要给实际参数串、缓冲区编号、起始柱线号。信号条件靠另一套枚举收敛:值非空、带正负号、穿越另一指标或某价位、相对另一指标或等级的位置关系。第一个元素 '禁用' 用来关掉整组条件,默认每组信号都是禁用的,省得误触发。 方向检查另起一个枚举 EqualOrNone / UpSideOrAboveOrPositve / DownSideOrBelowOrNegative / NotEqual。比如 UpSide 那组能查上穿、数值为正、同指标连续柱线递增、异指标 X 高于 Y、位置高于某级;DownSide 镜像反过来。触发后由 Execution 枚举决定动作:发消息、买、卖、平掉所有仓、多空翻转、改止损止盈、或跳去查下一个条件——最后这项让信号链成为可能,例如先判主线穿信号线,再判是否发生在某级之上。 指标属性组最多 6 个,信号最多 6 个,单指标参数上限 20 个,都在 IndicatN.mqh 里可改。Indicator1Name 在选 iCustom 时填自定义指标名;Parameter1List 用逗号串自动识型:11.0 算 double、11 算 int、2015.01.01 20:00 算 datetime、true/false 布尔、带引号算 string;均线类型也能写 sma/ema 不带引号。缓冲区名认 main/signal/upper/jaw/tenkan 等。直接写 iMA@0(1,0,sma,high)[1] 这种原型,可以绕开属性组,在信号参数里现用现配。 EA 端用 #include <IndicatN.mqh> 编完,设置框就长出指标属性和信号定义两坨输入组。任何数值参数能写成 =var4 这种,绑到框架的 var1~var9 优化输入上,比如 iMACD@main(=var4,=var5,=var6,open)[0],关优化时也从同名输入读值,方便单一回测调参。
enum IndicatorType
{
iCustom,
iAC,
iAD,
tADX_period_price,
tAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price,
iAO,
iATR_period,
tBands_period_deviation_shift_price,
iBearsPower_period_price,
iBullsPower_period_price,
iBWMFI,
iCCI_period_price,
iDeMarker_period,
tEnvelopes_period_method_shift_price_deviation,信号条件与指标枚举的骨架
这段声明把 EA 可调用的指标类型先锁死在枚举里:从 iForce、iMomentum、iRSI 到 dStochastic、dMACD 共 17 种,覆盖了趋势、动量、波动率三类常用工具。想接自定义指标就走 iCustom,但必须手动填名称、参数列表和缓存区序号,MT5 不会替你猜。 SignalCondition 枚举定义了 7 种判定逻辑:最常用的是 IndicatorXcrossesIndicatorY(两指标金叉死叉)和 IndicatorXcrossesLevelX(触线)。UpZeroDown 用 EqualOrNone / UpSide / DownSide / NotEqual 四个值描述方向或正负号,相当于把「上穿零轴」「低于基准」这类口语翻成机器能比的大小关系。 SignalType 决定了条件命中后干什么:除了 Alert、Buy、Sell,还有 CloseBuy、ModifyStopLoss、ProceedToNextCondition 等 11 种。ProceedToNextCondition 很关键——它让多个信号串行校验,而不是一触发就下单,实盘里能挡掉不少毛刺。 TradeSignals 类把上述结果收口:alert/buy/sell 等 bool 字段初始全 false,value 给 EMPTY_VALUE。外部输入 ConsistentSignalNumber 默认 1,意思是连续几根 K 线同信号才认;外汇与贵金属波动剧烈、滑点频繁,建议先在策略测试器里把这个数调到 2~3 看回撤变化再上真仓。
enum IndicatorType { iForce_period_method_price, dFractals, dGator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price, fIchimoku_tenkan_kijun_senkou, iMomentum_period_price, iMFI_period, iMA_period_shift_method_price, dMACD_fast_slow_signal_price, iOBV_price, iOsMA_fast_slow_signal_price, iRSI_period_price, dRVI_period, iSAR_step_maximum, iStdDev_period_shift_method_price, dStochastic_K_D_slowing_method_price, iWPR_period }; enum SignalCondition { Disabled, NotEmptyIndicatorX, SignOfValueIndicatorX, IndicatorXcrossesIndicatorY, IndicatorXcrossesLevelX, IndicatorXrelatesToIndicatorY, IndicatorXrelatesToLevelX }; enum UpZeroDown { EqualOrNone, UpSideOrAboveOrPositve, DownSideOrBelowOrNegative, NotEqual }; enum SignalType { Alert, Buy, Sell, CloseBuy, CloseSell, CloseAll, BuyAndCloseSell, SellAndCloseBuy, ModifyStopLoss, ModifyTakeProfit, ProceedToNextCondition }; class="kw">input IndicatorType Indicator1Selector = iCustom; class="kw">input class="type">class="kw">string Indicator1Name = ""; class="kw">input class="type">class="kw">string Parameter1List = "" class=class="str">"cmt">/*class="num">1.0,value:t,value:t*/; class="kw">input class="type">class="kw">string Indicator1Buffer = ""; class="kw">input class="type">int Indicator1Bar = class="num">1; class="kw">input class="type">class="kw">string __SIGNAL_A = ""; class="kw">input SignalCondition ConditionA = Disabled; class="kw">input class="type">class="kw">string IndicatorA1 = ""; class="kw">input class="type">class="kw">string IndicatorA2 = ""; class="kw">input class="type">class="kw">double LevelA1 = class="num">0; class="kw">input class="type">class="kw">double LevelA2 = class="num">0; class="kw">input UpZeroDown DirectionA = EqualOrNone; class="kw">input SignalType ExecutionA = Alert; class TradeSignals { class="kw">public: class="type">bool alert; class="type">bool buy; class="type">bool sell; class="type">bool buyExit; class="type">bool sellExit; class="type">bool ModifySL; class="type">bool ModifyTP; class="type">int index; class="type">class="kw">double value; class="type">class="kw">string message; TradeSignals(): alert(class="kw">false), buy(class="kw">false), sell(class="kw">false), buyExit(class="kw">false), sellExit(class="kw">false), ModifySL(class="kw">false), ModifyTP(class="kw">false), value(EMPTY_VALUE), message(""){} }; class="macro">#include <IndicatN.mqh> class="macro">#include <Expert0.mqh> class="macro">#ifdef __MQL5__ class="macro">#include <MarketMQL4.mqh> class="macro">#include <ind4to5.mqh> class="macro">#include <mt4orders.mqh> class="macro">#endif class="kw">input class="type">int ConsistentSignalNumber = class="num">1; class="kw">input class="type">int Magic = class="num">0;
◍ 信号统计的柱线级埋点
在 EA 初始化阶段,先用 input 把 Lot 设成 0.01、TradeDuration 设成 1,再交给 Expert 对象托管下单逻辑;OnInit 直接回传指标命名空间的 handleInit 结果,不在这里写任何业务。 OnTick 里用 static datetime lastBar 卡住「每根新柱才跑一次」的节奏:只有 Time[0] 变了才调用 IndicatN::handleStart 拿信号数组,随后更新 lastBar。这样回测或实盘里都不会在同一根柱内重复统计,外汇与贵金属品种的高波动环境下能少算很多噪声。 统计块本身是一组裸计数器:bars 累计抽样柱数,bull/bear 分别记多头/空头事件柱,buy[]/sell[] 是无条件信号命中数,buyOnBull[]/sellOnBear[] 只在大方向已确立(up/down 标记为真)时才累加,相当于「条件过滤后的信号」。 遍历 ts 数组时,先靠 H_BULL / H_BEAR 翻转 up / down,之后凡是以 S_BUY 开头且 up 为真的,才进 buyOnBull;S_SELL 同理。PrintDetails 打开时会向日志吐出 buyOk / sellOk 加信号名,方便你直接在 MT5 终端对照哪类信号在趋势段里真正生效。
class="kw">input class="type">class="kw">float Lot = class="num">0.01f; class="kw">input class="type">int TradeDuration = class="num">1; Expert e(Magic, Lot); class="type">int OnInit() { class="kw">return IndicatN::handleInit(); } class="type">void OnTick() { class="kw">static class="type">class="kw">datetime lastBar; if(lastBar != Time[class="num">0]) { class="kw">const RubbArray<TradeSignals> *ts = IndicatN::handleStart(); ... lastBar = Time[class="num">0]; } } class="type">int bars = class="num">0; class=class="str">"cmt">// 柱线/抽样的总计数 class="type">int bull = class="num">0, bear = class="num">0; class=class="str">"cmt">// 每笔交易类型的柱线/抽样数量 class="type">int buy[MAX_SIGNAL_NUM] = {class="num">0}, sell[MAX_SIGNAL_NUM] = {class="num">0}; class=class="str">"cmt">// 无条件信号数组 class="type">int buyOnBull[MAX_SIGNAL_NUM] = {class="num">0}, sellOnBear[MAX_SIGNAL_NUM] = {class="num">0}; class=class="str">"cmt">// 条件 (成功) 信号数组 class="kw">const RubbArray<TradeSignals> *ts = IndicatN::handleStart(); class="type">bool up = class="kw">false, down = class="kw">false; class="type">int buySignalCount = class="num">0, sellSignalCount = class="num">0; for(class="type">int i = class="num">0; i < ts.size(); i++) { class=class="str">"cmt">// 用于收集统计信息d的警报 if(ts[i].alert) { class=class="str">"cmt">// 在设置事件时, 按照 i 进行列举, class=class="str">"cmt">// 假设 H_xxx 应先至, 在信号 S_xxx 之前, class=class="str">"cmt">// 因为我们在此分配向上或向下的标记 if(IndicatN::GetSignal(ts[i].index) == "H_BULL") { bull++; buy[ts[i].index]++; up = true; } else if(IndicatN::GetSignal(ts[i].index) == "H_BEAR") { bear++; sell[ts[i].index]++; down = true; } else if(StringFind(IndicatN::GetSignal(ts[i].index), "S_BUY") == class="num">0) { buy[ts[i].index]++; if(up) { if(PrintDetails) Print("buyOk ", IndicatN::GetSignal(ts[i].index)); buyOnBull[ts[i].index]++; } } else if(StringFind(IndicatN::GetSignal(ts[i].index), "S_SELL") == class="num">0) { sell[ts[i].index]++; if(down) { if(PrintDetails) Print("sellOk ", IndicatN::GetSignal(ts[i].index)); sellOnBear[ts[i].index]++; } } } }
「信号计数与冲突时的下单逻辑」
这段逻辑跑在每根 K 线收尾,先按 PrintDetails 开关决定是否把各策略消息打印到日志,再分别累加 buySignalCount 与 sellSignalCount。 若 e.getLastOrderBar() 返回的持仓 K 线数大于等于 TradeDuration,就直接平掉所有市价单,相当于给单子设了一个硬止损周期。 当买、卖信号计数都达到 ConsistentSignalNumber 时,代码只打印“信号冲突”而不下单,避免双向拉扯。 否则,若买信号达标就平掉卖单、且在无买单时补一个市价买;卖信号达标则对称处理。外汇与贵金属杠杆高,这种一致性阈值若设太小,可能在震荡里频繁开平。
if(PrintDetails) Print(ts[i].message); } } if(ts[i].buy) { buySignalCount++; } else if(ts[i].sell) { sellSignalCount++; } if(e.getLastOrderBar() >= TradeDuration) { e.closeMarketOrders(); } if(buySignalCount >= ConsistentSignalNumber && sellSignalCount >= ConsistentSignalNumber) { Print("信号冲突"); } else if(buySignalCount >= ConsistentSignalNumber) { e.closeMarketOrders(e.mask(OP_SELL)); if(e.getOrderCount(e.mask(OP_BUY)) == class="num">0) { e.placeMarketOrder(OP_BUY); } } else if(sellSignalCount >= ConsistentSignalNumber) { e.closeMarketOrders(e.mask(OP_BUY)); if(e.getOrderCount(e.mask(OP_SELL)) == class="num">0) { e.placeMarketOrder(OP_SELL); } }