找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
首页论坛EAHub 外汇论坛
查看: 19|回复: 0

网格+对冲,黄金EA源码

[复制链接]

4万

主题

54

回帖

4万

积分

管理员

积分
45954
发表于 2026-8-25 22:43:00 | 显示全部楼层 |阅读模式
#property strict
#property copyright "加仓-安全版"
#property link      ""

//============================================================
// 外部参数:资金管理
//============================================================

extern string MM_block      = "==== 资金管理 ====";
extern bool   UseMoneyManagement = true;   // 是否启用自动资金管理(按风险百分比自动算手数)
extern double RiskPerTrade  = 2.0;         // 每笔单风险百分比(用于主网格单基础手数)
extern double MaxDrawdown   = 20.0;        // 最大允许回撤百分比(从EA启动时权益算),超过则清仓并停止交易
extern double MinLots       = 0.01;        // 最小手数(还会按券商 minlot/lotstep 规范化)
extern double MaxLots       = 0.5;         // 最大手数(还会按券商 maxlot 规范化)
extern int    StopLossPips  = 600;         // 主网格每单止损(pips)。Digits=2且pip=0.01时:600=6美元
extern int    TakeProfitPips= 600;         // 主网格每单止盈(pips)

//============================================================
// 外部参数:加仓与网格
//============================================================

extern string Grid_block    = "==== 加仓设置 ====";
extern int    MaxTrades     = 6;           // 主网格同向最大持仓笔数(达到后停止同向加仓)
extern double LotExponent   = 1.3;         // 主网格每层手数倍增
extern bool   UseDynamicPips= true;        // 动态加仓间距
extern int    DefaultPips   = 300;         // 静态加仓间距(pips)
extern int    DynamicDepth  = 24;
extern int    DynamicDivider= 3;

//============================================================
// 外部参数:对冲设置
// 触发条件:主网格浮亏 >= 余额(Balance) 的 HedgeTriggerBalancePercent%
//============================================================

extern string Hedge_block   = "==== 对冲设置 ====";
extern bool   UseHedge      = true;        // 是否启用对冲
extern double HedgeTriggerBalancePercent = 20.0; // 余额20%触发对冲(你的要求)
extern int    HedgeStepPips = 200;         // 对冲分段触发间距(pips)
extern double HedgePct1     = 0.10;        // 第1次对冲手数 = 主网格总手数*10%
extern double HedgePct2     = 0.15;        // 第2次对冲手数 = 主网格总手数*15%
extern double HedgePct3     = 0.20;        // 第3次对冲手数 = 主网格总手数*20%
extern bool   HedgeUseTP    = false;       // 对冲单是否设TP(建议false)
extern bool   HedgeUseSL    = true;        // 对冲单是否设SL(建议true)
extern int    HedgeSLPips   = 1200;        // 对冲单SL(pips)
extern int    HedgeTPPips   = 600;         // 对冲单TP(pips)仅HedgeUseTP=true有效

//============================================================
// 外部参数:方向控制与指标
//============================================================

extern string Dir_block     = "==== 方向控制与信号 ====";
extern bool   onlylong      = true;
extern bool   onlyshort     = true;
extern int    CCI_Period    = 55;
extern int    RSI_Period    = 14;
extern int    CCI_Timeframe = PERIOD_M15;
extern int    RSI_Timeframe = PERIOD_H1;
extern double CCI_Limit     = 200.0;       // |CCI|>=该值时触发紧急清仓(主网格+对冲一起平)
extern double RSI_SellLevel = 70.0;
extern double RSI_BuyLevel  = 30.0;

//============================================================
// 外部参数:其它风控与交易参数
//============================================================

extern string Risk_block      = "==== 其它风控 ====";
extern bool   UseTrailingStop = true;      // 主网格跟踪止损开关
extern int    TrailingStart   = 300;       // 主网格盈利超过多少pips开始跟踪
extern int    TrailingStep    = 100;       // 主网格跟踪止损距离(pips)

// 整组风控阈值:主网格浮亏达到权益百分比 -> 停止同向加仓(不影响对冲)
extern double MaxBasketLossPercent = 20.0;

extern string Trade_block   = "==== 交易参数 ====";
extern int    MagicNumber   = 2222;        // 主网格magic
extern int    Slip          = 30;          // 滑点(points)

//============================================================
// 全局变量
//============================================================

double gInitialEquity = 0.0;
bool   gTradingStopped = false;
bool   gNoMoreAdd = false;

//============================================================
// 工具函数:pip/手数/止损距离规范化
//============================================================

double GetPipSize()
{
   if (Digits == 3 || Digits == 5) return(10 * Point);
   return(Point);
}

