- #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);
- }
复制代码
|