保持敬畏之心
交易是一场持久战

MQL4(61):包含文件 - 自定义函数源码合集

#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));
                        }
                    }
                }
            }
        }
    }
}
赞(0)
未经允许不得转载:图道交易 » MQL4(61):包含文件 - 自定义函数源码合集
分享到

评论 抢沙发