double NormalizeLots(double lots)
{
   double minLot  = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot  = MarketInfo(Symbol(), MODE_MAXLOT);
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
   if (lotStep <= 0) lotStep = 0.01;

   if (lots < minLot) lots = minLot;
   if (lots > maxLot) lots = maxLot;

   lots = MathFloor(lots / lotStep) * lotStep;

   int d = 2;
   if (lotStep < 0.01)  d = 3;
   if (lotStep < 0.001) d = 4;

   return NormalizeDouble(lots, d);
}

double GetMinStopDistancePrice()
{
   int stopLevel   = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
   int freezeLevel = (int)MarketInfo(Symbol(), MODE_FREEZELEVEL);
   int minLevel = stopLevel;
   if (freezeLevel > minLevel) minLevel = freezeLevel;
   return minLevel * Point;
}

void AdjustStopsToBrokerRules(int type, double entry, double &sl, double &tp)
{
   double minDist = GetMinStopDistancePrice();
   if (minDist > 0)
   {
      if (type == OP_BUY)
      {
         if (sl > 0 && (entry - sl) < minDist) sl = entry - minDist;
         if (tp > 0 && (tp - entry) < minDist) tp = entry + minDist;
      }
      else if (type == OP_SELL)
      {
         if (sl > 0 && (sl - entry) < minDist) sl = entry + minDist;
         if (tp > 0 && (entry - tp) < minDist) tp = entry - minDist;
      }
   }

   if (sl > 0) sl = NormalizeDouble(sl, Digits);
   if (tp > 0) tp = NormalizeDouble(tp, Digits);
}

//============================================================
// 订单统计(按magic区分主网格/对冲)
//============================================================

int CountOpenTradesByMagic(int magic)
{
   int count = 0;
   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=magic) continue;

      int type = OrderType();
      if(type==OP_BUY || type==OP_SELL) count++;
   }
   return count;
}

int CountOpenTrades()
{
   return CountOpenTradesByMagic(MagicNumber);
}

int GetCurrentDirection()
{
   bool hasBuy=false, hasSell=false;
   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=MagicNumber) continue;

      if(OrderType()==OP_BUY)  hasBuy=true;
      if(OrderType()==OP_SELL) hasSell=true;
   }
   if(hasBuy && !hasSell) return 1;
   if(!hasBuy && hasSell) return -1;
   return 0;
}

double GetLastOpenPriceByMagic(int type, int magic)
{
   double price=0.0;
   datetime lastTime=0;
   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=magic) continue;

      if(OrderType()==type && OrderOpenTime() > lastTime)
      {
         lastTime = OrderOpenTime();
         price = OrderOpenPrice();
      }
   }
   return price;
}

double GetLastOpenPrice(int type)
{
   return GetLastOpenPriceByMagic(type, MagicNumber);
}

double GetBasketFloatingProfitByMagic(int magic)
{
   double sum = 0.0;
   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=magic) continue;

      int type = OrderType();
      if(type!=OP_BUY && type!=OP_SELL) continue;

      sum += OrderProfit() + OrderSwap() + OrderCommission();
   }
   return sum;
}

double GetBasketFloatingProfit()
{
   return GetBasketFloatingProfitByMagic(MagicNumber);
}

double GetBasketLotsByMagicAndDirection(int magic, int dir)
{
   double lots = 0.0;
   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=magic) continue;

      int type = OrderType();
      if (dir == 1 && type == OP_BUY)  lots += OrderLots();
      if (dir == -1 && type == OP_SELL) lots += OrderLots();
   }
   return lots;
}

void CloseAllTradesByMagic(int magic)
{
   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=magic) continue;

      int type = OrderType();
      if(type!=OP_BUY && type!=OP_SELL) continue;

      double price = (type==OP_BUY) ? Bid : Ask;
      if(!OrderClose(OrderTicket(), OrderLots(), price, Slip, clrRed))
         Print("CloseAllTrades failed magic=", magic, " ticket=", OrderTicket(), " err=", GetLastError());
   }
}

void CloseAllTrades()
{
   CloseAllTradesByMagic(MagicNumber);
   CloseAllTradesByMagic(MagicNumber + 1);
}

//============================================================
// 资金管理:pip价值/基础手数/分层手数
//============================================================

double GetPipValuePerLot()
{
   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize  = MarketInfo(Symbol(), MODE_TICKSIZE);
   if (tickSize <= 0.0) return 0.0;

   double pipSize = GetPipSize();
   return tickValue * (pipSize / tickSize);
}

