bobochen0908 发表于 2025-5-2 19:37:23

徵求代寫MT5 EA - MACD背離+ATR停損策略

徵求一個MT5 EA源碼
主要是MACD背離,並以ATR數值設定停損價
交易邏輯如下:

1.面板可設定項目
(1)交易手數
(2)MACD快線長度(系統預設為13)
(3)MACD慢線長度(系統預設為34)
(4)ATR設置(系統預設為13)
(5)交易時區(系統預設為M15)
(6)魔術碼

2.多單進場條件
(1)MACD柱體在零線以下(柱體為負,表示空頭結構)
(2)柱體出現連續"明顯"的 2 個波峰,且高度升高(負值變小,數值往 0 靠近)
(3)對應的這 2 根柱體的 K 線,其低點呈現連續降低
(4)確認第2個波峰型態的柱體訊號穩定,對應的K棒(也就是波峰的下一根K棒,先暫稱L棒)結束後,在下一個K棒開始時進場多單
(5)止損:L棒的低點 - ATR(13)的點數,作為停損價格
(6)止盈價與止損價初始為1:1
(7)移動止盈

3.空單進場
(1)MACD 柱體在零線以上(柱體為正,表示多頭結構)
(2)柱體出現連續"明顯" 2 個波峰且高度降低(正值變小,數值往 0 靠近)
(3)對應的這 2 根柱體的 K 線,其高點連續抬高
(4)確認第2個波峰型態的柱體訊號穩定,對應的K棒(也就是波峰的下一根K棒,先暫稱H棒)結束後,在下一個K棒開始時進場空單
(5)止損:H棒的高點 + ATR(13)的點數,作為停損價格
(6)止盈價與止損價初始為1:1
(7)移動止盈

4.MACD柱體如果貫穿零線,則波峰的計算值規0,重新計算有效波峰數

5.上面敘述的內容,主要是參考這支影片,希望能將交易系統自動化

https://www.youtube.com/watch?v=Ic8V1Pue-2I&t=2s

因為這個系統的停損價,是價格直接減去ATR數值,不確定是不是能適用外匯、指數各商品,可以再討論,是否改採其他方式停損

有其他建議都歡迎討論

yzj 发表于 2025-5-17 17:18:53

我可以帮你写

bzcsx 发表于 2025-5-25 13:33:15

//+------------------------------------------------------------------+
//|                                              MACD_Div_ATR_EA.mq5 |
//|                        Copyright 2025, bzcsx                     |
//|                                             https://www.mql5.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, bzcsx"
#property link      "https://www.mql5.com/"
#property version   "2.00"
#property strict
#property description "增强版MACD背离交易系统(带ATR止损)"
#property description "做多条件: MACD柱状图低于零轴且峰值上升 + 价格低点下降"
#property description "做空条件: MACD柱状图高于零轴且峰值下降 + 价格高点上升"
#property description "包含多重过滤条件和风险管理选项"

#include <Trade\Trade.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

//+------------------------------------------------------------------+
//| 输入参数                                                         |
//+------------------------------------------------------------------+
//--- MACD核心参数
input int FastEMA = 13;                  // MACD快线周期
input int SlowEMA = 34;                  // MACD慢线周期
input int SignalPeriod = 9;            // MACD信号线周期
input ENUM_TIMEFRAMES TimeFrame = PERIOD_M15; // 交易时间框架
input ulong MagicNumber = 12345;         // EA魔术码

//--- 风险管理
input double RiskPercent = 2.0;          // 每笔交易风险百分比
input double FixedLotSize = 0.1;         // 固定交易手数(当RiskPercent=0时使用)
input double RiskRewardRatio = 1.5;      // 风险回报比
input int ATRPeriod = 13;                // ATR止损周期
input double ATRMultiplier = 1.5;      // ATR止损倍数
input bool UseTrailingStop = true;       // 启用移动止损
input int TrailingStopPoints = 50;       // 移动止损点数
input int MinStopLossPoints = 30;      // 最小止损点数
input int MaxStopLossPoints = 200;       // 最大止损点数

//--- 交易过滤器
input bool UseTrendFilter = true;      // 启用趋势MA过滤
input int TrendMAPeriod = 50;            // 趋势MA周期
input ENUM_MA_METHOD TrendMAMethod = MODE_SMA; // 趋势MA计算方法
input bool UseHigherTFConfirmation = false; // 启用更高时间框架确认
input ENUM_TIMEFRAMES HigherTF = PERIOD_H1; // 更高时间框架
input bool UseTimeFilter = false;      // 启用交易时间过滤
input string TradeStartTime = "09:00";   // 交易开始时间(服务器时间)
input string TradeEndTime = "17:00";   // 交易结束时间(服务器时间)
input int MinPeakCount = 2;            // 背离所需最小峰值数量
input int MaxPeakCount = 3;            // 考虑的最大峰值数量

//--- 出场策略
input bool UsePartialProfit = true;      // 启用部分平仓获利
input double PartialProfitRatio = 0.5;   // 1:1时平仓比例
input double SecondTPMultiplier = 2.0;   // 剩余仓位的目标倍数
input bool CloseOnOppositeSignal = true; // 出现相反信号时平仓

//--- 资金管理
input double MaxRiskPerTrade = 0.05;   // 每笔交易最大风险(0.05=5%)
input double MaxDailyLossPercent = 5.0;// 每日最大亏损百分比
input int MaxTradesPerDay = 5;         // 每日最大交易次数

//+------------------------------------------------------------------+
//| 全局变量                                                         |
//+------------------------------------------------------------------+
double macdMain[], macdSignal[], macdHistogram[], atrValues[];
datetime lastTradeTime, lastDailyCheck;
int peakCount = 0;
double lastPeakValue = 0;
double firstPeakValue = 0;
double firstPeakPrice = 0;
double secondPeakPrice = 0;
bool lookingForLong = false;
bool lookingForShort = false;
double lBarLow = 0;
double hBarHigh = 0;
double dailyProfitLoss = 0;
int dailyTradeCount = 0;
int macdHandle, atrHandle, trendMAHandle, higherTFMAHandle;
MqlRates rates[];
CTrade Trade;
bool TradingPaused = false;

// 控制按钮
CChartObjectButton BuyButton;
CChartObjectButton SellButton;
CChartObjectButton PauseButton;
CChartObjectButton CloseAllButton;

