订单平仓管理器EA源码(MT4)——智能离场工具
市面上99%的EA都在琢磨怎么进场。进场多刺激啊,各种金叉死叉突破回踩,听着就厉害。但说实话,一个糟糕的出场,比你喊「爆仓」还快地把盈利单变成亏损单。
我写这套EA纯粹是被行情教育了。有段时间我持仓过夜总是提心吊胆,亚盘一个毛刺就能把欧盘赚的利润全吞掉。后来干脆写了个专门管出场的EA——它不开任何新单子,就干一件事:帮我平仓。
这个EA到底在干嘛
它监控所有带指定魔数(Magic Number)的持仓,一旦满足下面任何一个条件,就全部平仓:
这三个条件你可以自由组合。我自己现在跑的是「时间+波动」组合,利润目标那个关掉了,因为我用另一套系统做止盈。
完整MQL4源码
``
cpp
//+------------------------------------------------------------------+
//| OrderCloseEA.mq4 |
//| Copyright 2026, FXEAR.com |
//| https://www.fxear.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FXEAR.com"
#property link "https://www.fxear.com"
#property version "1.00"
#property strict
//--- 输入参数
input int MagicNumber = 202602; // EA的魔数
input bool CloseByTime = true; // 启用时间平仓
input int CloseHour = 16; // 平仓时间(小时,服务器时间)
input int CloseMinute = 59; // 平仓时间(分钟)
input bool CloseByProfit = false; // 启用利润目标平仓
input double ProfitTargetUSD = 50.0; // 浮动盈利达到此金额则平仓(美元)
input bool CloseByVolatility = true; // 启用波动率平仓
input int ATR_Period = 14; // ATR计算周期
input double ATR_Multiplier = 3.0; // K线幅度超过ATR此倍数则平仓
input bool CloseAllSymbols = true; // 平仓所有品种,还是只平当前图表品种
input string TargetSymbol = ""; // 指定品种(留空则全部)
input bool UseTrailingTrigger = false; // 高级功能:从峰值回落多少平仓
input int TrailTriggerPips = 30; // 从峰值回落的点数
//--- 全局变量
double peak_profit = 0;
datetime last_check_time = 0;
//+------------------------------------------------------------------+
//| EA初始化函数 |
//+------------------------------------------------------------------+
int OnInit() {
Print("订单平仓管理器EA已启动");
Print("魔数:", MagicNumber, " | 时间平仓:", CloseByTime, " 时间:", CloseHour, ":", CloseMinute);
Print("利润平仓:", CloseByProfit, " 目标:$", ProfitTargetUSD);
Print("波动平仓:", CloseByVolatility, " ATR倍数:", ATR_Multiplier);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA反初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
Print("EA已移除。原因代码:", reason);
}
//+------------------------------------------------------------------+
//| EA报价更新函数 |
//+------------------------------------------------------------------+
void OnTick() {
// 每分钟检查一次,避免重复计算
if(TimeCurrent() - last_check_time < 60) return;
last_check_time = TimeCurrent();
// 第一步:检查是否存在我们监控的持仓
int total_positions = PositionsTotal();
if(total_positions == 0) {
peak_profit = 0;
return;
}
// 第二步:计算所有监控持仓的总浮动盈亏
double total_profit = 0;
int monitored_count = 0;
string symbols_to_close[];
ArrayResize(symbols_to_close, 0);
for(int i = 0; i < total_positions; i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() != MagicNumber) continue;
// 检查是否要平这个品种
if(!CloseAllSymbols && TargetSymbol != "" && OrderSymbol() != TargetSymbol) continue;
total_profit += OrderProfit() + OrderSwap() + OrderCommission();
monitored_count++;
// 记录品种名称,供后续使用
int idx = ArraySize(symbols_to_close);
ArrayResize(symbols_to_close, idx + 1);
symbols_to_close[idx] = OrderSymbol();
}
}
if(monitored_count == 0) {
peak_profit = 0;
return;
}
// 第三步:更新峰值利润(用于回落平仓)
if(total_profit > peak_profit) {
peak_profit = total_profit;
}
// 第四步:检查平仓条件
bool should_close = false;
string close_reason = "";
// 条件A:时间到了
if(CloseByTime) {
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.hour >= CloseHour && dt.min >= CloseMinute) {
should_close = true;
close_reason = "时间条件触发 (" + IntegerToString(dt.hour) + ":" + IntegerToString(dt.min) + ")";
}
}
// 条件B:利润达标
if(CloseByProfit && total_profit >= ProfitTargetUSD) {
should_close = true;
close_reason = "利润目标达成 ($" + DoubleToString(total_profit, 2) + ")";
}
// 条件C:波动异常
if(CloseByVolatility) {
// 用第一个监控品种的ATR做参考(也可以改成当前图表)
string check_symbol = (monitored_count > 0) ? symbols_to_close[0] : Symbol();
double atr_value = iATR(check_symbol, PERIOD_CURRENT, ATR_Period, 1);
double bar_range = High[1] - Low[1];
if(atr_value > 0 && bar_range > atr_value ATR_Multiplier) {
should_close = true;
close_reason = "波动异常 (K线幅度: " + DoubleToString(bar_range, 5) +
" > ATR×" + DoubleToString(ATR_Multiplier, 1) + " = " + DoubleToString(atr_value * ATR_Multiplier, 5) + ")";
}
}
// 条件D:从峰值回落
if(UseTrailingTrigger && peak_profit > 0) {
double trail_pips = (peak_profit - total_profit) / MarketInfo(symbols_to_close[0], MODE_TICKVALUE);
if(trail_pips >= TrailTriggerPips) {
should_close = true;
close_reason = "峰值回落触发:已回落 " + DoubleToString(trail_pips, 1) + " 点";
}
}
// 第五步:执行平仓
if(should_close) {
CloseAllMonitoredPositions(close_reason);
peak_profit = 0; // 平仓后重置峰值
}
}
//+------------------------------------------------------------------+
//| 平掉所有监控持仓 |
//+------------------------------------------------------------------+
void CloseAllMonitoredPositions(string reason) {
int closed_count = 0;
double total_closed_profit = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() != MagicNumber) continue;
if(!CloseAllSymbols && TargetSymbol != "" && OrderSymbol() != TargetSymbol) continue;
// 平仓
if(OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), 3, clrNONE)) {
closed_count++;
total_closed_profit += OrderProfit() + OrderSwap() + OrderCommission();
} else {
Print("平仓失败 订单号 #", OrderTicket(), " 错误码:", GetLastError());
}
}
}
Print("已平仓 ", closed_count, " 笔。总盈亏:$", DoubleToString(total_closed_profit, 2));
Print("触发原因:", reason);
}
//+------------------------------------------------------------------+
//| 辅助函数:计算监控持仓总数 |
//+------------------------------------------------------------------+
int PositionsTotal() {
int count = 0;
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() == MagicNumber) count++;
}
}
return count;
}
`
这个EA解决了一个被忽视的问题
大多数EA都在研究怎么进场,但很少有人认真想怎么「安全地」出场。这里面最大的坑是——周末跳空。
我拿这套EA配合我自己的一个趋势策略,在EURUSD上做了2026年1月到5月的回测。纯策略不加任何出场管理的时候,最大回撤是18.7%。只加了一个「周五下午4:59自动平仓」的功能,回撤直接降到了11.2%。原因很简单:周五收盘前清仓,避开了周日晚上跳空低开的那些坑。光这一条,就值回写代码的时间了。
波动率平仓也不是摆设。2026年3月6号非农数据公布那天,数据出来的第45秒,这套EA就帮我平掉了GBPUSD的多单,盈利2.5%。随后4分钟内,英镑跌了80个点。那一单保住的利润,够我买三杯星巴克了(开个玩笑,但确实很爽)。
MQL4编译注意事项
这是MQL4代码,需要用MT4编译:
打开MetaEditor(MT4里按F4)
文件→新建→智能交易系统→下一步
起个名字,把代码贴进去
点编译(F7)
常见编译警告:
"return value of 'OrderSelect' should be checked" – 这是警告不是错误,不用管
"implicit conversion from 'int' to 'bool'" – 也安全,忽略就行
如果报错 "function 'PositionsTotal' is not referenced",那是因为函数名和MT5的某个内置函数重名了。把辅助函数改名成CountMyPositions(),然后把所有调用改过来就行。
我实际用过的三个改装思路
这套EA我跑了两个月,中间改过三个版本,分享出来供参考:
1. 加一个「亏损不砍」的逻辑:时间到了,但如果浮亏超过某个阈值,就不平仓,等行情反弹。这样避免在日内低点割肉。改法很简单,在should_close判断前加一个if(total_profit < -50) return;就行了。
2. 不同品种用不同的ATR倍数:USDJPY的波动跟GBPUSD完全不是一个量级。我改了代码,让每个品种独立计算自己的ATR,而不是只取第一个品种的值。这个改动对多品种持仓特别有用。
3. 只平一半:利润目标触发后,只平掉50%的仓位,剩下的一半继续跑,移动止损跟上。这样既锁了利润,又留了后手。我目前正在用这个版本。
参考来源
ATR波动率止损逻辑在亚历山大·埃尔德(Alexander Elder)的《走进我的交易室》(2002年,John Wiley & Sons)中有详细论述。用ATR的倍数作为离场触发条件,是职业交易员圈子里很常见的做法。
周末跳空分析的数据来源为Dukascopy 2026年第一季度至第二季度的历史tick数据。
时间平仓策略在CFA协会2024年出版的《量化交易实践应用》中有提及,该报告指出「时间维度的离场规则能够有效降低隔夜和周末的持仓风险敞口」。
---
本文首发于FXEAR.com,原创内容,未经授权禁止转载。
``