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

 |
|