double CalcBaseLots()
{
   double lots = MinLots;

   if (!UseMoneyManagement || StopLossPips <= 0)
      return NormalizeLots(lots);

   double equity    = AccountEquity();
   double riskMoney = equity * RiskPerTrade / 100.0;
   double pipValue  = GetPipValuePerLot();
   if (pipValue <= 0.0) return NormalizeLots(lots);

   double riskPerLot = StopLossPips * pipValue;
   if (riskPerLot <= 0.0) return NormalizeLots(lots);

   lots = riskMoney / riskPerLot;

   if (lots < MinLots) lots = MinLots;
   if (lots > MaxLots) lots = MaxLots;

   return NormalizeLots(lots);
}

double CalcLotForLevel(int level)
{
   double lots = CalcBaseLots() * MathPow(LotExponent, level);
   if (lots < MinLots) lots = MinLots;
   if (lots > MaxLots) lots = MaxLots;
   return NormalizeLots(lots);
}

//============================================================
// 风控:最大回撤停机;整组亏损阈值判断
//============================================================

void CheckEquityStop()
{
   if (MaxDrawdown <= 0.0) return;
   if (gInitialEquity <= 0.0) gInitialEquity = AccountEquity();

   double dd = (gInitialEquity - AccountEquity()) / gInitialEquity * 100.0;
   if (dd >= MaxDrawdown)
   {
      Print("MaxDrawdown triggered. DD=", DoubleToStr(dd,2), "% >= ", DoubleToStr(MaxDrawdown,2), "%");
      CloseAllTrades();
      gTradingStopped = true;
   }
}

// 主网格是否达到“停止同向加仓阈值”(按权益%)
bool IsAddStopLossOverLimit()
{
   if (MaxBasketLossPercent <= 0.0) return false;
   if (CountOpenTrades() <= 0) return false;

   double eq = AccountEquity();
   if (eq <= 0.0) return false;

   double basketProfit = GetBasketFloatingProfit(); // 负数=浮亏
   if (basketProfit >= 0.0) return false;

   double lossPercent = (-basketProfit) / eq * 100.0;
   return (lossPercent >= MaxBasketLossPercent);
}

// 主网格是否达到“启动对冲阈值”(按余额Balance%)——你的要求
bool IsHedgeTriggerOverLimit()
{
   if (HedgeTriggerBalancePercent <= 0.0) return false;
   if (CountOpenTrades() <= 0) return false;

   double bal = AccountBalance();
   if (bal <= 0.0) return false;

   double basketProfit = GetBasketFloatingProfit(); // 负数=浮亏
   if (basketProfit >= 0.0) return false;

   double lossPercent = (-basketProfit) / bal * 100.0;
   return (lossPercent >= HedgeTriggerBalancePercent);
}

void CheckBasketLossStopAdding()
{
   gNoMoreAdd = IsAddStopLossOverLimit();
}

//============================================================
// 跟踪止损:只对主网格单应用
//============================================================

void ApplyTrailingStop()
{
   if (!UseTrailingStop) return;

   double pip = GetPipSize();

   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=MagicNumber) continue;

      int type = OrderType();
      if(type!=OP_BUY && type!=OP_SELL) continue;

      double open = OrderOpenPrice();
      double sl   = OrderStopLoss();
      double tp   = OrderTakeProfit();

      if (type == OP_BUY)
      {
         double profitPips = (Bid - open) / pip;
         if (profitPips > TrailingStart)
         {
            double newSL = Bid - TrailingStep * pip;
            if (sl < newSL)
            {
               if(!OrderModify(OrderTicket(), open, NormalizeDouble(newSL, Digits), tp, 0, clrAqua))
                  Print("Trailing BUY modify fail ticket=", OrderTicket(), " err=", GetLastError());
            }
         }
      }
      else
      {
         double profitPips = (open - Ask) / pip;
         if (profitPips > TrailingStart)
         {
            double newSL = Ask + TrailingStep * pip;
            if (sl == 0.0 || sl > newSL)
            {
               if(!OrderModify(OrderTicket(), open, NormalizeDouble(newSL, Digits), tp, 0, clrAqua))
                  Print("Trailing SELL modify fail ticket=", OrderTicket(), " err=", GetLastError());
            }
         }
      }
   }
}

//============================================================
// 动态加仓间距(pips)
//============================================================

