//+------------------------------------------------------------------+ //| CascadeGreepEA | //| Copyright 2025 Cascade (AI) | //| Purpose: ATR-risk FX/Gold mean-rev | //+------------------------------------------------------------------+ #property copyright "Cascade AI" #property link "https://windsurf.ai" #property version "1.0" #property strict #include //==================== Inputs ==================== input group "Risk Management" input double InpRiskPercent = 1.0; // per-trade risk % of equity (1.0 - 2.0) input double InpKillSwitchDDPercent = 2.0; // stop trading if equity DD >= this % from start input int InpMaxOpenPositions = 5; // cap across entire account input bool InpAllowTradeIfBelowMinLot = true; // if true, will use min lot even if > risk (guarded by caps) input bool InpCloseOnKillSwitch = true; // close EA positions when DD hit input group "Signals" input ENUM_TIMEFRAMES InpSignalTF = PERIOD_M15; input int InpSMAPeriod = 200; // trend filter input bool InpUseTrendFilter = true; input int InpRSIPeriod = 2; input int InpRSILow = 5; // buy when RSI <= low (with trend filter) input int InpRSIHigh = 95; // sell when RSI >= high (with trend filter) input bool InpAllowLong = true; input bool InpAllowShort = true; input group "Stops/Targets" input int InpATRPeriod = 14; input double InpATRMultiplierSL = 2.0; // SL = ATR * mult input bool InpUseTrailing = true; input double InpTrailATRMultiplier = 1.0; // trail distance in ATR input double InpTP_ATR_Mult = 0.0; // 0 disables fixed TP; else TP = ATR * mult input group "Execution Safety" input int InpMaxSpreadPoints = 30; // max allowed spread (points); 0 disables input double InpMaxATRPoints = 0.0; // max allowed ATR (points); 0 disables input double InpDailyLossLimitPercent = 2.0; // block trading for the day if daily DD >= this % input bool InpCloseOnDailyLoss = true; // close EA positions when daily loss hit input int InpMinBarsBetweenTrades = 3; // cooldown per symbol/timeframe input double InpMaxRiskPctWhenForcingMinLot = 5.0; // cap risk % when min lot is used input double InpMaxRiskPctWhenForcingMinLot_XAU = 3.0; // stricter cap for XAU/GOLD input group "Misc" input long InpMagic = 420250810; // unique EA id input int InpMaxSlippagePoints = 10; // allowed slippage in points for market orders input group "Master Adjustments" input bool InpUseMasterAdjust = true; // read dynamic factors from Master EA input string InpGVNamespaceEA = "CGreep"; // must match Master EA namespace //==================== Globals ==================== CTrade trade; double g_start_equity = 0.0; string g_symbol; bool g_trading_disabled = false; int hATR = INVALID_HANDLE; int hRSI = INVALID_HANDLE; int hSMA = INVALID_HANDLE; datetime g_last_bar_time = 0; datetime g_last_trade_bar_time = 0; bool g_daily_blocked = false; int g_day_key = 0; double g_day_equity_start = 0.0; //==================== Utility ==================== bool CopyOne(const int handle, const int buffer, const int shift, double &out) { double tmp[]; if(CopyBuffer(handle, buffer, shift, 1, tmp) != 1) return false; out = tmp[0]; return true; } bool GetATR(const int shift, double &atr) { if(hATR==INVALID_HANDLE) return false; return CopyOne(hATR, 0, shift, atr); } bool GetRSI(const int shift, double &rsi) { if(hRSI==INVALID_HANDLE) return false; return CopyOne(hRSI, 0, shift, rsi); } bool GetSMA(const int shift, double &sma) { if(hSMA==INVALID_HANDLE) return false; return CopyOne(hSMA, 0, shift, sma); } bool SymbolIsXAU(const string sym) { return (StringFind(sym, "XAU", 0) >= 0 || StringFind(sym, "GOLD", 0) >= 0); } int TodayKey() { MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); return (dt.year*10000 + dt.mon*100 + dt.day); } void DailyResetIfNeeded() { int key = TodayKey(); if(key != g_day_key) { g_day_key = key; g_day_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); g_daily_blocked = false; Print("[RESET] New day baseline equity=", g_day_equity_start); } } bool CheckDailyLossLimit() { if(g_daily_blocked) return true; if(g_day_equity_start<=0) return false; double eq = AccountInfoDouble(ACCOUNT_EQUITY); double ddpct = (g_day_equity_start - eq) / g_day_equity_start * 100.0; if(ddpct >= InpDailyLossLimitPercent) { g_daily_blocked = true; Print("[DAILY] Loss limit ", DoubleToString(ddpct,2), "% >= ", InpDailyLossLimitPercent, "%. Trading disabled for today."); if(InpCloseOnDailyLoss) CloseEAPositions(); return true; } return false; } bool CheckSpreadAtrOK(const double atr) { MqlTick t; if(!SymbolInfoTick(g_symbol, t)) return false; double spread_pts = (t.ask - t.bid) / _Point; if(InpMaxSpreadPoints>0 && spread_pts > InpMaxSpreadPoints) { PrintFormat("[SKIP] Spread %.1f pts > max %d", spread_pts, InpMaxSpreadPoints); return false; } if(InpMaxATRPoints>0.0) { double atr_pts = atr / _Point; if(atr_pts > InpMaxATRPoints) { PrintFormat("[SKIP] ATR %.1f pts > max %.1f pts", atr_pts, InpMaxATRPoints); return false; } } return true; } int CountAllOpenPositions() { return (int)PositionsTotal(); } bool HasAnyPositionForSymbol(const string sym) { for(int i=PositionsTotal()-1; i>=0; --i) { ulong ticket = PositionGetTicket(i); if(ticket==0) continue; string psym = PositionGetString(POSITION_SYMBOL); if(psym==sym) return true; } return false; } int CountEAPositions() { int count=0; for(int i=PositionsTotal()-1; i>=0; --i) { ulong ticket = PositionGetTicket(i); if(ticket==0) continue; long magic = (long)PositionGetInteger(POSITION_MAGIC); if(magic==InpMagic) count++; } return count; } void CloseEAPositions() { for(int i=PositionsTotal()-1; i>=0; --i) { ulong ticket = PositionGetTicket(i); if(ticket==0) continue; string sym = PositionGetString(POSITION_SYMBOL); long mgc = (long)PositionGetInteger(POSITION_MAGIC); long type= (long)PositionGetInteger(POSITION_TYPE); double vol = PositionGetDouble(POSITION_VOLUME); if(mgc!=InpMagic) continue; if(type==POSITION_TYPE_BUY) trade.PositionClose(sym); else if(type==POSITION_TYPE_SELL) trade.PositionClose(sym); } } bool CheckKillSwitch() { double eq = AccountInfoDouble(ACCOUNT_EQUITY); if(g_start_equity<=0) return false; double ddpct = (g_start_equity - eq) / g_start_equity * 100.0; if(ddpct >= InpKillSwitchDDPercent) { if(!g_trading_disabled) { g_trading_disabled = true; Print("[KILL] Drawdown ", DoubleToString(ddpct,2), "% >= ", InpKillSwitchDDPercent, "%. Trading disabled."); if(InpCloseOnKillSwitch) CloseEAPositions(); } return true; } return false; } bool CalcLotsForRisk(const string sym, const double risk_money, const double entry_price, const double sl_price, double &lots, const double cap_scale=1.0) { lots = 0.0; double ticksize = 0, tickvalue = 0, minlot=0, maxlot=0, lotstep=0; if(!SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE, ticksize)) return false; if(!SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE, tickvalue)) return false; if(!SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN, minlot)) return false; if(!SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX, maxlot)) return false; if(!SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP, lotstep)) return false; double delta = MathAbs(entry_price - sl_price); if(delta<=0 || ticksize<=0 || tickvalue<=0) return false; double ticks = delta / ticksize; double risk_per_lot = ticks * tickvalue; // currency per 1.00 lot if(risk_per_lot<=0) return false; double raw_lots = risk_money / risk_per_lot; if(raw_lots<=0) { lots=0; return true; } // snap to step without exceeding target risk if(lotstep>0) raw_lots = MathFloor(raw_lots/lotstep) * lotstep; lots = raw_lots; // clamp if(lots>maxlot) lots=maxlot; if(lots cap) { PrintFormat("[SKIP] Min-lot risk %.2f%% > cap %.2f%% on %s", riskpct_minlot, cap, sym); lots = 0.0; // too risky } else { lots = minlot; // accept min lot under cap } } else lots = 0.0; // skip trade } // normalize to lot step digits int step_digits = 0; if(lotstep>0) step_digits = (int)MathRound(MathLog10(1.0/lotstep)); lots = NormalizeDouble(lots, MathMax(0, step_digits)); return true; } void ManageTrailing() { if(!InpUseTrailing) return; MqlTick t; if(!SymbolInfoTick(g_symbol,t)) return; double atr; if(!GetATR(1, atr)) return; // apply master adjustments to trailing distance (wider SL under stress) double rf, slm, cdm; GetAdjustments(rf, slm, cdm); for(int i=PositionsTotal()-1; i>=0; --i) { ulong ticket = PositionGetTicket(i); if(ticket==0) continue; string psym = PositionGetString(POSITION_SYMBOL); if(psym!=g_symbol) continue; // only manage current chart symbol to avoid multi-symbol side effects long magic = (long)PositionGetInteger(POSITION_MAGIC); if(magic!=InpMagic) continue; long type = (long)PositionGetInteger(POSITION_TYPE); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); double desired_sl; if(type==POSITION_TYPE_BUY) { desired_sl = t.bid - atr * InpTrailATRMultiplier * slm; if(sl==0 || desired_sl>sl) trade.PositionModify(psym, desired_sl, tp); } else if(type==POSITION_TYPE_SELL) { desired_sl = t.ask + atr * InpTrailATRMultiplier * slm; if(sl==0 || desired_sl sma); bool dnTrend = !InpUseTrendFilter || (close1 < sma); if(InpAllowLong && upTrend && rsi <= InpRSILow) buySignal=true; if(InpAllowShort && dnTrend && rsi >= InpRSIHigh) sellSignal=true; return true; } void TryEnter() { if(g_trading_disabled) return; if(g_daily_blocked) { Print("[INFO] Daily loss limit active; trading blocked."); return; } if(CountAllOpenPositions() >= InpMaxOpenPositions) { Print("[INFO] Max open positions cap reached: ", InpMaxOpenPositions); return; } if(HasAnyPositionForSymbol(g_symbol)) return; // only one position per symbol bool buySig=false, sellSig=false; if(!GetSignal(buySig, sellSig)) return; if(!buySig && !sellSig) return; MqlTick t; if(!SymbolInfoTick(g_symbol, t)) return; double atr; if(!GetATR(1, atr)) return; if(!CheckSpreadAtrOK(atr)) return; // skip on wide spreads or extreme ATR // read master adjustments double rf=1.0, slm=1.0, cdm=1.0; GetAdjustments(rf, slm, cdm); double risk_money = AccountInfoDouble(ACCOUNT_EQUITY) * (InpRiskPercent/100.0); risk_money *= rf; // scale risk dynamically (news/vol/equity) // cooldown by bars since last trade (scaled by cooldown_mult) int effCooldown = EffectiveCooldownBars(); if(effCooldown>0 && g_last_trade_bar_time>0) { int last_shift = iBarShift(g_symbol, InpSignalTF, g_last_trade_bar_time, false); if(last_shift >= 0 && last_shift < effCooldown) { Print("[INFO] Cooldown active: waiting ", effCooldown - last_shift, " more bars before next trade."); return; } } if(buySig) { double sl = t.ask - atr*(InpATRMultiplierSL * slm); double tp = 0.0; if(InpTP_ATR_Mult>0) tp = t.ask + atr*(InpTP_ATR_Mult * slm); double lots; if(!CalcLotsForRisk(g_symbol, risk_money, t.ask, sl, lots, rf)) return; if(lots<=0) { Print("[SKIP] Computed lots below min or invalid for BUY."); return; } trade.SetExpertMagicNumber(InpMagic); if(!trade.Buy(lots, g_symbol, t.ask, sl, tp, "CascadeGreepEA BUY")) Print("[ERR] Buy failed: ", _LastError); else g_last_trade_bar_time = iTime(g_symbol, InpSignalTF, 0); } else if(sellSig) { double sl = t.bid + atr*(InpATRMultiplierSL * slm); double tp = 0.0; if(InpTP_ATR_Mult>0) tp = t.bid - atr*(InpTP_ATR_Mult * slm); double lots; if(!CalcLotsForRisk(g_symbol, risk_money, t.bid, sl, lots, rf)) return; if(lots<=0) { Print("[SKIP] Computed lots below min or invalid for SELL."); return; } trade.SetExpertMagicNumber(InpMagic); if(!trade.Sell(lots, g_symbol, t.bid, sl, tp, "CascadeGreepEA SELL")) Print("[ERR] Sell failed: ", _LastError); else g_last_trade_bar_time = iTime(g_symbol, InpSignalTF, 0); } } //==================== Master Adjustments (Global Variables) ==================== double ReadGVDouble(const string name, const double defv) { if(!GlobalVariableCheck(name)) return defv; return GlobalVariableGet(name); } void GetAdjustments(double &risk_mult, double &sl_mult, double &cooldown_mult) { risk_mult=1.0; sl_mult=1.0; cooldown_mult=1.0; if(!InpUseMasterAdjust) return; string base = InpGVNamespaceEA + "/"; string sym = g_symbol; risk_mult = ReadGVDouble(base+"risk_mult/"+sym, 1.0); sl_mult = ReadGVDouble(base+"sl_mult/"+sym, 1.0); cooldown_mult = ReadGVDouble(base+"cooldown_mult/"+sym, 1.0); } int EffectiveCooldownBars() { double rf, sm, cm; GetAdjustments(rf, sm, cm); int baseBars = InpMinBarsBetweenTrades; int eff = (int)MathCeil(baseBars * cm); if(eff<0) eff=0; return eff; } //==================== MT5 Events ==================== int OnInit() { g_symbol = _Symbol; g_start_equity = AccountInfoDouble(ACCOUNT_EQUITY); g_day_key = TodayKey(); g_day_equity_start = g_start_equity; g_daily_blocked = false; hATR = iATR(g_symbol, InpSignalTF, InpATRPeriod); if(hATR==INVALID_HANDLE) { Print("[FATAL] ATR handle failed."); return INIT_FAILED; } hRSI = iRSI(g_symbol, InpSignalTF, InpRSIPeriod, PRICE_CLOSE); if(hRSI==INVALID_HANDLE) { Print("[FATAL] RSI handle failed."); return INIT_FAILED; } if(InpUseTrendFilter) { hSMA = iMA(g_symbol, InpSignalTF, InpSMAPeriod, 0, MODE_SMA, PRICE_CLOSE); if(hSMA==INVALID_HANDLE) { Print("[FATAL] SMA handle failed."); return INIT_FAILED; } } trade.SetDeviationInPoints(InpMaxSlippagePoints); Print("[INIT] CascadeGreepEA on ", g_symbol, ", start equity=", g_start_equity); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(hATR!=INVALID_HANDLE) IndicatorRelease(hATR); if(hRSI!=INVALID_HANDLE) IndicatorRelease(hRSI); if(hSMA!=INVALID_HANDLE) IndicatorRelease(hSMA); } void OnTick() { DailyResetIfNeeded(); if(CheckKillSwitch()) return; if(CheckDailyLossLimit()) return; ManageTrailing(); if(NewBarOnSignalTF()) TryEnter(); } //+------------------------------------------------------------------+