写一个简单的EA示例 (MA金叉死叉 – 市价单入场版)
下面我们通过一个完整的EA示例,来整合运用前面讨论过的各种概念和函数。这是一个基于移动平均线交叉的简单交易系统:
- 入场信号:
- 当10日MA均线上穿20日均线时 (金叉),买入信号。
- 当10日MA均线下穿20日均线时 (死叉),卖出信号。
- 交易逻辑:
- 根据信号开立市价单 (Buy 或 Sell)。
- 仓位管理: 设置为单向持仓,即同一时间最多只持有一个买单或一个卖单。
- 当出现新的买入信号时,如果当前持有卖单,则先将卖单平仓,然后再开立新的买单。
- 当出现新的卖出信号时,如果当前持有买单,则先将买单平仓,然后再开立新的卖单。
- 使用全局变量
g_BuyTicket
和g_SellTicket
来跟踪记录当前持有的买单或卖单的订单号 (0 表示无持仓)。这个机制可以有效防止在同一方向上连续重复开仓。 - 订单的退出由止损、止盈或反向开仓信号触发平仓来完成。
#property copyright "图道交易"
#property link "https://www.eamql5.com"
// --- 输入参数 ---
extern double LotSize = 0.1; // 交易手数
extern double StopLoss = 50; // 止损点数 (pips)
extern double TakeProfit = 100; // 止盈点数 (pips)
extern int Slippage = 5; // 允许滑点 (pips) - 会被转换为 points
extern int MagicNumber = 123; // 魔术号 (用于区分EA订单)
extern int FastMAPeriod = 10; // 快速MA均线
extern int SlowMAPeriod = 20; // 慢速MA均线
// --- 全局变量 ---
int g_BuyTicket = 0; // 存储当前买单订单号 (0表示无)
int g_SellTicket = 0; // 存储当前卖单订单号 (0表示无)
double g_onePipValue = 0.0; // 存储1 pip对应的价格值
int g_slippagePoints = 0; // 存储转换后的滑点points值
// --- 初始化函数 ---
int init()
{
// 计算并存储用于后续计算的 1 pip 值和滑点 points 值
g_onePipValue = PipPoint(Symbol());
g_slippagePoints = GetSlippage(Symbol(), Slippage);
// 打印信息以便调试确认
PrintFormat("EA '%s' 初始化完成: 交易品种 %s, 1 Pip = %f, Slippage Points = %d",
WindowExpertName(), Symbol(), g_onePipValue, g_slippagePoints);
return(INIT_SUCCEEDED); // 返回初始化成功状态
}
// --- 核心逻辑函数 ---
void OnTick() //
{
// --- 1. 计算指标 ---
// 获取当前 K 线 (索引 0) 的快速和慢速移动平均值
double FastMA = iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
double SlowMA = iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
// --- 2. 买入逻辑 ---
// 条件: 金叉 (Fast > Slow) 且当前没有持有买单 (g_BuyTicket == 0)
if (FastMA > SlowMA && g_BuyTicket == 0)
{
// a. 先尝试平掉可能存在的反向卖单
bool sellClosedOrNotExist = CloseOppositeOrder(g_SellTicket, OP_SELL);
// b. 确认已无反向卖单后,准备开立买单
if (sellClosedOrNotExist)
{
// 计算开仓价、止损价、止盈价
double openPrice_Buy = Ask; // 买单开仓价
double stopLoss_Buy = 0;
double takeProfit_Buy = 0;
if (StopLoss > 0)
stopLoss_Buy = NormalizeDouble(openPrice_Buy - (StopLoss * g_onePipValue), Digits);
if (TakeProfit > 0)
takeProfit_Buy = NormalizeDouble(openPrice_Buy + (TakeProfit * g_onePipValue), Digits);
// c. 发送开仓指令
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, openPrice_Buy, g_slippagePoints,
stopLoss_Buy, takeProfit_Buy, "MA Cross Buy", MagicNumber, 0, Green);
// d. 处理开仓结果
if (ticket > 0) { // 开仓成功
g_BuyTicket = ticket; // 记录新买单的订单号
Print("成功开立买单 #", g_BuyTicket);
} else { // 开仓失败
Print("开立买单失败, Error: ", GetLastError());
}
} // end if sellClosedOrNotExist
} // end if (金叉 && 无买单)
// --- 3. 卖出逻辑 ---
// 条件: 死叉 (Fast < Slow) 且当前没有持有卖单 (g_SellTicket == 0)
if (FastMA < SlowMA && g_SellTicket == 0)
{
// a. 先尝试平掉可能存在的反向买单
bool buyClosedOrNotExist = CloseOppositeOrder(g_BuyTicket, OP_BUY);
// b. 确认已无反向买单后,准备开立卖单
if (buyClosedOrNotExist)
{
// 计算开仓价、止损价、止盈价
double openPrice_Sell = Bid; // 卖单开仓价
double stopLoss_Sell = 0;
double takeProfit_Sell = 0;
if (StopLoss > 0)
stopLoss_Sell = NormalizeDouble(openPrice_Sell + (StopLoss * g_onePipValue), Digits);
if (TakeProfit > 0)
takeProfit_Sell = NormalizeDouble(openPrice_Sell - (TakeProfit * g_onePipValue), Digits);
// c. 发送开仓指令
ticket = OrderSend(Symbol(), OP_SELL, LotSize, openPrice_Sell, g_slippagePoints,
stopLoss_Sell, takeProfit_Sell, "MA Cross Sell", MagicNumber, 0, Red);
// d. 处理开仓结果
if (ticket > 0) { // 开仓成功
g_SellTicket = ticket; // 记录新卖单的订单号
Print("成功开立卖单 #", g_SellTicket);
} else { // 开仓失败
Print("开立卖单失败, Error: ", GetLastError());
}
} // end if buyClosedOrNotExist
} // end if (死叉 && 无卖单)
}
// --- 封装平仓/删单函数 ---
// 功能:尝试平掉指定市价单,并更新订单变量
// 返回值: true 表示反向订单已关闭或不存在,可以继续开新单; false 表示平仓失败
bool CloseOppositeOrder(int& ticketVar, int orderTypeToClose) {
bool closedOrCleared = true; // 默认为可以继续
if (ticketVar > 0) {
if (OrderSelect(ticketVar, SELECT_BY_TICKET)) {
if (OrderCloseTime() == 0) { // 订单未关闭
if (OrderType() == orderTypeToClose) { // 确认是目标类型的市价单
double closePrice = (orderTypeToClose == OP_BUY) ? Bid : Ask;
RefreshRates(); // 确保价格最新
if (OrderClose(ticketVar, OrderLots(), closePrice, g_slippagePoints, Red)) {
Print("成功发送平仓指令 for #", ticketVar);
ticketVar = 0; // 指令发送成功,清除票号记录
closedOrCleared = true;
} else {
Print("发送平仓指令失败 for #", ticketVar, " Error: ", GetLastError());
closedOrCleared = false; // 平仓失败,本次不能开新单
}
} else { // 类型不符
Print("订单 #", ticketVar, " 类型不是期望的 ", orderTypeToClose, ", 清除记录.");
ticketVar = 0;
closedOrCleared = true;
}
} else { // 订单已被关闭
ticketVar = 0; // 清除票号记录
closedOrCleared = true;
}
} else { // 无法选中订单
ticketVar = 0; // 清除票号记录
closedOrCleared = true;
}
}
return closedOrCleared;
}
// --- 自定义辅助函数 ---
double PipPoint(string currencySymbol)
{
int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS);
double p = Point;
if (digits == 3 || digits == 5) { p *= 10.0; }
return(p);
}
int GetSlippage(string currencySymbol, int slippageInPips)
{
int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS);
int points = slippageInPips;
if (digits == 3 || digits == 5) { points *= 10; }
return(points);
}
(注:以上意译版本的 EA 代码中包含了建议的 CloseOppositeOrder
辅助函数,并修正了 PipPoint
和 GetSlippage
函数的实现方式以提高准确性和兼容性。实际使用时,需要将这些辅助函数包含在 EA 代码中。)
将 EA 修改为使用挂单
现在,我们将之前的 MA 交叉 EA 进行修改,使其不再使用市价单,而是使用挂单,具体来说,是止损挂单。
- 新逻辑概述:
- 当快速 MA > 慢速 MA 时,不再立即买入,而是在当前价格上方一定距离处设置一个买入止损单 (Buy Stop)。
- 当快速 MA < 慢速 MA 时,在当前价格下方一定距离处设置一个卖出止损单 (Sell Stop)。
- 挂单距离: 引入一个新的外部参数
PendingPips
来定义挂单价格距离某个基准价的点数 (pips)。
// 添加新的外部参数
extern int PendingPips = 10; // 挂单距离当前价格的点数 (pips)
- 订单管理修改: 当产生新的挂单信号时,不仅要处理可能存在的反向市价单,还要处理可能存在的反向挂单。因此,在尝试设置新挂单前,需要检查反向订单的类型 (
OrderType()
),然后调用正确的函数来结束它:市价单用OrderClose()
,挂单用OrderDelete()
。
示例:修改后的平仓/删单逻辑 (在设置买挂单之前,处理反向卖单/卖挂单)
// 假设 g_SellTicket 存储了反向订单号
if (g_SellTicket > 0) // 如果存在反向订单记录
{
if (OrderSelect(g_SellTicket, SELECT_BY_TICKET)) // 尝试选中
{
if (OrderCloseTime() == 0) // 确认订单还活跃
{
int orderType = OrderType();
bool result = false;
string action = "";
if (orderType == OP_SELL) // 如果是市价卖单
{
result = OrderClose(g_SellTicket, OrderLots(), Ask, g_slippagePoints, Red); // 平仓
action = "平掉市价卖单";
}
// 检查是否为卖挂单 (OP_SELLSTOP 或 OP_SELLLIMIT)
else if (orderType == OP_SELLSTOP || orderType == OP_SELLLIMIT)
{
result = OrderDelete(g_SellTicket, Red); // 删除挂单
action = "删除挂卖单";
}
if (result) { // 处理成功
Print(action, " #", g_SellTicket, " 成功.");
g_SellTicket = 0; // 清除记录
} else { // 处理失败
Print(action, " #", g_SellTicket, " 失败, Error: ", GetLastError());
// 失败时暂不清除记录,下次 tick 可能重试
}
}
else g_SellTicket = 0; // 订单已结束 (CloseTime != 0),清除记录
}
else g_SellTicket = 0; // 无法选中,清除记录
} // end if (g_SellTicket > 0)
// ... 只有当 g_SellTicket 最终为 0 时,才继续设置买挂单 ...
(处理反向买单/买挂单的逻辑类似,检查 g_BuyTicket
,平买单用 Bid
,删除买挂单)
-
挂单价格计算与下单:
- 计算挂单价格。示例中使用
Close[0]
(上一根 K 线的收盘价) 作为计算基准价,这在逻辑上可能需要商榷。例如,设置买入止损单通常会基于当前Ask
价或High[0]
向上浮动。
// 计算买入止损挂单价格 (高于 Close[0]) // 注意: 使用 Close[0] 作为基准可能不符合常规逻辑,通常会用 High[0] 或 Ask double pendingPrice_Buy = NormalizeDouble(Close[0] + (PendingPips * g_onePipValue), Digits);
- 计算相对于挂单价格的止损和止盈。
- 调用
OrderSend()
时,使用挂单类型 (OP_BUYSTOP
) 和计算出的pendingPrice_Buy
。
示例 (设置买入止损挂单):
// ... (先执行上述平仓/删单逻辑,确保 g_SellTicket == 0) ... if (g_SellTicket == 0) // 确认没有反向订单/挂单后 { // 计算挂单价格 double pendingPrice_Buy = NormalizeDouble(Close[0] + (PendingPips * g_onePipValue), Digits); int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL); double minDistance = stopLevel * Point; if ((pendingPrice_Buy - Ask) < minDistance) { Print("挂单价格距离市价太近 (StopLevel), 不挂单。"); } // 计算相对于挂单价的 SL/TP double stopLoss_BuyStop = 0; double takeProfit_BuyStop = 0; if (StopLoss > 0) stopLoss_BuyStop = NormalizeDouble(pendingPrice_Buy - (StopLoss * g_onePipValue), Digits); if (TakeProfit > 0) takeProfit_BuyStop = NormalizeDouble(pendingPrice_Buy + (TakeProfit * g_onePipValue), Digits); // !!! 实际代码中还需检查挂单价格是否满足最小止损距离 !!! // 例如: if (pendingPrice_Buy <= Ask + MarketInfo(Symbol(), MODE_STOPLEVEL) * Point) return(0); // 设置买入止损挂单 int ticket = OrderSend(Symbol(), OP_BUYSTOP, LotSize, pendingPrice_Buy, g_slippagePoints, stopLoss_BuyStop, takeProfit_BuyStop, "MA Cross Buy Stop", MagicNumber, 0, Green); if (ticket > 0) { // 挂单设置成功 g_BuyTicket = ticket; // 记录新挂单的订单号 Print("成功设置买入止损挂单 #", g_BuyTicket, " at ", pendingPrice_Buy); } else { // 挂单设置失败 Print("设置买入止损挂单失败, Error: ", GetLastError()); } } // end if g_SellTicket == 0
卖出止损单的逻辑类似:
- 先处理反向买单/买挂单。
- 计算卖出止损挂单价格 (原书示例:
Close[0] - (PendingPips * g_onePipValue)
)。 - 计算相对于挂单价的 SL/TP。
- 检查最小距离。
- 调用
OrderSend()
设置OP_SELLSTOP
挂单,成功后记录到g_SellTicket
并将g_BuyTicket
置 0。
- 计算挂单价格。示例中使用