//+------------------------------------------------------------------+
//| 专家初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // 初始化指标句柄
   macdHandle = iMACD(_Symbol, TimeFrame, FastEMA, SlowEMA, SignalPeriod, PRICE_CLOSE);
   atrHandle = iATR(_Symbol, TimeFrame, ATRPeriod);
   trendMAHandle = iMA(_Symbol, TimeFrame, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   if(UseHigherTFConfirmation)
      higherTFMAHandle = iMA(_Symbol, HigherTF, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   // 检查参数设置是否有效
   if(RiskPercent <= 0 && FixedLotSize <= 0)
   {
      Alert("风险管理参数配置不正确!");
      return(INIT_PARAMETERS_INCORRECT);
   }
   
   // 重置每日统计
   ResetDailyStats();
   
   // 设置交易对象的魔术码
   Trade.SetExpertMagicNumber(MagicNumber);
   
   // 创建控制按钮
   CreateButtons();
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 创建控制按钮函数                                                 |
//+------------------------------------------------------------------+
void CreateButtons()
{
   // 获取图表宽度和高度
   long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   
   // 按钮尺寸
   int buttonWidth = 80;
   int buttonHeight = 30;
   int buttonSpacing = 10;
   
   // 计算按钮位置(右下角)
   int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
   int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
   
   // 创建买入按钮
   if(!BuyButton.Create(0, "BuyButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建买入按钮失败!");
   BuyButton.Description("做多");
   BuyButton.Color(clrDarkGreen);
   BuyButton.BackColor(clrForestGreen);
   BuyButton.BorderColor(clrWhite);
   BuyButton.FontSize(10);
   BuyButton.Color(clrWhite);
   BuyButton.State(false);
   
   // 创建卖出按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!SellButton.Create(0, "SellButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建卖出按钮失败!");
   SellButton.Description("做空");
   SellButton.Color(clrDarkRed);
   SellButton.BackColor(clrFireBrick);
   SellButton.BorderColor(clrWhite);
   SellButton.FontSize(10);
   SellButton.Color(clrWhite);
   SellButton.State(false);
   
   // 创建暂停加仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!PauseButton.Create(0, "PauseButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建暂停按钮失败!");
   PauseButton.Description("暂停交易");
   PauseButton.Color(clrGoldenrod);
   PauseButton.BackColor(clrDarkGoldenrod);
   PauseButton.BorderColor(clrWhite);
   PauseButton.FontSize(10);
   PauseButton.Color(clrWhite);
   PauseButton.State(false);
   
   // 创建清仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!CloseAllButton.Create(0, "CloseAllButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建清仓按钮失败!");
   CloseAllButton.Description("全部平仓");
   CloseAllButton.Color(clrDarkSlateGray);
   CloseAllButton.BackColor(clrDimGray);
   CloseAllButton.BorderColor(clrWhite);
   CloseAllButton.FontSize(10);
   CloseAllButton.Color(clrWhite);
   CloseAllButton.State(false);
}

//+------------------------------------------------------------------+
//| 重置每日统计数据                                                 |
//+------------------------------------------------------------------+
void ResetDailyStats()
{
   lastDailyCheck = iTime(_Symbol, PERIOD_D1, 0);
   dailyProfitLoss = 0;
   dailyTradeCount = 0;
}

//+------------------------------------------------------------------+
//| 专家逆初始化函数                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 释放指标句柄
   IndicatorRelease(macdHandle);
   IndicatorRelease(atrHandle);
   IndicatorRelease(trendMAHandle);
   if(UseHigherTFConfirmation) IndicatorRelease(higherTFMAHandle);
   
   // 删除按钮
   BuyButton.Delete();
   SellButton.Delete();
   PauseButton.Delete();
   CloseAllButton.Delete();
}

//+------------------------------------------------------------------+
//| 获取MACD指标值                                                   |
//+------------------------------------------------------------------+
void GetMACDValues()
{
   ArrayResize(macdMain, MaxPeakCount+2);
   ArrayResize(macdSignal, MaxPeakCount+2);
   ArrayResize(macdHistogram, MaxPeakCount+2);
   ArrayResize(atrValues, MaxPeakCount+2);
   
   // 获取价格数据
   if(CopyRates(_Symbol, TimeFrame, 0, MaxPeakCount+2, rates) < MaxPeakCount+2)
   {
      Print("获取价格数据失败!");
      return;
   }
   
   // 获取MACD值
   if(CopyBuffer(macdHandle, MAIN_LINE, 0, MaxPeakCount+2, macdMain) < MaxPeakCount+2 ||
      CopyBuffer(macdHandle, SIGNAL_LINE, 0, MaxPeakCount+2, macdSignal) < MaxPeakCount+2)
   {
      Print("获取MACD数据失败!");
      return;
   }
   
   // 计算柱状图值
   for(int i = 0; i < MaxPeakCount+2; i++)
   {
      macdHistogram = macdMain - macdSignal;
   }
   
   // 获取ATR值
   if(CopyBuffer(atrHandle, 0, 0, MaxPeakCount+2, atrValues) < MaxPeakCount+2)
   {
      Print("获取ATR数据失败!");
   }
}

//+------------------------------------------------------------------+
//| 根据风险计算手数                                                 |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPoints)
{
   if(RiskPercent <= 0) return FixedLotSize;
   
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   if(tickValue == 0) return FixedLotSize;
   
   double riskAmount = MathMin(AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100,
                              AccountInfoDouble(ACCOUNT_BALANCE) * MaxRiskPerTrade);
   double lotSize = NormalizeDouble(riskAmount / (stopLossPoints * _Point * tickValue), 2);
   
   // 调整最小和最大手数限制
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   return MathMax(minLot, MathMin(maxLot, lotSize));
}

//+------------------------------------------------------------------+
//| 检查当前是否在交易时间内                                          |
//+------------------------------------------------------------------+
bool IsTradingTime()
{
   if(!UseTimeFilter) return true;
   
   datetime start = StringToTime(TradeStartTime);
   datetime end = StringToTime(TradeEndTime);
   datetime now = TimeCurrent();
   
   if(start == 0 || end == 0)
   {
      Print("时间格式错误! 请使用HH:MM格式");
      return false;
   }
   
   // 处理隔夜交易时段
   if(start < end) return (now >= start && now <= end);
   else return (now >= start || now <= end);
}

//+------------------------------------------------------------------+
//| 检查更高时间框架趋势                                              |
//+------------------------------------------------------------------+
bool IsHigherTFTrendBullish()
{
   if(!UseHigherTFConfirmation) return true;
   
   double close, ma;
   if(CopyClose(_Symbol, HigherTF, 0, 2, close) < 2 ||
      CopyBuffer(higherTFMAHandle, 0, 0, 2, ma) < 2)
   {
      Print("获取更高时间框架数据失败!");
      return true;
   }
   
   return close > ma;
}

//+------------------------------------------------------------------+
//| 检查是否超过每日限制                                              |
//+------------------------------------------------------------------+
bool ExceededDailyLimits()
{
   datetime currentDay = iTime(_Symbol, PERIOD_D1, 0);
   if(currentDay != lastDailyCheck)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
      return false;
   }
   
   if(dailyProfitLoss < 0 && MathAbs(dailyProfitLoss) >= AccountInfoDouble(ACCOUNT_BALANCE) * MaxDailyLossPercent / 100)
   {
      Print("已达到每日亏损限额。今日停止交易。");
      return true;
   }
   
   if(dailyTradeCount >= MaxTradesPerDay)
   {
      Print("已达到每日最大交易次数。今日停止交易。");
      return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| 检查背离形态                                                   |
//+------------------------------------------------------------------+
void CheckDivergence()
{
   // 重置标志
   lookingForLong = false;
   lookingForShort = false;
   
   // 如果交易暂停,则跳过信号检查
   if(TradingPaused) return;
   
   // 检查做多条件(MACD低于零轴)
   if(macdHistogram < 0)
   {
      // 检查峰值形态
      bool isPeak = true;
      for(int i = 0; i <= MaxPeakCount; i++)
      {
         if(macdHistogram >= macdHistogram)
         {
            isPeak = false;
            break;
         }
      }
      
      if(isPeak && peakCount < MaxPeakCount)
      {
         peakCount++;
         if(peakCount == 1)
         {
            firstPeakValue = macdHistogram;
            firstPeakPrice = rates.low;
         }
         else if(peakCount >= MinPeakCount)
         {
            // 检查MACD峰值是否上升(负值减小)且价格创新低
            if(macdHistogram > firstPeakValue)
            {
               double currentLow = rates.low;
               if(currentLow < firstPeakPrice)
               {
                  lookingForLong = true;
                  lBarLow = rates.low;
                  secondPeakPrice = currentLow;
               }
            }
         }
      }
   }
   
   // 检查做空条件(MACD高于零轴)
   if(macdHistogram > 0)
   {
      // 检查峰值形态
      bool isPeak = true;
      for(int i = 0; i <= MaxPeakCount; i++)
      {
         if(macdHistogram <= macdHistogram)
         {
            isPeak = false;
            break;
         }
      }
      
      if(isPeak && peakCount < MaxPeakCount)
      {
         peakCount++;
         if(peakCount == 1)
         {
            firstPeakValue = macdHistogram;
            firstPeakPrice = rates.high;
         }
         else if(peakCount >= MinPeakCount)
         {
            // 检查MACD峰值是否下降(正值减小)且价格创新高
            if(macdHistogram < firstPeakValue)
            {
               double currentHigh = rates.high;
               if(currentHigh > firstPeakPrice)
               {
                  lookingForShort = true;
                  hBarHigh = rates.high;
                  secondPeakPrice = currentHigh;
               }
            }
         }
      }
   }
   
   // 如果MACD穿越零线则重置峰值计数
   if((macdHistogram <= 0 && macdHistogram > 0) ||
      (macdHistogram >= 0 && macdHistogram < 0))
   {
      peakCount = 0;
   }
}

//+------------------------------------------------------------------+
//| 下多单                                                         |
//+------------------------------------------------------------------+
void PlaceLongOrder()
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double atrValue = atrValues * ATRMultiplier;
   double stopLoss = NormalizeDouble(lBarLow - atrValue, _Digits);
   double stopLossPoints = (ask - stopLoss) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints) stopLoss = NormalizeDouble(ask - MinStopLossPoints * _Point, _Digits);
   if(stopLossPoints > MaxStopLossPoints) stopLoss = NormalizeDouble(ask - MaxStopLossPoints * _Point, _Digits);
   
   double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize((ask - stopLoss) / _Point);
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(ask + (ask - stopLoss), _Digits);
         if(Trade.Buy(firstLot, _Symbol, ask, stopLoss, firstTP, "MACD背离做多1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
         }
         else
         {
            Print("买入订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(ask + (ask - stopLoss) * SecondTPMultiplier, _Digits);
         if(Trade.Buy(secondLot, _Symbol, ask, stopLoss, secondTP, "MACD背离做多2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
         }
         else
         {
            Print("买入订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "MACD背离做多"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
      }
      else
      {
         Print("买入订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForLong = false;
   peakCount = 0;
}

//+------------------------------------------------------------------+
//| 下空单                                                         |
//+------------------------------------------------------------------+
void PlaceShortOrder()
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double atrValue = atrValues * ATRMultiplier;
   double stopLoss = NormalizeDouble(hBarHigh + atrValue, _Digits);
   double stopLossPoints = (stopLoss - bid) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints) stopLoss = NormalizeDouble(bid + MinStopLossPoints * _Point, _Digits);
   if(stopLossPoints > MaxStopLossPoints) stopLoss = NormalizeDouble(bid + MaxStopLossPoints * _Point, _Digits);
   
   double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize((stopLoss - bid) / _Point);
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(bid - (stopLoss - bid), _Digits);
         if(Trade.Sell(firstLot, _Symbol, bid, stopLoss, firstTP, "MACD背离做空1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
         }
         else
         {
            Print("卖出订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(bid - (stopLoss - bid) * SecondTPMultiplier, _Digits);
         if(Trade.Sell(secondLot, _Symbol, bid, stopLoss, secondTP, "MACD背离做空2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
         }
         else
         {
            Print("卖出订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "MACD背离做空"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
      }
      else
      {
         Print("卖出订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForShort = false;
   peakCount = 0;
}

//+------------------------------------------------------------------+
//| 管理移动止损                                                   |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double currentStop = PositionGetDouble(POSITION_SL);
            double newStop = 0;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               newStop = NormalizeDouble(bid - TrailingStopPoints * _Point, _Digits);
               
               if((newStop > currentStop) || (currentStop == 0))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
               }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               newStop = NormalizeDouble(ask + TrailingStopPoints * _Point, _Digits);
               
               if((newStop < currentStop) || (currentStop == 0))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查部分获利平仓                                                 |
//+------------------------------------------------------------------+
void CheckPartialProfit()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double profitInPips = 0;
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
               profitInPips = (SymbolInfoDouble(_Symbol, SYMBOL_BID) - PositionGetDouble(POSITION_PRICE_OPEN)) / _Point;
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
               profitInPips = (PositionGetDouble(POSITION_PRICE_OPEN) - SymbolInfoDouble(_Symbol, SYMBOL_ASK)) / _Point;
            
            double stopLossPips = MathAbs(PositionGetDouble(POSITION_PRICE_OPEN) - PositionGetDouble(POSITION_SL)) / _Point;
            
            // 检查是否达到第一个目标(1:1)
            if(profitInPips >= stopLossPips && StringFind(PositionGetString(POSITION_COMMENT), " 1") < 0)
            {
               double volumeToClose = NormalizeDouble(PositionGetDouble(POSITION_VOLUME) * PartialProfitRatio, 2);
               if(volumeToClose > 0)
               {
                  if(!Trade.PositionClosePartial(ticket, volumeToClose))
                  {
                     Print("部分平仓失败: ", Trade.ResultRetcodeDescription());
                  }
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查相反信号并平仓                                             |
//+------------------------------------------------------------------+
void CheckOppositeSignal()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            bool shouldClose = false;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && lookingForShort)
               shouldClose = true;
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && lookingForLong)
               shouldClose = true;
            
            if(shouldClose)
            {
               if(!Trade.PositionClose(ticket))
               {
                  Print("平仓失败: ", Trade.ResultRetcodeDescription());
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 关闭所有仓位                                                   |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            if(!Trade.PositionClose(ticket))
            {
               Print("平仓失败: ", Trade.ResultRetcodeDescription());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 更新每日盈亏统计                                                 |
//+------------------------------------------------------------------+
void UpdateDailyProfit()
{
   datetime currentDay = iTime(_Symbol, PERIOD_D1, 0);
   if(currentDay != lastDailyCheck)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
   }
   
   // 计算当日盈亏
   dailyProfitLoss = 0;
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            MqlDateTime posTime, currTime;
            TimeToStruct((datetime)PositionGetInteger(POSITION_TIME), posTime);
            TimeToStruct(currentDay, currTime);
            
            if(posTime.day == currTime.day && posTime.mon == currTime.mon && posTime.year == currTime.year)
            {
               dailyProfitLoss += PositionGetDouble(POSITION_PROFIT);
            }
         }
      }
   }
   
   // 添加当日已平仓的盈亏
   HistorySelect(lastDailyCheck, TimeCurrent());
   for(int i = HistoryDealsTotal()-1; i >= 0; i--)
   {
      ulong dealTicket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) == MagicNumber &&
         HistoryDealGetString(dealTicket, DEAL_SYMBOL) == _Symbol)
      {
         MqlDateTime dealTime, currTime;
         TimeToStruct((datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME), dealTime);
         TimeToStruct(currentDay, currTime);
         
         if(dealTime.day == currTime.day && dealTime.mon == currTime.mon && dealTime.year == currTime.year)
         {
            dailyProfitLoss += HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 专家报价处理函数                                                 |
//+------------------------------------------------------------------+
void OnTick()
{
   // 检查新K线
   static datetime lastBarTime;
   datetime currentBarTime = iTime(_Symbol, TimeFrame, 0);
   if(currentBarTime == lastBarTime) return;
   lastBarTime = currentBarTime;
   
   // 检查每日限制
   if(ExceededDailyLimits()) return;
   
   // 检查交易时间
   if(!IsTradingTime()) return;
   
   // 获取指标值
   GetMACDValues();
   
   // 检查背离形态
   CheckDivergence();
   
   // 检查入场条件
   if((lookingForLong || lookingForShort) && currentBarTime != lastTradeTime)
   {
      // 附加过滤器
      if(UseTrendFilter)
      {
         double maValue;
         if(CopyBuffer(trendMAHandle, 0, 0, 2, maValue) < 2)
         {
            Print("获取趋势MA数据失败!");
            return;
         }
         if(lookingForLong && rates.close < maValue) lookingForLong = false;
         if(lookingForShort && rates.close > maValue) lookingForShort = false;
      }
      
      if(UseHigherTFConfirmation)
      {
         if(lookingForLong && !IsHigherTFTrendBullish()) lookingForLong = false;
         if(lookingForShort && IsHigherTFTrendBullish()) lookingForShort = false;
      }
      
      // 如果通过过滤器则下单
      if(lookingForLong) PlaceLongOrder();
      if(lookingForShort) PlaceShortOrder();
   }
   
   // 移动止损管理
   if(UseTrailingStop) ManageTrailingStop();
   
   // 部分获利平仓
   if(UsePartialProfit) CheckPartialProfit();
   
   // 出现相反信号时平仓
   if(CloseOnOppositeSignal) CheckOppositeSignal();
}

//+------------------------------------------------------------------+
//| 专家订单处理函数                                                 |
//+------------------------------------------------------------------+
void OnTrade()
{
   UpdateDailyProfit();
}

//+------------------------------------------------------------------+
//| 图表事件处理函数                                                 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   // 处理按钮点击事件
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == "BuyButton")
      {
         // 手动买入
         double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
         double atrValue = atrValues * ATRMultiplier;
         double stopLoss = NormalizeDouble(ask - atrValue, _Digits);
         double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
         double lotSize = CalculateLotSize((ask - stopLoss) / _Point);
         
         if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "手动做多"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
         }
      }
      else if(sparam == "SellButton")
      {
         // 手动卖出
         double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         double atrValue = atrValues * ATRMultiplier;
         double stopLoss = NormalizeDouble(bid + atrValue, _Digits);
         double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
         double lotSize = CalculateLotSize((stopLoss - bid) / _Point);
         
         if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "手动做空"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
         }
      }
      else if(sparam == "PauseButton")
      {
         // 暂停/恢复交易
         TradingPaused = !TradingPaused;
         if(TradingPaused)
         {
            PauseButton.Description("恢复交易");
            PauseButton.BackColor(clrDarkOrange);
            Print("EA交易已暂停");
         }
         else
         {
            PauseButton.Description("暂停交易");
            PauseButton.BackColor(clrDarkGoldenrod);
            Print("EA交易已恢复");
         }
      }
      else if(sparam == "CloseAllButton")
      {
         // 清仓
         CloseAllPositions();
         Print("已平仓所有头寸");
      }
   }
   
   // 处理图表大小变化事件,调整按钮位置
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
      
      int buttonWidth = 80;
      int buttonHeight = 30;
      int buttonSpacing = 10;
      
      int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
      int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
      
      BuyButton.X_Distance(xPos);
      BuyButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      SellButton.X_Distance(xPos);
      SellButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      PauseButton.X_Distance(xPos);
      PauseButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      CloseAllButton.X_Distance(xPos);
      CloseAllButton.Y_Distance(yPos);
   }
}
//+------------------------------------------------------------------+

bzcsx 发表于 2025-5-29 13:42:59

//+------------------------------------------------------------------+
//|                                    Enhanced_MACD_Divergence_EA |
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "3.10"
#property strict
#property description "增强版MACD背离交易系统(带ATR止损)"
#property description "做多条件: MACD柱状图低于零轴且峰值上升 + 价格低点下降"
#property description "做空条件: MACD柱状图高于零轴且峰值下降 + 价格高点上升"
#property description "包含多重过滤条件和风险管理选项"

#include <Trade\Trade.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

//+------------------------------------------------------------------+
//| 输入参数                                                         |
//+------------------------------------------------------------------+
//--- MACD核心参数
input int FastEMA = 12;                  // MACD快线周期
input int SlowEMA = 26;                  // MACD慢线周期
input int SignalPeriod = 9;            // MACD信号线周期
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // 交易时间框架
input ulong MagicNumber = 12345;         // EA魔术码

//--- 风险管理
input double RiskPercent = 1.0;          // 每笔交易风险百分比
input double FixedLotSize = 0.1;         // 固定交易手数(当RiskPercent=0时使用)
input double RiskRewardRatio = 2.0;      // 风险回报比
input int ATRPeriod = 14;                // ATR止损周期
input double ATRMultiplier = 1.8;      // ATR止损倍数
input bool UseTrailingStop = true;       // 启用移动止损
input int TrailingStopPoints = 100;      // 移动止损点数
input int MinStopLossPoints = 50;      // 最小止损点数
input int MaxStopLossPoints = 300;       // 最大止损点数

//--- 交易过滤器
input bool UseTrendFilter = true;      // 启用趋势MA过滤
input int TrendMAPeriod = 200;         // 趋势MA周期
input ENUM_MA_METHOD TrendMAMethod = MODE_EMA; // 趋势MA计算方法
input bool UseHigherTFConfirmation = true; // 启用更高时间框架确认
input ENUM_TIMEFRAMES HigherTF = PERIOD_D1; // 更高时间框架
input bool UseTimeFilter = false;      // 启用交易时间过滤
input string TradeStartTime = "00:00";   // 交易开始时间(服务器时间)
input string TradeEndTime = "23:59";   // 交易结束时间(服务器时间)
input int MinPeakCount = 2;            // 背离所需最小峰值数量
input int MaxPeakCount = 3;            // 考虑的最大峰值数量

//--- 出场策略
input bool UsePartialProfit = true;      // 启用部分平仓获利
input double PartialProfitRatio = 0.5;   // 1:1时平仓比例
input double SecondTPMultiplier = 3.0;   // 剩余仓位的目标倍数
input bool CloseOnOppositeSignal = true; // 出现相反信号时平仓

//--- 资金管理
input double MaxRiskPerTrade = 0.05;   // 每笔交易最大风险(0.05=5%)
input double MaxDailyLossPercent = 2.0;// 每日最大亏损百分比
input int MaxTradesPerDay = 3;         // 每日最大交易次数

//--- 调试选项
input bool EnableDebug = true;         // 启用调试输出

//+------------------------------------------------------------------+
//| 全局变量                                                         |
//+------------------------------------------------------------------+
double macdMain[], macdSignal[], macdHistogram[], atrValues[];
datetime lastTradeTime, lastDailyCheck;
int peakCount = 0;
double lastPeakValue = 0;
double firstPeakValue = 0;
double firstPeakPrice = 0;
double secondPeakValue = 0;
double secondPeakPrice = 0;
bool lookingForLong = false;
bool lookingForShort = false;
double lBarLow = 0;
double hBarHigh = 0;
double dailyProfitLoss = 0;
int dailyTradeCount = 0;
int macdHandle, atrHandle, trendMAHandle, higherTFMAHandle;
MqlRates rates[];
CTrade Trade;
bool TradingPaused = false;

// 控制按钮
CChartObjectButton BuyButton;
CChartObjectButton SellButton;
CChartObjectButton PauseButton;
CChartObjectButton CloseAllButton;

//+------------------------------------------------------------------+
//| 专家初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // 初始化指标句柄
   macdHandle = iMACD(_Symbol, TimeFrame, FastEMA, SlowEMA, SignalPeriod, PRICE_CLOSE);
   atrHandle = iATR(_Symbol, TimeFrame, ATRPeriod);
   trendMAHandle = iMA(_Symbol, TimeFrame, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   if(UseHigherTFConfirmation)
      higherTFMAHandle = iMA(_Symbol, HigherTF, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   // 检查参数设置是否有效
   if(RiskPercent <= 0 && FixedLotSize <= 0)
   {
      Alert("风险管理参数配置不正确!");
      return(INIT_PARAMETERS_INCORRECT);
   }
   
   // 重置每日统计
   ResetDailyStats();
   
   // 设置交易对象的魔术码
   Trade.SetExpertMagicNumber(MagicNumber);
   
   // 创建控制按钮
   CreateButtons();
   
   if(EnableDebug) Print("EA初始化完成,开始运行...");
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 创建控制按钮函数                                                 |
//+------------------------------------------------------------------+
void CreateButtons()
{
   // 获取图表宽度和高度
   long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   
   // 按钮尺寸
   int buttonWidth = 80;
   int buttonHeight = 30;
   int buttonSpacing = 10;
   
   // 计算按钮位置(右下角)
   int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
   int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
   
   // 创建买入按钮
   if(!BuyButton.Create(0, "BuyButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建买入按钮失败!");
   BuyButton.Description("买入");
   BuyButton.Color(clrWhite);
   BuyButton.BackColor(clrDarkGreen);
   BuyButton.BorderColor(clrWhite);
   BuyButton.FontSize(10);
   BuyButton.State(false);
   
   // 创建卖出按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!SellButton.Create(0, "SellButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建卖出按钮失败!");
   SellButton.Description("卖出");
   SellButton.Color(clrWhite);
   SellButton.BackColor(clrDarkRed);
   SellButton.BorderColor(clrWhite);
   SellButton.FontSize(10);
   SellButton.State(false);
   
   // 创建暂停加仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!PauseButton.Create(0, "PauseButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建暂停按钮失败!");
   PauseButton.Description("暂停加仓");
   PauseButton.Color(clrWhite);
   PauseButton.BackColor(clrDarkOrange);
   PauseButton.BorderColor(clrWhite);
   PauseButton.FontSize(10);
   PauseButton.State(false);
   
   // 创建清仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!CloseAllButton.Create(0, "CloseAllButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建清仓按钮失败!");
   CloseAllButton.Description("清仓");
   CloseAllButton.Color(clrWhite);
   CloseAllButton.BackColor(clrDarkSlateGray);
   CloseAllButton.BorderColor(clrWhite);
   CloseAllButton.FontSize(10);
   CloseAllButton.State(false);
   
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| 重置每日统计数据                                                 |
//+------------------------------------------------------------------+
void ResetDailyStats()
{
   lastDailyCheck = iTime(_Symbol, PERIOD_D1, 0);
   dailyProfitLoss = 0;
   dailyTradeCount = 0;
   if(EnableDebug) Print("每日统计数据已重置");
}

//+------------------------------------------------------------------+
//| 专家逆初始化函数                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 释放指标句柄
   IndicatorRelease(macdHandle);
   IndicatorRelease(atrHandle);
   IndicatorRelease(trendMAHandle);
   if(UseHigherTFConfirmation) IndicatorRelease(higherTFMAHandle);
   
   // 删除按钮
   BuyButton.Delete();
   SellButton.Delete();
   PauseButton.Delete();
   CloseAllButton.Delete();
   
   if(EnableDebug) Print("EA已卸载");
}

//+------------------------------------------------------------------+
//| 获取MACD指标值                                                   |
//+------------------------------------------------------------------+
bool GetMACDValues()
{
   int barsNeeded = MaxPeakCount + 5;
   ArrayResize(macdMain, barsNeeded);
   ArrayResize(macdSignal, barsNeeded);
   ArrayResize(macdHistogram, barsNeeded);
   ArrayResize(atrValues, barsNeeded);
   ArrayResize(rates, barsNeeded);
   
   // 获取价格数据
   if(CopyRates(_Symbol, TimeFrame, 0, barsNeeded, rates) < barsNeeded)
   {
      if(EnableDebug) Print("获取价格数据失败! 需要: ", barsNeeded, " 实际: ", ArraySize(rates));
      return false;
   }
   
   // 获取MACD值
   if(CopyBuffer(macdHandle, 0, 0, barsNeeded, macdMain) < barsNeeded ||
      CopyBuffer(macdHandle, 1, 0, barsNeeded, macdSignal) < barsNeeded)
   {
      if(EnableDebug) Print("获取MACD数据失败!");
      return false;
   }
   
   // 计算柱状图值
   for(int i = 0; i < barsNeeded; i++)
   {
      macdHistogram = macdMain - macdSignal;
   }
   
   // 获取ATR值
   if(CopyBuffer(atrHandle, 0, 0, barsNeeded, atrValues) < barsNeeded)
   {
      if(EnableDebug) Print("获取ATR数据失败!");
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 根据风险计算手数                                                 |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPoints)
{
   if(RiskPercent <= 0) return FixedLotSize;
   
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(tickValue == 0 || tickSize == 0) return FixedLotSize;
   
   double riskAmount = MathMin(AccountInfoDouble(ACCOUNT_EQUITY) * RiskPercent / 100,
                              AccountInfoDouble(ACCOUNT_EQUITY) * MaxRiskPerTrade);
   
   double pointValue = tickValue / (SymbolInfoDouble(_Symbol, SYMBOL_POINT) / tickSize);
   double lotSize = riskAmount / (stopLossPoints * pointValue);
   
   // 调整最小和最大手数限制
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   
   lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
   lotSize = NormalizeDouble(lotSize / step, 0) * step;
   
   return lotSize;
}

//+------------------------------------------------------------------+
//| 检查当前是否在交易时间内                                          |
//+------------------------------------------------------------------+
bool IsTradingTime()
{
   if(!UseTimeFilter) return true;
   
   datetime start = StringToTime(TradeStartTime);
   datetime end = StringToTime(TradeEndTime);
   datetime now = TimeCurrent();
   
   if(start == 0 || end == 0)
   {
      if(EnableDebug) Print("时间格式错误! 请使用HH:MM格式");
      return false;
   }
   
   // 处理隔夜交易时段
   if(start < end) return (now >= start && now <= end);
   else return (now >= start || now <= end);
}

//+------------------------------------------------------------------+
//| 检查更高时间框架趋势                                              |
//+------------------------------------------------------------------+
bool IsHigherTFTrendBullish()
{
   if(!UseHigherTFConfirmation) return true;
   
   double ma;
   if(CopyBuffer(higherTFMAHandle, 0, 0, 1, ma) < 1)
   {
      if(EnableDebug) Print("获取更高时间框架数据失败!");
      return true;
   }
   
   double close = iClose(_Symbol, HigherTF, 0);
   
   return close > ma;
}

//+------------------------------------------------------------------+
//| 检查是否超过每日限制                                              |
//+------------------------------------------------------------------+
bool ExceededDailyLimits()
{
   datetime currentDay = iTime(_Symbol, PERIOD_D1, 0);
   if(currentDay != lastDailyCheck)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
      return false;
   }
   
   if(dailyProfitLoss < 0 && MathAbs(dailyProfitLoss) >= AccountInfoDouble(ACCOUNT_EQUITY) * MaxDailyLossPercent / 100)
   {
      if(EnableDebug) Print("已达到每日亏损限额。今日停止交易。");
      return true;
   }
   
   if(dailyTradeCount >= MaxTradesPerDay)
   {
      if(EnableDebug) Print("已达到每日最大交易次数。今日停止交易。");
      return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| 检查背离形态                                                   |
//+------------------------------------------------------------------+
void CheckDivergence()
{
   // 重置标志
   lookingForLong = false;
   lookingForShort = false;
   
   // 如果交易暂停,则跳过信号检查
   if(TradingPaused)
   {
      if(EnableDebug) Print("交易已暂停,跳过信号检查");
      return;
   }
   
   // 检查做多条件(MACD低于零轴)
   if(macdHistogram < 0)
   {
      // 寻找最近的2-3个峰值
      int valleysFound = 0;
      double valleyValues = {0};
      double valleyPrices = {0};
      int valleyIndexes = {0};
      
      // 从最近的bar开始向前搜索
      for(int i = 3; i < ArraySize(macdHistogram) - 1; i++)
      {
         // 检查是否为谷底 (MACD柱状图低点)
         if(macdHistogram < macdHistogram && macdHistogram < macdHistogram)
         {
            valleyValues = macdHistogram;
            valleyPrices = rates.low;
            valleyIndexes = i;
            valleysFound++;
            
            if(valleysFound >= 3) break;
         }
      }
      
      // 检查是否有足够的谷底
      if(valleysFound >= 2)
      {
         // 检查价格是否创新低而MACD谷底升高
         if(valleyPrices < valleyPrices && valleyValues > valleyValues)
         {
            lookingForLong = true;
            lBarLow = rates.low;
            firstPeakValue = valleyValues;
            firstPeakPrice = valleyPrices;
            secondPeakValue = valleyValues;
            secondPeakPrice = valleyPrices;
            
            if(EnableDebug)
            {
               Print("检测到看涨背离:");
               Print("价格1: ", valleyPrices, " MACD1: ", valleyValues);
               Print("价格2: ", valleyPrices, " MACD2: ", valleyValues);
            }
         }
      }
   }
   
   // 检查做空条件(MACD高于零轴)
   if(macdHistogram > 0)
   {
      // 寻找最近的2-3个峰值
      int peaksFound = 0;
      double peakValues = {0};
      double peakPrices = {0};
      int peakIndexes = {0};
      
      // 从最近的bar开始向前搜索
      for(int i = 3; i < ArraySize(macdHistogram) - 1; i++)
      {
         // 检查是否为峰值 (MACD柱状图高点)
         if(macdHistogram > macdHistogram && macdHistogram > macdHistogram)
         {
            peakValues = macdHistogram;
            peakPrices = rates.high;
            peakIndexes = i;
            peaksFound++;
            
            if(peaksFound >= 3) break;
         }
      }
      
      // 检查是否有足够的峰值
      if(peaksFound >= 2)
      {
         // 检查价格是否创新高而MACD峰值降低
         if(peakPrices > peakPrices && peakValues < peakValues)
         {
            lookingForShort = true;
            hBarHigh = rates.high;
            firstPeakValue = peakValues;
            firstPeakPrice = peakPrices;
            secondPeakValue = peakValues;
            secondPeakPrice = peakPrices;
            
            if(EnableDebug)
            {
               Print("检测到看跌背离:");
               Print("价格1: ", peakPrices, " MACD1: ", peakValues);
               Print("价格2: ", peakPrices, " MACD2: ", peakValues);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 专家订单处理函数                                                 |
//+------------------------------------------------------------------+
void OnTrade()
{
   UpdateDailyProfit();
}

//+------------------------------------------------------------------+
//| 专家报价处理函数                                                 |
//+------------------------------------------------------------------+
void OnTick()
{
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, TimeFrame, 0);
   
   // 只在新的K线开始时处理
   if(currentBarTime == lastBarTime)
      return;
   
   lastBarTime = currentBarTime;
   
   if(EnableDebug) Print("新K线开始: ", TimeToString(currentBarTime));
   
   // 检查每日限制
   if(ExceededDailyLimits())
   {
      if(EnableDebug) Print("超过每日限制,跳过交易");
      return;
   }
   
   // 检查交易时间
   if(!IsTradingTime())
   {
      if(EnableDebug) Print("不在交易时间内,跳过交易");
      return;
   }
   
   // 获取指标值
   if(!GetMACDValues())
   {
      if(EnableDebug) Print("获取指标数据失败,跳过处理");
      return;
   }
   
   // 检查背离形态
   CheckDivergence();
   
   // 检查入场条件
   if((lookingForLong || lookingForShort) && currentBarTime != lastTradeTime)
   {
      if(EnableDebug) Print("检测到交易信号 - 长: ", lookingForLong, " 短: ", lookingForShort);
      
      // 附加过滤器
      if(UseTrendFilter)
      {
         double maValue;
         if(CopyBuffer(trendMAHandle, 0, 0, 1, maValue) < 1)
         {
            if(EnableDebug) Print("获取趋势MA数据失败!");
            return;
         }
         double currentClose = rates.close;
         
         if(lookingForLong && currentClose < maValue)
         {
            if(EnableDebug) Print("长信号被趋势过滤器拒绝");
            lookingForLong = false;
         }
         if(lookingForShort && currentClose > maValue)
         {
            if(EnableDebug) Print("短信号被趋势过滤器拒绝");
            lookingForShort = false;
         }
      }
      
      if(UseHigherTFConfirmation)
      {
         if(lookingForLong && !IsHigherTFTrendBullish())
         {
            if(EnableDebug) Print("长信号被更高时间框架过滤器拒绝");
            lookingForLong = false;
         }
         if(lookingForShort && IsHigherTFTrendBullish())
         {
            if(EnableDebug) Print("短信号被更高时间框架过滤器拒绝");
            lookingForShort = false;
         }
      }
      
      // 如果通过过滤器则下单
      if(lookingForLong)
      {
         PlaceLongOrder();
         if(EnableDebug) Print("执行长订单");
      }
      if(lookingForShort)
      {
         PlaceShortOrder();
         if(EnableDebug) Print("执行短订单");
      }
   }
   
   // 移动止损管理
   if(UseTrailingStop) ManageTrailingStop();
   
   // 部分获利平仓
   if(UsePartialProfit) CheckPartialProfit();
   
   // 出现相反信号时平仓
   if(CloseOnOppositeSignal) CheckOppositeSignal();
}

//+------------------------------------------------------------------+
//| 下多单                                                         |
//+------------------------------------------------------------------+
void PlaceLongOrder()
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double atrValue = atrValues * ATRMultiplier;
   double stopLoss = NormalizeDouble(lBarLow - atrValue, _Digits);
   double stopLossPoints = (ask - stopLoss) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints)
   {
      stopLoss = NormalizeDouble(ask - MinStopLossPoints * _Point, _Digits);
      stopLossPoints = MinStopLossPoints;
   }
   if(stopLossPoints > MaxStopLossPoints)
   {
      stopLoss = NormalizeDouble(ask - MaxStopLossPoints * _Point, _Digits);
      stopLossPoints = MaxStopLossPoints;
   }
   
   double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize(stopLossPoints);
   
   if(EnableDebug)
   {
      Print("尝试买入: ", lotSize, " 手");
      Print("进场价: ", ask, " 止损: ", stopLoss, " 止盈: ", takeProfit);
   }
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(ask + (ask - stopLoss), _Digits);
         if(Trade.Buy(firstLot, _Symbol, ask, stopLoss, firstTP, "MACD背离做多1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("买入订单1成功,手数: ", firstLot);
         }
         else
         {
            Print("买入订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(ask + (ask - stopLoss) * SecondTPMultiplier, _Digits);
         if(Trade.Buy(secondLot, _Symbol, ask, stopLoss, secondTP, "MACD背离做多2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("买入订单2成功,手数: ", secondLot);
         }
         else
         {
            Print("买入订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "MACD背离做多"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
         if(EnableDebug) Print("买入订单成功,手数: ", lotSize);
      }
      else
      {
         Print("买入订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForLong = false;
}

//+------------------------------------------------------------------+
//| 下空单                                                         |
//+------------------------------------------------------------------+
void PlaceShortOrder()
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double atrValue = atrValues * ATRMultiplier;
   double stopLoss = NormalizeDouble(hBarHigh + atrValue, _Digits);
   double stopLossPoints = (stopLoss - bid) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints)
   {
      stopLoss = NormalizeDouble(bid + MinStopLossPoints * _Point, _Digits);
      stopLossPoints = MinStopLossPoints;
   }
   if(stopLossPoints > MaxStopLossPoints)
   {
      stopLoss = NormalizeDouble(bid + MaxStopLossPoints * _Point, _Digits);
      stopLossPoints = MaxStopLossPoints;
   }
   
   double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize(stopLossPoints);
   
   if(EnableDebug)
   {
      Print("尝试卖出: ", lotSize, " 手");
      Print("进场价: ", bid, " 止损: ", stopLoss, " 止盈: ", takeProfit);
   }
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(bid - (stopLoss - bid), _Digits);
         if(Trade.Sell(firstLot, _Symbol, bid, stopLoss, firstTP, "MACD背离做空1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("卖出订单1成功,手数: ", firstLot);
         }
         else
         {
            Print("卖出订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(bid - (stopLoss - bid) * SecondTPMultiplier, _Digits);
         if(Trade.Sell(secondLot, _Symbol, bid, stopLoss, secondTP, "MACD背离做空2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("卖出订单2成功,手数: ", secondLot);
         }
         else
         {
            Print("卖出订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "MACD背离做空"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
         if(EnableDebug) Print("卖出订单成功,手数: ", lotSize);
      }
      else
      {
         Print("卖出订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForShort = false;
}

//+------------------------------------------------------------------+
//| 管理移动止损                                                   |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double currentStop = PositionGetDouble(POSITION_SL);
            double currentProfit = PositionGetDouble(POSITION_PROFIT);
            double newStop = 0;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               newStop = NormalizeDouble(bid - TrailingStopPoints * _Point, _Digits);
               
               // 确保新的止损高于当前止损且不会造成亏损
               if((newStop > currentStop) && (newStop > PositionGetDouble(POSITION_PRICE_OPEN)))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     if(EnableDebug) Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
                  else if(EnableDebug) Print("多头止损更新为: ", newStop);
               }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               newStop = NormalizeDouble(ask + TrailingStopPoints * _Point, _Digits);
               
               // 确保新的止损低于当前止损且不会造成亏损
               if((newStop < currentStop) && (newStop < PositionGetDouble(POSITION_PRICE_OPEN)))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     if(EnableDebug) Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
                  else if(EnableDebug) Print("空头止损更新为: ", newStop);
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查部分获利平仓                                                 |
//+------------------------------------------------------------------+
void CheckPartialProfit()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double profitInPips = 0;
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double stopLoss = PositionGetDouble(POSITION_SL);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               profitInPips = (bid - openPrice) / _Point;
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               profitInPips = (openPrice - ask) / _Point;
            }
            
            double stopLossPips = MathAbs(openPrice - stopLoss) / _Point;
            
            // 检查是否达到第一个目标(1:1)
            if(profitInPips >= stopLossPips && StringFind(PositionGetString(POSITION_COMMENT), " 1") < 0)
            {
               double volume = PositionGetDouble(POSITION_VOLUME);
               double volumeToClose = NormalizeDouble(volume * PartialProfitRatio, 2);
               
               if(volumeToClose > 0)
               {
                  if(!Trade.PositionClosePartial(ticket, volumeToClose))
                  {
                     if(EnableDebug) Print("部分平仓失败: ", Trade.ResultRetcodeDescription());
                  }
                  else
                  {
                     if(EnableDebug) Print("部分平仓成功: ", volumeToClose, " 手");
                  }
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查相反信号并平仓                                             |
//+------------------------------------------------------------------+
void CheckOppositeSignal()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            bool shouldClose = false;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && lookingForShort)
               shouldClose = true;
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && lookingForLong)
               shouldClose = true;
            
            if(shouldClose)
            {
               if(!Trade.PositionClose(ticket))
               {
                  if(EnableDebug) Print("平仓失败: ", Trade.ResultRetcodeDescription());
               }
               else
               {
                  if(EnableDebug) Print("因相反信号平仓: ", ticket);
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 关闭所有仓位                                                   |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
   int count = 0;
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            if(Trade.PositionClose(ticket))
            {
               count++;
               if(EnableDebug) Print("平仓成功: ", ticket);
            }
            else
            {
               if(EnableDebug) Print("平仓失败: ", Trade.ResultRetcodeDescription());
            }
         }
      }
   }
   if(EnableDebug) Print("已平仓所有头寸,数量: ", count);
}

//+------------------------------------------------------------------+
//| 更新每日盈亏统计                                                 |
//+------------------------------------------------------------------+
void UpdateDailyProfit()
{
   datetime currentDay = iTime(_Symbol, PERIOD_D1, 0);
   if(currentDay != lastDailyCheck)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
   }
   
   // 计算当日盈亏
   dailyProfitLoss = 0;
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            MqlDateTime posTime, currTime;
            TimeToStruct((datetime)PositionGetInteger(POSITION_TIME), posTime);
            TimeToStruct(currentDay, currTime);
            
            if(posTime.day == currTime.day && posTime.mon == currTime.mon && posTime.year == currTime.year)
            {
               dailyProfitLoss += PositionGetDouble(POSITION_PROFIT);
            }
         }
      }
   }
   
   // 添加当日已平仓的盈亏
   HistorySelect(lastDailyCheck, TimeCurrent());
   for(int i = HistoryDealsTotal()-1; i >= 0; i--)
   {
      ulong dealTicket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) == MagicNumber &&
         HistoryDealGetString(dealTicket, DEAL_SYMBOL) == _Symbol)
      {
         MqlDateTime dealTime, currTime;
         TimeToStruct((datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME), dealTime);
         TimeToStruct(currentDay, currTime);
         
         if(dealTime.day == currTime.day && dealTime.mon == currTime.mon && dealTime.year == currTime.year)
         {
            dailyProfitLoss += HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 图表事件处理函数                                                 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   // 处理按钮点击事件
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == "BuyButton")
      {
         // 手动买入
         double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
         double atrValue = atrValues * ATRMultiplier;
         double stopLoss = NormalizeDouble(ask - atrValue, _Digits);
         double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
         double lotSize = CalculateLotSize((ask - stopLoss) / _Point);
         
         if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "手动买入"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("手动买入成功: ", lotSize, " 手");
         }
      }
      else if(sparam == "SellButton")
      {
         // 手动卖出
         double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         double atrValue = atrValues * ATRMultiplier;
         double stopLoss = NormalizeDouble(bid + atrValue, _Digits);
         double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
         double lotSize = CalculateLotSize((stopLoss - bid) / _Point);
         
         if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "手动卖出"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("手动卖出成功: ", lotSize, " 手");
         }
      }
      else if(sparam == "PauseButton")
      {
         // 暂停/恢复加仓
         TradingPaused = !TradingPaused;
         if(TradingPaused)
         {
            PauseButton.Description("恢复加仓");
            PauseButton.BackColor(clrDarkSlateBlue);
            if(EnableDebug) Print("EA已暂停自动加仓");
         }
         else
         {
            PauseButton.Description("暂停加仓");
            PauseButton.BackColor(clrDarkOrange);
            if(EnableDebug) Print("EA已恢复自动加仓");
         }
         ChartRedraw();
      }
      else if(sparam == "CloseAllButton")
      {
         // 清仓
         CloseAllPositions();
         if(EnableDebug) Print("已平仓所有头寸");
      }
   }
   
   // 处理图表大小变化事件,调整按钮位置
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
      
      int buttonWidth = 80;
      int buttonHeight = 30;
      int buttonSpacing = 10;
      
      int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
      int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
      
      BuyButton.X_Distance(xPos);
      BuyButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      SellButton.X_Distance(xPos);
      SellButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      PauseButton.X_Distance(xPos);
      PauseButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      CloseAllButton.X_Distance(xPos);
      CloseAllButton.Y_Distance(yPos);
      
      ChartRedraw();
   }
}

bzcsx 发表于 2025-5-29 13:45:14

bzcsx 发表于 2025-6-2 11:27:07

//+------------------------------------------------------------------+
//|                                     Enhanced_MACD_Divergence_EA|
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "3.23"
#property strict
#property description "增强版MACD背离交易系统(带ATR止损)"
#property description "做多条件: MACD柱状图低于零轴且峰值上升 + 价格低点下降"
#property description "做空条件: MACD柱状图高于零轴且峰值下降 + 价格高点上升"
#property description "包含多重过滤条件和风险管理选项"

#include <Trade\Trade.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

//+------------------------------------------------------------------+
//| 输入参数                                                         |
//+------------------------------------------------------------------+
//--- MACD核心参数
input int FastEMA = 12;                  // MACD快线周期
input int SlowEMA = 26;                  // MACD慢线周期
input int SignalPeriod = 9;            // MACD信号线周期
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // 交易时间框架
input ulong MagicNumber = 12345;         // EA魔术码

//--- 风险管理
input double RiskPercent = 1.0;          // 每笔交易风险百分比
input double FixedLotSize = 0.1;         // 固定交易手数(当RiskPercent=0时使用)
input double RiskRewardRatio = 2.0;      // 风险回报比
input int ATRPeriod = 14;                // ATR止损周期
input double ATRMultiplier = 1.5;      // ATR止损倍数(从1.8降低到1.5)
input bool UseTrailingStop = true;       // 启用移动止损
input int TrailingStopPoints = 100;      // 移动止损点数
input int MinStopLossPoints = 30;      // 最小止损点数(从50降低到30)
input int MaxStopLossPoints = 300;       // 最大止损点数

//--- 交易过滤器
input bool UseTrendFilter = false;       // 启用趋势MA过滤(默认禁用)
input int TrendMAPeriod = 200;         // 趋势MA周期
input ENUM_MA_METHOD TrendMAMethod = MODE_EMA; // 趋势MA计算方法
input bool UseHigherTFConfirmation = false; // 启用更高时间框架确认(默认禁用)
input ENUM_TIMEFRAMES HigherTF = PERIOD_D1; // 更高时间框架
input bool UseTimeFilter = false;      // 启用交易时间过滤
input string TradeStartTime = "00:00";   // 交易开始时间(服务器时间)
input string TradeEndTime = "23:59";   // 交易结束时间(服务器时间)
input int MinPeakCount = 2;            // 背离所需最小峰值数量
input int MaxPeakCount = 3;            // 考虑的最大峰值数量

//--- 出场策略
input bool UsePartialProfit = true;      // 启用部分平仓获利
input double PartialProfitRatio = 0.5;   // 1:1时平仓比例
input double SecondTPMultiplier = 3.0;   // 剩余仓位的目标倍数
input bool CloseOnOppositeSignal = true; // 出现相反信号时平仓

//--- 资金管理
input double MaxRiskPerTrade = 0.05;   // 每笔交易最大风险(0.05=5%)
input double MaxDailyLossPercent = 5.0;// 每日最大亏损百分比(从2%提高到5%)
input int MaxTradesPerDay = 5;         // 每日最大交易次数(从3提高到5)

//--- 调试选项
input bool EnableDebug = true;         // 启用调试输出
input bool VisualMode = true;            // 在图表上显示标记

//+------------------------------------------------------------------+
//| 全局变量                                                         |
//+------------------------------------------------------------------+
double macdMain[], macdSignal[], macdHistogram[], atrValues[];
double trendMAValues[], higherTFMAValues[];
MqlRates rates[];
datetime lastTradeTime, lastDailyCheck;
int peakCount = 0;
double lastPeakValue = 0;
double firstPeakValue = 0;
double firstPeakPrice = 0;
double secondPeakValue = 0;
double secondPeakPrice = 0;
bool lookingForLong = false;
bool lookingForShort = false;
double lBarLow = 0;
double hBarHigh = 0;
double dailyProfitLoss = 0;
int dailyTradeCount = 0;
int macdHandle = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
int trendMAHandle = INVALID_HANDLE;
int higherTFMAHandle = INVALID_HANDLE;
CTrade Trade;
bool TradingPaused = false;
bool SignalAlerted = false;

// 控制按钮
CChartObjectButton BuyButton;
CChartObjectButton SellButton;
CChartObjectButton PauseButton;
CChartObjectButton CloseAllButton;

//+------------------------------------------------------------------+
//| 创建控制按钮函数                                                 |
//+------------------------------------------------------------------+
void CreateButtons()
{
   // 获取图表宽度和高度
   long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   
   // 按钮尺寸
   int buttonWidth = 80;
   int buttonHeight = 30;
   int buttonSpacing = 10;
   
   // 计算按钮位置(右下角)
   int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
   int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
   
   // 创建买入按钮
   if(!BuyButton.Create(0, "BuyButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建买入按钮失败!");
   BuyButton.Description("买入");
   BuyButton.Color(clrWhite);
   BuyButton.BackColor(clrDarkGreen);
   BuyButton.BorderColor(clrWhite);
   BuyButton.FontSize(10);
   BuyButton.State(false);
   
   // 创建卖出按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!SellButton.Create(0, "SellButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建卖出按钮失败!");
   SellButton.Description("卖出");
   SellButton.Color(clrWhite);
   SellButton.BackColor(clrDarkRed);
   SellButton.BorderColor(clrWhite);
   SellButton.FontSize(10);
   SellButton.State(false);
   
   // 创建暂停加仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!PauseButton.Create(0, "PauseButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建暂停按钮失败!");
   PauseButton.Description("暂停加仓");
   PauseButton.Color(clrWhite);
   PauseButton.BackColor(clrDarkOrange);
   PauseButton.BorderColor(clrWhite);
   PauseButton.FontSize(10);
   PauseButton.State(false);
   
   // 创建清仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!CloseAllButton.Create(0, "CloseAllButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建清仓按钮失败!");
   CloseAllButton.Description("清仓");
   CloseAllButton.Color(clrWhite);
   CloseAllButton.BackColor(clrDarkSlateGray);
   CloseAllButton.BorderColor(clrWhite);
   CloseAllButton.FontSize(10);
   CloseAllButton.State(false);
   
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| 重置每日统计数据                                                 |
//+------------------------------------------------------------------+
void ResetDailyStats()
{
   MqlDateTime today;
   TimeCurrent(today);
   today.hour = 0;
   today.min = 0;
   today.sec = 0;
   lastDailyCheck = StructToTime(today);
   dailyProfitLoss = 0;
   dailyTradeCount = 0;
   if(EnableDebug) Print("每日统计数据已重置");
}

//+------------------------------------------------------------------+
//| 专家初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // 初始化指标句柄
   macdHandle = iMACD(_Symbol, TimeFrame, FastEMA, SlowEMA, SignalPeriod, PRICE_CLOSE);
   atrHandle = iATR(_Symbol, TimeFrame, ATRPeriod);
   
   if(UseTrendFilter)
      trendMAHandle = iMA(_Symbol, TimeFrame, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   if(UseHigherTFConfirmation)
      higherTFMAHandle = iMA(_Symbol, HigherTF, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   // 检查参数设置是否有效
   if(RiskPercent <= 0 && FixedLotSize <= 0)
   {
      Alert("风险管理参数配置不正确!");
      return(INIT_PARAMETERS_INCORRECT);
   }
   
   // 重置每日统计
   ResetDailyStats();
   
   // 设置交易对象的魔术码
   Trade.SetExpertMagicNumber(MagicNumber);
   
   // 创建控制按钮
   CreateButtons();
   
   if(EnableDebug) Print("EA初始化完成,开始运行...");
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 专家逆初始化函数                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 释放指标句柄
   if(macdHandle != INVALID_HANDLE) IndicatorRelease(macdHandle);
   if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
   if(trendMAHandle != INVALID_HANDLE) IndicatorRelease(trendMAHandle);
   if(UseHigherTFConfirmation && higherTFMAHandle != INVALID_HANDLE) IndicatorRelease(higherTFMAHandle);
   
   // 删除按钮
   BuyButton.Delete();
   SellButton.Delete();
   PauseButton.Delete();
   CloseAllButton.Delete();
   
   // 删除所有图形对象
   ObjectsDeleteAll(0, "DIVERGENCE_");
   
   if(EnableDebug) Print("EA已卸载");
}

//+------------------------------------------------------------------+
//| 获取指标数据                                                   |
//+------------------------------------------------------------------+
bool GetIndicatorValues()
{
   int barsNeeded = 100; // 确保足够的历史数据
   
   // 获取价格数据
   if(CopyRates(_Symbol, TimeFrame, 0, barsNeeded, rates) < barsNeeded)
   {
      if(EnableDebug) Print("获取价格数据失败! 需要: ", barsNeeded, " 实际: ", ArraySize(rates));
      return false;
   }
   
   // 获取MACD值
   ArrayResize(macdMain, barsNeeded);
   ArrayResize(macdSignal, barsNeeded);
   ArrayResize(macdHistogram, barsNeeded);
   
   if(CopyBuffer(macdHandle, 0, 0, barsNeeded, macdMain) < barsNeeded)
   {
      if(EnableDebug) Print("获取MACD主数据失败!");
      return false;
   }
   
   if(CopyBuffer(macdHandle, 1, 0, barsNeeded, macdSignal) < barsNeeded)
   {
      if(EnableDebug) Print("获取MACD信号数据失败!");
      return false;
   }
   
   // 计算柱状图值
   for(int i = 0; i < barsNeeded; i++)
   {
      macdHistogram = macdMain - macdSignal;
   }
   
   // 获取ATR值
   ArrayResize(atrValues, barsNeeded);
   if(CopyBuffer(atrHandle, 0, 0, barsNeeded, atrValues) < barsNeeded)
   {
      if(EnableDebug) Print("获取ATR数据失败!");
      return false;
   }
   
   // 获取趋势MA值
   if(UseTrendFilter)
   {
      ArrayResize(trendMAValues, barsNeeded);
      if(CopyBuffer(trendMAHandle, 0, 0, barsNeeded, trendMAValues) < barsNeeded)
      {
         if(EnableDebug) Print("获取趋势MA数据失败!");
         return false;
      }
   }
   
   // 获取更高时间框架MA值
   if(UseHigherTFConfirmation)
   {
      ArrayResize(higherTFMAValues, barsNeeded);
      if(CopyBuffer(higherTFMAHandle, 0, 0, barsNeeded, higherTFMAValues) < barsNeeded)
      {
         if(EnableDebug) Print("获取更高时间框架MA数据失败!");
         return false;
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 根据风险计算手数                                                 |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPoints)
{
   if(RiskPercent <= 0) return FixedLotSize;
   
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double pointValue = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(tickValue <= 0 || tickSize <= 0 || pointValue <= 0)
   {
      if(EnableDebug) Print("无法获取点值信息,使用固定手数");
      return FixedLotSize;
   }
   
   double riskAmount = MathMin(AccountInfoDouble(ACCOUNT_EQUITY) * RiskPercent / 100,
                              AccountInfoDouble(ACCOUNT_EQUITY) * MaxRiskPerTrade);
   
   double lotSize = riskAmount / (stopLossPoints * tickValue * pointValue);
   
   // 调整最小和最大手数限制
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   
   lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
   lotSize = NormalizeDouble(lotSize, 2);
   
   if(EnableDebug) Print("计算手数: ", lotSize, " SL点数: ", stopLossPoints, " 风险金额: ", riskAmount);
   return lotSize;
}

//+------------------------------------------------------------------+
//| 检查当前是否在交易时间内                                          |
//+------------------------------------------------------------------+
bool IsTradingTime()
{
   if(!UseTimeFilter) return true;
   
   datetime start = StringToTime(TradeStartTime);
   datetime end = StringToTime(TradeEndTime);
   datetime now = TimeCurrent();
   
   if(start == 0 || end == 0)
   {
      if(EnableDebug) Print("时间格式错误! 请使用HH:MM格式");
      return false;
   }
   
   // 处理隔夜交易时段
   if(start < end) return (now >= start && now <= end);
   else return (now >= start || now <= end);
}

//+------------------------------------------------------------------+
//| 检查更高时间框架趋势                                              |
//+------------------------------------------------------------------+
bool IsHigherTFTrendBullish()
{
   if(!UseHigherTFConfirmation) return true;
   
   double close = iClose(_Symbol, HigherTF, 0);
   double ma;
   if(CopyBuffer(higherTFMAHandle, 0, 0, 1, ma) < 1)
   {
      if(EnableDebug) Print("获取更高时间框架MA数据失败!");
      return true;
   }
   
   return close > ma;
}

//+------------------------------------------------------------------+
//| 检查是否超过每日限制                                              |
//+------------------------------------------------------------------+
bool ExceededDailyLimits()
{
   MqlDateTime today, last;
   TimeCurrent(today);
   TimeToStruct(lastDailyCheck, last);
   
   if(today.day != last.day || today.mon != last.mon || today.year != last.year)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
      return false;
   }
   
   if(dailyProfitLoss < 0 && MathAbs(dailyProfitLoss) >= AccountInfoDouble(ACCOUNT_EQUITY) * MaxDailyLossPercent / 100)
   {
      if(EnableDebug) Print("已达到每日亏损限额。今日停止交易。");
      return true;
   }
   
   if(dailyTradeCount >= MaxTradesPerDay)
   {
      if(EnableDebug) Print("已达到每日最大交易次数。今日停止交易。");
      return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| 检查背离形态                                                   |
//+------------------------------------------------------------------+
void CheckDivergence()
{
   // 重置标志
   lookingForLong = false;
   lookingForShort = false;
   SignalAlerted = false;
   
   // 如果交易暂停,则跳过信号检查
   if(TradingPaused)
   {
      if(EnableDebug) Print("交易已暂停,跳过信号检查");
      return;
   }
   
   // 检查做多条件(MACD低于零轴)
   if(macdHistogram < 0)
   {
      // 寻找最近的2-3个谷底
      int valleysFound = 0;
      double valleyValues = {0};
      double valleyPrices = {0};
      int valleyIndexes = {0};
      
      // 从最近的bar开始向前搜索(索引0是最新)
      for(int i = 1; i < MathMin(50, ArraySize(macdHistogram)-1); i++)
      {
         // 检查是否为谷底 (MACD柱状图低点)
         if(macdHistogram < macdHistogram &&
            macdHistogram < macdHistogram)
         {
            valleyValues = macdHistogram;
            valleyPrices = rates.low;
            valleyIndexes = i;
            valleysFound++;
            
            if(valleysFound >= MaxPeakCount) break;
         }
      }
      
      // 检查是否有足够的谷底
      if(valleysFound >= MinPeakCount)
      {
         // 检查价格是否创新低而MACD谷底升高
         if(valleyPrices < valleyPrices && valleyValues > valleyValues)
         {
            lookingForLong = true;
            lBarLow = valleyPrices; // 使用最新谷底价格
            firstPeakValue = valleyValues;
            firstPeakPrice = valleyPrices;
            secondPeakValue = valleyValues;
            secondPeakPrice = valleyPrices;
            
            if(EnableDebug)
            {
               Print("检测到看涨背离:");
               Print("价格1: ", valleyPrices, " MACD1: ", valleyValues, " 索引: ", valleyIndexes);
               Print("价格2: ", valleyPrices, " MACD2: ", valleyValues, " 索引: ", valleyIndexes);
            }
            
            if(VisualMode)
            {
               // 在图表上标记背离
               string objName = "DIVERGENCE_LONG_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_BUY, 0, rates].time, valleyPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
               
               objName = "DIVERGENCE_LONG_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_BUY, 0, rates].time, valleyPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
            }
         }
      }
   }
   
   // 检查做空条件(MACD高于零轴)
   if(macdHistogram > 0)
   {
      // 寻找最近的2-3个峰值
      int peaksFound = 0;
      double peakValues = {0};
      double peakPrices = {0};
      int peakIndexes = {0};
      
      // 从最近的bar开始向前搜索
      for(int i = 1; i < MathMin(50, ArraySize(macdHistogram)-1); i++)
      {
         // 检查是否为峰值 (MACD柱状图高点)
         if(macdHistogram > macdHistogram &&
            macdHistogram > macdHistogram)
         {
            peakValues = macdHistogram;
            peakPrices = rates.high;
            peakIndexes = i;
            peaksFound++;
            
            if(peaksFound >= MaxPeakCount) break;
         }
      }
      
      // 检查是否有足够的峰值
      if(peaksFound >= MinPeakCount)
      {
         // 检查价格是否创新高而MACD峰值降低
         if(peakPrices > peakPrices && peakValues < peakValues)
         {
            lookingForShort = true;
            hBarHigh = peakPrices; // 使用最新峰值价格
            firstPeakValue = peakValues;
            firstPeakPrice = peakPrices;
            secondPeakValue = peakValues;
            secondPeakPrice = peakPrices;
            
            if(EnableDebug)
            {
               Print("检测到看跌背离:");
               Print("价格1: ", peakPrices, " MACD1: ", peakValues, " 索引: ", peakIndexes);
               Print("价格2: ", peakPrices, " MACD2: ", peakValues, " 索引: ", peakIndexes);
            }
            
            if(VisualMode)
            {
               // 在图表上标记背离
               string objName = "DIVERGENCE_SHORT_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_SELL, 0, rates].time, peakPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
               
               objName = "DIVERGENCE_SHORT_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_SELL, 0, rates].time, peakPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 专家订单处理函数                                                 |
//+------------------------------------------------------------------+
void OnTrade()
{
   UpdateDailyProfit();
}

//+------------------------------------------------------------------+
//| 专家报价处理函数                                                 |
//+------------------------------------------------------------------+
void OnTick()
{
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, TimeFrame, 0);
   
   // 只在新的K线开始时处理
   if(currentBarTime == lastBarTime)
   {
      // 即使不是新K线,也检查是否有手动交易信号
      if((lookingForLong || lookingForShort) && !SignalAlerted)
      {
         string signal = lookingForLong ? "买入信号" : "卖出信号";
         Print("检测到交易信号 - ", signal, " 等待新K线确认");
         SignalAlerted = true;
      }
      return;
   }
   
   lastBarTime = currentBarTime;
   
   if(EnableDebug) Print("新K线开始: ", TimeToString(currentBarTime));
   
   // 检查每日限制
   if(ExceededDailyLimits())
   {
      if(EnableDebug) Print("超过每日限制,跳过交易");
      return;
   }
   
   // 检查交易时间
   if(!IsTradingTime())
   {
      if(EnableDebug) Print("不在交易时间内,跳过交易");
      return;
   }
   
   // 获取指标值
   if(!GetIndicatorValues())
   {
      if(EnableDebug) Print("获取指标数据失败,跳过处理");
      return;
   }
   
   // 检查背离形态
   CheckDivergence();
   
   // 检查入场条件
   if((lookingForLong || lookingForShort) && currentBarTime != lastTradeTime)
   {
      string signalType = lookingForLong ? "长" : "短";
      if(EnableDebug) Print("检测到交易信号 - 类型: ", signalType);
      
      // 附加过滤器
      bool trendFilterPassed = true;
      if(UseTrendFilter)
      {
         double currentClose = rates.close;
         double ma;
         if(CopyBuffer(trendMAHandle, 0, 0, 1, ma) < 1)
         {
            if(EnableDebug) Print("获取趋势MA数据失败!");
         }
         else
         {
            if(lookingForLong && currentClose < ma)
            {
               if(EnableDebug) Print("长信号被趋势过滤器拒绝");
               trendFilterPassed = false;
            }
            if(lookingForShort && currentClose > ma)
            {
               if(EnableDebug) Print("短信号被趋势过滤器拒绝");
               trendFilterPassed = false;
            }
         }
      }
      
      bool higherTFFilterPassed = true;
      if(UseHigherTFConfirmation)
      {
         if(lookingForLong && !IsHigherTFTrendBullish())
         {
            if(EnableDebug) Print("长信号被更高时间框架过滤器拒绝");
            higherTFFilterPassed = false;
         }
         if(lookingForShort && IsHigherTFTrendBullish())
         {
            if(EnableDebug) Print("短信号被更高时间框架过滤器拒绝");
            higherTFFilterPassed = false;
         }
      }
      
      // 如果通过过滤器则下单
      if(lookingForLong && trendFilterPassed && higherTFFilterPassed)
      {
         PlaceLongOrder();
         if(EnableDebug) Print("执行长订单");
      }
      else if(lookingForShort && trendFilterPassed && higherTFFilterPassed)
      {
         PlaceShortOrder();
         if(EnableDebug) Print("执行短订单");
      }
      else
      {
         if(EnableDebug) Print("信号被过滤器阻止");
      }
   }
   else
   {
      if(EnableDebug) Print("无有效交易信号或已在当前K线交易");
   }
   
   // 移动止损管理
   if(UseTrailingStop) ManageTrailingStop();
   
   // 部分获利平仓
   if(UsePartialProfit) CheckPartialProfit();
   
   // 出现相反信号时平仓
   if(CloseOnOppositeSignal) CheckOppositeSignal();
}

//+------------------------------------------------------------------+
//| 下多单                                                         |
//+------------------------------------------------------------------+
void PlaceLongOrder()
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double atrValue = atrValues * ATRMultiplier; // 使用最新ATR值
   double stopLoss = NormalizeDouble(lBarLow - atrValue, _Digits);
   double stopLossPoints = (ask - stopLoss) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints)
   {
      stopLoss = NormalizeDouble(ask - MinStopLossPoints * _Point, _Digits);
      stopLossPoints = MinStopLossPoints;
   }
   if(stopLossPoints > MaxStopLossPoints)
   {
      stopLoss = NormalizeDouble(ask - MaxStopLossPoints * _Point, _Digits);
      stopLossPoints = MaxStopLossPoints;
   }
   
   double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize(stopLossPoints);
   
   if(EnableDebug)
   {
      Print("尝试买入: ", lotSize, " 手");
      Print("进场价: ", ask, " 止损: ", stopLoss, " 止盈: ", takeProfit);
   }
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(ask + (ask - stopLoss), _Digits);
         if(Trade.Buy(firstLot, _Symbol, ask, stopLoss, firstTP, "MACD背离做多1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("买入订单1成功,手数: ", firstLot);
         }
         else
         {
            Print("买入订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(ask + (ask - stopLoss) * SecondTPMultiplier, _Digits);
         if(Trade.Buy(secondLot, _Symbol, ask, stopLoss, secondTP, "MACD背离做多2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("买入订单2成功,手数: ", secondLot);
         }
         else
         {
            Print("买入订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "MACD背离做多"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
         if(EnableDebug) Print("买入订单成功,手数: ", lotSize);
      }
      else
      {
         Print("买入订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForLong = false;
   SignalAlerted = false;
}

//+------------------------------------------------------------------+
//| 下空单                                                         |
//+------------------------------------------------------------------+
void PlaceShortOrder()
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double atrValue = atrValues * ATRMultiplier; // 使用最新ATR值
   double stopLoss = NormalizeDouble(hBarHigh + atrValue, _Digits);
   double stopLossPoints = (stopLoss - bid) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints)
   {
      stopLoss = NormalizeDouble(bid + MinStopLossPoints * _Point, _Digits);
      stopLossPoints = MinStopLossPoints;
   }
   if(stopLossPoints > MaxStopLossPoints)
   {
      stopLoss = NormalizeDouble(bid + MaxStopLossPoints * _Point, _Digits);
      stopLossPoints = MaxStopLossPoints;
   }
   
   double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize(stopLossPoints);
   
   if(EnableDebug)
   {
      Print("尝试卖出: ", lotSize, " 手");
      Print("进场价: ", bid, " 止损: ", stopLoss, " 止盈: ", takeProfit);
   }
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(bid - (stopLoss - bid), _Digits);
         if(Trade.Sell(firstLot, _Symbol, bid, stopLoss, firstTP, "MACD背离做空1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("卖出订单1成功,手数: ", firstLot);
         }
         else
         {
            Print("卖出订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(bid - (stopLoss - bid) * SecondTPMultiplier, _Digits);
         if(Trade.Sell(secondLot, _Symbol, bid, stopLoss, secondTP, "MACD背离做空2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("卖出订单2成功,手数: ", secondLot);
         }
         else
         {
            Print("卖出订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "MACD背离做空"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
         if(EnableDebug) Print("卖出订单成功,手数: ", lotSize);
      }
      else
      {
         Print("卖出订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForShort = false;
   SignalAlerted = false;
}

//+------------------------------------------------------------------+
//| 管理移动止损                                                   |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double currentStop = PositionGetDouble(POSITION_SL);
            double newStop = 0;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               newStop = NormalizeDouble(bid - TrailingStopPoints * _Point, _Digits);
               
               // 确保新的止损高于当前止损且不会造成亏损
               if((newStop > currentStop || currentStop == 0) && (newStop > PositionGetDouble(POSITION_PRICE_OPEN)))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     if(EnableDebug) Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
                  else if(EnableDebug) Print("多头止损更新为: ", newStop);
               }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               newStop = NormalizeDouble(ask + TrailingStopPoints * _Point, _Digits);
               
               // 确保新的止损低于当前止损且不会造成亏损
               if((newStop < currentStop || currentStop == 0) && (newStop < PositionGetDouble(POSITION_PRICE_OPEN)))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     if(EnableDebug) Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
                  else if(EnableDebug) Print("空头止损更新为: ", newStop);
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查部分获利平仓                                                 |
//+------------------------------------------------------------------+
void CheckPartialProfit()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double profitInPips = 0;
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double stopLoss = PositionGetDouble(POSITION_SL);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               profitInPips = (bid - openPrice) / _Point;
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               profitInPips = (openPrice - ask) / _Point;
            }
            
            double stopLossPips = MathAbs(openPrice - stopLoss) / _Point;
            
            // 检查是否达到第一个目标(1:1)
            if(profitInPips >= stopLossPips && StringFind(PositionGetString(POSITION_COMMENT), " 1") < 0)
            {
               double volume = PositionGetDouble(POSITION_VOLUME);
               double volumeToClose = NormalizeDouble(volume * PartialProfitRatio, 2);
               
               if(volumeToClose > 0)
               {
                  if(!Trade.PositionClosePartial(ticket, volumeToClose))
                  {
                     if(EnableDebug) Print("部分平仓失败: ", Trade.ResultRetcodeDescription());
                  }
                  else
                  {
                     if(EnableDebug) Print("部分平仓成功: ", volumeToClose, " 手");
                  }
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查相反信号并平仓                                             |
//+------------------------------------------------------------------+
void CheckOppositeSignal()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            bool shouldClose = false;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && lookingForShort)
               shouldClose = true;
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && lookingForLong)
               shouldClose = true;
            
            if(shouldClose)
            {
               if(!Trade.PositionClose(ticket))
                  {
                     if(EnableDebug) Print("平仓失败: ", Trade.ResultRetcodeDescription());
                  }
                  else
                  {
                     if(EnableDebug) Print("因相反信号平仓: ", ticket);
                  }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 关闭所有仓位                                                   |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
   int count = 0;
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            if(Trade.PositionClose(ticket))
            {
               count++;
               if(EnableDebug) Print("平仓成功: ", ticket);
            }
            else
            {
               if(EnableDebug) Print("平仓失败: ", Trade.ResultRetcodeDescription());
            }
         }
      }
   }
   if(EnableDebug) Print("已平仓所有头寸,数量: ", count);
}

//+------------------------------------------------------------------+
//| 更新每日盈亏统计                                                 |
//+------------------------------------------------------------------+
void UpdateDailyProfit()
{
   MqlDateTime today, last;
   TimeCurrent(today);
   TimeToStruct(lastDailyCheck, last);
   
   if(today.day != last.day || today.mon != last.mon || today.year != last.year)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
      return;
   }
   
   // 计算当日盈亏
   double totalProfit = 0;
   
   // 获取当日所有持仓的盈亏
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            datetime positionTime = (datetime)PositionGetInteger(POSITION_TIME);
            MqlDateTime positionTimeStruct;
            TimeToStruct(positionTime, positionTimeStruct);
            
            if(positionTimeStruct.day == today.day &&
               positionTimeStruct.mon == today.mon &&
               positionTimeStruct.year == today.year)
            {
               totalProfit += PositionGetDouble(POSITION_PROFIT);
            }
         }
      }
   }
   
   // 获取当日已平仓交易的盈亏
   HistorySelect(lastDailyCheck, TimeCurrent());
   int dealsTotal = HistoryDealsTotal();
   for(int i = 0; i < dealsTotal; i++)
   {
      ulong dealTicket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) == MagicNumber &&
         HistoryDealGetString(dealTicket, DEAL_SYMBOL) == _Symbol)
      {
         datetime dealTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
         MqlDateTime dealTimeStruct;
         TimeToStruct(dealTime, dealTimeStruct);
         
         if(dealTimeStruct.day == today.day &&
            dealTimeStruct.mon == today.mon &&
            dealTimeStruct.year == today.year)
         {
            totalProfit += HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
         }
      }
   }
   
   dailyProfitLoss = totalProfit;
   if(EnableDebug) Print("每日盈亏更新: ", dailyProfitLoss);
}

//+------------------------------------------------------------------+
//| 图表事件处理函数                                                 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   // 处理按钮点击事件
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == "BuyButton")
      {
         // 手动买入
         double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
         double atrArray[];
         if(CopyBuffer(atrHandle, 0, 0, 1, atrArray) < 1)
         {
            Print("获取ATR值失败");
            return;
         }
         double atrValue = atrArray * ATRMultiplier;
         double stopLoss = NormalizeDouble(ask - atrValue, _Digits);
         double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
         double stopLossPoints = (ask - stopLoss) / _Point;
         double lotSize = CalculateLotSize(stopLossPoints);
         
         if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "手动买入"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("手动买入成功: ", lotSize, " 手");
         }
      }
      else if(sparam == "SellButton")
      {
         // 手动卖出
         double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         double atrArray[];
         if(CopyBuffer(atrHandle, 0, 0, 1, atrArray) < 1)
         {
            Print("获取ATR值失败");
            return;
         }
         double atrValue = atrArray * ATRMultiplier;
         double stopLoss = NormalizeDouble(bid + atrValue, _Digits);
         double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
         double stopLossPoints = (stopLoss - bid) / _Point;
         double lotSize = CalculateLotSize(stopLossPoints);
         
         if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "手动卖出"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("手动卖出成功: ", lotSize, " 手");
         }
      }
      else if(sparam == "PauseButton")
      {
         // 暂停/恢复加仓
         TradingPaused = !TradingPaused;
         if(TradingPaused)
         {
            PauseButton.Description("恢复加仓");
            PauseButton.BackColor(clrDarkSlateBlue);
            if(EnableDebug) Print("EA已暂停自动加仓");
         }
         else
         {
            PauseButton.Description("暂停加仓");
            PauseButton.BackColor(clrDarkOrange);
            if(EnableDebug) Print("EA已恢复自动加仓");
         }
         ChartRedraw();
      }
      else if(sparam == "CloseAllButton")
      {
         // 清仓
         CloseAllPositions();
         if(EnableDebug) Print("已平仓所有头寸");
      }
   }
   
   // 处理图表大小变化事件,调整按钮位置
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
      
      int buttonWidth = 80;
      int buttonHeight = 30;
      int buttonSpacing = 10;
      
      int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
      int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
      
      BuyButton.X_Distance(xPos);
      BuyButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      SellButton.X_Distance(xPos);
      SellButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      PauseButton.X_Distance(xPos);
      PauseButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      CloseAllButton.X_Distance(xPos);
      CloseAllButton.Y_Distance(yPos);
      
      ChartRedraw();
   }
}
是因为下单的条件太严格,所以下单量很少,下单是会下的,如果想用的话,参数可以继续修改一下

bzcsx 发表于 2025-6-7 18:20:00

//+------------------------------------------------------------------+
//|                                     Enhanced_MACD_Divergence_EA|
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "3.23"
#property strict
#property description "增强版MACD背离交易系统(带ATR止损)"
#property description "做多条件: MACD柱状图低于零轴且峰值上升 + 价格低点下降"
#property description "做空条件: MACD柱状图高于零轴且峰值下降 + 价格高点上升"
#property description "包含多重过滤条件和风险管理选项"

#include <Trade\Trade.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

//+------------------------------------------------------------------+
//| 输入参数                                                         |
//+------------------------------------------------------------------+
//--- MACD核心参数
input int FastEMA = 12;                  // MACD快线周期
input int SlowEMA = 26;                  // MACD慢线周期
input int SignalPeriod = 9;            // MACD信号线周期
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // 交易时间框架
input ulong MagicNumber = 12345;         // EA魔术码

//--- 风险管理
input double RiskPercent = 1.0;          // 每笔交易风险百分比
input double FixedLotSize = 0.1;         // 固定交易手数(当RiskPercent=0时使用)
input double RiskRewardRatio = 2.0;      // 风险回报比
input int ATRPeriod = 14;                // ATR止损周期
input double ATRMultiplier = 1.5;      // ATR止损倍数(从1.8降低到1.5)
input bool UseTrailingStop = true;       // 启用移动止损
input int TrailingStopPoints = 100;      // 移动止损点数
input int MinStopLossPoints = 30;      // 最小止损点数(从50降低到30)
input int MaxStopLossPoints = 300;       // 最大止损点数

//--- 交易过滤器
input bool UseTrendFilter = false;       // 启用趋势MA过滤(默认禁用)
input int TrendMAPeriod = 200;         // 趋势MA周期
input ENUM_MA_METHOD TrendMAMethod = MODE_EMA; // 趋势MA计算方法
input bool UseHigherTFConfirmation = false; // 启用更高时间框架确认(默认禁用)
input ENUM_TIMEFRAMES HigherTF = PERIOD_D1; // 更高时间框架
input bool UseTimeFilter = false;      // 启用交易时间过滤
input string TradeStartTime = "00:00";   // 交易开始时间(服务器时间)
input string TradeEndTime = "23:59";   // 交易结束时间(服务器时间)
input int MinPeakCount = 2;            // 背离所需最小峰值数量
input int MaxPeakCount = 3;            // 考虑的最大峰值数量

//--- 出场策略
input bool UsePartialProfit = true;      // 启用部分平仓获利
input double PartialProfitRatio = 0.5;   // 1:1时平仓比例
input double SecondTPMultiplier = 3.0;   // 剩余仓位的目标倍数
input bool CloseOnOppositeSignal = true; // 出现相反信号时平仓

//--- 资金管理
input double MaxRiskPerTrade = 0.05;   // 每笔交易最大风险(0.05=5%)
input double MaxDailyLossPercent = 5.0;// 每日最大亏损百分比(从2%提高到5%)
input int MaxTradesPerDay = 5;         // 每日最大交易次数(从3提高到5)

//--- 调试选项
input bool EnableDebug = true;         // 启用调试输出
input bool VisualMode = true;            // 在图表上显示标记

//+------------------------------------------------------------------+
//| 全局变量                                                         |
//+------------------------------------------------------------------+
double macdMain[], macdSignal[], macdHistogram[], atrValues[];
double trendMAValues[], higherTFMAValues[];
MqlRates rates[];
datetime lastTradeTime, lastDailyCheck;
int peakCount = 0;
double lastPeakValue = 0;
double firstPeakValue = 0;
double firstPeakPrice = 0;
double secondPeakValue = 0;
double secondPeakPrice = 0;
bool lookingForLong = false;
bool lookingForShort = false;
double lBarLow = 0;
double hBarHigh = 0;
double dailyProfitLoss = 0;
int dailyTradeCount = 0;
int macdHandle = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
int trendMAHandle = INVALID_HANDLE;
int higherTFMAHandle = INVALID_HANDLE;
CTrade Trade;
bool TradingPaused = false;
bool SignalAlerted = false;

// 控制按钮
CChartObjectButton BuyButton;
CChartObjectButton SellButton;
CChartObjectButton PauseButton;
CChartObjectButton CloseAllButton;

//+------------------------------------------------------------------+
//| 创建控制按钮函数                                                 |
//+------------------------------------------------------------------+
void CreateButtons()
{
   // 获取图表宽度和高度
   long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   
   // 按钮尺寸
   int buttonWidth = 80;
   int buttonHeight = 30;
   int buttonSpacing = 10;
   
   // 计算按钮位置(右下角)
   int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
   int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
   
   // 创建买入按钮
   if(!BuyButton.Create(0, "BuyButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建买入按钮失败!");
   BuyButton.Description("买入");
   BuyButton.Color(clrWhite);
   BuyButton.BackColor(clrDarkGreen);
   BuyButton.BorderColor(clrWhite);
   BuyButton.FontSize(10);
   BuyButton.State(false);
   
   // 创建卖出按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!SellButton.Create(0, "SellButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建卖出按钮失败!");
   SellButton.Description("卖出");
   SellButton.Color(clrWhite);
   SellButton.BackColor(clrDarkRed);
   SellButton.BorderColor(clrWhite);
   SellButton.FontSize(10);
   SellButton.State(false);
   
   // 创建暂停加仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!PauseButton.Create(0, "PauseButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建暂停按钮失败!");
   PauseButton.Description("暂停加仓");
   PauseButton.Color(clrWhite);
   PauseButton.BackColor(clrDarkOrange);
   PauseButton.BorderColor(clrWhite);
   PauseButton.FontSize(10);
   PauseButton.State(false);
   
   // 创建清仓按钮
   yPos -= (buttonHeight + buttonSpacing);
   if(!CloseAllButton.Create(0, "CloseAllButton", 0, xPos, yPos, buttonWidth, buttonHeight))
      Print("创建清仓按钮失败!");
   CloseAllButton.Description("清仓");
   CloseAllButton.Color(clrWhite);
   CloseAllButton.BackColor(clrDarkSlateGray);
   CloseAllButton.BorderColor(clrWhite);
   CloseAllButton.FontSize(10);
   CloseAllButton.State(false);
   
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| 重置每日统计数据                                                 |
//+------------------------------------------------------------------+
void ResetDailyStats()
{
   MqlDateTime today;
   TimeCurrent(today);
   today.hour = 0;
   today.min = 0;
   today.sec = 0;
   lastDailyCheck = StructToTime(today);
   dailyProfitLoss = 0;
   dailyTradeCount = 0;
   if(EnableDebug) Print("每日统计数据已重置");
}

//+------------------------------------------------------------------+
//| 专家初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // 初始化指标句柄
   macdHandle = iMACD(_Symbol, TimeFrame, FastEMA, SlowEMA, SignalPeriod, PRICE_CLOSE);
   atrHandle = iATR(_Symbol, TimeFrame, ATRPeriod);
   
   if(UseTrendFilter)
      trendMAHandle = iMA(_Symbol, TimeFrame, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   if(UseHigherTFConfirmation)
      higherTFMAHandle = iMA(_Symbol, HigherTF, TrendMAPeriod, 0, TrendMAMethod, PRICE_CLOSE);
   
   // 检查参数设置是否有效
   if(RiskPercent <= 0 && FixedLotSize <= 0)
   {
      Alert("风险管理参数配置不正确!");
      return(INIT_PARAMETERS_INCORRECT);
   }
   
   // 重置每日统计
   ResetDailyStats();
   
   // 设置交易对象的魔术码
   Trade.SetExpertMagicNumber(MagicNumber);
   
   // 创建控制按钮
   CreateButtons();
   
   if(EnableDebug) Print("EA初始化完成,开始运行...");
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 专家逆初始化函数                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 释放指标句柄
   if(macdHandle != INVALID_HANDLE) IndicatorRelease(macdHandle);
   if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
   if(trendMAHandle != INVALID_HANDLE) IndicatorRelease(trendMAHandle);
   if(UseHigherTFConfirmation && higherTFMAHandle != INVALID_HANDLE) IndicatorRelease(higherTFMAHandle);
   
   // 删除按钮
   BuyButton.Delete();
   SellButton.Delete();
   PauseButton.Delete();
   CloseAllButton.Delete();
   
   // 删除所有图形对象
   ObjectsDeleteAll(0, "DIVERGENCE_");
   
   if(EnableDebug) Print("EA已卸载");
}

//+------------------------------------------------------------------+
//| 获取指标数据                                                   |
//+------------------------------------------------------------------+
bool GetIndicatorValues()
{
   int barsNeeded = 100; // 确保足够的历史数据
   
   // 获取价格数据
   if(CopyRates(_Symbol, TimeFrame, 0, barsNeeded, rates) < barsNeeded)
   {
      if(EnableDebug) Print("获取价格数据失败! 需要: ", barsNeeded, " 实际: ", ArraySize(rates));
      return false;
   }
   
   // 获取MACD值
   ArrayResize(macdMain, barsNeeded);
   ArrayResize(macdSignal, barsNeeded);
   ArrayResize(macdHistogram, barsNeeded);
   
   if(CopyBuffer(macdHandle, 0, 0, barsNeeded, macdMain) < barsNeeded)
   {
      if(EnableDebug) Print("获取MACD主数据失败!");
      return false;
   }
   
   if(CopyBuffer(macdHandle, 1, 0, barsNeeded, macdSignal) < barsNeeded)
   {
      if(EnableDebug) Print("获取MACD信号数据失败!");
      return false;
   }
   
   // 计算柱状图值
   for(int i = 0; i < barsNeeded; i++)
   {
      macdHistogram = macdMain - macdSignal;
   }
   
   // 获取ATR值
   ArrayResize(atrValues, barsNeeded);
   if(CopyBuffer(atrHandle, 0, 0, barsNeeded, atrValues) < barsNeeded)
   {
      if(EnableDebug) Print("获取ATR数据失败!");
      return false;
   }
   
   // 获取趋势MA值
   if(UseTrendFilter)
   {
      ArrayResize(trendMAValues, barsNeeded);
      if(CopyBuffer(trendMAHandle, 0, 0, barsNeeded, trendMAValues) < barsNeeded)
      {
         if(EnableDebug) Print("获取趋势MA数据失败!");
         return false;
      }
   }
   
   // 获取更高时间框架MA值
   if(UseHigherTFConfirmation)
   {
      ArrayResize(higherTFMAValues, barsNeeded);
      if(CopyBuffer(higherTFMAHandle, 0, 0, barsNeeded, higherTFMAValues) < barsNeeded)
      {
         if(EnableDebug) Print("获取更高时间框架MA数据失败!");
         return false;
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 根据风险计算手数                                                 |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPoints)
{
   if(RiskPercent <= 0) return FixedLotSize;
   
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double pointValue = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(tickValue <= 0 || tickSize <= 0 || pointValue <= 0)
   {
      if(EnableDebug) Print("无法获取点值信息,使用固定手数");
      return FixedLotSize;
   }
   
   double riskAmount = MathMin(AccountInfoDouble(ACCOUNT_EQUITY) * RiskPercent / 100,
                              AccountInfoDouble(ACCOUNT_EQUITY) * MaxRiskPerTrade);
   
   double lotSize = riskAmount / (stopLossPoints * tickValue * pointValue);
   
   // 调整最小和最大手数限制
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   
   lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
   lotSize = NormalizeDouble(lotSize, 2);
   
   if(EnableDebug) Print("计算手数: ", lotSize, " SL点数: ", stopLossPoints, " 风险金额: ", riskAmount);
   return lotSize;
}

//+------------------------------------------------------------------+
//| 检查当前是否在交易时间内                                          |
//+------------------------------------------------------------------+
bool IsTradingTime()
{
   if(!UseTimeFilter) return true;
   
   datetime start = StringToTime(TradeStartTime);
   datetime end = StringToTime(TradeEndTime);
   datetime now = TimeCurrent();
   
   if(start == 0 || end == 0)
   {
      if(EnableDebug) Print("时间格式错误! 请使用HH:MM格式");
      return false;
   }
   
   // 处理隔夜交易时段
   if(start < end) return (now >= start && now <= end);
   else return (now >= start || now <= end);
}

//+------------------------------------------------------------------+
//| 检查更高时间框架趋势                                              |
//+------------------------------------------------------------------+
bool IsHigherTFTrendBullish()
{
   if(!UseHigherTFConfirmation) return true;
   
   double close = iClose(_Symbol, HigherTF, 0);
   double ma;
   if(CopyBuffer(higherTFMAHandle, 0, 0, 1, ma) < 1)
   {
      if(EnableDebug) Print("获取更高时间框架MA数据失败!");
      return true;
   }
   
   return close > ma;
}

//+------------------------------------------------------------------+
//| 检查是否超过每日限制                                              |
//+------------------------------------------------------------------+
bool ExceededDailyLimits()
{
   MqlDateTime today, last;
   TimeCurrent(today);
   TimeToStruct(lastDailyCheck, last);
   
   if(today.day != last.day || today.mon != last.mon || today.year != last.year)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
      return false;
   }
   
   if(dailyProfitLoss < 0 && MathAbs(dailyProfitLoss) >= AccountInfoDouble(ACCOUNT_EQUITY) * MaxDailyLossPercent / 100)
   {
      if(EnableDebug) Print("已达到每日亏损限额。今日停止交易。");
      return true;
   }
   
   if(dailyTradeCount >= MaxTradesPerDay)
   {
      if(EnableDebug) Print("已达到每日最大交易次数。今日停止交易。");
      return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| 检查背离形态                                                   |
//+------------------------------------------------------------------+
void CheckDivergence()
{
   // 重置标志
   lookingForLong = false;
   lookingForShort = false;
   SignalAlerted = false;
   
   // 如果交易暂停,则跳过信号检查
   if(TradingPaused)
   {
      if(EnableDebug) Print("交易已暂停,跳过信号检查");
      return;
   }
   
   // 检查做多条件(MACD低于零轴)
   if(macdHistogram < 0)
   {
      // 寻找最近的2-3个谷底
      int valleysFound = 0;
      double valleyValues = {0};
      double valleyPrices = {0};
      int valleyIndexes = {0};
      
      // 从最近的bar开始向前搜索(索引0是最新)
      for(int i = 1; i < MathMin(50, ArraySize(macdHistogram)-1); i++)
      {
         // 检查是否为谷底 (MACD柱状图低点)
         if(macdHistogram < macdHistogram &&
            macdHistogram < macdHistogram)
         {
            valleyValues = macdHistogram;
            valleyPrices = rates.low;
            valleyIndexes = i;
            valleysFound++;
            
            if(valleysFound >= MaxPeakCount) break;
         }
      }
      
      // 检查是否有足够的谷底
      if(valleysFound >= MinPeakCount)
      {
         // 检查价格是否创新低而MACD谷底升高
         if(valleyPrices < valleyPrices && valleyValues > valleyValues)
         {
            lookingForLong = true;
            lBarLow = valleyPrices; // 使用最新谷底价格
            firstPeakValue = valleyValues;
            firstPeakPrice = valleyPrices;
            secondPeakValue = valleyValues;
            secondPeakPrice = valleyPrices;
            
            if(EnableDebug)
            {
               Print("检测到看涨背离:");
               Print("价格1: ", valleyPrices, " MACD1: ", valleyValues, " 索引: ", valleyIndexes);
               Print("价格2: ", valleyPrices, " MACD2: ", valleyValues, " 索引: ", valleyIndexes);
            }
            
            if(VisualMode)
            {
               // 在图表上标记背离
               string objName = "DIVERGENCE_LONG_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_BUY, 0, rates].time, valleyPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
               
               objName = "DIVERGENCE_LONG_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_BUY, 0, rates].time, valleyPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
            }
         }
      }
   }
   
   // 检查做空条件(MACD高于零轴)
   if(macdHistogram > 0)
   {
      // 寻找最近的2-3个峰值
      int peaksFound = 0;
      double peakValues = {0};
      double peakPrices = {0};
      int peakIndexes = {0};
      
      // 从最近的bar开始向前搜索
      for(int i = 1; i < MathMin(50, ArraySize(macdHistogram)-1); i++)
      {
         // 检查是否为峰值 (MACD柱状图高点)
         if(macdHistogram > macdHistogram &&
            macdHistogram > macdHistogram)
         {
            peakValues = macdHistogram;
            peakPrices = rates.high;
            peakIndexes = i;
            peaksFound++;
            
            if(peaksFound >= MaxPeakCount) break;
         }
      }
      
      // 检查是否有足够的峰值
      if(peaksFound >= MinPeakCount)
      {
         // 检查价格是否创新高而MACD峰值降低
         if(peakPrices > peakPrices && peakValues < peakValues)
         {
            lookingForShort = true;
            hBarHigh = peakPrices; // 使用最新峰值价格
            firstPeakValue = peakValues;
            firstPeakPrice = peakPrices;
            secondPeakValue = peakValues;
            secondPeakPrice = peakPrices;
            
            if(EnableDebug)
            {
               Print("检测到看跌背离:");
               Print("价格1: ", peakPrices, " MACD1: ", peakValues, " 索引: ", peakIndexes);
               Print("价格2: ", peakPrices, " MACD2: ", peakValues, " 索引: ", peakIndexes);
            }
            
            if(VisualMode)
            {
               // 在图表上标记背离
               string objName = "DIVERGENCE_SHORT_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_SELL, 0, rates].time, peakPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
               
               objName = "DIVERGENCE_SHORT_" + TimeToString(rates].time);
               ObjectCreate(0, objName, OBJ_ARROW_SELL, 0, rates].time, peakPrices);
               ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
               ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 专家订单处理函数                                                 |
//+------------------------------------------------------------------+
void OnTrade()
{
   UpdateDailyProfit();
}

//+------------------------------------------------------------------+
//| 专家报价处理函数                                                 |
//+------------------------------------------------------------------+
void OnTick()
{
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, TimeFrame, 0);
   
   // 只在新的K线开始时处理
   if(currentBarTime == lastBarTime)
   {
      // 即使不是新K线,也检查是否有手动交易信号
      if((lookingForLong || lookingForShort) && !SignalAlerted)
      {
         string signal = lookingForLong ? "买入信号" : "卖出信号";
         Print("检测到交易信号 - ", signal, " 等待新K线确认");
         SignalAlerted = true;
      }
      return;
   }
   
   lastBarTime = currentBarTime;
   
   if(EnableDebug) Print("新K线开始: ", TimeToString(currentBarTime));
   
   // 检查每日限制
   if(ExceededDailyLimits())
   {
      if(EnableDebug) Print("超过每日限制,跳过交易");
      return;
   }
   
   // 检查交易时间
   if(!IsTradingTime())
   {
      if(EnableDebug) Print("不在交易时间内,跳过交易");
      return;
   }
   
   // 获取指标值
   if(!GetIndicatorValues())
   {
      if(EnableDebug) Print("获取指标数据失败,跳过处理");
      return;
   }
   
   // 检查背离形态
   CheckDivergence();
   
   // 检查入场条件
   if((lookingForLong || lookingForShort) && currentBarTime != lastTradeTime)
   {
      string signalType = lookingForLong ? "长" : "短";
      if(EnableDebug) Print("检测到交易信号 - 类型: ", signalType);
      
      // 附加过滤器
      bool trendFilterPassed = true;
      if(UseTrendFilter)
      {
         double currentClose = rates.close;
         double ma;
         if(CopyBuffer(trendMAHandle, 0, 0, 1, ma) < 1)
         {
            if(EnableDebug) Print("获取趋势MA数据失败!");
         }
         else
         {
            if(lookingForLong && currentClose < ma)
            {
               if(EnableDebug) Print("长信号被趋势过滤器拒绝");
               trendFilterPassed = false;
            }
            if(lookingForShort && currentClose > ma)
            {
               if(EnableDebug) Print("短信号被趋势过滤器拒绝");
               trendFilterPassed = false;
            }
         }
      }
      
      bool higherTFFilterPassed = true;
      if(UseHigherTFConfirmation)
      {
         if(lookingForLong && !IsHigherTFTrendBullish())
         {
            if(EnableDebug) Print("长信号被更高时间框架过滤器拒绝");
            higherTFFilterPassed = false;
         }
         if(lookingForShort && IsHigherTFTrendBullish())
         {
            if(EnableDebug) Print("短信号被更高时间框架过滤器拒绝");
            higherTFFilterPassed = false;
         }
      }
      
      // 如果通过过滤器则下单
      if(lookingForLong && trendFilterPassed && higherTFFilterPassed)
      {
         PlaceLongOrder();
         if(EnableDebug) Print("执行长订单");
      }
      else if(lookingForShort && trendFilterPassed && higherTFFilterPassed)
      {
         PlaceShortOrder();
         if(EnableDebug) Print("执行短订单");
      }
      else
      {
         if(EnableDebug) Print("信号被过滤器阻止");
      }
   }
   else
   {
      if(EnableDebug) Print("无有效交易信号或已在当前K线交易");
   }
   
   // 移动止损管理
   if(UseTrailingStop) ManageTrailingStop();
   
   // 部分获利平仓
   if(UsePartialProfit) CheckPartialProfit();
   
   // 出现相反信号时平仓
   if(CloseOnOppositeSignal) CheckOppositeSignal();
}

//+------------------------------------------------------------------+
//| 下多单                                                         |
//+------------------------------------------------------------------+
void PlaceLongOrder()
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double atrValue = atrValues * ATRMultiplier; // 使用最新ATR值
   double stopLoss = NormalizeDouble(lBarLow - atrValue, _Digits);
   double stopLossPoints = (ask - stopLoss) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints)
   {
      stopLoss = NormalizeDouble(ask - MinStopLossPoints * _Point, _Digits);
      stopLossPoints = MinStopLossPoints;
   }
   if(stopLossPoints > MaxStopLossPoints)
   {
      stopLoss = NormalizeDouble(ask - MaxStopLossPoints * _Point, _Digits);
      stopLossPoints = MaxStopLossPoints;
   }
   
   double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize(stopLossPoints);
   
   if(EnableDebug)
   {
      Print("尝试买入: ", lotSize, " 手");
      Print("进场价: ", ask, " 止损: ", stopLoss, " 止盈: ", takeProfit);
   }
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(ask + (ask - stopLoss), _Digits);
         if(Trade.Buy(firstLot, _Symbol, ask, stopLoss, firstTP, "MACD背离做多1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("买入订单1成功,手数: ", firstLot);
         }
         else
         {
            Print("买入订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(ask + (ask - stopLoss) * SecondTPMultiplier, _Digits);
         if(Trade.Buy(secondLot, _Symbol, ask, stopLoss, secondTP, "MACD背离做多2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("买入订单2成功,手数: ", secondLot);
         }
         else
         {
            Print("买入订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "MACD背离做多"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
         if(EnableDebug) Print("买入订单成功,手数: ", lotSize);
      }
      else
      {
         Print("买入订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForLong = false;
   SignalAlerted = false;
}

//+------------------------------------------------------------------+
//| 下空单                                                         |
//+------------------------------------------------------------------+
void PlaceShortOrder()
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double atrValue = atrValues * ATRMultiplier; // 使用最新ATR值
   double stopLoss = NormalizeDouble(hBarHigh + atrValue, _Digits);
   double stopLossPoints = (stopLoss - bid) / _Point;
   
   // 验证止损
   if(stopLossPoints < MinStopLossPoints)
   {
      stopLoss = NormalizeDouble(bid + MinStopLossPoints * _Point, _Digits);
      stopLossPoints = MinStopLossPoints;
   }
   if(stopLossPoints > MaxStopLossPoints)
   {
      stopLoss = NormalizeDouble(bid + MaxStopLossPoints * _Point, _Digits);
      stopLossPoints = MaxStopLossPoints;
   }
   
   double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
   double lotSize = CalculateLotSize(stopLossPoints);
   
   if(EnableDebug)
   {
      Print("尝试卖出: ", lotSize, " 手");
      Print("进场价: ", bid, " 止损: ", stopLoss, " 止盈: ", takeProfit);
   }
   
   if(UsePartialProfit)
   {
      // 分成两个不同止盈的订单
      double firstLot = NormalizeDouble(lotSize * PartialProfitRatio, 2);
      double secondLot = NormalizeDouble(lotSize - firstLot, 2);
      
      if(firstLot > 0)
      {
         double firstTP = NormalizeDouble(bid - (stopLoss - bid), _Digits);
         if(Trade.Sell(firstLot, _Symbol, bid, stopLoss, firstTP, "MACD背离做空1"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("卖出订单1成功,手数: ", firstLot);
         }
         else
         {
            Print("卖出订单1失败: ", Trade.ResultRetcodeDescription());
         }
      }
      
      if(secondLot > 0)
      {
         double secondTP = NormalizeDouble(bid - (stopLoss - bid) * SecondTPMultiplier, _Digits);
         if(Trade.Sell(secondLot, _Symbol, bid, stopLoss, secondTP, "MACD背离做空2"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("卖出订单2成功,手数: ", secondLot);
         }
         else
         {
            Print("卖出订单2失败: ", Trade.ResultRetcodeDescription());
         }
      }
   }
   else
   {
      if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "MACD背离做空"))
      {
         lastTradeTime = iTime(_Symbol, TimeFrame, 0);
         dailyTradeCount++;
         if(EnableDebug) Print("卖出订单成功,手数: ", lotSize);
      }
      else
      {
         Print("卖出订单失败: ", Trade.ResultRetcodeDescription());
      }
   }
   
   lookingForShort = false;
   SignalAlerted = false;
}

//+------------------------------------------------------------------+
//| 管理移动止损                                                   |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double currentStop = PositionGetDouble(POSITION_SL);
            double newStop = 0;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               newStop = NormalizeDouble(bid - TrailingStopPoints * _Point, _Digits);
               
               // 确保新的止损高于当前止损且不会造成亏损
               if((newStop > currentStop || currentStop == 0) && (newStop > PositionGetDouble(POSITION_PRICE_OPEN)))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     if(EnableDebug) Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
                  else if(EnableDebug) Print("多头止损更新为: ", newStop);
               }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               newStop = NormalizeDouble(ask + TrailingStopPoints * _Point, _Digits);
               
               // 确保新的止损低于当前止损且不会造成亏损
               if((newStop < currentStop || currentStop == 0) && (newStop < PositionGetDouble(POSITION_PRICE_OPEN)))
               {
                  if(!Trade.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP)))
                  {
                     if(EnableDebug) Print("移动止损修改失败: ", Trade.ResultRetcodeDescription());
                  }
                  else if(EnableDebug) Print("空头止损更新为: ", newStop);
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查部分获利平仓                                                 |
//+------------------------------------------------------------------+
void CheckPartialProfit()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            double profitInPips = 0;
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double stopLoss = PositionGetDouble(POSITION_SL);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               profitInPips = (bid - openPrice) / _Point;
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               profitInPips = (openPrice - ask) / _Point;
            }
            
            double stopLossPips = MathAbs(openPrice - stopLoss) / _Point;
            
            // 检查是否达到第一个目标(1:1)
            if(profitInPips >= stopLossPips && StringFind(PositionGetString(POSITION_COMMENT), " 1") < 0)
            {
               double volume = PositionGetDouble(POSITION_VOLUME);
               double volumeToClose = NormalizeDouble(volume * PartialProfitRatio, 2);
               
               if(volumeToClose > 0)
               {
                  if(!Trade.PositionClosePartial(ticket, volumeToClose))
                  {
                     if(EnableDebug) Print("部分平仓失败: ", Trade.ResultRetcodeDescription());
                  }
                  else
                  {
                     if(EnableDebug) Print("部分平仓成功: ", volumeToClose, " 手");
                  }
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 检查相反信号并平仓                                             |
//+------------------------------------------------------------------+
void CheckOppositeSignal()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            bool shouldClose = false;
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && lookingForShort)
               shouldClose = true;
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && lookingForLong)
               shouldClose = true;
            
            if(shouldClose)
            {
               if(!Trade.PositionClose(ticket))
                  {
                     if(EnableDebug) Print("平仓失败: ", Trade.ResultRetcodeDescription());
                  }
                  else
                  {
                     if(EnableDebug) Print("因相反信号平仓: ", ticket);
                  }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 关闭所有仓位                                                   |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
   int count = 0;
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            if(Trade.PositionClose(ticket))
            {
               count++;
               if(EnableDebug) Print("平仓成功: ", ticket);
            }
            else
            {
               if(EnableDebug) Print("平仓失败: ", Trade.ResultRetcodeDescription());
            }
         }
      }
   }
   if(EnableDebug) Print("已平仓所有头寸,数量: ", count);
}

//+------------------------------------------------------------------+
//| 更新每日盈亏统计                                                 |
//+------------------------------------------------------------------+
void UpdateDailyProfit()
{
   MqlDateTime today, last;
   TimeCurrent(today);
   TimeToStruct(lastDailyCheck, last);
   
   if(today.day != last.day || today.mon != last.mon || today.year != last.year)
   {
      // 新的一天,重置计数器
      ResetDailyStats();
      return;
   }
   
   // 计算当日盈亏
   double totalProfit = 0;
   
   // 获取当日所有持仓的盈亏
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            datetime positionTime = (datetime)PositionGetInteger(POSITION_TIME);
            MqlDateTime positionTimeStruct;
            TimeToStruct(positionTime, positionTimeStruct);
            
            if(positionTimeStruct.day == today.day &&
               positionTimeStruct.mon == today.mon &&
               positionTimeStruct.year == today.year)
            {
               totalProfit += PositionGetDouble(POSITION_PROFIT);
            }
         }
      }
   }
   
   // 获取当日已平仓交易的盈亏
   HistorySelect(lastDailyCheck, TimeCurrent());
   int dealsTotal = HistoryDealsTotal();
   for(int i = 0; i < dealsTotal; i++)
   {
      ulong dealTicket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) == MagicNumber &&
         HistoryDealGetString(dealTicket, DEAL_SYMBOL) == _Symbol)
      {
         datetime dealTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
         MqlDateTime dealTimeStruct;
         TimeToStruct(dealTime, dealTimeStruct);
         
         if(dealTimeStruct.day == today.day &&
            dealTimeStruct.mon == today.mon &&
            dealTimeStruct.year == today.year)
         {
            totalProfit += HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
         }
      }
   }
   
   dailyProfitLoss = totalProfit;
   if(EnableDebug) Print("每日盈亏更新: ", dailyProfitLoss);
}

//+------------------------------------------------------------------+
//| 图表事件处理函数                                                 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   // 处理按钮点击事件
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == "BuyButton")
      {
         // 手动买入
         double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
         double atrArray[];
         if(CopyBuffer(atrHandle, 0, 0, 1, atrArray) < 1)
         {
            Print("获取ATR值失败");
            return;
         }
         double atrValue = atrArray * ATRMultiplier;
         double stopLoss = NormalizeDouble(ask - atrValue, _Digits);
         double takeProfit = NormalizeDouble(ask + (ask - stopLoss) * RiskRewardRatio, _Digits);
         double stopLossPoints = (ask - stopLoss) / _Point;
         double lotSize = CalculateLotSize(stopLossPoints);
         
         if(Trade.Buy(lotSize, _Symbol, ask, stopLoss, takeProfit, "手动买入"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("手动买入成功: ", lotSize, " 手");
         }
      }
      else if(sparam == "SellButton")
      {
         // 手动卖出
         double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         double atrArray[];
         if(CopyBuffer(atrHandle, 0, 0, 1, atrArray) < 1)
         {
            Print("获取ATR值失败");
            return;
         }
         double atrValue = atrArray * ATRMultiplier;
         double stopLoss = NormalizeDouble(bid + atrValue, _Digits);
         double takeProfit = NormalizeDouble(bid - (stopLoss - bid) * RiskRewardRatio, _Digits);
         double stopLossPoints = (stopLoss - bid) / _Point;
         double lotSize = CalculateLotSize(stopLossPoints);
         
         if(Trade.Sell(lotSize, _Symbol, bid, stopLoss, takeProfit, "手动卖出"))
         {
            lastTradeTime = iTime(_Symbol, TimeFrame, 0);
            dailyTradeCount++;
            if(EnableDebug) Print("手动卖出成功: ", lotSize, " 手");
         }
      }
      else if(sparam == "PauseButton")
      {
         // 暂停/恢复加仓
         TradingPaused = !TradingPaused;
         if(TradingPaused)
         {
            PauseButton.Description("恢复加仓");
            PauseButton.BackColor(clrDarkSlateBlue);
            if(EnableDebug) Print("EA已暂停自动加仓");
         }
         else
         {
            PauseButton.Description("暂停加仓");
            PauseButton.BackColor(clrDarkOrange);
            if(EnableDebug) Print("EA已恢复自动加仓");
         }
         ChartRedraw();
      }
      else if(sparam == "CloseAllButton")
      {
         // 清仓
         CloseAllPositions();
         if(EnableDebug) Print("已平仓所有头寸");
      }
   }
   
   // 处理图表大小变化事件,调整按钮位置
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      long chartWidth = (long)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      long chartHeight = (long)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
      
      int buttonWidth = 80;
      int buttonHeight = 30;
      int buttonSpacing = 10;
      
      int xPos = (int)(chartWidth - buttonWidth - buttonSpacing);
      int yPos = (int)(chartHeight - buttonHeight - buttonSpacing);
      
      BuyButton.X_Distance(xPos);
      BuyButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      SellButton.X_Distance(xPos);
      SellButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      PauseButton.X_Distance(xPos);
      PauseButton.Y_Distance(yPos);
      
      yPos -= (buttonHeight + buttonSpacing);
      CloseAllButton.X_Distance(xPos);
      CloseAllButton.Y_Distance(yPos);
      
      ChartRedraw();
   }
}
这个改好的EA,周五一天只下了四个单子,下单量很少
页: [1]
查看完整版本: 徵求代寫MT5 EA - MACD背離+ATR停損策略