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

MQL4(57):进阶版EA交易程序源码

本节展示的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);
}

赞(0)
未经允许不得转载:图道交易 » MQL4(57):进阶版EA交易程序源码
分享到