本节展示的是一款采用挂单(止损单)方式进行交易,并融合了更多高级特性的EA程序代码。
#property copyright "tudaojiaoyi" // 版权声明
#include <stdlib.mqh> // 引入标准库
// ===== 外部输入参数 =====
extern int PendingPips = 20; // 挂单距离 (点数)
extern double LotSize = 0.1; // 交易手数
extern double StopLoss = 50; // 止损点数
extern double TakeProfit = 100; // 止盈点数
extern int Slippage = 5; // 滑点
extern int MagicNumber = 123; // 魔术数字,用于标识EA订单
extern int FastMAPeriod = 10; // 快速移动平均线周期
extern int SlowMAPeriod = 20; // 慢速移动平均线周期
// ===== 全局变量 =====
int BuyTicket; // 用于存储买入订单的订单号
int SellTicket; // 用于存储卖出订单的订单号
double UsePoint; // 用于存储当前货币对的点值 (例如 0.0001 或 0.01)
int UseSlippage; // 用于存储根据经纪商位数调整后的滑点
int ErrorCode; // 用于存储上一个操作的错误代码
string ErrDesc; // 用于存储错误代码对应的描述信息
// ===== 初始化函数 =====
// EA加载时执行一次,用于初始化全局变量和设置
int OnInit()
{
// 基础初始化
UsePoint = PipPoint(Symbol()); // 计算并设置当前交易品种的点值
UseSlippage = GetSlippage(Symbol(), Slippage); // 计算并设置当前交易品种的滑点
return(INIT_SUCCEEDED); // 返回初始化成功信号
}
// ===== 主体逻辑函数 =====
// 每个报价变动 (tick) 时执行一次,包含EA的主要交易逻辑
void OnTick()
{
// 基础检查,确保交易环境正常
if(!IsTradeAllowed()) return; // 检查EA是否被允许交易
if(IsStopped()) return; // 检查EA是否已停止运行
if(!IsConnected()) return; // 检查是否已连接到交易服务器
// 检查是否有足够的可用保证金
if(AccountFreeMargin() < (1000 * LotSize)) { // 简单地检查保证金是否足够,这里的1000是一个估算值
Print("可用保证金不足");
return;
}
// --- 计算移动平均线 (基于当前K线) ---
double FastMA = iMA(NULL, 0, FastMAPeriod, 0, 0, 0, 0); // 计算快速移动平均线的值
double SlowMA = iMA(NULL, 0, SlowMAPeriod, 0, 0, 0, 0); // 计算慢速移动平均线的值
// --- 买入挂单逻辑 ---
// 当快速均线上穿慢速均线,并且当前没有持有的买单或买入挂单时,执行买入逻辑
if(FastMA > SlowMA && BuyTicket == 0)
{
// - 处理反向订单/挂单 -
// 如果存在反向的卖单或卖出挂单,先将其平仓或删除
if(SellTicket > 0) // 首先检查订单号是否有效
{
// 尝试选择订单
if(!OrderSelect(SellTicket, SELECT_BY_TICKET))
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Print("订单选择失败 - 错误 ", ErrorCode, ": ", ErrDesc);
SellTicket = 0; // 重置无效的订单号
return;
}
// 验证魔术数字,确保是本EA下的订单
if(OrderMagicNumber() != MagicNumber)
{
Print("订单魔术数字不匹配 - 订单号: ", SellTicket);
SellTicket = 0; // 如果魔术数字不匹配,则重置订单号,不再管理此订单
return;
}
// 验证订单是否已关闭
if(OrderCloseTime() != 0)
{
Print("订单已关闭 - 订单号: ", SellTicket);
SellTicket = 0; // 如果订单已关闭,则重置订单号
return;
}
// 根据订单类型执行不同操作:平仓或删除挂单
if(OrderType() == OP_SELL) // 如果是已成交的卖单
{
double CloseLots = OrderLots(); // 获取订单手数
while(IsTradeContextBusy()) Sleep(10); // 如果交易上下文正忙,则等待
RefreshRates(); // 刷新报价
double ClosePrice = Ask; // 使用当前的买价(Ask)来平掉卖单
bool Closed = OrderClose(SellTicket, CloseLots, ClosePrice, UseSlippage, Red); // 执行平仓操作
if(!Closed) // 如果平仓失败
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("关闭卖单失败(买入挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
Print("关闭卖单失败详情 - Ask: ", Ask, " 手数: ", CloseLots, " 订单号: ", SellTicket);
}
else SellTicket = 0; // 平仓成功后,重置订单号
}
else if(OrderType() == OP_SELLSTOP) // 如果是卖出挂单
{
bool Deleted = OrderDelete(SellTicket, Red); // 删除挂单
if(Deleted) SellTicket = 0; // 删除成功后,重置订单号
else // 如果删除失败
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("删除卖出挂单失败(买入挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
Print("删除卖出挂单失败详情 - Ticket: ", SellTicket);
}
}
}
// - 设置买入止损挂单 (Buy Stop) -
double BuyStopLevelPts = MarketInfo(Symbol(), MODE_STOPLEVEL); // 获取服务器要求的最小止损距离(点数)
double BuyMinStopDist = BuyStopLevelPts * Point; // 将最小止损距离转换为价格单位
if (BuyMinStopDist < 5 * UsePoint) BuyMinStopDist = 5 * UsePoint; // 确保最小距离至少为5个点
RefreshRates(); // 刷新报价
double CurrentAskForStop = Ask; // 获取当前买价
// 计算挂单价格,设置在当前K线最高点上方指定点数的位置
double BuyPendingPrice = High[0] + (PendingPips * UsePoint);
// 如果计算出的挂单价格离当前市场价太近,不满足最小距离要求
if(BuyPendingPrice <= CurrentAskForStop + BuyMinStopDist)
{
// 则将挂单价格调整到满足最小距离要求的位置
BuyPendingPrice = CurrentAskForStop + BuyMinStopDist + Point;
}
double BuyStopLoss = 0; // 初始化止损价格
double BuyTakeProfit = 0; // 初始化止盈价格
if(StopLoss > 0) BuyStopLoss = BuyPendingPrice - (StopLoss * UsePoint); // 如果设置了止损,计算止损价格
if(TakeProfit > 0) BuyTakeProfit = BuyPendingPrice + (TakeProfit * UsePoint); // 如果设置了止盈,计算止盈价格
// 检查并调整止损价格,确保其满足服务器的最小止损距离要求
if(BuyStopLoss > 0 && BuyStopLoss >= BuyPendingPrice - BuyMinStopDist)
{
BuyStopLoss = BuyPendingPrice - BuyMinStopDist - Point;
}
if (BuyStopLoss == 0 && StopLoss > 0) { // 如果计算出的止损为0但用户设置了止损点数
if (BuyPendingPrice - BuyMinStopDist > 0) BuyStopLoss = BuyPendingPrice - BuyMinStopDist; // 设置一个最小距离的止损
}
// 检查并调整止盈价格,确保其满足服务器的最小止损距离要求
if(BuyTakeProfit > 0 && BuyTakeProfit <= BuyPendingPrice + BuyMinStopDist)
{
BuyTakeProfit = BuyPendingPrice + BuyMinStopDist + Point;
}
if (BuyTakeProfit == 0 && TakeProfit > 0) { // 如果计算出的止盈为0但用户设置了止盈点数
BuyTakeProfit = BuyPendingPrice + BuyMinStopDist; // 设置一个最小距离的止盈
}
while(IsTradeContextBusy()) Sleep(10); // 等待交易上下文空闲
// 发送买入挂单指令
BuyTicket = OrderSend(Symbol(), OP_BUYSTOP, LotSize, BuyPendingPrice, UseSlippage,
BuyStopLoss, BuyTakeProfit, "Buy Stop Order", MagicNumber, 0, Green);
if(BuyTicket == -1) // 如果下单失败
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("开立买入挂单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("开立买入挂单失败详情 - Ask: ", Ask, " 手数: ", LotSize, " 挂单价: ", DoubleToStr(BuyPendingPrice,Digits),
" SL: ", DoubleToStr(BuyStopLoss,Digits), " TP: ", DoubleToStr(BuyTakeProfit,Digits));
}
SellTicket = 0; // 重置
}
// --- 卖出挂单逻辑 (与买入逻辑对称) ---
// 当快速均线下穿慢速均线,并且当前没有持有的卖单或卖出挂单时,执行卖出逻辑
if(FastMA < SlowMA && SellTicket == 0)
{
// - 处理反向订单/挂单 -
// 如果存在反向的买单或买入挂单,先将其平仓或删除
if(BuyTicket > 0) // 首先检查订单号是否有效
{
// 尝试选择订单
if(!OrderSelect(BuyTicket, SELECT_BY_TICKET))
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Print("订单选择失败 - 错误 ", ErrorCode, ": ", ErrDesc);
BuyTicket = 0; // 重置无效的订单号
return;
}
// 验证魔术数字
if(OrderMagicNumber() != MagicNumber)
{
Print("订单魔术数字不匹配 - 订单号: ", BuyTicket);
BuyTicket = 0;
return;
}
// 验证订单是否已关闭
if(OrderCloseTime() != 0)
{
Print("订单已关闭 - 订单号: ", BuyTicket);
BuyTicket = 0;
return;
}
// 验证订单类型
if(OrderType() == OP_BUY) // 如果是已成交的买单
{
double SellCloseLots = OrderLots();
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
double SellClosePrice = Bid; // 使用当前的卖价(Bid)来平掉买单
bool SellClosed = OrderClose(BuyTicket, SellCloseLots, SellClosePrice, UseSlippage, Red);
if(!SellClosed)
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("关闭买单失败(卖出挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
Print("关闭买单失败详情 - Bid: ", Bid, " 手数: ", SellCloseLots, " 订单号: ", BuyTicket);
}
else BuyTicket = 0;
}
else if(OrderType() == OP_BUYSTOP) // 如果是买入止损挂单
{
while(IsTradeContextBusy()) Sleep(10);
bool BuyDeleted = OrderDelete(BuyTicket, Red);
if(BuyDeleted) BuyTicket = 0;
else
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("删除买入挂单失败(卖出挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
Print("删除买入挂单失败详情 - Ticket: ", BuyTicket);
}
}
}
// - 设置卖出止损挂单 (Sell Stop) -
double SellStopLevelPts = MarketInfo(Symbol(), MODE_STOPLEVEL);
double SellMinStopDist = SellStopLevelPts * Point;
if (SellMinStopDist < 5 * UsePoint) SellMinStopDist = 5 * UsePoint;
RefreshRates();
double CurrentBidForStop = Bid;
double SellPendingPrice = Low[0] - (PendingPips * UsePoint); // 挂单价格在当前K线最低点下方
// 如果计算出的挂单价格离当前市场价太近
if(SellPendingPrice >= CurrentBidForStop - SellMinStopDist)
{
// 调整挂单价以满足最小距离要求
SellPendingPrice = CurrentBidForStop - SellMinStopDist - Point;
}
double SellStopLoss = 0; // 初始化止损价格
double SellTakeProfit = 0; // 初始化止盈价格
if(StopLoss > 0) SellStopLoss = SellPendingPrice + (StopLoss * UsePoint); // 计算止损价
if(TakeProfit > 0) SellTakeProfit = SellPendingPrice - (TakeProfit * UsePoint); // 计算止盈价
// 检查并调整止损价格
if(SellStopLoss > 0 && SellStopLoss <= SellPendingPrice + SellMinStopDist)
{
SellStopLoss = SellPendingPrice + SellMinStopDist + Point;
}
if (SellStopLoss == 0 && StopLoss > 0) {
if (SellPendingPrice + SellMinStopDist < 99999) SellStopLoss = SellPendingPrice + SellMinStopDist;
}
// 检查并调整止盈价格
if(SellTakeProfit > 0 && SellTakeProfit >= SellPendingPrice - SellMinStopDist)
{
SellTakeProfit = SellPendingPrice - SellMinStopDist - Point;
}
if (SellTakeProfit == 0 && TakeProfit > 0) {
if (SellPendingPrice - SellMinStopDist > 0) SellTakeProfit = SellPendingPrice - SellMinStopDist;
}
while(IsTradeContextBusy()) Sleep(10);
// 发送卖出挂单指令
SellTicket = OrderSend(Symbol(), OP_SELLSTOP, LotSize, SellPendingPrice, UseSlippage,
SellStopLoss, SellTakeProfit, "Sell Stop Order", MagicNumber, 0, Red);
if(SellTicket == -1)
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("开立卖出挂单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("开立卖出挂单失败详情 - Bid: ", Bid, " 手数: ", LotSize, " 挂单价: ", DoubleToStr(SellPendingPrice,Digits),
" SL: ", DoubleToStr(SellStopLoss,Digits), " TP: ", DoubleToStr(SellTakeProfit,Digits));
}
BuyTicket = 0; // 重置
}
}
// ===== 辅助函数:计算点值 =====
// (与之前EA中的 PipPoint 函数相同)
// 根据货币对的小数位数确定其点值(例如 EURUSD 是 0.0001, USDJPY 是 0.01)
double PipPoint(string Currency)
{
int CalcDigits = MarketInfo(Currency, MODE_DIGITS); // 获取小数位数
double CalcPoint = 0.0;
// 如果是2位或3位小数(如USDJPY)
if(CalcDigits == 2 || CalcDigits == 3) CalcPoint = 0.01;
// 如果是4位或5位小数(如EURUSD)
else if(CalcDigits == 4 || CalcDigits == 5) CalcPoint = 0.0001;
return(CalcPoint); // 返回计算出的点值
}
// ===== 辅助函数:获取滑点 =====
// (与之前EA中的 GetSlippage 函数相同)
// 根据经纪商是4位数还是5位数报价,来调整滑点参数
int GetSlippage(string Currency, int SlippagePips)
{
int CalDigits = MarketInfo(Currency, MODE_DIGITS); // 获取小数位数
int CalcSlippage = 0;
// 对于4位数报价的经纪商,滑点值不变
if(CalDigits == 2 || CalDigits == 4) CalcSlippage = SlippagePips;
// 对于5位数报价的经纪商,滑点值需要乘以10
else if(CalDigits == 3 || CalDigits == 5) CalcSlippage = SlippagePips * 10;
return(CalcSlippage); // 返回调整后的滑点
}