//+------------------------------------------------------------------+ //| Adaptive MultiMarket EA v1.00 | //| Prototype based on the first five user-defined feature groups. | //| AI is optional and OFF by default; configure a compatible local | //| HTTP service to enable it. | //+------------------------------------------------------------------+ #property strict #property version "1.00" #property description "Multi-symbol configurable EA: technical/momentum confirmation, ATR exits, recovery sizing, profit lock, CSV log." #include CTrade trade; //--- General input ulong InpMagicNumber=2026110201; input bool InpAllowBuy=true; input bool InpAllowSell=true; input int InpMaxSlippagePoints=30; input bool InpOnePositionPerSymbol=true; input bool InpUseGlobalRecoveryLock=true; input int InpLossesBeforeGlobalLock=3; // lock other new entries after 3 consecutive losses, starting with recovery trade #4 //--- Confirmation switches (any selected filters must pass) input bool InpUseTechnicalConfirmation=true; input bool InpUseMomentumConfirmation=true; input bool InpUseAIConfirmation=false; // OFF until a compatible local/API endpoint is configured input double InpAIMinProbability=51.0; input string InpAIEndpoint=""; // HTTP endpoint; response should contain buy_probability and sell_probability numbers input int InpAIRequestTimeoutMs=2500; input int InpAIHorizonMinutes=15; // 15, 30 or 60 input int InpSignalCheckSeconds=5; // evaluate a fresh signal every five minutes //--- Indicators / technical filters input ENUM_TIMEFRAMES InpTrendTF1=PERIOD_H1; input ENUM_TIMEFRAMES InpTrendTF2=PERIOD_M15; input ENUM_TIMEFRAMES InpFastTF=PERIOD_M5; input int InpSMAFast=14; input int InpSMASlow=21; input int InpADXPeriod=14; input double InpADXMinimum=18.0; input int InpRSIPeriod=14; input double InpRSIBuyMinimum=52.0; input double InpRSISellMaximum=48.0; input int InpMACDFast=12; input int InpMACDSlow=26; input int InpMACDSignal=9; input int InpATRPeriod=14; //--- Momentum: Points mode or ADR/ratio mode input bool InpUseAutomaticADRMovement=true; input int InpADRDays=30; input double InpADRDivisor=200.0; input double InpManualMovementPoints=15.0; input int InpMomentumWindowSeconds=60; // 30 or 60 input double InpMomentumMultiplier=1.0; //--- ATR exits / manual exits input bool InpUseATRStops=true; input double InpSL_ATR_Multiplier=1.5; input double InpTP_ATR_Multiplier=2.0; input double InpManualSLPoints=300.0; input double InpManualTPPoints=400.0; //--- Lot sizing and loss recovery input bool InpAutoRiskLot=true; input bool InpRiskUseEquity=false; input double InpRiskPercent=1.0; input double InpManualLot=0.01; input double InpLossLotMultiplier=2.0; input double InpMinimumLot=0.01; input double InpMaximumLot=5.0; //--- Position management input bool InpEnableProfitLock=true; input double InpLockStartPercent=60.0; input double InpLockStartSLPercent=30.0; input double InpLockStepPercent=10.0; input double InpLockStepSLStart=30.0; input double InpLockAt95Percent=85.0; input double InpPost95TrailKeepPercent=32.0; input bool InpExtendTPAfter95=true; input double InpExtendedTP_ATR_Multiplier=3.0; //--- Reporting input bool InpEnableCSVReport=true; input bool InpSeparateReportByDate=true; input string InpReportFolder="AdaptiveEA"; int hFast1=INVALID_HANDLE,hSlow1=INVALID_HANDLE,hFast2=INVALID_HANDLE,hSlow2=INVALID_HANDLE; int hADX=INVALID_HANDLE,hRSI=INVALID_HANDLE,hMACD=INVALID_HANDLE,hATR=INVALID_HANDLE; datetime g_lastSignalCheck=0; double g_recentBid=0.0; datetime g_recentTickTime=0; double g_momentumStartBid=0.0; datetime g_momentumStartTime=0; string g_symbol; string Key(const string suffix){ return "AM_EA_"+(string)InpMagicNumber+"_"+g_symbol+"_"+suffix; } string GlobalLockKey(){ return "AM_EA_"+(string)InpMagicNumber+"_GLOBAL_LOCK"; } string GlobalOwnerKey(){ return "AM_EA_"+(string)InpMagicNumber+"_GLOBAL_OWNER"; } double SymbolCode(){ double h=0.0; for(int i=0;i=0;i--) { ulong d=HistoryDealGetTicket(i); if(d==0) continue; if(HistoryDealGetString(d,DEAL_SYMBOL)!=g_symbol) continue; if((ulong)HistoryDealGetInteger(d,DEAL_MAGIC)!=InpMagicNumber) continue; ENUM_DEAL_ENTRY e=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(d,DEAL_ENTRY); if(e!=DEAL_ENTRY_OUT && e!=DEAL_ENTRY_OUT_BY) continue; if(d==lastSeen) continue; lastSeen=d; double net=HistoryDealGetDouble(d,DEAL_PROFIT)+HistoryDealGetDouble(d,DEAL_SWAP)+HistoryDealGetDouble(d,DEAL_COMMISSION); if(net<0.0) losses++; else if(net>0.0) break; } return losses; } bool HasOurPosition() { for(int i=PositionsTotal()-1;i>=0;i--) { ulong ticket=PositionGetTicket(i); if(ticket==0) continue; if(PositionGetString(POSITION_SYMBOL)==g_symbol && (ulong)PositionGetInteger(POSITION_MAGIC)==InpMagicNumber) return true; } return false; } bool GlobalLockActive() { if(!InpUseGlobalRecoveryLock) return false; if(!GlobalVariableCheck(GlobalLockKey())) return false; return GlobalVariableGet(GlobalLockKey())>0.5; } void SetGlobalLock(const bool on){ if(InpUseGlobalRecoveryLock){ GlobalVariableSet(GlobalLockKey(),on?1.0:0.0); if(on) GlobalVariableSet(GlobalOwnerKey(),SymbolCode()); } } bool GetATR(double &atr){ return ReadBuffer(hATR,0,1,atr) && atr>0.0; } double AverageDailyRange() { int n=MathMax(1,InpADRDays); MqlRates rates[]; ArraySetAsSeries(rates,true); int got=CopyRates(g_symbol,PERIOD_D1,1,n,rates); if(got<=0) return 0.0; double sum=0.0; int count=0; for(int i=0;i0){sum+=r;count++;} } if(count==0) return 0.0; return sum/(double)count; } double RequiredMovementPrice() { if(InpUseAutomaticADRMovement) { double adr=AverageDailyRange(); if(adr>0.0 && InpADRDivisor>0.0) return (adr/InpADRDivisor)*InpMomentumMultiplier; } return InpManualMovementPoints*_Point*InpMomentumMultiplier; } bool TechnicalSignal(int &direction,double &atr,double &rsi,double &adx,double &macdMain,double &macdSignal) { direction=0; double f1,s1,f2,s2; if(!ReadBuffer(hFast1,0,1,f1)||!ReadBuffer(hSlow1,0,1,s1)||!ReadBuffer(hFast2,0,1,f2)||!ReadBuffer(hSlow2,0,1,s2)) return false; if(!ReadBuffer(hADX,0,1,adx)||!ReadBuffer(hRSI,0,1,rsi)||!ReadBuffer(hMACD,0,1,macdMain)||!ReadBuffer(hMACD,1,1,macdSignal)||!GetATR(atr)) return false; double close1=iClose(g_symbol,InpTrendTF2,1); if(close1<=0) return false; bool buy=(f1>s1 && f2>s2 && close1>f2 && adx>=InpADXMinimum && rsi>=InpRSIBuyMinimum && macdMain>macdSignal); bool sell=(f1=InpADXMinimum && rsi<=InpRSISellMaximum && macdMainMathMax(1,InpMomentumWindowSeconds)) { g_momentumStartTime=now; g_momentumStartBid=tick.bid; return false; } moved=MathAbs(tick.bid-g_momentumStartBid); double need=RequiredMovementPrice(); if(need<=0) return false; if(direction>0) return (tick.bid-g_momentumStartBid)>=need; if(direction<0) return (g_momentumStartBid-tick.bid)>=need; return false; } // Optional HTTP JSON service. Add endpoint to MT5: Tools > Options > Expert Advisors > Allow WebRequest. bool AIConfirm(const int direction,double &probability) { probability=0.0; if(!InpUseAIConfirmation) return true; if(StringLen(InpAIEndpoint)==0) return false; string payload="{\"symbol\":\""+g_symbol+"\",\"horizon_minutes\":"+(string)InpAIHorizonMinutes+",\"direction\":\""+(direction>0?"BUY":"SELL")+"\"}"; char data[],result[]; StringToCharArray(payload,data,0,WHOLE_ARRAY,CP_UTF8); string headers="Content-Type: application/json\r\n"; string response_headers; ResetLastError(); int code=WebRequest("POST",InpAIEndpoint,headers,InpAIRequestTimeoutMs,data,result,response_headers); if(code<200 || code>=300) return false; string body=CharArrayToString(result,0,WHOLE_ARRAY,CP_UTF8); string key=(direction>0?"buy_probability":"sell_probability"); int pos=StringFind(body,key); if(pos<0) return false; pos=StringFind(body,":",pos); if(pos<0) return false; string tail=StringSubstr(body,pos+1); StringReplace(tail," ",""); StringReplace(tail,"\"",""); int end=StringFind(tail,","); if(end<0) end=StringFind(tail,"}"); if(end<0) return false; probability=StringToDouble(StringSubstr(tail,0,end)); return probability>=InpAIMinProbability; } bool CalculateStops(const int direction,const double atr,double &sl,double &tp) { MqlTick t; if(!SymbolInfoTick(g_symbol,t)) return false; double slDist,tpDist; if(InpUseATRStops){ slDist=atr*InpSL_ATR_Multiplier; tpDist=atr*InpTP_ATR_Multiplier; } else { slDist=InpManualSLPoints*_Point; tpDist=InpManualTPPoints*_Point; } if(slDist<=0||tpDist<=0) return false; int digits=(int)SymbolInfoInteger(g_symbol,SYMBOL_DIGITS); if(direction>0){ sl=NormalizeDouble(t.ask-slDist,digits); tp=NormalizeDouble(t.ask+tpDist,digits); } else { sl=NormalizeDouble(t.bid+slDist,digits); tp=NormalizeDouble(t.bid-tpDist,digits); } long stops=SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL); double minDist=stops*_Point; if(direction>0 && (t.ask-sl0 && tickSize>0 && slDistance>0) base=riskMoney/(slDistance/tickSize*tickVal); } double lots=base*MathPow(MathMax(1.0,InpLossLotMultiplier),losses); return NormalizeVolume(lots); } void WriteReport(const string event,const int direction,const double probability,const double rsi,const double adx,const double macd,const double atr,const double moved,const double lots,const double price,const double sl,const double tp,const double pnl=0.0) { if(!InpEnableCSVReport) return; string day=TimeToString(TimeCurrent(),TIME_DATE); StringReplace(day,".",""); string filename=InpReportFolder+"/"+g_symbol+"_"+day+".csv"; int flags=FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE; int f=FileOpen(filename,flags,';'); if(f==INVALID_HANDLE) return; if(FileSize(f)==0) FileWrite(f,"Time","Event","Symbol","Direction","AI_Probability","RSI","ADX","MACD_Main","ATR","Momentum_PriceMove","Lots","Price","SL","TP","NetPnL"); FileSeek(f,0,SEEK_END); FileWrite(f,TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),event,g_symbol,(direction>0?"BUY":(direction<0?"SELL":"NA")),DoubleToString(probability,2),DoubleToString(rsi,2),DoubleToString(adx,2),DoubleToString(macd,6),DoubleToString(atr,_Digits),DoubleToString(moved,_Digits),DoubleToString(lots,2),DoubleToString(price,_Digits),DoubleToString(sl,_Digits),DoubleToString(tp,_Digits),DoubleToString(pnl,2)); FileClose(f); } void ManagePosition() { if(!InpEnableProfitLock) return; for(int i=PositionsTotal()-1;i>=0;i--) { ulong ticket=PositionGetTicket(i); if(ticket==0) continue; if(PositionGetString(POSITION_SYMBOL)!=g_symbol || (ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagicNumber) continue; long type=PositionGetInteger(POSITION_TYPE); bool buy=(type==POSITION_TYPE_BUY); double entry=PositionGetDouble(POSITION_PRICE_OPEN),sl=PositionGetDouble(POSITION_SL),tp=PositionGetDouble(POSITION_TP); double current=buy?SymbolInfoDouble(g_symbol,SYMBOL_BID):SymbolInfoDouble(g_symbol,SYMBOL_ASK); double initialTP=GlobalVariableCheck(Key("INITIAL_TP"))?GlobalVariableGet(Key("INITIAL_TP")):tp; double initialSL=GlobalVariableCheck(Key("INITIAL_SL"))?GlobalVariableGet(Key("INITIAL_SL")):sl; double full=MathAbs(initialTP-entry); if(full<=0) continue; double progress=(buy?(current-entry):(entry-current))/full*100.0; double desiredSL=sl; if(progress>=InpLockAt95Percent) { double locked=full*(InpLockAt95Percent/100.0); double extra=MathMax(0.0,(buy?(current-entry):(entry-current))-locked); double lockDist=locked+extra*InpPost95TrailKeepPercent/100.0; desiredSL=buy?entry+lockDist:entry-lockDist; if(InpExtendTPAfter95) { double atr; if(GetATR(atr)) { double ext=atr*InpExtendedTP_ATR_Multiplier; double newTP=buy?current+ext:current-ext; if(buy && newTP>tp) tp=newTP; if(!buy && newTP=InpLockStartPercent) { double step=InpLockStepPercent; if(step<=0) step=10; double stage=MathFloor((progress-InpLockStartPercent)/step); double lockPct=InpLockStepSLStart+stage*step; if(progress>=InpLockAt95Percent) lockPct=InpLockAt95Percent; lockPct=MathMin(lockPct,InpLockAt95Percent); if(lockPctsl && desiredSLcurrent))); if(improve || (tp!=PositionGetDouble(POSITION_TP))) trade.PositionModify(ticket,improve?desiredSL:sl,tp); } } void TryOpenTrade() { if(InpOnePositionPerSymbol && HasOurPosition()) return; if(GlobalLockActive()) return; int dir=0; double atr=0,rsi=0,adx=0,macd=0,macds=0; if(InpUseTechnicalConfirmation) { if(!TechnicalSignal(dir,atr,rsi,adx,macd,macds) || dir==0) return; } else { if(!GetATR(atr)) atr=SymbolInfoDouble(g_symbol,SYMBOL_POINT)*100.0; // Without technical filter, direction is inferred from short-term SMA on M5. double a=iClose(g_symbol,InpFastTF,1),b=iClose(g_symbol,InpFastTF,2); if(a==0||b==0||a==b) return; dir=(a>b?1:-1); } if((dir>0&&!InpAllowBuy)||(dir<0&&!InpAllowSell)) return; double prob=0.0; if(InpUseAIConfirmation && !AIConfirm(dir,prob)) return; double moved=0.0; if(InpUseMomentumConfirmation && !MomentumPass(dir,moved)) return; double sl,tp; if(!CalculateStops(dir,atr,sl,tp)) return; double price=(dir>0?SymbolInfoDouble(g_symbol,SYMBOL_ASK):SymbolInfoDouble(g_symbol,SYMBOL_BID)); double lots=CalculateLots(MathAbs(price-sl)); if(lots<=0) return; trade.SetExpertMagicNumber(InpMagicNumber); trade.SetDeviationInPoints(InpMaxSlippagePoints); bool ok=(dir>0?trade.Buy(lots,g_symbol,0.0,sl,tp,"AdaptiveEA BUY"):trade.Sell(lots,g_symbol,0.0,sl,tp,"AdaptiveEA SELL")); if(ok) { GlobalVariableSet(Key("INITIAL_TP"),tp); GlobalVariableSet(Key("INITIAL_SL"),sl); if(ConsecutiveLosses()>=InpLossesBeforeGlobalLock) SetGlobalLock(true); WriteReport("OPEN",dir,prob,rsi,adx,macd,atr,moved,lots,price,sl,tp,0.0); } } int OnInit() { g_symbol=_Symbol; hFast1=iMA(g_symbol,InpTrendTF1,InpSMAFast,0,MODE_SMA,PRICE_CLOSE); hSlow1=iMA(g_symbol,InpTrendTF1,InpSMASlow,0,MODE_SMA,PRICE_CLOSE); hFast2=iMA(g_symbol,InpTrendTF2,InpSMAFast,0,MODE_SMA,PRICE_CLOSE); hSlow2=iMA(g_symbol,InpTrendTF2,InpSMASlow,0,MODE_SMA,PRICE_CLOSE); hADX=iADX(g_symbol,InpFastTF,InpADXPeriod); hRSI=iRSI(g_symbol,InpFastTF,InpRSIPeriod,PRICE_CLOSE); hMACD=iMACD(g_symbol,InpFastTF,InpMACDFast,InpMACDSlow,InpMACDSignal,PRICE_CLOSE); hATR=iATR(g_symbol,InpFastTF,InpATRPeriod); trade.SetExpertMagicNumber(InpMagicNumber); trade.SetDeviationInPoints(InpMaxSlippagePoints); if(InpEnableCSVReport) FolderCreate(InpReportFolder); EventSetTimer(1); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); if(hFast1!=INVALID_HANDLE) IndicatorRelease(hFast1); if(hSlow1!=INVALID_HANDLE) IndicatorRelease(hSlow1); if(hFast2!=INVALID_HANDLE) IndicatorRelease(hFast2); if(hSlow2!=INVALID_HANDLE) IndicatorRelease(hSlow2); if(hADX!=INVALID_HANDLE) IndicatorRelease(hADX); if(hRSI!=INVALID_HANDLE) IndicatorRelease(hRSI); if(hMACD!=INVALID_HANDLE) IndicatorRelease(hMACD); if(hATR!=INVALID_HANDLE) IndicatorRelease(hATR); } void OnTick() { ManagePosition(); int checkInterval=MathMax(1,InpSignalCheckSeconds); if(InpUseMomentumConfirmation) { int maxInterval=MathMax(1,InpMomentumWindowSeconds/6); checkInterval=MathMin(checkInterval,maxInterval); } datetime now=TimeCurrent(); if(g_lastSignalCheck==0 || (now-g_lastSignalCheck)>=checkInterval) { g_lastSignalCheck=now; TryOpenTrade(); } } void OnTimer() { ManagePosition(); // Global lock is released by the recovery-owner chart after its profitable recovery close. } void OnTradeTransaction(const MqlTradeTransaction &trans,const MqlTradeRequest &request,const MqlTradeResult &result) { if(trans.type!=TRADE_TRANSACTION_DEAL_ADD || trans.deal==0) return; if(!HistoryDealSelect(trans.deal)) return; if(HistoryDealGetString(trans.deal,DEAL_SYMBOL)!=g_symbol) return; if((ulong)HistoryDealGetInteger(trans.deal,DEAL_MAGIC)!=InpMagicNumber) return; ENUM_DEAL_ENTRY e=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal,DEAL_ENTRY); if(e!=DEAL_ENTRY_OUT && e!=DEAL_ENTRY_OUT_BY) return; double pnl=HistoryDealGetDouble(trans.deal,DEAL_PROFIT)+HistoryDealGetDouble(trans.deal,DEAL_SWAP)+HistoryDealGetDouble(trans.deal,DEAL_COMMISSION); if(pnl>0.0 && ConsecutiveLosses()==0 && GlobalLockActive() && GlobalVariableCheck(GlobalOwnerKey()) && GlobalVariableGet(GlobalOwnerKey())==SymbolCode()) SetGlobalLock(false); WriteReport("CLOSE",0,0,0,0,0,0,0,HistoryDealGetDouble(trans.deal,DEAL_VOLUME),HistoryDealGetDouble(trans.deal,DEAL_PRICE),0,0,pnl); } //+------------------------------------------------------------------+