double CalcDynamicPips()
{
   if (!UseDynamicPips || DynamicDepth <= 0 || DynamicDivider <= 0)
      return(DefaultPips);

   double pip = GetPipSize();

   int hi = iHighest(NULL, 0, MODE_HIGH, DynamicDepth, 1);
   int lo = iLowest(NULL, 0, MODE_LOW,  DynamicDepth, 1);

   double rangePips = (High[hi] - Low[lo]) / pip;
   if (rangePips <= 0.0) return(DefaultPips);

   double dist = rangePips / DynamicDivider;

   double minDist = DefaultPips / 2.0;
   double maxDist = DefaultPips * 3.0;

   if (dist < minDist) dist = minDist;
   if (dist > maxDist) dist = maxDist;

   return(dist);
}

//============================================================
// 紧急离场:CCI极值清仓(主网格+对冲一起平)
//============================================================

void CheckCCIEmergencyExit()
{
   double cci = iCCI(NULL, CCI_Timeframe, CCI_Period, PRICE_CLOSE, 0);
   if (MathAbs(cci) >= CCI_Limit)
   {
      Print("CCI emergency exit. cci=", DoubleToStr(cci,2));
      CloseAllTrades();
   }
}

//============================================================
// 主网格:首单信号与加仓逻辑
//============================================================

void CheckFirstEntry()
{
   if (CountOpenTrades() > 0) return;

   double close2 = iClose(Symbol(), 0, 2);
   double close1 = iClose(Symbol(), 0, 1);
   double rsi    = iRSI(NULL, RSI_Timeframe, RSI_Period, PRICE_CLOSE, 1);

   double lots = CalcBaseLots();
   if (lots <= 0.0) return;

   double pip = GetPipSize();

   if (close2 < close1 && rsi > RSI_SellLevel && onlyshort)
   {
      int type = OP_SELL;
      double entry = Bid;

      double sl = entry + StopLossPips * pip;
      double tp = entry - TakeProfitPips * pip;
      AdjustStopsToBrokerRules(type, entry, sl, tp);

      int ticket = OrderSend(Symbol(), type, lots, entry, Slip, sl, tp,
                             "GridEA SELL 0", MagicNumber, 0, clrRed);
      if (ticket < 0) Print("First SELL failed err=", GetLastError());
   }
   else if (close2 > close1 && rsi < RSI_BuyLevel && onlylong)
   {
      int type = OP_BUY;
      double entry = Ask;

      double sl = entry - StopLossPips * pip;
      double tp = entry + TakeProfitPips * pip;
      AdjustStopsToBrokerRules(type, entry, sl, tp);

      int ticket = OrderSend(Symbol(), type, lots, entry, Slip, sl, tp,
                             "GridEA BUY 0", MagicNumber, 0, clrLime);
      if (ticket < 0) Print("First BUY failed err=", GetLastError());
   }
}

void CheckAddPositions()
{
   // 达到权益亏损阈值后停止同向加仓
   if (gNoMoreAdd) return;

   int total = CountOpenTrades();
   if (total <= 0) return;
   if (total >= MaxTrades) return;

   int dir = GetCurrentDirection();
   if (dir == 0) return;

   double pip      = GetPipSize();
   double distPips = CalcDynamicPips();

   int    level = total;
   double lots  = CalcLotForLevel(level);
   if (lots <= 0.0) return;

   if (dir == 1)
   {
      double lastBuyPrice = GetLastOpenPrice(OP_BUY);
      if (lastBuyPrice <= 0.0) return;

      double movePips = (lastBuyPrice - Bid) / pip;
      if (movePips >= distPips)
      {
         int type = OP_BUY;
         double entry = Ask;

         double sl = entry - StopLossPips * pip;
         double tp = entry + TakeProfitPips * pip;
         AdjustStopsToBrokerRules(type, entry, sl, tp);

         int ticket = OrderSend(Symbol(), type, lots, entry, Slip, sl, tp,
                                "GridEA BUY " + IntegerToString(level),
                                MagicNumber, 0, clrLime);
         if (ticket < 0) Print("Add BUY failed err=", GetLastError());
      }
   }
   else if (dir == -1)
   {
      double lastSellPrice = GetLastOpenPrice(OP_SELL);
      if (lastSellPrice <= 0.0) return;

      double movePips = (Ask - lastSellPrice) / pip;
      if (movePips >= distPips)
      {
         int type = OP_SELL;
         double entry = Bid;

         double sl = entry + StopLossPips * pip;
         double tp = entry - TakeProfitPips * pip;
         AdjustStopsToBrokerRules(type, entry, sl, tp);

         int ticket = OrderSend(Symbol(), type, lots, entry, Slip, sl, tp,
                                "GridEA SELL " + IntegerToString(level),
                                MagicNumber, 0, clrRed);
         if (ticket < 0) Print("Add SELL failed err=", GetLastError());
      }
   }
}

