构建蜡烛图趋势约束模型(第8部分):EA开发(II)·综合运用
EA 骨架里的手工作业:参数与 RSI 句柄
这段 MT5 专家顾问的头部声明,把仓位、止损、止盈和移动止损直接做成外部输入:Lots=0.1、StopLoss=100 点、TakeProfit=200 点、TrailingStop=50 点。调参时不用动代码,在属性框改数字即可,但外汇和贵金属杠杆高,0.1 手在迷你账户也可能触发强平,先按自己保证金算一遍。 全局里除了 RSI 相关变量,还挂了一个 CTrade trade 实例,后续发单、平仓都靠它。OnInit 里用 iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE) 拿指标句柄,失败就打印并返回 INIT_FAILED,这是 MT5 里最容易忽略的坑——句柄无效时后面全废。 OnDeinit 只做一件事:IndicatorRelease(rsi_handle) 释放资源,避免反复加载 EA 时句柄泄漏。OnTick 开头先抓日线开盘价和收盘价判断趋势方向,再用 CopyBuffer 取当前 RSI 值,取不到就直接 return。下面这段是原文截到一半的循环,用来在趋势反转时平掉旧仓。
class="kw">input class="type">class="kw">double Lots = class="num">0.1; class=class="str">"cmt">// Lot size class="kw">input class="type">class="kw">double StopLoss = class="num">100; class=class="str">"cmt">// Stop Loss in points class="kw">input class="type">class="kw">double TakeProfit = class="num">200; class=class="str">"cmt">// Take Profit in points class="kw">input class="type">class="kw">double TrailingStop = class="num">50; class=class="str">"cmt">// Trailing Stop in points class=class="str">"cmt">// Global variables class="type">class="kw">double rsi_value; class="type">int rsi_handle; CTrade trade; class=class="str">"cmt">// Declare an instance of the CTrade class class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Create an RSI indicator handle rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE); if (rsi_handle == INVALID_HANDLE) { Print("Failed to create RSI indicator handle"); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">// Release the RSI indicator handle IndicatorRelease(rsi_handle); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Determine current daily trend(bullish or bearish) class="type">class="kw">double daily_open = iOpen(_Symbol, PERIOD_D1, class="num">0); class="type">class="kw">double daily_close = iClose(_Symbol, PERIOD_D1, class="num">0); class="type">bool is_bullish = daily_close > daily_open; class="type">bool is_bearish = daily_close < daily_open; class=class="str">"cmt">// Get the RSI value for the current bar class="type">class="kw">double rsi_values[]; if (CopyBuffer(rsi_handle, class="num">0, class="num">0, class="num">1, rsi_values) <= class="num">0) { Print("Failed to get RSI value"); class="kw">return; } rsi_value = rsi_values[class="num">0]; class=class="str">"cmt">// Close open positions if the trend changes for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { if (PositionSelect(PositionGetSymbol(i))) class=class="str">"cmt">// Corrected usage {
◍ 反向持仓与趋势冲突时的平仓逻辑
这段逻辑解决一个实战常见坑:当持仓方向与实时判定的趋势相反时,EA 必须能拿到具体 ticket 并立刻平掉,而不是笼统遍历。下面这段代码先取持仓类型和 ticket,再比对 is_bearish / is_bullish 信号,冲突即用 trade.PositionClose 平仓。 int position_type = PositionGetInteger(POSITION_TYPE); ulong ticket = PositionGetInteger(POSITION_TICKET); // Get the position ticket
| if ((position_type == POSITION_TYPE_BUY && is_bearish) |
|---|
(position_type == POSITION_TYPE_SELL && is_bullish)) { trade.PositionClose(ticket); // Use the ulong variable directly } } } 开仓部分则卡死「无持仓」前提:多头条件为 is_bullish 且 rsi_value < RSI_Oversold,空单为 is_bearish 且 rsi_value > RSI_Overbought,两者都要求 PositionsTotal()==0 才下手,避免马丁加仓。SL/TP 直接用 StopLoss、TakeProfit 乘 _Point 算,外汇与贵金属波动大,这套参数在高杠杆下回撤可能很猛。 trailing stop 段从 PositionsTotal()-1 倒序扫,PositionSelect 配合 PositionGetSymbol(i) 选对持仓;买仓用 SYMBOL_BID 测距,盈利超过 TrailingStop*_Point 才移动止损。倒序是为防止平仓后索引错位,直接抄正序循环会漏单。
class="type">int position_type = PositionGetInteger(POSITION_TYPE); class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">// Get the position ticket if ((position_type == POSITION_TYPE_BUY && is_bearish) || (position_type == POSITION_TYPE_SELL && is_bullish)) { trade.PositionClose(ticket); class=class="str">"cmt">// Use the class="type">ulong variable directly } } } class=class="str">"cmt">// Check for buy condition(bullish trend + RSI oversold) if (is_bullish && rsi_value < RSI_Oversold) { class=class="str">"cmt">// No open positions? Place a buy order if (PositionsTotal() == class="num">0) { class="type">class="kw">double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class="type">class="kw">double sl = price - StopLoss * _Point; class="type">class="kw">double tp = price + TakeProfit * _Point; class=class="str">"cmt">// Open a buy order trade.Buy(Lots, _Symbol, price, sl, tp, "TrendConstraintExpert Buy"); } } class=class="str">"cmt">// Check for sell condition(bearish trend + RSI overbought) if (is_bearish && rsi_value > RSI_Overbought) { class=class="str">"cmt">// No open positions? Place a sell order if (PositionsTotal() == class="num">0) { class="type">class="kw">double price = SymbolInfoDouble(_Symbol, SYMBOL_BID); class="type">class="kw">double sl = price + StopLoss * _Point; class="type">class="kw">double tp = price - TakeProfit * _Point; class=class="str">"cmt">// Open a sell order trade.Sell(Lots, _Symbol, price, sl, tp, "TrendConstraintExpert Sell"); } } class=class="str">"cmt">// Apply trailing stop for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { if (PositionSelect(PositionGetSymbol(i))) class=class="str">"cmt">// Corrected usage { class="type">class="kw">double price = PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">double stopLoss = PositionGetDouble(POSITION_SL); class="type">class="kw">double current_price; if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID); if (current_price - price > TrailingStop * _Point) { if (stopLoss < current_price - TrailingStop * _Point)
「空单尾随止损的修改逻辑」
上面这段是处理卖出仓位的尾随止损分支。先取当前卖价 SymbolInfoDouble(_Symbol, SYMBOL_ASK),再用持仓开价与现价的差值判断是否越过 TrailingStop 个点的阈值。 判定条件 price - current_price > TrailingStop * _Point 成立时,说明浮盈空间已够移动止损。此时若原止损位在 current_price + TrailingStop*_Point 之外,或止损从未设置(stopLoss==0),就通过 trade.PositionModify 把止损提到市价上方 TrailingStop 点处,止盈维持原 POSITION_TP 不变。 在 MT5 里跑这段,TrailingStop 若设为 50,意味着空单盈利超过 50 点才触发第一次移位;外汇与贵金属杠杆高,点位跳动快,这类尾随可能在剧烈反转中连续改单,实际滑点成本需自己回测确认。
{
trade.PositionModify(PositionGetInteger(POSITION_TICKET), current_price - TrailingStop * _Point, PositionGetDouble(POSITION_TP));
}
}
}
else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
current_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if (price - current_price > TrailingStop * _Point)
{
if (stopLoss > current_price + TrailingStop * _Point || stopLoss == class="num">0)
{
trade.PositionModify(PositionGetInteger(POSITION_TICKET), current_price + TrailingStop * _Point, PositionGetDouble(POSITION_TP));
}
}
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//HAPPY DEVELOPING!在 MT5 回测里压一压趋势约束 EA
把「趋势约束 EA」丢进 MetaTrader 5 策略测试器,第一步是用历史数据搭回测环境,重点看它在不同波动段里是否真的只做趋势跟随、且严格卡 RSI 条件。先在测试器里选定 M1 周期,再把 EA 的输入参数和成交环境(点差、滑点、杠杆)按你实盘打算用的填进去,不然跑出来的曲线没有参考价值。 我自己习惯拿 Boom 500 指数先试水——这个品种跳点凶、趋势段清晰,很适合检验趋势类逻辑会不会乱开单。在 2023 年 1 月到 12 月的样本里跑一遍,主要盯三件事:盈利曲线斜率、最大回撤、以及 EA 违背 RSI 过滤的成交次数。 外汇和贵金属品种的高杠杆与滑点会放大回测与实盘的偏差,回测「可能」通过不代表实盘「倾向」盈利。任何参数微调都先在测试器里复跑,再考虑上真金白银。
◍ 下一步要补的缺口
这套不依赖外部指标的 Trend Constraint Expert 已经能在 MT5 测试器里跑出真实订单流,源码文件 5.76 KB,直接拖进编辑器就能编译验证。但它目前没写 Magic 数字,我在真实模拟账户上跑的时候,EA 会把账户里已有的其他订单也一并接管,这是实盘前必须修的坑。 日线趋势过滤那一层还太粗,入场条件只做了最基础的约束,和当前日线方向对齐的盈利概率并不高,后续得把入场逻辑拆细。外汇和贵金属杠杆高、滑点跳空频繁,没隔离订单的 EA 直接上模拟盘都有搅乱仓位的风险。 下一篇会补上 Magic 数字并把入场技术换一套写法,你現在就可以把源码里的 OrderSend 部分翻一遍,找找它没带标识位的地方。等 Magic 补齐,再拿同一条 EURUSD 日线样本回测,应该能看到订单互不干扰的差别。