#property copyright "tudaojiaoyi" // 版权声明
#include <stdlib.mqh> // 引入MQL4标准库,提供如ErrorDescription()等实用函数
// ===== 手数计算与验证函数 =====
double CalcLotSize(bool argDynamicLotSize, double argEquityPercent, double argStopLoss,
double argFixedLotSize)
{
double lotSize; // 在函数开始时声明,确保所有路径都有赋值
// 如果启用动态手数并且设置了有效的止损点数
if(argDynamicLotSize == true && argStopLoss > 0)
{
// 根据净值和风险百分比,计算本次交易可承受的风险金额
double riskAmount = AccountEquity() * (argEquityPercent / 100.0); // 计算风险金额
// 计算1个“大点”对应的价值。对于大多数货币对,1个大点(Point)等于10个最小点(Tick)。
double pointValue = MarketInfo(Symbol(), MODE_TICKVALUE) * 10; // 大多数货币对1点=10ticks
// 对于3位或5位小数的品种(如日元对),1个大点(Point)的价值直接等于TICKVALUE。
if(Digits == 3 || Digits == 5) pointValue = MarketInfo(Symbol(), MODE_TICKVALUE); // 日元对例外
// 计算手数 = 风险金额 / (止损点数 * 每点价值)
lotSize = riskAmount / (argStopLoss * pointValue);
}
else
{
// 如果不启用动态手数,则直接使用指定的固定手数
lotSize = argFixedLotSize; // 使用固定手数
}
// 返回最终计算出的手数
return(lotSize);
}
/**
* @brief 验证并格式化手数,确保其符合服务器的最小/最大手数及步进要求。
* @param argLotSize 待验证的手数。
* @return 符合服务器要求的、格式化后的手数。
*/
double VerifyLotSize(double argLotSize)
{
// 从服务器获取当前品种的手数限制信息
double minLot = MarketInfo(Symbol(), MODE_MINLOT); // 最小手数
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT); // 最大手数
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP); // 手数步进 (例如0.01)
// 1. 首先确保手数在允许的最小和最大范围内
if(argLotSize < minLot) argLotSize = minLot;
if(argLotSize > maxLot) argLotSize = maxLot;
// 2. 根据手数步进值,对计算出的手数进行对齐
if(lotStep > 0)
{
// 例如,lotSize=0.123, lotStep=0.01 => (0.123/0.01)=12.3 -> MathRound(12.3)=12 -> 12*0.01=0.12
argLotSize = MathRound(argLotSize / lotStep) * lotStep;
}
// 3. 为了使用NormalizeDouble,需要确定正确的小数位数
int digits = 2; // 默认2位小数,适用于0.01步进
if(lotStep >= 1.0) digits = 0; // 例如步进为1手
else if(lotStep >= 0.1) digits = 1; // 例如步进为0.1手
else if(lotStep >= 0.01) digits = 2;// 例如步进为0.01手
else digits = 3; // 对应更小的步进
// 使用计算出的小数位数来规范化手数
argLotSize = NormalizeDouble(argLotSize, digits);
// 4. 最后再次检查,因为对齐操作可能导致结果略小于最小手数
if(argLotSize < minLot) argLotSize = minLot;
return argLotSize;
}
// ===== 订单开立函数 (市价单与挂单) =====
int OpenBuyOrder(string argSymbol, double argLotSize, int argSlippage,
int argMagicNumber, string argComment = "Buy Order")
{
while(IsTradeContextBusy()) Sleep(10); // 等待交易上下文空闲
RefreshRates(); // 开单前刷新价格信息
// 发送开仓指令,买单使用卖价(Ask)开仓
int ticket = OrderSend(argSymbol, OP_BUY, argLotSize, Ask, argSlippage,
0, 0, argComment, argMagicNumber, 0, Green); // 初始不设SL/TP
if(ticket == -1) // 如果开单失败
{
int errorCode = GetLastError(); // 获取错误码
string errDesc = ErrorDescription(errorCode); // 获取错误描述
Alert("开立市价买单失败 - 错误 ", errorCode, ": ", errDesc); // 弹出警报
// 在“专家”日志中打印更详细的失败信息
Print("开买单失败详情 - Bid: ", DoubleToStr(Bid, Digits), " Ask: ", DoubleToStr(Ask, Digits),
" 手数: ", DoubleToStr(argLotSize,2));
}
return(ticket);
}
/** @brief 开立市价卖单。*/
int OpenSellOrder(string argSymbol, double argLotSize, int argSlippage,
int argMagicNumber, string argComment = "Sell Order")
{
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
// 发送开仓指令,卖单使用买价(Bid)开仓
int ticket = OrderSend(argSymbol, OP_SELL, argLotSize, Bid, argSlippage,
0, 0, argComment, argMagicNumber, 0, Red);
if(ticket == -1)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("开立市价卖单失败 - 错误 ", errorCode, ": ", errDesc);
Print("开卖单失败详情 - Bid: ", DoubleToStr(Bid, Digits), " Ask: ", DoubleToStr(Ask, Digits),
" 手数: ", DoubleToStr(argLotSize,2));
}
return(ticket);
}
/** @brief 开立买入停止挂单 (Buy Stop)。*/
int OpenBuyStopOrder(string argSymbol, double argLotSize, double argPendingPrice,
double argStopLoss, double argTakeProfit, int argSlippage, int argMagicNumber,
datetime argExpiration = 0, string argComment = "Buy Stop Order")
{
while(IsTradeContextBusy()) Sleep(10);
RefreshRates(); // 确保挂单价格和SL/TP基于最新市场情况(如果它们是动态计算的)
// 发送买入停止挂单指令
int ticket = OrderSend(argSymbol, OP_BUYSTOP, argLotSize, argPendingPrice, argSlippage,
argStopLoss, argTakeProfit, argComment, argMagicNumber, argExpiration, Green);
if(ticket == -1)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("开立买入停止挂单失败 - 错误 ", errorCode, ": ", errDesc);
Print("开买入停止挂单失败详情 - Ask: ", DoubleToStr(Ask, Digits), " 手数: ", DoubleToStr(argLotSize,2),
" 挂单价: ", DoubleToStr(argPendingPrice,Digits), " SL: ", DoubleToStr(argStopLoss,Digits),
" TP: ", DoubleToStr(argTakeProfit,Digits), " 过期时间: ", TimeToStr(argExpiration, TIME_DATE|TIME_MINUTES));
}
return(ticket);
}
/** @brief 开立卖出停止挂单 (Sell Stop)。*/
int OpenSellStopOrder(string argSymbol, double argLotSize, double argPendingPrice,
double argStopLoss, double argTakeProfit, int argSlippage, int argMagicNumber,
datetime argExpiration = 0, string argComment = "Sell Stop Order")
{
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
// 发送卖出停止挂单指令
int ticket = OrderSend(argSymbol, OP_SELLSTOP, argLotSize, argPendingPrice, argSlippage,
argStopLoss, argTakeProfit, argComment, argMagicNumber, argExpiration, Red);
if(ticket == -1)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("开立卖出停止挂单失败 - 错误 ", errorCode, ": ", errDesc);
Print("开卖出停止挂单失败详情 - Bid: ", DoubleToStr(Bid, Digits), " 手数: ", DoubleToStr(argLotSize,2),
" 挂单价: ", DoubleToStr(argPendingPrice,Digits), " SL: ", DoubleToStr(argStopLoss,Digits),
" TP: ", DoubleToStr(argTakeProfit,Digits), " 过期时间: ", TimeToStr(argExpiration, TIME_DATE|TIME_MINUTES));
}
return(ticket);
}
/** @brief 开立买入限价挂单 (Buy Limit)。*/
int OpenBuyLimitOrder(string argSymbol, double argLotSize, double argPendingPrice,
double argStopLoss, double argTakeProfit, int argSlippage, int argMagicNumber,
datetime argExpiration, string argComment = "Buy Limit Order")
{
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
// 发送买入限价挂单指令
int ticket = OrderSend(argSymbol, OP_BUYLIMIT, argLotSize, argPendingPrice, argSlippage,
argStopLoss, argTakeProfit, argComment, argMagicNumber, argExpiration, Green);
if(ticket == -1)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("开立买入限价挂单失败 - 错误 ", errorCode, ": ", errDesc);
Print("开买入限价挂单失败详情 - Bid: ", DoubleToStr(Bid, Digits), " 手数: ", DoubleToStr(argLotSize,2),
" 挂单价: ", DoubleToStr(argPendingPrice,Digits), " SL: ", DoubleToStr(argStopLoss,Digits),
" TP: ", DoubleToStr(argTakeProfit,Digits), " 过期时间: ", TimeToStr(argExpiration, TIME_DATE|TIME_MINUTES));
}
return(ticket);
}
/** @brief 开立卖出限价挂单 (Sell Limit)。*/
int OpenSellLimitOrder(string argSymbol, double argLotSize, double argPendingPrice,
double argStopLoss, double argTakeProfit, int argSlippage, int argMagicNumber,
datetime argExpiration, string argComment = "Sell Limit Order")
{
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
// 发送卖出限价挂单指令
int ticket = OrderSend(argSymbol, OP_SELLLIMIT, argLotSize, argPendingPrice, argSlippage,
argStopLoss, argTakeProfit, argComment, argMagicNumber, argExpiration, Red);
if(ticket == -1)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("开立卖出限价挂单失败 - 错误 ", errorCode, ": ", errDesc);
Print("开卖出限价挂单失败详情 - Ask: ", DoubleToStr(Ask, Digits), " 手数: ", DoubleToStr(argLotSize,2),
" 挂单价: ", DoubleToStr(argPendingPrice,Digits), " SL: ", DoubleToStr(argStopLoss,Digits),
" TP: ", DoubleToStr(argTakeProfit,Digits), " 过期时间: ", TimeToStr(argExpiration, TIME_DATE|TIME_MINUTES));
}
return(ticket);
}
// ===== 价格与滑点辅助函数 =====
/** @brief 计算当前品种1个“大点”对应的价格小数。*/
double PipPoint(string currencySymbol) // 参数名更清晰
{
int decimals = MarketInfo(currencySymbol, MODE_DIGITS); // 获取小数位数
double pointValue = 0.0;
if(decimals == 2 || decimals == 3) pointValue = 0.01; // 如 JPY 对 (USDJPY 100.12)
else if(decimals == 4 || decimals == 5) pointValue = 0.0001; // 如 EURUSD (1.1234 or 1.12345)
return(pointValue);
}
/** @brief 根据品种小数位数调整滑点值。*/
int GetSlippage(string currencySymbol, int slippageInPips)
{
int decimals = MarketInfo(currencySymbol, MODE_DIGITS);
int adjustedSlippage = slippageInPips;
// 对于5位或3位报价的品种,输入的“点”通常指“大点”
if(decimals == 3 || decimals == 5)
{
// 将“大点”滑点转换为服务器能理解的“微点”(pips)
adjustedSlippage = slippageInPips * 10;
}
return(adjustedSlippage);
}
// ===== 订单平仓与删除函数 =====
/** @brief 平掉指定的市价买单。*/
bool CloseBuyOrder(string argSymbol, int argCloseTicket, int argSlippage)
{
bool isClosed = false; // 初始化返回状态
// 根据订单号选择订单,如果成功选择
if(OrderSelect(argCloseTicket, SELECT_BY_TICKET))
{
// 确认是本EA的、开仓状态的、市价买单
if(OrderCloseTime() == 0 && OrderType() == OP_BUY)
{
double lotsToClose = OrderLots(); // 获取订单手数
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
double closePrice = Bid; // 市价买单以卖价(Bid)平仓
isClosed = OrderClose(argCloseTicket, lotsToClose, closePrice, argSlippage, Green);
if(!isClosed) // 如果平仓失败
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("平仓买单失败 - 错误: ", errorCode, ": ", errDesc);
Print("平买单失败详情 - 订单号: ", argCloseTicket, " 当前卖价: ", DoubleToStr(Bid, Digits));
}
}
}
return(isClosed);
}
/** @brief 平掉指定的市价卖单。*/
bool CloseSellOrder(string argSymbol, int argCloseTicket, int argSlippage)
{
bool isClosed = false;
if(OrderSelect(argCloseTicket, SELECT_BY_TICKET))
{
// 确认是开仓状态的市价卖单
if(OrderCloseTime() == 0 && OrderType() == OP_SELL)
{
double lotsToClose = OrderLots();
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
double closePrice = Ask; // 市价卖单以买价(Ask)平仓
isClosed = OrderClose(argCloseTicket, lotsToClose, closePrice, argSlippage, Red); // 原文用Red,正确
if(!isClosed)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("平仓卖单失败 - 错误: ", errorCode, ": ", errDesc);
Print("平卖单失败详情 - 订单号: ", argCloseTicket, " 当前买价: ", DoubleToStr(Ask, Digits));
}
}
}
return(isClosed);
}
/** @brief 删除指定的挂单。*/
bool ClosePendingOrder(string argSymbol_unused, int argCloseTicket) // argSymbol在此函数中实际未使用
{
bool isDeleted = false;
if(OrderSelect(argCloseTicket, SELECT_BY_TICKET))
{
// 确保是挂单类型且尚未成交 (挂单类型值从OP_BUYLIMIT到OP_SELLSTOP)
if(OrderType() >= OP_BUYLIMIT && OrderType() <= OP_SELLSTOP && OrderCloseTime() == 0)
{
while(IsTradeContextBusy()) Sleep(10);
isDeleted = OrderDelete(argCloseTicket, Red); // 删除挂单,颜色参数通常不重要
if(!isDeleted)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("删除挂单失败 - 错误: ", errorCode, ": ", errDesc);
// OrderSymbol() 比 argSymbol 更可靠,因为订单自身带有品种信息
Print("删除挂单失败详情 - 订单号: ", argCloseTicket, " 品种: ", OrderSymbol(),
" Bid: ", DoubleToStr(MarketInfo(OrderSymbol(),MODE_BID),Digits),
" Ask: ", DoubleToStr(MarketInfo(OrderSymbol(),MODE_ASK),Digits));
}
}
}
return(isDeleted);
}
// ===== 止损止盈价格计算函数 =====
/** @brief 计算买单的止损价格。*/
double CalcBuyStopLoss(string argSymbol, double argStopLossPips, double argOpenPrice)
{
if(argStopLossPips <= 0) return(0); // 如果止损点数为0或负,则不设置止损,返回0
// 买单止损价 = 开仓价 - 止损点数对应的价格差
double stopLossPrice = argOpenPrice - (argStopLossPips * PipPoint(argSymbol));
// 规范化价格到当前品种要求的小数位数
return(NormalizeDouble(stopLossPrice, MarketInfo(argSymbol, MODE_DIGITS)));
}
/** @brief 计算卖单的止损价格。*/
double CalcSellStopLoss(string argSymbol, double argStopLossPips, double argOpenPrice)
{
if(argStopLossPips <= 0) return(0);
// 卖单止损价 = 开仓价 + 止损点数对应的价格差
double stopLossPrice = argOpenPrice + (argStopLossPips * PipPoint(argSymbol));
return(NormalizeDouble(stopLossPrice, MarketInfo(argSymbol, MODE_DIGITS)));
}
/** @brief 计算买单的止盈价格。*/
double CalcBuyTakeProfit(string argSymbol, double argTakeProfitPips, double argOpenPrice)
{
if(argTakeProfitPips <= 0) return(0); // 如果止盈点数为0或负,则不设置止盈
// 买单止盈价 = 开仓价 + 止盈点数对应的价格差
double takeProfitPrice = argOpenPrice + (argTakeProfitPips * PipPoint(argSymbol));
return(NormalizeDouble(takeProfitPrice, MarketInfo(argSymbol, MODE_DIGITS)));
}
/** @brief 计算卖单的止盈价格。*/
double CalcSellTakeProfit(string argSymbol, double argTakeProfitPips, double argOpenPrice)
{
if(argTakeProfitPips <= 0) return(0);
// 卖单止盈价 = 开仓价 - 止盈点数对应的价格差
double takeProfitPrice = argOpenPrice - (argTakeProfitPips * PipPoint(argSymbol));
return(NormalizeDouble(takeProfitPrice, MarketInfo(argSymbol, MODE_DIGITS)));
}
// ===== 价格与最小止损距离验证及调整函数 =====
/**
* @brief 验证给定价格是否有效高于(远离)基准价加上最小止损距离(StopLevel)。
* @param argVerifyPrice 待验证的价格 (例如,买单的止盈价或买入停止单的开仓价)。
* @param argBasePrice 计算的基准价。默认为0,此时使用当前卖价(Ask)。
* @return true 如果有效,false 如果无效 (太近)。
*/
bool VerifyUpperStopLevel(string argSymbol, double argVerifyPrice,
double argBasePrice = 0) // argOpenPrice 改为 argBasePrice 更通用
{
// 获取服务器要求的最小止损距离,并转换为价格单位
double stopLevelDistance = MarketInfo(argSymbol, MODE_STOPLEVEL) * Point; // Point 是预定义变量
double basePrice;
if(argBasePrice == 0) basePrice = Ask; // 若未提供基准价,默认用当前卖价 (例如,为买单设置TP)
else basePrice = argBasePrice; // 否则用提供的基准价 (例如,挂单的开仓价)
// 计算出不可设置价格的上限边界
double upperStopBoundary = basePrice + stopLevelDistance;
return (argVerifyPrice > upperStopBoundary); // 价格需严格大于边界
}
/** * @brief 验证给定价格是否有效低于(远离)当前买价减去最小止损距离。
* (用于买单止损价或卖单止盈价的初步验证)
*/
bool VerifyLowerStopLevel(string argSymbol, double argVerifyPrice,
double argBasePrice = 0)
{
double stopLevelDistance = MarketInfo(argSymbol, MODE_STOPLEVEL) * Point;
double basePrice;
if(argBasePrice == 0) basePrice = Bid; // 若未提供基准价,默认用当前买价 (例如,为买单设置SL)
else basePrice = argBasePrice;
// 计算出不可设置价格的下限边界
double lowerStopBoundary = basePrice - stopLevelDistance;
return (argVerifyPrice < lowerStopBoundary && argVerifyPrice > 0); // 价格需严格小于边界且大于0
}
/** * @brief 调整价格,确保其至少高于(远离)基准价加上最小止损距离,并额外增加指定点数。
* @param argAddPips 额外增加的点数(我们定义的“大点”)。
*/
double AdjustAboveStopLevel(string argSymbol, double argAdjustPrice, int argAddPips = 0,
double argBasePrice = 0)
{
double stopLevelDistance = MarketInfo(argSymbol, MODE_STOPLEVEL) * Point;
double pointVal = PipPoint(argSymbol); // 获取1个“大点”的价格值
double basePrice;
if(argBasePrice == 0) basePrice = Ask;
else basePrice = argBasePrice;
double upperStopBoundary = basePrice + stopLevelDistance;
double adjustedPrice = argAdjustPrice;
if(argAdjustPrice <= upperStopBoundary) // 如果原价格不满足条件 (太近或在界内)
{
// 调整到边界外再加指定点数和最小单位(1个Point)
adjustedPrice = upperStopBoundary + (argAddPips * pointVal) + Point;
}
return(NormalizeDouble(adjustedPrice, MarketInfo(argSymbol, MODE_DIGITS)));
}
/** * @brief 调整价格,确保其至少低于(远离)基准价减去最小止损距离,并额外减去指定点数。
*/
double AdjustBelowStopLevel(string argSymbol, double argAdjustPrice, int argAddPips = 0,
double argBasePrice = 0)
{
double stopLevelDistance = MarketInfo(argSymbol, MODE_STOPLEVEL) * Point;
double pointVal = PipPoint(argSymbol);
double basePrice;
if(argBasePrice == 0) basePrice = Bid;
else basePrice = argBasePrice;
double lowerStopBoundary = basePrice - stopLevelDistance;
double adjustedPrice = argAdjustPrice;
// 如果原价格不满足条件 (太近或在界内),并且边界是有效的(大于一个最小点)
if(argAdjustPrice >= lowerStopBoundary && lowerStopBoundary > Point)
{
// 调整到边界外再减去指定点数和最小单位
adjustedPrice = lowerStopBoundary - (argAddPips * pointVal) - Point;
}
// 如果调整后价格无效 (例如小于等于0),则可能返回0,表示无法设置
if (adjustedPrice <= 0 && argAdjustPrice > 0) return 0; // 或者返回原价 argAdjustPrice 如果策略允许
return(NormalizeDouble(adjustedPrice, MarketInfo(argSymbol, MODE_DIGITS)));
}
// ===== 订单修改与管理函数 =====
/** @brief 为已开立的订单添加或修改止损和止盈。*/
bool AddStopProfit(int argTicket, double argStopLoss, double argTakeProfit)
{
bool isModified = false;
if(OrderSelect(argTicket, SELECT_BY_TICKET))
{
// 确保SL/TP值有效 (例如,SL不为0时才尝试设置,TP也是)
// 同时,MQL4中,如果传入的SL/TP值为0,则表示不修改该项
if (argStopLoss < 0) argStopLoss = 0; // 止损不能为负
if (argTakeProfit < 0) argTakeProfit = 0; // 止盈不能为负
// 只有当至少有一个有效值需要设置,或者目标值与当前值不同时,才尝试修改
if (argStopLoss > 0 || argTakeProfit > 0 || OrderStopLoss() != argStopLoss || OrderTakeProfit() != argTakeProfit)
{
while(IsTradeContextBusy()) Sleep(10);
RefreshRates(); // 修改前刷新价格,确保SL/TP与当前市场状态兼容
// 发送修改指令
isModified = OrderModify(argTicket, OrderOpenPrice(), argStopLoss, argTakeProfit, 0, CLR_NONE); // 0=不修改过期时间, CLR_NONE=不修改箭头
if(!isModified)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("添加/修改止损止盈失败 - 错误 ", errorCode, ": ", errDesc);
// 避免在错误日志中再次调用MarketInfo,可能引发连锁问题
Print("修改SL/TP失败详情 - 订单号: ", argTicket, " 目标SL: ", DoubleToStr(argStopLoss,Digits),
" 目标TP: ", DoubleToStr(argTakeProfit,Digits), " 开仓价: ", DoubleToStr(OrderOpenPrice(),Digits));
}
} else {
// 如果传入的SL/TP与现有的一致或都为0,则无需修改,也视为“成功”
isModified = true;
}
}
return(isModified);
}
// ===== 订单数量统计函数 =====
/** @brief 统计当前品种、指定魔术数字的总订单数 (包括市价单和挂单)。*/
int TotalOrderCount(string argSymbol, int argMagicNumber)
{
int orderCount = 0;
// 从后往前遍历订单列表,这样在循环内部删除订单时不会影响后续的索引
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
// 根据位置选择订单
if(OrderSelect(i, SELECT_BY_POS))
{
// 如果订单的魔术数字和交易品种都匹配
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol)
{
orderCount++; // 计数器加一
}
}
}
return(orderCount);
}
/** @brief 统计市价买单数量。*/
int BuyMarketCount(string argSymbol, int argMagicNumber)
{
int orderCount = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、订单类型为市价买单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUY)
{
orderCount++;
}
}
}
return(orderCount);
}
/** @brief 统计市价卖单数量。*/
int SellMarketCount(string argSymbol, int argMagicNumber)
{
int orderCount = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、订单类型为市价卖单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_SELL)
{
orderCount++;
}
}
}
return(orderCount);
}
/** @brief 统计买入停止挂单数量。*/
int BuyStopCount(string argSymbol, int argMagicNumber)
{
int orderCount = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、订单类型为买入停止单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUYSTOP)
{
orderCount++;
}
}
}
return(orderCount);
}
/** @brief 统计卖出停止挂单数量。*/
int SellStopCount(string argSymbol, int argMagicNumber)
{
int orderCount = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、订单类型为卖出停止单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_SELLSTOP)
{
orderCount++;
}
}
}
return(orderCount);
}
/** @brief 统计买入限价挂单数量。*/
int BuyLimitCount(string argSymbol, int argMagicNumber)
{
int orderCount = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、订单类型为买入限价单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUYLIMIT)
{
orderCount++;
}
}
}
return(orderCount);
}
/** @brief 统计卖出限价挂单数量。*/
int SellLimitCount(string argSymbol, int argMagicNumber)
{
int orderCount = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、订单类型为卖出限价单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_SELLLIMIT)
{
orderCount++;
}
}
}
return(orderCount);
}
// ===== 一键平仓/删单系列函数 =====
/** @brief 平掉所有符合条件的市价买单。*/
void CloseAllBuyOrders(string argSymbol, int argMagicNumber, int argSlippage)
{
// 从后往前遍历,避免因平仓导致索引错乱
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、订单类型为市价买单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUY)
{
int ticketToClose = OrderTicket(); // 获取订单号
double lotsToClose = OrderLots(); // 获取手数
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
double closePrice = Bid; // 买单以卖价(Bid)平
// 尝试平仓
bool isClosed = OrderClose(ticketToClose, lotsToClose, closePrice, argSlippage, Red);
if(!isClosed) // 如果失败
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("平仓所有买单过程中失败(单个订单) - 错误 ", errorCode, ": ", errDesc);
Print("平所有买单失败详情 - 订单号: ", ticketToClose, " 当前卖价: ", DoubleToStr(Bid, Digits),
" 平仓尝试价: ", DoubleToStr(closePrice,Digits));
}
}
}
}
}
/** @brief 平掉所有符合条件的市价卖单。*/
void CloseAllSellOrders(string argSymbol, int argMagicNumber, int argSlippage)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_SELL)
{
int ticketToClose = OrderTicket();
double lotsToClose = OrderLots();
while(IsTradeContextBusy()) Sleep(10);
RefreshRates();
double closePrice = Ask; // 卖单以买价(Ask)平
bool isClosed = OrderClose(ticketToClose, lotsToClose, closePrice, argSlippage, Red);
if(!isClosed)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("平仓所有卖单过程中失败(单个订单) - 错误 ", errorCode, ": ", errDesc);
Print("平所有卖单失败详情 - 订单号: ", ticketToClose, " 当前买价: ", DoubleToStr(Ask, Digits),
" 平仓尝试价: ", DoubleToStr(closePrice,Digits));
}
}
}
}
}
/** @brief 删除所有符合条件的买入停止挂单。*/
void CloseAllBuyStopOrders(string argSymbol, int argMagicNumber)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUYSTOP)
{
int ticketToDelete = OrderTicket();
while(IsTradeContextBusy()) Sleep(10);
bool isDeleted = OrderDelete(ticketToDelete, Red); // 原文用Closed变量,语义上应为isDeleted
if(!isDeleted)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
// 原文错误信息 "Error"后无空格,已补上。
Alert("删除所有买入停止挂单过程中失败(单个订单) - 错误 ", errorCode, ": ", errDesc);
Print("删所有买入停止挂单失败详情 - 订单号: ", ticketToDelete);
}
}
}
}
}
/** @brief 删除所有符合条件的卖出停止挂单。*/
void CloseAllSellStopOrders(string argSymbol, int argMagicNumber)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_SELLSTOP)
{
int ticketToDelete = OrderTicket();
while(IsTradeContextBusy()) Sleep(10);
bool isDeleted = OrderDelete(ticketToDelete, Red);
if(!isDeleted)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("删除所有卖出停止挂单过程中失败(单个订单) - 错误 ", errorCode, ": ", errDesc);
Print("删所有卖出停止挂单失败详情 - 订单号: ", ticketToDelete);
}
}
}
}
}
/** @brief 删除所有符合条件的买入限价挂单。*/
void CloseAllBuyLimitOrders(string argSymbol, int argMagicNumber)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUYLIMIT)
{
int ticketToDelete = OrderTicket();
while(IsTradeContextBusy()) Sleep(10);
bool isDeleted = OrderDelete(ticketToDelete, Red);
if(!isDeleted)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("删除所有买入限价挂单过程中失败(单个订单) - 错误 ", errorCode, ": ", errDesc);
Print("删所有买入限价挂单失败详情 - 订单号: ", ticketToDelete);
}
}
}
}
}
/** @brief 删除所有符合条件的卖出限价挂单。*/
void CloseAllSellLimitOrders(string argSymbol, int argMagicNumber)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_SELLLIMIT)
{
int ticketToDelete = OrderTicket();
while(IsTradeContextBusy()) Sleep(10);
bool isDeleted = OrderDelete(ticketToDelete, Red);
if(!isDeleted)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("删除所有卖出限价挂单过程中失败(单个订单) - 错误 ", errorCode, ": ", errDesc);
Print("删所有卖出限价挂单失败详情 - 订单号: ", ticketToDelete);
}
}
}
}
}
// ===== 追踪止损函数 =====
/** * @brief 为符合条件的买单执行追踪止损。
* @param argTrailingStop 追踪止损距离 (单位:"大点")。
* @param argMinProfit 启动追踪所需的最小盈利 (单位:"大点")。
*/
void BuyTrailingStop(string argSymbol, int argTrailingStopPips, int argMinProfitPips,
int argMagicNumber)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、开仓状态的市价买单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUY && OrderCloseTime() == 0)
{
RefreshRates(); // 获取最新市价
double pointVal = PipPoint(argSymbol);
// 计算新的建议止损价 (基于当前卖价Bid)
double newStopLossPrice = Bid - (argTrailingStopPips * pointVal);
newStopLossPrice = NormalizeDouble(newStopLossPrice, MarketInfo(argSymbol, MODE_DIGITS));
// 获取当前止损价并规范化
double currentStopLoss = NormalizeDouble(OrderStopLoss(), MarketInfo(argSymbol, MODE_DIGITS));
// 计算当前盈利 (基于当前卖价Bid)
double profitInPrice = Bid - OrderOpenPrice();
// 计算启动追踪所需的最小盈利价格差
double minProfitRequired = argMinProfitPips * pointVal;
// 条件:盈利达到最小要求,并且新的止损位优于(高于)当前止损位 (或者当前无止损)
if(profitInPrice >= minProfitRequired &&
(newStopLossPrice > currentStopLoss || currentStopLoss == 0))
{
// 增加安全检查:确保新的止损价低于当前市价且有效
if(newStopLossPrice < Bid && newStopLossPrice > 0)
{
// 尝试修改订单,只更新止损价
bool isTrailed = OrderModify(OrderTicket(), OrderOpenPrice(), newStopLossPrice,
OrderTakeProfit(), 0, CLR_NONE);
if(!isTrailed)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("买单追踪止损失败 - 错误 ", errorCode, ": ", errDesc);
Print("买单追踪止损失败详情 - Bid: ", DoubleToStr(Bid, Digits),
" 订单号: ", OrderTicket(), " 当前SL: ", DoubleToStr(currentStopLoss,Digits),
" 尝试SL: ", DoubleToStr(newStopLossPrice,Digits));
}
}
}
}
}
}
}
/** * @brief 为符合条件的卖单执行追踪止损。
*/
void SellTrailingStop(string argSymbol, int argTrailingStopPips, int argMinProfitPips,
int argMagicNumber)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS))
{
// 筛选条件:魔术数字、品种、开仓状态的市价卖单
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_SELL && OrderCloseTime() == 0)
{
RefreshRates();
double pointVal = PipPoint(argSymbol);
// 计算新的建议止损价 (基于当前买价Ask)
double newStopLossPrice = Ask + (argTrailingStopPips * pointVal);
newStopLossPrice = NormalizeDouble(newStopLossPrice, MarketInfo(argSymbol, MODE_DIGITS));
double currentStopLoss = NormalizeDouble(OrderStopLoss(), MarketInfo(argSymbol, MODE_DIGITS));
// 计算当前盈利 (基于当前买价Ask)
double profitInPrice = OrderOpenPrice() - Ask;
double minProfitRequired = argMinProfitPips * pointVal;
// 修复:卖单追踪止损,新的止损位应该优于(低于)当前止损位
if(profitInPrice >= minProfitRequired &&
(currentStopLoss == 0 || newStopLossPrice < currentStopLoss))
{
// 修复:确保新的止损价高于当前市价
if(newStopLossPrice > Ask)
{
// 尝试修改订单,只更新止损价
bool isTrailed = OrderModify(OrderTicket(), OrderOpenPrice(), newStopLossPrice,
OrderTakeProfit(), 0, CLR_NONE);
if(!isTrailed)
{
int errorCode = GetLastError();
string errDesc = ErrorDescription(errorCode);
Alert("卖单追踪止损失败 - 错误 ", errorCode, ": ", errDesc);
Print("卖单追踪止损失败详情 - Ask: ", DoubleToStr(Ask, Digits),
" 订单号: ", OrderTicket(), " 当前SL: ", DoubleToStr(currentStopLoss,Digits),
" 尝试SL: ", DoubleToStr(newStopLossPrice,Digits));
}
}
}
}
}
}
}
MQL4(61):包含文件 - 自定义函数源码合集
未经允许不得转载:图道交易 » MQL4(61):包含文件 - 自定义函数源码合集