从入门到精通,系统化的 MT4/MT5 EA 开发学习路径
MQL (MetaQuotes Language) 是 MetaQuotes 公司为 MT4/MT5 平台开发的专用编程语言,用于编写 EA(交易机器人)、自定义指标和脚本。
自动交易机器人,可执行开仓、平仓、止损止盈等操作
自定义技术指标,在图表上显示独特的分析线和信号
一次性执行的脚本,用于批量操作或特定任务
通过DLL调用外部C/C++函数,扩展MQL能力边界
| 特性 | MQL4 (MT4) | MQL5 (MT5) |
|---|---|---|
| 订单模型 | 单订单系统 | 持仓+订单双系统 |
| 执行模式 | OnTick 事件驱动 | OnTick + 多事件模型 |
| 面向对象 | 部分支持 | 完整OOP支持 |
| 回测引擎 | 单线程 | 多线程遗传算法 |
| 市场深度 | 有限 | 完整DOM支持 |
MetaEditor 是 MT4/MT5 内置的集成开发环境(IDE),无需额外安装,随客户端一同发布。
从交易商官网下载 MetaTrader 客户端并安装。建议同时安装 MT4 和 MT5,方便对照学习。
方式一:在 MT 菜单点击「工具 → MetaQuotes 语言编辑器」;方式二:直接按 F4 快捷键;方式三:点击工具栏上的 MetaEditor 图标。
左侧 Navigator 面板(文件导航)、中间代码编辑区、下方 Toolbox 面板(编译输出/调试/搜索结果)。
在 Navigator 面板的 Experts 文件夹上右键 → 新建 → 选择「Expert Advisor (template)」→ 输入名称 → 一路下一步完成。
按分类显示所有 EA、指标、脚本、库文件,支持新建、删除、编译操作。
支持语法高亮、行号显示、括号匹配、代码折叠、自动补全等功能。
显示编译错误、搜索结果、断点列表、调用栈等调试信息。
按 F1 打开 MQL 官方文档,所有内置函数都有详细说明和示例。
| 快捷键 | 功能 | 说明 |
|---|---|---|
F4 | 打开 MetaEditor | 从 MT 图表快速跳转到编辑器 |
F5 | 编译并运行 | 编译当前文件并附加到图表 |
F7 | 编译 | 编译当前文件,错误显示在 Toolbox |
F9 | 设置/取消断点 | 在当前行设置调试断点 |
Ctrl+F | 查找 | 在当前文件中搜索文本 |
Ctrl+H | 替换 | 查找并替换文本 |
Ctrl+Shift+F | 全局搜索 | 在所有文件中搜索 |
Ctrl+Space | 自动补全 | 显示函数/变量名提示 |
Ctrl+D | 删除整行 | 删除当前光标所在行 |
Ctrl+Z / Ctrl+Y | 撤销/重做 | 撤销或恢复上一步操作 |
推荐在「工具 → 选项」中进行以下配置,提升开发效率:
按照以下步骤创建你的第一个 EA 项目:
在 Navigator 面板的 Experts 文件夹上右键 → 点击「新建」,或在菜单栏选择「文件 → 新建」。
在弹出的向导中选择「Expert Advisor (template)」,点击下一步。
输入 EA 名称(如 MyFirstEA)、作者名称、链接地址,这些会自动生成文件头注释。
勾选 OnInit、OnDeinit、OnTick 三个核心函数,新手建议全部勾选。
点击「完成」,系统自动生成包含基本框架的 .mq4 文件,可以直接开始编写代码。
让我们编写一个完整的 HelloWorld EA,理解 OnInit / OnDeinit / OnTick 三大核心函数的作用。
//+------------------------------------------------------------------+
//| HelloEA.mq4 |
//| Copyright 2024, 晓辉编程 eafxtech.com |
//| https://www.eafxtech.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, 晓辉编程"
#property link "https://www.eafxtech.com"
#property version "1.00"
#property strict
//+------------------------------------------------------------------+
//| Expert initialization function |
//| EA加载到图表时执行一次,用于初始化变量、检查环境 |
//| 返回 INIT_SUCCEEDED 表示成功,返回 INIT_FAILED 表示失败 |
//+------------------------------------------------------------------+
int OnInit()
{
Print("======================================");
Print("HelloEA 已加载");
Print("品种: ", Symbol());
Print("周期: ", Period());
Print("点值: ", Point);
Print("小数位: ", Digits);
Print("======================================");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//| EA从图表移除时执行一次,用于清理资源、保存数据 |
//| reason 参数表示卸载原因:REASON_REMOVE/REASON_RECOMPILE等 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("HelloEA 已卸载, 原因代码: ", reason);
Comment(""); // 清除图表上的显示
}
//+------------------------------------------------------------------+
//| Expert tick function |
//| 每次价格变动(tick)都会触发,是EA的主循环 |
//| 所有交易逻辑、信号判断都在这里执行 |
//+------------------------------------------------------------------+
void OnTick()
{
// 获取当前价格
double bid = Bid; // 买价(你卖出时的价格)
double ask = Ask; // 卖价(你买入时的价格)
// 计算点差(以点为单位)
int spread = (int)((ask - bid) / Point);
// 获取当前K线信息
double curClose = Close[0]; // 当前未完成K线的收盘价
double prevClose = Close[1]; // 上一根已完成K线的收盘价
// 在图表左上角显示实时信息
Comment(
"┌── Hello EA ──┐\n",
"│ Bid: ", DoubleToStr(bid, Digits), "\n",
"│ Ask: ", DoubleToStr(ask, Digits), "\n",
"│ 点差: ", spread, " 点\n",
"│ 收盘: ", DoubleToStr(prevClose, Digits), "\n",
"└──────────────┘"
);
// 价格变动时打印日志(避免刷屏,只在整点打印)
static int lastHour = -1;
if (Hour() != lastHour)
{
Print("整点播报: 当前价格 Bid=", bid, " Ask=", ask, " 点差=", spread);
lastHour = Hour();
}
}
//+------------------------------------------------------------------+
初始化函数,EA加载时只执行一次。用来初始化变量、读取配置、检查参数合法性。
主函数,每次价格变动都会触发。所有交易逻辑、信号判断、订单管理都写在这里。
清理函数,EA卸载时执行一次。用来保存数据、关闭文件、清除图表对象。
在 MetaEditor 中新建一个 Expert Advisor,命名为 HelloEA,把上面的代码复制进去替换全部内容。
按 F7 编译,如果下方 Toolbox 显示 0 errors 0 warnings 就说明编译成功了。
回到 MT4,在左侧 Navigator 面板找到 HelloEA,拖到任意图表上。
点击工具栏上的「自动交易」按钮,确保图表右上角显示😊笑脸。
图表左上角会出现实时价格信息,按 Ctrl+J 打开工具箱的 Experts 标签可以看到日志输出。
MQL 是强类型语言,变量使用前必须声明类型。正确的数据类型选择直接影响代码效率和准确性。
| 类型 | 说明 | 占用字节 | 示例 |
|---|---|---|---|
int | 32位有符号整数 | 4 | int count = 10; |
double | 双精度浮点数 | 8 | double price = 1.23456; |
bool | 布尔值 true/false | 1 | bool isLong = true; |
string | 字符串 | 8(指针) | string name = "EURUSD"; |
datetime | 日期时间(秒数) | 4 | datetime now = TimeCurrent(); |
color | RGB颜色值 | 4 | color c = clrRed; |
char | 单字符 | 1 | char ch = 'A'; |
long | 64位整数 | 8 | long bigNum = 1234567890L; |
整数常用于计数、索引、订单号、魔术号等场景。MQL4 中 int 是最常用的整数类型。
// 整数变量声明与初始化
int i_count = 0; // 计数器,初始化为0
int i_magicNum = 12345; // EA魔术号,用于识别自己的订单
int i_bars = Bars; // 当前图表K线总数
int i_hour = Hour(); // 当前小时数
// 整数运算
int a = 10, b = 3;
Print("加法: ", a + b); // 13
Print("减法: ", a - b); // 7
Print("乘法: ", a * b); // 30
Print("除法: ", a / b); // 3 (整数除法自动取整)
Print("取模: ", a % b); // 1 (取余数)
// 自增自减
int n = 0;
n++; // 等价于 n = n + 1
n--; // 等价于 n = n - 1
浮点数用于价格、指标值、盈亏金额等。交易中绝大多数数值计算都用 double 类型。
// 浮点数变量
double d_price = Ask; // 当前卖出价
double d_pipValue = 10.0; // 每点价值
double d_lot = 0.1; // 交易手数
double d_profit = 0.0; // 浮动盈亏
// 浮点数比较注意事项(重要!)
// 不要直接用 == 比较浮点数,应该比较差值是否小于一个极小值
bool IsEqual(double a, double b)
{
return MathAbs(a - b) < Point;
}
// 价格精度处理(非常重要)
double d_sl = Bid - 50 * Point; // 止损价
d_sl = NormalizeDouble(d_sl, Digits); // 修正到品种的小数位数
字符串用于品种名称、订单注释、日志输出等。
// 字符串声明
string s_symbol = Symbol(); // 当前品种名
string s_comment = "EA订单"; // 订单注释
string s_empty = ""; // 空字符串
// 字符串拼接(MQL4 中用 + 号)
string s_msg = "品种: " + s_symbol + ", 价格: " + DoubleToStr(Ask, Digits);
Print(s_msg);
// 字符串长度
int len = StringLen(s_symbol);
Print("字符串长度: ", len);
// 字符串比较
if (s_symbol == "EURUSD")
{
Print("当前是欧元美元");
}
// 常用字符串函数
string s_upper = StringUpper("hello"); // 转大写: "HELLO"
string s_lower = StringLower("HELLO"); // 转小写: "hello"
int pos = StringFind(s_symbol, "USD"); // 查找子串位置
string s_sub = StringSubstr(s_symbol, 0, 3); // 截取子串
布尔类型只有 true 和 false 两个值;枚举用于定义一组有限的可选值。
// 布尔变量
bool b_isLong = true; // 是否做多
bool b_useStop = false; // 是否使用止损
// 布尔表达式
bool b_overbought = (iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 1) > 70);
// 逻辑运算
bool b_a = true, b_b = false;
Print("与: ", b_a && b_b); // 两个都为true才为true
Print("或: ", b_a || b_b); // 一个为true就为true
Print("非: ", !b_a); // 取反
// 枚举类型
enum ENUM_DIRECTION
{
DIR_LONG = 0, // 做多
DIR_SHORT = 1, // 做空
DIR_BOTH = 2 // 双向
};
input ENUM_DIRECTION InpDirection = DIR_BOTH;
结构体可以把多个相关数据打包在一起,方便传参和管理。
// 交易信号结构体
struct SSignal
{
string symbol; // 品种
int type; // 订单类型 OP_BUY/OP_SELL
double entry; // 入场价
double sl; // 止损价
double tp; // 止盈价
double lot; // 手数
};
// 使用结构体
void OnTick()
{
SSignal sig;
sig.symbol = _Symbol;
sig.type = OP_BUY;
sig.entry = Ask;
sig.sl = Bid - 50 * Point;
sig.tp = Ask + 100 * Point;
sig.lot = 0.1;
if (sig.type == OP_BUY)
{
Print("收到做多信号, 入场=", sig.entry);
}
}
MQL 支持标准的条件判断和循环结构,掌握这些是编写交易逻辑的基础。
// 基础 if 判断
double rsi = iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 1);
if (rsi > 70)
{
Print("超买区域, RSI=", rsi);
}
// if-else 二选一
double ma20 = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
if (Close[1] > ma20)
{
Print("价格在20日均线上方,偏多");
}
else
{
Print("价格在20日均线下方,偏空");
}
// if-else if-else 多分支
if (rsi > 80)
{
Print("严重超买");
}
else if (rsi > 70)
{
Print("超买");
}
else if (rsi < 20)
{
Print("严重超卖");
}
else if (rsi < 30)
{
Print("超卖");
}
else
{
Print("中性区域");
}
// 多条件组合
double fastMA = iMA(_Symbol, _Period, 5, 0, MODE_SMA, PRICE_CLOSE, 1);
double slowMA = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
double fastPrev = iMA(_Symbol, _Period, 5, 0, MODE_SMA, PRICE_CLOSE, 2);
double slowPrev = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 2);
// 金叉条件:快线从下往上穿慢线
bool goldenCross = (fastMA > slowMA) && (fastPrev <= slowPrev);
if (goldenCross && OrdersTotal() == 0)
{
Print("金叉信号,且当前无持仓");
}
// 示例1:计算最近N根K线的收盘价均值
double CalcSMA(int period, int shift)
{
if (Bars < period + shift) return 0;
double sum = 0;
for (int i = shift; i < shift + period; i++)
{
sum += Close[i];
}
return sum / period;
}
// 示例2:遍历订单(倒序遍历,防止删除后索引错位)
void CloseAllBuyOrders()
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == _Symbol && OrderType() == OP_BUY)
{
OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrRed);
}
}
}
}
// 示例3:带 continue 的循环
for (int i = 0; i < OrdersTotal(); i++)
{
if (!OrderSelect(i, SELECT_BY_POS)) continue;
if (OrderMagicNumber() != InpMagic) continue;
if (OrderSymbol() != _Symbol) continue;
Print("订单号: ", OrderTicket());
}
while 循环在条件为真时反复执行。注意必须确保循环能终止,防止死循环!
// 查找最近一个满足条件的K线
int FindLastHighAbove(double level)
{
int i = 1;
while (i < Bars)
{
if (High[i] > level) return i;
i++;
}
return -1;
}
// 根据订单类型输出描述
void PrintOrderType(int type)
{
switch (type)
{
case OP_BUY: Print("市价买单"); break;
case OP_SELL: Print("市价卖单"); break;
case OP_BUYLIMIT: Print("限价买单"); break;
case OP_SELLLIMIT: Print("限价卖单"); break;
case OP_BUYSTOP: Print("止损买单"); break;
case OP_SELLSTOP: Print("止损卖单"); break;
default: Print("未知类型: ", type); break;
}
}
// 根据周期返回描述
string PeriodName()
{
switch (Period())
{
case PERIOD_M1: return "1分钟";
case PERIOD_M5: return "5分钟";
case PERIOD_M15: return "15分钟";
case PERIOD_M30: return "30分钟";
case PERIOD_H1: return "1小时";
case PERIOD_H4: return "4小时";
case PERIOD_D1: return "日线";
default: return "其他周期";
}
}
// 格式: 条件 ? 值1 : 值2
double direction = 1;
double slPrice = (direction == 1) ? (Bid - 50*Point) : (Ask + 50*Point);
// 获取较大值
double maxPrice = (High[0] > High[1]) ? High[0] : High[1];
// 判断持仓方向
string posType = (OrderType() == OP_BUY) ? "多单" : "空单";
函数是 MQL 程序的基本构建块,将代码模块化封装,提高复用性和可维护性。
// 语法:返回值类型 函数名(参数列表) { 函数体 return 返回值; }
// 有返回值的函数
double CalcSMA(int period)
{
if (period <= 0 || Bars < period) return 0;
double sum = 0.0;
for (int i = 0; i < period; i++) sum += Close[i];
return sum / period;
}
// 无返回值的函数(void)
void PrintSymbolInfo()
{
Print("品种: ", Symbol(), " 现价: ", Bid);
Print("点差: ", (int)((Ask - Bid) / Point), " 点");
}
// 带提前返回的函数
bool IsValidPrice(double price)
{
if (price <= 0) return false;
if (price < MarketInfo(_Symbol, MODE_LOW)) return false;
return true;
}
// ===== 传值方式(默认)=====
// 函数内部修改参数不会影响外部变量
void AddOne(int n) { n = n + 1; }
// ===== 传引用方式(& 符号)=====
// 函数内部修改参数会改变外部变量
void AddOneRef(int &n) { n = n + 1; }
// ===== 传引用的典型用途:返回多个值 =====
void GetAccountStats(double &balance, double &equity, int &orders)
{
balance = AccountBalance();
equity = AccountEquity();
orders = OrdersTotal();
}
// 调用
void OnTick()
{
double bal, eq;
int cnt;
GetAccountStats(bal, eq, cnt);
Print("余额=", bal, " 净值=", eq, " 订单数=", cnt);
}
// 函数重载:同名不同参
double CalcMA(int period)
{ return iMA(_Symbol, _Period, period, 0, MODE_SMA, PRICE_CLOSE, 0); }
double CalcMA(int period, int shift)
{ return iMA(_Symbol, _Period, period, 0, MODE_SMA, PRICE_CLOSE, shift); }
// 默认参数:从右往左连续设置
double CalcLotSize(double riskPercent = 2.0, int stopLossPoints = 50)
{
double riskAmount = AccountBalance() * riskPercent / 100.0;
double lot = riskAmount / (stopLossPoints * 10.0);
lot = NormalizeDouble(lot, 2);
return MathMax(MarketInfo(_Symbol, MODE_MINLOT), lot);
}
// 多种调用方式
double lot1 = CalcLotSize(); // risk=2%, sl=50点
double lot2 = CalcLotSize(1.5); // risk=1.5%, sl=50点
double lot3 = CalcLotSize(2.0, 80); // risk=2%, sl=80点
MQL 支持一维和多维数组,是处理 K 线数据、指标序列、批量数据的核心工具。
// 静态数组声明
double prices[100]; // 100个double元素
int indices[50]; // 50个int元素
// 初始化
double arr1[] = {1.5, 2.5, 3.5, 4.5};
// 赋值与访问
prices[0] = Bid;
prices[1] = Close[1];
double v = prices[0];
// 遍历数组
double sum = 0;
for (int i = 0; i < 100; i++) sum += prices[i];
double avg = sum / 100;
// 动态数组
double dynamicArr[];
ArrayResize(dynamicArr, 50); // 调整大小
int size = ArraySize(dynamicArr); // 获取大小
// 常用数组函数
double data[] = {5, 2, 8, 1, 9};
ArraySort(data); // 升序排序
double minVal = ArrayMinimum(data); // 最小值
double maxVal = ArrayMaximum(data); // 最大值
// 二维数组声明
double matrix[5][5];
string multiData[10][3];
// 赋值
matrix[0][0] = 1.0; // 第1行第1列
matrix[0][1] = 2.0; // 第1行第2列
// 遍历二维数组
for (int row = 0; row < 5; row++)
for (int col = 0; col < 5; col++)
matrix[row][col] = row * 10 + col;
// 实用场景:多品种数据存储
double symbolData[5][4]; // [品种索引][0=开 1=高 2=低 3=收]
设为系列数组后,索引 0 表示最新数据,和内置 Close[]、High[] 行为一致。
double myMA[];
ArraySetAsSeries(myMA, true); // 设为系列数组
ArrayResize(myMA, 100);
// 填充后 myMA[0] 是最新值,和 Close[0] 索引方式一致
for (int i = 0; i < 100; i++)
{
myMA[i] = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, i);
}
// 现在 myMA[0] = 当前K线MA值
// myMA[1] = 前一根K线MA值
// 这和 Close[0] High[0] 的索引方式完全一致
// MQL4 手动复制(兼容性最好)
void CopyRatesManual(string symbol, int tf,
double &open[], double &high[],
double &low[], double &close[],
datetime &timeArr[], int count)
{
ArraySetAsSeries(open, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
ArraySetAsSeries(timeArr, true);
ArrayResize(open, count);
ArrayResize(high, count);
ArrayResize(low, count);
ArrayResize(close, count);
ArrayResize(timeArr, count);
for (int i = 0; i < count; i++)
{
open[i] = iOpen(symbol, tf, i);
high[i] = iHigh(symbol, tf, i);
low[i] = iLow(symbol, tf, i);
close[i] = iClose(symbol, tf, i);
timeArr[i] = iTime(symbol, tf, i);
}
}
通过内置函数获取账户余额、净值、保证金等信息,是风控和资金管理的基础。
| 函数 | 返回类型 | 说明 |
|---|---|---|
AccountBalance() | double | 账户余额(已平仓后的资金) |
AccountEquity() | double | 账户净值(余额 + 浮动盈亏) |
AccountProfit() | double | 当前浮动盈亏总额 |
AccountFreeMargin() | double | 可用保证金 |
AccountMargin() | double | 已用保证金 |
AccountLeverage() | int | 杠杆倍数 |
AccountCurrency() | string | 账户币种 |
AccountFreeMarginCheck() | double | 检查开仓后剩余可用保证金 |
AccountCompany() | string | 交易商公司名称 |
AccountNumber() | int | 账户号码 |
// 1. 显示账户基本信息
void PrintAccountInfo()
{
Print("账户号: ", AccountNumber());
Print("余额: ", AccountBalance(), " ", AccountCurrency());
Print("净值: ", AccountEquity(), " ", AccountCurrency());
Print("浮动盈亏: ", AccountProfit());
Print("可用保证金: ", AccountFreeMargin());
Print("杠杆: 1:", AccountLeverage());
}
// 2. 检查最大回撤限制
input double InpMaxDrawdown = 20.0;
double g_initialBalance = 0;
int OnInit()
{
g_initialBalance = AccountBalance();
return INIT_SUCCEEDED;
}
bool IsDrawdownExceeded()
{
double equity = AccountEquity();
double drawdown = ((g_initialBalance - equity) / g_initialBalance) * 100;
if (drawdown >= InpMaxDrawdown)
{
Print("⚠️ 回撤已达 ", drawdown, "%");
return true;
}
return false;
}
// 3. 检查保证金是否足够
bool CheckFreeMarginForLot(double lot)
{
double freeAfter = AccountFreeMarginCheck(_Symbol, OP_BUY, lot);
if (freeAfter <= 0)
{
Print("❌ 保证金不足");
return false;
}
return true;
}
MQL4 使用订单编号系统,每张订单有唯一的 ticket 号。
OrderSend / OrderClose / OrderModify 是 MQL4 交易的三大核心函数,掌握它们是编写 EA 的关键。
// 市价买入示例
int OpenBuyOrder(double lot, double slPoints, double tpPoints)
{
double sl = (slPoints > 0) ? NormalizeDouble(Bid - slPoints * Point, Digits) : 0;
double tp = (tpPoints > 0) ? NormalizeDouble(Ask + tpPoints * Point, Digits) : 0;
int ticket = OrderSend(
_Symbol, // 品种
OP_BUY, // 类型:市价买单
lot, // 手数
Ask, // 价格:用卖价开多
30, // 滑点:30点(实盘建议设大一点)
sl, // 止损价
tp, // 止盈价
"eafxtech EA", // 注释
12345, // 魔术号
0, // 有效期(市价单为0)
clrGreen // 箭头颜色
);
if (ticket > 0)
Print("✅ 做多成功,订单号:", ticket);
else
Print("❌ 做多失败,错误码:", GetLastError());
return ticket;
}
// 市价卖出示例
int OpenSellOrder(double lot, double slPoints, double tpPoints)
{
double sl = (slPoints > 0) ? NormalizeDouble(Ask + slPoints * Point, Digits) : 0;
double tp = (tpPoints > 0) ? NormalizeDouble(Bid - tpPoints * Point, Digits) : 0;
int ticket = OrderSend(_Symbol, OP_SELL, lot, Bid, 30, sl, tp,
"eafxtech EA", 12345, 0, clrRed);
return ticket;
}
// 按订单号平仓
bool CloseOrderByTicket(int ticket)
{
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return false;
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
bool result = OrderClose(ticket, OrderLots(), closePrice, 30, clrRed);
if (!result) Print("❌ 平仓失败,错误码:", GetLastError());
return result;
}
// 平掉当前品种所有订单
void CloseAllOrders()
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != _Symbol || OrderMagicNumber() != 12345) continue;
int type = OrderType();
if (type == OP_BUY || type == OP_SELL)
{
double price = (type == OP_BUY) ? Bid : Ask;
OrderClose(OrderTicket(), OrderLots(), price, 30, clrRed);
}
else
{
OrderDelete(OrderTicket()); // 挂单删除
}
}
}
// 修改止损止盈
bool ModifySLTP(int ticket, double newSL, double newTP)
{
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return false;
return OrderModify(ticket, OrderOpenPrice(),
NormalizeDouble(newSL, Digits),
NormalizeDouble(newTP, Digits), 0, clrYellow);
}
// 移动止损(Trailing Stop)
void TrailStop(int magicNumber, double trailPoints)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS)) continue;
if (OrderMagicNumber() != magicNumber) continue;
if (OrderSymbol() != _Symbol) continue;
if (OrderType() == OP_BUY)
{
double newSL = Bid - trailPoints * Point;
if (newSL > OrderStopLoss() + Point)
OrderModify(OrderTicket(), OrderOpenPrice(),
NormalizeDouble(newSL, Digits),
OrderTakeProfit(), 0, clrYellow);
}
else if (OrderType() == OP_SELL)
{
double newSL = Ask + trailPoints * Point;
if (OrderStopLoss() == 0 || newSL < OrderStopLoss() - Point)
OrderModify(OrderTicket(), OrderOpenPrice(),
NormalizeDouble(newSL, Digits),
OrderTakeProfit(), 0, clrYellow);
}
}
}
// 按订单号选择
bool ok = OrderSelect(1234567, SELECT_BY_TICKET);
// 按索引位置选择(遍历用)
bool ok2 = OrderSelect(0, SELECT_BY_POS, MODE_TRADES);
// 订单信息获取(必须先 OrderSelect)
int ticket = OrderTicket(); // 订单号
int type = OrderType(); // 订单类型
double lots = OrderLots(); // 手数
double open = OrderOpenPrice(); // 开仓价
double sl = OrderStopLoss(); // 止损价
double tp = OrderTakeProfit(); // 止盈价
double profit = OrderProfit(); // 盈亏金额
string sym = OrderSymbol(); // 品种
int magic = OrderMagicNumber(); // 魔术号
// 标准遍历写法(从后往前,防止删除后索引错乱)
void ListAllOrders()
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
Print("订单:", OrderTicket(), " 类型:", OrderType(),
" 手数:", OrderLots(), " 盈亏:", OrderProfit());
}
}
}
风控是 EA 的生命线。再好的策略,没有风控也迟早爆仓。开仓前必须先算好手数和止损。
// 按风险百分比计算手数
double CalcLotByRisk(double riskPercent, double stopLossPoints)
{
// 1. 计算愿意亏损的金额
double riskAmount = AccountBalance() * riskPercent / 100.0;
// 2. 计算每点价值
double tickValue = MarketInfo(_Symbol, MODE_TICKVALUE);
double tickSize = MarketInfo(_Symbol, MODE_TICKSIZE);
double valuePerPoint = tickValue / tickSize * Point;
// 3. 计算手数
double lot = riskAmount / (stopLossPoints * valuePerPoint);
// 4. 修正精度和范围
double lotStep = MarketInfo(_Symbol, MODE_LOTSTEP);
lot = MathFloor(lot / lotStep) * lotStep;
lot = NormalizeDouble(lot, 2);
double minLot = MarketInfo(_Symbol, MODE_MINLOT);
double maxLot = MarketInfo(_Symbol, MODE_MAXLOT);
lot = MathMax(minLot, MathMin(maxLot, lot));
// 5. 二次确认保证金足够
if (AccountFreeMarginCheck(_Symbol, OP_BUY, lot) <= 0)
lot = minLot;
return lot;
}
// 多单止损止盈计算
void CalcBuySLTP(double slPoints, double tpPoints, double &sl, double &tp)
{
sl = (slPoints > 0) ? NormalizeDouble(Bid - slPoints * Point, Digits) : 0;
tp = (tpPoints > 0) ? NormalizeDouble(Ask + tpPoints * Point, Digits) : 0;
}
// 空单止损止盈计算
void CalcSellSLTP(double slPoints, double tpPoints, double &sl, double &tp)
{
sl = (slPoints > 0) ? NormalizeDouble(Ask + slPoints * Point, Digits) : 0;
tp = (tpPoints > 0) ? NormalizeDouble(Bid - tpPoints * Point, Digits) : 0;
}
// 基于 ATR 的动态止损(进阶)
double CalcATRSl(int atrPeriod, double atrMultiplier)
{
double atr = iATR(_Symbol, _Period, atrPeriod, 0);
return (atr * atrMultiplier) / Point;
}
input double InpMaxDrawdown = 20.0; // 最大回撤百分比
input double InpDailyLossLimit = 5.0; // 单日最大亏损百分比
double g_peakEquity = 0;
double g_dayStartEquity = 0;
int g_lastDay = 0;
int OnInit()
{
g_peakEquity = AccountEquity();
g_dayStartEquity = AccountEquity();
g_lastDay = Day();
return INIT_SUCCEEDED;
}
bool CanOpenNewPosition()
{
double equity = AccountEquity();
// 更新峰值
if (equity > g_peakEquity) g_peakEquity = equity;
// 检查从峰值的回撤
double drawdown = ((g_peakEquity - equity) / g_peakEquity) * 100;
if (drawdown >= InpMaxDrawdown) return false;
// 检查单日亏损
if (Day() != g_lastDay)
{
g_dayStartEquity = equity;
g_lastDay = Day();
}
double dailyLoss = ((g_dayStartEquity - equity) / g_dayStartEquity) * 100;
if (dailyLoss >= InpDailyLossLimit) return false;
return true;
}
高级 EA 通常需要监控多个品种,捕捉跨品种交易机会,分散单一品种风险。
// 获取所有可交易品种列表
void ListAllSymbols()
{
int total = SymbolsTotal(true);
Print("可交易品种总数: ", total);
for (int i = 0; i < total; i++)
{
Print(i, ": ", SymbolName(i, true));
}
}
// 筛选符合条件的品种
void FilterSymbols()
{
int total = SymbolsTotal(true);
for (int i = 0; i < total; i++)
{
string symbol = SymbolName(i, true);
double spread = MarketInfo(symbol, MODE_SPREAD);
double minLot = MarketInfo(symbol, MODE_MINLOT);
double ask = MarketInfo(symbol, MODE_ASK);
if (ask <= 0 || spread > 30 || minLot > 0.1) continue;
double atr = iATR(symbol, PERIOD_H1, 14, 0);
double atrPercent = (atr / ask) * 100;
if (atrPercent > 0.1 && atrPercent < 2.0)
Print("✅ ", symbol, " 点差:", spread, " ATR%:", DoubleToStr(atrPercent, 2));
}
}
string g_watchList[] = {"EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCAD"};
int g_magic = 12345;
// 判断某品种是否有做多信号
bool HasBuySignal(string symbol)
{
double close = iClose(symbol, PERIOD_M15, 1);
double ma20 = iMA(symbol, PERIOD_M15, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
double ma60 = iMA(symbol, PERIOD_M15, 60, 0, MODE_SMA, PRICE_CLOSE, 1);
return (close > ma20 && ma20 > ma60);
}
// 检查某品种是否已有持仓
bool HasPosition(string symbol, int type)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS) &&
OrderSymbol() == symbol &&
OrderMagicNumber() == g_magic &&
OrderType() == type)
return true;
}
return false;
}
// 主循环:扫描所有品种
void ScanAndTrade()
{
for (int i = 0; i < ArraySize(g_watchList); i++)
{
string sym = g_watchList[i];
if (HasBuySignal(sym) && !HasPosition(sym, OP_BUY))
{
Print("🚀 ", sym, " 做多信号");
// 开仓逻辑...
}
}
}
// 记录每个品种上次处理的K线时间
datetime g_lastBarTime[];
int OnInit()
{
ArrayResize(g_lastBarTime, ArraySize(g_watchList));
ArrayInitialize(g_lastBarTime, 0);
return INIT_SUCCEEDED;
}
bool IsNewBar(int index)
{
string sym = g_watchList[index];
datetime curBarTime = iTime(sym, PERIOD_M15, 0);
if (curBarTime != g_lastBarTime[index])
{
g_lastBarTime[index] = curBarTime;
return true;
}
return false;
}
每多监控一个品种,指标函数就要多调用一次。品种太多会导致 OnTick 执行变慢。
正相关品种同时开同向单等于加杠杆,比如EURUSD和GBPUSD经常同向波动。
EA 运行效率直接影响实盘表现。优化的核心是:能不计算的就不算,能少算的就少算。
// 反例:每tick都重新计算指标(浪费)
void OnTickBad()
{
double ma5 = iMA(_Symbol, _Period, 5, 0, MODE_SMA, PRICE_CLOSE, 1);
double ma20 = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
double rsi = iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 1);
}
// 正例:只在新K线时计算(缓存结果)
double g_ma5 = 0, g_ma20 = 0, g_rsi = 0;
datetime g_lastBar = 0;
bool IsNewBar()
{
datetime cur = iTime(_Symbol, _Period, 0);
if (cur != g_lastBar) { g_lastBar = cur; return true; }
return false;
}
void UpdateIndicators()
{
g_ma5 = iMA(_Symbol, _Period, 5, 0, MODE_SMA, PRICE_CLOSE, 1);
g_ma20 = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
g_rsi = iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 1);
}
void OnTickGood()
{
if (IsNewBar()) UpdateIndicators();
// 直接使用缓存的变量
if (g_ma5 > g_ma20 && g_rsi < 70) { /* 交易逻辑 */ }
}
// 用静态变量缓存(函数内也能保留值)
double GetSMA20()
{
static double cachedValue = 0;
static datetime lastCalc = 0;
datetime curBar = iTime(_Symbol, _Period, 0);
if (curBar != lastCalc)
{
cachedValue = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
lastCalc = curBar;
}
return cachedValue;
}
// 反例:遍历所有K线
double FindHighestHighBad()
{
double highest = 0;
for (int i = 0; i < Bars; i++) // Bars 可能有几万根
if (High[i] > highest) highest = High[i];
return highest;
}
// 正例:只遍历需要的K线
double FindHighestHighGood(int lookback)
{
if (lookback > Bars) lookback = Bars;
double highest = 0;
for (int i = 0; i < lookback; i++) // 只看最近N根
if (High[i] > highest) highest = High[i];
return highest;
}
// 技巧1:循环外提取不变量
void GoodLoop()
{
double point = MarketInfo(_Symbol, MODE_POINT); // 只调用1次
for (int i = 0; i < 1000; i++)
Close[i] = Close[i] * point;
}
// 技巧2:减少循环内函数调用
// 先批量复制到数组,再计算,比循环内调用iMA快得多
// 技巧3:尽早 break / continue
for (int i = 0; i < OrdersTotal(); i++)
{
if (!OrderSelect(i, SELECT_BY_POS)) continue;
if (OrderMagicNumber() != g_magic) continue;
if (OrderType() == OP_BUY) { /* 处理 */ break; } // 找到就退出
}
MQL 调试没有现代 IDE 那么方便,掌握 Print/Comment/断点调试等方法可以大幅提升排错效率。
| 方法 | 用途 | 输出位置 | 注意事项 |
|---|---|---|---|
Print() | 输出日志信息 | Toolbox → Experts | 支持多参数混合,最常用 |
Comment() | 图表实时显示 | 图表左上角 | 实时更新,适合观察动态变量 |
Alert() | 弹窗+声音提醒 | 弹出窗口 | 重要事件触发用 |
GetLastError() | 获取错误码 | 返回 int | 交易失败后立即调用 |
断点调试 | 逐行执行 | 调试器 | 按 F5 启动,F9 设断点 |
// 带标签的日志(方便筛选)
void Log(string msg)
{
Print("[MyEA] ", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
" | ", msg);
}
// 调试开关(生产环境关闭)
input bool InpDebug = true;
void DebugPrint(string msg)
{
if (InpDebug) Print("[DEBUG] ", msg);
}
// 交易失败时打印详细错误
int ticket = OrderSend(_Symbol, OP_BUY, 0.1, Ask, 30, 0, 0, "test", 12345, 0, clrGreen);
if (ticket < 0)
{
int err = GetLastError();
Print("❌ 下单失败! 错误码: ", err);
Print(" 品种: ", _Symbol, " 手数: 0.1 价格: ", Ask);
}
// 显示EA运行状态
void UpdateComment()
{
double ma20 = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
double rsi = iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 1);
Comment(
"╔══ 晓辉EA 状态 ══╗\n",
" 时间: ", TimeToString(TimeCurrent(), TIME_SECONDS), "\n",
" MA20: ", DoubleToStr(ma20, Digits), "\n",
" RSI: ", DoubleToStr(rsi, 2), "\n",
" 订单: ", OrdersTotal(), "\n",
" 余额: ", DoubleToStr(AccountBalance(), 2), "\n",
"╚══════════════════╝"
);
}
在代码行号左侧点击(或按 F9),出现红点表示断点已设置。
按 F5 开始调试,EA 会附加到图表并运行,遇到断点自动暂停。
F10 单步跳过 / F11 单步进入 / F5 继续运行 / Shift+F5 停止调试。
鼠标悬停在变量上可查看当前值,Watch 窗口可添加监控。
| 错误码 | 常量名 | 说明 |
|---|---|---|
| 5 | ERR_NOT_ENOUGH_MONEY | 资金不足 |
| 6 | ERR_PRICE_CHANGED | 价格已变化 |
| 7 | ERR_OFF_QUOTES | 没有报价 |
| 8 | ERR_BROKER_BUSY | 交易商繁忙 |
| 128 | ERR_INVALID_TRADE_VOLUME | 无效手数 |
| 129 | ERR_INVALID_PRICE | 无效价格 |
| 130 | ERR_INVALID_STOPS | 无效止损止盈(太近) |
| 131 | ERR_INVALID_TRADE_PARAMS | 无效交易参数 |
| 146 | ERR_TRADE_CONTEXT_BUSY | 交易上下文繁忙 |
良好的代码风格是团队协作和长期维护的基础。规范的代码不仅好看,更能减少 Bug。
| 类型 | 前缀 | 好的示例 | 不好的示例 |
|---|---|---|---|
| int | i_ | i_magicNumber | magic、mn |
| double | d_ | d_lotSize | lot、ls |
| bool | b_ | b_useStopLoss | usl、flag |
| string | s_ | s_orderComment | comment |
| datetime | dt_ | dt_lastBar | time |
| 数组 | 类型前缀 + _arr | d_ma_arr | ma |
| 全局 | g_ | g_d_balance | 无前缀的全局变量 |
| 常量 | 全大写 | EA_MAGIC | magic |
// 全局变量:g_ 前缀
double g_d_initialBalance;
int g_i_magicNumber;
bool g_b_canTrade;
datetime g_dt_lastBarTime;
// input 参数:Inp 前缀
input double InpLotSize = 0.1;
input int InpStopLoss = 50;
input bool InpUseTrail = false;
// 局部变量:类型前缀
void OnTick()
{
double d_ma20 = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 1);
int i_total = OrdersTotal();
bool b_hasPos = false;
}
// 常量:全大写 + 下划线
#define MAGIC_NUMBER 12345
#define MAX_DRAWDOWN 20.0
// 布尔函数:Is/Has/Can/Check 开头
bool IsNewBar();
bool HasPosition();
bool CanOpenOrder();
// 动作函数:动词 + 对象
void OpenBuyOrder();
void CloseAllOrders();
void UpdateIndicators();
double CalcLotSize();
// Get 系列:获取某个值
double GetMAValue(int period);
int GetOrderCount();
// 文件头注释
//+------------------------------------------------------------------+
//| MyStrategy.mq4 |
//| Copyright 2024, 晓辉编程 eafxtech.com |
//| https://www.eafxtech.com |
//+------------------------------------------------------------------+
// 区块分隔注释
//+------------------------------------------------------------------+
//| 常量定义区 |
//+------------------------------------------------------------------+
// 写"为什么",不写"做什么"
// 从后往前遍历,防止删除订单后索引错位导致漏项
for (int i = OrdersTotal() - 1; i >= 0; i--) { }
成熟的EA开发中常用的架构模式。
信号生成和订单执行解耦,便于独立测试和替换策略
用枚举管理交易状态:等待信号→挂单→持仓→平仓
所有开仓请求先过风控层,统一校验手数、止损、风险限额
独立模块收集交易统计,输出CSV用于分析和复盘
新手最容易踩的坑,提前避坑少走弯路。
访问 iBarShift 或价格数组前,务必检查 Bars 数量是否足够
实盘点差波动大,滑点设太小会导致频繁下单失败,建议设 30-50 点
iMA 等指标第0根K线值会变化,策略信号应使用第1根及以后的K线
Point 在五位数平台是 0.00001,止损计算要用 _Digits 判断
OrderSelect 后必须检查返回值,遍历订单时注意索引变化
高频使用的 MQL4 代码模板,直接复制到你的 EA 中使用,开箱即用。
//+==== 开多单 ====+
int OpenBuy(double lot, double slPoints, double tpPoints, int magic)
{
double sl = (slPoints > 0) ? NormalizeDouble(Bid - slPoints * Point, Digits) : 0;
double tp = (tpPoints > 0) ? NormalizeDouble(Ask + tpPoints * Point, Digits) : 0;
int ticket = OrderSend(_Symbol, OP_BUY, lot, Ask, 30, sl, tp, "EA", magic, 0, clrGreen);
if (ticket < 0) Print("开多失败, err=", GetLastError());
return ticket;
}
//+==== 开空单 ====+
int OpenSell(double lot, double slPoints, double tpPoints, int magic)
{
double sl = (slPoints > 0) ? NormalizeDouble(Ask + slPoints * Point, Digits) : 0;
double tp = (tpPoints > 0) ? NormalizeDouble(Bid - tpPoints * Point, Digits) : 0;
int ticket = OrderSend(_Symbol, OP_SELL, lot, Bid, 30, sl, tp, "EA", magic, 0, clrRed);
if (ticket < 0) Print("开空失败, err=", GetLastError());
return ticket;
}
//+==== 平掉指定订单 ====+
bool CloseTicket(int ticket)
{
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return false;
double price = (OrderType() == OP_BUY) ? Bid : Ask;
return OrderClose(ticket, OrderLots(), price, 30, clrRed);
}
//+==== 平掉当前品种所有订单 ====+
void CloseAll(string symbol, int magic)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != symbol || OrderMagicNumber() != magic) continue;
int type = OrderType();
if (type == OP_BUY || type == OP_SELL)
{
double price = (type == OP_BUY) ? Bid : Ask;
OrderClose(OrderTicket(), OrderLots(), price, 30, clrRed);
}
else { OrderDelete(OrderTicket()); }
}
}
//+==== 修改订单止损止盈 ====+
bool SetSLTP(int ticket, double sl, double tp)
{
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return false;
return OrderModify(ticket, OrderOpenPrice(),
NormalizeDouble(sl, Digits),
NormalizeDouble(tp, Digits), 0, clrYellow);
}
//+==== 移动止损(Trailing Stop)====+
void TrailingStop(int magic, double trailPts)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS)) continue;
if (OrderMagicNumber() != magic || OrderSymbol() != _Symbol) continue;
if (OrderType() == OP_BUY)
{
double newSL = NormalizeDouble(Bid - trailPts * Point, Digits);
if (newSL > OrderStopLoss() + Point)
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrYellow);
}
else if (OrderType() == OP_SELL)
{
double newSL = NormalizeDouble(Ask + trailPts * Point, Digits);
if (OrderStopLoss() == 0 || newSL < OrderStopLoss() - Point)
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrYellow);
}
}
}
//+==== 按风险百分比计算手数 ====+
double CalcLot(double riskPercent, double slPoints)
{
if (slPoints <= 0) return MarketInfo(_Symbol, MODE_MINLOT);
double riskAmount = AccountBalance() * riskPercent / 100.0;
double tickValue = MarketInfo(_Symbol, MODE_TICKVALUE);
double tickSize = MarketInfo(_Symbol, MODE_TICKSIZE);
double valPerPt = tickValue / tickSize * Point;
double lot = riskAmount / (slPoints * valPerPt);
double lotStep = MarketInfo(_Symbol, MODE_LOTSTEP);
lot = MathFloor(lot / lotStep) * lotStep;
lot = NormalizeDouble(lot, 2);
double minLot = MarketInfo(_Symbol, MODE_MINLOT);
double maxLot = MarketInfo(_Symbol, MODE_MAXLOT);
return MathMax(minLot, MathMin(maxLot, lot));
}
//+==== 移动平均线 MA ====+
double GetMA(int period, int shift, int method=MODE_SMA, int priceType=PRICE_CLOSE)
{
return iMA(_Symbol, _Period, period, 0, method, priceType, shift);
}
//+==== RSI 指标 ====+
double GetRSI(int period, int shift)
{
return iRSI(_Symbol, _Period, period, PRICE_CLOSE, shift);
}
//+==== MACD 快线 / 信号线 / 柱状图 ====+
double GetMACDMain(int fast, int slow, int shift)
{ return iMACD(_Symbol, _Period, fast, slow, 1, PRICE_CLOSE, MODE_MAIN, shift); }
double GetMACDSignal(int fast, int slow, int signal, int shift)
{ return iMACD(_Symbol, _Period, fast, slow, signal, PRICE_CLOSE, MODE_SIGNAL, shift); }
double GetMACDHist(int fast, int slow, int signal, int shift)
{ return iMACD(_Symbol, _Period, fast, slow, signal, PRICE_CLOSE, MODE_HISTOGRAM, shift); }
//+==== 判断是否在交易时段内 ====+
bool IsTradingTime(int startHour, int endHour)
{
int h = Hour();
if (startHour < endHour)
return (h >= startHour && h < endHour);
else // 跨午夜,比如 22:00 - 06:00
return (h >= startHour || h < endHour);
}
//+==== 伦敦/纽约时段判断 ====+
bool IsLondonSession() { return (Hour() >= 8 && Hour() < 16); }
bool IsNewYorkSession() { return (Hour() >= 13 && Hour() < 21); }
//+==== 全局变量方式 ====+
datetime g_lastBarTime = 0;
bool IsNewBar()
{
datetime curTime = iTime(_Symbol, _Period, 0);
if (curTime != g_lastBarTime)
{ g_lastBarTime = curTime; return true; }
return false;
}
//+==== 静态变量方式 ====+
bool IsNewBarStatic()
{
static datetime s_lastTime = 0;
datetime cur = iTime(_Symbol, _Period, 0);
if (cur != s_lastTime) { s_lastTime = cur; return true; }
return false;
}
MQL 开发中最常遇到的问题和解答,帮你快速排疑解惑。
可以手动转换,但无法自动无缝转换。两者主要区别在订单模型:MQL4 用单订单系统(OrderSend/OrderClose),MQL5 用持仓+订单双系统(PositionGetInfo/OrderSend)。 另外 MQL5 是完全面向对象的,有类和继承,MQL4 只支持部分 OOP 特性。 如果策略逻辑简单,转换工作量不大;如果用到了高级特性,可能需要重写交易模块。 我们建议:面向 MT4 用户就用 MQL4,面向 MT5 用户就用 MQL5,不要追求一套代码跑两个平台。
有三种方式:
方式一(推荐新手):租一台 Windows VPS,远程桌面上去安装 MT4,登录账号,把 EA 拖到图表上,保持 VPS 不关机即可。
方式二:使用 MT4 内置的 MQL5 虚拟主机(Virtual Hosting),在 MT 菜单「文件 → 租用虚拟主机」,直接把 EA 同步到云端运行。
方式三:Linux 服务器用 Wine 运行 MT4,但稳定性不如 Windows,不推荐新手尝试。
常见原因:
1. 点差差异:回测用固定点差,实盘点差会波动。解决:回测时把点差设大一点。
2. 滑点差异:回测默认 0 滑点,实盘有滑点。解决:回测时加合理滑点(10-20 点)。
3. 指标重绘:第 0 根 K 线的指标值会随价格变化。解决:信号只用第 1 根及以后的已完成 K 线(shift >= 1)。
4. 未来函数:某些指标会用到未来数据。解决:检查是否用到了 future functions。
代码保护有几个层次:
1. 编译成 ex4:最基本的保护,但 ex4 可以被反编译。
2. 加壳/混淆:使用 Themida、VMProtect 等工具加壳。
3. DLL 封装核心逻辑:把核心策略写到 C++ DLL 里,MQL 只负责调用。这是商业 EA 常用做法。
4. 在线验证:EA 启动时连接服务器验证授权,绑定账号或硬件 ID。
建议:核心策略放 DLL + 在线验证,这是目前最稳妥的方案。
理论上没有硬性限制,主要取决于电脑性能和 EA 复杂度:
普通电脑(4核 8G):同时跑 10-20 个简单 EA 没问题。
高配电脑(8核 16G):可以跑 30-50 个 EA。
注意:不是越多越好,跑太多会导致延迟增加,影响下单速度。同一个图表上只能挂 1 个 EA。
按以下步骤排查:
1. 检查自动交易开关:工具栏「自动交易」按钮是否亮着。
2. 检查 EA 笑脸/哭脸:图表右上角是笑脸还是哭脸,哭脸说明 EA 被禁用。
3. 检查 DLL 权限:EA 属性里是否勾选了「允许 DLL 导入」。
4. 查看 Experts 日志:按 Ctrl+J 打开工具箱,看有没有错误信息。
5. 检查信号条件:是不是当前行情没有触发信号?
6. 检查交易时间:市场关闭时(周末/节假日)无法下单。
// 在 OnInit 中检查(推荐)
input string AllowedSymbol = "EURUSD";
int OnInit()
{
if (_Symbol != AllowedSymbol)
{
Print("❌ 此EA只允许在 ", AllowedSymbol, " 上运行");
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
有编程基础(尤其是 C/C++/Java/C#)学 MQL 会非常快,一周就能上手写简单 EA。
没有编程基础也能学,建议学习路径:
1. 先学基础语法(变量、if、for、函数)— 约 1-2 周
2. 再学交易函数(OrderSend/OrderClose)— 约 1 周
3. 仿写简单策略(均线交叉)— 约 1-2 周
4. 学习风控和进阶技巧 — 持续学习
如果实在不想学编程,可以把策略需求交给我们定制开发,通常 3-7 天交付。