//============================================================
// 对冲逻辑:触发条件 = 浮亏达到 Balance 的 20%(可调)
// 对冲分三段触发:第1段立即;第2/3段按 HedgeStepPips 分段
//============================================================

int GetHedgeStage()
{
   int hedgeMagic = MagicNumber + 1;
   int count = CountOpenTradesByMagic(hedgeMagic);
   if (count <= 0) return 0;
   if (count == 1) return 1;
   if (count == 2) return 2;
   return 3;
}

void CheckHedge()
{
   if (!UseHedge) return;

   // 必须有主网格持仓
   if (CountOpenTrades() <= 0) return;

   // 触发条件:主网格浮亏达到 Balance 的 HedgeTriggerBalancePercent%
   if (!IsHedgeTriggerOverLimit()) return;

   // 主网格方向必须明确
   int dir = GetCurrentDirection();
   if (dir == 0) return;

   int hedgeMagic = MagicNumber + 1;
   int stage = GetHedgeStage();
   if (stage >= 3) return;

   // 主网格当前方向总手数
   double basketLots = GetBasketLotsByMagicAndDirection(MagicNumber, dir);
   if (basketLots <= 0.0) return;

   // 本次对冲手数比例
   double pct = (stage == 0 ? HedgePct1 : (stage == 1 ? HedgePct2 : HedgePct3));
   double lots = NormalizeLots(basketLots * pct);
   if (lots <= 0.0) return;

   double pip = GetPipSize();

   // 分段触发:第1段立刻开;第2/3段需要继续不利方向移动 HedgeStepPips
   bool allowOpen = false;

   if (stage == 0)
   {
      allowOpen = true;
   }
   else
   {
      int hedgeTypePrev = (dir == 1) ? OP_SELL : OP_BUY;
      double lastHedgePrice = GetLastOpenPriceByMagic(hedgeTypePrev, hedgeMagic);
      if (lastHedgePrice <= 0.0) return;

      if (dir == 1)
      {
         // 主仓BUY:下跌不利
         double move = (lastHedgePrice - Bid) / pip;
         if (move >= HedgeStepPips) allowOpen = true;
      }
      else
      {
         // 主仓SELL:上涨不利
         double move = (Ask - lastHedgePrice) / pip;
         if (move >= HedgeStepPips) allowOpen = true;
      }
   }

   if (!allowOpen) return;

   // 对冲方向:与主网格相反
   int type = (dir == 1) ? OP_SELL : OP_BUY;
   double entry = (type == OP_BUY) ? Ask : Bid;

   double sl = 0.0, tp = 0.0;

   if (HedgeUseSL)
   {
      if (type == OP_BUY)  sl = entry - HedgeSLPips * pip;
      if (type == OP_SELL) sl = entry + HedgeSLPips * pip;
   }
   if (HedgeUseTP)
   {
      if (type == OP_BUY)  tp = entry + HedgeTPPips * pip;
      if (type == OP_SELL) tp = entry - HedgeTPPips * pip;
   }

   AdjustStopsToBrokerRules(type, entry, sl, tp);

   string comment = "HEDGE " + IntegerToString(stage + 1);

   int ticket = OrderSend(Symbol(), type, lots, entry, Slip,
                          (HedgeUseSL ? sl : 0.0),
                          (HedgeUseTP ? tp : 0.0),
                          comment, hedgeMagic, 0, clrOrange);

   if (ticket < 0)
   {
      Print("Hedge OrderSend failed stage=", stage+1,
            " lots=", DoubleToStr(lots,2),
            " err=", GetLastError());
   }
}

//============================================================
// EA 生命周期函数
//============================================================

int init()
{
   gInitialEquity  = AccountEquity();
   gTradingStopped = false;
   gNoMoreAdd      = false;
   return(0);
}

int deinit()
{
   Comment("");
   return(0);
}

int start()
{
   if (gTradingStopped) return(0);

   // 1) 回撤超限:清仓并停机
   CheckEquityStop();
   if (gTradingStopped) return(0);

   // 2) 主网格浮亏达到权益阈值:停止同向加仓
   CheckBasketLossStopAdding();

   // 3) 主网格跟踪止损
   ApplyTrailingStop();

   // 4) CCI 紧急离场:清仓
   CheckCCIEmergencyExit();

   // 5) 首单
   CheckFirstEntry();

   // 6) 同向加仓(可能被停止)
   CheckAddPositions();

   // 7) 对冲:当主网格浮亏达到余额20%触发(分段加码)
   CheckHedge();

   return(0);
}
复制代码



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|EAHub外汇论坛

GMT+8, 2026-10-11 17:57 , Processed in 0.109058 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表