本节展示的EA源码相比于上一版,增加了更多高级功能:手数计算的改进、错误处理机制、交易环境繁忙检测、止损止盈的改进、订单管理的改进(分两步开单,先开仓再设置止损止盈,然后验证并调整止损止盈价格,确保符合经纪商要求)、手数规范化处理。
这些改进使EA更加稳定和专业,特别是在实盘交易中更安全可靠,是一个更完整的交易系统,而前两版更多的是帮助理解基本概念。
#property copyright "tudaojiaoyi" // 版权声明
#include <stdlib.mqh> // 引入标准库,包含如ErrorDescription等函数
// ===== 外部输入参数 =====
extern bool DynamicLotSize = true; // 是否启用动态手数计算
extern double EquityPercent = 2; // 动态手数:使用的账户净值百分比 (例如2%)
extern double FixedLotSize = 0.1; // 固定手数:若不启用动态手数,则使用此值
extern double StopLoss = 50; // 止损点数
extern double TakeProfit = 100; // 止盈点数
extern int Slippage = 5; // 允许滑点
extern int MagicNumber = 123; // EA魔术数字
extern int FastMAPeriod = 10; // 快速MA周期
extern int SlowMAPeriod = 20; // 慢速MA周期
// ===== 全局变量 =====
int BuyTicket; // 买单订单号
int SellTicket; // 卖单订单号
double UsePoint; // 当前品种点值
int UseSlippage; // 调整后的滑点
int ErrorCode; // 用于存储错误代码
// ===== 初始化函数 =====
int OnInit()
{
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(), Slippage);
return(INIT_SUCCEEDED);
}
// ===== 主逻辑函数 =====
void OnTick()
{
string ErrDesc; //错误处理相关变量声明
// --- 计算移动平均线 (基于前一根K线) ---
double FastMA = iMA(NULL, 0, FastMAPeriod, 0, 0, 0, 1); // shift=1 表示上一根K线
double SlowMA = iMA(NULL, 0, SlowMAPeriod, 0, 0, 0, 1);
// --- 手数计算逻辑 ---
double LotSize; // 将LotSize声明在外部,以便后续逻辑块使用
if(DynamicLotSize == true)
{
double RiskAmount = AccountEquity() * (EquityPercent / 100.0); // 计算风险金额
double TickValue = MarketInfo(Symbol(), MODE_TICKVALUE); // 获取每标准手波动一个tick的价值
// 一个更通用的动态手数计算 (基于止损点数和每点价值):
// double PipValuePerLot = MarketInfo(Symbol(), MODE_TICKVALUE) / (MarketInfo(Symbol(), MODE_TICKSIZE) / Point);
// double CalcLots = RiskAmount / (StopLoss * PipValuePerLot);
if (StopLoss > 0 && TickValue > 0) { // 防止除零
LotSize = (RiskAmount / StopLoss) / TickValue; // StopLoss是目标亏损金额对应的“点数”
} else {
LotSize = FixedLotSize; // 如果无法计算,则使用固定手数
}
}
else
{
LotSize = FixedLotSize; // 使用固定手数
}
// --- 手数规范化与验证 ---
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
if(LotSize < minLot) LotSize = minLot;
if(LotSize > maxLot) LotSize = maxLot;
// 根据手数步进调整手数 (例如,调整到0.01或0.1的倍数)
// LotSize = MathFloor(LotSize / lotStep) * lotStep; // 一种可能的规范化方法
if(lotStep == 0.1)
{
LotSize = NormalizeDouble(LotSize, 1); // 保留1位小数
}
else // 其他情况主要是0.01步进
{
LotSize = NormalizeDouble(LotSize, 2); // 保留2位小数
}
if(LotSize < minLot) LotSize = minLot; // 再次确保不小于最小手数
// --- 买入逻辑 ---
if(FastMA > SlowMA && BuyTicket == 0)
{
// - 平掉可能存在的卖单 -
if(SellTicket > 0 && OrderSelect(SellTicket, SELECT_BY_TICKET) && OrderCloseTime() == 0)
{
double CloseLots = OrderLots();
while(IsTradeContextBusy()) Sleep(10); // 等待交易环境空闲
RefreshRates(); // 刷新价格信息
double ClosePrice = Ask;
bool Closed = OrderClose(SellTicket, CloseLots, ClosePrice, UseSlippage, Red);
if(!Closed)
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("关闭卖单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("关闭卖单失败详情 - Ask: ", Ask, " 手数: ", CloseLots, " 订单号: ", SellTicket);
}
}
// - 开立新买单 (初始不设止损止盈) -
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
BuyTicket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, UseSlippage, 0, 0,
"Buy Order", MagicNumber, 0, Green);
if(BuyTicket == -1) // 如果开单失败
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("开立买单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("开立买单失败详情 - Ask: ", Ask, " 手数: ", LotSize);
}
else // 如果开单成功,则修改订单以设置止损止盈
{
if(OrderSelect(BuyTicket, SELECT_BY_TICKET))
{
double OpenPrice = OrderOpenPrice();
double StopLevelPts = MarketInfo(Symbol(), MODE_STOPLEVEL); // 最小止损点数 (broker points)
double MinStopDist = StopLevelPts * Point; // 转换为价格单位
if (MinStopDist < 5 * UsePoint) MinStopDist = 5 * UsePoint; // 确保至少5个我们定义的点
RefreshRates(); // 获取最新的价格,用于计算止损水平线
double UpperStopLine = Ask + MinStopDist; // 止盈价必须高于此
double LowerStopLine = Bid - MinStopDist; // 止损价必须低于此
double BuyStopLoss = 0;
double BuyTakeProfit = 0;
if(StopLoss > 0) BuyStopLoss = OpenPrice - (StopLoss * UsePoint);
if(TakeProfit > 0) BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint);
// 验证并调整止损价 (确保在允许范围内且不为0)
if(BuyStopLoss > 0 && BuyStopLoss >= LowerStopLine)
{
BuyStopLoss = LowerStopLine - Point; // 调整到允许范围再减一个Point
}
if (BuyStopLoss == 0 && StopLoss > 0) { // 如果原计算为0,但策略要求止损
if (OpenPrice - MinStopDist > 0) BuyStopLoss = OpenPrice - MinStopDist; // 尝试设置最小允许止损
}
// 验证并调整止盈价
if(BuyTakeProfit > 0 && BuyTakeProfit <= UpperStopLine)
{
BuyTakeProfit = UpperStopLine + Point;
}
if (BuyTakeProfit == 0 && TakeProfit > 0) {
BuyTakeProfit = OpenPrice + MinStopDist; // 尝试设置最小允许止盈
}
if(BuyStopLoss > 0 || BuyTakeProfit > 0) // 只有在需要设置SL/TP时才修改
{
while(IsTradeContextBusy()) Sleep(10);
bool TicketMod = OrderModify(BuyTicket, OpenPrice, BuyStopLoss, BuyTakeProfit, 0, CLR_NONE);
if(!TicketMod)
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("修改买单(设SL/TP)失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("修改买单失败详情 - Ask: ", Ask, " Bid: ", Bid, " 订单号: ", BuyTicket,
" SL: ", DoubleToStr(BuyStopLoss, Digits), " TP: ", DoubleToStr(BuyTakeProfit, Digits));
}
}
}
}
SellTicket = 0; // 重置卖单追踪
}
// --- 卖出逻辑 (与买入逻辑对称) ---
if(FastMA < SlowMA && SellTicket == 0)
{
// - 平掉可能存在的买单 -
if(BuyTicket > 0 && OrderSelect(BuyTicket, SELECT_BY_TICKET) && OrderCloseTime() == 0)
{
double SellCloseLots = OrderLots();
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
double SellClosePrice = Bid;
bool SellClosed = OrderClose(BuyTicket, SellCloseLots, SellClosePrice, UseSlippage, Red);
if(!SellClosed)
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode); // 声明错误信息变量
Alert("关闭买单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("关闭买单失败详情 - Bid: ", Bid, " 手数: ", CloseLots, " 订单号: ", BuyTicket);
}
}
// - 开立新卖单 (初始不设止损止盈) -
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
SellTicket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, UseSlippage, 0, 0,
"Sell Order", MagicNumber, 0, Red);
if(SellTicket == -1)
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("开立卖单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("开立卖单失败详情 - Bid: ", Bid, " 手数: ", LotSize);
}
else // 开单成功,则修改订单以设置止损止盈
{
if(OrderSelect(SellTicket, SELECT_BY_TICKET))
{
double SellOpenPrice = OrderOpenPrice();
double SellStopLevelPts = MarketInfo(Symbol(), MODE_STOPLEVEL);
double SellMinStopDist = SellStopLevelPts * Point;
if (SellMinStopDist < 5 * UsePoint) SellMinStopDist = 5 * UsePoint;
RefreshRates();
double SellUpperStopLine = Ask + SellMinStopDist;
double SellLowerStopLine = Bid - SellMinStopDist;
double SellStopLoss = 0;
double SellTakeProfit = 0;
if(StopLoss > 0) SellStopLoss = SellOpenPrice + (StopLoss * UsePoint);
if(TakeProfit > 0) SellTakeProfit = SellOpenPrice - (TakeProfit * UsePoint);
// 验证并调整止损价
if(SellStopLoss > 0 && SellStopLoss <= SellUpperStopLine)
{
SellStopLoss = SellUpperStopLine + Point;
}
if (SellStopLoss == 0 && StopLoss > 0) {
if (SellOpenPrice + SellMinStopDist < 99999) SellStopLoss = SellOpenPrice + SellMinStopDist; // 99999 避免价格过高
}
// 验证并调整止盈价
if(SellTakeProfit > 0 && SellTakeProfit >= SellLowerStopLine)
{
SellTakeProfit = SellLowerStopLine - Point;
}
if(SellTakeProfit == 0 && TakeProfit > 0) {
if(SellOpenPrice - SellMinStopDist > 0) SellTakeProfit = SellOpenPrice - SellMinStopDist;
}
if(SellStopLoss > 0 || SellTakeProfit > 0)
{
while(IsTradeContextBusy()) Sleep(10);
bool SellTicketMod = OrderModify(SellTicket, SellOpenPrice, SellStopLoss, SellTakeProfit, 0, CLR_NONE);
if(!SellTicketMod)
{
ErrorCode = GetLastError();
ErrDesc = ErrorDescription(ErrorCode);
Alert("修改卖单(设SL/TP)失败 - 错误 ", ErrorCode, ": ", ErrDesc);
Print("修改卖单失败详情 - Ask: ", Ask, " Bid: ", Bid, " 订单号: ", SellTicket,
" SL: ", DoubleToStr(SellStopLoss, Digits), " TP: ", DoubleToStr(SellTakeProfit, Digits));
}
}
}
}
BuyTicket = 0; // 重置买单追踪
}
}
// ===== 辅助函数:计算点值 =====
// (与之前EA中的 PipPoint 函数相同)
double PipPoint(string Currency)
{
int CalcDigits = MarketInfo(Currency, MODE_DIGITS);
double CalcPoint = 0.0;
if(CalcDigits == 2 || CalcDigits == 3) CalcPoint = 0.01;
else if(CalcDigits == 4 || CalcDigits == 5) CalcPoint = 0.0001;
return(CalcPoint);
}
// ===== 辅助函数:获取滑点 =====
// (与之前EA中的 GetSlippage 函数相同)
int GetSlippage(string Currency, int SlippagePips)
{
int CalcDigits = MarketInfo(Currency, MODE_DIGITS);
int CalcSlippage = 0;
if(CalcDigits == 2 || CalcDigits == 4) CalcSlippage = SlippagePips;
else if(CalcDigits == 3 || CalcDigits == 5) CalcSlippage = SlippagePips * 10;
return(CalcSlippage);
}