diff --git a/PaPP trading/PaPP_Trading.mq5 b/PaPP trading/PaPP_Trading.mq5 new file mode 100644 index 0000000..ed7a96e --- /dev/null +++ b/PaPP trading/PaPP_Trading.mq5 @@ -0,0 +1,407 @@ +//+------------------------------------------------------------------+ +//| PaPP_Trading.mq5 | +//| PaPP Trading | +//+------------------------------------------------------------------+ +#property copyright "PaPP Trading" +#property version "2.00" +#property description "PaPP Trading - Multi timeframe MA + volatilita + accelerazione" +#property indicator_chart_window +#property indicator_buffers 10 +#property indicator_plots 10 + +input bool ShowLines = false; +input int FontSize = 9; +input int XPos = 10; +input int YPos = 30; +input int TP_Points = 100; // Take Profit (punti) +input int SL_Points = 10000; // Stop Loss (punti) +input bool ShowTPSL = true; // Draw TP/SL lines on chart + +//--- indicator buffers (SetIndexBuffer bound) +double B1Y[], B6M[], B4M[], B1Mo[], B2W[], B1W[], B3D[], B1D[], BSc[], BAc[]; + +//--- handles +int hMA[8]; +int bars[8]; +string pN[8] = {"1Y","6M","4M","1M","2W","1W","3G","1G"}; +string _pfx = "PP_"; + +//--- rolling volatility window +double hMin[8], hMax[8]; +int volResetBar[8]; // bar index (non-series) of last reset +int WINDOW = 200; // rolling window bars + +//--- last bar tracking for dashboard update +datetime lastBarTime = 0; + +//+------------------------------------------------------------------+ +int TimeToBars(int days) + { + datetime now = TimeCurrent(); + if(now==0) + { + long s = (long)days*86400L; + long p = PeriodSeconds((ENUM_TIMEFRAMES)_Period); + return (int)MathMax(1,s/p); + } + datetime then = now - days*86400; + int cnt = Bars(_Symbol,_Period,then,now); + return MathMax(1,cnt); + } + +//+------------------------------------------------------------------+ +int OnInit() + { + IndicatorSetString(INDICATOR_SHORTNAME,"PaPP Trading v2"); + IndicatorSetInteger(INDICATOR_DIGITS,_Digits); + + int days[8] = {365,182,121,30,14,7,3,1}; + for(int i=0;i<8;i++) + { + bars[i] = (i<7) ? TimeToBars(days[i]) : 1; + hMA[i]=iMA(_Symbol,_Period,bars[i],0,MODE_SMA,PRICE_CLOSE); + if(hMA[i]==INVALID_HANDLE) return INIT_FAILED; + } + + SetIndexBuffer(0,B1Y,INDICATOR_DATA); ArraySetAsSeries(B1Y,true); + SetIndexBuffer(1,B6M,INDICATOR_DATA); ArraySetAsSeries(B6M,true); + SetIndexBuffer(2,B4M,INDICATOR_DATA); ArraySetAsSeries(B4M,true); + SetIndexBuffer(3,B1Mo,INDICATOR_DATA); ArraySetAsSeries(B1Mo,true); + SetIndexBuffer(4,B2W,INDICATOR_DATA); ArraySetAsSeries(B2W,true); + SetIndexBuffer(5,B1W,INDICATOR_DATA); ArraySetAsSeries(B1W,true); + SetIndexBuffer(6,B3D,INDICATOR_DATA); ArraySetAsSeries(B3D,true); + SetIndexBuffer(7,B1D,INDICATOR_DATA); ArraySetAsSeries(B1D,true); + SetIndexBuffer(8,BSc,INDICATOR_DATA); ArraySetAsSeries(BSc,true); + SetIndexBuffer(9,BAc,INDICATOR_DATA); ArraySetAsSeries(BAc,true); + + color cl[10] = {clrRed,clrOrange,clrGold,clrLimeGreen, + clrDodgerBlue,clrViolet,clrBrown,clrGray,clrCyan,clrMagenta}; + for(int i=0;i<8;i++) + { + PlotIndexSetInteger(i,PLOT_DRAW_TYPE,ShowLines?DRAW_LINE:DRAW_NONE); + PlotIndexSetInteger(i,PLOT_LINE_COLOR,cl[i]); + PlotIndexSetString(i,PLOT_LABEL,pN[i]+" MA"); + PlotIndexSetInteger(i,PLOT_LINE_WIDTH,2); + } + PlotIndexSetInteger(8,PLOT_DRAW_TYPE,DRAW_NONE); + PlotIndexSetInteger(9,PLOT_DRAW_TYPE,DRAW_NONE); + + for(int i=0;i<8;i++) { hMin[i]=1e10; hMax[i]=-1e10; volResetBar[i]=0; } + + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + for(int i=0;i<8;i++) + if(hMA[i]!=INVALID_HANDLE) IndicatorRelease(hMA[i]); + ObjectsDeleteAll(0,_pfx); + } + +//+------------------------------------------------------------------+ +//| Buffer helpers (separate MA index from bar index) | +//+------------------------------------------------------------------+ +void SetMA(int b, int bar, double v) + { + if(b==0) B1Y[bar]=v; else if(b==1) B6M[bar]=v; else if(b==2) B4M[bar]=v; + else if(b==3) B1Mo[bar]=v; else if(b==4) B2W[bar]=v; else if(b==5) B1W[bar]=v; + else if(b==6) B3D[bar]=v; else if(b==7) B1D[bar]=v; + } +double GetMA(int bar, int m) + { + if(m==0) return B1Y[bar]; if(m==1) return B6M[bar]; if(m==2) return B4M[bar]; + if(m==3) return B1Mo[bar]; if(m==4) return B2W[bar]; if(m==5) return B1W[bar]; + if(m==6) return B3D[bar]; return B1D[bar]; + } + +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const datetime &time[], + const double &open[], + const double &high[], + const double &low[], + const double &close[], + const long &tick_volume[], + const long &volume[], + const int &spread[]) + { + if(rates_total<2) return 0; + + ArraySetAsSeries(close,true); + ArraySetAsSeries(time,true); + + //--- limit = number of new bars to process + int limit = rates_total - prev_calculated; + if(limit>1) limit = rates_total-1; + + //--- Copy only the needed range (performance) + // needed = limit+1 bars from position startPos + int needed = limit + 1; + int startPos = rates_total - needed; + if(startPos<0) { startPos=0; needed=rates_total; } + + //--- Temp arrays for MA data + double tmp[8][]; + for(int m=0;m<8;m++) + { + ArrayResize(tmp[m],needed); + int copied = CopyBuffer(hMA[m],0,startPos,needed,tmp[m]); + if(copied0?copied:0);i indicator buffers (series indexed: idx=0=newest) + for(int idx=limit; idx>=0; idx--) + { + double mv[8]; + for(int m=0;m<8;m++) + { + if(idxmv[m]) ? 1 : -1; + BSc[idx]=sc; + + //--- Acceleration + double aSum=0; + for(int m=0;m<7;m++) aSum+=(vel[m]-vel[m+1]); + BAc[idx]=aSum/7.0; + + //--- Rolling volatility window: reset & rebuild every WINDOW bars + // volResetBar tracks the non-series bar index of last reset + // Non-series index = rates_total - 1 - idx (since idx is series) + int nsIdx = rates_total - 1 - idx; // non-series index (0=oldest) + if(nsIdx == volResetBar[0] + WINDOW) + { + // Time to reset: scan back WINDOW bars to recompute min/max + for(int m=0;m<8;m++) { hMin[m]=1e10; hMax[m]=-1e10; } + int scanStart = MathMax(0, nsIdx - WINDOW); + int scanEnd = MathMin(rates_total-1, nsIdx); + for(int s=scanStart; s<=scanEnd; s++) + { + int si = rates_total - 1 - s; // series index + double pp = close[si]; + for(int m=0;m<8;m++) + { + double vm = GetMA(si,m); + if(vm>0) + { + double d = MathAbs(pp - vm); + if(dhMax[m]) hMax[m]=d; + } + } + } + for(int m=0;m<8;m++) volResetBar[m]=nsIdx; + } + else + { + // Normal update: extend min/max if this bar pushes them + for(int m=0;m<8;m++) + { + if(mv[m]>0) + { + double d = MathAbs(p-mv[m]); + if(dhMax[m]) hMax[m]=d; + } + } + } + } + + //--- Dashboard: only on new bar or first run + bool newBar = (time[0]!=lastBarTime); + if(newBar || prev_calculated==0) + { + lastBarTime = time[0]; + DrawDashboard(); + } + + return rates_total; + } + +//+------------------------------------------------------------------+ +void DrawDashboard() + { + double mv[8], vel[8]; + double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); + + for(int m=0;m<8;m++) + { + mv[m]=GetMA(0,m); + double prv = (GetMA(1,m)>0) ? GetMA(1,m) : mv[m]; + vel[m]=mv[m]-prv; + } + + string tf = EnumToString((ENUM_TIMEFRAMES)_Period); + int x=XPos, y=YPos, lh=FontSize+4; + + //--- Title + Lbl("T",_Symbol+" PaPP ["+tf+"] v2",x,y,FontSize+2,clrGold,true); + y+=lh+4; + + double c0=GetMA(0,7); // B1D[0] = SMA(1) = latest close + double c1=GetMA(1,7); + Lbl("P","Oggi: "+DTS(c0,_Digits)+" | Ieri: "+DTS(c1,_Digits),x,y,FontSize,clrWhite,false); + y+=lh+2; + + Lbl("S1","------------------------------",x,y,FontSize-1,clrGold,false); + y+=lh; + + string hdr = StringFormat("%-6s %-12s %-4s %-8s %-5s %-7s","Periodo","Media","Pos","Dist","Vol%","Veloc"); + Lbl("H",hdr,x,y,FontSize,clrGold,false); + y+=lh; + + for(int m=0;m<8;m++) + { + if(mv[m]<=0) continue; + double dist = bid-mv[m]; + double absD = MathAbs(dist); + + //--- Volatility % (rolling window) + double vn=50.0; + if(hMax[m]>hMin[m] && hMax[m]>0) + { vn=((absD-hMin[m])/(hMax[m]-hMin[m]))*100.0; vn=MathMax(0,MathMin(100,vn)); } + + string row = StringFormat("%-6s %-12.*f %-4s %-+8.*f %-5.0f%% %-+7.*f", + pN[m],_Digits,mv[m], + (dist>0)?"SU":"GIU", + _Digits,dist,vn,_Digits,vel[m]); + Lbl("R"+(string)m,row,x,y,FontSize,(dist>0)?clrLimeGreen:clrRed,false); + y+=lh; + } + + Lbl("S2","------------------------------",x,y,FontSize-1,clrGold,false); + y+=lh; + + //--- Score + int sc = (int)BSc[0]; + string sS = StringFormat("SCORE: %+d/8",sc); + string sD = " [LATERALE]"; + if(sc>=5) sD=" [IPERCOMPRATO]"; + else if(sc<=-5) sD=" [IPERVENDUTO]"; + else if(sc>=2) sD=" [RIALZO]"; + else if(sc<=-2) sD=" [RIBASSO]"; + Lbl("Sc",sS+sD,x,y,FontSize+1,(sc>=2)?clrLimeGreen:(sc<=-2)?clrRed:clrGray,true); + y+=lh+2; + + //--- Acceleration (threshold based on _Point) + double acc = BAc[0]; + double accThresh = _Point * 10.0; + string aS = StringFormat("ACCEL: %+.*f",_Digits+1,acc); + string aD = " [STABILE/FRENATA]"; + if(acc>accThresh) aD=" [ACCELERAZIONE +]"; + else if(acc<-accThresh) aD=" [ACCELERAZIONE -]"; + Lbl("Ac",aS+aD,x,y,FontSize+1,(acc>accThresh)?clrLimeGreen:(acc<-accThresh)?clrRed:clrGray,true); + y+=lh; + + //--- Average volatility + double vAvg=0; int vCnt=0; + for(int m=0;m<8;m++) + { + if(mv[m]>0 && hMax[m]>hMin[m] && hMax[m]>0) + { + double ad=MathAbs(bid-mv[m]); + double vn=((ad-hMin[m])/(hMax[m]-hMin[m]))*100.0; + vAvg+=MathMax(0,MathMin(100,vn)); vCnt++; + } + } + if(vCnt>0) vAvg/=vCnt; + string vS = StringFormat("VOL: %.0f%%",vAvg); + string vD = " [NORMALE]"; + if(vAvg>70) vD=" [ALTA - breakout in corso]"; + else if(vAvg<40) vD=" [BASSA - compressione]"; + Lbl("Vo",vS+vD,x,y,FontSize+1,(vAvg>70)?clrRed:(vAvg>40)?clrYellow:clrLimeGreen,true); + y+=lh+2; + Lbl("TPSL","TP: +"+string(TP_Points)+" pt ("+DTS(bid+TP_Points*_Point,_Digits)+ + ") | SL: -"+string(SL_Points)+" pt ("+DTS(bid-SL_Points*_Point,_Digits)+")", + x,y,FontSize,clrGray,false); + y+=lh+6; + + //--- TP/SL lines on chart + if(ShowTPSL) + { + string tpName = _pfx+"TPLINE"; + string slName = _pfx+"SLLINE"; + double tpPrice = bid + TP_Points * _Point; + double slPrice = bid - SL_Points * _Point; + if(ObjectFind(0,tpName)<0) ObjectCreate(0,tpName,OBJ_HLINE,0,0,0); + if(ObjectFind(0,slName)<0) ObjectCreate(0,slName,OBJ_HLINE,0,0,0); + ObjectSetDouble(0,tpName,OBJPROP_PRICE,tpPrice); + ObjectSetDouble(0,slName,OBJPROP_PRICE,slPrice); + ObjectSetInteger(0,tpName,OBJPROP_COLOR,clrLimeGreen); + ObjectSetInteger(0,slName,OBJPROP_COLOR,clrRed); + ObjectSetInteger(0,tpName,OBJPROP_WIDTH,1); + ObjectSetInteger(0,slName,OBJPROP_WIDTH,1); + ObjectSetInteger(0,tpName,OBJPROP_STYLE,STYLE_DASHDOT); + ObjectSetInteger(0,slName,OBJPROP_STYLE,STYLE_DASHDOT); + ObjectSetString(0,tpName,OBJPROP_TEXT,"TP +"+string(TP_Points)+" ("+DTS(tpPrice,_Digits)+")"); + ObjectSetString(0,slName,OBJPROP_TEXT,"SL -"+string(SL_Points)+" ("+DTS(slPrice,_Digits)+")"); + ObjectSetInteger(0,tpName,OBJPROP_BACK,true); + ObjectSetInteger(0,slName,OBJPROP_BACK,true); + ObjectSetInteger(0,tpName,OBJPROP_SELECTABLE,false); + ObjectSetInteger(0,slName,OBJPROP_SELECTABLE,false); + } + else + { + if(ObjectFind(0,_pfx+"TPLINE")>=0) ObjectDelete(0,_pfx+"TPLINE"); + if(ObjectFind(0,_pfx+"SLLINE")>=0) ObjectDelete(0,_pfx+"SLLINE"); + } + + Lbl("L0","LEGENDA:",x,y,FontSize,clrGold,true); + y+=lh; + Lbl("L1","SU/GIU = prezzo sopra/sotto media | Vol% = 0(min)-100(max) rolling "+string(WINDOW),x,y,FontSize-1,clrWhite,false); + y+=lh-2; + Lbl("L2","Score +8=tutto rialzo -8=tutto ribasso ~0=laterale",x,y,FontSize-1,clrWhite,false); + y+=lh-2; + Lbl("L3","Accel +=forza in aumento -=forza in calo ~0=stabile/frenata",x,y,FontSize-1,clrWhite,false); + y+=lh-2; + Lbl("L4","Vol>70%=esplosione Vol<40%=compressione (molla)",x,y,FontSize-1,clrWhite,false); + } + +//+------------------------------------------------------------------+ +void Lbl(string n,string t,int x,int y,int fs,color c,bool b) + { + string o=_pfx+n; + if(ObjectFind(0,o)<0) ObjectCreate(0,o,OBJ_LABEL,0,0,0); + ObjectSetInteger(0,o,OBJPROP_XDISTANCE,x); + ObjectSetInteger(0,o,OBJPROP_YDISTANCE,y); + ObjectSetInteger(0,o,OBJPROP_CORNER,CORNER_LEFT_UPPER); + ObjectSetString(0,o,OBJPROP_TEXT,t); + ObjectSetString(0,o,OBJPROP_FONT,b?"Lucida Console Bold":"Lucida Console"); + ObjectSetInteger(0,o,OBJPROP_FONTSIZE,fs); + ObjectSetInteger(0,o,OBJPROP_COLOR,c); + ObjectSetInteger(0,o,OBJPROP_BACK,false); + ObjectSetInteger(0,o,OBJPROP_SELECTABLE,false); + ObjectSetInteger(0,o,OBJPROP_HIDDEN,true); + } + +//+------------------------------------------------------------------+ +string DTS(double v,int d) { return DoubleToString(v,d); } +//+------------------------------------------------------------------+ diff --git a/PaPP trading/PaPP_Trading_EA.mq5 b/PaPP trading/PaPP_Trading_EA.mq5 new file mode 100644 index 0000000..20d2a12 --- /dev/null +++ b/PaPP trading/PaPP_Trading_EA.mq5 @@ -0,0 +1,208 @@ +//+------------------------------------------------------------------+ +//| PaPP_Trading_EA.mq5 | +//| PaPP Trading | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +#property copyright "PaPP Trading" +#property version "1.03" +#property description "PaPP Trading EA - Mean Reversion su Score multi-TF" +#property description "Compra quando Score <= BuyThresh (sotto media = ipervenduto)" +#property description "Vende quando Score >= SellThresh (sopra media = ipercomprato)" + +input double LotSize = 0.01; +input int TP_Points = 100; +input int SL_Points = 10000; // ~1000 pips, safety net largo +input int BuyThresh = -3; +input int SellThresh = 3; +input bool UseAccel = false; +input bool DebugPrint = true; +input int MinValidMAs = 4; +input int Magic = 2024001; +input int Slippage = 30; + +int hMA[8]; +int bars[8]; +string pN[8] = {"1Y","6M","4M","1M","2W","1W","3G","1G"}; +datetime lastBar = 0; +ulong myTicket = 0; + +//+------------------------------------------------------------------+ +int TimeToBars(int days) + { + datetime now = TimeCurrent(); + if(now==0) + { + long s = (long)days*86400L; + long p = PeriodSeconds((ENUM_TIMEFRAMES)_Period); + return (int)MathMax(1,s/p); + } + datetime then = now - days*86400; + int cnt = Bars(_Symbol,_Period,then,now); + return MathMax(1,cnt); + } + +//+------------------------------------------------------------------+ +int OnInit() + { + int days[8] = {365,182,121,30,14,7,3,1}; + for(int i=0;i<8;i++) + { + bars[i] = (i<7) ? TimeToBars(days[i]) : 1; + hMA[i] = iMA(_Symbol,_Period,bars[i],0,MODE_SMA,PRICE_CLOSE); + if(hMA[i]==INVALID_HANDLE) return INIT_FAILED; + } + myTicket = 0; + Print("=== PaPP v1.03 INIT ==="); + for(int i=0;i<8;i++) Print(" MA[",i,"] ",pN[i]," bars=",bars[i]); + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + for(int i=0;i<8;i++) + if(hMA[i]!=INVALID_HANDLE) IndicatorRelease(hMA[i]); + } + +//+------------------------------------------------------------------+ +double GetMA(int idx,int m) + { + double buf[1]; + // idx=0 -> last COMPLETED bar (start_pos=1) + // idx=1 -> second-to-last (start_pos=2) + if(CopyBuffer(hMA[m],0,idx+1,1,buf)==1) return buf[0]; + return 0; + } + +//+------------------------------------------------------------------+ +void OnTick() + { + datetime curBar = iTime(_Symbol,_Period,0); + if(curBar==lastBar) return; + lastBar = curBar; + + double close0 = iClose(_Symbol,_Period,0); + if(close0<=0) return; + + double mv[8], vel[8]; + int validMAs=0; + for(int m=0;m<8;m++) + { + mv[m] = GetMA(0,m); + if(mv[m]>0) validMAs++; + double prv = GetMA(1,m); + if(prv<=0) prv=mv[m]; + vel[m] = mv[m] - prv; + } + + if(DebugPrint) + { + Print("--- ",TimeToString(curBar)," Close=",DoubleToString(close0,_Digits)," Valid=",validMAs); + for(int m=0;m<8;m++) + if(mv[m]>0) + Print(" ",pN[m],"=",DoubleToString(mv[m],_Digits), + " diff=",DoubleToString(close0-mv[m],_Digits), + " vel=",DoubleToString(vel[m],6)); + } + + if(validMAs < MinValidMAs) return; + + //--- Score (-validMAs .. +validMAs) + int score = 0; + for(int m=0;m<8;m++) + if(mv[m]>0) score += (close0>mv[m]) ? 1 : -1; + + //--- Acceleration + double accel=0; + int ac=0; + for(int m=0;m<7;m++) + { + if(mv[m]>0 && mv[m+1]>0) { accel+=(vel[m]-vel[m+1]); ac++; } + } + if(ac>0) accel/=ac; + + double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT); + + if(DebugPrint) Print("Score:",score," Entry:",BuyThresh,"/",SellThresh," Accel:",DoubleToString(accel,6)); + + //--- Position check + bool hasPos = PositionSelectByTicket(myTicket); + if(!hasPos) myTicket = 0; + + //--- Exit + if(hasPos) + { + bool isBuy = (PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY); + bool shouldClose = false; + if(isBuy && score >= SellThresh) shouldClose = true; + if(!isBuy && score <= BuyThresh) shouldClose = true; + if(!shouldClose && score>=-1 && score<=1) shouldClose = true; + if(shouldClose) + { + if(DebugPrint) Print(">>> CLOSE ",isBuy?"BUY":"SELL"," score=",score); + CTrade trade; + trade.PositionClose(myTicket,Slippage); + myTicket = 0; + } + return; + } + + //--- BUY + if(score <= BuyThresh) + { + if(!UseAccel || accel > -point*10) + { + if(DebugPrint) Print(">>> BUY score=",score); + double tp = close0 + TP_Points*point; + double sl = close0 - SL_Points*point; + if(sl<0) sl = point; + MqlTradeRequest req={}; + MqlTradeResult res={}; + req.action = TRADE_ACTION_DEAL; + req.symbol = _Symbol; + req.volume = LotSize; + req.type = ORDER_TYPE_BUY; + req.price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); + req.tp = tp; + req.sl = sl; + req.deviation = Slippage; + req.magic = Magic; + req.comment = "PaPP B"+IntegerToString(score); + if(OrderSend(req,res)) + { + if(res.retcode==TRADE_RETCODE_DONE) myTicket = res.order; + else if(DebugPrint) Print("BUY fail: c",res.retcode); + } + } + } + + //--- SELL + if(score >= SellThresh) + { + if(!UseAccel || accel < point*10) + { + if(DebugPrint) Print(">>> SELL score=",score); + double tp = close0 - TP_Points*point; + double sl = close0 + SL_Points*point; + MqlTradeRequest req={}; + MqlTradeResult res={}; + req.action = TRADE_ACTION_DEAL; + req.symbol = _Symbol; + req.volume = LotSize; + req.type = ORDER_TYPE_SELL; + req.price = SymbolInfoDouble(_Symbol,SYMBOL_BID); + req.tp = tp; + req.sl = sl; + req.deviation = Slippage; + req.magic = Magic; + req.comment = "PaPP S"+IntegerToString(score); + if(OrderSend(req,res)) + { + if(res.retcode==TRADE_RETCODE_DONE) myTicket = res.order; + else if(DebugPrint) Print("SELL fail: c",res.retcode); + } + } + } + } +//+------------------------------------------------------------------+ diff --git a/PaPP v2/PaPP_Median.mq5 b/PaPP v2/PaPP_Median.mq5 new file mode 100644 index 0000000..ea69632 --- /dev/null +++ b/PaPP v2/PaPP_Median.mq5 @@ -0,0 +1,146 @@ +//+------------------------------------------------------------------+ +//| PaPP_Median.mq5 | +//| PaPP v2 | +//+------------------------------------------------------------------+ +#property copyright "PaPP v2" +#property version "2.00" +#property description "PaPP Median - Media 8 MA (1g-1y)" +#property description "Linea singola = fair value multi-TF" +#property indicator_chart_window +#property indicator_buffers 1 +#property indicator_plots 1 + +input int FontSize = 9; + +double Buff_Median[]; + +int hMA[8]; +int bars[8]; +string _pfx = "PM_"; + +//+------------------------------------------------------------------+ +int TimeToBars(int d) + { + datetime n = TimeCurrent(); + if(n==0) + { + long s = (long)d*86400L, p = PeriodSeconds((ENUM_TIMEFRAMES)_Period); + return (int)MathMax(1,s/p); + } + return MathMax(1,Bars(_Symbol,_Period,n-d*86400,n)); + } + +//+------------------------------------------------------------------+ +int OnInit() + { + int days[8] = {365,182,121,30,14,7,3,1}; + for(int i=0;i<8;i++) + { + bars[i] = (i<7) ? TimeToBars(days[i]) : 1; + hMA[i] = iMA(_Symbol,_Period,bars[i],0,MODE_SMA,PRICE_CLOSE); + if(hMA[i]==INVALID_HANDLE) return INIT_FAILED; + } + + SetIndexBuffer(0,Buff_Median,INDICATOR_DATA); + ArraySetAsSeries(Buff_Median,true); + + PlotIndexSetInteger(0,PLOT_DRAW_TYPE,DRAW_LINE); + PlotIndexSetInteger(0,PLOT_LINE_COLOR,clrGold); + PlotIndexSetInteger(0,PLOT_LINE_WIDTH,2); + PlotIndexSetString(0,PLOT_LABEL,"PaPP Median"); + IndicatorSetString(INDICATOR_SHORTNAME,"PaPP Median"); + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + for(int i=0;i<8;i++) if(hMA[i]!=INVALID_HANDLE) IndicatorRelease(hMA[i]); + ObjectsDeleteAll(0,_pfx); + } + +//+------------------------------------------------------------------+ +double GetMA(int idx,int m) + { + double buf[1]; + if(CopyBuffer(hMA[m],0,idx+1,1,buf)==1) return buf[0]; + return 0; + } + +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const datetime &time[], + const double &open[], + const double &high[], + const double &low[], + const double &close[], + const long &tick_volume[], + const long &volume[], + const int &spread[]) + { + if(rates_total<2) return 0; + ArraySetAsSeries(close,true); + + int limit = rates_total - prev_calculated; + if(limit>1) limit=rates_total-1; + + for(int idx=limit;idx>=0;idx--) + { + double sum=0; int cnt=0; + for(int m=0;m<8;m++) + { + double v = GetMA(idx,m); + if(v>0) { sum+=v; cnt++; } + } + Buff_Median[idx]=(cnt>0) ? sum/cnt : 0; + } + + DrawInfo(); + return rates_total; + } + +//+------------------------------------------------------------------+ +void DrawInfo() + { + double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); + double median = Buff_Median[0]; + if(median<=0) return; + + int x=10,y=30,lh=FontSize+4; + ObjectsDeleteAll(0,_pfx); + + Lbl("T","PaPP Median ["+EnumToString((ENUM_TIMEFRAMES)_Period)+"]",x,y,FontSize+2,clrGold,true); + y+=lh+4; + + double distPct = (bid-median)/median*100; + string distS = StringFormat("%+.2f%%",distPct); + color distC = (distPct>0)?clrRed:clrLimeGreen; + Lbl("M","Median: "+DoubleToString(median,_Digits),x,y,FontSize,clrWhite,false); y+=lh; + Lbl("D","Dist: "+distS,x,y,FontSize,distC,true); y+=lh+2; + + //--- Dispersione MA e bande (info) + double ds=0; int dc=0; + for(int m=0;m<8;m++) { double v=GetMA(0,m); if(v>0) { ds+=MathAbs(v-median)/median*100; dc++; } } + double vol = (dc>0)?ds/dc:0; + Lbl("V","Cluster +/-"+DoubleToString(vol,3)+"%",x,y,FontSize,clrGray,false); y+=lh; + Lbl("H","Sopra=SELL | Sotto=BUY",x,y,FontSize-1,clrGray,false); + } + +//+------------------------------------------------------------------+ +void Lbl(string n,string t,int x,int y,int fs,color c,bool b) + { + string o=_pfx+n; + if(ObjectFind(0,o)<0) ObjectCreate(0,o,OBJ_LABEL,0,0,0); + ObjectSetInteger(0,o,OBJPROP_XDISTANCE,x); + ObjectSetInteger(0,o,OBJPROP_YDISTANCE,y); + ObjectSetInteger(0,o,OBJPROP_CORNER,CORNER_LEFT_UPPER); + ObjectSetString(0,o,OBJPROP_TEXT,t); + ObjectSetString(0,o,OBJPROP_FONT,b?"Lucida Console Bold":"Lucida Console"); + ObjectSetInteger(0,o,OBJPROP_FONTSIZE,fs); + ObjectSetInteger(0,o,OBJPROP_COLOR,c); + ObjectSetInteger(0,o,OBJPROP_BACK,false); + ObjectSetInteger(0,o,OBJPROP_SELECTABLE,false); + ObjectSetInteger(0,o,OBJPROP_HIDDEN,true); + } +//+------------------------------------------------------------------+ diff --git a/PaPP v2/PaPP_Median_EA.mq5 b/PaPP v2/PaPP_Median_EA.mq5 new file mode 100644 index 0000000..4cb4443 --- /dev/null +++ b/PaPP v2/PaPP_Median_EA.mq5 @@ -0,0 +1,234 @@ +//+------------------------------------------------------------------+ +//| PaPP_Median_EA.mq5 | +//| PaPP v2 | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +#property copyright "PaPP v2" +#property version "2.00" +#property description "PaPP Median EA - Mean Reversion puro" +#property description "Linea mediana (media 8 MA 1g-1y) = unico segnale" +#property description "Sopra = SELL | Sotto = BUY" + +input double LotSize = 0.01; +input int TrailStart = 0; +input int TrailStep = 0; +input int MaxPosPerSide = 3; +input bool DebugPrint = true; +input int Magic = 2024002; +input int Slippage = 30; + +int hMA[8]; +int bars[8]; +datetime lastBar = 0; +bool buyFired = false; +bool sellFired = false; + +//+------------------------------------------------------------------+ +int TimeToBars(int d) + { + datetime n = TimeCurrent(); + if(n==0) + { + long s = (long)d*86400L, p = PeriodSeconds((ENUM_TIMEFRAMES)_Period); + return (int)MathMax(1,s/p); + } + return MathMax(1,Bars(_Symbol,_Period,n-d*86400,n)); + } + +//+------------------------------------------------------------------+ +int OnInit() + { + int days[8] = {365,182,121,30,14,7,3,1}; + for(int i=0;i<8;i++) + { + bars[i] = (i<7) ? TimeToBars(days[i]) : 1; + hMA[i] = iMA(_Symbol,_Period,bars[i],0,MODE_SMA,PRICE_CLOSE); + if(hMA[i]==INVALID_HANDLE) return INIT_FAILED; + } + if(DebugPrint) Print("=== PaPP Median EA v2.00 INIT ==="); + Print(" Lot=",LotSize," Trail=",TrailStart,"/",TrailStep," MaxPos=",MaxPosPerSide); + for(int i=0;i<8;i++) Print(" MA[",i,"] bars=",bars[i]); + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + for(int i=0;i<8;i++) if(hMA[i]!=INVALID_HANDLE) IndicatorRelease(hMA[i]); + } + +//+------------------------------------------------------------------+ +double GetMA(int idx,int m) + { + double buf[1]; + if(CopyBuffer(hMA[m],0,idx+1,1,buf)==1) return buf[0]; + return 0; + } + +//+------------------------------------------------------------------+ +int CountPos(int type) + { + int n=0; + for(int i=PositionsTotal()-1; i>=0; i--) + { + ulong t=PositionGetTicket(i); + if(t>0 && PositionSelectByTicket(t)) + if(PositionGetInteger(POSITION_MAGIC)==Magic && PositionGetString(POSITION_SYMBOL)==_Symbol) + if(PositionGetInteger(POSITION_TYPE)==type) n++; + } + return n; + } + +//+------------------------------------------------------------------+ +void TrailAll() + { + if(TrailStart<=0 || TrailStep<=0) return; + double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT); + double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); + double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); + CTrade trade; + for(int i=PositionsTotal()-1; i>=0; i--) + { + ulong t=PositionGetTicket(i); + if(t<=0 || !PositionSelectByTicket(t)) continue; + if(PositionGetInteger(POSITION_MAGIC)!=Magic || PositionGetString(POSITION_SYMBOL)!=_Symbol) continue; + + bool isBuy = (PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY); + double entry = PositionGetDouble(POSITION_PRICE_OPEN); + double currSL = PositionGetDouble(POSITION_SL); + double currTP = PositionGetDouble(POSITION_TP); + double newSL=0; + + if(isBuy && bid >= entry+TrailStart*point) + { + newSL = bid - TrailStep*point; + if(newSL > currSL+point && trade.PositionModify(t,newSL,currTP)) + if(DebugPrint) Print(">>> TRAIL BUY t",t," SL->",DoubleToString(newSL,_Digits)); + } + if(!isBuy && ask <= entry-TrailStart*point) + { + newSL = ask + TrailStep*point; + if(currSL==0 || newSL < currSL-point) + { + if(trade.PositionModify(t,newSL,currTP)) + if(DebugPrint) Print(">>> TRAIL SELL t",t," SL->",DoubleToString(newSL,_Digits)); + } + } + } + } + +//+------------------------------------------------------------------+ +void OnTick() + { + TrailAll(); + + datetime curBar = iTime(_Symbol,_Period,0); + if(curBar==lastBar) return; + lastBar = curBar; + + double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); + double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); + double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT); + + //--- Leggi le 8 MA e calcola mediana + double mv[8]; int valid=0; double sum=0; + for(int m=0;m<8;m++) { mv[m]=GetMA(0,m); if(mv[m]>0) { sum+=mv[m]; valid++; } } + if(valid==0 || sum<=0) { if(DebugPrint) Print("SKIP: valid=",valid); return; } + + double median = sum/valid; + + //--- Dispersione MA = banda strutturale del cluster + int dc=0; double ds=0; + for(int m=0;m<8;m++) + if(mv[m]>0) { ds+=MathAbs(mv[m]-median)/median*100; dc++; } + double vol = (dc>0) ? ds/dc : 0; + + double sellBand = median*(1+vol/100); + double buyBand = median*(1-vol/100); + double distPct = (bid-median)/median*100; + + int nBuy = CountPos(POSITION_TYPE_BUY); + int nSell = CountPos(POSITION_TYPE_SELL); + + //--- Reset escursion flags quando price rientra nel cluster + if(bid>=buyBand && bid<=sellBand) { buyFired=false; sellFired=false; } + + //--- LOG + if(DebugPrint) + { + Print(""); + Print("=== BAR: ",TimeToString(curBar)," ==="); + Print("Bid=",DoubleToString(bid,_Digits)," Median=",DoubleToString(median,_Digits)); + Print("Dist=",DoubleToString(distPct,3),"% DispMA=",DoubleToString(vol,4),"%"); + Print("Band: [",DoubleToString(buyBand,_Digits)," <- ",DoubleToString(sellBand,_Digits)," ] Pos: ",nBuy,"B ",nSell,"S"); + for(int m=0;m<8;m++) + if(mv[m]>0) + Print(" MA",m,"=",DoubleToString(mv[m],_Digits), + " diff=",DoubleToString(bid-mv[m],_Digits)); + Print("---"); + } + + //--- Entry: fuori dal cluster, TP=mediana, SL simmetrico + if(bid < buyBand && !buyFired && nBuy < MaxPosPerSide) + { + double entry = ask; + double tpDist = MathAbs(median-entry); + double sl = NormalizeDouble(entry-tpDist,_Digits); + double tp = NormalizeDouble(median,_Digits); + if(sl>=point) + { + if(DebugPrint) Print(">>> BUY: bid=",DoubleToString(bid,_Digits), + " < buyBand=",DoubleToString(buyBand,_Digits), + " TP=",DoubleToString(tp,_Digits)," SL=",DoubleToString(sl,_Digits), + " (",DoubleToString(tpDist/point,0),"pts)"); + MqlTradeRequest req={}; MqlTradeResult res={}; + req.action = TRADE_ACTION_DEAL; req.symbol = _Symbol; + req.volume = LotSize; req.type = ORDER_TYPE_BUY; req.price = entry; + req.sl = sl; req.tp = tp; req.deviation = Slippage; req.magic = Magic; + req.comment = "Pv2B "+DoubleToString(distPct,1)+"%"; + if(OrderSend(req,res) && res.retcode==TRADE_RETCODE_DONE) + { if(DebugPrint) Print(">>> BUY OPENED t",res.order); buyFired=true; } + else if(DebugPrint) Print("BUY fail: c",res.retcode); + } + else if(DebugPrint) Print("BUY SKIP: tpDist too small (",DoubleToString(tpDist/point,0),"pts)"); + } + + if(bid > sellBand && !sellFired && nSell < MaxPosPerSide) + { + double entry = bid; + double tpDist = MathAbs(entry-median); + double sl = NormalizeDouble(entry+tpDist,_Digits); + double tp = NormalizeDouble(median,_Digits); + if(tpDist>=point) + { + if(DebugPrint) Print(">>> SELL: bid=",DoubleToString(bid,_Digits), + " > sellBand=",DoubleToString(sellBand,_Digits), + " TP=",DoubleToString(tp,_Digits)," SL=",DoubleToString(sl,_Digits), + " (",DoubleToString(tpDist/point,0),"pts)"); + MqlTradeRequest req={}; MqlTradeResult res={}; + req.action = TRADE_ACTION_DEAL; req.symbol = _Symbol; + req.volume = LotSize; req.type = ORDER_TYPE_SELL; req.price = entry; + req.sl = sl; req.tp = tp; req.deviation = Slippage; req.magic = Magic; + req.comment = "Pv2S "+DoubleToString(distPct,1)+"%"; + if(OrderSend(req,res) && res.retcode==TRADE_RETCODE_DONE) + { if(DebugPrint) Print(">>> SELL OPENED t",res.order); sellFired=true; } + else if(DebugPrint) Print("SELL fail: c",res.retcode); + } + else if(DebugPrint) Print("SELL SKIP: tpDist too small (",DoubleToString(tpDist/point,0),"pts)"); + } + + if(DebugPrint) + { + if(bid>=buyBand && bid<=sellBand) + Print("NO ENTRY: inside cluster [",DoubleToString(buyBand,_Digits), + " - ",DoubleToString(sellBand,_Digits),"]"); + else if(bid=MaxPosPerSide) + Print("BUY BLOCKED: max pos (",nBuy,"/",MaxPosPerSide,") bid=",DoubleToString(bid,_Digits), + " < buyBand=",DoubleToString(buyBand,_Digits)); + else if(bid>sellBand && nSell>=MaxPosPerSide) + Print("SELL BLOCKED: max pos (",nSell,"/",MaxPosPerSide,") bid=",DoubleToString(bid,_Digits), + " > sellBand=",DoubleToString(sellBand,_Digits)); + } + } +//+------------------------------------------------------------------+